traceperf 0.2.1 → 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.
Files changed (268) hide show
  1. package/README.md +608 -253
  2. package/dist/__tests__/helpers/console.d.ts +9 -0
  3. package/dist/__tests__/helpers/console.js +25 -0
  4. package/dist/__tests__/helpers/console.js.map +1 -0
  5. package/dist/auto.d.ts +30 -0
  6. package/dist/auto.js +91 -0
  7. package/dist/auto.js.map +1 -0
  8. package/dist/browser.d.ts +52 -144
  9. package/dist/browser.js +112 -439
  10. package/dist/browser.js.map +1 -1
  11. package/dist/cli.d.ts +68 -0
  12. package/dist/cli.js +528 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/core/allocations.d.ts +38 -0
  15. package/dist/core/allocations.js +85 -0
  16. package/dist/core/allocations.js.map +1 -0
  17. package/dist/core/cpu-profile.d.ts +84 -0
  18. package/dist/core/cpu-profile.js +181 -0
  19. package/dist/core/cpu-profile.js.map +1 -0
  20. package/dist/core/env.d.ts +10 -0
  21. package/dist/core/env.js +40 -0
  22. package/dist/core/env.js.map +1 -0
  23. package/dist/core/event-loop.d.ts +30 -0
  24. package/dist/core/event-loop.js +43 -0
  25. package/dist/core/event-loop.js.map +1 -0
  26. package/dist/core/explain.d.ts +48 -0
  27. package/dist/core/explain.js +236 -0
  28. package/dist/core/explain.js.map +1 -0
  29. package/dist/core/exporters.d.ts +111 -0
  30. package/dist/core/exporters.js +236 -0
  31. package/dist/core/exporters.js.map +1 -0
  32. package/dist/core/index.d.ts +9 -3
  33. package/dist/core/index.js +9 -3
  34. package/dist/core/index.js.map +1 -1
  35. package/dist/core/optimized-performance-monitor.d.ts +180 -26
  36. package/dist/core/optimized-performance-monitor.js +687 -175
  37. package/dist/core/optimized-performance-monitor.js.map +1 -1
  38. package/dist/core/prometheus.d.ts +36 -0
  39. package/dist/core/prometheus.js +93 -0
  40. package/dist/core/prometheus.js.map +1 -0
  41. package/dist/core/records.d.ts +97 -0
  42. package/dist/core/records.js +87 -0
  43. package/dist/core/records.js.map +1 -0
  44. package/dist/core/stats.d.ts +45 -0
  45. package/dist/core/stats.js +136 -0
  46. package/dist/core/stats.js.map +1 -0
  47. package/dist/core/timeline.d.ts +28 -0
  48. package/dist/core/timeline.js +65 -0
  49. package/dist/core/timeline.js.map +1 -0
  50. package/dist/drivers.d.ts +51 -0
  51. package/dist/drivers.js +205 -0
  52. package/dist/drivers.js.map +1 -0
  53. package/dist/esm/auto.d.ts +30 -0
  54. package/dist/esm/auto.js +87 -0
  55. package/dist/esm/auto.js.map +1 -0
  56. package/dist/esm/browser.d.ts +52 -144
  57. package/dist/esm/browser.js +101 -445
  58. package/dist/esm/browser.js.map +1 -1
  59. package/dist/esm/cli.d.ts +68 -0
  60. package/dist/esm/cli.js +520 -0
  61. package/dist/esm/cli.js.map +1 -0
  62. package/dist/esm/core/allocations.d.ts +38 -0
  63. package/dist/esm/core/allocations.js +82 -0
  64. package/dist/esm/core/allocations.js.map +1 -0
  65. package/dist/esm/core/cpu-profile.d.ts +84 -0
  66. package/dist/esm/core/cpu-profile.js +176 -0
  67. package/dist/esm/core/cpu-profile.js.map +1 -0
  68. package/dist/esm/core/env.d.ts +10 -0
  69. package/dist/esm/core/env.js +35 -0
  70. package/dist/esm/core/env.js.map +1 -0
  71. package/dist/esm/core/event-loop.d.ts +30 -0
  72. package/dist/esm/core/event-loop.js +40 -0
  73. package/dist/esm/core/event-loop.js.map +1 -0
  74. package/dist/esm/core/explain.d.ts +48 -0
  75. package/dist/esm/core/explain.js +232 -0
  76. package/dist/esm/core/explain.js.map +1 -0
  77. package/dist/esm/core/exporters.d.ts +111 -0
  78. package/dist/esm/core/exporters.js +229 -0
  79. package/dist/esm/core/exporters.js.map +1 -0
  80. package/dist/esm/core/index.d.ts +10 -4
  81. package/dist/esm/core/index.js +10 -4
  82. package/dist/esm/core/index.js.map +1 -1
  83. package/dist/esm/core/optimized-performance-monitor.d.ts +180 -26
  84. package/dist/esm/core/optimized-performance-monitor.js +685 -173
  85. package/dist/esm/core/optimized-performance-monitor.js.map +1 -1
  86. package/dist/esm/core/prometheus.d.ts +36 -0
  87. package/dist/esm/core/prometheus.js +89 -0
  88. package/dist/esm/core/prometheus.js.map +1 -0
  89. package/dist/esm/core/records.d.ts +97 -0
  90. package/dist/esm/core/records.js +82 -0
  91. package/dist/esm/core/records.js.map +1 -0
  92. package/dist/esm/core/stats.d.ts +45 -0
  93. package/dist/esm/core/stats.js +131 -0
  94. package/dist/esm/core/stats.js.map +1 -0
  95. package/dist/esm/core/timeline.d.ts +28 -0
  96. package/dist/esm/core/timeline.js +62 -0
  97. package/dist/esm/core/timeline.js.map +1 -0
  98. package/dist/esm/drivers.d.ts +51 -0
  99. package/dist/esm/drivers.js +197 -0
  100. package/dist/esm/drivers.js.map +1 -0
  101. package/dist/esm/index.d.ts +13 -5
  102. package/dist/esm/index.js +21 -17
  103. package/dist/esm/index.js.map +1 -1
  104. package/dist/esm/instrument.d.ts +88 -0
  105. package/dist/esm/instrument.js +274 -0
  106. package/dist/esm/instrument.js.map +1 -0
  107. package/dist/esm/integrations.d.ts +70 -0
  108. package/dist/esm/integrations.js +197 -0
  109. package/dist/esm/integrations.js.map +1 -0
  110. package/dist/esm/node-builtins.d.ts +1 -0
  111. package/dist/esm/node-builtins.js +32 -0
  112. package/dist/esm/node-builtins.js.map +1 -0
  113. package/dist/esm/package.json +3 -0
  114. package/dist/esm/register.d.ts +20 -0
  115. package/dist/esm/register.js +98 -0
  116. package/dist/esm/register.js.map +1 -0
  117. package/dist/esm/report-html.d.ts +11 -0
  118. package/dist/esm/report-html.js +106 -0
  119. package/dist/esm/report-html.js.map +1 -0
  120. package/dist/esm/types/index.d.ts +4 -80
  121. package/dist/esm/types/index.js +1 -2
  122. package/dist/esm/types/index.js.map +1 -1
  123. package/dist/esm/types/options.d.ts +72 -0
  124. package/dist/esm/types/options.js +2 -0
  125. package/dist/esm/types/options.js.map +1 -0
  126. package/dist/esm/watch.d.ts +17 -0
  127. package/dist/esm/watch.js +87 -0
  128. package/dist/esm/watch.js.map +1 -0
  129. package/dist/index.d.ts +10 -2
  130. package/dist/index.js +26 -29
  131. package/dist/index.js.map +1 -1
  132. package/dist/instrument.d.ts +88 -0
  133. package/dist/instrument.js +282 -0
  134. package/dist/instrument.js.map +1 -0
  135. package/dist/integrations.d.ts +70 -0
  136. package/dist/integrations.js +205 -0
  137. package/dist/integrations.js.map +1 -0
  138. package/dist/node-builtins.d.ts +1 -0
  139. package/dist/node-builtins.js +67 -0
  140. package/dist/node-builtins.js.map +1 -0
  141. package/dist/register.d.ts +20 -0
  142. package/dist/register.js +103 -0
  143. package/dist/register.js.map +1 -0
  144. package/dist/report-html.d.ts +11 -0
  145. package/dist/report-html.js +109 -0
  146. package/dist/report-html.js.map +1 -0
  147. package/dist/traceperf.browser.js +1910 -0
  148. package/dist/traceperf.browser.min.js +6 -0
  149. package/dist/types/index.d.ts +4 -80
  150. package/dist/types/index.js +1 -2
  151. package/dist/types/index.js.map +1 -1
  152. package/dist/types/options.d.ts +72 -0
  153. package/dist/types/{config.js → options.js} +1 -1
  154. package/dist/types/options.js.map +1 -0
  155. package/dist/watch.d.ts +17 -0
  156. package/dist/watch.js +90 -0
  157. package/dist/watch.js.map +1 -0
  158. package/package.json +99 -19
  159. package/dist/core/config.d.ts +0 -77
  160. package/dist/core/config.js +0 -116
  161. package/dist/core/config.js.map +0 -1
  162. package/dist/core/constants.d.ts +0 -78
  163. package/dist/core/constants.js +0 -109
  164. package/dist/core/constants.js.map +0 -1
  165. package/dist/core/logger.d.ts +0 -97
  166. package/dist/core/logger.js +0 -186
  167. package/dist/core/logger.js.map +0 -1
  168. package/dist/esm/core/config.d.ts +0 -77
  169. package/dist/esm/core/config.js +0 -112
  170. package/dist/esm/core/config.js.map +0 -1
  171. package/dist/esm/core/constants.d.ts +0 -78
  172. package/dist/esm/core/constants.js +0 -106
  173. package/dist/esm/core/constants.js.map +0 -1
  174. package/dist/esm/core/logger.d.ts +0 -97
  175. package/dist/esm/core/logger.js +0 -182
  176. package/dist/esm/core/logger.js.map +0 -1
  177. package/dist/esm/formatters/ascii.d.ts +0 -52
  178. package/dist/esm/formatters/ascii.js +0 -100
  179. package/dist/esm/formatters/ascii.js.map +0 -1
  180. package/dist/esm/formatters/cli.d.ts +0 -50
  181. package/dist/esm/formatters/cli.js +0 -96
  182. package/dist/esm/formatters/cli.js.map +0 -1
  183. package/dist/esm/formatters/index.d.ts +0 -3
  184. package/dist/esm/formatters/index.js +0 -4
  185. package/dist/esm/formatters/index.js.map +0 -1
  186. package/dist/esm/formatters/json.d.ts +0 -45
  187. package/dist/esm/formatters/json.js +0 -85
  188. package/dist/esm/formatters/json.js.map +0 -1
  189. package/dist/esm/trackers/execution.d.ts +0 -101
  190. package/dist/esm/trackers/execution.js +0 -226
  191. package/dist/esm/trackers/execution.js.map +0 -1
  192. package/dist/esm/trackers/index.d.ts +0 -2
  193. package/dist/esm/trackers/index.js +0 -3
  194. package/dist/esm/trackers/index.js.map +0 -1
  195. package/dist/esm/trackers/performance.d.ts +0 -68
  196. package/dist/esm/trackers/performance.js +0 -112
  197. package/dist/esm/trackers/performance.js.map +0 -1
  198. package/dist/esm/types/config.d.ts +0 -142
  199. package/dist/esm/types/config.js +0 -2
  200. package/dist/esm/types/config.js.map +0 -1
  201. package/dist/esm/types/logger.d.ts +0 -147
  202. package/dist/esm/types/logger.js +0 -2
  203. package/dist/esm/types/logger.js.map +0 -1
  204. package/dist/esm/utils/colors.d.ts +0 -87
  205. package/dist/esm/utils/colors.js +0 -146
  206. package/dist/esm/utils/colors.js.map +0 -1
  207. package/dist/esm/utils/index.d.ts +0 -3
  208. package/dist/esm/utils/index.js +0 -4
  209. package/dist/esm/utils/index.js.map +0 -1
  210. package/dist/esm/utils/proxy-tracker.d.ts +0 -68
  211. package/dist/esm/utils/proxy-tracker.js +0 -127
  212. package/dist/esm/utils/proxy-tracker.js.map +0 -1
  213. package/dist/esm/utils/stack.d.ts +0 -63
  214. package/dist/esm/utils/stack.js +0 -110
  215. package/dist/esm/utils/stack.js.map +0 -1
  216. package/dist/esm/utils/string.d.ts +0 -29
  217. package/dist/esm/utils/string.js +0 -45
  218. package/dist/esm/utils/string.js.map +0 -1
  219. package/dist/esm/utils/timing.d.ts +0 -65
  220. package/dist/esm/utils/timing.js +0 -99
  221. package/dist/esm/utils/timing.js.map +0 -1
  222. package/dist/formatters/ascii.d.ts +0 -52
  223. package/dist/formatters/ascii.js +0 -104
  224. package/dist/formatters/ascii.js.map +0 -1
  225. package/dist/formatters/cli.d.ts +0 -50
  226. package/dist/formatters/cli.js +0 -100
  227. package/dist/formatters/cli.js.map +0 -1
  228. package/dist/formatters/index.d.ts +0 -3
  229. package/dist/formatters/index.js +0 -20
  230. package/dist/formatters/index.js.map +0 -1
  231. package/dist/formatters/json.d.ts +0 -45
  232. package/dist/formatters/json.js +0 -89
  233. package/dist/formatters/json.js.map +0 -1
  234. package/dist/trackers/execution.d.ts +0 -101
  235. package/dist/trackers/execution.js +0 -230
  236. package/dist/trackers/execution.js.map +0 -1
  237. package/dist/trackers/index.d.ts +0 -2
  238. package/dist/trackers/index.js +0 -19
  239. package/dist/trackers/index.js.map +0 -1
  240. package/dist/trackers/performance.d.ts +0 -68
  241. package/dist/trackers/performance.js +0 -116
  242. package/dist/trackers/performance.js.map +0 -1
  243. package/dist/types/config.d.ts +0 -142
  244. package/dist/types/config.js.map +0 -1
  245. package/dist/types/logger.d.ts +0 -147
  246. package/dist/types/logger.js +0 -3
  247. package/dist/types/logger.js.map +0 -1
  248. package/dist/types.d.ts +0 -65
  249. package/dist/types.js +0 -6
  250. package/dist/types.js.map +0 -1
  251. package/dist/utils/colors.d.ts +0 -87
  252. package/dist/utils/colors.js +0 -160
  253. package/dist/utils/colors.js.map +0 -1
  254. package/dist/utils/index.d.ts +0 -3
  255. package/dist/utils/index.js +0 -20
  256. package/dist/utils/index.js.map +0 -1
  257. package/dist/utils/proxy-tracker.d.ts +0 -68
  258. package/dist/utils/proxy-tracker.js +0 -131
  259. package/dist/utils/proxy-tracker.js.map +0 -1
  260. package/dist/utils/stack.d.ts +0 -63
  261. package/dist/utils/stack.js +0 -116
  262. package/dist/utils/stack.js.map +0 -1
  263. package/dist/utils/string.d.ts +0 -29
  264. package/dist/utils/string.js +0 -50
  265. package/dist/utils/string.js.map +0 -1
  266. package/dist/utils/timing.d.ts +0 -65
  267. package/dist/utils/timing.js +0 -106
  268. 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
