@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.
Files changed (36) hide show
  1. package/dist/lib/browser/index.mjs +307 -33
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +311 -33
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/types/src/api.d.ts +1 -0
  8. package/dist/types/src/api.d.ts.map +1 -1
  9. package/dist/types/src/index.d.ts +1 -0
  10. package/dist/types/src/index.d.ts.map +1 -1
  11. package/dist/types/src/metrics/base.d.ts +7 -0
  12. package/dist/types/src/metrics/base.d.ts.map +1 -0
  13. package/dist/types/src/metrics/index.d.ts +5 -0
  14. package/dist/types/src/metrics/index.d.ts.map +1 -0
  15. package/dist/types/src/metrics/time-series-counter.d.ts +15 -0
  16. package/dist/types/src/metrics/time-series-counter.d.ts.map +1 -0
  17. package/dist/types/src/metrics/time-usage-counter.d.ts +15 -0
  18. package/dist/types/src/metrics/time-usage-counter.d.ts.map +1 -0
  19. package/dist/types/src/metrics/unary-counter.d.ts +12 -0
  20. package/dist/types/src/metrics/unary-counter.d.ts.map +1 -0
  21. package/dist/types/src/symbols.d.ts +1 -0
  22. package/dist/types/src/symbols.d.ts.map +1 -1
  23. package/dist/types/src/trace-processor.d.ts +10 -1
  24. package/dist/types/src/trace-processor.d.ts.map +1 -1
  25. package/dist/types/src/trace-sender.d.ts.map +1 -1
  26. package/package.json +10 -9
  27. package/src/api.ts +17 -1
  28. package/src/index.ts +1 -0
  29. package/src/metrics/base.ts +26 -0
  30. package/src/metrics/index.ts +8 -0
  31. package/src/metrics/time-series-counter.ts +52 -0
  32. package/src/metrics/time-usage-counter.ts +62 -0
  33. package/src/metrics/unary-counter.ts +31 -0
  34. package/src/symbols.ts +4 -2
  35. package/src/trace-processor.ts +158 -2
  36. package/src/trace-sender.ts +28 -18
@@ -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
+ };
@@ -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 subscription: TraceSubscription = {
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
- flushEvents(null, null);
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
- }, FLUSH_INTERVAL);
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
- clearInterval(timer);
82
+ this._traceProcessor.subscriptions.delete(subscription);
75
83
  });
84
+
85
+ flushEvents(null, null, null);
76
86
  });
77
87
  }
78
88
  }