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,199 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
BaselineReference,
|
|
7
|
+
BaselineStorage,
|
|
8
|
+
BaselineSummaryData,
|
|
9
|
+
BenchmarkRun,
|
|
10
|
+
TaskId,
|
|
11
|
+
} from '../types/core.js';
|
|
12
|
+
|
|
13
|
+
import { validateBaselineStorage } from '../config/budget-schema.js';
|
|
14
|
+
import { StorageError } from '../errors/storage.js';
|
|
15
|
+
import { createTaskId } from '../types/core.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Service for managing named baselines
|
|
19
|
+
*
|
|
20
|
+
* @packageDocumentation
|
|
21
|
+
*/
|
|
22
|
+
export class BaselineStorageService {
|
|
23
|
+
private readonly storageDir: string;
|
|
24
|
+
|
|
25
|
+
private readonly storageFile: string;
|
|
26
|
+
|
|
27
|
+
constructor(storageDir: string = '.') {
|
|
28
|
+
this.storageDir = storageDir;
|
|
29
|
+
this.storageFile = join(storageDir, '.modestbench.baselines.json');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Delete a baseline
|
|
34
|
+
*/
|
|
35
|
+
async deleteBaseline(name: string): Promise<void> {
|
|
36
|
+
let storage = await this.loadStorage();
|
|
37
|
+
|
|
38
|
+
if (storage.baselines[name]) {
|
|
39
|
+
delete storage.baselines[name];
|
|
40
|
+
|
|
41
|
+
// Clear default if it was the deleted baseline
|
|
42
|
+
if (storage.default === name) {
|
|
43
|
+
storage = { ...storage, default: undefined };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await this.saveStorage(storage);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get a baseline by name
|
|
52
|
+
*/
|
|
53
|
+
async getBaseline(name: string): Promise<BaselineReference | null> {
|
|
54
|
+
const storage = await this.loadStorage();
|
|
55
|
+
return storage.baselines[name] ?? null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get default baseline name
|
|
60
|
+
*/
|
|
61
|
+
async getDefault(): Promise<null | string> {
|
|
62
|
+
const storage = await this.loadStorage();
|
|
63
|
+
return storage.default ?? null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List all baselines
|
|
68
|
+
*/
|
|
69
|
+
async listBaselines(): Promise<BaselineReference[]> {
|
|
70
|
+
const storage = await this.loadStorage();
|
|
71
|
+
return Object.values(storage.baselines).sort(
|
|
72
|
+
(a, b) => b.date.getTime() - a.date.getTime(),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolve baseline name (use provided or fall back to default)
|
|
78
|
+
*/
|
|
79
|
+
async resolveBaselineName(name?: string): Promise<null | string> {
|
|
80
|
+
if (name) {
|
|
81
|
+
return name;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return await this.getDefault();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Save a benchmark run as a named baseline
|
|
89
|
+
*/
|
|
90
|
+
async saveBaseline(
|
|
91
|
+
name: string,
|
|
92
|
+
run: BenchmarkRun,
|
|
93
|
+
metadata?: {
|
|
94
|
+
branch?: string;
|
|
95
|
+
commit?: string;
|
|
96
|
+
},
|
|
97
|
+
): Promise<void> {
|
|
98
|
+
const storage = await this.loadStorage();
|
|
99
|
+
|
|
100
|
+
const baseline: BaselineReference = {
|
|
101
|
+
branch: metadata?.branch,
|
|
102
|
+
commit: metadata?.commit,
|
|
103
|
+
date:
|
|
104
|
+
run.startTime instanceof Date ? run.startTime : new Date(run.startTime),
|
|
105
|
+
name,
|
|
106
|
+
runId: run.id,
|
|
107
|
+
summary: this.extractSummary(run),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
storage.baselines[name] = baseline;
|
|
111
|
+
|
|
112
|
+
await this.saveStorage(storage);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Set default baseline
|
|
117
|
+
*/
|
|
118
|
+
async setDefault(name: string): Promise<void> {
|
|
119
|
+
let storage = await this.loadStorage();
|
|
120
|
+
|
|
121
|
+
if (!storage.baselines[name]) {
|
|
122
|
+
throw new StorageError(
|
|
123
|
+
`Baseline "${name}" does not exist. Cannot set as default.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
storage = { ...storage, default: name };
|
|
128
|
+
await this.saveStorage(storage);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Extract task summary from benchmark run
|
|
133
|
+
*/
|
|
134
|
+
private extractSummary(
|
|
135
|
+
run: BenchmarkRun,
|
|
136
|
+
): Record<TaskId, BaselineSummaryData> {
|
|
137
|
+
const summary: Record<TaskId, BaselineSummaryData> = {};
|
|
138
|
+
|
|
139
|
+
for (const file of run.files) {
|
|
140
|
+
for (const suite of file.suites) {
|
|
141
|
+
for (const task of suite.tasks) {
|
|
142
|
+
if (!task.error) {
|
|
143
|
+
const taskId = createTaskId(file.filePath, suite.name, task.name);
|
|
144
|
+
summary[taskId] = {
|
|
145
|
+
mean: task.mean,
|
|
146
|
+
opsPerSecond: task.opsPerSecond,
|
|
147
|
+
p99: task.p99,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return summary;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Load baseline storage from disk
|
|
159
|
+
*/
|
|
160
|
+
private async loadStorage(): Promise<BaselineStorage> {
|
|
161
|
+
if (!existsSync(this.storageFile)) {
|
|
162
|
+
return {
|
|
163
|
+
baselines: {},
|
|
164
|
+
version: '1.0.0',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const content = await readFile(this.storageFile, 'utf-8');
|
|
170
|
+
const data = JSON.parse(content) as unknown;
|
|
171
|
+
return validateBaselineStorage(data);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
throw new StorageError(
|
|
174
|
+
`Failed to load baseline storage from ${this.storageFile}`,
|
|
175
|
+
{ cause: error },
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Save baseline storage to disk
|
|
182
|
+
*/
|
|
183
|
+
private async saveStorage(storage: BaselineStorage): Promise<void> {
|
|
184
|
+
try {
|
|
185
|
+
// Ensure directory exists
|
|
186
|
+
if (!existsSync(this.storageDir)) {
|
|
187
|
+
await mkdir(this.storageDir, { recursive: true });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const content = JSON.stringify(storage, null, 2);
|
|
191
|
+
await writeFile(this.storageFile, content, 'utf-8');
|
|
192
|
+
} catch (error) {
|
|
193
|
+
throw new StorageError(
|
|
194
|
+
`Failed to save baseline storage to ${this.storageFile}`,
|
|
195
|
+
{ cause: error },
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BaselineSummaryData,
|
|
3
|
+
Budget,
|
|
4
|
+
BudgetResult,
|
|
5
|
+
BudgetSummary,
|
|
6
|
+
BudgetViolation,
|
|
7
|
+
TaskId,
|
|
8
|
+
TaskResult,
|
|
9
|
+
} from '../types/core.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Service for evaluating performance budgets
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
export class BudgetEvaluator {
|
|
17
|
+
/**
|
|
18
|
+
* Format number with thousands separators
|
|
19
|
+
*/
|
|
20
|
+
private static formatNumber(this: void, value: number): string {
|
|
21
|
+
return value.toLocaleString('en-US', {
|
|
22
|
+
maximumFractionDigits: 0,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Format decimal as percentage
|
|
28
|
+
*/
|
|
29
|
+
private static formatPercentage(this: void, value: number): string {
|
|
30
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Format time in nanoseconds to human-readable string
|
|
35
|
+
*/
|
|
36
|
+
private static formatTime(this: void, nanoseconds: number): string {
|
|
37
|
+
if (nanoseconds < 1_000) {
|
|
38
|
+
return `${nanoseconds.toFixed(0)}ns`;
|
|
39
|
+
} else if (nanoseconds < 1_000_000) {
|
|
40
|
+
return `${(nanoseconds / 1_000).toFixed(2)}μs`;
|
|
41
|
+
} else if (nanoseconds < 1_000_000_000) {
|
|
42
|
+
return `${(nanoseconds / 1_000_000).toFixed(2)}ms`;
|
|
43
|
+
} else {
|
|
44
|
+
return `${(nanoseconds / 1_000_000_000).toFixed(2)}s`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Evaluate budgets for an entire benchmark run
|
|
50
|
+
*/
|
|
51
|
+
evaluateRun(
|
|
52
|
+
budgets: Record<string, Budget>,
|
|
53
|
+
taskResults: Map<TaskId, TaskResult>,
|
|
54
|
+
baselineData?: Map<TaskId, BaselineSummaryData>,
|
|
55
|
+
): BudgetSummary {
|
|
56
|
+
const results: BudgetResult[] = [];
|
|
57
|
+
|
|
58
|
+
for (const [taskId, budget] of Object.entries(budgets)) {
|
|
59
|
+
const taskResult = taskResults.get(taskId as TaskId);
|
|
60
|
+
|
|
61
|
+
// Skip if no result for this task
|
|
62
|
+
if (!taskResult) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Skip relative budgets if no baseline data
|
|
67
|
+
if (budget.relative && !baselineData) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const budgetResult = this.evaluateTask(
|
|
72
|
+
taskId as TaskId,
|
|
73
|
+
budget,
|
|
74
|
+
taskResult,
|
|
75
|
+
baselineData?.get(taskId as TaskId),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
results.push(budgetResult);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const passed = results.filter((r) => r.passed).length;
|
|
82
|
+
const failed = results.filter((r) => !r.passed).length;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
failed,
|
|
86
|
+
passed,
|
|
87
|
+
results,
|
|
88
|
+
total: results.length,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Evaluate budgets for a single task
|
|
94
|
+
*/
|
|
95
|
+
private evaluateTask(
|
|
96
|
+
taskId: TaskId,
|
|
97
|
+
budget: Budget,
|
|
98
|
+
actual: TaskResult,
|
|
99
|
+
baseline?: BaselineSummaryData,
|
|
100
|
+
): BudgetResult {
|
|
101
|
+
const violations: BudgetViolation[] = [];
|
|
102
|
+
|
|
103
|
+
// Evaluate absolute budgets
|
|
104
|
+
if (budget.absolute) {
|
|
105
|
+
if (budget.absolute.maxTime !== undefined) {
|
|
106
|
+
if (actual.mean > budget.absolute.maxTime) {
|
|
107
|
+
violations.push({
|
|
108
|
+
actual: actual.mean,
|
|
109
|
+
delta:
|
|
110
|
+
(actual.mean - budget.absolute.maxTime) / budget.absolute.maxTime,
|
|
111
|
+
message: `Mean execution time ${BudgetEvaluator.formatTime(actual.mean)} exceeded budget of ${BudgetEvaluator.formatTime(budget.absolute.maxTime)} by ${BudgetEvaluator.formatPercentage((actual.mean - budget.absolute.maxTime) / budget.absolute.maxTime)}`,
|
|
112
|
+
threshold: budget.absolute.maxTime,
|
|
113
|
+
type: 'maxTime',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (budget.absolute.minOpsPerSec !== undefined) {
|
|
119
|
+
if (actual.opsPerSecond < budget.absolute.minOpsPerSec) {
|
|
120
|
+
violations.push({
|
|
121
|
+
actual: actual.opsPerSecond,
|
|
122
|
+
delta:
|
|
123
|
+
(budget.absolute.minOpsPerSec - actual.opsPerSecond) /
|
|
124
|
+
budget.absolute.minOpsPerSec,
|
|
125
|
+
message: `Operations per second ${BudgetEvaluator.formatNumber(actual.opsPerSecond)} is below minimum of ${BudgetEvaluator.formatNumber(budget.absolute.minOpsPerSec)} by ${BudgetEvaluator.formatPercentage((budget.absolute.minOpsPerSec - actual.opsPerSecond) / budget.absolute.minOpsPerSec)}`,
|
|
126
|
+
threshold: budget.absolute.minOpsPerSec,
|
|
127
|
+
type: 'minOpsPerSec',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (budget.absolute.maxP99 !== undefined && actual.p99 !== undefined) {
|
|
133
|
+
if (actual.p99 > budget.absolute.maxP99) {
|
|
134
|
+
violations.push({
|
|
135
|
+
actual: actual.p99,
|
|
136
|
+
delta:
|
|
137
|
+
(actual.p99 - budget.absolute.maxP99) / budget.absolute.maxP99,
|
|
138
|
+
message: `P99 latency ${BudgetEvaluator.formatTime(actual.p99)} exceeded budget of ${BudgetEvaluator.formatTime(budget.absolute.maxP99)} by ${BudgetEvaluator.formatPercentage((actual.p99 - budget.absolute.maxP99) / budget.absolute.maxP99)}`,
|
|
139
|
+
threshold: budget.absolute.maxP99,
|
|
140
|
+
type: 'maxP99',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Evaluate relative budgets
|
|
147
|
+
if (budget.relative && baseline) {
|
|
148
|
+
if (budget.relative.maxRegression !== undefined) {
|
|
149
|
+
const regression = (actual.mean - baseline.mean) / baseline.mean;
|
|
150
|
+
|
|
151
|
+
if (regression > budget.relative.maxRegression) {
|
|
152
|
+
violations.push({
|
|
153
|
+
actual: regression,
|
|
154
|
+
delta: regression - budget.relative.maxRegression,
|
|
155
|
+
message: `Performance regressed by ${BudgetEvaluator.formatPercentage(regression)} exceeding maximum allowed regression of ${BudgetEvaluator.formatPercentage(budget.relative.maxRegression)}`,
|
|
156
|
+
threshold: budget.relative.maxRegression,
|
|
157
|
+
type: 'maxRegression',
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
actual: {
|
|
165
|
+
mean: actual.mean,
|
|
166
|
+
opsPerSecond: actual.opsPerSecond,
|
|
167
|
+
p99: actual.p99,
|
|
168
|
+
},
|
|
169
|
+
baseline: baseline
|
|
170
|
+
? {
|
|
171
|
+
mean: baseline.mean,
|
|
172
|
+
opsPerSecond: baseline.opsPerSecond,
|
|
173
|
+
p99: baseline.p99,
|
|
174
|
+
}
|
|
175
|
+
: undefined,
|
|
176
|
+
budget,
|
|
177
|
+
passed: violations.length === 0,
|
|
178
|
+
taskId,
|
|
179
|
+
violations,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -55,8 +55,8 @@ const DEFAULT_CONFIG: ModestBenchConfig = {
|
|
|
55
55
|
iterations: 100, // Sufficient iterations for reliable statistics
|
|
56
56
|
limitBy: 'iterations', // Default to limiting by iteration count
|
|
57
57
|
metadata: {},
|
|
58
|
-
outputDir: '
|
|
59
|
-
pattern: '
|
|
58
|
+
outputDir: '.modestbench',
|
|
59
|
+
pattern: 'bench/**/*.bench.{js,ts,mjs,cjs,mts,cts}', // Search bench/ directory recursively
|
|
60
60
|
quiet: false,
|
|
61
61
|
reporterConfig: {},
|
|
62
62
|
reporters: [getDefaultReporter()],
|
|
@@ -121,10 +121,16 @@ export class ModestBenchConfigurationManager implements ConfigurationManager {
|
|
|
121
121
|
|
|
122
122
|
/**
|
|
123
123
|
* Load configuration from various sources with precedence
|
|
124
|
+
*
|
|
125
|
+
* @param configPath - Optional path to configuration file
|
|
126
|
+
* @param cliArgs - Optional CLI arguments to merge
|
|
127
|
+
* @param commandDefaults - Command-specific defaults (fallback to
|
|
128
|
+
* DEFAULT_CONFIG)
|
|
124
129
|
*/
|
|
125
130
|
async load(
|
|
126
131
|
configPath?: string,
|
|
127
132
|
cliArgs?: Record<string, unknown>,
|
|
133
|
+
commandDefaults?: Partial<ModestBenchConfig>,
|
|
128
134
|
): Promise<ModestBenchConfig> {
|
|
129
135
|
try {
|
|
130
136
|
// Create a fresh explorer for each load to avoid module caching issues
|
|
@@ -164,9 +170,13 @@ export class ModestBenchConfigurationManager implements ConfigurationManager {
|
|
|
164
170
|
|
|
165
171
|
const fileConfig = (result?.config || {}) as Partial<ModestBenchConfig>;
|
|
166
172
|
|
|
167
|
-
// 2. Merge: defaults <- file <- CLI args
|
|
173
|
+
// 2. Merge: command defaults <- file <- CLI args
|
|
174
|
+
// Use command-specific defaults if provided, otherwise use DEFAULT_CONFIG
|
|
175
|
+
const baseDefaults = commandDefaults
|
|
176
|
+
? this.merge(DEFAULT_CONFIG, commandDefaults)
|
|
177
|
+
: DEFAULT_CONFIG;
|
|
168
178
|
const normalizedCliArgs = cliArgs ? this.normalizeCliArgs(cliArgs) : {};
|
|
169
|
-
const merged = this.merge(
|
|
179
|
+
const merged = this.merge(baseDefaults, fileConfig, normalizedCliArgs);
|
|
170
180
|
|
|
171
181
|
// 2.5. Apply smart defaults for limitBy if not explicitly provided
|
|
172
182
|
const finalConfig = ModestBenchConfigurationManager.applySmartDefaults(
|
|
@@ -175,15 +185,20 @@ export class ModestBenchConfigurationManager implements ConfigurationManager {
|
|
|
175
185
|
fileConfig,
|
|
176
186
|
);
|
|
177
187
|
|
|
178
|
-
// 3. Validate final configuration
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
// 3. Validate final configuration and get transformed config
|
|
189
|
+
// The validation also transforms budgets from nested to flat format
|
|
190
|
+
const validation = safeParseConfig(finalConfig);
|
|
191
|
+
if (!validation.success) {
|
|
192
|
+
const errors = validation.error.issues.map((issue) => {
|
|
193
|
+
const path = issue.path.join('.');
|
|
194
|
+
return `${path ? `${path}: ` : ''}${issue.message}`;
|
|
195
|
+
});
|
|
181
196
|
throw new ConfigValidationError(
|
|
182
|
-
`Configuration validation failed: ${
|
|
197
|
+
`Configuration validation failed: ${errors.join(', ')}`,
|
|
183
198
|
);
|
|
184
199
|
}
|
|
185
200
|
|
|
186
|
-
return
|
|
201
|
+
return validation.data;
|
|
187
202
|
} catch (error) {
|
|
188
203
|
// Re-throw our custom errors
|
|
189
204
|
if (
|
|
@@ -64,8 +64,7 @@ export class BenchmarkFileLoader implements FileLoader {
|
|
|
64
64
|
// Handle empty patterns - use sensible defaults
|
|
65
65
|
if (patterns.length === 0) {
|
|
66
66
|
patterns = [
|
|
67
|
-
|
|
68
|
-
`bench/*${BENCHMARK_FILE_PATTERN}`, // top-level bench/ directory
|
|
67
|
+
`bench/**/*${BENCHMARK_FILE_PATTERN}`, // bench/ directory (recursive)
|
|
69
68
|
];
|
|
70
69
|
}
|
|
71
70
|
|
|
@@ -152,10 +151,8 @@ export class BenchmarkFileLoader implements FileLoader {
|
|
|
152
151
|
default?: unknown;
|
|
153
152
|
};
|
|
154
153
|
} else {
|
|
155
|
-
// Use native dynamic import for JavaScript files
|
|
156
|
-
|
|
157
|
-
const timestamp = Date.now();
|
|
158
|
-
module = (await import(`${filePath}?t=${timestamp}`)) as {
|
|
154
|
+
// Use native dynamic import for JavaScript files
|
|
155
|
+
module = (await import(filePath)) as {
|
|
159
156
|
[key: string]: unknown;
|
|
160
157
|
default?: unknown;
|
|
161
158
|
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile Filter Service
|
|
3
|
+
*
|
|
4
|
+
* Filters and sorts profiled functions based on configuration. Implements smart
|
|
5
|
+
* detection to focus on user code by excluding node_modules and Node.js
|
|
6
|
+
* internals.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { minimatch } from 'minimatch';
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
FilteredProfileData,
|
|
15
|
+
ProfileConfig,
|
|
16
|
+
ProfiledFunction,
|
|
17
|
+
RawProfileData,
|
|
18
|
+
} from '../../types/profiler.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Filter profile data based on configuration
|
|
22
|
+
*
|
|
23
|
+
* @param data - Raw profile data
|
|
24
|
+
* @param config - Filter configuration
|
|
25
|
+
* @param packageRoot - Package root directory for smart detection
|
|
26
|
+
* @returns Filtered profile data
|
|
27
|
+
*/
|
|
28
|
+
export const filterProfile = (
|
|
29
|
+
data: RawProfileData,
|
|
30
|
+
config: ProfileConfig,
|
|
31
|
+
packageRoot: string,
|
|
32
|
+
): FilteredProfileData => {
|
|
33
|
+
let filtered = data.functions.filter((fn) => {
|
|
34
|
+
// Only JavaScript functions
|
|
35
|
+
if (fn.category !== 'JavaScript') {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Apply smart detection if enabled
|
|
40
|
+
if (config.smartDetection && !config.focus?.length) {
|
|
41
|
+
if (!isUserCode(fn.file, packageRoot)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Apply focus patterns (if provided, overrides smart detection)
|
|
47
|
+
if (config.focus?.length) {
|
|
48
|
+
if (!matchesAnyPattern(fn.file, config.focus)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Apply exclude patterns (always applied if provided)
|
|
54
|
+
if (config.exclude?.length) {
|
|
55
|
+
if (matchesAnyPattern(fn.file, config.exclude)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Save count of user functions before percentage filtering
|
|
64
|
+
const totalUserFunctions = filtered.length;
|
|
65
|
+
|
|
66
|
+
// Apply percentage threshold
|
|
67
|
+
const minPercent = config.minExecutionPercent ?? 0.5;
|
|
68
|
+
filtered = filtered.filter((fn) => fn.percentage >= minPercent);
|
|
69
|
+
|
|
70
|
+
// Sort by percentage (highest first)
|
|
71
|
+
filtered.sort((a, b) => b.percentage - a.percentage);
|
|
72
|
+
|
|
73
|
+
// Limit to topN
|
|
74
|
+
const topN = config.topN ?? 25;
|
|
75
|
+
filtered = filtered.slice(0, topN);
|
|
76
|
+
|
|
77
|
+
// Group by file if requested
|
|
78
|
+
let groupedByFile: Map<string, ProfiledFunction[]> | undefined;
|
|
79
|
+
if (config.groupByFile) {
|
|
80
|
+
groupedByFile = groupByFile(filtered);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
functions: filtered,
|
|
85
|
+
groupedByFile,
|
|
86
|
+
minExecutionPercent: minPercent,
|
|
87
|
+
summary: data.summary,
|
|
88
|
+
totalFiltered: totalUserFunctions,
|
|
89
|
+
totalShown: filtered.length,
|
|
90
|
+
totalTicks: data.totalTicks,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if a file path is user code (not node_modules or internals)
|
|
96
|
+
*/
|
|
97
|
+
const isUserCode = (filePath: string, packageRoot: string): boolean => {
|
|
98
|
+
// Exclude node_modules
|
|
99
|
+
if (
|
|
100
|
+
filePath.includes('/node_modules/') ||
|
|
101
|
+
filePath.includes('\\node_modules\\')
|
|
102
|
+
) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Exclude Node.js internals
|
|
107
|
+
if (filePath.startsWith('node:') || filePath.startsWith('internal/')) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Allow <unknown> files (could be eval'd code or other user code without file paths)
|
|
112
|
+
if (filePath === '<unknown>' || filePath === '[eval]') {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Must be within package root
|
|
117
|
+
return filePath.startsWith(packageRoot);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if a file path matches any of the given glob patterns
|
|
122
|
+
*/
|
|
123
|
+
const matchesAnyPattern = (filePath: string, patterns: string[]): boolean => {
|
|
124
|
+
return patterns.some((pattern) => minimatch(filePath, pattern));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Group functions by file path
|
|
129
|
+
*/
|
|
130
|
+
const groupByFile = (
|
|
131
|
+
functions: ProfiledFunction[],
|
|
132
|
+
): Map<string, ProfiledFunction[]> => {
|
|
133
|
+
const grouped = new Map<string, ProfiledFunction[]>();
|
|
134
|
+
|
|
135
|
+
for (const fn of functions) {
|
|
136
|
+
const existing = grouped.get(fn.file) || [];
|
|
137
|
+
existing.push(fn);
|
|
138
|
+
grouped.set(fn.file, existing);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Sort functions within each file by percentage
|
|
142
|
+
for (const [, fns] of grouped.entries()) {
|
|
143
|
+
fns.sort((a, b) => b.percentage - a.percentage);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return grouped;
|
|
147
|
+
};
|