@ohos-ports/dxos-tracing 0.11.1-beta.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 (39) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +3 -0
  3. package/dist/lib/index.mjs +509 -0
  4. package/dist/lib/index.mjs.map +1 -0
  5. package/dist/types/src/api.d.ts +47 -0
  6. package/dist/types/src/api.d.ts.map +1 -0
  7. package/dist/types/src/buffering-backend.d.ts +24 -0
  8. package/dist/types/src/buffering-backend.d.ts.map +1 -0
  9. package/dist/types/src/diagnostic.d.ts +38 -0
  10. package/dist/types/src/diagnostic.d.ts.map +1 -0
  11. package/dist/types/src/diagnostics-channel.d.ts +34 -0
  12. package/dist/types/src/diagnostics-channel.d.ts.map +1 -0
  13. package/dist/types/src/index.d.ts +7 -0
  14. package/dist/types/src/index.d.ts.map +1 -0
  15. package/dist/types/src/remote/index.d.ts +2 -0
  16. package/dist/types/src/remote/index.d.ts.map +1 -0
  17. package/dist/types/src/remote/metrics.d.ts +37 -0
  18. package/dist/types/src/remote/metrics.d.ts.map +1 -0
  19. package/dist/types/src/trace-processor.d.ts +29 -0
  20. package/dist/types/src/trace-processor.d.ts.map +1 -0
  21. package/dist/types/src/tracing-types.d.ts +67 -0
  22. package/dist/types/src/tracing-types.d.ts.map +1 -0
  23. package/dist/types/src/tracing.test.d.ts +2 -0
  24. package/dist/types/src/tracing.test.d.ts.map +1 -0
  25. package/dist/types/src/util.d.ts +2 -0
  26. package/dist/types/src/util.d.ts.map +1 -0
  27. package/dist/types/tsconfig.tsbuildinfo +1 -0
  28. package/package.json +42 -0
  29. package/src/api.ts +204 -0
  30. package/src/buffering-backend.ts +112 -0
  31. package/src/diagnostic.ts +103 -0
  32. package/src/diagnostics-channel.ts +174 -0
  33. package/src/index.ts +25 -0
  34. package/src/remote/index.ts +5 -0
  35. package/src/remote/metrics.ts +57 -0
  36. package/src/trace-processor.ts +82 -0
  37. package/src/tracing-types.ts +77 -0
  38. package/src/tracing.test.ts +296 -0
  39. package/src/util.ts +6 -0
