@prisma/streams-server 0.1.1 → 0.1.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/CONTRIBUTING.md +8 -0
- package/package.json +2 -1
- package/src/app.ts +290 -17
- package/src/app_core.ts +1833 -698
- package/src/app_local.ts +144 -4
- package/src/auto_tune.ts +62 -0
- package/src/bootstrap.ts +159 -1
- package/src/concurrency_gate.ts +108 -0
- package/src/config.ts +116 -14
- package/src/db/db.ts +1201 -131
- package/src/db/schema.ts +308 -8
- package/src/foreground_activity.ts +55 -0
- package/src/index/indexer.ts +254 -124
- package/src/index/lexicon_file_cache.ts +261 -0
- package/src/index/lexicon_format.ts +93 -0
- package/src/index/lexicon_indexer.ts +789 -0
- package/src/index/secondary_indexer.ts +824 -0
- package/src/index/secondary_schema.ts +105 -0
- package/src/ingest.ts +10 -12
- package/src/manifest.ts +143 -8
- package/src/memory.ts +183 -8
- package/src/metrics.ts +15 -29
- package/src/metrics_emitter.ts +26 -3
- package/src/notifier.ts +121 -5
- package/src/objectstore/accounting.ts +92 -0
- package/src/objectstore/mock_r2.ts +1 -1
- package/src/objectstore/r2.ts +17 -1
- package/src/profiles/evlog/schema.ts +234 -0
- package/src/profiles/evlog.ts +299 -0
- package/src/profiles/generic.ts +47 -0
- package/src/profiles/index.ts +205 -0
- package/src/profiles/metrics/block_format.ts +109 -0
- package/src/profiles/metrics/normalize.ts +366 -0
- package/src/profiles/metrics/schema.ts +319 -0
- package/src/profiles/metrics.ts +85 -0
- package/src/profiles/profile.ts +225 -0
- package/src/{touch/engine.ts → profiles/stateProtocol/changes.ts} +3 -20
- package/src/profiles/stateProtocol/routes.ts +389 -0
- package/src/profiles/stateProtocol/types.ts +6 -0
- package/src/profiles/stateProtocol/validation.ts +51 -0
- package/src/profiles/stateProtocol.ts +100 -0
- package/src/read_filter.ts +468 -0
- package/src/reader.ts +2151 -164
- package/src/runtime/host_runtime.ts +5 -0
- package/src/runtime_memory.ts +200 -0
- package/src/runtime_memory_sampler.ts +235 -0
- package/src/schema/read_json.ts +43 -0
- package/src/schema/registry.ts +563 -59
- package/src/search/agg_format.ts +638 -0
- package/src/search/aggregate.ts +389 -0
- package/src/search/binary/codec.ts +162 -0
- package/src/search/binary/docset.ts +67 -0
- package/src/search/binary/restart_strings.ts +181 -0
- package/src/search/binary/varint.ts +34 -0
- package/src/search/bitset.ts +19 -0
- package/src/search/col_format.ts +382 -0
- package/src/search/col_runtime.ts +59 -0
- package/src/search/column_encoding.ts +43 -0
- package/src/search/companion_file_cache.ts +319 -0
- package/src/search/companion_format.ts +313 -0
- package/src/search/companion_manager.ts +1086 -0
- package/src/search/companion_plan.ts +218 -0
- package/src/search/fts_format.ts +423 -0
- package/src/search/fts_runtime.ts +333 -0
- package/src/search/query.ts +875 -0
- package/src/search/schema.ts +245 -0
- package/src/segment/cache.ts +93 -2
- package/src/segment/cached_segment.ts +89 -0
- package/src/segment/format.ts +108 -36
- package/src/segment/segmenter.ts +79 -5
- package/src/segment/segmenter_worker.ts +35 -6
- package/src/segment/segmenter_workers.ts +42 -12
- package/src/server.ts +150 -36
- package/src/sqlite/adapter.ts +185 -14
- package/src/sqlite/runtime_stats.ts +163 -0
- package/src/stats.ts +3 -3
- package/src/stream_size_reconciler.ts +100 -0
- package/src/touch/canonical_change.ts +7 -0
- package/src/touch/live_metrics.ts +94 -64
- package/src/touch/live_templates.ts +15 -1
- package/src/touch/manager.ts +166 -88
- package/src/touch/{interpreter_worker.ts → processor_worker.ts} +19 -14
- package/src/touch/spec.ts +95 -92
- package/src/touch/touch_journal.ts +4 -0
- package/src/touch/worker_pool.ts +8 -14
- package/src/touch/worker_protocol.ts +3 -3
- package/src/uploader.ts +77 -6
- package/src/util/bloom256.ts +2 -2
- package/src/util/byte_lru.ts +73 -0
- package/src/util/lru.ts +8 -0
- package/src/util/stream_paths.ts +19 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { Result } from "better-result";
|
|
2
|
+
import type { AggSummaryState } from "../../search/agg_format";
|
|
3
|
+
import type { NormalizedMetricsRecord } from "../profile";
|
|
4
|
+
import { expectPlainObjectResult, isPlainObject } from "../profile";
|
|
5
|
+
|
|
6
|
+
type PrimitiveAttribute = string | number | boolean | bigint;
|
|
7
|
+
|
|
8
|
+
type HistogramMap = Record<string, number>;
|
|
9
|
+
|
|
10
|
+
function normalizeString(value: unknown): string | null {
|
|
11
|
+
if (typeof value !== "string") return null;
|
|
12
|
+
const trimmed = value.trim();
|
|
13
|
+
return trimmed === "" ? null : trimmed;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeFiniteNumber(value: unknown): number | null {
|
|
17
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
18
|
+
if (typeof value === "bigint") return Number(value);
|
|
19
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
20
|
+
const parsed = Number(value);
|
|
21
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeInteger(value: unknown): number | null {
|
|
27
|
+
const numeric = normalizeFiniteNumber(value);
|
|
28
|
+
if (numeric == null) return null;
|
|
29
|
+
return Math.trunc(numeric);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeHistogram(value: unknown): HistogramMap | undefined {
|
|
33
|
+
if (!isPlainObject(value)) return undefined;
|
|
34
|
+
const out: HistogramMap = {};
|
|
35
|
+
for (const [bucket, raw] of Object.entries(value)) {
|
|
36
|
+
const count = normalizeFiniteNumber(raw);
|
|
37
|
+
if (count == null || count <= 0) continue;
|
|
38
|
+
out[String(bucket)] = (out[String(bucket)] ?? 0) + count;
|
|
39
|
+
}
|
|
40
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function histogramPercentile(histogram: HistogramMap | undefined, percentile: number): number | null {
|
|
44
|
+
if (!histogram) return null;
|
|
45
|
+
const entries = Object.entries(histogram)
|
|
46
|
+
.map(([bucket, count]) => ({ bucket: Number(bucket), count }))
|
|
47
|
+
.filter((entry) => Number.isFinite(entry.bucket) && Number.isFinite(entry.count) && entry.count > 0)
|
|
48
|
+
.sort((a, b) => a.bucket - b.bucket);
|
|
49
|
+
if (entries.length === 0) return null;
|
|
50
|
+
const total = entries.reduce((sum, entry) => sum + entry.count, 0);
|
|
51
|
+
if (total <= 0) return null;
|
|
52
|
+
const threshold = total * percentile;
|
|
53
|
+
let seen = 0;
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
seen += entry.count;
|
|
56
|
+
if (seen >= threshold) return entry.bucket;
|
|
57
|
+
}
|
|
58
|
+
return entries[entries.length - 1]?.bucket ?? null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeAttributes(value: unknown): Record<string, string> {
|
|
62
|
+
if (!isPlainObject(value)) return {};
|
|
63
|
+
const out: Record<string, string> = {};
|
|
64
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
65
|
+
if (typeof raw === "string") out[key] = raw;
|
|
66
|
+
else if (typeof raw === "number" && Number.isFinite(raw)) out[key] = String(raw);
|
|
67
|
+
else if (typeof raw === "boolean") out[key] = raw ? "true" : "false";
|
|
68
|
+
else if (typeof raw === "bigint") out[key] = raw.toString();
|
|
69
|
+
}
|
|
70
|
+
return Object.fromEntries(Object.entries(out).sort((a, b) => a[0].localeCompare(b[0])));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildDimensionPairs(attributes: Record<string, string>): string[] {
|
|
74
|
+
return Object.entries(attributes)
|
|
75
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
76
|
+
.sort((a, b) => a.localeCompare(b));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildSeriesKey(args: {
|
|
80
|
+
metricKind: string;
|
|
81
|
+
temporality: string;
|
|
82
|
+
metric: string;
|
|
83
|
+
unit: string;
|
|
84
|
+
stream: string | null;
|
|
85
|
+
instance: string | null;
|
|
86
|
+
dimensionKey: string | null;
|
|
87
|
+
}): string {
|
|
88
|
+
return [
|
|
89
|
+
args.metricKind,
|
|
90
|
+
args.temporality,
|
|
91
|
+
args.metric,
|
|
92
|
+
args.unit,
|
|
93
|
+
args.stream ?? "",
|
|
94
|
+
args.instance ?? "",
|
|
95
|
+
args.dimensionKey ?? "",
|
|
96
|
+
].join("|");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function normalizeSummaryResult(value: Record<string, unknown>): Result<AggSummaryState, { message: string }> {
|
|
100
|
+
const summaryObject = isPlainObject(value.summary) ? value.summary : null;
|
|
101
|
+
const histogram =
|
|
102
|
+
normalizeHistogram(value.buckets) ??
|
|
103
|
+
normalizeHistogram(summaryObject?.histogram ?? undefined);
|
|
104
|
+
const count =
|
|
105
|
+
normalizeFiniteNumber(value.count) ??
|
|
106
|
+
normalizeFiniteNumber(summaryObject?.count);
|
|
107
|
+
const sum =
|
|
108
|
+
normalizeFiniteNumber(value.sum) ??
|
|
109
|
+
normalizeFiniteNumber(summaryObject?.sum);
|
|
110
|
+
const min =
|
|
111
|
+
normalizeFiniteNumber(value.min) ??
|
|
112
|
+
normalizeFiniteNumber(summaryObject?.min);
|
|
113
|
+
const max =
|
|
114
|
+
normalizeFiniteNumber(value.max) ??
|
|
115
|
+
normalizeFiniteNumber(summaryObject?.max);
|
|
116
|
+
|
|
117
|
+
if (count == null || count < 0) return Result.err({ message: "metrics interval requires count" });
|
|
118
|
+
if (sum == null) return Result.err({ message: "metrics interval requires sum" });
|
|
119
|
+
|
|
120
|
+
return Result.ok({
|
|
121
|
+
count,
|
|
122
|
+
sum,
|
|
123
|
+
min: count === 0 ? null : min ?? 0,
|
|
124
|
+
max: count === 0 ? null : max ?? 0,
|
|
125
|
+
histogram,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function normalizeMetricsRecordResult(value: unknown): Result<NormalizedMetricsRecord, { message: string }> {
|
|
130
|
+
const objRes = expectPlainObjectResult(value, "metrics record");
|
|
131
|
+
if (Result.isError(objRes)) return objRes;
|
|
132
|
+
const input = objRes.value;
|
|
133
|
+
|
|
134
|
+
const kind = normalizeString(input.kind) ?? "interval";
|
|
135
|
+
if (kind !== "interval") return Result.err({ message: "metrics record.kind must be interval" });
|
|
136
|
+
|
|
137
|
+
const metric = normalizeString(input.metric);
|
|
138
|
+
if (!metric) return Result.err({ message: "metrics record.metric must be a non-empty string" });
|
|
139
|
+
|
|
140
|
+
const unit = normalizeString(input.unit);
|
|
141
|
+
if (!unit) return Result.err({ message: "metrics record.unit must be a non-empty string" });
|
|
142
|
+
|
|
143
|
+
const windowStart = normalizeInteger(input.windowStart);
|
|
144
|
+
const windowEnd = normalizeInteger(input.windowEnd);
|
|
145
|
+
if (windowStart == null || windowEnd == null) {
|
|
146
|
+
return Result.err({ message: "metrics record.windowStart and windowEnd must be integers" });
|
|
147
|
+
}
|
|
148
|
+
if (windowEnd < windowStart) return Result.err({ message: "metrics record.windowEnd must be >= windowStart" });
|
|
149
|
+
|
|
150
|
+
const intervalMs = normalizeInteger(input.intervalMs) ?? (windowEnd - windowStart);
|
|
151
|
+
if (intervalMs < 0) return Result.err({ message: "metrics record.intervalMs must be >= 0" });
|
|
152
|
+
|
|
153
|
+
const metricKind = normalizeString(input.metricKind) ?? "summary";
|
|
154
|
+
const temporality = normalizeString(input.temporality) ?? "delta";
|
|
155
|
+
const stream = normalizeString(input.stream);
|
|
156
|
+
const instance = normalizeString(input.instance);
|
|
157
|
+
const attributes = normalizeAttributes(input.attributes ?? input.tags);
|
|
158
|
+
const dimensionPairs = buildDimensionPairs(attributes);
|
|
159
|
+
const dimensionKey = dimensionPairs.length > 0 ? dimensionPairs.join("\u0000") : null;
|
|
160
|
+
const seriesKey = buildSeriesKey({
|
|
161
|
+
metricKind,
|
|
162
|
+
temporality,
|
|
163
|
+
metric,
|
|
164
|
+
unit,
|
|
165
|
+
stream,
|
|
166
|
+
instance,
|
|
167
|
+
dimensionKey,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const summaryRes = normalizeSummaryResult(input);
|
|
171
|
+
if (Result.isError(summaryRes)) return summaryRes;
|
|
172
|
+
const summary = summaryRes.value;
|
|
173
|
+
const avg = normalizeFiniteNumber(input.avg) ?? (summary.count > 0 ? summary.sum / summary.count : 0);
|
|
174
|
+
const p50 = normalizeFiniteNumber(input.p50) ?? histogramPercentile(summary.histogram, 0.5) ?? 0;
|
|
175
|
+
const p95 = normalizeFiniteNumber(input.p95) ?? histogramPercentile(summary.histogram, 0.95) ?? 0;
|
|
176
|
+
const p99 = normalizeFiniteNumber(input.p99) ?? histogramPercentile(summary.histogram, 0.99) ?? 0;
|
|
177
|
+
|
|
178
|
+
const normalizedValue: Record<string, unknown> = {
|
|
179
|
+
apiVersion: "durable.streams/metrics/v1",
|
|
180
|
+
kind: "interval",
|
|
181
|
+
metric,
|
|
182
|
+
unit,
|
|
183
|
+
metricKind,
|
|
184
|
+
temporality,
|
|
185
|
+
windowStart,
|
|
186
|
+
windowEnd,
|
|
187
|
+
intervalMs,
|
|
188
|
+
instance,
|
|
189
|
+
stream,
|
|
190
|
+
tags: attributes,
|
|
191
|
+
attributes,
|
|
192
|
+
dimensionPairs,
|
|
193
|
+
dimensionKey,
|
|
194
|
+
seriesKey,
|
|
195
|
+
count: summary.count,
|
|
196
|
+
sum: summary.sum,
|
|
197
|
+
min: summary.min,
|
|
198
|
+
max: summary.max,
|
|
199
|
+
avg,
|
|
200
|
+
p50,
|
|
201
|
+
p95,
|
|
202
|
+
p99,
|
|
203
|
+
buckets: summary.histogram ?? {},
|
|
204
|
+
summary: {
|
|
205
|
+
count: summary.count,
|
|
206
|
+
sum: summary.sum,
|
|
207
|
+
min: summary.min,
|
|
208
|
+
max: summary.max,
|
|
209
|
+
histogram: summary.histogram ?? {},
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
return Result.ok({
|
|
214
|
+
value: normalizedValue,
|
|
215
|
+
routingKey: seriesKey,
|
|
216
|
+
companion: {
|
|
217
|
+
metric,
|
|
218
|
+
unit,
|
|
219
|
+
metricKind,
|
|
220
|
+
temporality,
|
|
221
|
+
windowStartMs: windowStart,
|
|
222
|
+
windowEndMs: windowEnd,
|
|
223
|
+
intervalMs,
|
|
224
|
+
stream,
|
|
225
|
+
instance,
|
|
226
|
+
attributes,
|
|
227
|
+
dimensionPairs,
|
|
228
|
+
dimensionKey,
|
|
229
|
+
seriesKey,
|
|
230
|
+
summary,
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export type MetricsBlockRecord = {
|
|
236
|
+
doc_id: number;
|
|
237
|
+
metric: string;
|
|
238
|
+
unit: string;
|
|
239
|
+
metricKind: string;
|
|
240
|
+
temporality: string;
|
|
241
|
+
windowStartMs: number;
|
|
242
|
+
windowEndMs: number;
|
|
243
|
+
intervalMs: number;
|
|
244
|
+
stream: string | null;
|
|
245
|
+
instance: string | null;
|
|
246
|
+
attributes: Record<string, string>;
|
|
247
|
+
dimensionPairs: string[];
|
|
248
|
+
dimensionKey: string | null;
|
|
249
|
+
seriesKey: string;
|
|
250
|
+
summary: AggSummaryState;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export function buildMetricsBlockRecord(docId: number, value: unknown): Result<MetricsBlockRecord, { message: string }> {
|
|
254
|
+
const normalizedRes = normalizeMetricsRecordResult(value);
|
|
255
|
+
if (Result.isError(normalizedRes)) return normalizedRes;
|
|
256
|
+
return Result.ok({
|
|
257
|
+
doc_id: docId,
|
|
258
|
+
...normalizedRes.value.companion,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function materializeMetricsBlockRecord(record: MetricsBlockRecord): Record<string, unknown> {
|
|
263
|
+
const histogram = record.summary.histogram ?? {};
|
|
264
|
+
const avg = record.summary.count > 0 ? record.summary.sum / record.summary.count : 0;
|
|
265
|
+
return {
|
|
266
|
+
apiVersion: "durable.streams/metrics/v1",
|
|
267
|
+
kind: "interval",
|
|
268
|
+
metric: record.metric,
|
|
269
|
+
unit: record.unit,
|
|
270
|
+
metricKind: record.metricKind,
|
|
271
|
+
temporality: record.temporality,
|
|
272
|
+
windowStart: record.windowStartMs,
|
|
273
|
+
windowEnd: record.windowEndMs,
|
|
274
|
+
intervalMs: record.intervalMs,
|
|
275
|
+
instance: record.instance,
|
|
276
|
+
stream: record.stream,
|
|
277
|
+
tags: { ...record.attributes },
|
|
278
|
+
attributes: { ...record.attributes },
|
|
279
|
+
dimensionPairs: [...record.dimensionPairs],
|
|
280
|
+
dimensionKey: record.dimensionKey,
|
|
281
|
+
seriesKey: record.seriesKey,
|
|
282
|
+
count: record.summary.count,
|
|
283
|
+
sum: record.summary.sum,
|
|
284
|
+
min: record.summary.min,
|
|
285
|
+
max: record.summary.max,
|
|
286
|
+
avg,
|
|
287
|
+
p50: histogramPercentile(histogram, 0.5) ?? 0,
|
|
288
|
+
p95: histogramPercentile(histogram, 0.95) ?? 0,
|
|
289
|
+
p99: histogramPercentile(histogram, 0.99) ?? 0,
|
|
290
|
+
buckets: { ...histogram },
|
|
291
|
+
summary: {
|
|
292
|
+
count: record.summary.count,
|
|
293
|
+
sum: record.summary.sum,
|
|
294
|
+
min: record.summary.min,
|
|
295
|
+
max: record.summary.max,
|
|
296
|
+
histogram: { ...histogram },
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function buildInternalMetricsRecord(args: {
|
|
302
|
+
metric: string;
|
|
303
|
+
unit: string;
|
|
304
|
+
windowStart: number;
|
|
305
|
+
windowEnd: number;
|
|
306
|
+
intervalMs: number;
|
|
307
|
+
instance: string;
|
|
308
|
+
stream?: string;
|
|
309
|
+
tags?: Record<string, PrimitiveAttribute>;
|
|
310
|
+
count: number;
|
|
311
|
+
sum: number;
|
|
312
|
+
min: number;
|
|
313
|
+
max: number;
|
|
314
|
+
avg: number;
|
|
315
|
+
p50: number;
|
|
316
|
+
p95: number;
|
|
317
|
+
p99: number;
|
|
318
|
+
buckets: Record<string, number>;
|
|
319
|
+
}): Record<string, unknown> {
|
|
320
|
+
const attributes = normalizeAttributes(args.tags);
|
|
321
|
+
const dimensionPairs = buildDimensionPairs(attributes);
|
|
322
|
+
const dimensionKey = dimensionPairs.length > 0 ? dimensionPairs.join("\u0000") : null;
|
|
323
|
+
const seriesKey = buildSeriesKey({
|
|
324
|
+
metricKind: "summary",
|
|
325
|
+
temporality: "delta",
|
|
326
|
+
metric: args.metric,
|
|
327
|
+
unit: args.unit,
|
|
328
|
+
stream: args.stream ?? null,
|
|
329
|
+
instance: args.instance,
|
|
330
|
+
dimensionKey,
|
|
331
|
+
});
|
|
332
|
+
return {
|
|
333
|
+
apiVersion: "durable.streams/metrics/v1",
|
|
334
|
+
kind: "interval",
|
|
335
|
+
metric: args.metric,
|
|
336
|
+
unit: args.unit,
|
|
337
|
+
metricKind: "summary",
|
|
338
|
+
temporality: "delta",
|
|
339
|
+
windowStart: args.windowStart,
|
|
340
|
+
windowEnd: args.windowEnd,
|
|
341
|
+
intervalMs: args.intervalMs,
|
|
342
|
+
instance: args.instance,
|
|
343
|
+
stream: args.stream ?? null,
|
|
344
|
+
tags: attributes,
|
|
345
|
+
attributes,
|
|
346
|
+
dimensionPairs,
|
|
347
|
+
dimensionKey,
|
|
348
|
+
seriesKey,
|
|
349
|
+
count: args.count,
|
|
350
|
+
sum: args.sum,
|
|
351
|
+
min: args.count === 0 ? null : args.min,
|
|
352
|
+
max: args.count === 0 ? null : args.max,
|
|
353
|
+
avg: args.avg,
|
|
354
|
+
p50: args.p50,
|
|
355
|
+
p95: args.p95,
|
|
356
|
+
p99: args.p99,
|
|
357
|
+
buckets: { ...args.buckets },
|
|
358
|
+
summary: {
|
|
359
|
+
count: args.count,
|
|
360
|
+
sum: args.sum,
|
|
361
|
+
min: args.count === 0 ? null : args.min,
|
|
362
|
+
max: args.count === 0 ? null : args.max,
|
|
363
|
+
histogram: { ...args.buckets },
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SCHEMA_REGISTRY_API_VERSION,
|
|
3
|
+
type SchemaRegistry,
|
|
4
|
+
type SearchConfig,
|
|
5
|
+
} from "../../schema/registry";
|
|
6
|
+
|
|
7
|
+
export const METRICS_CANONICAL_SCHEMA = {
|
|
8
|
+
type: "object",
|
|
9
|
+
additionalProperties: false,
|
|
10
|
+
properties: {
|
|
11
|
+
apiVersion: { type: "string", const: "durable.streams/metrics/v1" },
|
|
12
|
+
kind: { type: "string", const: "interval" },
|
|
13
|
+
metric: { type: "string" },
|
|
14
|
+
unit: { type: "string" },
|
|
15
|
+
metricKind: { type: "string" },
|
|
16
|
+
temporality: { type: "string" },
|
|
17
|
+
windowStart: { type: "integer" },
|
|
18
|
+
windowEnd: { type: "integer" },
|
|
19
|
+
intervalMs: { type: "integer" },
|
|
20
|
+
instance: { type: ["string", "null"] },
|
|
21
|
+
stream: { type: ["string", "null"] },
|
|
22
|
+
tags: {
|
|
23
|
+
type: "object",
|
|
24
|
+
additionalProperties: { type: "string" },
|
|
25
|
+
},
|
|
26
|
+
attributes: {
|
|
27
|
+
type: "object",
|
|
28
|
+
additionalProperties: { type: "string" },
|
|
29
|
+
},
|
|
30
|
+
dimensionPairs: {
|
|
31
|
+
type: "array",
|
|
32
|
+
items: { type: "string" },
|
|
33
|
+
},
|
|
34
|
+
dimensionKey: { type: ["string", "null"] },
|
|
35
|
+
seriesKey: { type: "string" },
|
|
36
|
+
count: { type: "number" },
|
|
37
|
+
sum: { type: "number" },
|
|
38
|
+
min: { type: ["number", "null"] },
|
|
39
|
+
max: { type: ["number", "null"] },
|
|
40
|
+
avg: { type: "number" },
|
|
41
|
+
p50: { type: "number" },
|
|
42
|
+
p95: { type: "number" },
|
|
43
|
+
p99: { type: "number" },
|
|
44
|
+
buckets: {
|
|
45
|
+
type: "object",
|
|
46
|
+
additionalProperties: { type: "number" },
|
|
47
|
+
},
|
|
48
|
+
summary: {
|
|
49
|
+
type: "object",
|
|
50
|
+
additionalProperties: false,
|
|
51
|
+
properties: {
|
|
52
|
+
count: { type: "number" },
|
|
53
|
+
sum: { type: "number" },
|
|
54
|
+
min: { type: ["number", "null"] },
|
|
55
|
+
max: { type: ["number", "null"] },
|
|
56
|
+
histogram: {
|
|
57
|
+
type: "object",
|
|
58
|
+
additionalProperties: { type: "number" },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
required: ["count", "sum", "min", "max", "histogram"],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
required: [
|
|
65
|
+
"apiVersion",
|
|
66
|
+
"kind",
|
|
67
|
+
"metric",
|
|
68
|
+
"unit",
|
|
69
|
+
"metricKind",
|
|
70
|
+
"temporality",
|
|
71
|
+
"windowStart",
|
|
72
|
+
"windowEnd",
|
|
73
|
+
"intervalMs",
|
|
74
|
+
"instance",
|
|
75
|
+
"stream",
|
|
76
|
+
"tags",
|
|
77
|
+
"attributes",
|
|
78
|
+
"dimensionPairs",
|
|
79
|
+
"dimensionKey",
|
|
80
|
+
"seriesKey",
|
|
81
|
+
"count",
|
|
82
|
+
"sum",
|
|
83
|
+
"min",
|
|
84
|
+
"max",
|
|
85
|
+
"avg",
|
|
86
|
+
"p50",
|
|
87
|
+
"p95",
|
|
88
|
+
"p99",
|
|
89
|
+
"buckets",
|
|
90
|
+
"summary",
|
|
91
|
+
],
|
|
92
|
+
} as const;
|
|
93
|
+
|
|
94
|
+
export const METRICS_DEFAULT_SEARCH_CONFIG: SearchConfig = {
|
|
95
|
+
profile: "metrics",
|
|
96
|
+
primaryTimestampField: "windowStart",
|
|
97
|
+
aliases: {
|
|
98
|
+
ts: "windowStart",
|
|
99
|
+
time: "windowStart",
|
|
100
|
+
name: "metric",
|
|
101
|
+
dims: "dimensionKey",
|
|
102
|
+
series: "seriesKey",
|
|
103
|
+
},
|
|
104
|
+
defaultFields: [
|
|
105
|
+
{ field: "metric", boost: 2 },
|
|
106
|
+
{ field: "stream", boost: 1.25 },
|
|
107
|
+
{ field: "dimensionPairs", boost: 1 },
|
|
108
|
+
],
|
|
109
|
+
fields: {
|
|
110
|
+
windowStart: {
|
|
111
|
+
kind: "date",
|
|
112
|
+
bindings: [{ version: 1, jsonPointer: "/windowStart" }],
|
|
113
|
+
exact: true,
|
|
114
|
+
column: true,
|
|
115
|
+
exists: true,
|
|
116
|
+
sortable: true,
|
|
117
|
+
aggregatable: true,
|
|
118
|
+
},
|
|
119
|
+
windowEnd: {
|
|
120
|
+
kind: "date",
|
|
121
|
+
bindings: [{ version: 1, jsonPointer: "/windowEnd" }],
|
|
122
|
+
exact: true,
|
|
123
|
+
column: true,
|
|
124
|
+
exists: true,
|
|
125
|
+
sortable: true,
|
|
126
|
+
},
|
|
127
|
+
intervalMs: {
|
|
128
|
+
kind: "integer",
|
|
129
|
+
bindings: [{ version: 1, jsonPointer: "/intervalMs" }],
|
|
130
|
+
exact: true,
|
|
131
|
+
column: true,
|
|
132
|
+
exists: true,
|
|
133
|
+
sortable: true,
|
|
134
|
+
aggregatable: true,
|
|
135
|
+
},
|
|
136
|
+
metric: {
|
|
137
|
+
kind: "keyword",
|
|
138
|
+
bindings: [{ version: 1, jsonPointer: "/metric" }],
|
|
139
|
+
normalizer: "lowercase_v1",
|
|
140
|
+
exact: true,
|
|
141
|
+
prefix: true,
|
|
142
|
+
exists: true,
|
|
143
|
+
sortable: true,
|
|
144
|
+
aggregatable: true,
|
|
145
|
+
},
|
|
146
|
+
unit: {
|
|
147
|
+
kind: "keyword",
|
|
148
|
+
bindings: [{ version: 1, jsonPointer: "/unit" }],
|
|
149
|
+
normalizer: "lowercase_v1",
|
|
150
|
+
exact: true,
|
|
151
|
+
prefix: true,
|
|
152
|
+
exists: true,
|
|
153
|
+
aggregatable: true,
|
|
154
|
+
},
|
|
155
|
+
metricKind: {
|
|
156
|
+
kind: "keyword",
|
|
157
|
+
bindings: [{ version: 1, jsonPointer: "/metricKind" }],
|
|
158
|
+
normalizer: "lowercase_v1",
|
|
159
|
+
exact: true,
|
|
160
|
+
prefix: true,
|
|
161
|
+
exists: true,
|
|
162
|
+
aggregatable: true,
|
|
163
|
+
},
|
|
164
|
+
temporality: {
|
|
165
|
+
kind: "keyword",
|
|
166
|
+
bindings: [{ version: 1, jsonPointer: "/temporality" }],
|
|
167
|
+
normalizer: "lowercase_v1",
|
|
168
|
+
exact: true,
|
|
169
|
+
prefix: true,
|
|
170
|
+
exists: true,
|
|
171
|
+
aggregatable: true,
|
|
172
|
+
},
|
|
173
|
+
stream: {
|
|
174
|
+
kind: "keyword",
|
|
175
|
+
bindings: [{ version: 1, jsonPointer: "/stream" }],
|
|
176
|
+
normalizer: "lowercase_v1",
|
|
177
|
+
exact: true,
|
|
178
|
+
prefix: true,
|
|
179
|
+
exists: true,
|
|
180
|
+
sortable: true,
|
|
181
|
+
aggregatable: true,
|
|
182
|
+
},
|
|
183
|
+
instance: {
|
|
184
|
+
kind: "keyword",
|
|
185
|
+
bindings: [{ version: 1, jsonPointer: "/instance" }],
|
|
186
|
+
exact: true,
|
|
187
|
+
prefix: true,
|
|
188
|
+
exists: true,
|
|
189
|
+
sortable: true,
|
|
190
|
+
aggregatable: true,
|
|
191
|
+
},
|
|
192
|
+
seriesKey: {
|
|
193
|
+
kind: "keyword",
|
|
194
|
+
bindings: [{ version: 1, jsonPointer: "/seriesKey" }],
|
|
195
|
+
exact: true,
|
|
196
|
+
exists: true,
|
|
197
|
+
sortable: true,
|
|
198
|
+
},
|
|
199
|
+
dimensionKey: {
|
|
200
|
+
kind: "keyword",
|
|
201
|
+
bindings: [{ version: 1, jsonPointer: "/dimensionKey" }],
|
|
202
|
+
exact: true,
|
|
203
|
+
prefix: true,
|
|
204
|
+
exists: true,
|
|
205
|
+
sortable: true,
|
|
206
|
+
aggregatable: true,
|
|
207
|
+
},
|
|
208
|
+
dimensionPairs: {
|
|
209
|
+
kind: "keyword",
|
|
210
|
+
bindings: [{ version: 1, jsonPointer: "/dimensionPairs" }],
|
|
211
|
+
exact: true,
|
|
212
|
+
prefix: true,
|
|
213
|
+
exists: true,
|
|
214
|
+
},
|
|
215
|
+
count: {
|
|
216
|
+
kind: "integer",
|
|
217
|
+
bindings: [{ version: 1, jsonPointer: "/count" }],
|
|
218
|
+
exact: true,
|
|
219
|
+
column: true,
|
|
220
|
+
exists: true,
|
|
221
|
+
sortable: true,
|
|
222
|
+
aggregatable: true,
|
|
223
|
+
},
|
|
224
|
+
sum: {
|
|
225
|
+
kind: "float",
|
|
226
|
+
bindings: [{ version: 1, jsonPointer: "/sum" }],
|
|
227
|
+
exact: true,
|
|
228
|
+
column: true,
|
|
229
|
+
exists: true,
|
|
230
|
+
sortable: true,
|
|
231
|
+
aggregatable: true,
|
|
232
|
+
},
|
|
233
|
+
min: {
|
|
234
|
+
kind: "float",
|
|
235
|
+
bindings: [{ version: 1, jsonPointer: "/min" }],
|
|
236
|
+
exact: true,
|
|
237
|
+
column: true,
|
|
238
|
+
exists: true,
|
|
239
|
+
sortable: true,
|
|
240
|
+
},
|
|
241
|
+
max: {
|
|
242
|
+
kind: "float",
|
|
243
|
+
bindings: [{ version: 1, jsonPointer: "/max" }],
|
|
244
|
+
exact: true,
|
|
245
|
+
column: true,
|
|
246
|
+
exists: true,
|
|
247
|
+
sortable: true,
|
|
248
|
+
},
|
|
249
|
+
avg: {
|
|
250
|
+
kind: "float",
|
|
251
|
+
bindings: [{ version: 1, jsonPointer: "/avg" }],
|
|
252
|
+
exact: true,
|
|
253
|
+
column: true,
|
|
254
|
+
exists: true,
|
|
255
|
+
sortable: true,
|
|
256
|
+
},
|
|
257
|
+
p95: {
|
|
258
|
+
kind: "float",
|
|
259
|
+
bindings: [{ version: 1, jsonPointer: "/p95" }],
|
|
260
|
+
exact: true,
|
|
261
|
+
column: true,
|
|
262
|
+
exists: true,
|
|
263
|
+
sortable: true,
|
|
264
|
+
},
|
|
265
|
+
p99: {
|
|
266
|
+
kind: "float",
|
|
267
|
+
bindings: [{ version: 1, jsonPointer: "/p99" }],
|
|
268
|
+
exact: true,
|
|
269
|
+
column: true,
|
|
270
|
+
exists: true,
|
|
271
|
+
sortable: true,
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
rollups: {
|
|
275
|
+
metrics: {
|
|
276
|
+
timestampField: "windowStart",
|
|
277
|
+
dimensions: ["metric", "unit", "stream", "instance", "dimensionKey", "metricKind", "temporality"],
|
|
278
|
+
intervals: ["10s", "1m", "5m", "1h"],
|
|
279
|
+
measures: {
|
|
280
|
+
value: {
|
|
281
|
+
kind: "summary_parts",
|
|
282
|
+
countJsonPointer: "/count",
|
|
283
|
+
sumJsonPointer: "/sum",
|
|
284
|
+
minJsonPointer: "/min",
|
|
285
|
+
maxJsonPointer: "/max",
|
|
286
|
+
histogramJsonPointer: "/buckets",
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export function buildMetricsDefaultRegistry(stream: string): SchemaRegistry {
|
|
294
|
+
return {
|
|
295
|
+
apiVersion: SCHEMA_REGISTRY_API_VERSION,
|
|
296
|
+
schema: stream,
|
|
297
|
+
currentVersion: 1,
|
|
298
|
+
routingKey: { jsonPointer: "/seriesKey", required: true },
|
|
299
|
+
search: structuredClone(METRICS_DEFAULT_SEARCH_CONFIG),
|
|
300
|
+
boundaries: [{ offset: 0, version: 1 }],
|
|
301
|
+
schemas: {
|
|
302
|
+
"1": structuredClone(METRICS_CANONICAL_SCHEMA),
|
|
303
|
+
},
|
|
304
|
+
lenses: {},
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function buildInternalMetricsRegistry(stream: string): SchemaRegistry {
|
|
309
|
+
return {
|
|
310
|
+
apiVersion: SCHEMA_REGISTRY_API_VERSION,
|
|
311
|
+
schema: stream,
|
|
312
|
+
currentVersion: 1,
|
|
313
|
+
boundaries: [{ offset: 0, version: 1 }],
|
|
314
|
+
schemas: {
|
|
315
|
+
"1": structuredClone(METRICS_CANONICAL_SCHEMA),
|
|
316
|
+
},
|
|
317
|
+
lenses: {},
|
|
318
|
+
};
|
|
319
|
+
}
|