Release v0.6.36: offload notification registration and mitigate Viber paste timing
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
param([ValidateRange(1,20)][int]$Runs=3,[ValidateRange(100,10000)][int]$SettleMilliseconds=1500)
|
||||
# Private resident pages are counted externally, so the measurement buffer is
|
||||
# allocated in PowerShell, not in the measured process. Never trim working sets.
|
||||
$ErrorActionPreference='Stop'
|
||||
$root=Split-Path $PSScriptRoot -Parent
|
||||
$output=Join-Path $root 'build/memory-components'
|
||||
New-Item -ItemType Directory -Force $output | Out-Null
|
||||
$exe=Join-Path $output 'probe.exe'
|
||||
& "$root/.tools/w64devkit/bin/g++.exe" -std=c++20 -Os -static -s -DUNICODE -D_UNICODE -D_WIN32_WINNT=0x0A00 "$PSScriptRoot/memory-components.cpp" -o $exe -lole32 -loleaut32 -lshell32 -lpropsys -lshlwapi -luuid -luiautomationcore
|
||||
if($LASTEXITCODE) { throw 'Probe compilation failed' }
|
||||
Add-Type @'
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
public static class ComponentWorkingSet {
|
||||
[DllImport("psapi.dll", SetLastError=true)] static extern bool QueryWorkingSet(IntPtr process,IntPtr buffer,int size);
|
||||
public static long PrivateBytes(IntPtr process) {
|
||||
int size=4*1024*1024;
|
||||
IntPtr buffer=Marshal.AllocHGlobal(size);
|
||||
try {
|
||||
if(!QueryWorkingSet(process,buffer,size)) throw new System.ComponentModel.Win32Exception();
|
||||
long count=Marshal.ReadIntPtr(buffer).ToInt64(), pages=0;
|
||||
if(count>(size/IntPtr.Size)-1) throw new InvalidOperationException("Working set buffer is too small");
|
||||
for(int i=0;i<count;i++) if((Marshal.ReadIntPtr(buffer,(i+1)*IntPtr.Size).ToInt64() & 256)==0) pages++;
|
||||
return pages*Environment.SystemPageSize;
|
||||
} finally { Marshal.FreeHGlobal(buffer); }
|
||||
}
|
||||
}
|
||||
'@
|
||||
$rows=[Collections.Generic.List[object]]::new()
|
||||
foreach($run in 1..$Runs) {
|
||||
foreach($mode in @('baseline','sta','mta','shell','uia','focused','combined')) {
|
||||
$start=[Diagnostics.ProcessStartInfo]::new($exe,$mode)
|
||||
$start.UseShellExecute=$false; $start.CreateNoWindow=$true
|
||||
$start.RedirectStandardOutput=$true; $start.RedirectStandardInput=$true; $start.RedirectStandardError=$true
|
||||
$process=[Diagnostics.Process]::Start($start)
|
||||
$errors=$process.StandardError.ReadToEndAsync()
|
||||
try {
|
||||
foreach($expected in @('baseline','com','active','released','uninitialized')) {
|
||||
$pending=$process.StandardOutput.ReadLineAsync()
|
||||
if(!$pending.Wait(15000)) { throw "Timeout: $mode/$expected" }
|
||||
$phase=$pending.Result
|
||||
if($phase -ne $expected) { throw "Unexpected phase '$phase', expected $expected" }
|
||||
Start-Sleep -Milliseconds $SettleMilliseconds
|
||||
$process.Refresh()
|
||||
$rows.Add([pscustomobject]@{Run=$run;Mode=$mode;Phase=$phase;PrivateBytes=$process.PrivateMemorySize64;PrivateWorkingSet=[ComponentWorkingSet]::PrivateBytes($process.Handle);WorkingSet=$process.WorkingSet64;Threads=$process.Threads.Count;Modules=@($process.Modules | ForEach-Object ModuleName)})
|
||||
$process.StandardInput.Write("`n"); $process.StandardInput.Flush()
|
||||
}
|
||||
if(!$process.WaitForExit(15000)) { throw 'Exit timeout' }
|
||||
if($process.ExitCode) { throw "Probe failed: $($errors.Result)" }
|
||||
$errors.Result | Set-Content (Join-Path $output "$run-$mode.txt")
|
||||
} finally {
|
||||
if(!$process.HasExited) { $process.Kill(); $process.WaitForExit() }
|
||||
$process.Dispose()
|
||||
$rows | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $output 'results.json')
|
||||
}
|
||||
Write-Host "Completed $run/$Runs $mode"
|
||||
}
|
||||
}
|
||||
$rows | Group-Object Mode,Phase | ForEach-Object {
|
||||
[pscustomobject]@{Case=$_.Name;PrivateMiB=[math]::Round(($_.Group.PrivateBytes | Measure-Object -Average).Average/1MB,3);PrivateWorkingSetMiB=[math]::Round(($_.Group.PrivateWorkingSet | Measure-Object -Average).Average/1MB,3)}
|
||||
} | Format-Table
|
||||
@@ -0,0 +1,65 @@
|
||||
// Standalone attribution probe. No clipboard, registry or user input mutations.
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <propkey.h>
|
||||
#include <propvarutil.h>
|
||||
#include <uiautomation.h>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
|
||||
static void check(HRESULT hr) {
|
||||
if (FAILED(hr)) { fprintf(stderr,"HRESULT=%08lx\n",(unsigned long)hr); exit(2); }
|
||||
}
|
||||
static void phase(const char *name) {
|
||||
printf("%s\n",name); fflush(stdout);
|
||||
if (getchar()==EOF) exit(3);
|
||||
}
|
||||
int main(int argc,char **argv) {
|
||||
const char *mode=argc>1?argv[1]:"baseline";
|
||||
const bool baseline=!strcmp(mode,"baseline");
|
||||
const bool shell=!strcmp(mode,"shell") || !strcmp(mode,"combined");
|
||||
const bool uia=!strcmp(mode,"uia") || !strcmp(mode,"focused") || !strcmp(mode,"combined");
|
||||
phase("baseline");
|
||||
if(!baseline) check(CoInitializeEx(nullptr,!strcmp(mode,"sta") || shell ? COINIT_APARTMENTTHREADED : COINIT_MULTITHREADED));
|
||||
phase("com");
|
||||
IShellLinkW *link{}; IPropertyStore *store{}; IPersistFile *file{};
|
||||
IUIAutomation2 *automation{}; IUIAutomationElement *element{}; IUIAutomationTextPattern *pattern{};
|
||||
wchar_t temporary[MAX_PATH]{},shortcut[MAX_PATH]{},exe[MAX_PATH]{};
|
||||
if(shell) {
|
||||
check(CoCreateInstance(CLSID_ShellLink,nullptr,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&link)));
|
||||
GetModuleFileNameW(nullptr,exe,MAX_PATH);
|
||||
check(link->SetPath(exe)); check(link->SetIconLocation(exe,0));
|
||||
check(link->QueryInterface(IID_PPV_ARGS(&store)));
|
||||
PROPVARIANT value{}; check(InitPropVariantFromString(L"KSwitcher.MemoryProbe",&value));
|
||||
check(store->SetValue(PKEY_AppUserModel_ID,value)); PropVariantClear(&value);
|
||||
GUID id{0xe3f78047,0xf0ad,0x49b6,{0x90,0x74,0x41,0x19,0x31,0x70,0x61,0x67}};
|
||||
value.vt=VT_CLSID; value.puuid=&id;
|
||||
check(store->SetValue(PKEY_AppUserModel_ToastActivatorCLSID,value)); check(store->Commit());
|
||||
PWSTR programs{}; check(SHGetKnownFolderPath(FOLDERID_Programs,0,nullptr,&programs)); CoTaskMemFree(programs);
|
||||
GetTempPathW(MAX_PATH,temporary); if(!GetTempFileNameW(temporary,L"ksm",0,shortcut)) return 4;
|
||||
check(link->QueryInterface(IID_PPV_ARGS(&file))); check(file->Save(shortcut,TRUE));
|
||||
}
|
||||
if(uia) {
|
||||
check(CoCreateInstance(CLSID_CUIAutomation8,nullptr,CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&automation)));
|
||||
check(automation->put_ConnectionTimeout(200)); check(automation->put_TransactionTimeout(200));
|
||||
if(strcmp(mode,"uia")) {
|
||||
HRESULT focused=automation->GetFocusedElement(&element);
|
||||
fprintf(stderr,"focus=%08lx\n",(unsigned long)focused);
|
||||
if(SUCCEEDED(focused) && element) {
|
||||
BOOL password=TRUE,enabled=FALSE; CONTROLTYPEID type{};
|
||||
element->get_CurrentIsPassword(&password); element->get_CurrentIsEnabled(&enabled); element->get_CurrentControlType(&type);
|
||||
fprintf(stderr,"type=%d enabled=%d protected=%d\n",type,enabled,password);
|
||||
if(!password) fprintf(stderr,"pattern=%08lx\n",(unsigned long)element->GetCurrentPatternAs(UIA_TextPatternId,IID_PPV_ARGS(&pattern)));
|
||||
}
|
||||
}
|
||||
}
|
||||
phase("active");
|
||||
if(pattern) pattern->Release(); if(element) element->Release(); if(automation) automation->Release();
|
||||
if(file) file->Release(); if(store) store->Release(); if(link) link->Release();
|
||||
if(shortcut[0]) DeleteFileW(shortcut);
|
||||
phase("released");
|
||||
if(!baseline) { CoFreeUnusedLibrariesEx(0,0); CoUninitialize(); }
|
||||
phase("uninitialized");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user