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