@proj-airi/trace-kit 0.0.0-stage → 0.15.3
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/LICENSE +21 -0
- package/README.md +102 -2
- package/dist/clock-CewQJzn8.d.mts +545 -0
- package/dist/clock-hjTPEs21.mjs +212 -0
- package/dist/collector/index.d.mts +207 -0
- package/dist/collector/index.mjs +601 -0
- package/dist/conventions-DLiC8rkL.mjs +285 -0
- package/dist/index-DV04qeax.d.mts +49 -0
- package/dist/index.d.mts +782 -0
- package/dist/index.mjs +6 -0
- package/dist/keys-DD_VE8Fk.mjs +34 -0
- package/dist/profiles/agent.d.mts +70 -0
- package/dist/profiles/agent.mjs +295 -0
- package/dist/profiles/voice.d.mts +128 -0
- package/dist/profiles/voice.mjs +669 -0
- package/dist/projections/index.d.mts +3 -0
- package/dist/projections/index.mjs +1 -0
- package/dist/semconv-BOVWnou1.d.mts +832 -0
- package/dist/semconv.d.mts +2 -0
- package/dist/semconv.mjs +832 -0
- package/dist/series-ring-CaKfvyWQ.mjs +118 -0
- package/dist/series-ring-DCb7czkl.d.mts +49 -0
- package/dist/sink-BoftsA-W.mjs +501 -0
- package/dist/testing/index.d.mts +79 -0
- package/dist/testing/index.mjs +880 -0
- package/dist/timeline-B4Vc6qQf.mjs +766 -0
- package/dist/types-Fp54Yi6b.d.mts +319 -0
- package/dist/views/index.d.mts +97 -0
- package/dist/views/index.mjs +219 -0
- package/package.json +71 -3
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//#region src/core/series-ring.ts
|
|
2
|
+
/** The retention of the collector rings, and the default retention of a live timeline. */
|
|
3
|
+
const DEFAULT_SERIES_RETENTION = {
|
|
4
|
+
maxAgeMs: 18e5,
|
|
5
|
+
fullRateMs: 3e5
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* A columnar ring of one series: point times on the trace clock and their values.
|
|
9
|
+
*
|
|
10
|
+
* Retention: points newer than `fullRateMs` keep the full rate. Older points keep the minimum and the maximum of each
|
|
11
|
+
* second, so a long view still shows peaks. Points older than `maxAgeMs` go.
|
|
12
|
+
*/
|
|
13
|
+
var SeriesRing = class {
|
|
14
|
+
maxAgeMs;
|
|
15
|
+
fullRateMs;
|
|
16
|
+
times = /* @__PURE__ */ new Float64Array(256);
|
|
17
|
+
values = /* @__PURE__ */ new Float32Array(256);
|
|
18
|
+
length = 0;
|
|
19
|
+
/** Points before this index are already reduced to one second buckets. */
|
|
20
|
+
compactedUntil = 0;
|
|
21
|
+
changes = 0;
|
|
22
|
+
constructor(maxAgeMs, fullRateMs) {
|
|
23
|
+
this.maxAgeMs = maxAgeMs;
|
|
24
|
+
this.fullRateMs = fullRateMs;
|
|
25
|
+
}
|
|
26
|
+
get size() {
|
|
27
|
+
return this.length;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Changes when points that were already in the ring change: a compaction, an expiry, or a late point inserted
|
|
31
|
+
* before newer points. A reader that copies points incrementally starts again after a change.
|
|
32
|
+
*/
|
|
33
|
+
get revision() {
|
|
34
|
+
return this.changes;
|
|
35
|
+
}
|
|
36
|
+
/** The point times, in time order. The view is valid until the next append or compaction. */
|
|
37
|
+
timesView() {
|
|
38
|
+
return this.times.subarray(0, this.length);
|
|
39
|
+
}
|
|
40
|
+
/** The point values, in the order of `timesView`. The view is valid until the next append or compaction. */
|
|
41
|
+
valuesView() {
|
|
42
|
+
return this.values.subarray(0, this.length);
|
|
43
|
+
}
|
|
44
|
+
append(time, value) {
|
|
45
|
+
if (this.length === this.times.length) this.grow();
|
|
46
|
+
let index = this.length;
|
|
47
|
+
while (index > 0 && this.times[index - 1] > time) index--;
|
|
48
|
+
if (index < this.length) {
|
|
49
|
+
this.changes++;
|
|
50
|
+
this.times.copyWithin(index + 1, index, this.length);
|
|
51
|
+
this.values.copyWithin(index + 1, index, this.length);
|
|
52
|
+
}
|
|
53
|
+
this.times[index] = time;
|
|
54
|
+
this.values[index] = value;
|
|
55
|
+
this.length++;
|
|
56
|
+
if (index < this.compactedUntil) this.compactedUntil++;
|
|
57
|
+
}
|
|
58
|
+
/** Reduces old points and drops expired points. `now` is on the trace clock. */
|
|
59
|
+
compact(now) {
|
|
60
|
+
const expiry = now - this.maxAgeMs;
|
|
61
|
+
let start = 0;
|
|
62
|
+
while (start < this.length && this.times[start] < expiry) start++;
|
|
63
|
+
if (start > 0) this.shift(start);
|
|
64
|
+
const fullRateStart = now - this.fullRateMs;
|
|
65
|
+
let end = this.compactedUntil;
|
|
66
|
+
while (end < this.length && this.times[end] < fullRateStart) end++;
|
|
67
|
+
if (end === this.compactedUntil) return;
|
|
68
|
+
const reducedTimes = [];
|
|
69
|
+
const reducedValues = [];
|
|
70
|
+
let index = this.compactedUntil;
|
|
71
|
+
while (index < end) {
|
|
72
|
+
const bucket = Math.floor(this.times[index] / 1e3);
|
|
73
|
+
let minIndex = index;
|
|
74
|
+
let maxIndex = index;
|
|
75
|
+
while (index < end && Math.floor(this.times[index] / 1e3) === bucket) {
|
|
76
|
+
if (this.values[index] < this.values[minIndex]) minIndex = index;
|
|
77
|
+
if (this.values[index] > this.values[maxIndex]) maxIndex = index;
|
|
78
|
+
index++;
|
|
79
|
+
}
|
|
80
|
+
for (const kept of minIndex === maxIndex ? [minIndex] : [Math.min(minIndex, maxIndex), Math.max(minIndex, maxIndex)]) {
|
|
81
|
+
reducedTimes.push(this.times[kept]);
|
|
82
|
+
reducedValues.push(this.values[kept]);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const removed = end - this.compactedUntil - reducedTimes.length;
|
|
86
|
+
if (removed > 0) this.changes++;
|
|
87
|
+
this.times.set(reducedTimes, this.compactedUntil);
|
|
88
|
+
this.values.set(reducedValues, this.compactedUntil);
|
|
89
|
+
this.times.copyWithin(this.compactedUntil + reducedTimes.length, end, this.length);
|
|
90
|
+
this.values.copyWithin(this.compactedUntil + reducedTimes.length, end, this.length);
|
|
91
|
+
this.length -= removed;
|
|
92
|
+
this.compactedUntil += reducedTimes.length;
|
|
93
|
+
}
|
|
94
|
+
/** A copy of the points, in time order. */
|
|
95
|
+
read() {
|
|
96
|
+
return {
|
|
97
|
+
times: Array.from(this.times.subarray(0, this.length)),
|
|
98
|
+
values: Array.from(this.values.subarray(0, this.length))
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
shift(count) {
|
|
102
|
+
this.changes++;
|
|
103
|
+
this.times.copyWithin(0, count, this.length);
|
|
104
|
+
this.values.copyWithin(0, count, this.length);
|
|
105
|
+
this.length -= count;
|
|
106
|
+
this.compactedUntil = Math.max(0, this.compactedUntil - count);
|
|
107
|
+
}
|
|
108
|
+
grow() {
|
|
109
|
+
const times = new Float64Array(this.times.length * 2);
|
|
110
|
+
const values = new Float32Array(this.values.length * 2);
|
|
111
|
+
times.set(this.times);
|
|
112
|
+
values.set(this.values);
|
|
113
|
+
this.times = times;
|
|
114
|
+
this.values = values;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
//#endregion
|
|
118
|
+
export { SeriesRing as n, DEFAULT_SERIES_RETENTION as t };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/core/series-ring.d.ts
|
|
2
|
+
/** How long a series keeps its points. */
|
|
3
|
+
interface SeriesRetention {
|
|
4
|
+
/** Points older than this go. */
|
|
5
|
+
maxAgeMs: number;
|
|
6
|
+
/** Points newer than this keep the full rate. Older points keep the minimum and the maximum of each second. */
|
|
7
|
+
fullRateMs: number;
|
|
8
|
+
}
|
|
9
|
+
/** The retention of the collector rings, and the default retention of a live timeline. */
|
|
10
|
+
declare const DEFAULT_SERIES_RETENTION: Readonly<SeriesRetention>;
|
|
11
|
+
/**
|
|
12
|
+
* A columnar ring of one series: point times on the trace clock and their values.
|
|
13
|
+
*
|
|
14
|
+
* Retention: points newer than `fullRateMs` keep the full rate. Older points keep the minimum and the maximum of each
|
|
15
|
+
* second, so a long view still shows peaks. Points older than `maxAgeMs` go.
|
|
16
|
+
*/
|
|
17
|
+
declare class SeriesRing {
|
|
18
|
+
private readonly maxAgeMs;
|
|
19
|
+
private readonly fullRateMs;
|
|
20
|
+
private times;
|
|
21
|
+
private values;
|
|
22
|
+
private length;
|
|
23
|
+
/** Points before this index are already reduced to one second buckets. */
|
|
24
|
+
private compactedUntil;
|
|
25
|
+
private changes;
|
|
26
|
+
constructor(maxAgeMs: number, fullRateMs: number);
|
|
27
|
+
get size(): number;
|
|
28
|
+
/**
|
|
29
|
+
* Changes when points that were already in the ring change: a compaction, an expiry, or a late point inserted
|
|
30
|
+
* before newer points. A reader that copies points incrementally starts again after a change.
|
|
31
|
+
*/
|
|
32
|
+
get revision(): number;
|
|
33
|
+
/** The point times, in time order. The view is valid until the next append or compaction. */
|
|
34
|
+
timesView(): Float64Array;
|
|
35
|
+
/** The point values, in the order of `timesView`. The view is valid until the next append or compaction. */
|
|
36
|
+
valuesView(): Float32Array;
|
|
37
|
+
append(time: number, value: number): void;
|
|
38
|
+
/** Reduces old points and drops expired points. `now` is on the trace clock. */
|
|
39
|
+
compact(now: number): void;
|
|
40
|
+
/** A copy of the points, in time order. */
|
|
41
|
+
read(): {
|
|
42
|
+
times: number[];
|
|
43
|
+
values: number[];
|
|
44
|
+
};
|
|
45
|
+
private shift;
|
|
46
|
+
private grow;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { SeriesRetention as n, SeriesRing as r, DEFAULT_SERIES_RETENTION as t };
|
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import { G as definitionRecordsOf } from "./conventions-DLiC8rkL.mjs";
|
|
2
|
+
import { array, boolean, check, finite, integer, literal, maxLength, minValue, nonEmpty, number, object, optional, picklist, pipe, record, regex, string, union, unknown, variant } from "valibot";
|
|
3
|
+
//#region src/core/records.ts
|
|
4
|
+
/**
|
|
5
|
+
* Version of the record format. A reader upcasts an older minor format and rejects a newer major format.
|
|
6
|
+
* `source.started.format` carries it.
|
|
7
|
+
*/
|
|
8
|
+
const TRACE_FORMAT = 1;
|
|
9
|
+
const SYSTEM_TYPES = /* @__PURE__ */ new Set([
|
|
10
|
+
"source.started",
|
|
11
|
+
"source.stopped",
|
|
12
|
+
"source.lost",
|
|
13
|
+
"clock.snapshot",
|
|
14
|
+
"clock.discontinuity",
|
|
15
|
+
"stream.started",
|
|
16
|
+
"stream.stopped",
|
|
17
|
+
"definition"
|
|
18
|
+
]);
|
|
19
|
+
function isSystemRecord(record) {
|
|
20
|
+
return SYSTEM_TYPES.has(record.type);
|
|
21
|
+
}
|
|
22
|
+
function isTrackedRecord(record) {
|
|
23
|
+
return "track" in record && typeof record.track === "string";
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/core/schema.ts
|
|
27
|
+
const nonEmptyString = pipe(string(), nonEmpty());
|
|
28
|
+
/**
|
|
29
|
+
* The most points in one `sample.chunk`, over all its series. A chunk holds about one second, so this is above 4 kHz
|
|
30
|
+
* for one series. Series rings sit outside the byte limit of the buffer, so an untrusted chunk must be bounded here.
|
|
31
|
+
*/
|
|
32
|
+
const MAX_CHUNK_POINTS = 4096;
|
|
33
|
+
const finiteNumber = pipe(number(), finite());
|
|
34
|
+
/** A source or an epoch. Keys join them with `/`, so a `/` inside would make two keys equal. */
|
|
35
|
+
const keyPart = pipe(string(), nonEmpty(), regex(/^[^/]+$/, "A source or an epoch must not contain \"/\"."));
|
|
36
|
+
const assetRef = object({
|
|
37
|
+
$asset: nonEmptyString,
|
|
38
|
+
mediaType: nonEmptyString,
|
|
39
|
+
bytes: pipe(number(), integer(), minValue(0))
|
|
40
|
+
});
|
|
41
|
+
const value = union([
|
|
42
|
+
string(),
|
|
43
|
+
finiteNumber,
|
|
44
|
+
boolean(),
|
|
45
|
+
array(string()),
|
|
46
|
+
array(finiteNumber),
|
|
47
|
+
assetRef
|
|
48
|
+
]);
|
|
49
|
+
const ids = record(string(), union([string(), finiteNumber]));
|
|
50
|
+
const ref = object({
|
|
51
|
+
source: keyPart,
|
|
52
|
+
epoch: keyPart,
|
|
53
|
+
seq: pipe(number(), integer(), minValue(0))
|
|
54
|
+
});
|
|
55
|
+
const link = object({
|
|
56
|
+
to: union([
|
|
57
|
+
ref,
|
|
58
|
+
object({ spanId: nonEmptyString }),
|
|
59
|
+
object({
|
|
60
|
+
key: nonEmptyString,
|
|
61
|
+
id: union([string(), finiteNumber])
|
|
62
|
+
})
|
|
63
|
+
]),
|
|
64
|
+
rel: nonEmptyString
|
|
65
|
+
});
|
|
66
|
+
const status = picklist([
|
|
67
|
+
"ok",
|
|
68
|
+
"error",
|
|
69
|
+
"cancelled"
|
|
70
|
+
]);
|
|
71
|
+
const untracked = {
|
|
72
|
+
source: keyPart,
|
|
73
|
+
epoch: keyPart,
|
|
74
|
+
seq: pipe(number(), integer(), minValue(0)),
|
|
75
|
+
ts: finiteNumber,
|
|
76
|
+
clock: nonEmptyString,
|
|
77
|
+
observedTs: optional(finiteNumber),
|
|
78
|
+
ids: optional(ids),
|
|
79
|
+
attrs: optional(record(string(), value))
|
|
80
|
+
};
|
|
81
|
+
const tracked = {
|
|
82
|
+
...untracked,
|
|
83
|
+
track: nonEmptyString,
|
|
84
|
+
links: optional(array(link)),
|
|
85
|
+
evidence: optional(picklist(["reported", "observed"]))
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Validates one trace record at a trust boundary, such as IPC from a window or `trace:emit` from a plugin.
|
|
89
|
+
* Producers inside one process do not validate their own records.
|
|
90
|
+
*/
|
|
91
|
+
const traceRecordSchema = variant("type", [
|
|
92
|
+
object({
|
|
93
|
+
...untracked,
|
|
94
|
+
type: literal("source.started"),
|
|
95
|
+
format: pipe(number(), integer()),
|
|
96
|
+
runtime: string(),
|
|
97
|
+
profiles: array(object({
|
|
98
|
+
key: nonEmptyString,
|
|
99
|
+
version: string()
|
|
100
|
+
}))
|
|
101
|
+
}),
|
|
102
|
+
object({
|
|
103
|
+
...untracked,
|
|
104
|
+
type: literal("source.stopped")
|
|
105
|
+
}),
|
|
106
|
+
object({
|
|
107
|
+
...untracked,
|
|
108
|
+
type: literal("source.lost"),
|
|
109
|
+
of: object({
|
|
110
|
+
source: keyPart,
|
|
111
|
+
epoch: keyPart,
|
|
112
|
+
lastSeq: number()
|
|
113
|
+
}),
|
|
114
|
+
reason: picklist([
|
|
115
|
+
"ipc-closed",
|
|
116
|
+
"heartbeat-timeout",
|
|
117
|
+
"epoch-replaced"
|
|
118
|
+
])
|
|
119
|
+
}),
|
|
120
|
+
object({
|
|
121
|
+
...untracked,
|
|
122
|
+
type: literal("clock.snapshot"),
|
|
123
|
+
values: array(object({
|
|
124
|
+
clock: nonEmptyString,
|
|
125
|
+
value: finiteNumber
|
|
126
|
+
})),
|
|
127
|
+
uncertaintyMs: pipe(finiteNumber, minValue(0))
|
|
128
|
+
}),
|
|
129
|
+
object({
|
|
130
|
+
...untracked,
|
|
131
|
+
type: literal("clock.discontinuity"),
|
|
132
|
+
of: nonEmptyString,
|
|
133
|
+
reason: string()
|
|
134
|
+
}),
|
|
135
|
+
object({
|
|
136
|
+
...untracked,
|
|
137
|
+
type: literal("stream.started"),
|
|
138
|
+
streamId: nonEmptyString,
|
|
139
|
+
mediaType: nonEmptyString,
|
|
140
|
+
rate: optional(finiteNumber),
|
|
141
|
+
channels: optional(pipe(number(), integer())),
|
|
142
|
+
uri: optional(string())
|
|
143
|
+
}),
|
|
144
|
+
object({
|
|
145
|
+
...untracked,
|
|
146
|
+
type: literal("stream.stopped"),
|
|
147
|
+
streamId: nonEmptyString
|
|
148
|
+
}),
|
|
149
|
+
object({
|
|
150
|
+
...tracked,
|
|
151
|
+
type: literal("span.begin"),
|
|
152
|
+
spanId: nonEmptyString,
|
|
153
|
+
parentSpanId: optional(nonEmptyString),
|
|
154
|
+
name: string(),
|
|
155
|
+
deadlineMs: optional(pipe(finiteNumber, minValue(0)))
|
|
156
|
+
}),
|
|
157
|
+
object({
|
|
158
|
+
...tracked,
|
|
159
|
+
type: literal("span.update"),
|
|
160
|
+
spanId: nonEmptyString
|
|
161
|
+
}),
|
|
162
|
+
object({
|
|
163
|
+
...tracked,
|
|
164
|
+
type: literal("span.end"),
|
|
165
|
+
spanId: nonEmptyString,
|
|
166
|
+
status,
|
|
167
|
+
reason: optional(string()),
|
|
168
|
+
message: optional(string())
|
|
169
|
+
}),
|
|
170
|
+
object({
|
|
171
|
+
...tracked,
|
|
172
|
+
type: literal("span.complete"),
|
|
173
|
+
spanId: nonEmptyString,
|
|
174
|
+
parentSpanId: optional(nonEmptyString),
|
|
175
|
+
name: string(),
|
|
176
|
+
durationMs: pipe(finiteNumber, minValue(0)),
|
|
177
|
+
status,
|
|
178
|
+
reason: optional(string())
|
|
179
|
+
}),
|
|
180
|
+
object({
|
|
181
|
+
...tracked,
|
|
182
|
+
type: literal("instant"),
|
|
183
|
+
name: string(),
|
|
184
|
+
spanId: optional(nonEmptyString)
|
|
185
|
+
}),
|
|
186
|
+
object({
|
|
187
|
+
...tracked,
|
|
188
|
+
type: literal("state"),
|
|
189
|
+
value: string(),
|
|
190
|
+
from: optional(string())
|
|
191
|
+
}),
|
|
192
|
+
object({
|
|
193
|
+
...tracked,
|
|
194
|
+
type: literal("sample"),
|
|
195
|
+
values: record(string(), finiteNumber)
|
|
196
|
+
}),
|
|
197
|
+
object({
|
|
198
|
+
...tracked,
|
|
199
|
+
type: literal("sample.chunk"),
|
|
200
|
+
intervalMs: optional(pipe(finiteNumber, minValue(0))),
|
|
201
|
+
offsetsMs: optional(pipe(array(finiteNumber), maxLength(MAX_CHUNK_POINTS))),
|
|
202
|
+
values: pipe(record(string(), array(finiteNumber)), check((values) => Object.values(values).reduce((count, list) => count + list.length, 0) <= MAX_CHUNK_POINTS, `A sample chunk holds at most ${MAX_CHUNK_POINTS} points.`))
|
|
203
|
+
}),
|
|
204
|
+
object({
|
|
205
|
+
...tracked,
|
|
206
|
+
type: literal("log"),
|
|
207
|
+
level: picklist([
|
|
208
|
+
"debug",
|
|
209
|
+
"info",
|
|
210
|
+
"warn",
|
|
211
|
+
"error"
|
|
212
|
+
]),
|
|
213
|
+
message: string()
|
|
214
|
+
}),
|
|
215
|
+
object({
|
|
216
|
+
...untracked,
|
|
217
|
+
type: literal("definition"),
|
|
218
|
+
kind: picklist([
|
|
219
|
+
"track",
|
|
220
|
+
"attribute",
|
|
221
|
+
"id-key",
|
|
222
|
+
"link",
|
|
223
|
+
"end-reason",
|
|
224
|
+
"clock",
|
|
225
|
+
"metric",
|
|
226
|
+
"score",
|
|
227
|
+
"pipeline",
|
|
228
|
+
"preset",
|
|
229
|
+
"profile"
|
|
230
|
+
]),
|
|
231
|
+
key: nonEmptyString,
|
|
232
|
+
data: unknown()
|
|
233
|
+
}),
|
|
234
|
+
object({
|
|
235
|
+
...untracked,
|
|
236
|
+
type: literal("annotation"),
|
|
237
|
+
target: object({
|
|
238
|
+
ref: optional(ref),
|
|
239
|
+
spanId: optional(nonEmptyString),
|
|
240
|
+
ids: optional(ids)
|
|
241
|
+
}),
|
|
242
|
+
name: nonEmptyString,
|
|
243
|
+
value: optional(value),
|
|
244
|
+
label: optional(string()),
|
|
245
|
+
explanation: optional(string()),
|
|
246
|
+
annotator: object({
|
|
247
|
+
kind: picklist([
|
|
248
|
+
"rule",
|
|
249
|
+
"model",
|
|
250
|
+
"human"
|
|
251
|
+
]),
|
|
252
|
+
id: optional(string()),
|
|
253
|
+
model: optional(string()),
|
|
254
|
+
version: optional(string())
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
]);
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/core/sink.ts
|
|
260
|
+
const noopTraceSink = {
|
|
261
|
+
emit: () => {},
|
|
262
|
+
wants: () => false,
|
|
263
|
+
now: () => performance.now()
|
|
264
|
+
};
|
|
265
|
+
/**
|
|
266
|
+
* Sends each record to several sinks. The error of one sink goes to `onError` and does not stop the others.
|
|
267
|
+
* `wants` is true when any sink wants the track. `now` comes from the first sink.
|
|
268
|
+
*/
|
|
269
|
+
function fanOutTraceSink(sinks, onError) {
|
|
270
|
+
return {
|
|
271
|
+
emit: (record) => {
|
|
272
|
+
for (const sink of sinks) try {
|
|
273
|
+
sink.emit(record);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
onError(error);
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
wants: (track) => sinks.some((sink) => sink.wants(track)),
|
|
279
|
+
now: () => (sinks[0] ?? noopTraceSink).now()
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* One run of a producer: it stamps `source`, `epoch`, `seq`, and the default clock on each record.
|
|
284
|
+
*
|
|
285
|
+
* Lifecycle: the constructor writes `source.started` and the definitions of its profiles. `stop` writes
|
|
286
|
+
* `source.stopped` and ignores later records. A reload creates a new instance, which is a new epoch.
|
|
287
|
+
*
|
|
288
|
+
* `wants` reads the track set that {@link TraceSource.setWanted} received from the collector. Until the collector
|
|
289
|
+
* answers, no costly series is wanted.
|
|
290
|
+
*/
|
|
291
|
+
var TraceSource = class {
|
|
292
|
+
source;
|
|
293
|
+
epoch;
|
|
294
|
+
clock;
|
|
295
|
+
seq = 0;
|
|
296
|
+
stopped = false;
|
|
297
|
+
wanted = /* @__PURE__ */ new Set();
|
|
298
|
+
wantsAll = false;
|
|
299
|
+
sendRecord;
|
|
300
|
+
now;
|
|
301
|
+
constructor(options) {
|
|
302
|
+
this.source = options.source;
|
|
303
|
+
this.clock = options.clock;
|
|
304
|
+
this.epoch = (options.createEpoch ?? (() => crypto.randomUUID()))();
|
|
305
|
+
this.sendRecord = options.send;
|
|
306
|
+
this.now = options.now ?? (() => performance.now());
|
|
307
|
+
const ts = this.now();
|
|
308
|
+
const profiles = options.profiles ?? [];
|
|
309
|
+
this.emit({
|
|
310
|
+
type: "source.started",
|
|
311
|
+
ts,
|
|
312
|
+
format: 1,
|
|
313
|
+
runtime: options.runtime,
|
|
314
|
+
profiles: profiles.map((profile) => ({
|
|
315
|
+
key: profile.key,
|
|
316
|
+
version: profile.version
|
|
317
|
+
})),
|
|
318
|
+
...options.attrs ? { attrs: options.attrs } : {}
|
|
319
|
+
});
|
|
320
|
+
for (const profile of profiles) this.announce(profile);
|
|
321
|
+
}
|
|
322
|
+
/** Announces the definitions of a profile, for example a plugin profile that loads later. */
|
|
323
|
+
announce(profile) {
|
|
324
|
+
for (const record of definitionRecordsOf(profile, this.now())) this.emit(record);
|
|
325
|
+
}
|
|
326
|
+
emit = (input) => {
|
|
327
|
+
if (this.stopped) return;
|
|
328
|
+
this.sendRecord({
|
|
329
|
+
...input,
|
|
330
|
+
clock: input.clock ?? this.clock,
|
|
331
|
+
source: this.source,
|
|
332
|
+
epoch: this.epoch,
|
|
333
|
+
seq: this.seq++
|
|
334
|
+
});
|
|
335
|
+
};
|
|
336
|
+
wants = (track) => {
|
|
337
|
+
return this.wantsAll || this.wanted.has(track);
|
|
338
|
+
};
|
|
339
|
+
/** Sets the track paths that readers show. `'*'` wants every track. */
|
|
340
|
+
setWanted(tracks) {
|
|
341
|
+
this.wantsAll = tracks === "*";
|
|
342
|
+
this.wanted = tracks === "*" ? /* @__PURE__ */ new Set() : new Set(tracks);
|
|
343
|
+
}
|
|
344
|
+
stop() {
|
|
345
|
+
if (this.stopped) return;
|
|
346
|
+
this.emit({
|
|
347
|
+
type: "source.stopped",
|
|
348
|
+
ts: this.now()
|
|
349
|
+
});
|
|
350
|
+
this.stopped = true;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
/** A random 16 hex digit span ID, the size of a W3C trace context span ID. */
|
|
354
|
+
function createSpanId() {
|
|
355
|
+
const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(8));
|
|
356
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* An open span of one producer. It writes `span.begin` at once, so readers see open work.
|
|
360
|
+
*
|
|
361
|
+
* Lifecycle: `end` writes `span.end` once. Later calls of `update`, `instant`, and `end` do nothing, so a cleanup
|
|
362
|
+
* path can end a span that a success path already ended.
|
|
363
|
+
*/
|
|
364
|
+
var TraceSpan = class {
|
|
365
|
+
sink;
|
|
366
|
+
spanId;
|
|
367
|
+
track;
|
|
368
|
+
ids;
|
|
369
|
+
ended = false;
|
|
370
|
+
constructor(sink, options) {
|
|
371
|
+
this.sink = sink;
|
|
372
|
+
this.spanId = options.spanId ?? createSpanId();
|
|
373
|
+
this.track = options.track;
|
|
374
|
+
this.ids = options.ids;
|
|
375
|
+
const parentSpanId = typeof options.parent === "string" ? options.parent : options.parent?.spanId;
|
|
376
|
+
sink.emit({
|
|
377
|
+
type: "span.begin",
|
|
378
|
+
ts: options.ts ?? sink.now(),
|
|
379
|
+
track: options.track,
|
|
380
|
+
spanId: this.spanId,
|
|
381
|
+
name: options.name,
|
|
382
|
+
...parentSpanId ? { parentSpanId } : {},
|
|
383
|
+
...options.ids ? { ids: options.ids } : {},
|
|
384
|
+
...options.attrs ? { attrs: options.attrs } : {},
|
|
385
|
+
...options.links ? { links: options.links } : {},
|
|
386
|
+
...options.deadlineMs !== void 0 ? { deadlineMs: options.deadlineMs } : {}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
get isEnded() {
|
|
390
|
+
return this.ended;
|
|
391
|
+
}
|
|
392
|
+
update(attrs) {
|
|
393
|
+
if (this.ended) return;
|
|
394
|
+
this.sink.emit({
|
|
395
|
+
type: "span.update",
|
|
396
|
+
ts: this.sink.now(),
|
|
397
|
+
track: this.track,
|
|
398
|
+
spanId: this.spanId,
|
|
399
|
+
attrs,
|
|
400
|
+
...this.idFields()
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
instant(name, attrs, ts) {
|
|
404
|
+
if (this.ended) return;
|
|
405
|
+
this.sink.emit({
|
|
406
|
+
type: "instant",
|
|
407
|
+
ts: ts ?? this.sink.now(),
|
|
408
|
+
track: this.track,
|
|
409
|
+
spanId: this.spanId,
|
|
410
|
+
name,
|
|
411
|
+
...attrs ? { attrs } : {},
|
|
412
|
+
...this.idFields()
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
end(options = {}) {
|
|
416
|
+
if (this.ended) return;
|
|
417
|
+
this.ended = true;
|
|
418
|
+
this.sink.emit({
|
|
419
|
+
type: "span.end",
|
|
420
|
+
ts: options.ts ?? this.sink.now(),
|
|
421
|
+
track: this.track,
|
|
422
|
+
spanId: this.spanId,
|
|
423
|
+
status: options.status ?? "ok",
|
|
424
|
+
...options.reason ? { reason: options.reason } : {},
|
|
425
|
+
...options.message ? { message: options.message } : {},
|
|
426
|
+
...options.attrs ? { attrs: options.attrs } : {},
|
|
427
|
+
...options.links ? { links: options.links } : {},
|
|
428
|
+
...this.idFields()
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
idFields() {
|
|
432
|
+
return this.ids ? { ids: this.ids } : {};
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
/** Writes `span.begin` on `sink` and returns the open span. */
|
|
436
|
+
function beginSpan(sink, options) {
|
|
437
|
+
return new TraceSpan(sink, options);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Writes a frequent series, such as an audio level or a VAD probability, as `sample.chunk` records.
|
|
441
|
+
*
|
|
442
|
+
* Samples wait in memory until the chunk reaches `chunkMs`, then go out as one record with offsets from its first
|
|
443
|
+
* sample. While the sink does not want the track, `push` drops the sample, so a series that no one reads costs nothing.
|
|
444
|
+
* Call `flush` when the series pauses, for example when an input ends, so its last samples show at once.
|
|
445
|
+
*/
|
|
446
|
+
var SeriesWriter = class {
|
|
447
|
+
sink;
|
|
448
|
+
options;
|
|
449
|
+
chunkMs;
|
|
450
|
+
start;
|
|
451
|
+
offsets = [];
|
|
452
|
+
values = /* @__PURE__ */ new Map();
|
|
453
|
+
constructor(sink, options) {
|
|
454
|
+
this.sink = sink;
|
|
455
|
+
this.options = options;
|
|
456
|
+
this.chunkMs = options.chunkMs ?? 1e3;
|
|
457
|
+
}
|
|
458
|
+
/** Whether a reader wants the track now. A producer can skip costly work, such as decoding, while it is false. */
|
|
459
|
+
wanted() {
|
|
460
|
+
return this.sink().wants(this.options.track);
|
|
461
|
+
}
|
|
462
|
+
push(values, ts) {
|
|
463
|
+
const sink = this.sink();
|
|
464
|
+
if (!sink.wants(this.options.track)) {
|
|
465
|
+
this.offsets.length = 0;
|
|
466
|
+
this.values.clear();
|
|
467
|
+
this.start = void 0;
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const time = ts ?? sink.now();
|
|
471
|
+
const keys = Object.keys(values);
|
|
472
|
+
const sameKeys = keys.length === this.values.size && keys.every((key) => this.values.has(key));
|
|
473
|
+
if (this.start !== void 0 && (time - this.start >= this.chunkMs || !sameKeys)) this.flush();
|
|
474
|
+
this.start ??= time;
|
|
475
|
+
this.offsets.push(time - this.start);
|
|
476
|
+
for (const key of keys) {
|
|
477
|
+
let column = this.values.get(key);
|
|
478
|
+
if (!column) {
|
|
479
|
+
column = [];
|
|
480
|
+
this.values.set(key, column);
|
|
481
|
+
}
|
|
482
|
+
column.push(values[key]);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
flush() {
|
|
486
|
+
if (this.start === void 0 || this.offsets.length === 0) return;
|
|
487
|
+
this.sink().emit({
|
|
488
|
+
type: "sample.chunk",
|
|
489
|
+
ts: this.start,
|
|
490
|
+
track: this.options.track,
|
|
491
|
+
...this.options.ids ? { ids: this.options.ids } : {},
|
|
492
|
+
offsetsMs: [...this.offsets],
|
|
493
|
+
values: Object.fromEntries([...this.values].map(([key, column]) => [key, [...column]]))
|
|
494
|
+
});
|
|
495
|
+
this.offsets.length = 0;
|
|
496
|
+
this.values.clear();
|
|
497
|
+
this.start = void 0;
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
//#endregion
|
|
501
|
+
export { createSpanId as a, MAX_CHUNK_POINTS as c, isSystemRecord as d, isTrackedRecord as f, beginSpan as i, traceRecordSchema as l, TraceSource as n, fanOutTraceSink as o, TraceSpan as r, noopTraceSink as s, SeriesWriter as t, TRACE_FORMAT as u };
|