diff --git a/packages/core/src/shared/utilities/collectionUtils.ts b/packages/core/src/shared/utilities/collectionUtils.ts index f723e0096cc..9f9fe9875b9 100644 --- a/packages/core/src/shared/utilities/collectionUtils.ts +++ b/packages/core/src/shared/utilities/collectionUtils.ts @@ -566,3 +566,30 @@ export function createCollectionFromPages(...pages: T[]): AsyncCollection export function isPresent(value: T | undefined): value is T { return value !== undefined } + +export class CircularBuffer { + private buffer = new Set() + private maxSize: number + + constructor(size: number) { + this.maxSize = size + } + + add(value: number): void { + if (this.buffer.size >= this.maxSize) { + // Set iterates its keys in insertion-order. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set + const firstKey = this.buffer.keys().next().value + this.buffer.delete(firstKey) + } + this.buffer.add(value) + } + + contains(value: number): boolean { + return this.buffer.has(value) + } + + clear(): void { + this.buffer.clear() + } +} diff --git a/packages/core/src/shared/utilities/processUtils.ts b/packages/core/src/shared/utilities/processUtils.ts index 25af4418e1e..31f2ec238f3 100644 --- a/packages/core/src/shared/utilities/processUtils.ts +++ b/packages/core/src/shared/utilities/processUtils.ts @@ -8,6 +8,7 @@ import * as crossSpawn from 'cross-spawn' import * as logger from '../logger/logger' import { Timeout, CancellationError, waitUntil } from './timeoutUtils' import { PollingSet } from './pollingSet' +import { CircularBuffer } from './collectionUtils' export interface RunParameterContext { /** Reports an error parsed from the stdin/stdout streams. */ @@ -73,6 +74,7 @@ export class ChildProcessTracker { cpu: 50, } static readonly logger = logger.getLogger('childProcess') + static readonly loggedPids = new CircularBuffer(1000) #processByPid: Map = new Map() #pids: PollingSet @@ -100,21 +102,28 @@ export class ChildProcessTracker { private async checkProcessUsage(pid: number): Promise { if (!this.#pids.has(pid)) { - ChildProcessTracker.logger.warn(`Missing process with id ${pid}`) + ChildProcessTracker.logOnce(pid, `Missing process with id ${pid}`) return } const stats = this.getUsage(pid) if (stats) { ChildProcessTracker.logger.debug(`Process ${pid} usage: %O`, stats) if (stats.memory > ChildProcessTracker.thresholds.memory) { - ChildProcessTracker.logger.warn(`Process ${pid} exceeded memory threshold: ${stats.memory}`) + ChildProcessTracker.logOnce(pid, `Process ${pid} exceeded memory threshold: ${stats.memory}`) } if (stats.cpu > ChildProcessTracker.thresholds.cpu) { - ChildProcessTracker.logger.warn(`Process ${pid} exceeded cpu threshold: ${stats.cpu}`) + ChildProcessTracker.logOnce(pid, `Process ${pid} exceeded cpu threshold: ${stats.cpu}`) } } } + public static logOnce(pid: number, msg: string) { + if (!ChildProcessTracker.loggedPids.contains(pid)) { + ChildProcessTracker.loggedPids.add(pid) + ChildProcessTracker.logger.warn(msg) + } + } + public add(childProcess: ChildProcess) { const pid = childProcess.pid() this.#processByPid.set(pid, childProcess) @@ -147,7 +156,7 @@ export class ChildProcessTracker { // isWin() leads to circular dependency. return process.platform === 'win32' ? getWindowsUsage() : getUnixUsage() } catch (e) { - ChildProcessTracker.logger.warn(`Failed to get process stats for ${pid}: ${e}`) + ChildProcessTracker.logOnce(pid, `Failed to get process stats for ${pid}: ${e}`) return { cpu: 0, memory: 0 } } diff --git a/packages/core/src/test/shared/utilities/processUtils.test.ts b/packages/core/src/test/shared/utilities/processUtils.test.ts index 0e6f474ed10..436ac48ecc4 100644 --- a/packages/core/src/test/shared/utilities/processUtils.test.ts +++ b/packages/core/src/test/shared/utilities/processUtils.test.ts @@ -393,6 +393,10 @@ describe('ChildProcessTracker', function () { usageMock = sinon.stub(ChildProcessTracker.prototype, 'getUsage') }) + beforeEach(function () { + ChildProcessTracker.loggedPids.clear() + }) + afterEach(function () { tracker.clear() usageMock.reset() @@ -463,6 +467,7 @@ describe('ChildProcessTracker', function () { await clock.tickAsync(ChildProcessTracker.pollingInterval) assertLogsContain('exceeded cpu threshold', false, 'warn') + ChildProcessTracker.loggedPids.clear() usageMock.returns(highMemory) await clock.tickAsync(ChildProcessTracker.pollingInterval) assertLogsContain('exceeded memory threshold', false, 'warn')