traceperf 0.2.0 → 1.3.0
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/README.md +612 -209
- package/dist/__tests__/helpers/console.d.ts +9 -0
- package/dist/__tests__/helpers/console.js +25 -0
- package/dist/__tests__/helpers/console.js.map +1 -0
- package/dist/auto.d.ts +30 -0
- package/dist/auto.js +91 -0
- package/dist/auto.js.map +1 -0
- package/dist/browser.d.ts +52 -144
- package/dist/browser.js +112 -439
- package/dist/browser.js.map +1 -1
- package/dist/cli.d.ts +68 -0
- package/dist/cli.js +528 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/allocations.d.ts +38 -0
- package/dist/core/allocations.js +85 -0
- package/dist/core/allocations.js.map +1 -0
- package/dist/core/cpu-profile.d.ts +84 -0
- package/dist/core/cpu-profile.js +181 -0
- package/dist/core/cpu-profile.js.map +1 -0
- package/dist/core/env.d.ts +10 -0
- package/dist/core/env.js +40 -0
- package/dist/core/env.js.map +1 -0
- package/dist/core/event-loop.d.ts +30 -0
- package/dist/core/event-loop.js +43 -0
- package/dist/core/event-loop.js.map +1 -0
- package/dist/core/explain.d.ts +48 -0
- package/dist/core/explain.js +236 -0
- package/dist/core/explain.js.map +1 -0
- package/dist/core/exporters.d.ts +111 -0
- package/dist/core/exporters.js +236 -0
- package/dist/core/exporters.js.map +1 -0
- package/dist/core/index.d.ts +9 -3
- package/dist/core/index.js +9 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/optimized-performance-monitor.d.ts +180 -26
- package/dist/core/optimized-performance-monitor.js +687 -175
- package/dist/core/optimized-performance-monitor.js.map +1 -1
- package/dist/core/prometheus.d.ts +36 -0
- package/dist/core/prometheus.js +93 -0
- package/dist/core/prometheus.js.map +1 -0
- package/dist/core/records.d.ts +97 -0
- package/dist/core/records.js +87 -0
- package/dist/core/records.js.map +1 -0
- package/dist/core/stats.d.ts +45 -0
- package/dist/core/stats.js +136 -0
- package/dist/core/stats.js.map +1 -0
- package/dist/core/timeline.d.ts +28 -0
- package/dist/core/timeline.js +65 -0
- package/dist/core/timeline.js.map +1 -0
- package/dist/drivers.d.ts +51 -0
- package/dist/drivers.js +205 -0
- package/dist/drivers.js.map +1 -0
- package/dist/esm/auto.d.ts +30 -0
- package/dist/esm/auto.js +87 -0
- package/dist/esm/auto.js.map +1 -0
- package/dist/esm/browser.d.ts +52 -144
- package/dist/esm/browser.js +101 -445
- package/dist/esm/browser.js.map +1 -1
- package/dist/esm/cli.d.ts +68 -0
- package/dist/esm/cli.js +520 -0
- package/dist/esm/cli.js.map +1 -0
- package/dist/esm/core/allocations.d.ts +38 -0
- package/dist/esm/core/allocations.js +82 -0
- package/dist/esm/core/allocations.js.map +1 -0
- package/dist/esm/core/cpu-profile.d.ts +84 -0
- package/dist/esm/core/cpu-profile.js +176 -0
- package/dist/esm/core/cpu-profile.js.map +1 -0
- package/dist/esm/core/env.d.ts +10 -0
- package/dist/esm/core/env.js +35 -0
- package/dist/esm/core/env.js.map +1 -0
- package/dist/esm/core/event-loop.d.ts +30 -0
- package/dist/esm/core/event-loop.js +40 -0
- package/dist/esm/core/event-loop.js.map +1 -0
- package/dist/esm/core/explain.d.ts +48 -0
- package/dist/esm/core/explain.js +232 -0
- package/dist/esm/core/explain.js.map +1 -0
- package/dist/esm/core/exporters.d.ts +111 -0
- package/dist/esm/core/exporters.js +229 -0
- package/dist/esm/core/exporters.js.map +1 -0
- package/dist/esm/core/index.d.ts +10 -4
- package/dist/esm/core/index.js +10 -4
- package/dist/esm/core/index.js.map +1 -1
- package/dist/esm/core/optimized-performance-monitor.d.ts +180 -26
- package/dist/esm/core/optimized-performance-monitor.js +685 -173
- package/dist/esm/core/optimized-performance-monitor.js.map +1 -1
- package/dist/esm/core/prometheus.d.ts +36 -0
- package/dist/esm/core/prometheus.js +89 -0
- package/dist/esm/core/prometheus.js.map +1 -0
- package/dist/esm/core/records.d.ts +97 -0
- package/dist/esm/core/records.js +82 -0
- package/dist/esm/core/records.js.map +1 -0
- package/dist/esm/core/stats.d.ts +45 -0
- package/dist/esm/core/stats.js +131 -0
- package/dist/esm/core/stats.js.map +1 -0
- package/dist/esm/core/timeline.d.ts +28 -0
- package/dist/esm/core/timeline.js +62 -0
- package/dist/esm/core/timeline.js.map +1 -0
- package/dist/esm/drivers.d.ts +51 -0
- package/dist/esm/drivers.js +197 -0
- package/dist/esm/drivers.js.map +1 -0
- package/dist/esm/index.d.ts +13 -5
- package/dist/esm/index.js +21 -17
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/instrument.d.ts +88 -0
- package/dist/esm/instrument.js +274 -0
- package/dist/esm/instrument.js.map +1 -0
- package/dist/esm/integrations.d.ts +70 -0
- package/dist/esm/integrations.js +197 -0
- package/dist/esm/integrations.js.map +1 -0
- package/dist/esm/node-builtins.d.ts +1 -0
- package/dist/esm/node-builtins.js +32 -0
- package/dist/esm/node-builtins.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/register.d.ts +20 -0
- package/dist/esm/register.js +98 -0
- package/dist/esm/register.js.map +1 -0
- package/dist/esm/report-html.d.ts +11 -0
- package/dist/esm/report-html.js +106 -0
- package/dist/esm/report-html.js.map +1 -0
- package/dist/esm/types/index.d.ts +4 -80
- package/dist/esm/types/index.js +1 -2
- package/dist/esm/types/index.js.map +1 -1
- package/dist/esm/types/options.d.ts +72 -0
- package/dist/esm/types/options.js +2 -0
- package/dist/esm/types/options.js.map +1 -0
- package/dist/esm/watch.d.ts +17 -0
- package/dist/esm/watch.js +87 -0
- package/dist/esm/watch.js.map +1 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +26 -29
- package/dist/index.js.map +1 -1
- package/dist/instrument.d.ts +88 -0
- package/dist/instrument.js +282 -0
- package/dist/instrument.js.map +1 -0
- package/dist/integrations.d.ts +70 -0
- package/dist/integrations.js +205 -0
- package/dist/integrations.js.map +1 -0
- package/dist/node-builtins.d.ts +1 -0
- package/dist/node-builtins.js +67 -0
- package/dist/node-builtins.js.map +1 -0
- package/dist/register.d.ts +20 -0
- package/dist/register.js +103 -0
- package/dist/register.js.map +1 -0
- package/dist/report-html.d.ts +11 -0
- package/dist/report-html.js +109 -0
- package/dist/report-html.js.map +1 -0
- package/dist/traceperf.browser.js +1910 -0
- package/dist/traceperf.browser.min.js +6 -0
- package/dist/types/index.d.ts +4 -80
- package/dist/types/index.js +1 -2
- package/dist/types/index.js.map +1 -1
- package/dist/types/options.d.ts +72 -0
- package/dist/types/{config.js → options.js} +1 -1
- package/dist/types/options.js.map +1 -0
- package/dist/watch.d.ts +17 -0
- package/dist/watch.js +90 -0
- package/dist/watch.js.map +1 -0
- package/package.json +99 -19
- package/dist/core/config.d.ts +0 -77
- package/dist/core/config.js +0 -116
- package/dist/core/config.js.map +0 -1
- package/dist/core/constants.d.ts +0 -78
- package/dist/core/constants.js +0 -109
- package/dist/core/constants.js.map +0 -1
- package/dist/core/logger.d.ts +0 -97
- package/dist/core/logger.js +0 -186
- package/dist/core/logger.js.map +0 -1
- package/dist/esm/core/config.d.ts +0 -77
- package/dist/esm/core/config.js +0 -112
- package/dist/esm/core/config.js.map +0 -1
- package/dist/esm/core/constants.d.ts +0 -78
- package/dist/esm/core/constants.js +0 -106
- package/dist/esm/core/constants.js.map +0 -1
- package/dist/esm/core/logger.d.ts +0 -97
- package/dist/esm/core/logger.js +0 -182
- package/dist/esm/core/logger.js.map +0 -1
- package/dist/esm/formatters/ascii.d.ts +0 -52
- package/dist/esm/formatters/ascii.js +0 -100
- package/dist/esm/formatters/ascii.js.map +0 -1
- package/dist/esm/formatters/cli.d.ts +0 -50
- package/dist/esm/formatters/cli.js +0 -96
- package/dist/esm/formatters/cli.js.map +0 -1
- package/dist/esm/formatters/index.d.ts +0 -3
- package/dist/esm/formatters/index.js +0 -4
- package/dist/esm/formatters/index.js.map +0 -1
- package/dist/esm/formatters/json.d.ts +0 -45
- package/dist/esm/formatters/json.js +0 -85
- package/dist/esm/formatters/json.js.map +0 -1
- package/dist/esm/trackers/execution.d.ts +0 -101
- package/dist/esm/trackers/execution.js +0 -226
- package/dist/esm/trackers/execution.js.map +0 -1
- package/dist/esm/trackers/index.d.ts +0 -2
- package/dist/esm/trackers/index.js +0 -3
- package/dist/esm/trackers/index.js.map +0 -1
- package/dist/esm/trackers/performance.d.ts +0 -68
- package/dist/esm/trackers/performance.js +0 -112
- package/dist/esm/trackers/performance.js.map +0 -1
- package/dist/esm/types/config.d.ts +0 -142
- package/dist/esm/types/config.js +0 -2
- package/dist/esm/types/config.js.map +0 -1
- package/dist/esm/types/logger.d.ts +0 -147
- package/dist/esm/types/logger.js +0 -2
- package/dist/esm/types/logger.js.map +0 -1
- package/dist/esm/utils/colors.d.ts +0 -87
- package/dist/esm/utils/colors.js +0 -146
- package/dist/esm/utils/colors.js.map +0 -1
- package/dist/esm/utils/index.d.ts +0 -3
- package/dist/esm/utils/index.js +0 -4
- package/dist/esm/utils/index.js.map +0 -1
- package/dist/esm/utils/proxy-tracker.d.ts +0 -68
- package/dist/esm/utils/proxy-tracker.js +0 -127
- package/dist/esm/utils/proxy-tracker.js.map +0 -1
- package/dist/esm/utils/stack.d.ts +0 -63
- package/dist/esm/utils/stack.js +0 -110
- package/dist/esm/utils/stack.js.map +0 -1
- package/dist/esm/utils/string.d.ts +0 -29
- package/dist/esm/utils/string.js +0 -45
- package/dist/esm/utils/string.js.map +0 -1
- package/dist/esm/utils/timing.d.ts +0 -65
- package/dist/esm/utils/timing.js +0 -99
- package/dist/esm/utils/timing.js.map +0 -1
- package/dist/formatters/ascii.d.ts +0 -52
- package/dist/formatters/ascii.js +0 -104
- package/dist/formatters/ascii.js.map +0 -1
- package/dist/formatters/cli.d.ts +0 -50
- package/dist/formatters/cli.js +0 -100
- package/dist/formatters/cli.js.map +0 -1
- package/dist/formatters/index.d.ts +0 -3
- package/dist/formatters/index.js +0 -20
- package/dist/formatters/index.js.map +0 -1
- package/dist/formatters/json.d.ts +0 -45
- package/dist/formatters/json.js +0 -89
- package/dist/formatters/json.js.map +0 -1
- package/dist/trackers/execution.d.ts +0 -101
- package/dist/trackers/execution.js +0 -230
- package/dist/trackers/execution.js.map +0 -1
- package/dist/trackers/index.d.ts +0 -2
- package/dist/trackers/index.js +0 -19
- package/dist/trackers/index.js.map +0 -1
- package/dist/trackers/performance.d.ts +0 -68
- package/dist/trackers/performance.js +0 -116
- package/dist/trackers/performance.js.map +0 -1
- package/dist/types/config.d.ts +0 -142
- package/dist/types/config.js.map +0 -1
- package/dist/types/logger.d.ts +0 -147
- package/dist/types/logger.js +0 -3
- package/dist/types/logger.js.map +0 -1
- package/dist/types.d.ts +0 -65
- package/dist/types.js +0 -6
- package/dist/types.js.map +0 -1
- package/dist/utils/colors.d.ts +0 -87
- package/dist/utils/colors.js +0 -160
- package/dist/utils/colors.js.map +0 -1
- package/dist/utils/index.d.ts +0 -3
- package/dist/utils/index.js +0 -20
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/proxy-tracker.d.ts +0 -68
- package/dist/utils/proxy-tracker.js +0 -131
- package/dist/utils/proxy-tracker.js.map +0 -1
- package/dist/utils/stack.d.ts +0 -63
- package/dist/utils/stack.js +0 -116
- package/dist/utils/stack.js.map +0 -1
- package/dist/utils/string.d.ts +0 -29
- package/dist/utils/string.js +0 -50
- package/dist/utils/string.js.map +0 -1
- package/dist/utils/timing.d.ts +0 -65
- package/dist/utils/timing.js +0 -106
- package/dist/utils/timing.js.map +0 -1
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* 6. Module registration capability
|
|
11
11
|
* 7. Enhanced visualization
|
|
12
12
|
*/
|
|
13
|
+
import { coveredLength, formatTree, walkRecords, } from './records.js';
|
|
14
|
+
import { StatsCollector, formatStatsTable } from './stats.js';
|
|
15
|
+
import { createTraceRecorder } from './exporters.js';
|
|
16
|
+
import { loadNodeBuiltin } from './env.js';
|
|
13
17
|
// Define tracking mode constants directly to avoid circular dependency
|
|
14
18
|
export const TrackingModes = {
|
|
15
19
|
PERFORMANCE: 'performance',
|
|
@@ -19,12 +23,21 @@ export const TrackingModes = {
|
|
|
19
23
|
};
|
|
20
24
|
// Define default tracking mode separately to avoid circular dependency
|
|
21
25
|
const DEFAULT_TRACKING_MODE = TrackingModes.BALANCED;
|
|
26
|
+
// v8.getHeapStatistics() reports the same heap figure as
|
|
27
|
+
// process.memoryUsage().heapUsed but ~20x faster (it skips measuring RSS).
|
|
28
|
+
const v8Module = loadNodeBuiltin('v8');
|
|
29
|
+
const readHeapUsed = v8Module && typeof v8Module.getHeapStatistics === 'function'
|
|
30
|
+
? () => v8Module.getHeapStatistics().used_heap_size
|
|
31
|
+
: undefined;
|
|
22
32
|
/**
|
|
23
33
|
* Get current memory usage with environment detection
|
|
24
34
|
* @returns Memory usage in bytes
|
|
25
35
|
*/
|
|
26
36
|
export const getMemoryUsage = () => {
|
|
27
37
|
var _a;
|
|
38
|
+
if (readHeapUsed) {
|
|
39
|
+
return readHeapUsed();
|
|
40
|
+
}
|
|
28
41
|
// Safe check for Node.js process.memoryUsage()
|
|
29
42
|
if (typeof process !== 'undefined' && process.memoryUsage) {
|
|
30
43
|
const mem = process.memoryUsage();
|
|
@@ -32,8 +45,7 @@ export const getMemoryUsage = () => {
|
|
|
32
45
|
}
|
|
33
46
|
// Browser memory API if available
|
|
34
47
|
else if (typeof performance !== 'undefined' && 'memory' in performance) {
|
|
35
|
-
|
|
36
|
-
return ((_a = window.performance.memory) === null || _a === void 0 ? void 0 : _a.usedJSHeapSize) || 0;
|
|
48
|
+
return ((_a = performance.memory) === null || _a === void 0 ? void 0 : _a.usedJSHeapSize) || 0;
|
|
37
49
|
}
|
|
38
50
|
// Fallback when no memory API available
|
|
39
51
|
return 0;
|
|
@@ -42,19 +54,16 @@ export const getMemoryUsage = () => {
|
|
|
42
54
|
* Get high-resolution timestamp with environment detection
|
|
43
55
|
* @returns Timestamp in nanoseconds
|
|
44
56
|
*/
|
|
45
|
-
export const getTimestamp =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// Fallback to Date.now() (converted to nanoseconds)
|
|
56
|
-
return Date.now() * 1000000;
|
|
57
|
-
};
|
|
57
|
+
export const getTimestamp =
|
|
58
|
+
// Clock chosen once at load time (the hot path should not re-detect it)
|
|
59
|
+
typeof performance !== 'undefined' && typeof performance.now === 'function'
|
|
60
|
+
? () => performance.now() * 1000000
|
|
61
|
+
: typeof process !== 'undefined' && typeof process.hrtime === 'function'
|
|
62
|
+
? () => {
|
|
63
|
+
const hrTime = process.hrtime();
|
|
64
|
+
return hrTime[0] * 1000000000 + hrTime[1];
|
|
65
|
+
}
|
|
66
|
+
: () => Date.now() * 1000000;
|
|
58
67
|
/**
|
|
59
68
|
* Calculate time difference between two timestamps
|
|
60
69
|
* @param startTime Starting timestamp in nanoseconds
|
|
@@ -63,6 +72,91 @@ export const getTimestamp = () => {
|
|
|
63
72
|
export const getDuration = (startTime) => {
|
|
64
73
|
return getTimestamp() - startTime;
|
|
65
74
|
};
|
|
75
|
+
// Offset from the monotonic clock (ms) to Unix epoch ms, fixed at load time
|
|
76
|
+
const EPOCH_OFFSET_MS = Date.now() - getTimestamp() / 1000000;
|
|
77
|
+
const MAX_ARG_LENGTH = 50;
|
|
78
|
+
/**
|
|
79
|
+
* Render one captured argument as a short, safe string.
|
|
80
|
+
* Never throws and never walks large structures in full.
|
|
81
|
+
*/
|
|
82
|
+
export const formatArg = (arg) => {
|
|
83
|
+
var _a, _b;
|
|
84
|
+
let text;
|
|
85
|
+
try {
|
|
86
|
+
if (typeof arg === 'string') {
|
|
87
|
+
text = JSON.stringify(arg.length > MAX_ARG_LENGTH ? arg.slice(0, MAX_ARG_LENGTH) : arg);
|
|
88
|
+
}
|
|
89
|
+
else if (typeof arg === 'function') {
|
|
90
|
+
text = `[Function ${arg.name || 'anonymous'}]`;
|
|
91
|
+
}
|
|
92
|
+
else if (Array.isArray(arg)) {
|
|
93
|
+
text = `Array(${arg.length})`;
|
|
94
|
+
}
|
|
95
|
+
else if (arg !== null && typeof arg === 'object') {
|
|
96
|
+
const keys = Object.keys(arg);
|
|
97
|
+
if (keys.length <= 5) {
|
|
98
|
+
text = JSON.stringify(arg);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const name = ((_a = arg.constructor) === null || _a === void 0 ? void 0 : _a.name) || 'Object';
|
|
102
|
+
text = `${name}{${keys.length} keys}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
text = String(arg);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (_c) {
|
|
110
|
+
// circular structures, null prototypes, throwing getters/toJSON, ...
|
|
111
|
+
const name = (_b = arg === null || arg === void 0 ? void 0 : arg.constructor) === null || _b === void 0 ? void 0 : _b.name;
|
|
112
|
+
text = typeof name === 'string' && name ? `[${name}]` : '[object]';
|
|
113
|
+
}
|
|
114
|
+
return text.length > MAX_ARG_LENGTH ? `${text.slice(0, MAX_ARG_LENGTH - 1)}…` : text;
|
|
115
|
+
};
|
|
116
|
+
const loadAsyncLocalStorage = () => {
|
|
117
|
+
var _a, _b;
|
|
118
|
+
return (_b = (_a = loadNodeBuiltin('async_hooks')) === null || _a === void 0 ? void 0 : _a.AsyncLocalStorage) !== null && _b !== void 0 ? _b :
|
|
119
|
+
// Edge runtimes (e.g. Next.js Edge) expose it as a global
|
|
120
|
+
(typeof globalThis !== 'undefined' ? globalThis.AsyncLocalStorage : undefined);
|
|
121
|
+
};
|
|
122
|
+
const createExecutionContext = (provided) => {
|
|
123
|
+
const AsyncLocalStorage = provided !== null && provided !== void 0 ? provided : loadAsyncLocalStorage();
|
|
124
|
+
if (AsyncLocalStorage) {
|
|
125
|
+
const storage = new AsyncLocalStorage();
|
|
126
|
+
return {
|
|
127
|
+
current: () => { var _a; return (_a = storage.getStore()) !== null && _a !== void 0 ? _a : null; },
|
|
128
|
+
run: (executionId, fn) => storage.run(executionId, fn),
|
|
129
|
+
// AsyncLocalStorage.run forwards extra arguments to the callback
|
|
130
|
+
apply: (executionId, fn, thisArg, args) => storage.run(executionId, Reflect.apply, fn, thisArg, args)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
let current = null;
|
|
134
|
+
const run = (executionId, fn) => {
|
|
135
|
+
const previous = current;
|
|
136
|
+
current = executionId;
|
|
137
|
+
try {
|
|
138
|
+
return fn();
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
current = previous;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
return {
|
|
145
|
+
current: () => current,
|
|
146
|
+
run,
|
|
147
|
+
apply: (executionId, fn, thisArg, args) => run(executionId, () => fn.apply(thisArg, args))
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
const NOOP_SPAN = {
|
|
151
|
+
id: '',
|
|
152
|
+
setLabel: () => undefined,
|
|
153
|
+
run: (fn) => fn(),
|
|
154
|
+
end: () => undefined
|
|
155
|
+
};
|
|
156
|
+
const clampRate = (rate) => Math.max(0, Math.min(1, rate));
|
|
157
|
+
const isNativePromise = (value) => value !== null && typeof value === 'object' &&
|
|
158
|
+
(value instanceof Promise ||
|
|
159
|
+
(typeof value.then === 'function' && Object.prototype.toString.call(value) === '[object Promise]'));
|
|
66
160
|
/**
|
|
67
161
|
* Optimized tracker for individual function executions
|
|
68
162
|
*/
|
|
@@ -72,25 +166,85 @@ export class OptimizedExecutionTracker {
|
|
|
72
166
|
* @param options Tracker configuration options
|
|
73
167
|
*/
|
|
74
168
|
constructor(options = {}) {
|
|
75
|
-
var _a, _b, _c, _d, _e;
|
|
76
|
-
this.executionTree =
|
|
169
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
170
|
+
this.executionTree = new Map();
|
|
77
171
|
this.currentExecutionId = 0;
|
|
78
172
|
this.activeExecutions = new Map();
|
|
79
|
-
|
|
80
|
-
this.
|
|
81
|
-
|
|
82
|
-
childPrefix: '├─ ',
|
|
83
|
-
lastChildPrefix: '└─ ',
|
|
84
|
-
linePrefix: '│ ',
|
|
85
|
-
emptyPrefix: ' ',
|
|
86
|
-
errorMarker: '✖ ',
|
|
87
|
-
memoryLabel: '📊 '
|
|
88
|
-
};
|
|
173
|
+
this.plugins = [];
|
|
174
|
+
this.wrappers = [];
|
|
175
|
+
this.failedPlugins = new Set();
|
|
89
176
|
this.showMemoryUsage = (_a = options.showMemoryUsage) !== null && _a !== void 0 ? _a : true;
|
|
90
177
|
this.enableNestedTracking = (_b = options.enableNestedTracking) !== null && _b !== void 0 ? _b : true;
|
|
91
178
|
this.silent = (_c = options.silent) !== null && _c !== void 0 ? _c : false;
|
|
92
179
|
this.trackingMode = (_d = options.trackingMode) !== null && _d !== void 0 ? _d : TrackingModes.BALANCED;
|
|
93
|
-
this.defaultThreshold = (_e = options.threshold) !== null && _e !== void 0 ? _e :
|
|
180
|
+
this.defaultThreshold = (_e = options.threshold) !== null && _e !== void 0 ? _e : 0;
|
|
181
|
+
this.defaultSampleRate = clampRate((_f = options.sampleRate) !== null && _f !== void 0 ? _f : 1);
|
|
182
|
+
this.context = createExecutionContext(options.asyncLocalStorage);
|
|
183
|
+
this.enabled = (_g = options.enabled) !== null && _g !== void 0 ? _g : true;
|
|
184
|
+
this.blockingThreshold = (_h = options.blockingThreshold) !== null && _h !== void 0 ? _h : 50;
|
|
185
|
+
this.reporter = (_j = options.reporter) !== null && _j !== void 0 ? _j : null;
|
|
186
|
+
this.stats = options.stats === false ? null :
|
|
187
|
+
new StatsCollector(typeof options.stats === 'object' ? options.stats : {});
|
|
188
|
+
(options.plugins || []).forEach(plugin => this.use(plugin));
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Register a plugin. Returns a function that removes it again.
|
|
192
|
+
*/
|
|
193
|
+
use(plugin) {
|
|
194
|
+
this.plugins = [...this.plugins, plugin];
|
|
195
|
+
this.wrappers = this.plugins.filter(p => typeof p.wrap === 'function');
|
|
196
|
+
return () => {
|
|
197
|
+
this.plugins = this.plugins.filter(p => p !== plugin);
|
|
198
|
+
this.wrappers = this.plugins.filter(p => typeof p.wrap === 'function');
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Run `run` inside every plugin's wrap() (first registered = outermost).
|
|
203
|
+
* A wrap() that throws before calling run is reported once and skipped.
|
|
204
|
+
*/
|
|
205
|
+
_wrap(execution, run) {
|
|
206
|
+
let next = run;
|
|
207
|
+
for (let i = this.wrappers.length - 1; i >= 0; i--) {
|
|
208
|
+
const plugin = this.wrappers[i];
|
|
209
|
+
const inner = next;
|
|
210
|
+
next = () => {
|
|
211
|
+
let ran = false;
|
|
212
|
+
const once = () => {
|
|
213
|
+
ran = true;
|
|
214
|
+
return inner();
|
|
215
|
+
};
|
|
216
|
+
try {
|
|
217
|
+
return plugin.wrap(execution, once);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
if (ran)
|
|
221
|
+
throw error; // the tracked code itself threw
|
|
222
|
+
this._pluginFailed(plugin, 'wrap', error);
|
|
223
|
+
return inner();
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return next();
|
|
228
|
+
}
|
|
229
|
+
_pluginFailed(plugin, hook, error) {
|
|
230
|
+
if (!this.failedPlugins.has(plugin)) {
|
|
231
|
+
this.failedPlugins.add(plugin);
|
|
232
|
+
console.warn(`traceperf: plugin ${plugin.name || '(unnamed)'} threw in ${hook}:`, error);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
_callPlugins(hook, payload) {
|
|
236
|
+
for (const plugin of this.plugins) {
|
|
237
|
+
const fn = plugin[hook];
|
|
238
|
+
if (!fn)
|
|
239
|
+
continue;
|
|
240
|
+
try {
|
|
241
|
+
fn.call(plugin, payload);
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
// Never let a plugin break the tracked code; warn once per plugin
|
|
245
|
+
this._pluginFailed(plugin, hook, error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
94
248
|
}
|
|
95
249
|
/**
|
|
96
250
|
* Start timing a function execution
|
|
@@ -99,9 +253,13 @@ export class OptimizedExecutionTracker {
|
|
|
99
253
|
* @param options Additional options
|
|
100
254
|
* @returns Execution ID
|
|
101
255
|
*/
|
|
102
|
-
startTimer(label, parentExecutionId = null, options = {}) {
|
|
256
|
+
startTimer(label, parentExecutionId = null, options = {}, args = options.args) {
|
|
257
|
+
var _a, _b, _c, _d;
|
|
258
|
+
// Per-execution settings, falling back to the tracker's current config
|
|
259
|
+
const trackingMode = (_a = options.trackingMode) !== null && _a !== void 0 ? _a : this.trackingMode;
|
|
260
|
+
const trackMemory = (_b = options.trackMemory) !== null && _b !== void 0 ? _b : this.showMemoryUsage;
|
|
103
261
|
// Skip detailed tracking in performance mode
|
|
104
|
-
const collectMemory =
|
|
262
|
+
const collectMemory = trackingMode !== TrackingModes.PERFORMANCE && trackMemory;
|
|
105
263
|
const startTime = getTimestamp();
|
|
106
264
|
const executionId = (++this.currentExecutionId).toString();
|
|
107
265
|
const execution = {
|
|
@@ -112,9 +270,12 @@ export class OptimizedExecutionTracker {
|
|
|
112
270
|
duration: 0,
|
|
113
271
|
parentId: parentExecutionId,
|
|
114
272
|
children: [],
|
|
115
|
-
args: options.captureArgs &&
|
|
116
|
-
Array.from(
|
|
117
|
-
complete: false
|
|
273
|
+
args: options.captureArgs && args ?
|
|
274
|
+
Array.from(args).map(formatArg) : undefined,
|
|
275
|
+
complete: false,
|
|
276
|
+
silent: (_c = options.silent) !== null && _c !== void 0 ? _c : this.silent,
|
|
277
|
+
trackingMode,
|
|
278
|
+
threshold: (_d = options.threshold) !== null && _d !== void 0 ? _d : this.defaultThreshold
|
|
118
279
|
};
|
|
119
280
|
// Collect memory usage if enabled
|
|
120
281
|
if (collectMemory) {
|
|
@@ -131,6 +292,14 @@ export class OptimizedExecutionTracker {
|
|
|
131
292
|
}
|
|
132
293
|
// Store in active executions map
|
|
133
294
|
this.activeExecutions.set(executionId, execution);
|
|
295
|
+
if (this.plugins.length > 0) {
|
|
296
|
+
this._callPlugins('onStart', {
|
|
297
|
+
id: executionId,
|
|
298
|
+
label: execution.label,
|
|
299
|
+
parentId: parentExecutionId,
|
|
300
|
+
startEpochMs: EPOCH_OFFSET_MS + startTime / 1000000
|
|
301
|
+
});
|
|
302
|
+
}
|
|
134
303
|
return executionId;
|
|
135
304
|
}
|
|
136
305
|
/**
|
|
@@ -145,69 +314,185 @@ export class OptimizedExecutionTracker {
|
|
|
145
314
|
return null;
|
|
146
315
|
}
|
|
147
316
|
const execution = this.activeExecutions.get(executionId);
|
|
317
|
+
this.activeExecutions.delete(executionId);
|
|
148
318
|
execution.endTime = getTimestamp();
|
|
149
319
|
execution.duration = execution.endTime - execution.startTime;
|
|
150
320
|
execution.result = result;
|
|
151
321
|
execution.error = error;
|
|
152
322
|
execution.complete = true;
|
|
153
|
-
// Only collect memory
|
|
154
|
-
if (
|
|
323
|
+
// Only collect memory if it was collected at start
|
|
324
|
+
if (execution.memoryAtStart !== undefined) {
|
|
155
325
|
execution.memoryAtEnd = getMemoryUsage();
|
|
156
326
|
execution.memoryDelta = execution.memoryAtEnd - (execution.memoryAtStart || 0);
|
|
157
327
|
}
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
328
|
+
// A child whose parent is still running is reported as part of the
|
|
329
|
+
// parent's tree once the parent finishes.
|
|
330
|
+
const parentRunning = execution.parentId !== null && this.activeExecutions.has(execution.parentId);
|
|
331
|
+
if (parentRunning) {
|
|
332
|
+
this.executionTree.set(executionId, execution);
|
|
333
|
+
return execution;
|
|
334
|
+
}
|
|
335
|
+
// Root (or a child that outlived its parent): build the record tree once
|
|
336
|
+
// and hand it to stats, the console reporter and plugins.
|
|
337
|
+
const record = this._toRecord(execution);
|
|
338
|
+
// Release the finished tree so memory use does not grow with call count
|
|
339
|
+
if (execution.children.length > 0) {
|
|
340
|
+
this._release(execution);
|
|
341
|
+
}
|
|
342
|
+
if (this.stats) {
|
|
343
|
+
this.stats.addTree(record);
|
|
344
|
+
}
|
|
345
|
+
if (!record.silent &&
|
|
346
|
+
record.trackingMode !== TrackingModes.PERFORMANCE &&
|
|
347
|
+
record.duration >= record.threshold) {
|
|
348
|
+
this._report(record);
|
|
349
|
+
}
|
|
350
|
+
if (this.plugins.length > 0) {
|
|
351
|
+
this._callPlugins('onComplete', record);
|
|
164
352
|
}
|
|
165
353
|
return execution;
|
|
166
354
|
}
|
|
167
355
|
/**
|
|
168
|
-
*
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
for (let i = 0; i < childExecutions.length; i++) {
|
|
205
|
-
const isLast = i === childExecutions.length - 1;
|
|
206
|
-
const childFlowChart = this._generateExecutionFlowChart(childExecutions[i], depth + 1, isLast, [...ancestors, !isLastChild]);
|
|
207
|
-
flowChart += childFlowChart;
|
|
356
|
+
* Drop a finished execution and its finished descendants
|
|
357
|
+
*/
|
|
358
|
+
_release(execution) {
|
|
359
|
+
for (const childId of execution.children) {
|
|
360
|
+
const child = this.executionTree.get(childId);
|
|
361
|
+
if (child) {
|
|
362
|
+
this.executionTree.delete(childId);
|
|
363
|
+
this._release(child);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Mark the end of the synchronous part of an execution that returned a promise
|
|
369
|
+
*/
|
|
370
|
+
markSyncEnd(executionId) {
|
|
371
|
+
const execution = this.activeExecutions.get(executionId);
|
|
372
|
+
if (execution) {
|
|
373
|
+
execution.syncEndTime = getTimestamp();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Convert a finished execution (and its finished children) to a record tree
|
|
378
|
+
*/
|
|
379
|
+
_toRecord(execution) {
|
|
380
|
+
var _a, _b, _c;
|
|
381
|
+
const startTime = execution.startTime / 1000000;
|
|
382
|
+
const endTime = execution.endTime / 1000000;
|
|
383
|
+
const duration = execution.duration / 1000000;
|
|
384
|
+
const children = [];
|
|
385
|
+
let covered = 0;
|
|
386
|
+
if (execution.children.length > 0) {
|
|
387
|
+
for (const childId of execution.children) {
|
|
388
|
+
const child = this.executionTree.get(childId);
|
|
389
|
+
if (child) {
|
|
390
|
+
children.push(this._toRecord(child));
|
|
391
|
+
}
|
|
208
392
|
}
|
|
393
|
+
covered = coveredLength(children.map(c => [Math.max(c.startTime, startTime), Math.min(c.endTime, endTime)]));
|
|
394
|
+
}
|
|
395
|
+
const isAsync = execution.syncEndTime !== undefined;
|
|
396
|
+
const record = {
|
|
397
|
+
id: execution.id,
|
|
398
|
+
label: execution.label,
|
|
399
|
+
parentId: execution.parentId,
|
|
400
|
+
startTime,
|
|
401
|
+
endTime,
|
|
402
|
+
startEpochMs: EPOCH_OFFSET_MS + startTime,
|
|
403
|
+
duration,
|
|
404
|
+
selfDuration: Math.max(0, duration - covered),
|
|
405
|
+
syncDuration: isAsync ? (execution.syncEndTime - execution.startTime) / 1000000 : duration,
|
|
406
|
+
async: isAsync,
|
|
407
|
+
children,
|
|
408
|
+
threshold: (_a = execution.threshold) !== null && _a !== void 0 ? _a : 0,
|
|
409
|
+
trackingMode: (_b = execution.trackingMode) !== null && _b !== void 0 ? _b : this.trackingMode,
|
|
410
|
+
silent: (_c = execution.silent) !== null && _c !== void 0 ? _c : this.silent,
|
|
411
|
+
};
|
|
412
|
+
if (execution.memoryAtStart !== undefined && execution.memoryDelta !== undefined) {
|
|
413
|
+
record.memoryDelta = execution.memoryDelta;
|
|
414
|
+
}
|
|
415
|
+
if (execution.error !== null && execution.error !== undefined) {
|
|
416
|
+
record.error = execution.error;
|
|
417
|
+
}
|
|
418
|
+
if (execution.args && execution.args.length > 0) {
|
|
419
|
+
record.args = execution.args;
|
|
209
420
|
}
|
|
210
|
-
return
|
|
421
|
+
return record;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Print a finished tree (or hand it to the custom reporter)
|
|
425
|
+
*/
|
|
426
|
+
_report(record) {
|
|
427
|
+
if (this.reporter) {
|
|
428
|
+
this.reporter(record);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const detailed = record.trackingMode === TrackingModes.DETAILED || record.trackingMode === TrackingModes.DEBUG;
|
|
432
|
+
console.log(formatTree(record, {
|
|
433
|
+
detailed,
|
|
434
|
+
showErrors: detailed,
|
|
435
|
+
blockingThreshold: this.blockingThreshold
|
|
436
|
+
}));
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Run one call of `fn` with tracking (shared by createTrackable and track)
|
|
440
|
+
*/
|
|
441
|
+
_invoke(fn, thisArg, args, options, label) {
|
|
442
|
+
// Disabled: call straight through
|
|
443
|
+
if (!this.enabled) {
|
|
444
|
+
return fn.apply(thisArg, args);
|
|
445
|
+
}
|
|
446
|
+
// Apply sampling - randomly decide whether to track this call
|
|
447
|
+
const sampleRate = options.sampleRate !== undefined ?
|
|
448
|
+
clampRate(options.sampleRate) : this.defaultSampleRate;
|
|
449
|
+
const shouldSample = Math.random() < sampleRate;
|
|
450
|
+
// If not sampling this call, just execute the function directly
|
|
451
|
+
if (!shouldSample) {
|
|
452
|
+
return fn.apply(thisArg, args);
|
|
453
|
+
}
|
|
454
|
+
const nested = this.enableNestedTracking && options.enableNestedTracking !== false;
|
|
455
|
+
const explicitParentId = options.parentExecutionId || null;
|
|
456
|
+
const parentExecutionId = explicitParentId !== null && explicitParentId !== void 0 ? explicitParentId : (nested ? this.context.current() : null);
|
|
457
|
+
// Start tracking the execution
|
|
458
|
+
// The options object already has the TimerOptions fields; pass it as is
|
|
459
|
+
const executionId = this.startTimer(label, parentExecutionId, options, args);
|
|
460
|
+
let result;
|
|
461
|
+
try {
|
|
462
|
+
// Execute the function with the caller's `this`, as the current
|
|
463
|
+
// execution so nested tracked calls attach to it
|
|
464
|
+
if (this.wrappers.length > 0) {
|
|
465
|
+
result = this._wrap({ id: executionId, label, parentId: parentExecutionId }, () => (nested ?
|
|
466
|
+
this.context.apply(executionId, fn, thisArg, args) :
|
|
467
|
+
fn.apply(thisArg, args)));
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
result = nested ?
|
|
471
|
+
this.context.apply(executionId, fn, thisArg, args) :
|
|
472
|
+
fn.apply(thisArg, args);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
// Handle synchronous errors
|
|
477
|
+
this.endTimer(executionId, undefined, error);
|
|
478
|
+
throw error; // Re-throw to preserve error behavior
|
|
479
|
+
}
|
|
480
|
+
// Native promises (incl. async functions): time until settled.
|
|
481
|
+
// Other thenables are returned untouched - calling their then() could
|
|
482
|
+
// trigger side effects (e.g. query builders) or change their type.
|
|
483
|
+
if (isNativePromise(result)) {
|
|
484
|
+
this.markSyncEnd(executionId);
|
|
485
|
+
return result.then((resolvedValue) => {
|
|
486
|
+
this.endTimer(executionId, resolvedValue);
|
|
487
|
+
return resolvedValue;
|
|
488
|
+
}, (error) => {
|
|
489
|
+
this.endTimer(executionId, undefined, error);
|
|
490
|
+
throw error; // Re-throw to preserve error behavior
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
// Handle synchronous functions
|
|
494
|
+
this.endTimer(executionId, result);
|
|
495
|
+
return result;
|
|
211
496
|
}
|
|
212
497
|
/**
|
|
213
498
|
* Create a trackable version of a function
|
|
@@ -216,65 +501,80 @@ export class OptimizedExecutionTracker {
|
|
|
216
501
|
* @returns Tracked version of the function
|
|
217
502
|
*/
|
|
218
503
|
createTrackable(fn, options = {}) {
|
|
504
|
+
var _a;
|
|
219
505
|
if (typeof fn !== 'function') {
|
|
220
506
|
throw new Error('First argument must be a function');
|
|
221
507
|
}
|
|
222
508
|
const label = options.label || fn.name || 'anonymous';
|
|
223
|
-
const
|
|
224
|
-
const silent = options.hasOwnProperty('silent') ? options.silent : this.silent;
|
|
509
|
+
const silent = (_a = options.silent) !== null && _a !== void 0 ? _a : this.silent;
|
|
225
510
|
const trackingMode = options.trackingMode || this.trackingMode;
|
|
226
|
-
const sampleRate = options.hasOwnProperty('sampleRate') ?
|
|
227
|
-
Math.max(0, Math.min(1, options.sampleRate)) : 1.0;
|
|
228
511
|
// Performance optimization - if in performance mode and silent, return original function
|
|
229
512
|
if (trackingMode === TrackingModes.PERFORMANCE &&
|
|
230
|
-
|
|
513
|
+
silent &&
|
|
231
514
|
!this.enableNestedTracking) {
|
|
232
515
|
return fn;
|
|
233
516
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
// If not sampling this call, just execute the function directly
|
|
239
|
-
if (!shouldSample) {
|
|
240
|
-
return fn.apply(this, args);
|
|
241
|
-
}
|
|
242
|
-
// Start tracking the execution
|
|
243
|
-
const executionId = this.startTimer(label, parentExecutionId, {
|
|
244
|
-
trackMemory: options.trackMemory !== undefined ? options.trackMemory : this.showMemoryUsage,
|
|
245
|
-
captureArgs: options.captureArgs,
|
|
246
|
-
args: args
|
|
247
|
-
});
|
|
248
|
-
try {
|
|
249
|
-
// Execute the function with correct this context
|
|
250
|
-
const result = fn.apply(this, args);
|
|
251
|
-
// Handle promises
|
|
252
|
-
if (result && typeof result.then === 'function') {
|
|
253
|
-
return result
|
|
254
|
-
.then((resolvedValue) => {
|
|
255
|
-
this.endTimer(executionId, resolvedValue);
|
|
256
|
-
return resolvedValue;
|
|
257
|
-
})
|
|
258
|
-
.catch((error) => {
|
|
259
|
-
this.endTimer(executionId, undefined, error);
|
|
260
|
-
throw error; // Re-throw to preserve error behavior
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
// Handle synchronous functions
|
|
264
|
-
this.endTimer(executionId, result);
|
|
265
|
-
return result;
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
// Handle synchronous errors
|
|
269
|
-
this.endTimer(executionId, undefined, error);
|
|
270
|
-
throw error; // Re-throw to preserve error behavior
|
|
271
|
-
}
|
|
517
|
+
const invoke = (thisArg, args) => this._invoke(fn, thisArg, args, options, label);
|
|
518
|
+
// A regular function (not an arrow) so callers keep their own `this`
|
|
519
|
+
const trackedFn = function (...args) {
|
|
520
|
+
return invoke(this, args);
|
|
272
521
|
};
|
|
273
522
|
// Preserve function properties
|
|
274
523
|
Object.defineProperty(trackedFn, 'name', { value: fn.name, configurable: true });
|
|
275
524
|
Object.defineProperty(trackedFn, 'length', { value: fn.length });
|
|
276
525
|
return trackedFn;
|
|
277
526
|
}
|
|
527
|
+
/**
|
|
528
|
+
* Start a manually-ended execution (a "span"). Use `run()` to execute code
|
|
529
|
+
* as part of it, so tracked calls inside attach as children, and `end()`
|
|
530
|
+
* when the work is finished.
|
|
531
|
+
*/
|
|
532
|
+
startSpan(label, options = {}) {
|
|
533
|
+
if (!this.enabled) {
|
|
534
|
+
return NOOP_SPAN;
|
|
535
|
+
}
|
|
536
|
+
const sampleRate = options.sampleRate !== undefined ? clampRate(options.sampleRate) : this.defaultSampleRate;
|
|
537
|
+
if (!(Math.random() < sampleRate)) {
|
|
538
|
+
return NOOP_SPAN;
|
|
539
|
+
}
|
|
540
|
+
const nested = this.enableNestedTracking && options.enableNestedTracking !== false;
|
|
541
|
+
const explicitParentId = options.parentExecutionId || null;
|
|
542
|
+
const parentExecutionId = explicitParentId !== null && explicitParentId !== void 0 ? explicitParentId : (nested ? this.context.current() : null);
|
|
543
|
+
const executionId = this.startTimer(label || 'anonymous', parentExecutionId, {
|
|
544
|
+
trackMemory: options.trackMemory,
|
|
545
|
+
silent: options.silent,
|
|
546
|
+
trackingMode: options.trackingMode,
|
|
547
|
+
threshold: options.threshold
|
|
548
|
+
});
|
|
549
|
+
let ended = false;
|
|
550
|
+
return {
|
|
551
|
+
id: executionId,
|
|
552
|
+
setLabel: (newLabel) => {
|
|
553
|
+
const execution = this.activeExecutions.get(executionId);
|
|
554
|
+
if (execution && newLabel) {
|
|
555
|
+
execution.label = newLabel;
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
run: (fn) => {
|
|
559
|
+
const runInContext = () => (nested ? this.context.run(executionId, fn) : fn());
|
|
560
|
+
const result = this.wrappers.length > 0 ?
|
|
561
|
+
this._wrap({ id: executionId, label: label || 'anonymous', parentId: parentExecutionId }, runInContext) :
|
|
562
|
+
runInContext();
|
|
563
|
+
// The span's synchronous part ends when its first run() returns
|
|
564
|
+
const execution = this.activeExecutions.get(executionId);
|
|
565
|
+
if (execution && execution.syncEndTime === undefined) {
|
|
566
|
+
execution.syncEndTime = getTimestamp();
|
|
567
|
+
}
|
|
568
|
+
return result;
|
|
569
|
+
},
|
|
570
|
+
end: (error) => {
|
|
571
|
+
if (ended)
|
|
572
|
+
return;
|
|
573
|
+
ended = true;
|
|
574
|
+
this.endTimer(executionId, undefined, (error !== null && error !== void 0 ? error : null));
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
278
578
|
/**
|
|
279
579
|
* Track a module's methods
|
|
280
580
|
* @param module Module to track
|
|
@@ -282,6 +582,7 @@ export class OptimizedExecutionTracker {
|
|
|
282
582
|
* @returns Tracked module
|
|
283
583
|
*/
|
|
284
584
|
trackModule(module, options = {}) {
|
|
585
|
+
var _a;
|
|
285
586
|
if (!module || typeof module !== 'object') {
|
|
286
587
|
throw new Error('Module must be an object');
|
|
287
588
|
}
|
|
@@ -289,7 +590,7 @@ export class OptimizedExecutionTracker {
|
|
|
289
590
|
const trackingMode = options.trackingMode || this.trackingMode;
|
|
290
591
|
// Performance optimization - if in performance mode and silent, return original module
|
|
291
592
|
if (trackingMode === TrackingModes.PERFORMANCE &&
|
|
292
|
-
(options.silent
|
|
593
|
+
((_a = options.silent) !== null && _a !== void 0 ? _a : this.silent) &&
|
|
293
594
|
!this.enableNestedTracking) {
|
|
294
595
|
return module;
|
|
295
596
|
}
|
|
@@ -322,17 +623,29 @@ export class OptimizedExecutionTracker {
|
|
|
322
623
|
* @returns Function result
|
|
323
624
|
*/
|
|
324
625
|
track(fn, options = {}) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
626
|
+
var _a;
|
|
627
|
+
if (typeof fn !== 'function') {
|
|
628
|
+
throw new Error('First argument must be a function');
|
|
629
|
+
}
|
|
630
|
+
// Same fast path as createTrackable, without building a wrapper per call
|
|
631
|
+
if ((options.trackingMode || this.trackingMode) === TrackingModes.PERFORMANCE &&
|
|
632
|
+
((_a = options.silent) !== null && _a !== void 0 ? _a : this.silent) &&
|
|
633
|
+
!this.enableNestedTracking) {
|
|
634
|
+
return fn.apply(options.thisContext, options.args || []);
|
|
635
|
+
}
|
|
636
|
+
const label = options.label || fn.name || 'anonymous';
|
|
637
|
+
return this._invoke(fn, options.thisContext, (options.args || []), options, label);
|
|
328
638
|
}
|
|
329
639
|
/**
|
|
330
640
|
* Reset the tracker state
|
|
331
641
|
*/
|
|
332
642
|
reset() {
|
|
333
|
-
this.executionTree =
|
|
643
|
+
this.executionTree = new Map();
|
|
334
644
|
this.currentExecutionId = 0;
|
|
335
645
|
this.activeExecutions = new Map();
|
|
646
|
+
if (this.stats) {
|
|
647
|
+
this.stats.reset();
|
|
648
|
+
}
|
|
336
649
|
}
|
|
337
650
|
}
|
|
338
651
|
/**
|
|
@@ -344,22 +657,34 @@ export class OptimizedPerformanceMonitor {
|
|
|
344
657
|
* @param options Configuration options
|
|
345
658
|
*/
|
|
346
659
|
constructor(options = {}) {
|
|
660
|
+
var _a, _b, _c;
|
|
347
661
|
this.config = {
|
|
348
662
|
trackingMode: options.trackingMode || DEFAULT_TRACKING_MODE,
|
|
349
663
|
silent: options.silent || false,
|
|
350
664
|
trackMemory: options.trackMemory !== undefined ? options.trackMemory : true,
|
|
351
665
|
enableNestedTracking: options.enableNestedTracking !== undefined ? options.enableNestedTracking : true,
|
|
352
|
-
threshold: options.threshold
|
|
353
|
-
sampleRate: options.sampleRate !== undefined ?
|
|
354
|
-
|
|
666
|
+
threshold: (_a = options.threshold) !== null && _a !== void 0 ? _a : 0,
|
|
667
|
+
sampleRate: options.sampleRate !== undefined ? clampRate(options.sampleRate) : 1.0,
|
|
668
|
+
enabled: (_b = options.enabled) !== null && _b !== void 0 ? _b : true,
|
|
669
|
+
blockingThreshold: (_c = options.blockingThreshold) !== null && _c !== void 0 ? _c : 50
|
|
355
670
|
};
|
|
356
671
|
this.executionTracker = new OptimizedExecutionTracker({
|
|
357
672
|
trackingMode: this.config.trackingMode,
|
|
358
673
|
silent: this.config.silent,
|
|
359
674
|
showMemoryUsage: this.config.trackMemory,
|
|
360
675
|
enableNestedTracking: this.config.enableNestedTracking,
|
|
361
|
-
threshold: this.config.threshold
|
|
676
|
+
threshold: this.config.threshold,
|
|
677
|
+
sampleRate: this.config.sampleRate,
|
|
678
|
+
enabled: this.config.enabled,
|
|
679
|
+
blockingThreshold: this.config.blockingThreshold,
|
|
680
|
+
stats: options.stats,
|
|
681
|
+
reporter: options.reporter,
|
|
682
|
+
plugins: options.plugins,
|
|
683
|
+
asyncLocalStorage: options.asyncLocalStorage
|
|
362
684
|
});
|
|
685
|
+
if (options.onComplete) {
|
|
686
|
+
this.onComplete(options.onComplete);
|
|
687
|
+
}
|
|
363
688
|
}
|
|
364
689
|
/**
|
|
365
690
|
* Track a function execution
|
|
@@ -371,20 +696,9 @@ export class OptimizedPerformanceMonitor {
|
|
|
371
696
|
if (typeof fn !== 'function') {
|
|
372
697
|
throw new Error('First argument must be a function');
|
|
373
698
|
}
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
trackingMode: options.trackingMode || this.config.trackingMode,
|
|
378
|
-
silent: options.hasOwnProperty('silent') ? options.silent : this.config.silent,
|
|
379
|
-
trackMemory: options.hasOwnProperty('trackMemory') ? options.trackMemory : this.config.trackMemory,
|
|
380
|
-
sampleRate: options.hasOwnProperty('sampleRate') ? options.sampleRate : this.config.sampleRate,
|
|
381
|
-
captureArgs: options.hasOwnProperty('captureArgs') ? options.captureArgs : false,
|
|
382
|
-
parentExecutionId: options.parentExecutionId || null,
|
|
383
|
-
thisContext: options.thisContext,
|
|
384
|
-
args: options.args
|
|
385
|
-
};
|
|
386
|
-
// Use the tracker to directly track the function
|
|
387
|
-
return this.executionTracker.track(fn, trackingOptions);
|
|
699
|
+
// Only per-call options are passed down; anything left out falls back to
|
|
700
|
+
// this instance's *current* configuration when the call runs.
|
|
701
|
+
return this.executionTracker.track(fn, options);
|
|
388
702
|
}
|
|
389
703
|
/**
|
|
390
704
|
* Create a trackable version of a function
|
|
@@ -396,17 +710,7 @@ export class OptimizedPerformanceMonitor {
|
|
|
396
710
|
if (typeof fn !== 'function') {
|
|
397
711
|
throw new Error('First argument must be a function');
|
|
398
712
|
}
|
|
399
|
-
|
|
400
|
-
const trackingOptions = {
|
|
401
|
-
label: options.label || fn.name || 'anonymous',
|
|
402
|
-
trackingMode: options.trackingMode || this.config.trackingMode,
|
|
403
|
-
silent: options.hasOwnProperty('silent') ? options.silent : this.config.silent,
|
|
404
|
-
trackMemory: options.hasOwnProperty('trackMemory') ? options.trackMemory : this.config.trackMemory,
|
|
405
|
-
sampleRate: options.hasOwnProperty('sampleRate') ? options.sampleRate : this.config.sampleRate,
|
|
406
|
-
captureArgs: options.hasOwnProperty('captureArgs') ? options.captureArgs : false,
|
|
407
|
-
parentExecutionId: options.parentExecutionId || null
|
|
408
|
-
};
|
|
409
|
-
return this.executionTracker.createTrackable(fn, trackingOptions);
|
|
713
|
+
return this.executionTracker.createTrackable(fn, options);
|
|
410
714
|
}
|
|
411
715
|
/**
|
|
412
716
|
* Register a module for tracking
|
|
@@ -418,16 +722,201 @@ export class OptimizedPerformanceMonitor {
|
|
|
418
722
|
if (!module || typeof module !== 'object') {
|
|
419
723
|
throw new Error('Module must be an object');
|
|
420
724
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
725
|
+
return this.executionTracker.trackModule(module, options);
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Track every method of an object or class instance, including methods
|
|
729
|
+
* inherited from its prototype, without copying it. Returns a proxy:
|
|
730
|
+
* state changes go to the original object, and methods run with the
|
|
731
|
+
* original object as `this` (so private #fields keep working).
|
|
732
|
+
*/
|
|
733
|
+
trackObject(target, options = {}) {
|
|
734
|
+
var _a;
|
|
735
|
+
if (!target || (typeof target !== 'object' && typeof target !== 'function')) {
|
|
736
|
+
throw new Error('trackObject expects an object');
|
|
737
|
+
}
|
|
738
|
+
const ctorName = target.constructor && target.constructor !== Object
|
|
739
|
+
? target.constructor.name : '';
|
|
740
|
+
const prefix = (_a = options.prefix) !== null && _a !== void 0 ? _a : (ctorName ? `${ctorName}.` : '');
|
|
741
|
+
const trackOptions = { ...options };
|
|
742
|
+
delete trackOptions.prefix;
|
|
743
|
+
const cache = new Map();
|
|
744
|
+
const wrap = (original, key, proxy) => {
|
|
745
|
+
var _a;
|
|
746
|
+
const tracked = this.createTrackable(original, {
|
|
747
|
+
...trackOptions,
|
|
748
|
+
label: (_a = trackOptions.label) !== null && _a !== void 0 ? _a : `${prefix}${key}`
|
|
749
|
+
});
|
|
750
|
+
return function (...args) {
|
|
751
|
+
return tracked.apply(this === proxy ? target : this, args);
|
|
752
|
+
};
|
|
429
753
|
};
|
|
430
|
-
|
|
754
|
+
const proxy = new Proxy(target, {
|
|
755
|
+
get: (obj, key, receiver) => {
|
|
756
|
+
const value = Reflect.get(obj, key, receiver);
|
|
757
|
+
if (typeof value !== 'function' || typeof key === 'symbol' || key === 'constructor' ||
|
|
758
|
+
Object.prototype[key] === value) {
|
|
759
|
+
return value;
|
|
760
|
+
}
|
|
761
|
+
const hit = cache.get(key);
|
|
762
|
+
if (hit && hit.original === value) {
|
|
763
|
+
return hit.tracked;
|
|
764
|
+
}
|
|
765
|
+
const tracked = wrap(value, key, proxy);
|
|
766
|
+
cache.set(key, { original: value, tracked });
|
|
767
|
+
return tracked;
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
return proxy;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Method decorator. Works with TypeScript 5 / TC39 standard decorators and
|
|
774
|
+
* with legacy `experimentalDecorators`. The label defaults to
|
|
775
|
+
* "ClassName.method".
|
|
776
|
+
*
|
|
777
|
+
* class UserService {
|
|
778
|
+
* @tp.traced()
|
|
779
|
+
* async getUser(id: string) { ... }
|
|
780
|
+
* }
|
|
781
|
+
*/
|
|
782
|
+
traced(options = {}) {
|
|
783
|
+
const trackables = new Map();
|
|
784
|
+
const trackedFor = (fn, label) => {
|
|
785
|
+
let tracked = trackables.get(label);
|
|
786
|
+
if (!tracked) {
|
|
787
|
+
tracked = this.createTrackable(fn, { ...options, label });
|
|
788
|
+
trackables.set(label, tracked);
|
|
789
|
+
}
|
|
790
|
+
return tracked;
|
|
791
|
+
};
|
|
792
|
+
const ownerName = (receiver) => { var _a; return typeof receiver === 'function' ? receiver.name : ((_a = receiver === null || receiver === void 0 ? void 0 : receiver.constructor) === null || _a === void 0 ? void 0 : _a.name) || 'anonymous'; };
|
|
793
|
+
const wrapMethod = (fn, methodName) => {
|
|
794
|
+
const wrapped = function (...args) {
|
|
795
|
+
var _a;
|
|
796
|
+
const label = (_a = options.label) !== null && _a !== void 0 ? _a : `${ownerName(this)}.${methodName}`;
|
|
797
|
+
return trackedFor(fn, label).apply(this, args);
|
|
798
|
+
};
|
|
799
|
+
Object.defineProperty(wrapped, 'name', { value: fn.name, configurable: true });
|
|
800
|
+
Object.defineProperty(wrapped, 'length', { value: fn.length, configurable: true });
|
|
801
|
+
return wrapped;
|
|
802
|
+
};
|
|
803
|
+
return (target, context, descriptor) => {
|
|
804
|
+
// Standard decorators: (method, context)
|
|
805
|
+
if (typeof target === 'function' && context && typeof context === 'object' && 'kind' in context) {
|
|
806
|
+
if (context.kind !== 'method') {
|
|
807
|
+
throw new TypeError('@traced can only decorate methods');
|
|
808
|
+
}
|
|
809
|
+
return wrapMethod(target, String(context.name));
|
|
810
|
+
}
|
|
811
|
+
// Legacy decorators: (prototype or constructor, key, descriptor)
|
|
812
|
+
if (descriptor && typeof descriptor.value === 'function') {
|
|
813
|
+
descriptor.value = wrapMethod(descriptor.value, String(context));
|
|
814
|
+
return descriptor;
|
|
815
|
+
}
|
|
816
|
+
throw new TypeError('@traced can only decorate methods');
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Start a manually-ended execution (a span), e.g. for a request:
|
|
821
|
+
*
|
|
822
|
+
* const span = tp.start('GET /users');
|
|
823
|
+
* span.run(() => next()); // tracked calls inside become children
|
|
824
|
+
* res.on('finish', () => span.end());
|
|
825
|
+
*/
|
|
826
|
+
start(label, options = {}) {
|
|
827
|
+
return this.executionTracker.startSpan(label, options);
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Register a plugin (exporter, custom sink). Returns an unsubscribe function.
|
|
831
|
+
*/
|
|
832
|
+
use(plugin) {
|
|
833
|
+
return this.executionTracker.use(plugin);
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Call `listener` with every finished top-level execution tree (even when
|
|
837
|
+
* silent or below threshold). Returns an unsubscribe function.
|
|
838
|
+
*/
|
|
839
|
+
onComplete(listener) {
|
|
840
|
+
return this.use({ name: 'onComplete', onComplete: listener });
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Call `listener` for every finished call (nested ones included) that
|
|
844
|
+
* took at least `threshold` ms. Pass a number, or per-label thresholds
|
|
845
|
+
* with an optional '*' default:
|
|
846
|
+
*
|
|
847
|
+
* tp.onSlow(200, (call, root) => logger.warn(`${call.label} took ${call.duration}ms in ${root.label}`));
|
|
848
|
+
* tp.onSlow({ 'db.query': 50, 'GET /search': 500, '*': 1000 }, alert);
|
|
849
|
+
*
|
|
850
|
+
* Works in every tracking mode and when silent. Returns an unsubscribe function.
|
|
851
|
+
*/
|
|
852
|
+
onSlow(threshold, listener) {
|
|
853
|
+
const limitFor = typeof threshold === 'number'
|
|
854
|
+
? () => threshold
|
|
855
|
+
: (label) => (label in threshold ? threshold[label] : threshold['*']);
|
|
856
|
+
return this.use({
|
|
857
|
+
name: 'onSlow',
|
|
858
|
+
onComplete: root => {
|
|
859
|
+
walkRecords(root, record => {
|
|
860
|
+
const limit = limitFor(record.label);
|
|
861
|
+
if (limit !== undefined && record.duration >= limit)
|
|
862
|
+
listener(record, root);
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
getStats(label) {
|
|
868
|
+
const stats = this.executionTracker.stats;
|
|
869
|
+
if (label !== undefined) {
|
|
870
|
+
return stats ? stats.get(label) : undefined;
|
|
871
|
+
}
|
|
872
|
+
return stats ? stats.all() : {};
|
|
873
|
+
}
|
|
874
|
+
/** Clear collected statistics */
|
|
875
|
+
resetStats() {
|
|
876
|
+
if (this.executionTracker.stats) {
|
|
877
|
+
this.executionTracker.stats.reset();
|
|
878
|
+
}
|
|
879
|
+
return this;
|
|
880
|
+
}
|
|
881
|
+
/** Statistics as a text table, slowest total first */
|
|
882
|
+
formatStats() {
|
|
883
|
+
return formatStatsTable(this.getStats());
|
|
884
|
+
}
|
|
885
|
+
/** Print the statistics table to the console */
|
|
886
|
+
printStats() {
|
|
887
|
+
console.log(this.formatStats());
|
|
888
|
+
return this;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Start recording finished calls for a Chrome/Perfetto trace.
|
|
892
|
+
*
|
|
893
|
+
* const recording = tp.startTrace();
|
|
894
|
+
* await work();
|
|
895
|
+
* fs.writeFileSync('trace.json', JSON.stringify(recording.stop()));
|
|
896
|
+
*
|
|
897
|
+
* Open the file in https://ui.perfetto.dev or chrome://tracing.
|
|
898
|
+
*/
|
|
899
|
+
startTrace(options = {}) {
|
|
900
|
+
let unsubscribe = () => undefined;
|
|
901
|
+
const recorder = createTraceRecorder({ maxRoots: options.maxRoots, onStop: () => unsubscribe() });
|
|
902
|
+
unsubscribe = this.use(recorder);
|
|
903
|
+
return recorder;
|
|
904
|
+
}
|
|
905
|
+
/** Turn tracking on */
|
|
906
|
+
enable() {
|
|
907
|
+
this.config.enabled = true;
|
|
908
|
+
this.executionTracker.enabled = true;
|
|
909
|
+
return this;
|
|
910
|
+
}
|
|
911
|
+
/** Turn tracking off: tracked functions become plain pass-through calls */
|
|
912
|
+
disable() {
|
|
913
|
+
this.config.enabled = false;
|
|
914
|
+
this.executionTracker.enabled = false;
|
|
915
|
+
return this;
|
|
916
|
+
}
|
|
917
|
+
/** Whether tracking is on */
|
|
918
|
+
isEnabled() {
|
|
919
|
+
return this.config.enabled;
|
|
431
920
|
}
|
|
432
921
|
/**
|
|
433
922
|
* Set configuration options
|
|
@@ -462,7 +951,25 @@ export class OptimizedPerformanceMonitor {
|
|
|
462
951
|
}
|
|
463
952
|
// Update sampleRate if specified
|
|
464
953
|
if (config.sampleRate !== undefined) {
|
|
465
|
-
this.config.sampleRate =
|
|
954
|
+
this.config.sampleRate = clampRate(config.sampleRate);
|
|
955
|
+
this.executionTracker.defaultSampleRate = this.config.sampleRate;
|
|
956
|
+
}
|
|
957
|
+
if (config.enabled !== undefined) {
|
|
958
|
+
this.config.enabled = config.enabled;
|
|
959
|
+
this.executionTracker.enabled = config.enabled;
|
|
960
|
+
}
|
|
961
|
+
if (config.blockingThreshold !== undefined) {
|
|
962
|
+
this.config.blockingThreshold = config.blockingThreshold;
|
|
963
|
+
this.executionTracker.blockingThreshold = config.blockingThreshold;
|
|
964
|
+
}
|
|
965
|
+
if (config.reporter !== undefined) {
|
|
966
|
+
this.executionTracker.reporter = config.reporter;
|
|
967
|
+
}
|
|
968
|
+
if (config.plugins) {
|
|
969
|
+
config.plugins.forEach(plugin => this.use(plugin));
|
|
970
|
+
}
|
|
971
|
+
if (config.onComplete) {
|
|
972
|
+
this.onComplete(config.onComplete);
|
|
466
973
|
}
|
|
467
974
|
return { ...this.config };
|
|
468
975
|
}
|
|
@@ -494,6 +1001,12 @@ export class OptimizedPerformanceMonitor {
|
|
|
494
1001
|
this.executionTracker.trackingMode = mode;
|
|
495
1002
|
return this;
|
|
496
1003
|
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Get the current tracking mode
|
|
1006
|
+
*/
|
|
1007
|
+
getTrackingMode() {
|
|
1008
|
+
return this.config.trackingMode;
|
|
1009
|
+
}
|
|
497
1010
|
/**
|
|
498
1011
|
* Enable memory usage tracking
|
|
499
1012
|
* @returns This instance for chaining
|
|
@@ -518,11 +1031,12 @@ export class OptimizedPerformanceMonitor {
|
|
|
518
1031
|
* @returns This instance for chaining
|
|
519
1032
|
*/
|
|
520
1033
|
setSampleRate(rate) {
|
|
521
|
-
this.config.sampleRate =
|
|
1034
|
+
this.config.sampleRate = clampRate(rate);
|
|
1035
|
+
this.executionTracker.defaultSampleRate = this.config.sampleRate;
|
|
522
1036
|
return this;
|
|
523
1037
|
}
|
|
524
1038
|
/**
|
|
525
|
-
* Reset the tracker state
|
|
1039
|
+
* Reset the tracker state (in-flight executions and statistics)
|
|
526
1040
|
* @returns This instance for chaining
|
|
527
1041
|
*/
|
|
528
1042
|
reset() {
|
|
@@ -530,6 +1044,4 @@ export class OptimizedPerformanceMonitor {
|
|
|
530
1044
|
return this;
|
|
531
1045
|
}
|
|
532
1046
|
}
|
|
533
|
-
// Remove singleton instance to avoid circular dependency - it will be created in index.ts
|
|
534
|
-
// export const optimizedTracePerf = new OptimizedPerformanceMonitor();
|
|
535
1047
|
//# sourceMappingURL=optimized-performance-monitor.js.map
|