@dxos/tracing 0.1.56-main.c19b7cd → 0.1.56-main.c277f68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +307 -33
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +311 -33
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/api.d.ts +1 -0
- package/dist/types/src/api.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/metrics/base.d.ts +7 -0
- package/dist/types/src/metrics/base.d.ts.map +1 -0
- package/dist/types/src/metrics/index.d.ts +5 -0
- package/dist/types/src/metrics/index.d.ts.map +1 -0
- package/dist/types/src/metrics/time-series-counter.d.ts +15 -0
- package/dist/types/src/metrics/time-series-counter.d.ts.map +1 -0
- package/dist/types/src/metrics/time-usage-counter.d.ts +15 -0
- package/dist/types/src/metrics/time-usage-counter.d.ts.map +1 -0
- package/dist/types/src/metrics/unary-counter.d.ts +12 -0
- package/dist/types/src/metrics/unary-counter.d.ts.map +1 -0
- package/dist/types/src/symbols.d.ts +1 -0
- package/dist/types/src/symbols.d.ts.map +1 -1
- package/dist/types/src/trace-processor.d.ts +10 -1
- package/dist/types/src/trace-processor.d.ts.map +1 -1
- package/dist/types/src/trace-sender.d.ts.map +1 -1
- package/package.json +10 -9
- package/src/api.ts +17 -1
- package/src/index.ts +1 -0
- package/src/metrics/base.ts +26 -0
- package/src/metrics/index.ts +8 -0
- package/src/metrics/time-series-counter.ts +52 -0
- package/src/metrics/time-usage-counter.ts +62 -0
- package/src/metrics/unary-counter.ts +31 -0
- package/src/symbols.ts +4 -2
- package/src/trace-processor.ts +158 -2
- package/src/trace-sender.ts +28 -18
package/src/trace-processor.ts
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { unrefTimeout } from '@dxos/async';
|
|
5
6
|
import { Context } from '@dxos/context';
|
|
7
|
+
import { LogLevel, LogProcessor, getContextFromEntry, log } from '@dxos/log';
|
|
8
|
+
import { LogEntry } from '@dxos/protocols/proto/dxos/client/services';
|
|
6
9
|
import { Error as SerializedError } from '@dxos/protocols/proto/dxos/error';
|
|
7
|
-
import { Resource, Span } from '@dxos/protocols/proto/dxos/tracing';
|
|
10
|
+
import { Metric, Resource, Span } from '@dxos/protocols/proto/dxos/tracing';
|
|
8
11
|
import { getPrototypeSpecificInstanceId } from '@dxos/util';
|
|
9
12
|
|
|
10
13
|
import type { AddLinkOptions } from './api';
|
|
14
|
+
import { BaseCounter } from './metrics';
|
|
11
15
|
import { TRACE_SPAN_ATTRIBUTE, getTracingContext } from './symbols';
|
|
12
16
|
import { TraceSender } from './trace-sender';
|
|
13
17
|
|
|
@@ -28,12 +32,18 @@ export type ResourceEntry = {
|
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
export type TraceSubscription = {
|
|
35
|
+
flush: () => void;
|
|
36
|
+
|
|
31
37
|
dirtyResources: Set<number>;
|
|
32
38
|
dirtySpans: Set<number>;
|
|
39
|
+
newLogs: LogEntry[];
|
|
33
40
|
};
|
|
34
41
|
|
|
35
42
|
const MAX_RESOURCE_RECORDS = 500;
|
|
36
43
|
const MAX_SPAN_RECORDS = 1_000;
|
|
44
|
+
const MAX_LOG_RECORDS = 1_000;
|
|
45
|
+
|
|
46
|
+
const REFRESH_INTERVAL = 1_000;
|
|
37
47
|
|
|
38
48
|
export class TraceProcessor {
|
|
39
49
|
resources = new Map<number, ResourceEntry>();
|
|
@@ -43,10 +53,26 @@ export class TraceProcessor {
|
|
|
43
53
|
spans = new Map<number, Span>();
|
|
44
54
|
spanIdList: number[] = [];
|
|
45
55
|
|
|
56
|
+
logs: LogEntry[] = [];
|
|
57
|
+
|
|
46
58
|
subscriptions: Set<TraceSubscription> = new Set();
|
|
47
59
|
|
|
60
|
+
constructor() {
|
|
61
|
+
log.addProcessor(this._logProcessor.bind(this));
|
|
62
|
+
|
|
63
|
+
const refreshInterval = setInterval(this.refresh.bind(this), REFRESH_INTERVAL);
|
|
64
|
+
unrefTimeout(refreshInterval);
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
traceResourceConstructor(params: TraceResourceConstructorParams) {
|
|
49
68
|
const id = this.resources.size;
|
|
69
|
+
|
|
70
|
+
// init metrics counters.
|
|
71
|
+
const tracingContext = getTracingContext(Object.getPrototypeOf(params.instance));
|
|
72
|
+
for (const key of Object.keys(tracingContext.metricsProperties)) {
|
|
73
|
+
(params.instance[key] as BaseCounter)._assign(params.instance, key);
|
|
74
|
+
}
|
|
75
|
+
|
|
50
76
|
const entry: ResourceEntry = {
|
|
51
77
|
data: {
|
|
52
78
|
id,
|
|
@@ -54,6 +80,7 @@ export class TraceProcessor {
|
|
|
54
80
|
instanceId: getPrototypeSpecificInstanceId(params.instance),
|
|
55
81
|
info: this.getResourceInfo(params.instance),
|
|
56
82
|
links: [],
|
|
83
|
+
metrics: this.getResourceMetrics(params.instance),
|
|
57
84
|
},
|
|
58
85
|
instance: new WeakRef(params.instance),
|
|
59
86
|
};
|
|
@@ -72,7 +99,7 @@ export class TraceProcessor {
|
|
|
72
99
|
|
|
73
100
|
for (const [key, _opts] of Object.entries(tracingContext.infoProperties)) {
|
|
74
101
|
try {
|
|
75
|
-
res[key] = typeof instance[key] === 'function' ? instance[key]() : instance[key];
|
|
102
|
+
res[key] = sanitizeValue(typeof instance[key] === 'function' ? instance[key]() : instance[key]);
|
|
76
103
|
} catch (err: any) {
|
|
77
104
|
res[key] = err.message;
|
|
78
105
|
}
|
|
@@ -81,6 +108,17 @@ export class TraceProcessor {
|
|
|
81
108
|
return res;
|
|
82
109
|
}
|
|
83
110
|
|
|
111
|
+
getResourceMetrics(instance: any): Metric[] {
|
|
112
|
+
const res: Metric[] = [];
|
|
113
|
+
const tracingContext = getTracingContext(Object.getPrototypeOf(instance));
|
|
114
|
+
|
|
115
|
+
for (const [key, _opts] of Object.entries(tracingContext.metricsProperties)) {
|
|
116
|
+
res.push(instance[key].getData());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return res;
|
|
120
|
+
}
|
|
121
|
+
|
|
84
122
|
traceSpan(params: TraceSpanParams): TracingSpan {
|
|
85
123
|
const span = new TracingSpan(this, params);
|
|
86
124
|
this._flushSpan(span);
|
|
@@ -98,6 +136,40 @@ export class TraceProcessor {
|
|
|
98
136
|
return new TraceSender(this);
|
|
99
137
|
}
|
|
100
138
|
|
|
139
|
+
refresh() {
|
|
140
|
+
for (const resource of this.resources.values()) {
|
|
141
|
+
const instance = resource.instance.deref();
|
|
142
|
+
if (!instance) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const tracingContext = getTracingContext(Object.getPrototypeOf(instance));
|
|
147
|
+
const time = performance.now();
|
|
148
|
+
for (const key of Object.keys(tracingContext.metricsProperties)) {
|
|
149
|
+
(instance[key] as BaseCounter)._tick?.(time);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let _changed = false;
|
|
153
|
+
|
|
154
|
+
const oldInfo = resource.data.info;
|
|
155
|
+
resource.data.info = this.getResourceInfo(instance);
|
|
156
|
+
_changed ||= !areEqualShallow(oldInfo, resource.data.info);
|
|
157
|
+
|
|
158
|
+
const oldMetrics = resource.data.metrics;
|
|
159
|
+
resource.data.metrics = this.getResourceMetrics(instance);
|
|
160
|
+
_changed ||= !areEqualShallow(oldMetrics, resource.data.metrics);
|
|
161
|
+
|
|
162
|
+
// TODO(dmaretskyi): Test if works and enable.
|
|
163
|
+
// if (changed) {
|
|
164
|
+
this._markResourceDirty(resource.data.id);
|
|
165
|
+
// }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (const subscription of this.subscriptions) {
|
|
169
|
+
subscription.flush();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
101
173
|
/**
|
|
102
174
|
* @internal
|
|
103
175
|
*/
|
|
@@ -137,6 +209,52 @@ export class TraceProcessor {
|
|
|
137
209
|
this.spans.delete(id);
|
|
138
210
|
}
|
|
139
211
|
}
|
|
212
|
+
|
|
213
|
+
private _pushLog(log: LogEntry) {
|
|
214
|
+
this.logs.push(log);
|
|
215
|
+
if (this.logs.length > MAX_LOG_RECORDS) {
|
|
216
|
+
this.logs.shift();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const subscription of this.subscriptions) {
|
|
220
|
+
subscription.newLogs.push(log);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private _logProcessor: LogProcessor = (config, entry) => {
|
|
225
|
+
switch (entry.level) {
|
|
226
|
+
case LogLevel.ERROR:
|
|
227
|
+
case LogLevel.WARN:
|
|
228
|
+
case LogLevel.TRACE: {
|
|
229
|
+
const scope = entry.meta?.S;
|
|
230
|
+
const resource = this.resourceInstanceIndex.get(scope);
|
|
231
|
+
if (!resource) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const context = getContextFromEntry(entry) ?? {};
|
|
236
|
+
|
|
237
|
+
for (const key of Object.keys(context)) {
|
|
238
|
+
context[key] = sanitizeValue(context[key]);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const entryToPush: LogEntry = {
|
|
242
|
+
level: entry.level,
|
|
243
|
+
message: entry.message,
|
|
244
|
+
context,
|
|
245
|
+
timestamp: new Date(),
|
|
246
|
+
meta: {
|
|
247
|
+
file: entry.meta?.F ?? '',
|
|
248
|
+
line: entry.meta?.L ?? 0,
|
|
249
|
+
resourceId: resource.data.id,
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
this._pushLog(entryToPush);
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
default:
|
|
256
|
+
}
|
|
257
|
+
};
|
|
140
258
|
}
|
|
141
259
|
|
|
142
260
|
export class TracingSpan {
|
|
@@ -213,3 +331,41 @@ const serializeError = (err: unknown): SerializedError => {
|
|
|
213
331
|
};
|
|
214
332
|
|
|
215
333
|
export const TRACE_PROCESSOR: TraceProcessor = ((globalThis as any).TRACE_PROCESSOR ??= new TraceProcessor());
|
|
334
|
+
|
|
335
|
+
const sanitizeValue = (value: any) => {
|
|
336
|
+
switch (typeof value) {
|
|
337
|
+
case 'string':
|
|
338
|
+
case 'number':
|
|
339
|
+
case 'boolean':
|
|
340
|
+
case 'undefined':
|
|
341
|
+
return value;
|
|
342
|
+
break;
|
|
343
|
+
case 'object':
|
|
344
|
+
case 'function':
|
|
345
|
+
if (value === null) {
|
|
346
|
+
return value;
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// TODO(dmaretskyi): Expose trait.
|
|
351
|
+
if (typeof value.truncate === 'function') {
|
|
352
|
+
return value.truncate();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return value.toString();
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const areEqualShallow = (a: any, b: any) => {
|
|
360
|
+
for (const key in a) {
|
|
361
|
+
if (!(key in b) || a[key] !== b[key]) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
for (const key in b) {
|
|
366
|
+
if (!(key in a) || a[key] !== b[key]) {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return true;
|
|
371
|
+
};
|
package/src/trace-sender.ts
CHANGED
|
@@ -3,31 +3,22 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Stream } from '@dxos/codec-protobuf';
|
|
6
|
+
import { LogEntry } from '@dxos/protocols/proto/dxos/client/services';
|
|
6
7
|
import { StreamTraceEvent, TracingService } from '@dxos/protocols/proto/dxos/tracing';
|
|
7
8
|
|
|
8
9
|
import { TraceProcessor, TraceSubscription } from './trace-processor';
|
|
9
10
|
|
|
10
|
-
const FLUSH_INTERVAL = 1_000;
|
|
11
|
-
|
|
12
11
|
export class TraceSender implements TracingService {
|
|
13
12
|
constructor(private _traceProcessor: TraceProcessor) {}
|
|
14
13
|
|
|
15
14
|
streamTrace(request: void): Stream<StreamTraceEvent> {
|
|
16
15
|
return new Stream(({ ctx, next }) => {
|
|
17
|
-
const
|
|
18
|
-
dirtyResources: new Set(),
|
|
19
|
-
dirtySpans: new Set(),
|
|
20
|
-
};
|
|
21
|
-
this._traceProcessor.subscriptions.add(subscription);
|
|
22
|
-
ctx.onDispose(() => {
|
|
23
|
-
this._traceProcessor.subscriptions.delete(subscription);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const flushEvents = (resources: Set<number> | null, spans: Set<number> | null) => {
|
|
16
|
+
const flushEvents = (resources: Set<number> | null, spans: Set<number> | null, logs: LogEntry[] | null) => {
|
|
27
17
|
const event: StreamTraceEvent = {
|
|
28
18
|
resourceAdded: [],
|
|
29
19
|
resourceRemoved: [],
|
|
30
20
|
spanAdded: [],
|
|
21
|
+
logAdded: [],
|
|
31
22
|
};
|
|
32
23
|
|
|
33
24
|
if (resources) {
|
|
@@ -58,21 +49,40 @@ export class TraceSender implements TracingService {
|
|
|
58
49
|
}
|
|
59
50
|
}
|
|
60
51
|
|
|
52
|
+
if (logs) {
|
|
53
|
+
for (const log of logs) {
|
|
54
|
+
event.logAdded!.push({ log });
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
for (const log of this._traceProcessor.logs) {
|
|
58
|
+
event.logAdded!.push({ log });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
61
62
|
if (event.resourceAdded!.length > 0 || event.resourceRemoved!.length > 0 || event.spanAdded!.length > 0) {
|
|
62
63
|
next(event);
|
|
63
64
|
}
|
|
64
65
|
};
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const timer = setInterval(() => {
|
|
69
|
-
flushEvents(subscription.dirtyResources, subscription.dirtySpans);
|
|
67
|
+
const flush = () => {
|
|
68
|
+
flushEvents(subscription.dirtyResources, subscription.dirtySpans, subscription.newLogs);
|
|
70
69
|
subscription.dirtyResources.clear();
|
|
71
70
|
subscription.dirtySpans.clear();
|
|
72
|
-
|
|
71
|
+
subscription.newLogs.length = 0;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const subscription: TraceSubscription = {
|
|
75
|
+
flush,
|
|
76
|
+
dirtyResources: new Set(),
|
|
77
|
+
dirtySpans: new Set(),
|
|
78
|
+
newLogs: [],
|
|
79
|
+
};
|
|
80
|
+
this._traceProcessor.subscriptions.add(subscription);
|
|
73
81
|
ctx.onDispose(() => {
|
|
74
|
-
|
|
82
|
+
this._traceProcessor.subscriptions.delete(subscription);
|
|
75
83
|
});
|
|
84
|
+
|
|
85
|
+
flushEvents(null, null, null);
|
|
76
86
|
});
|
|
77
87
|
}
|
|
78
88
|
}
|