Top resident processes on a 32GB Surface Pro 11: why WS was the wrong metric
In this article
On 2026-07-16, the Surface Pro 11 had only 2.53GB free out of 31.57GB of physical memory. This is a note about not hunting for a culprit from free capacity alone.
I had assumed that 32GB would leave plenty of room for everyday work. With Edge, Teams, OneDrive, Clawpilot, Docker Desktop, Google Drive, and Notion open on Windows 11 Pro, free memory became thin. There were 149 running services and 18 startup registrations. Those numbers made resident applications look like the obvious culprit, but the simple approach I tried did not hold up. The problem was trying to explain the total with a sum of working sets.
The biggest mistake was choosing what to look at. I expected sorting Get-Process by WS to show the heavy residents, but that metric alone could not explain why only 2.53GB remained free. I continued reading the table incorrectly until I looked back at the measurement.
I started by sorting on WS
I collected five types of data: Get-Process working set, the number of running services, startup registrations, physical memory, and CPU counters. The machine was a Microsoft Surface Pro, 11th Edition, with a Snapdragon X 12-core X1E80100 @ 3.40GHz. The OS was Windows 11 Pro 10.0.26200 ARM64.
Get-Process |
Sort-Object WorkingSet64 -Descending |
Select-Object Name, Id, @{n="WS_MB";e={[math]::Round($_.WorkingSet64 / 1MB, 1)}}, CPU, Threads, Path
For memory, I used TotalVisibleMemorySize and FreePhysicalMemory from Win32_OperatingSystem. Those values are in KB, so I divided by 1024 twice to convert to GiB. For CPU, I collected five samples of \Processor Information(_Total)\% Processor Performance, \% Processor Utility, and \Processor Frequency.
At that point I assumed that adding WS_MB would approximate how much memory the processes used. In hindsight, that was the entry point for the failure.
The table covered only the range left in the log
The physical-memory values were TotalVisibleMemorySize 33,099,544KB and FreePhysicalMemory 2,650,072KB. In GiB, that is 2.53GB free out of 31.57GB, leaving 29.04GB in use by subtraction.
The startup registrations included ClawpilotTokenDashboard, PC Temp Monitor, Skill Control Center, Ueli, Teams, OneDrive, electron.app.Notion, Microsoft.Lists, electron.app.Clawpilot, GoogleDriveFS, Docker Desktop, SecurityHealth, iTunesHelper, and GlobalSecureAccessClient. Some names look heavy, but a registration list cannot determine their actual memory use.
The process listing was in procs.stdout in JSON. There is a major limitation: stdout was truncated at 20,000 characters, and the first readable entries in the log already started around WS_MB 8.1MB. The following is therefore the first 15 entries left in the file, not the exact top 15 processes on the machine.
| Name | PID | WS_MB | Threads | Path |
|---|---|---|---|---|
| cmd | 33500 | 8.1 | 2 | C:\Windows\system32\cmd.exe |
| cmd | 33816 | 8.1 | 2 | C:\Windows\system32\cmd.exe |
| cmd | 29920 | 8.1 | 2 | C:\Windows\system32\cmd.exe |
| SenseDlpProcessor | 2648 | 8.1 | 5 | |
| msedge | 15664 | 8.1 | 24 | C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe |
| cmd | 28048 | 8.1 | 2 | C:\Windows\system32\cmd.exe |
| cowork-svc | 8652 | 8.1 | 14 | |
| msedgewebview2 | 21272 | 8.0 | 9 | C:\Program Files (x86)\Microsoft\EdgeWebView\Application\151.0.4129.59\msedgewebview2.exe |
| msedgewebview2 | 7852 | 7.8 | 16 | C:\Program Files (x86)\Microsoft\EdgeWebView\Application\151.0.4129.59\msedgewebview2.exe |
| git | 39248 | 7.8 | 12 | C:\Users\akirasakai\AppData\Local\github-copilot-git-2.53.0-3\clangarm64\bin\git.exe |
| svchost | 4968 | 7.7 | 5 | |
| cmd | 53996 | 7.7 | 2 | C:\Windows\System32\cmd.exe |
| cmd | 35660 | 7.6 | 2 | C:\Windows\system32\cmd.exe |
| svchost | 11844 | 7.6 | 3 | |
| conhost | 32104 | 7.5 | 2 | C:\Windows\system32\conhost.exe |
These 15 entries totaled 118.4MB and 102 threads. The 98 processes that remained readable totaled 411.8MB and 1,042 threads. The top five totaled 40.5MB, only 9.8% of the readable range.
The table made cmd and conhost look unusually numerous, and it showed many small processes around Clawpilot and Node.js. But it could not explain a machine with only 2.53GB free.
CPU counters were not readable by name alone
The CPU data was also complicated. The five Processor Frequency samples were 1905MHz, 1367MHz, 1774MHz, 1516MHz, and 1596MHz. The average was 1631.6MHz, which looked about half of the nominal 3.40GHz.
It is reasonable for a mostly idle system to report a low frequency. The Surface Pro 11 was not running permanently at 3.40GHz. This mismatch later helped me question why the process sum did not connect to physical memory. At the same times, % Processor Performance was 73.20%, 74.62%, 68.53%, 63.50%, and 64.99%; % Processor Utility was 55.996%, 63.093%, 41.019%, 31.584%, and 31.524%.
These counters are easy to misread from their names. I initially treated them all as variants of CPU utilization. % Processor Performance can exceed 100%, so it is not simple CPU usage. It did not exceed 100% in this sample, but frequency, performance percentage, and utility were still different signals. Taking one value in isolation would give the wrong picture of load.
The WS sum was broken as a model
The central failure was trying to add working sets. As with the CPU counter names, I trusted the labels too literally. The 98 readable processes added up to only 411.8MB. Physical memory showed 31.57GB total and 2.53GB free, implying 29.04GB in use. The numbers did not connect, so I had to question the method.
There were two reasons. First, procs.stdout was truncated at 20,000 characters, so large processes were missing from the log. Second, even a complete list would double-count shared memory in working sets. A DLL or shared page can appear in multiple processes' WS_MB. I need to repeat this with Private Working Set.
The incomplete log made the sum too small. A complete log could make it too large because of shared-page duplication. Either way, interpreting the sum of WS_MB as physical memory use is close to a bug.
The measurement should include Private Working Set or commit size. At minimum, it is unsafe to prioritize which resident applications to remove using only Get-Process WS. This was an uncomfortable record of mixing missing log data with a metric that has shared-memory duplication.
The next run needs different metrics
I did not immediately decide to remove resident applications. The evidence was too weak. Docker Desktop, GoogleDriveFS, Teams, OneDrive, and Clawpilot may look heavy, but this method cannot make that a decision. The log was incomplete and the meaning of the metric was insufficient.
The next run should collect Get-Process WorkingSet64 together with Private Working Set, Commit Size, process trees, and grouping by process name. Applications such as Edge and msedgewebview2 split into multiple processes, so I want to see them as applications rather than isolated rows. This is a follow-up, not a result from this run.
What I learned on 2026-07-16 was not that the Surface Pro 11's 32GB was definitively insufficient. It was that changing the conclusion is easy when the metric's meaning is misunderstood. Before reducing resident software, choose the measurement correctly. My next step is to collect the same log again with Private Working Set and Commit Size beside it, rather than hunting for a culprit from WS alone.