From 87897f6475b8fa23ba46b45d5da657e9f432c1f7 Mon Sep 17 00:00:00 2001 From: KushalMeghani1644 Date: Mon, 25 Aug 2025 16:06:58 +0530 Subject: [PATCH 1/7] docs(worker): improve JSDoc and strengthen error handling - Added detailed JSDoc comments for better clarity and maintainability - Narrowed to properly handle Error vs non-Error cases - Removed unused field from ILocalTimeInfo --- apps/cpu-profile-summarizer/src/worker.ts | 72 ++++++++++++++--------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/apps/cpu-profile-summarizer/src/worker.ts b/apps/cpu-profile-summarizer/src/worker.ts index 65b84db5a66..1cf9d8de385 100644 --- a/apps/cpu-profile-summarizer/src/worker.ts +++ b/apps/cpu-profile-summarizer/src/worker.ts @@ -7,15 +7,19 @@ import worker_threads from 'node:worker_threads'; import type { ICallFrame, ICpuProfile, INodeSummary, IProfileSummary } from './types'; import type { IMessageToWorker } from './protocol'; +/** + * Tracks the time spent in a local node. + */ interface ILocalTimeInfo { + /** Time spent exclusively in this node (excluding children). */ self: number; - contributors: Set | undefined; } /** * Computes an identifier to use for summarizing call frames. - * @param callFrame - The call frame to compute the ID for - * @returns A portable string identifying the call frame + * + * @param callFrame - The call frame to compute the ID for. + * @returns A portable string uniquely identifying the call frame. */ function computeCallFrameId(callFrame: ICallFrame): string { const { url, lineNumber, columnNumber, functionName } = callFrame; @@ -23,9 +27,10 @@ function computeCallFrameId(callFrame: ICallFrame): string { } /** - * Adds the contents of a .cpuprofile file to a summary. - * @param filePath - The path to the .cpuprofile file to read - * @param accumulator - The summary to add the profile to + * Reads and parses a `.cpuprofile` file from disk, then adds its data to a profile summary. + * + * @param filePath - The path to the `.cpuprofile` file to read. + * @param accumulator - The summary to add the parsed profile data to. */ function addFileToSummary(filePath: string, accumulator: IProfileSummary): void { const profile: ICpuProfile = JSON.parse(fs.readFileSync(filePath, 'utf8')); @@ -33,10 +38,13 @@ function addFileToSummary(filePath: string, accumulator: IProfileSummary): void } /** - * Adds a CPU profile to a summary. - * @param profile - The profile to add - * @param accumulator - The summary to add the profile to - * @returns + * Aggregates CPU profile data into a summary map. + * Handles recursive frames by ensuring totalTime is computed + * via traversal instead of naive summation. + * + * @param profile - The parsed `.cpuprofile` data. + * @param accumulator - A Map keyed by callFrameId with summary info. + * @returns The updated accumulator with the new profile included. */ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary): IProfileSummary { const { nodes, samples, timeDeltas, startTime, endTime }: ICpuProfile = profile; @@ -53,32 +61,31 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) return index; } - for (let i: number = 0; i < nodes.length; i++) { - localTimes.push({ - self: 0, - contributors: undefined - }); + // Initialize local time info for all nodes + for (let i = 0; i < nodes.length; i++) { + localTimes.push({ self: 0 }); const { id } = nodes[i]; // Ensure that the mapping entry has been created. getIndexFromNodeId(id); } + // Distribute time samples across nodes const duration: number = endTime - startTime; let lastNodeTime: number = duration - timeDeltas[0]; - for (let i: number = 0; i < timeDeltas.length - 1; i++) { + for (let i = 0; i < timeDeltas.length - 1; i++) { const sampleDuration: number = timeDeltas[i + 1]; const localTime: ILocalTimeInfo = localTimes[getIndexFromNodeId(samples[i])]; localTime.self += sampleDuration; lastNodeTime -= sampleDuration; } + // Add remaining time to the last sample localTimes[getIndexFromNodeId(samples[samples.length - 1])].self += lastNodeTime; - // Have to pick the maximum totalTime for a given frame, + // Group nodes by frameId const nodesByFrame: Map> = new Map(); - - for (let i: number = 0; i < nodes.length; i++) { + for (let i = 0; i < nodes.length; i++) { const { callFrame } = nodes[i]; const frameId: string = computeCallFrameId(callFrame); @@ -90,11 +97,10 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) } } + // Summarize per-frame data for (const [frameId, contributors] of nodesByFrame) { - // To compute the total time spent in a node, we need to sum the self time of all contributors. - // We can't simply add up total times because a frame can recurse. - let selfTime: number = 0; - let totalTime: number = 0; + let selfTime = 0; + let totalTime = 0; let selfIndex: number | undefined; for (const contributor of contributors) { @@ -107,6 +113,7 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) selfTime += localTime.self; } + // Traverse children to compute total time const queue: Set = new Set(contributors); for (const nodeIndex of queue) { totalTime += localTimes[nodeIndex].self; @@ -134,7 +141,6 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) url, lineNumber, columnNumber, - selfTime, totalTime }); @@ -161,13 +167,21 @@ if (parentPort) { const summary: IProfileSummary = new Map(); addFileToSummary(message, summary); parentPort.postMessage({ file: message, data: summary }); - } catch (error) { - parentPort.postMessage({ - file: message, - data: error.stack || error.message - }); + } catch (error: unknown) { + if (error instanceof Error) { + parentPort.postMessage({ + file: message, + data: error.stack ?? error.message + }); + } else { + parentPort.postMessage({ + file: message, + data: String(error) + }); + } } }; parentPort.on('message', messageHandler); } + From 33a4f45ff0e781e78baf1e4056c01327cee0a1fa Mon Sep 17 00:00:00 2001 From: KushalMeghani1644 Date: Thu, 28 Aug 2025 15:05:36 +0530 Subject: [PATCH 2/7] Add changelog file --- .../cpu-profile-summarizer/fix_2025-08-28-09-31.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@rushstack/cpu-profile-summarizer/fix_2025-08-28-09-31.json diff --git a/common/changes/@rushstack/cpu-profile-summarizer/fix_2025-08-28-09-31.json b/common/changes/@rushstack/cpu-profile-summarizer/fix_2025-08-28-09-31.json new file mode 100644 index 00000000000..ed5e402d76d --- /dev/null +++ b/common/changes/@rushstack/cpu-profile-summarizer/fix_2025-08-28-09-31.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/cpu-profile-summarizer", + "comment": "docs(worker): improve JSDoc and strengthen error handling", + "type": "none" + } + ], + "packageName": "@rushstack/cpu-profile-summarizer" +} \ No newline at end of file From 8b4e72a30dc3e9248f04c65e926559eee47664d3 Mon Sep 17 00:00:00 2001 From: Kushal Meghani <168952248+KushalMeghani1644@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:41:24 +0530 Subject: [PATCH 3/7] Update apps/cpu-profile-summarizer/src/worker.ts Co-authored-by: Ian Clanton-Thuon --- apps/cpu-profile-summarizer/src/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cpu-profile-summarizer/src/worker.ts b/apps/cpu-profile-summarizer/src/worker.ts index 1cf9d8de385..75c999fa948 100644 --- a/apps/cpu-profile-summarizer/src/worker.ts +++ b/apps/cpu-profile-summarizer/src/worker.ts @@ -73,7 +73,7 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) // Distribute time samples across nodes const duration: number = endTime - startTime; let lastNodeTime: number = duration - timeDeltas[0]; - for (let i = 0; i < timeDeltas.length - 1; i++) { + for (let i: number = 0; i < timeDeltas.length - 1; i++) { const sampleDuration: number = timeDeltas[i + 1]; const localTime: ILocalTimeInfo = localTimes[getIndexFromNodeId(samples[i])]; localTime.self += sampleDuration; From 526f017a5e2b2403d51bee136e2a148b70ca490f Mon Sep 17 00:00:00 2001 From: Kushal Meghani <168952248+KushalMeghani1644@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:41:34 +0530 Subject: [PATCH 4/7] Update apps/cpu-profile-summarizer/src/worker.ts Co-authored-by: Ian Clanton-Thuon --- apps/cpu-profile-summarizer/src/worker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/cpu-profile-summarizer/src/worker.ts b/apps/cpu-profile-summarizer/src/worker.ts index 75c999fa948..0b8996dfe63 100644 --- a/apps/cpu-profile-summarizer/src/worker.ts +++ b/apps/cpu-profile-summarizer/src/worker.ts @@ -99,8 +99,8 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) // Summarize per-frame data for (const [frameId, contributors] of nodesByFrame) { - let selfTime = 0; - let totalTime = 0; + let selfTime: number = 0; + let totalTime: number = 0; let selfIndex: number | undefined; for (const contributor of contributors) { From 0410f5bd8d4231eff17e80cb294729fa1e19069c Mon Sep 17 00:00:00 2001 From: Kushal Meghani <168952248+KushalMeghani1644@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:41:43 +0530 Subject: [PATCH 5/7] Update apps/cpu-profile-summarizer/src/worker.ts Co-authored-by: Ian Clanton-Thuon --- apps/cpu-profile-summarizer/src/worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cpu-profile-summarizer/src/worker.ts b/apps/cpu-profile-summarizer/src/worker.ts index 0b8996dfe63..5282d241fa1 100644 --- a/apps/cpu-profile-summarizer/src/worker.ts +++ b/apps/cpu-profile-summarizer/src/worker.ts @@ -85,7 +85,7 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) // Group nodes by frameId const nodesByFrame: Map> = new Map(); - for (let i = 0; i < nodes.length; i++) { + for (let i: number = 0; i < nodes.length; i++) { const { callFrame } = nodes[i]; const frameId: string = computeCallFrameId(callFrame); From a04962f57f63c7aacbbb2d5ee05df7dea73dfcc6 Mon Sep 17 00:00:00 2001 From: Kushal Meghani <168952248+KushalMeghani1644@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:41:57 +0530 Subject: [PATCH 6/7] Update common/changes/@rushstack/cpu-profile-summarizer/fix_2025-08-28-09-31.json Co-authored-by: Ian Clanton-Thuon --- .../@rushstack/cpu-profile-summarizer/fix_2025-08-28-09-31.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/changes/@rushstack/cpu-profile-summarizer/fix_2025-08-28-09-31.json b/common/changes/@rushstack/cpu-profile-summarizer/fix_2025-08-28-09-31.json index ed5e402d76d..c7f4b48cdd0 100644 --- a/common/changes/@rushstack/cpu-profile-summarizer/fix_2025-08-28-09-31.json +++ b/common/changes/@rushstack/cpu-profile-summarizer/fix_2025-08-28-09-31.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@rushstack/cpu-profile-summarizer", - "comment": "docs(worker): improve JSDoc and strengthen error handling", + "comment": "Improve error handling in the worker in cases when the error thrown is not an instance of `Error`.", "type": "none" } ], From a7274656962c19c3fee597ab612cf33d6f8fce5b Mon Sep 17 00:00:00 2001 From: KushalMeghani1644 Date: Mon, 8 Sep 2025 15:09:48 +0530 Subject: [PATCH 7/7] feat(cpu-profile-summarizer) fix TS errors in worker correct imports, init loop consolidated error handling, typed MessafePort alias --- apps/cpu-profile-summarizer/src/worker.ts | 62 ++++++++++++++++------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/apps/cpu-profile-summarizer/src/worker.ts b/apps/cpu-profile-summarizer/src/worker.ts index 65b84db5a66..a3fd926b732 100644 --- a/apps/cpu-profile-summarizer/src/worker.ts +++ b/apps/cpu-profile-summarizer/src/worker.ts @@ -1,21 +1,27 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import fs from 'node:fs'; -import worker_threads from 'node:worker_threads'; +import * as fs from 'fs'; +import { parentPort, type MessagePort } from 'worker_threads'; import type { ICallFrame, ICpuProfile, INodeSummary, IProfileSummary } from './types'; import type { IMessageToWorker } from './protocol'; +/** + * Tracks the time spent in a local node. + */ interface ILocalTimeInfo { + /** Time spent exclusively in this node (excluding children). */ self: number; - contributors: Set | undefined; } /** * Computes an identifier to use for summarizing call frames. * @param callFrame - The call frame to compute the ID for * @returns A portable string identifying the call frame + * + * @param callFrame - The call frame to compute the ID for. + * @returns A portable string uniquely identifying the call frame. */ function computeCallFrameId(callFrame: ICallFrame): string { const { url, lineNumber, columnNumber, functionName } = callFrame; @@ -26,6 +32,10 @@ function computeCallFrameId(callFrame: ICallFrame): string { * Adds the contents of a .cpuprofile file to a summary. * @param filePath - The path to the .cpuprofile file to read * @param accumulator - The summary to add the profile to + * Reads and parses a `.cpuprofile` file from disk, then adds its data to a profile summary. + * + * @param filePath - The path to the `.cpuprofile` file to read. + * @param accumulator - The summary to add the parsed profile data to. */ function addFileToSummary(filePath: string, accumulator: IProfileSummary): void { const profile: ICpuProfile = JSON.parse(fs.readFileSync(filePath, 'utf8')); @@ -37,6 +47,13 @@ function addFileToSummary(filePath: string, accumulator: IProfileSummary): void * @param profile - The profile to add * @param accumulator - The summary to add the profile to * @returns + * Aggregates CPU profile data into a summary map. + * Handles recursive frames by ensuring totalTime is computed + * via traversal instead of naive summation. + * + * @param profile - The parsed `.cpuprofile` data. + * @param accumulator - A Map keyed by callFrameId with summary info. + * @returns The updated accumulator with the new profile included. */ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary): IProfileSummary { const { nodes, samples, timeDeltas, startTime, endTime }: ICpuProfile = profile; @@ -53,17 +70,15 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) return index; } + // Initialize local time info for all nodes and establish nodeId -> index mapping for (let i: number = 0; i < nodes.length; i++) { - localTimes.push({ - self: 0, - contributors: undefined - }); - + localTimes.push({ self: 0 }); const { id } = nodes[i]; // Ensure that the mapping entry has been created. getIndexFromNodeId(id); } + // Distribute time samples across nodes const duration: number = endTime - startTime; let lastNodeTime: number = duration - timeDeltas[0]; for (let i: number = 0; i < timeDeltas.length - 1; i++) { @@ -73,9 +88,11 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) lastNodeTime -= sampleDuration; } + // Add remaining time to the last sample localTimes[getIndexFromNodeId(samples[samples.length - 1])].self += lastNodeTime; // Have to pick the maximum totalTime for a given frame, + // Group nodes by frameId const nodesByFrame: Map> = new Map(); for (let i: number = 0; i < nodes.length; i++) { @@ -90,6 +107,7 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) } } + // Summarize per-frame data for (const [frameId, contributors] of nodesByFrame) { // To compute the total time spent in a node, we need to sum the self time of all contributors. // We can't simply add up total times because a frame can recurse. @@ -107,6 +125,7 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) selfTime += localTime.self; } + // Traverse children to compute total time const queue: Set = new Set(contributors); for (const nodeIndex of queue) { totalTime += localTimes[nodeIndex].self; @@ -147,27 +166,34 @@ function addProfileToSummary(profile: ICpuProfile, accumulator: IProfileSummary) return accumulator; } -const { parentPort } = worker_threads; if (parentPort) { + const pp: MessagePort = parentPort; const messageHandler = (message: IMessageToWorker): void => { if (message === false) { // Shutdown signal. - parentPort.removeListener('message', messageHandler); - parentPort.close(); + pp.removeListener('message', messageHandler); + pp.close(); return; } try { const summary: IProfileSummary = new Map(); addFileToSummary(message, summary); - parentPort.postMessage({ file: message, data: summary }); - } catch (error) { - parentPort.postMessage({ - file: message, - data: error.stack || error.message - }); + pp.postMessage({ file: message, data: summary }); + } catch (error: unknown) { + if (error instanceof Error) { + pp.postMessage({ + file: message, + data: error.stack ?? error.message + }); + } else { + pp.postMessage({ + file: message, + data: String(error) + }); + } } }; - parentPort.on('message', messageHandler); + pp.on('message', messageHandler); }