@contrail/util 1.1.15-alpha-6 → 1.1.15-alpha-8

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.
@@ -1,4 +1,13 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
3
12
  const performance_util_1 = require("./performance-util");
4
13
  function demonstrateStartEndSpan() {
@@ -71,5 +80,219 @@ function demonstrateWarningBehavior() {
71
80
  console.log('Attempted to end "Non-existent Span" (should see warning above)');
72
81
  (0, performance_util_1.displayTimingTree)();
73
82
  }
74
- demonstrateStartEndSpan();
75
- demonstrateWarningBehavior();
83
+ function demonstrateParentNodeId() {
84
+ return __awaiter(this, void 0, void 0, function* () {
85
+ console.log('\n--- Demonstrating parentNodeId Functionality ---\n');
86
+ (0, performance_util_1.clearDefaultTimingProfile)();
87
+ console.log('Example 5: Concurrent async operations with explicit parent');
88
+ yield (0, performance_util_1.withTimingAsync)('Main Operation', () => __awaiter(this, void 0, void 0, function* () {
89
+ const mainOpId = (0, performance_util_1.getCurrentParentNodeId)();
90
+ console.log(`Main Operation ID: ${mainOpId}`);
91
+ console.log('Starting concurrent operations...');
92
+ const results = yield Promise.all([
93
+ (0, performance_util_1.withTimingAsync)('Fetch User Data', () => __awaiter(this, void 0, void 0, function* () {
94
+ yield new Promise((resolve) => setTimeout(resolve, 80));
95
+ console.log(' ✓ User data fetched');
96
+ return 'user-data';
97
+ }), { parentNodeId: mainOpId }),
98
+ (0, performance_util_1.withTimingAsync)('Fetch Permissions', () => __awaiter(this, void 0, void 0, function* () {
99
+ yield new Promise((resolve) => setTimeout(resolve, 60));
100
+ console.log(' ✓ Permissions fetched');
101
+ return 'permissions-data';
102
+ }), { parentNodeId: mainOpId }),
103
+ (0, performance_util_1.withTimingAsync)('Fetch Preferences', () => __awaiter(this, void 0, void 0, function* () {
104
+ yield new Promise((resolve) => setTimeout(resolve, 100));
105
+ console.log(' ✓ Preferences fetched');
106
+ return 'preferences-data';
107
+ }), { parentNodeId: mainOpId }),
108
+ ]);
109
+ console.log('All concurrent operations completed:', results);
110
+ yield (0, performance_util_1.withTimingAsync)('Process Results', () => __awaiter(this, void 0, void 0, function* () {
111
+ yield new Promise((resolve) => setTimeout(resolve, 30));
112
+ console.log(' ✓ Results processed');
113
+ }));
114
+ }));
115
+ (0, performance_util_1.displayTimingTree)();
116
+ });
117
+ }
118
+ function demonstrateMixedNesting() {
119
+ return __awaiter(this, void 0, void 0, function* () {
120
+ console.log('\n--- Demonstrating Mixed Natural and Explicit Nesting ---\n');
121
+ (0, performance_util_1.clearDefaultTimingProfile)();
122
+ console.log('Example 6: Mixed natural and explicit nesting');
123
+ yield (0, performance_util_1.withTimingAsync)('Application Startup', () => __awaiter(this, void 0, void 0, function* () {
124
+ yield (0, performance_util_1.withTimingAsync)('Load Configuration', () => __awaiter(this, void 0, void 0, function* () {
125
+ yield new Promise((resolve) => setTimeout(resolve, 50));
126
+ console.log(' ✓ Configuration loaded');
127
+ yield (0, performance_util_1.withTimingAsync)('Validate Config', () => __awaiter(this, void 0, void 0, function* () {
128
+ yield new Promise((resolve) => setTimeout(resolve, 20));
129
+ console.log(' ✓ Configuration validated');
130
+ }));
131
+ }));
132
+ console.log('Starting parallel initialization...');
133
+ (0, performance_util_1.startSpan)('Initialization');
134
+ const ininitializeId = (0, performance_util_1.getCurrentParentNodeId)();
135
+ (0, performance_util_1.withTimingSync)('Do something before initialization', () => {
136
+ const start = performance.now();
137
+ while (performance.now() < start + 20) {
138
+ }
139
+ console.log(' ✓ Pre-initialization task completed');
140
+ });
141
+ yield Promise.all([
142
+ (0, performance_util_1.withTimingAsync)('Initialize Database', () => __awaiter(this, void 0, void 0, function* () {
143
+ yield new Promise((resolve) => setTimeout(resolve, 120));
144
+ console.log(' ✓ Database initialized');
145
+ }), { parentNodeId: ininitializeId }),
146
+ (0, performance_util_1.withTimingAsync)('Initialize Cache', () => __awaiter(this, void 0, void 0, function* () {
147
+ yield new Promise((resolve) => setTimeout(resolve, 90));
148
+ console.log(' ✓ Cache initialized');
149
+ }), { parentNodeId: ininitializeId }),
150
+ (0, performance_util_1.withTimingAsync)('Initialize Logging', () => __awaiter(this, void 0, void 0, function* () {
151
+ yield new Promise((resolve) => setTimeout(resolve, 40));
152
+ console.log(' ✓ Logging initialized');
153
+ }), { parentNodeId: ininitializeId }),
154
+ ]);
155
+ (0, performance_util_1.withTimingSync)('Do something after initialization', () => {
156
+ const start = performance.now();
157
+ while (performance.now() < start + 20) {
158
+ }
159
+ console.log(' ✓ Post-initialization task completed');
160
+ });
161
+ (0, performance_util_1.endSpan)('Initialization');
162
+ yield (0, performance_util_1.withTimingAsync)('Final Setup', () => __awaiter(this, void 0, void 0, function* () {
163
+ yield new Promise((resolve) => setTimeout(resolve, 30));
164
+ console.log(' ✓ Final setup completed');
165
+ }));
166
+ }));
167
+ (0, performance_util_1.displayTimingTree)();
168
+ });
169
+ }
170
+ function demonstrateSyncWithParentId() {
171
+ console.log('\n--- Demonstrating Sync Functions with parentNodeId ---\n');
172
+ (0, performance_util_1.clearDefaultTimingProfile)();
173
+ console.log('Example 7: Sync functions with explicit parent');
174
+ (0, performance_util_1.withTimingSync)('Data Processing Pipeline', () => {
175
+ const pipelineId = (0, performance_util_1.getCurrentParentNodeId)();
176
+ console.log(`Pipeline ID: ${pipelineId}`);
177
+ (0, performance_util_1.withTimingSync)('Load Input Data', () => {
178
+ const start = performance.now();
179
+ while (performance.now() < start + 50) {
180
+ }
181
+ console.log(' ✓ Input data loaded');
182
+ });
183
+ (0, performance_util_1.withTimingSync)('Process Batch 1', () => {
184
+ const start = performance.now();
185
+ while (performance.now() < start + 80) {
186
+ }
187
+ console.log(' ✓ Batch 1 processed');
188
+ }, { parentNodeId: pipelineId });
189
+ (0, performance_util_1.withTimingSync)('Process Batch 2', () => {
190
+ const start = performance.now();
191
+ while (performance.now() < start + 70) {
192
+ }
193
+ console.log(' ✓ Batch 2 processed');
194
+ }, { parentNodeId: pipelineId });
195
+ (0, performance_util_1.withTimingSync)('Process Batch 3', () => {
196
+ const start = performance.now();
197
+ while (performance.now() < start + 90) {
198
+ }
199
+ console.log(' ✓ Batch 3 processed');
200
+ }, { parentNodeId: pipelineId });
201
+ (0, performance_util_1.withTimingSync)('Save Results', () => {
202
+ const start = performance.now();
203
+ while (performance.now() < start + 40) {
204
+ }
205
+ console.log(' ✓ Results saved');
206
+ });
207
+ });
208
+ (0, performance_util_1.displayTimingTree)();
209
+ }
210
+ function demonstrateManualSpansWithParentId() {
211
+ console.log('\n--- Demonstrating Manual Spans with parentNodeId ---\n');
212
+ (0, performance_util_1.clearDefaultTimingProfile)();
213
+ console.log('Example 8: Manual spans with explicit parent');
214
+ (0, performance_util_1.startSpan)('Service Request');
215
+ const serviceId = (0, performance_util_1.getCurrentParentNodeId)();
216
+ console.log(`Service Request ID: ${serviceId}`);
217
+ (0, performance_util_1.startSpan)('Authenticate User');
218
+ const start1 = performance.now();
219
+ while (performance.now() < start1 + 30) {
220
+ }
221
+ (0, performance_util_1.endSpan)('Authenticate User');
222
+ console.log(' ✓ User authenticated');
223
+ (0, performance_util_1.startSpan)('Validate Input', { parentNodeId: serviceId });
224
+ const start2 = performance.now();
225
+ while (performance.now() < start2 + 20) {
226
+ }
227
+ (0, performance_util_1.endSpan)('Validate Input');
228
+ console.log(' ✓ Input validated');
229
+ (0, performance_util_1.startSpan)('Check Rate Limits', { parentNodeId: serviceId });
230
+ const start3 = performance.now();
231
+ while (performance.now() < start3 + 15) {
232
+ }
233
+ (0, performance_util_1.endSpan)('Check Rate Limits');
234
+ console.log(' ✓ Rate limits checked');
235
+ (0, performance_util_1.startSpan)('Log Request', { parentNodeId: serviceId });
236
+ const start4 = performance.now();
237
+ while (performance.now() < start4 + 10) {
238
+ }
239
+ (0, performance_util_1.endSpan)('Log Request');
240
+ console.log(' ✓ Request logged');
241
+ (0, performance_util_1.startSpan)('Process Request');
242
+ const start5 = performance.now();
243
+ while (performance.now() < start5 + 60) {
244
+ }
245
+ (0, performance_util_1.endSpan)('Process Request');
246
+ console.log(' ✓ Request processed');
247
+ (0, performance_util_1.endSpan)('Service Request');
248
+ (0, performance_util_1.displayTimingTree)();
249
+ }
250
+ function demonstrateNestingComparison() {
251
+ return __awaiter(this, void 0, void 0, function* () {
252
+ console.log('\n--- Demonstrating Natural vs Explicit Nesting ---\n');
253
+ console.log('Natural Nesting (call stack based):');
254
+ (0, performance_util_1.clearDefaultTimingProfile)();
255
+ yield (0, performance_util_1.withTimingAsync)('Sequential Operations', () => __awaiter(this, void 0, void 0, function* () {
256
+ yield (0, performance_util_1.withTimingAsync)('Operation 1', () => __awaiter(this, void 0, void 0, function* () {
257
+ yield new Promise((resolve) => setTimeout(resolve, 30));
258
+ }));
259
+ yield (0, performance_util_1.withTimingAsync)('Operation 2', () => __awaiter(this, void 0, void 0, function* () {
260
+ yield new Promise((resolve) => setTimeout(resolve, 40));
261
+ }));
262
+ yield (0, performance_util_1.withTimingAsync)('Operation 3', () => __awaiter(this, void 0, void 0, function* () {
263
+ yield new Promise((resolve) => setTimeout(resolve, 35));
264
+ }));
265
+ }));
266
+ (0, performance_util_1.displayTimingTree)();
267
+ console.log('\nExplicit Parent Assignment (logical grouping):');
268
+ (0, performance_util_1.clearDefaultTimingProfile)();
269
+ yield (0, performance_util_1.withTimingAsync)('Parallel Operations', () => __awaiter(this, void 0, void 0, function* () {
270
+ const parentId = (0, performance_util_1.getCurrentParentNodeId)();
271
+ yield Promise.all([
272
+ (0, performance_util_1.withTimingAsync)('Operation 1', () => __awaiter(this, void 0, void 0, function* () {
273
+ yield new Promise((resolve) => setTimeout(resolve, 30));
274
+ }), { parentNodeId: parentId }),
275
+ (0, performance_util_1.withTimingAsync)('Operation 2', () => __awaiter(this, void 0, void 0, function* () {
276
+ yield new Promise((resolve) => setTimeout(resolve, 40));
277
+ }), { parentNodeId: parentId }),
278
+ (0, performance_util_1.withTimingAsync)('Operation 3', () => __awaiter(this, void 0, void 0, function* () {
279
+ yield new Promise((resolve) => setTimeout(resolve, 35));
280
+ }), { parentNodeId: parentId }),
281
+ ]);
282
+ }));
283
+ (0, performance_util_1.displayTimingTree)();
284
+ });
285
+ }
286
+ function runAllExamples() {
287
+ return __awaiter(this, void 0, void 0, function* () {
288
+ demonstrateStartEndSpan();
289
+ demonstrateWarningBehavior();
290
+ yield demonstrateParentNodeId();
291
+ yield demonstrateMixedNesting();
292
+ demonstrateSyncWithParentId();
293
+ demonstrateManualSpansWithParentId();
294
+ yield demonstrateNestingComparison();
295
+ console.log('\nAll examples completed. ');
296
+ });
297
+ }
298
+ runAllExamples().catch(console.error);
@@ -2,23 +2,34 @@ type TimingSpans = TimingNode[];
2
2
  type TimingStack = TimingNode[];
3
3
  type TimingNode = {
4
4
  label: string;
5
+ id: string;
5
6
  durationMs: number;
6
7
  children: TimingNode[];
8
+ startTime: number;
7
9
  };
8
10
  type ActiveSpan = {
9
11
  node: TimingNode;
10
12
  startTime: number;
13
+ wasAddedToStack: boolean;
11
14
  };
12
15
  type TimingProfile = {
16
+ activeSpans: Map<string, ActiveSpan>;
17
+ spansById: Map<string, TimingNode>;
13
18
  timingSpans: TimingSpans;
14
19
  timingStack: TimingStack;
15
- activeSpans: Map<string, ActiveSpan>;
16
20
  };
17
21
  export declare function getDefaultTimingProfile(): TimingProfile;
18
22
  export declare function clearDefaultTimingProfile(): void;
19
- export declare function withTimingAsync<T>(label: string, fn: () => Promise<T>): Promise<T>;
20
- export declare function withTimingSync<T>(label: string, fn: () => T): T;
23
+ export declare function getCurrentParentNodeId(): string | null;
24
+ export declare function withTimingAsync<T>(label: string, fn: () => Promise<T>, options?: {
25
+ parentNodeId?: string | null;
26
+ }): Promise<T>;
27
+ export declare function withTimingSync<T>(label: string, fn: () => T, options?: {
28
+ parentNodeId?: string | null;
29
+ }): T;
21
30
  export declare function displayTimingTree(nodes?: TimingSpans, depth?: number): string;
22
- export declare function startSpan(spanName: string): void;
31
+ export declare function startSpan(spanName: string, options?: {
32
+ parentNodeId?: string | null;
33
+ }): void;
23
34
  export declare function endSpan(spanName: string): void;
24
35
  export {};
@@ -11,15 +11,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.getDefaultTimingProfile = getDefaultTimingProfile;
13
13
  exports.clearDefaultTimingProfile = clearDefaultTimingProfile;
14
+ exports.getCurrentParentNodeId = getCurrentParentNodeId;
14
15
  exports.withTimingAsync = withTimingAsync;
15
16
  exports.withTimingSync = withTimingSync;
16
17
  exports.displayTimingTree = displayTimingTree;
17
18
  exports.startSpan = startSpan;
18
19
  exports.endSpan = endSpan;
20
+ const crypto_1 = require("crypto");
19
21
  const DEFAULT_TIMING_PROFILE = {
22
+ activeSpans: new Map(),
23
+ spansById: new Map(),
20
24
  timingSpans: [],
21
25
  timingStack: [],
22
- activeSpans: new Map(),
23
26
  };
24
27
  function getDefaultTimingProfile() {
25
28
  return DEFAULT_TIMING_PROFILE;
@@ -30,48 +33,91 @@ function getTimingStack() {
30
33
  function getTimingSpans() {
31
34
  return DEFAULT_TIMING_PROFILE.timingSpans;
32
35
  }
36
+ function getNodeById(id) {
37
+ return DEFAULT_TIMING_PROFILE.spansById.get(id);
38
+ }
39
+ function setSpanId(id, node) {
40
+ DEFAULT_TIMING_PROFILE.spansById.set(id, node);
41
+ }
33
42
  function clearDefaultTimingProfile() {
34
43
  DEFAULT_TIMING_PROFILE.timingSpans = [];
35
44
  DEFAULT_TIMING_PROFILE.timingStack = [];
36
45
  DEFAULT_TIMING_PROFILE.activeSpans.clear();
46
+ DEFAULT_TIMING_PROFILE.spansById.clear();
37
47
  }
38
- function withTimingAsync(label, fn) {
48
+ function getParentNode() {
49
+ const stack = getTimingStack();
50
+ return stack.length > 0 ? stack[stack.length - 1] : null;
51
+ }
52
+ function getCurrentParentNodeId() {
53
+ const parentNode = getParentNode();
54
+ return parentNode ? parentNode.id : null;
55
+ }
56
+ function withTimingAsync(label, fn, options) {
39
57
  return __awaiter(this, void 0, void 0, function* () {
40
58
  const start = performance.now();
41
- const timingNode = { label, durationMs: 0, children: [] };
42
- const parent = getTimingStack().length > 0 ? getTimingStack()[getTimingStack().length - 1] : null;
59
+ const chosenId = options === null || options === void 0 ? void 0 : options.parentNodeId;
60
+ const uniqueId = (0, crypto_1.randomUUID)();
61
+ const timingNode = {
62
+ label,
63
+ durationMs: 0,
64
+ children: [],
65
+ id: uniqueId,
66
+ startTime: start,
67
+ };
68
+ setSpanId(uniqueId, timingNode);
69
+ const parent = chosenId ? getNodeById(chosenId) : getParentNode();
43
70
  if (parent) {
44
71
  parent.children.push(timingNode);
45
72
  }
46
73
  else {
47
74
  getTimingSpans().push(timingNode);
48
75
  }
49
- getTimingStack().push(timingNode);
76
+ const isRunningConcurrently = !!chosenId;
77
+ if (!isRunningConcurrently) {
78
+ getTimingStack().push(timingNode);
79
+ }
50
80
  return fn().finally(() => {
51
81
  const end = performance.now();
52
82
  timingNode.durationMs = end - start;
53
- getTimingStack().pop();
83
+ if (!isRunningConcurrently) {
84
+ getTimingStack().pop();
85
+ }
54
86
  });
55
87
  });
56
88
  }
57
- function withTimingSync(label, fn) {
89
+ function withTimingSync(label, fn, options) {
58
90
  const start = performance.now();
59
- const timingNode = { label, durationMs: 0, children: [] };
60
- const parent = getTimingStack().length > 0 ? getTimingStack()[getTimingStack().length - 1] : null;
91
+ const chosenId = options === null || options === void 0 ? void 0 : options.parentNodeId;
92
+ const uniqueId = (0, crypto_1.randomUUID)();
93
+ const timingNode = {
94
+ label,
95
+ durationMs: 0,
96
+ children: [],
97
+ id: uniqueId,
98
+ startTime: start,
99
+ };
100
+ setSpanId(uniqueId, timingNode);
101
+ const parent = chosenId ? getNodeById(chosenId) : getParentNode();
61
102
  if (parent) {
62
103
  parent.children.push(timingNode);
63
104
  }
64
105
  else {
65
106
  getTimingSpans().push(timingNode);
66
107
  }
67
- getTimingStack().push(timingNode);
108
+ const isRunningConcurrently = !!chosenId;
109
+ if (!isRunningConcurrently) {
110
+ getTimingStack().push(timingNode);
111
+ }
68
112
  try {
69
113
  return fn();
70
114
  }
71
115
  finally {
72
116
  const end = performance.now();
73
117
  timingNode.durationMs = end - start;
74
- getTimingStack().pop();
118
+ if (!isRunningConcurrently) {
119
+ getTimingStack().pop();
120
+ }
75
121
  }
76
122
  }
77
123
  function displayTimingTree(nodes = DEFAULT_TIMING_PROFILE.timingSpans, depth = 0) {
@@ -79,14 +125,58 @@ function displayTimingTree(nodes = DEFAULT_TIMING_PROFILE.timingSpans, depth = 0
79
125
  const indent = ' '.repeat(depth);
80
126
  const totalWidth = 120;
81
127
  const timeWidth = 10;
82
- nodes.forEach((node) => {
83
- const timeStr = `${node.durationMs.toFixed(2)}ms`;
84
- const labelWithIndent = `${indent}${node.label}`;
85
- const availableWidth = totalWidth - timeWidth - labelWithIndent.length;
86
- const dots = '.'.repeat(Math.max(2, availableWidth));
87
- lines.push(`${labelWithIndent}${dots}${timeStr.padStart(timeWidth)}`);
88
- if (node.children && node.children.length > 0) {
89
- lines.push(displayTimingTree(node.children, depth + 1));
128
+ const sortedNodes = [...nodes].sort((a, b) => a.startTime - b.startTime);
129
+ const groups = [];
130
+ let currentGroup = [];
131
+ for (let i = 0; i < sortedNodes.length; i++) {
132
+ const node = sortedNodes[i];
133
+ if (currentGroup.length === 0) {
134
+ currentGroup = [node];
135
+ }
136
+ else {
137
+ const hasOverlap = currentGroup.some((groupNode) => {
138
+ const nodeEnd = node.startTime + node.durationMs;
139
+ const groupNodeEnd = groupNode.startTime + groupNode.durationMs;
140
+ return node.startTime < groupNodeEnd && nodeEnd > groupNode.startTime;
141
+ });
142
+ if (hasOverlap) {
143
+ currentGroup.push(node);
144
+ }
145
+ else {
146
+ groups.push(currentGroup);
147
+ currentGroup = [node];
148
+ }
149
+ }
150
+ }
151
+ if (currentGroup.length > 0) {
152
+ groups.push(currentGroup);
153
+ }
154
+ groups.forEach((group) => {
155
+ if (group.length === 1) {
156
+ const node = group[0];
157
+ const timeStr = `${node.durationMs.toFixed(2)}ms`;
158
+ const labelWithIndent = `${indent}${node.label}`;
159
+ const availableWidth = totalWidth - timeWidth - labelWithIndent.length;
160
+ const dots = '.'.repeat(Math.max(2, availableWidth));
161
+ lines.push(`${labelWithIndent}${dots}${timeStr.padStart(timeWidth)}`);
162
+ if (node.children && node.children.length > 0) {
163
+ lines.push(displayTimingTree(node.children, depth + 1));
164
+ }
165
+ }
166
+ else {
167
+ const parallelHeader = `${indent}∥ Parallel Operations (${group.length})`;
168
+ lines.push(parallelHeader);
169
+ group.forEach((node) => {
170
+ const timeStr = `${node.durationMs.toFixed(2)}ms`;
171
+ const parallelIndicator = '∥ ';
172
+ const labelWithIndent = `${indent}${parallelIndicator}${node.label}`;
173
+ const availableWidth = totalWidth - timeWidth - labelWithIndent.length;
174
+ const dots = '.'.repeat(Math.max(2, availableWidth));
175
+ lines.push(`${labelWithIndent}${dots}${timeStr.padStart(timeWidth)}`);
176
+ if (node.children && node.children.length > 0) {
177
+ lines.push(displayTimingTree(node.children, depth + 1));
178
+ }
179
+ });
90
180
  }
91
181
  });
92
182
  const result = lines.join('\n');
@@ -97,24 +187,37 @@ function displayTimingTree(nodes = DEFAULT_TIMING_PROFILE.timingSpans, depth = 0
97
187
  }
98
188
  return result;
99
189
  }
100
- function startSpan(spanName) {
190
+ function startSpan(spanName, options) {
101
191
  if (DEFAULT_TIMING_PROFILE.activeSpans.has(spanName)) {
102
192
  console.warn(`Span "${spanName}" is already active. Please end it before starting a new one with the same name.`);
103
193
  return;
104
194
  }
105
195
  const startTime = performance.now();
106
- const timingNode = { label: spanName, durationMs: 0, children: [] };
107
- const parent = getTimingStack().length > 0 ? getTimingStack()[getTimingStack().length - 1] : null;
196
+ const chosenId = options === null || options === void 0 ? void 0 : options.parentNodeId;
197
+ const uniqueId = (0, crypto_1.randomUUID)();
198
+ const timingNode = {
199
+ label: spanName,
200
+ durationMs: 0,
201
+ children: [],
202
+ id: uniqueId,
203
+ startTime: startTime,
204
+ };
205
+ setSpanId(uniqueId, timingNode);
206
+ const parent = chosenId ? getNodeById(chosenId) : getParentNode();
108
207
  if (parent) {
109
208
  parent.children.push(timingNode);
110
209
  }
111
210
  else {
112
211
  getTimingSpans().push(timingNode);
113
212
  }
114
- getTimingStack().push(timingNode);
213
+ const isRunningConcurrently = !!chosenId;
214
+ if (!isRunningConcurrently) {
215
+ getTimingStack().push(timingNode);
216
+ }
115
217
  DEFAULT_TIMING_PROFILE.activeSpans.set(spanName, {
116
218
  node: timingNode,
117
219
  startTime: startTime,
220
+ wasAddedToStack: !isRunningConcurrently,
118
221
  });
119
222
  }
120
223
  function endSpan(spanName) {
@@ -125,14 +228,16 @@ function endSpan(spanName) {
125
228
  }
126
229
  const endTime = performance.now();
127
230
  activeSpan.node.durationMs = endTime - activeSpan.startTime;
128
- const currentTop = getTimingStack()[getTimingStack().length - 1];
129
- if (currentTop === activeSpan.node) {
130
- getTimingStack().pop();
131
- }
132
- else {
133
- const stackIndex = getTimingStack().findIndex((node) => node === activeSpan.node);
134
- if (stackIndex !== -1) {
135
- getTimingStack().splice(stackIndex, 1);
231
+ if (activeSpan.wasAddedToStack) {
232
+ const currentTop = getTimingStack()[getTimingStack().length - 1];
233
+ if (currentTop === activeSpan.node) {
234
+ getTimingStack().pop();
235
+ }
236
+ else {
237
+ const stackIndex = getTimingStack().findIndex((node) => node === activeSpan.node);
238
+ if (stackIndex !== -1) {
239
+ getTimingStack().splice(stackIndex, 1);
240
+ }
136
241
  }
137
242
  }
138
243
  DEFAULT_TIMING_PROFILE.activeSpans.delete(spanName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrail/util",
3
- "version": "1.1.15-alpha-6",
3
+ "version": "1.1.15-alpha-8",
4
4
  "description": "General JavaScript utilities",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",