- // @ts-expect-error This is a browser-specific check
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
- // Use performance.now() in browsers (convert to nanoseconds)
47
- if (typeof performance !== 'undefined' && performance.now) {
48
- return performance.now() * 1000000;
49
- }
50
- // Use process.hrtime() in Node.js
51
- else if (typeof process !== 'undefined' && process.hrtime) {
52
- const hrTime = process.hrtime();
53
- return hrTime[0] * 1000000000 + hrTime[1];
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
- // Formatters for flow chart generation
80
- this._formatters = {
81
- indent: ' ',
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 : 100;
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 = this.trackingMode !== TrackingModes.PERFORMANCE && this.showMemoryUsage;
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 && options.args ?
116
- Array.from(options.args).map(arg => String(arg).substring(0, 50)) : [],
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 in non-performance modes
154
- if (this.trackingMode !== TrackingModes.PERFORMANCE && this.showMemoryUsage) {
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
- // Store in the execution tree
159
- this.executionTree[executionId] = { ...execution };
160
- // Generate flow chart if not silent
161
- if (!this.silent && this.trackingMode !== TrackingModes.PERFORMANCE) {
162
- const flowChart = this._generateExecutionFlowChart(execution);
163
- console.log(flowChart);
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
- * Generate a visualization of the execution flow
169
- * @param execution The execution to visualize
170
- * @param depth Current depth in the tree
171
- * @param isLastChild Whether this is the last child
172
- * @param ancestors Array tracking if parents were last children
173
- * @returns Formatted execution flow chart
174
- */
175
- _generateExecutionFlowChart(execution, depth = 0, isLastChild = true, ancestors = []) {
176
- // Skip in performance mode
177
- if (this.trackingMode === TrackingModes.PERFORMANCE) {
178
- return '';
179
- }
180
- // Build the flow chart
181
- let flowChart = '';
182
- // Format the execution line based on tracking mode
183
- const durationStr = (execution.duration / 1000000).toFixed(3);
184
- const formatters = this._formatters;
185
- let line = `${formatters.indent.repeat(depth)}${isLastChild ? formatters.lastChildPrefix : formatters.childPrefix}${execution.label}: ${durationStr} ms`;
186
- // Add memory usage if enabled and in detailed modes
187
- if (this.showMemoryUsage &&
188
- execution.memoryDelta !== undefined &&
189
- this.trackingMode !== TrackingModes.PERFORMANCE) {
190
- const memoryStr = (execution.memoryDelta / 1024).toFixed(2);
191
- const sign = execution.memoryDelta >= 0 ? '+' : '';
192
- line += ` ${formatters.memoryLabel}${sign}${memoryStr} KB`;
193
- }
194
- // Add error indicator
195
- if (execution.error && (this.trackingMode === TrackingModes.DETAILED || this.trackingMode === TrackingModes.DEBUG)) {
196
- line += ` ${formatters.errorMarker}${execution.error.message}`;
197
- }
198
- flowChart += line + '\n';
199
- // Process children (skip in performance mode)
200
- if (execution.children && execution.children.length > 0 && this.trackingMode !== TrackingModes.PERFORMANCE) {
201
- const childExecutions = execution.children
202
- .map(childId => this.executionTree[childId] || this.activeExecutions.get(childId))
203
- .filter(Boolean);
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 flowChart;
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 parentExecutionId = options.parentExecutionId || null;
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
- (options.silent || this.silent) &&
513
+ silent &&
231
514
  !this.enableNestedTracking) {
232
515
  return fn;
233
516
  }
234
- // Create the tracked function using arrow function to preserve 'this'
235
- const trackedFn = (...args) => {
236
- // Apply sampling - randomly decide whether to track this call
237
- const shouldSample = Math.random() < sampleRate;
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 || this.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
- // Create a trackable version and call it immediately
326
- const trackableFn = this.createTrackable(fn, options);
327
- return trackableFn.apply(options.thisContext || this, options.args || []);
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 || 100,
353
- sampleRate: options.sampleRate !== undefined ?
354
- Math.max(0, Math.min(1, options.sampleRate)) : 1.0 // Clamp between 0 and 1
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
- // Merge global config with function-specific options
375
- const trackingOptions = {
376
- label: options.label || fn.name || 'anonymous',
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
- // Merge global config with function-specific options
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
- // Merge global config with module-specific options
422
- const trackingOptions = {
423
- prefix: options.prefix || '',
424
- trackingMode: options.trackingMode || this.config.trackingMode,
425
- silent: options.hasOwnProperty('silent') ? options.silent : this.config.silent,
426
- trackMemory: options.hasOwnProperty('trackMemory') ? options.trackMemory : this.config.trackMemory,
427
- sampleRate: options.hasOwnProperty('sampleRate') ? options.sampleRate : this.config.sampleRate,
428
- captureArgs: options.hasOwnProperty('captureArgs') ? options.captureArgs : false
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
- return this.executionTracker.trackModule(module, trackingOptions);
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 = Math.max(0, Math.min(1, 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 = Math.max(0, Math.min(1, rate));
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