package/src/api.ts ADDED
@@ -0,0 +1,204 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Context, TRACE_SPAN_ATTRIBUTE } from '@dxos/context';
6
+ import { type MaybePromise } from '@dxos/util';
7
+
8
+ import { TRACE_PROCESSOR, sanitizeClassName } from './trace-processor';
9
+ import type { RemoteSpan } from './tracing-types';
10
+
11
+ /** localStorage key that switches the browser OTEL sampler from 30% to 100%. */
12
+ export const TRACE_ALL_KEY = 'dxos.debug.traceAll';
13
+
14
+ const mark = (name: string) => {
15
+ performance.mark(name);
16
+ };
17
+
18
+ export type SpanOptions = {
19
+ showInBrowserTimeline?: boolean;
20
+ /** When false the span is not exported to remote OTLP collectors. Defaults to true. */
21
+ showInRemoteTracing?: boolean;
22
+ op?: string;
23
+ attributes?: Record<string, any>;
24
+ };
25
+
26
+ /**
27
+ * Decorator that creates a span for the execution duration of the decorated method.
28
+ * Calls the TracingBackend directly; no custom TracingSpan objects.
29
+ */
30
+ const span =
31
+ ({ showInBrowserTimeline = false, showInRemoteTracing = true, op, attributes }: SpanOptions = {}) =>
32
+ (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<(...args: any) => any>) => {
33
+ const method = descriptor.value!;
34
+
35
+ descriptor.value = async function (this: any, ...args: any) {
36
+ const parentCtx = args[0] instanceof Context ? args[0] : null;
37
+ const startTs = performance.now();
38
+
39
+ const parentSpanContext = parentCtx?.getAttribute(TRACE_SPAN_ATTRIBUTE);
40
+
41
+ const className = sanitizeClassName(target.constructor?.name ?? 'unknown');
42
+ const spanName = `${className}.${propertyKey}`;
43
+
44
+ const spanAttributes: Record<string, any> = {};
45
+ if (attributes) {
46
+ for (const [key, value] of Object.entries(attributes)) {
47
+ spanAttributes[key.startsWith('ctx.') ? key : `ctx.${key}`] = value;
48
+ }
49
+ }
50
+
51
+ const remoteSpan = showInRemoteTracing
52
+ ? TRACE_PROCESSOR.tracingBackend?.startSpan({
53
+ name: spanName,
54
+ op: op ?? 'function',
55
+ attributes: spanAttributes,
56
+ parentContext: parentSpanContext,
57
+ })
58
+ : undefined;
59
+
60
+ let callArgs = args;
61
+ if (parentCtx) {
62
+ const childCtx =
63
+ remoteSpan?.spanContext != null
64
+ ? parentCtx.derive({ attributes: { [TRACE_SPAN_ATTRIBUTE]: remoteSpan.spanContext } })
65
+ : parentCtx.derive();
66
+ callArgs = [childCtx, ...args.slice(1)];
67
+ }
68
+
69
+ try {
70
+ return await method.apply(this, callArgs);
71
+ } catch (err) {
72
+ remoteSpan?.setError?.(err);
73
+ throw err;
74
+ } finally {
75
+ remoteSpan?.end();
76
+ if (showInBrowserTimeline && typeof globalThis?.performance?.measure === 'function') {
77
+ performance.measure(spanName, { start: startTs, end: performance.now() });
78
+ }
79
+ }
80
+ };
81
+ };
82
+
83
+ const manualSpans = new Map<string, RemoteSpan>();
84
+ const manualSpanTimestamps = new Map<string, { name: string; startTs: number }>();
85
+
86
+ export type ManualSpanParams = {
87
+ id: string;
88
+ instance: any;
89
+ methodName: string;
90
+ parentCtx: Context | null;
91
+ showInBrowserTimeline?: boolean;
92
+ showInRemoteTracing?: boolean;
93
+ op?: string;
94
+ attributes?: Record<string, any>;
95
+ };
96
+
97
+ /**
98
+ * Creates a span that must be ended manually.
99
+ *
100
+ * Returns a child Context that carries the new span's `TRACE_SPAN_ATTRIBUTE`.
101
+ * Callers should use the returned ctx for downstream work so that nested
102
+ * `@trace.span` methods and RPC calls inherit this span as their parent
103
+ * and land in the same trace (rather than starting a new root).
104
+ *
105
+ * When the new span cannot be created (duplicate id, no backend, or
106
+ * `showInRemoteTracing: false`), the parentCtx is returned unchanged.
107
+ */
108
+ const spanStart = (params: ManualSpanParams): Context | null => {
109
+ if (manualSpans.has(params.id) || manualSpanTimestamps.has(params.id)) {
110
+ return params.parentCtx;
111
+ }
112
+
113
+ const className = sanitizeClassName(params.instance?.constructor?.name ?? 'unknown');
114
+ const spanName = `${className}.${params.methodName}`;
115
+
116
+ if (params.showInBrowserTimeline) {
117
+ manualSpanTimestamps.set(params.id, { name: spanName, startTs: performance.now() });
118
+ }
119
+
120
+ if (params.showInRemoteTracing === false || !TRACE_PROCESSOR.tracingBackend) {
121
+ return params.parentCtx;
122
+ }
123
+
124
+ const parentSpanContext = params.parentCtx?.getAttribute(TRACE_SPAN_ATTRIBUTE);
125
+
126
+ const spanAttributes: Record<string, any> = {};
127
+ if (params.attributes) {
128
+ for (const [key, value] of Object.entries(params.attributes)) {
129
+ spanAttributes[key.startsWith('ctx.') ? key : `ctx.${key}`] = value;
130
+ }
131
+ }
132
+
133
+ const remoteSpan = TRACE_PROCESSOR.tracingBackend.startSpan({
134
+ name: spanName,
135
+ op: params.op ?? 'function',
136
+ attributes: spanAttributes,
137
+ parentContext: parentSpanContext,
138
+ });
139
+ manualSpans.set(params.id, remoteSpan);
140
+
141
+ if (params.parentCtx && remoteSpan.spanContext != null) {
142
+ return params.parentCtx.derive({ attributes: { [TRACE_SPAN_ATTRIBUTE]: remoteSpan.spanContext } });
143
+ }
144
+ return params.parentCtx;
145
+ };
146
+
147
+ /**
148
+ * Ends a span that was started manually.
149
+ */
150
+ const spanEnd = (id: string) => {
151
+ const remoteSpan = manualSpans.get(id);
152
+ if (remoteSpan) {
153
+ remoteSpan.end();
154
+ manualSpans.delete(id);
155
+ }
156
+
157
+ const timestamps = manualSpanTimestamps.get(id);
158
+ if (timestamps && typeof globalThis?.performance?.measure === 'function') {
159
+ performance.measure(timestamps.name, { start: timestamps.startTs, end: performance.now() });
160
+ manualSpanTimestamps.delete(id);
161
+ }
162
+ };
163
+
164
+ export type AddLinkOptions = {};
165
+
166
+ const addLink = (parent: any, child: any, opts: AddLinkOptions = {}) => {
167
+ TRACE_PROCESSOR.addLink(parent, child, opts);
168
+ };
169
+
170
+ export type TraceDiagnosticProps<T> = {
171
+ /** Unique ID. */
172
+ id: string;
173
+
174
+ /**
175
+ * Human-readable name.
176
+ * @defaults Defaults to `id`
177
+ */
178
+ name?: string;
179
+
180
+ /** Function that will be called to fetch the diagnostic data. */
181
+ fetch: () => MaybePromise<T>;
182
+ };
183
+
184
+ export interface TraceDiagnostic {
185
+ id: string;
186
+ unregister(): void;
187
+ }
188
+
189
+ /**
190
+ * Register a diagnostic that could be queried.
191
+ */
192
+ const diagnostic = <T>(params: TraceDiagnosticProps<T>): TraceDiagnostic => {
193
+ return TRACE_PROCESSOR.diagnostics.registerDiagnostic(params);
194
+ };
195
+
196
+ export const trace = {
197
+ addLink,
198
+ diagnostic,
199
+ mark,
200
+ span,
201
+ spanStart,
202
+ spanEnd,
203
+ metrics: TRACE_PROCESSOR.remoteMetrics,
204
+ };
@@ -0,0 +1,112 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type TraceContextData } from '@dxos/context';
6
+
7
+ import type { RemoteSpan, StartSpanOptions, TracingBackend } from './tracing-types';
8
+
9
+ export const BUFFERED_PREFIX = 'buffered-';
10
+
11
+ /**
12
+ * Span handle that records operations while no real OTEL backend is available.
13
+ * Once a real backend is registered, the buffered span is replayed and a
14
+ * {@link delegate} is set so future calls forward to the real span.
15
+ */
16
+ class BufferedSpan implements RemoteSpan {
17
+ readonly spanContext: TraceContextData;
18
+ readonly startTime: number;
19
+ delegate?: RemoteSpan;
20
+
21
+ #ended = false;
22
+ #endTime?: number;
23
+ #error?: unknown;
24
+ #hasError = false;
25
+
26
+ constructor(
27
+ readonly options: StartSpanOptions,
28
+ id: number,
29
+ ) {
30
+ this.spanContext = { traceparent: `${BUFFERED_PREFIX}${id}` };
31
+ this.startTime = Date.now();
32
+ }
33
+
34
+ end(endTime?: number): void {
35
+ if (this.delegate) {
36
+ this.delegate.end(endTime);
37
+ return;
38
+ }
39
+ this.#endTime = endTime ?? Date.now();
40
+ this.#ended = true;
41
+ }
42
+
43
+ setError(err: unknown): void {
44
+ if (this.delegate) {
45
+ this.delegate.setError?.(err);
46
+ return;
47
+ }
48
+ this.#error = err;
49
+ this.#hasError = true;
50
+ }
51
+
52
+ replay(real: RemoteSpan): void {
53
+ if (this.#hasError) {
54
+ real.setError?.(this.#error);
55
+ }
56
+ if (this.#ended) {
57
+ real.end(this.#endTime);
58
+ } else {
59
+ this.delegate = real;
60
+ }
61
+ }
62
+ }
63
+
64
+ /**
65
+ * A {@link TracingBackend} that buffers span operations until a real backend
66
+ * registers. On {@link drain}, buffered spans are replayed in FIFO order with
67
+ * parent IDs translated from synthetic `buffered-*` traceparents to real OTEL
68
+ * IDs, preserving the trace hierarchy.
69
+ */
70
+ export class BufferingTracingBackend implements TracingBackend {
71
+ readonly #pending: BufferedSpan[] = [];
72
+ #counter = 0;
73
+
74
+ startSpan(options: StartSpanOptions): RemoteSpan {
75
+ const span = new BufferedSpan(options, ++this.#counter);
76
+ this.#pending.push(span);
77
+ return span;
78
+ }
79
+
80
+ /** Discard all buffered spans without replaying them. */
81
+ clear(): void {
82
+ this.#pending.length = 0;
83
+ }
84
+
85
+ /**
86
+ * Replay all buffered spans into {@link backend}.
87
+ *
88
+ * @returns Map from synthetic buffered traceparent to real {@link TraceContextData},
89
+ * used by the post-drain translating wrapper to resolve stale buffered IDs
90
+ * still present on in-flight {@link Context} objects.
91
+ */
92
+ drain(backend: TracingBackend): Map<string, TraceContextData> {
93
+ const idMap = new Map<string, TraceContextData>();
94
+
95
+ for (const buffered of this.#pending) {
96
+ let parentContext = buffered.options.parentContext;
97
+ if (parentContext && parentContext.traceparent.startsWith(BUFFERED_PREFIX)) {
98
+ parentContext = idMap.get(parentContext.traceparent) ?? parentContext;
99
+ }
100
+
101
+ const real = backend.startSpan({ ...buffered.options, parentContext, startTime: buffered.startTime });
102
+
103
+ if (real.spanContext) {
104
+ idMap.set(buffered.spanContext.traceparent, real.spanContext);
105
+ }
106
+
107
+ buffered.replay(real);
108
+ }
109
+ this.#pending.length = 0;
110
+ return idMap;
111
+ }
112
+ }
@@ -0,0 +1,103 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { asyncTimeout } from '@dxos/async';
6
+ import { invariant } from '@dxos/invariant';
7
+
8
+ import { type TraceDiagnostic, type TraceDiagnosticProps } from './api';
9
+ import { createId } from './util';
10
+
11
+ export const DIAGNOSTICS_TIMEOUT = 10_000;
12
+
13
+ export type DiagnosticMetadata = {
14
+ id: string;
15
+ instanceId: string;
16
+ instanceTag: string | null;
17
+ name: string;
18
+ };
19
+
20
+ export type DiagnosticsRequest = {
21
+ id: string;
22
+ instanceId?: string;
23
+ instanceTag?: string | null;
24
+ };
25
+
26
+ export type DiagnosticsData = {
27
+ id: string;
28
+ instanceId: string;
29
+ data: any;
30
+ error?: string;
31
+ };
32
+
33
+ export class TraceDiagnosticImpl implements TraceDiagnostic {
34
+ constructor(
35
+ public id: string,
36
+ public fetch: () => any,
37
+ public name: string,
38
+ private readonly _onUnregister: () => void,
39
+ ) {}
40
+
41
+ unregister(): void {
42
+ this._onUnregister();
43
+ }
44
+ }
45
+
46
+ export class DiagnosticsManager {
47
+ readonly instanceId = createId();
48
+
49
+ readonly registry = new Map<string, TraceDiagnosticImpl>();
50
+
51
+ private _instanceTag: string | null = null;
52
+
53
+ get instanceTag(): string | null {
54
+ return this._instanceTag;
55
+ }
56
+
57
+ setInstanceTag(tag: string): void {
58
+ this._instanceTag = tag;
59
+ }
60
+
61
+ registerDiagnostic(params: TraceDiagnosticProps<any>): TraceDiagnostic {
62
+ const impl = new TraceDiagnosticImpl(params.id, params.fetch, params.name ?? params.id, () => {
63
+ if (this.registry.get(params.id) === impl) {
64
+ this.registry.delete(params.id);
65
+ }
66
+ });
67
+ this.registry.set(params.id, impl);
68
+ return impl;
69
+ }
70
+
71
+ list(): DiagnosticMetadata[] {
72
+ return Array.from(this.registry.values()).map((diagnostic) => ({
73
+ id: diagnostic.id,
74
+ instanceId: this.instanceId,
75
+ instanceTag: this._instanceTag,
76
+ name: diagnostic.name,
77
+ }));
78
+ }
79
+
80
+ async fetch(request: DiagnosticsRequest): Promise<DiagnosticsData> {
81
+ if (request.instanceId != null) {
82
+ invariant(request.instanceId === this.instanceId, 'Invalid instance id');
83
+ }
84
+ const { id } = request;
85
+ const diagnostic = this.registry.get(id);
86
+ invariant(diagnostic, 'Diagnostic not found');
87
+ try {
88
+ const data = await asyncTimeout(diagnostic.fetch(), DIAGNOSTICS_TIMEOUT);
89
+ return {
90
+ id,
91
+ instanceId: this.instanceId,
92
+ data,
93
+ };
94
+ } catch (err: any) {
95
+ return {
96
+ id,
97
+ instanceId: this.instanceId,
98
+ data: null,
99
+ error: err.stack,
100
+ };
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,174 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { Trigger, sleep } from '@dxos/async';
6
+ import { Context } from '@dxos/context';
7
+ import { invariant } from '@dxos/invariant';
8
+
9
+ import {
10
+ type DiagnosticMetadata,
11
+ DIAGNOSTICS_TIMEOUT,
12
+ type DiagnosticsData,
13
+ type DiagnosticsManager,
14
+ type DiagnosticsRequest,
15
+ } from './diagnostic';
16
+ import { createId } from './util';
17
+
18
+ const DEFAULT_CHANNEL_NAME = 'dxos-diagnostics';
19
+
20
+ const DISCOVER_TIME = 500;
21
+
22
+ export type DiagnosticChannelMessage =
23
+ | {
24
+ type: 'DIAGNOSTICS_DISCOVER';
25
+ }
26
+ | {
27
+ type: 'DIAGNOSTICS_ANNOUNCE';
28
+ diagnostics: DiagnosticMetadata[];
29
+ }
30
+ | {
31
+ type: 'DIAGNOSTICS_FETCH';
32
+ requestId: string;
33
+ request: DiagnosticsRequest;
34
+ }
35
+ | {
36
+ type: 'DIAGNOSTICS_RESPONSE';
37
+ requestId: string;
38
+ data: DiagnosticsData;
39
+ };
40
+
41
+ export class DiagnosticsChannel {
42
+ static get supported() {
43
+ return globalThis.BroadcastChannel != null;
44
+ }
45
+
46
+ private _ctx = new Context();
47
+
48
+ // Separate channels becauase the client and server may be in the same process.
49
+ private readonly _serveChannel?: BroadcastChannel = undefined;
50
+ private readonly _clientChannel?: BroadcastChannel = undefined;
51
+
52
+ constructor(private readonly _channelName: string = DEFAULT_CHANNEL_NAME) {
53
+ if (DiagnosticsChannel.supported) {
54
+ this._serveChannel = new BroadcastChannel(_channelName);
55
+ this._clientChannel = new BroadcastChannel(_channelName);
56
+ }
57
+ }
58
+
59
+ destroy(): void {
60
+ void this._ctx.dispose();
61
+ this._serveChannel?.close();
62
+ this._clientChannel?.close();
63
+ }
64
+
65
+ /**
66
+ * In node.js, the channel will keep the process alive.
67
+ * This method allows the process to exit.
68
+ * Noop in the browser.
69
+ */
70
+ unref(): void {
71
+ if (this._serveChannel && typeof (this._serveChannel as any).unref === 'function') {
72
+ (this._serveChannel as any).unref();
73
+ (this._clientChannel as any).unref();
74
+ }
75
+ }
76
+
77
+ serve(manager: DiagnosticsManager): void {
78
+ invariant(this._serveChannel);
79
+ const listener = async (event: MessageEvent) => {
80
+ switch (event.data.type) {
81
+ case 'DIAGNOSTICS_DISCOVER': {
82
+ const diagnostics = manager.list();
83
+ this._serveChannel!.postMessage({
84
+ type: 'DIAGNOSTICS_ANNOUNCE',
85
+ diagnostics,
86
+ } satisfies DiagnosticChannelMessage);
87
+ break;
88
+ }
89
+ case 'DIAGNOSTICS_FETCH': {
90
+ const { requestId, request } = event.data;
91
+
92
+ if (request.instanceId != null && request.instanceId !== manager.instanceId) {
93
+ break;
94
+ } else if (request.instanceTag != null && request.instanceTag !== manager.instanceTag) {
95
+ break;
96
+ }
97
+
98
+ const data = await manager.fetch(request);
99
+ this._serveChannel!.postMessage({
100
+ type: 'DIAGNOSTICS_RESPONSE',
101
+ requestId,
102
+ data,
103
+ } satisfies DiagnosticChannelMessage);
104
+ break;
105
+ }
106
+ }
107
+ };
108
+
109
+ this._serveChannel.addEventListener('message', listener);
110
+ this._ctx.onDispose(() => this._serveChannel!.removeEventListener('message', listener));
111
+ }
112
+
113
+ async discover(): Promise<DiagnosticMetadata[]> {
114
+ invariant(this._clientChannel);
115
+ const diagnostics: DiagnosticMetadata[] = [];
116
+
117
+ const collector = (event: MessageEvent) => {
118
+ const data = event.data as DiagnosticChannelMessage;
119
+ switch (data.type) {
120
+ case 'DIAGNOSTICS_ANNOUNCE':
121
+ diagnostics.push(...data.diagnostics);
122
+ break;
123
+ }
124
+ };
125
+
126
+ try {
127
+ this._clientChannel.addEventListener('message', collector);
128
+ this._clientChannel.postMessage({ type: 'DIAGNOSTICS_DISCOVER' } satisfies DiagnosticChannelMessage);
129
+
130
+ await sleep(DISCOVER_TIME);
131
+
132
+ // Dedup.
133
+ const result: DiagnosticMetadata[] = [];
134
+ for (const diagnostic of diagnostics) {
135
+ if (!result.some((d) => d.id === diagnostic.id && d.instanceId === diagnostic.instanceId)) {
136
+ result.push(diagnostic);
137
+ }
138
+ }
139
+
140
+ return diagnostics;
141
+ } finally {
142
+ this._clientChannel.removeEventListener('message', collector);
143
+ }
144
+ }
145
+
146
+ async fetch(request: DiagnosticsRequest): Promise<DiagnosticsData> {
147
+ invariant(this._clientChannel);
148
+ const requestId = createId();
149
+
150
+ const trigger = new Trigger<DiagnosticsData>();
151
+ const listener = (event: MessageEvent) => {
152
+ const data = event.data as DiagnosticChannelMessage;
153
+ if (data.type === 'DIAGNOSTICS_RESPONSE' && data.requestId === requestId) {
154
+ trigger.wake(data.data);
155
+ }
156
+ };
157
+
158
+ try {
159
+ this._clientChannel.addEventListener('message', listener);
160
+ this._clientChannel.postMessage({
161
+ type: 'DIAGNOSTICS_FETCH',
162
+ requestId,
163
+ request,
164
+ } satisfies DiagnosticChannelMessage);
165
+
166
+ // NOTE: Must have await keyword in this block.
167
+ const result = await trigger.wait({ timeout: DIAGNOSTICS_TIMEOUT });
168
+
169
+ return result;
170
+ } finally {
171
+ this._clientChannel.removeEventListener('message', listener);
172
+ }
173
+ }
174
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { trace } from './api';
6
+
7
+ export * from './api';
8
+ export * from './trace-processor';
9
+ export * from './tracing-types';
10
+ export * from './diagnostic';
11
+ export * from './diagnostics-channel';
12
+ export * from './remote/metrics';
13
+
14
+ trace.diagnostic({
15
+ id: 'process-info',
16
+ name: 'Process Info',
17
+ fetch: async () => {
18
+ return {
19
+ platform: globalThis.process?.platform,
20
+ arch: globalThis.process?.arch,
21
+ versions: globalThis.process?.versions,
22
+ href: globalThis.location?.href,
23
+ };
24
+ },
25
+ });
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './metrics';
@@ -0,0 +1,57 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ type TagType = number | string | boolean | null | undefined;
6
+
7
+ export interface MetricData {
8
+ unit?: string;
9
+ tags?: Record<string, TagType>;
10
+ timestamp?: number;
11
+ }
12
+
13
+ interface MetricsMethods {
14
+ /**
15
+ * Adds a value to a counter metric
16
+ */
17
+ increment(name: string, value?: number, data?: MetricData): void;
18
+ /**
19
+ * Adds a value to a distribution metric
20
+ */
21
+ distribution(name: string, value: number, data?: MetricData): void;
22
+ /**
23
+ * Adds a value to a set metric. Value must be a string or integer.
24
+ */
25
+ set(name: string, value: number | string, data?: MetricData): void;
26
+ /**
27
+ * Adds a value to a gauge metric
28
+ */
29
+ gauge(name: string, value: number, data?: MetricData): void;
30
+ }
31
+
32
+ /**
33
+ * Allows metrics to be recorded within SDK code without requiring specific consumers.
34
+ */
35
+ export class RemoteMetrics implements MetricsMethods {
36
+ private _metrics = new Set<MetricsMethods>();
37
+
38
+ registerProcessor(processor: MetricsMethods): void {
39
+ this._metrics.add(processor);
40
+ }
41
+
42
+ increment(name: string, value?: number, data?: MetricData): void[] {
43
+ return Array.from(this._metrics.values()).map((processor) => processor.increment(name, value, data));
44
+ }
45
+
46
+ distribution(name: string, value: number, data?: MetricData): void[] {
47
+ return Array.from(this._metrics.values()).map((processor) => processor.distribution(name, value, data));
48
+ }
49
+
50
+ set(name: string, value: number | string, data?: MetricData): void[] {
51
+ return Array.from(this._metrics.values()).map((processor) => processor.set(name, value, data));
52
+ }
53
+
54
+ gauge(name: string, value: number, data?: MetricData): void[] {
55
+ return Array.from(this._metrics.values()).map((processor) => processor.gauge(name, value, data));
56
+ }
57
+ }