Skip to content

Commit 4c0d098

Browse files
authored
fix(childprocess): noisy "memory threshold" logs aws#7017
## Problem "memory threshold" message logged multiple times for a the same PID: 2025-04-11 ... Process 23662 exceeded memory threshold: 506986496 2025-04-11 ... Process 23662 exceeded memory threshold: 507019264 2025-04-11 ... Process 23662 exceeded memory threshold: 507052032 2025-04-11 ... Process 23662 exceeded memory threshold: 507084800 This is noisy in the logs. ## Solution Only log "memory threshold" once per PID.
1 parent f1dded7 commit 4c0d098

File tree

3 files changed

+45
-4
lines changed

3 files changed

+45
-4
lines changed

packages/core/src/shared/utilities/collectionUtils.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,3 +566,30 @@ export function createCollectionFromPages<T>(...pages: T[]): AsyncCollection<T>
566566
export function isPresent<T>(value: T | undefined): value is T {
567567
return value !== undefined
568568
}
569+
570+
export class CircularBuffer {
571+
private buffer = new Set<number>()
572+
private maxSize: number
573+
574+
constructor(size: number) {
575+
this.maxSize = size
576+
}
577+
578+
add(value: number): void {
579+
if (this.buffer.size >= this.maxSize) {
580+
// Set iterates its keys in insertion-order.
581+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
582+
const firstKey = this.buffer.keys().next().value
583+
this.buffer.delete(firstKey)
584+
}
585+
this.buffer.add(value)
586+
}
587+
588+
contains(value: number): boolean {
589+
return this.buffer.has(value)
590+
}
591+
592+
clear(): void {
593+
this.buffer.clear()
594+
}
595+
}

packages/core/src/shared/utilities/processUtils.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as crossSpawn from 'cross-spawn'
88
import * as logger from '../logger/logger'
99
import { Timeout, CancellationError, waitUntil } from './timeoutUtils'
1010
import { PollingSet } from './pollingSet'
11+
import { CircularBuffer } from './collectionUtils'
1112

1213
export interface RunParameterContext {
1314
/** Reports an error parsed from the stdin/stdout streams. */
@@ -73,6 +74,7 @@ export class ChildProcessTracker {
7374
cpu: 50,
7475
}
7576
static readonly logger = logger.getLogger('childProcess')
77+
static readonly loggedPids = new CircularBuffer(1000)
7678
#processByPid: Map<number, ChildProcess> = new Map<number, ChildProcess>()
7779
#pids: PollingSet<number>
7880

@@ -100,21 +102,28 @@ export class ChildProcessTracker {
100102

101103
private async checkProcessUsage(pid: number): Promise<void> {
102104
if (!this.#pids.has(pid)) {
103-
ChildProcessTracker.logger.warn(`Missing process with id ${pid}`)
105+
ChildProcessTracker.logOnce(pid, `Missing process with id ${pid}`)
104106
return
105107
}
106108
const stats = this.getUsage(pid)
107109
if (stats) {
108110
ChildProcessTracker.logger.debug(`Process ${pid} usage: %O`, stats)
109111
if (stats.memory > ChildProcessTracker.thresholds.memory) {
110-
ChildProcessTracker.logger.warn(`Process ${pid} exceeded memory threshold: ${stats.memory}`)
112+
ChildProcessTracker.logOnce(pid, `Process ${pid} exceeded memory threshold: ${stats.memory}`)
111113
}
112114
if (stats.cpu > ChildProcessTracker.thresholds.cpu) {
113-
ChildProcessTracker.logger.warn(`Process ${pid} exceeded cpu threshold: ${stats.cpu}`)
115+
ChildProcessTracker.logOnce(pid, `Process ${pid} exceeded cpu threshold: ${stats.cpu}`)
114116
}
115117
}
116118
}
117119

120+
public static logOnce(pid: number, msg: string) {
121+
if (!ChildProcessTracker.loggedPids.contains(pid)) {
122+
ChildProcessTracker.loggedPids.add(pid)
123+
ChildProcessTracker.logger.warn(msg)
124+
}
125+
}
126+
118127
public add(childProcess: ChildProcess) {
119128
const pid = childProcess.pid()
120129
this.#processByPid.set(pid, childProcess)
@@ -147,7 +156,7 @@ export class ChildProcessTracker {
147156
// isWin() leads to circular dependency.
148157
return process.platform === 'win32' ? getWindowsUsage() : getUnixUsage()
149158
} catch (e) {
150-
ChildProcessTracker.logger.warn(`Failed to get process stats for ${pid}: ${e}`)
159+
ChildProcessTracker.logOnce(pid, `Failed to get process stats for ${pid}: ${e}`)
151160
return { cpu: 0, memory: 0 }
152161
}
153162

packages/core/src/test/shared/utilities/processUtils.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,10 @@ describe('ChildProcessTracker', function () {
393393
usageMock = sinon.stub(ChildProcessTracker.prototype, 'getUsage')
394394
})
395395

396+
beforeEach(function () {
397+
ChildProcessTracker.loggedPids.clear()
398+
})
399+
396400
afterEach(function () {
397401
tracker.clear()
398402
usageMock.reset()
@@ -463,6 +467,7 @@ describe('ChildProcessTracker', function () {
463467
await clock.tickAsync(ChildProcessTracker.pollingInterval)
464468
assertLogsContain('exceeded cpu threshold', false, 'warn')
465469

470+
ChildProcessTracker.loggedPids.clear()
466471
usageMock.returns(highMemory)
467472
await clock.tickAsync(ChildProcessTracker.pollingInterval)
468473
assertLogsContain('exceeded memory threshold', false, 'warn')

0 commit comments

Comments
 (0)