From 64f9d9f973a9c5e4e7e67be11610a6844112e508 Mon Sep 17 00:00:00 2001
From: Peter Schuster
Date: Wed, 22 Jul 2026 20:55:52 +0200
Subject: [PATCH] fix: replace deprecated wmic process stats with CIM query
wmic is removed on current Windows 11 builds, so process usage polling
in ChildProcessTracker silently failed. Replace the two blocking
execFileSync('wmic', ...) calls with a single async PowerShell
Get-CimInstance query; Win32_PerfFormattedData_PerfProc_Process
provides both cpu percentage and working set, and ConvertTo-Json keeps
the output locale-independent.
Also fixes memory usage being overstated 1024x: WorkingSetSize is
reported in bytes, not KB, so the 100 MB warning threshold effectively
fired at ~100 KB for every tracked process.
---
.../src/util/processUtils.test.ts | 29 +++++++-
core/aws-lsp-core/src/util/processUtils.ts | 66 ++++++++++++-------
2 files changed, 69 insertions(+), 26 deletions(-)
diff --git a/core/aws-lsp-core/src/util/processUtils.test.ts b/core/aws-lsp-core/src/util/processUtils.test.ts
index c358cd6db1..45b9c321a7 100644
--- a/core/aws-lsp-core/src/util/processUtils.test.ts
+++ b/core/aws-lsp-core/src/util/processUtils.test.ts
@@ -4,7 +4,7 @@ import * as os from 'os'
import * as path from 'path'
import * as sinon from 'sinon'
import { chmod } from 'fs/promises'
-import { ChildProcess, ChildProcessResult, ChildProcessTracker, eof } from './processUtils'
+import { ChildProcess, ChildProcessResult, ChildProcessTracker, eof, parseCimProcessStats } from './processUtils'
import { waitUntil } from './timeoutUtils'
import { TestFolder } from '../test/testFolder'
import { TestFeatures } from '@aws/language-server-runtimes/testing'
@@ -395,6 +395,33 @@ describe('ChildProcessTracker', function () {
})
})
+describe('parseCimProcessStats', function () {
+ it('parses a single-object JSON result', function () {
+ const output = '{\r\n "PercentProcessorTime": 42,\r\n "WorkingSet": 157286400\r\n}\r\n'
+ assert.deepStrictEqual(parseCimProcessStats(output), { cpu: 42, memory: 157286400 })
+ })
+
+ it('uses the first entry of an array result', function () {
+ const output =
+ '[{"PercentProcessorTime": 7, "WorkingSet": 1048576}, {"PercentProcessorTime": 99, "WorkingSet": 2097152}]'
+ assert.deepStrictEqual(parseCimProcessStats(output), { cpu: 7, memory: 1048576 })
+ })
+
+ it('returns zero usage for empty output (process already exited)', function () {
+ assert.deepStrictEqual(parseCimProcessStats(''), { cpu: 0, memory: 0 })
+ assert.deepStrictEqual(parseCimProcessStats('\r\n'), { cpu: 0, memory: 0 })
+ })
+
+ it('returns zero usage when properties are missing', function () {
+ assert.deepStrictEqual(parseCimProcessStats('{}'), { cpu: 0, memory: 0 })
+ assert.deepStrictEqual(parseCimProcessStats('null'), { cpu: 0, memory: 0 })
+ })
+
+ it('throws on malformed output so the caller can log it', function () {
+ assert.throws(() => parseCimProcessStats('not json'))
+ })
+})
+
async function startTestProcess(
tempFolder: TestFolder,
logger: Features['logging'],
diff --git a/core/aws-lsp-core/src/util/processUtils.ts b/core/aws-lsp-core/src/util/processUtils.ts
index eca985d548..b0edb7f08d 100644
--- a/core/aws-lsp-core/src/util/processUtils.ts
+++ b/core/aws-lsp-core/src/util/processUtils.ts
@@ -76,6 +76,26 @@ export interface ProcessStats {
memory: number
cpu: number
}
+
+/**
+ * Parses the JSON emitted by the `Get-CimInstance ... | ConvertTo-Json` process-stats query.
+ * Empty output (process already exited) yields zero usage. Exported for testing.
+ */
+export function parseCimProcessStats(output: string): ProcessStats {
+ const trimmed = output.trim()
+ if (!trimmed) {
+ return { cpu: 0, memory: 0 }
+ }
+ const parsed = JSON.parse(trimmed)
+ // ConvertTo-Json emits a bare object for a single match and an array for multiple.
+ const stats = Array.isArray(parsed) ? parsed[0] : parsed
+ const cpu = Number(stats?.PercentProcessorTime)
+ const memory = Number(stats?.WorkingSet)
+ return {
+ cpu: isNaN(cpu) ? 0 : cpu,
+ memory: isNaN(memory) ? 0 : memory,
+ }
+}
export class ChildProcessTracker {
static #instance: ChildProcessTracker
static readonly pollingInterval: number = 10000 // Check usage every 10 seconds
@@ -120,7 +140,7 @@ export class ChildProcessTracker {
this.logging.warn(`Missing process with id ${pid}`)
return
}
- const stats = this.getUsage(pid)
+ const stats = await this.getUsage(pid)
if (stats) {
this.logIfExceeds(pid, stats)
}
@@ -162,36 +182,32 @@ export class ChildProcessTracker {
this.#processByPid.clear()
}
- private getUsage(pid: number): ProcessStats {
+ private async getUsage(pid: number): Promise {
try {
- return process.platform === 'win32' ? getWindowsUsage() : getUnixUsage()
+ return process.platform === 'win32' ? await getWindowsUsage() : getUnixUsage()
} catch (e) {
this.logging.warn(`Failed to get process stats for ${pid}: ${e}`)
return { cpu: 0, memory: 0 }
}
- function getWindowsUsage() {
- const cpuOutput = proc
- .execFileSync('wmic', [
- 'path',
- 'Win32_PerfFormattedData_PerfProc_Process',
- 'where',
- `IDProcess=${pid}`,
- 'get',
- 'PercentProcessorTime',
- ])
- .toString()
- const memOutput = proc
- .execFileSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'WorkingSetSize'])
- .toString()
-
- const cpuPercentage = parseFloat(cpuOutput.split('\n')[1])
- const memoryBytes = parseInt(memOutput.split('\n')[1]) * 1024
-
- return {
- cpu: isNaN(cpuPercentage) ? 0 : cpuPercentage,
- memory: memoryBytes,
- }
+ // wmic was removed from current Windows 11 builds; query CIM through PowerShell instead.
+ // A single Win32_PerfFormattedData query provides both cpu percentage and working set,
+ // and ConvertTo-Json keeps the output locale-independent.
+ async function getWindowsUsage(): Promise {
+ const output = await new Promise((resolve, reject) => {
+ proc.execFile(
+ 'powershell.exe',
+ [
+ '-NoProfile',
+ '-NonInteractive',
+ '-Command',
+ `Get-CimInstance -ClassName Win32_PerfFormattedData_PerfProc_Process -Filter 'IDProcess=${pid}' | Select-Object PercentProcessorTime,WorkingSet | ConvertTo-Json`,
+ ],
+ { windowsHide: true },
+ (error, stdout) => (error ? reject(error) : resolve(stdout))
+ )
+ })
+ return parseCimProcessStats(output)
}
function getUnixUsage() {