modestbench 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +131 -34
- package/dist/cli/commands/analyze.cjs +60 -0
- package/dist/cli/commands/analyze.cjs.map +1 -0
- package/dist/cli/commands/analyze.d.cts +35 -0
- package/dist/cli/commands/analyze.d.cts.map +1 -0
- package/dist/cli/commands/analyze.d.ts +35 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +56 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/baseline.cjs +404 -0
- package/dist/cli/commands/baseline.cjs.map +1 -0
- package/dist/cli/commands/baseline.d.cts +72 -0
- package/dist/cli/commands/baseline.d.cts.map +1 -0
- package/dist/cli/commands/baseline.d.ts +72 -0
- package/dist/cli/commands/baseline.d.ts.map +1 -0
- package/dist/cli/commands/baseline.js +396 -0
- package/dist/cli/commands/baseline.js.map +1 -0
- package/dist/cli/commands/history.d.cts +1 -1
- package/dist/cli/commands/history.d.cts.map +1 -1
- package/dist/cli/commands/history.d.ts +1 -1
- package/dist/cli/commands/history.d.ts.map +1 -1
- package/dist/cli/commands/init.cjs +99 -166
- package/dist/cli/commands/init.cjs.map +1 -1
- package/dist/cli/commands/init.d.cts +4 -4
- package/dist/cli/commands/init.d.cts.map +1 -1
- package/dist/cli/commands/init.d.ts +4 -4
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +99 -166
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/run.cjs +146 -127
- package/dist/cli/commands/run.cjs.map +1 -1
- package/dist/cli/commands/run.d.cts +16 -3
- package/dist/cli/commands/run.d.cts.map +1 -1
- package/dist/cli/commands/run.d.ts +16 -3
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +145 -93
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/index.cjs +583 -394
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +4 -16
- package/dist/cli/index.d.cts.map +1 -1
- package/dist/cli/index.d.ts +4 -16
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +575 -386
- package/dist/cli/index.js.map +1 -1
- package/dist/config/budget-schema.cjs +172 -0
- package/dist/config/budget-schema.cjs.map +1 -0
- package/dist/config/budget-schema.d.cts +59 -0
- package/dist/config/budget-schema.d.cts.map +1 -0
- package/dist/config/budget-schema.d.ts +59 -0
- package/dist/config/budget-schema.d.ts.map +1 -0
- package/dist/config/budget-schema.js +166 -0
- package/dist/config/budget-schema.js.map +1 -0
- package/dist/config/schema.cjs +182 -2
- package/dist/config/schema.cjs.map +1 -1
- package/dist/config/schema.d.cts +122 -3
- package/dist/config/schema.d.cts.map +1 -1
- package/dist/config/schema.d.ts +122 -3
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +180 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/constants.cjs +45 -2
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +41 -0
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.ts +41 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +44 -1
- package/dist/constants.js.map +1 -1
- package/dist/core/engine.cjs +114 -23
- package/dist/core/engine.cjs.map +1 -1
- package/dist/core/engine.d.cts +7 -7
- package/dist/core/engine.d.cts.map +1 -1
- package/dist/core/engine.d.ts +7 -7
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +115 -24
- package/dist/core/engine.js.map +1 -1
- package/dist/core/engines/accurate-engine.cjs +171 -36
- package/dist/core/engines/accurate-engine.cjs.map +1 -1
- package/dist/core/engines/accurate-engine.d.cts +5 -0
- package/dist/core/engines/accurate-engine.d.cts.map +1 -1
- package/dist/core/engines/accurate-engine.d.ts +5 -0
- package/dist/core/engines/accurate-engine.d.ts.map +1 -1
- package/dist/core/engines/accurate-engine.js +171 -36
- package/dist/core/engines/accurate-engine.js.map +1 -1
- package/dist/core/engines/tinybench-engine.cjs +3 -2
- package/dist/core/engines/tinybench-engine.cjs.map +1 -1
- package/dist/core/engines/tinybench-engine.d.cts.map +1 -1
- package/dist/core/engines/tinybench-engine.d.ts.map +1 -1
- package/dist/core/engines/tinybench-engine.js +3 -2
- package/dist/core/engines/tinybench-engine.js.map +1 -1
- package/dist/core/output-path-resolver.cjs +8 -1
- package/dist/core/output-path-resolver.cjs.map +1 -1
- package/dist/core/output-path-resolver.d.cts.map +1 -1
- package/dist/core/output-path-resolver.d.ts.map +1 -1
- package/dist/core/output-path-resolver.js +9 -2
- package/dist/core/output-path-resolver.js.map +1 -1
- package/dist/errors/base.cjs +12 -3
- package/dist/errors/base.cjs.map +1 -1
- package/dist/errors/base.d.cts +7 -0
- package/dist/errors/base.d.cts.map +1 -1
- package/dist/errors/base.d.ts +7 -0
- package/dist/errors/base.d.ts.map +1 -1
- package/dist/errors/base.js +10 -2
- package/dist/errors/base.js.map +1 -1
- package/dist/errors/budget.cjs +37 -0
- package/dist/errors/budget.cjs.map +1 -0
- package/dist/errors/budget.d.cts +31 -0
- package/dist/errors/budget.d.cts.map +1 -0
- package/dist/errors/budget.d.ts +31 -0
- package/dist/errors/budget.d.ts.map +1 -0
- package/dist/errors/budget.js +33 -0
- package/dist/errors/budget.js.map +1 -0
- package/dist/errors/index.cjs +4 -1
- package/dist/errors/index.cjs.map +1 -1
- package/dist/errors/index.d.cts +1 -0
- package/dist/errors/index.d.cts.map +1 -1
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +2 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/index.cjs +13 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/reporters/csv.cjs +37 -17
- package/dist/reporters/csv.cjs.map +1 -1
- package/dist/reporters/csv.d.cts +3 -6
- package/dist/reporters/csv.d.cts.map +1 -1
- package/dist/reporters/csv.d.ts +3 -6
- package/dist/reporters/csv.d.ts.map +1 -1
- package/dist/reporters/csv.js +37 -17
- package/dist/reporters/csv.js.map +1 -1
- package/dist/reporters/human.cjs +290 -67
- package/dist/reporters/human.cjs.map +1 -1
- package/dist/reporters/human.d.cts +25 -13
- package/dist/reporters/human.d.cts.map +1 -1
- package/dist/reporters/human.d.ts +25 -13
- package/dist/reporters/human.d.ts.map +1 -1
- package/dist/reporters/human.js +290 -67
- package/dist/reporters/human.js.map +1 -1
- package/dist/reporters/json.cjs +23 -48
- package/dist/reporters/json.cjs.map +1 -1
- package/dist/reporters/json.d.cts +2 -28
- package/dist/reporters/json.d.cts.map +1 -1
- package/dist/reporters/json.d.ts +2 -28
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +25 -50
- package/dist/reporters/json.js.map +1 -1
- package/dist/reporters/profile-human.cjs +154 -0
- package/dist/reporters/profile-human.cjs.map +1 -0
- package/dist/reporters/profile-human.d.cts +44 -0
- package/dist/reporters/profile-human.d.cts.map +1 -0
- package/dist/reporters/profile-human.d.ts +44 -0
- package/dist/reporters/profile-human.d.ts.map +1 -0
- package/dist/reporters/profile-human.js +147 -0
- package/dist/reporters/profile-human.js.map +1 -0
- package/dist/reporters/simple.cjs +67 -45
- package/dist/reporters/simple.cjs.map +1 -1
- package/dist/reporters/simple.d.cts +14 -14
- package/dist/reporters/simple.d.cts.map +1 -1
- package/dist/reporters/simple.d.ts +14 -14
- package/dist/reporters/simple.d.ts.map +1 -1
- package/dist/reporters/simple.js +67 -45
- package/dist/reporters/simple.js.map +1 -1
- package/dist/schema/modestbench-config.schema.json +153 -0
- package/dist/services/baseline-storage.cjs +151 -0
- package/dist/services/baseline-storage.cjs.map +1 -0
- package/dist/services/baseline-storage.d.cts +55 -0
- package/dist/services/baseline-storage.d.cts.map +1 -0
- package/dist/services/baseline-storage.d.ts +55 -0
- package/dist/services/baseline-storage.d.ts.map +1 -0
- package/dist/services/baseline-storage.js +147 -0
- package/dist/services/baseline-storage.js.map +1 -0
- package/dist/services/budget-evaluator.cjs +146 -0
- package/dist/services/budget-evaluator.cjs.map +1 -0
- package/dist/services/budget-evaluator.d.cts +29 -0
- package/dist/services/budget-evaluator.d.cts.map +1 -0
- package/dist/services/budget-evaluator.d.ts +29 -0
- package/dist/services/budget-evaluator.d.ts.map +1 -0
- package/dist/services/budget-evaluator.js +142 -0
- package/dist/services/budget-evaluator.js.map +1 -0
- package/dist/services/config-manager.cjs +24 -10
- package/dist/services/config-manager.cjs.map +1 -1
- package/dist/services/config-manager.d.cts +6 -1
- package/dist/services/config-manager.d.cts.map +1 -1
- package/dist/services/config-manager.d.ts +6 -1
- package/dist/services/config-manager.d.ts.map +1 -1
- package/dist/services/config-manager.js +24 -10
- package/dist/services/config-manager.js.map +1 -1
- package/dist/services/file-loader.cjs +3 -6
- package/dist/services/file-loader.cjs.map +1 -1
- package/dist/services/file-loader.d.cts.map +1 -1
- package/dist/services/file-loader.d.ts.map +1 -1
- package/dist/services/file-loader.js +3 -6
- package/dist/services/file-loader.js.map +1 -1
- package/dist/services/profiler/profile-filter.cjs +116 -0
- package/dist/services/profiler/profile-filter.cjs.map +1 -0
- package/dist/services/profiler/profile-filter.d.cts +20 -0
- package/dist/services/profiler/profile-filter.d.cts.map +1 -0
- package/dist/services/profiler/profile-filter.d.ts +20 -0
- package/dist/services/profiler/profile-filter.d.ts.map +1 -0
- package/dist/services/profiler/profile-filter.js +112 -0
- package/dist/services/profiler/profile-filter.js.map +1 -0
- package/dist/services/profiler/profile-parser.cjs +139 -0
- package/dist/services/profiler/profile-parser.cjs.map +1 -0
- package/dist/services/profiler/profile-parser.d.cts +18 -0
- package/dist/services/profiler/profile-parser.d.cts.map +1 -0
- package/dist/services/profiler/profile-parser.d.ts +18 -0
- package/dist/services/profiler/profile-parser.d.ts.map +1 -0
- package/dist/services/profiler/profile-parser.js +132 -0
- package/dist/services/profiler/profile-parser.js.map +1 -0
- package/dist/services/profiler/profile-runner.cjs +90 -0
- package/dist/services/profiler/profile-runner.cjs.map +1 -0
- package/dist/services/profiler/profile-runner.d.cts +29 -0
- package/dist/services/profiler/profile-runner.d.cts.map +1 -0
- package/dist/services/profiler/profile-runner.d.ts +29 -0
- package/dist/services/profiler/profile-runner.d.ts.map +1 -0
- package/dist/services/profiler/profile-runner.js +86 -0
- package/dist/services/profiler/profile-runner.js.map +1 -0
- package/dist/services/progress-manager.cjs +10 -2
- package/dist/services/progress-manager.cjs.map +1 -1
- package/dist/services/progress-manager.d.cts +2 -0
- package/dist/services/progress-manager.d.cts.map +1 -1
- package/dist/services/progress-manager.d.ts +2 -0
- package/dist/services/progress-manager.d.ts.map +1 -1
- package/dist/services/progress-manager.js +10 -2
- package/dist/services/progress-manager.js.map +1 -1
- package/dist/services/reporter-registry.cjs +18 -24
- package/dist/services/reporter-registry.cjs.map +1 -1
- package/dist/services/reporter-registry.d.cts +18 -40
- package/dist/services/reporter-registry.d.cts.map +1 -1
- package/dist/services/reporter-registry.d.ts +18 -40
- package/dist/services/reporter-registry.d.ts.map +1 -1
- package/dist/services/reporter-registry.js +18 -24
- package/dist/services/reporter-registry.js.map +1 -1
- package/dist/types/budgets.cjs +8 -0
- package/dist/types/budgets.cjs.map +1 -0
- package/dist/types/budgets.d.cts +149 -0
- package/dist/types/budgets.d.cts.map +1 -0
- package/dist/types/budgets.d.ts +149 -0
- package/dist/types/budgets.d.ts.map +1 -0
- package/dist/types/budgets.js +7 -0
- package/dist/types/budgets.js.map +1 -0
- package/dist/types/cli.cjs +2 -11
- package/dist/types/cli.cjs.map +1 -1
- package/dist/types/cli.d.cts +3 -227
- package/dist/types/cli.d.cts.map +1 -1
- package/dist/types/cli.d.ts +3 -227
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js +2 -11
- package/dist/types/cli.js.map +1 -1
- package/dist/types/core.cjs +6 -1
- package/dist/types/core.cjs.map +1 -1
- package/dist/types/core.d.cts +15 -2
- package/dist/types/core.d.cts.map +1 -1
- package/dist/types/core.d.ts +15 -2
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/core.js +2 -1
- package/dist/types/core.js.map +1 -1
- package/dist/types/index.cjs +5 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +2 -0
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/interfaces.d.cts +19 -8
- package/dist/types/interfaces.d.cts.map +1 -1
- package/dist/types/interfaces.d.ts +19 -8
- package/dist/types/interfaces.d.ts.map +1 -1
- package/dist/types/profiler.cjs +11 -0
- package/dist/types/profiler.cjs.map +1 -0
- package/dist/types/profiler.d.cts +102 -0
- package/dist/types/profiler.d.cts.map +1 -0
- package/dist/types/profiler.d.ts +102 -0
- package/dist/types/profiler.d.ts.map +1 -0
- package/dist/types/profiler.js +10 -0
- package/dist/types/profiler.js.map +1 -0
- package/dist/types/utility.cjs.map +1 -1
- package/dist/types/utility.d.cts +0 -8
- package/dist/types/utility.d.cts.map +1 -1
- package/dist/types/utility.d.ts +0 -8
- package/dist/types/utility.d.ts.map +1 -1
- package/dist/types/utility.js.map +1 -1
- package/dist/utils/identifiers.cjs +32 -0
- package/dist/utils/identifiers.cjs.map +1 -0
- package/dist/utils/identifiers.d.cts +32 -0
- package/dist/utils/identifiers.d.cts.map +1 -0
- package/dist/utils/identifiers.d.ts +32 -0
- package/dist/utils/identifiers.d.ts.map +1 -0
- package/dist/utils/identifiers.js +27 -0
- package/dist/utils/identifiers.js.map +1 -0
- package/dist/utils/package.cjs +40 -0
- package/dist/utils/package.cjs.map +1 -0
- package/dist/utils/package.d.cts +15 -0
- package/dist/utils/package.d.cts.map +1 -0
- package/dist/utils/package.d.ts +15 -0
- package/dist/utils/package.d.ts.map +1 -0
- package/dist/utils/package.js +33 -0
- package/dist/utils/package.js.map +1 -0
- package/dist/utils/type-guards.cjs +48 -0
- package/dist/utils/type-guards.cjs.map +1 -0
- package/dist/utils/type-guards.d.cts +22 -0
- package/dist/utils/type-guards.d.cts.map +1 -0
- package/dist/utils/type-guards.d.ts +22 -0
- package/dist/utils/type-guards.d.ts.map +1 -0
- package/dist/utils/type-guards.js +43 -0
- package/dist/utils/type-guards.js.map +1 -0
- package/package.json +18 -19
- package/src/cli/commands/analyze.ts +101 -0
- package/src/cli/commands/baseline.ts +577 -0
- package/src/cli/commands/history.ts +1 -1
- package/src/cli/commands/init.ts +116 -194
- package/src/cli/commands/run.ts +183 -113
- package/src/cli/index.ts +425 -183
- package/src/config/budget-schema.ts +189 -0
- package/src/config/schema.ts +260 -1
- package/src/constants.ts +53 -1
- package/src/core/engine.ts +169 -22
- package/src/core/engines/accurate-engine.ts +195 -44
- package/src/core/engines/tinybench-engine.ts +3 -2
- package/src/core/output-path-resolver.ts +10 -2
- package/src/errors/base.ts +11 -2
- package/src/errors/budget.ts +38 -0
- package/src/errors/index.ts +3 -0
- package/src/index.ts +9 -0
- package/src/reporters/csv.ts +54 -25
- package/src/reporters/human.ts +434 -115
- package/src/reporters/json.ts +26 -71
- package/src/reporters/profile-human.ts +210 -0
- package/src/reporters/simple.ts +88 -54
- package/src/services/baseline-storage.ts +199 -0
- package/src/services/budget-evaluator.ts +182 -0
- package/src/services/config-manager.ts +24 -9
- package/src/services/file-loader.ts +3 -6
- package/src/services/profiler/profile-filter.ts +147 -0
- package/src/services/profiler/profile-parser.ts +194 -0
- package/src/services/profiler/profile-runner.ts +121 -0
- package/src/services/progress-manager.ts +12 -2
- package/src/services/reporter-registry.ts +46 -81
- package/src/types/budgets.ts +180 -0
- package/src/types/cli.ts +5 -238
- package/src/types/core.ts +52 -10
- package/src/types/index.ts +5 -0
- package/src/types/interfaces.ts +24 -6
- package/src/types/profiler.ts +135 -0
- package/src/types/utility.ts +0 -10
- package/src/utils/identifiers.ts +58 -0
- package/src/utils/package.ts +35 -0
- package/src/utils/type-guards.ts +51 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile Parser Service
|
|
3
|
+
*
|
|
4
|
+
* Parses Chrome DevTools CPU profile format (*.cpuprofile files) generated by
|
|
5
|
+
* Node.js --cpu-prof flag. Extracts JavaScript function execution data
|
|
6
|
+
* including file paths, line numbers, hit counts, and percentages.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
ProfiledFunction,
|
|
17
|
+
ProfileSummary,
|
|
18
|
+
RawProfileData,
|
|
19
|
+
} from '../../types/profiler.js';
|
|
20
|
+
|
|
21
|
+
interface CallFrame {
|
|
22
|
+
columnNumber: number;
|
|
23
|
+
functionName: string;
|
|
24
|
+
lineNumber: number;
|
|
25
|
+
scriptId: string;
|
|
26
|
+
url: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Chrome DevTools CPU profile format
|
|
31
|
+
*/
|
|
32
|
+
interface CpuProfile {
|
|
33
|
+
endTime: number;
|
|
34
|
+
nodes: ProfileNode[];
|
|
35
|
+
samples?: number[];
|
|
36
|
+
startTime: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ProfileNode {
|
|
40
|
+
callFrame: CallFrame;
|
|
41
|
+
children?: number[];
|
|
42
|
+
hitCount?: number;
|
|
43
|
+
id: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parse a CPU profile file
|
|
48
|
+
*
|
|
49
|
+
* @param profilePath - Path to *.cpuprofile file
|
|
50
|
+
* @returns Parsed profile data
|
|
51
|
+
*/
|
|
52
|
+
export const parseProfile = async (
|
|
53
|
+
profilePath: string,
|
|
54
|
+
): Promise<RawProfileData> => {
|
|
55
|
+
// Read and parse JSON profile
|
|
56
|
+
const content = await readFile(profilePath, 'utf-8');
|
|
57
|
+
const profile: CpuProfile = JSON.parse(content) as CpuProfile;
|
|
58
|
+
|
|
59
|
+
// Extract functions and calculate statistics
|
|
60
|
+
return parseCpuProfile(profile, profilePath);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parse CPU profile data structure
|
|
65
|
+
*/
|
|
66
|
+
const parseCpuProfile = (
|
|
67
|
+
profile: CpuProfile,
|
|
68
|
+
profilePath: string,
|
|
69
|
+
): RawProfileData => {
|
|
70
|
+
const functions: ProfiledFunction[] = [];
|
|
71
|
+
let totalTicks = 0;
|
|
72
|
+
|
|
73
|
+
// Calculate total ticks from all node hit counts
|
|
74
|
+
for (const node of profile.nodes) {
|
|
75
|
+
totalTicks += node.hitCount || 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Extract function information from nodes with hit counts
|
|
79
|
+
for (const node of profile.nodes) {
|
|
80
|
+
const hitCount = node.hitCount || 0;
|
|
81
|
+
if (hitCount === 0) {
|
|
82
|
+
continue; // Skip nodes with no samples
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { callFrame } = node;
|
|
86
|
+
const percentage = totalTicks > 0 ? (hitCount / totalTicks) * 100 : 0;
|
|
87
|
+
|
|
88
|
+
// Parse file path from URL
|
|
89
|
+
const filePath = parseFileUrl(callFrame.url);
|
|
90
|
+
|
|
91
|
+
// Determine category based on URL
|
|
92
|
+
const category = determineCategory(callFrame.url);
|
|
93
|
+
|
|
94
|
+
functions.push({
|
|
95
|
+
category,
|
|
96
|
+
file: filePath,
|
|
97
|
+
line: callFrame.lineNumber >= 0 ? callFrame.lineNumber + 1 : null, // DevTools uses 0-based line numbers
|
|
98
|
+
name: callFrame.functionName || '(anonymous)',
|
|
99
|
+
percentage,
|
|
100
|
+
ticks: hitCount,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Calculate summary statistics
|
|
105
|
+
const javascriptTicks = functions
|
|
106
|
+
.filter((fn) => fn.category === 'JavaScript')
|
|
107
|
+
.reduce((sum, fn) => sum + fn.ticks, 0);
|
|
108
|
+
|
|
109
|
+
const cppTicks = functions
|
|
110
|
+
.filter((fn) => fn.category === 'C++')
|
|
111
|
+
.reduce((sum, fn) => sum + fn.ticks, 0);
|
|
112
|
+
|
|
113
|
+
const summary: ProfileSummary = {
|
|
114
|
+
cppTicks,
|
|
115
|
+
gcTicks: 0, // CPU profiles don't separate GC
|
|
116
|
+
javascriptTicks,
|
|
117
|
+
sharedLibraryTicks: 0,
|
|
118
|
+
totalTicks,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
functions,
|
|
123
|
+
logPath: profilePath,
|
|
124
|
+
summary,
|
|
125
|
+
totalTicks,
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Parse file URL from call frame
|
|
131
|
+
*/
|
|
132
|
+
const parseFileUrl = (url: string): string => {
|
|
133
|
+
if (!url) {
|
|
134
|
+
return '<unknown>';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Handle file:// URLs
|
|
138
|
+
if (url.startsWith('file://')) {
|
|
139
|
+
try {
|
|
140
|
+
return fileURLToPath(url);
|
|
141
|
+
} catch {
|
|
142
|
+
// Fallback if URL is malformed
|
|
143
|
+
return path.normalize(url.replace('file://', ''));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Handle node: internal modules
|
|
148
|
+
if (url.startsWith('node:')) {
|
|
149
|
+
return url;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Handle plain paths
|
|
153
|
+
return path.normalize(url);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Determine function category based on URL
|
|
158
|
+
*/
|
|
159
|
+
const determineCategory = (
|
|
160
|
+
url: string,
|
|
161
|
+
): 'C++' | 'GC' | 'JavaScript' | 'Unknown' => {
|
|
162
|
+
if (!url) {
|
|
163
|
+
return 'Unknown';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Node.js internals and native modules
|
|
167
|
+
if (url.startsWith('node:') || url.includes('[native code]')) {
|
|
168
|
+
return 'C++';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// eval'd code is JavaScript
|
|
172
|
+
if (url === '[eval]') {
|
|
173
|
+
return 'JavaScript';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// JavaScript files
|
|
177
|
+
if (
|
|
178
|
+
url.endsWith('.js') ||
|
|
179
|
+
url.endsWith('.mjs') ||
|
|
180
|
+
url.endsWith('.cjs') ||
|
|
181
|
+
url.endsWith('.ts') ||
|
|
182
|
+
url.endsWith('.mts') ||
|
|
183
|
+
url.endsWith('.cts')
|
|
184
|
+
) {
|
|
185
|
+
return 'JavaScript';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Default to JavaScript for file:// URLs
|
|
189
|
+
if (url.startsWith('file://')) {
|
|
190
|
+
return 'JavaScript';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return 'Unknown';
|
|
194
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile Runner Service
|
|
3
|
+
*
|
|
4
|
+
* Executes commands with Node.js CPU profiling enabled and captures profiler
|
|
5
|
+
* output to *.cpuprofile files in .modestbench/profiles/.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { glob } from 'glob';
|
|
11
|
+
import { spawn } from 'node:child_process';
|
|
12
|
+
import { mkdir, stat } from 'node:fs/promises';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for running with profiling
|
|
17
|
+
*/
|
|
18
|
+
interface RunOptions {
|
|
19
|
+
/** Working directory */
|
|
20
|
+
cwd?: string;
|
|
21
|
+
|
|
22
|
+
/** Environment variables */
|
|
23
|
+
env?: NodeJS.ProcessEnv;
|
|
24
|
+
|
|
25
|
+
/** Timeout in milliseconds */
|
|
26
|
+
timeout?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Run a command with Node.js profiling enabled
|
|
31
|
+
*
|
|
32
|
+
* @param command - Command to run (e.g., "npm test")
|
|
33
|
+
* @param options - Execution options
|
|
34
|
+
* @returns Path to generated *.cpuprofile file
|
|
35
|
+
*/
|
|
36
|
+
export const runWithProfiling = async (
|
|
37
|
+
command: string,
|
|
38
|
+
options: RunOptions = {},
|
|
39
|
+
): Promise<string> => {
|
|
40
|
+
const cwd = options.cwd || process.cwd();
|
|
41
|
+
|
|
42
|
+
// Create profiles directory
|
|
43
|
+
const profilesDir = join(cwd, '.modestbench', 'profiles');
|
|
44
|
+
await mkdir(profilesDir, { recursive: true });
|
|
45
|
+
|
|
46
|
+
// Run command with NODE_OPTIONS="--cpu-prof --cpu-prof-dir=..."
|
|
47
|
+
const proc = spawn(command, {
|
|
48
|
+
cwd,
|
|
49
|
+
env: {
|
|
50
|
+
...process.env,
|
|
51
|
+
...options.env,
|
|
52
|
+
NODE_OPTIONS: `--cpu-prof --cpu-prof-dir=${profilesDir}`,
|
|
53
|
+
},
|
|
54
|
+
shell: true,
|
|
55
|
+
stdio: 'inherit',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Wait for process to complete
|
|
59
|
+
await new Promise<void>((resolve, reject) => {
|
|
60
|
+
const timeout = options.timeout
|
|
61
|
+
? setTimeout(() => {
|
|
62
|
+
proc.kill();
|
|
63
|
+
reject(
|
|
64
|
+
new Error(`Profile command timed out after ${options.timeout}ms`),
|
|
65
|
+
);
|
|
66
|
+
}, options.timeout)
|
|
67
|
+
: null;
|
|
68
|
+
|
|
69
|
+
proc.on('close', (code) => {
|
|
70
|
+
if (timeout) {
|
|
71
|
+
clearTimeout(timeout);
|
|
72
|
+
}
|
|
73
|
+
if (code === 0 || code === null) {
|
|
74
|
+
resolve();
|
|
75
|
+
} else {
|
|
76
|
+
reject(new Error(`Profile command exited with code ${code}`));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
proc.on('error', (err) => {
|
|
81
|
+
if (timeout) {
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
}
|
|
84
|
+
reject(err);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Find generated *.cpuprofile file in profiles directory
|
|
89
|
+
const profileFiles = await glob('*.cpuprofile', { cwd: profilesDir });
|
|
90
|
+
|
|
91
|
+
if (profileFiles.length === 0) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
'No CPU profile generated. Ensure the command runs Node.js code.',
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Return most recent profile file
|
|
98
|
+
return await getMostRecentFile(profileFiles, profilesDir);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the most recently modified file from a list
|
|
103
|
+
*/
|
|
104
|
+
const getMostRecentFile = async (
|
|
105
|
+
files: string[],
|
|
106
|
+
cwd: string,
|
|
107
|
+
): Promise<string> => {
|
|
108
|
+
let mostRecent = files[0];
|
|
109
|
+
let mostRecentTime = (await stat(`${cwd}/${mostRecent}`)).mtimeMs;
|
|
110
|
+
|
|
111
|
+
for (const file of files.slice(1)) {
|
|
112
|
+
const filePath = `${cwd}/${file}`;
|
|
113
|
+
const fileTime = (await stat(filePath)).mtimeMs;
|
|
114
|
+
if (fileTime > mostRecentTime) {
|
|
115
|
+
mostRecent = file;
|
|
116
|
+
mostRecentTime = fileTime;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return `${cwd}/${mostRecent}`;
|
|
121
|
+
};
|
|
@@ -32,12 +32,16 @@ interface ProgressMetrics {
|
|
|
32
32
|
export class ModestBenchProgressManager implements ProgressManager {
|
|
33
33
|
private callbacks: ProgressCallback[] = [];
|
|
34
34
|
|
|
35
|
+
private lastMetricsUpdate = 0;
|
|
36
|
+
|
|
35
37
|
private lastUpdate = 0;
|
|
36
38
|
|
|
37
39
|
private readonly maxRecentTimings = 10;
|
|
38
40
|
|
|
39
41
|
private metrics: null | ProgressMetrics = null;
|
|
40
42
|
|
|
43
|
+
private readonly metricsUpdateThrottleMs = 1000; // Throttle ETA calculations (1 second)
|
|
44
|
+
|
|
41
45
|
private state: ProgressState;
|
|
42
46
|
|
|
43
47
|
private readonly updateThrottleMs = 100; // Limit updates to avoid spam
|
|
@@ -53,6 +57,8 @@ export class ModestBenchProgressManager implements ProgressManager {
|
|
|
53
57
|
this.callbacks = [];
|
|
54
58
|
this.metrics = null;
|
|
55
59
|
this.state = this.createInitialState();
|
|
60
|
+
this.lastUpdate = 0;
|
|
61
|
+
this.lastMetricsUpdate = 0;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
/**
|
|
@@ -240,6 +246,7 @@ export class ModestBenchProgressManager implements ProgressManager {
|
|
|
240
246
|
};
|
|
241
247
|
|
|
242
248
|
this.lastUpdate = Date.now();
|
|
249
|
+
this.lastMetricsUpdate = Date.now();
|
|
243
250
|
this.notifyCallbacks();
|
|
244
251
|
}
|
|
245
252
|
|
|
@@ -314,8 +321,11 @@ export class ModestBenchProgressManager implements ProgressManager {
|
|
|
314
321
|
percentage: this.calculatePercentage(updates),
|
|
315
322
|
};
|
|
316
323
|
|
|
317
|
-
// Update metrics for completion estimation
|
|
318
|
-
this.
|
|
324
|
+
// Update metrics for completion estimation (throttled separately for ETA)
|
|
325
|
+
if (now - this.lastMetricsUpdate >= this.metricsUpdateThrottleMs) {
|
|
326
|
+
this.updateMetrics(now);
|
|
327
|
+
this.lastMetricsUpdate = now;
|
|
328
|
+
}
|
|
319
329
|
|
|
320
330
|
this.lastUpdate = now;
|
|
321
331
|
this.notifyCallbacks();
|
|
@@ -33,74 +33,10 @@ export abstract class BaseReporter implements Reporter {
|
|
|
33
33
|
this.options = options;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
* Get reporter name
|
|
38
|
-
*/
|
|
39
|
-
getName(): string {
|
|
40
|
-
return this.name;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get reporter options
|
|
45
|
-
*/
|
|
46
|
-
getOptions(): Record<string, unknown> {
|
|
47
|
-
return { ...this.options };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Called when benchmark run completes
|
|
52
|
-
*/
|
|
53
|
-
abstract onEnd(run: BenchmarkRun): Promise<void> | void;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Called when an error occurs
|
|
57
|
-
*/
|
|
58
|
-
abstract onError(error: Error): Promise<void> | void;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Called when a file completes
|
|
62
|
-
*/
|
|
63
|
-
abstract onFileEnd(result: FileResult): Promise<void> | void;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Called when a file starts execution
|
|
67
|
-
*/
|
|
68
|
-
abstract onFileStart(file: string): Promise<void> | void;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Called for progress updates
|
|
72
|
-
*/
|
|
73
|
-
abstract onProgress(state: ProgressState): Promise<void> | void;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Called when benchmark run starts
|
|
77
|
-
*/
|
|
78
|
-
abstract onStart(run: BenchmarkRun): Promise<void> | void;
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Called when a suite completes
|
|
82
|
-
*/
|
|
83
|
-
abstract onSuiteEnd(result: SuiteResult): Promise<void> | void;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Called when a suite starts execution
|
|
87
|
-
*/
|
|
88
|
-
abstract onSuiteStart(suite: string): Promise<void> | void;
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Called when a task completes
|
|
92
|
-
*/
|
|
93
|
-
abstract onTaskResult(result: TaskResult): Promise<void> | void;
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Called when a task starts execution
|
|
97
|
-
*/
|
|
98
|
-
abstract onTaskStart(task: string): Promise<void> | void;
|
|
99
|
-
|
|
100
36
|
/**
|
|
101
37
|
* Utility method to format duration in human-readable format
|
|
102
38
|
*/
|
|
103
|
-
protected formatDuration(nanoseconds: number): string {
|
|
39
|
+
protected static formatDuration(this: void, nanoseconds: number): string {
|
|
104
40
|
if (nanoseconds < 1000) {
|
|
105
41
|
return `${nanoseconds.toFixed(2)}ns`;
|
|
106
42
|
} else if (nanoseconds < 1000000) {
|
|
@@ -115,7 +51,10 @@ export abstract class BaseReporter implements Reporter {
|
|
|
115
51
|
/**
|
|
116
52
|
* Utility method to format operations per second
|
|
117
53
|
*/
|
|
118
|
-
protected formatOpsPerSecond(
|
|
54
|
+
protected static formatOpsPerSecond(
|
|
55
|
+
this: void,
|
|
56
|
+
opsPerSecond: number,
|
|
57
|
+
): string {
|
|
119
58
|
if (opsPerSecond < 1000) {
|
|
120
59
|
return `${opsPerSecond.toFixed(2)} ops/sec`;
|
|
121
60
|
} else if (opsPerSecond < 1000000) {
|
|
@@ -130,23 +69,43 @@ export abstract class BaseReporter implements Reporter {
|
|
|
130
69
|
/**
|
|
131
70
|
* Utility method to format percentage
|
|
132
71
|
*/
|
|
133
|
-
protected formatPercentage(value: number): string {
|
|
72
|
+
protected static formatPercentage(this: void, value: number): string {
|
|
134
73
|
return `${value.toFixed(2)}%`;
|
|
135
74
|
}
|
|
136
75
|
|
|
137
76
|
/**
|
|
138
|
-
*
|
|
77
|
+
* Get reporter name
|
|
139
78
|
*/
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
79
|
+
getName(): string {
|
|
80
|
+
return this.name;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get reporter options
|
|
85
|
+
*/
|
|
86
|
+
getOptions(): Record<string, unknown> {
|
|
87
|
+
return { ...this.options };
|
|
149
88
|
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Called when benchmark run completes
|
|
92
|
+
*/
|
|
93
|
+
abstract onEnd(run: BenchmarkRun): Promise<void> | void;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Called when an error occurs
|
|
97
|
+
*/
|
|
98
|
+
abstract onError(error: Error): Promise<void> | void;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Called when benchmark run starts
|
|
102
|
+
*/
|
|
103
|
+
abstract onStart(run: BenchmarkRun): Promise<void> | void;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Called when a task completes
|
|
107
|
+
*/
|
|
108
|
+
abstract onTaskResult(result: TaskResult): Promise<void> | void;
|
|
150
109
|
}
|
|
151
110
|
|
|
152
111
|
/**
|
|
@@ -162,6 +121,8 @@ export class CompositeReporter extends BaseReporter {
|
|
|
162
121
|
|
|
163
122
|
/**
|
|
164
123
|
* Add a reporter to the composite
|
|
124
|
+
*
|
|
125
|
+
* Intended for programmatic usage.
|
|
165
126
|
*/
|
|
166
127
|
addReporter(reporter: Reporter): void {
|
|
167
128
|
this.reporters.push(reporter);
|
|
@@ -169,16 +130,18 @@ export class CompositeReporter extends BaseReporter {
|
|
|
169
130
|
|
|
170
131
|
/**
|
|
171
132
|
* Get all reporters in the composite
|
|
133
|
+
*
|
|
134
|
+
* Intended for programmatic usage.
|
|
172
135
|
*/
|
|
173
136
|
getReporters(): Reporter[] {
|
|
174
137
|
return [...this.reporters];
|
|
175
138
|
}
|
|
176
139
|
|
|
177
|
-
async onEnd(run: BenchmarkRun): Promise<void> {
|
|
140
|
+
override async onEnd(run: BenchmarkRun): Promise<void> {
|
|
178
141
|
await this.broadcastAsync('onEnd', run);
|
|
179
142
|
}
|
|
180
143
|
|
|
181
|
-
async onError(error: Error): Promise<void> {
|
|
144
|
+
override async onError(error: Error): Promise<void> {
|
|
182
145
|
await this.broadcastAsync('onError', error);
|
|
183
146
|
}
|
|
184
147
|
|
|
@@ -194,7 +157,7 @@ export class CompositeReporter extends BaseReporter {
|
|
|
194
157
|
await this.broadcastAsync('onProgress', state);
|
|
195
158
|
}
|
|
196
159
|
|
|
197
|
-
async onStart(run: BenchmarkRun): Promise<void> {
|
|
160
|
+
override async onStart(run: BenchmarkRun): Promise<void> {
|
|
198
161
|
await this.broadcastAsync('onStart', run);
|
|
199
162
|
}
|
|
200
163
|
|
|
@@ -206,7 +169,7 @@ export class CompositeReporter extends BaseReporter {
|
|
|
206
169
|
await this.broadcastAsync('onSuiteStart', suite);
|
|
207
170
|
}
|
|
208
171
|
|
|
209
|
-
async onTaskResult(result: TaskResult): Promise<void> {
|
|
172
|
+
override async onTaskResult(result: TaskResult): Promise<void> {
|
|
210
173
|
await this.broadcastAsync('onTaskResult', result);
|
|
211
174
|
}
|
|
212
175
|
|
|
@@ -216,6 +179,8 @@ export class CompositeReporter extends BaseReporter {
|
|
|
216
179
|
|
|
217
180
|
/**
|
|
218
181
|
* Remove a reporter from the composite
|
|
182
|
+
*
|
|
183
|
+
* Intended for programmatic usage.
|
|
219
184
|
*/
|
|
220
185
|
removeReporter(reporter: Reporter): boolean {
|
|
221
186
|
const index = this.reporters.indexOf(reporter);
|