Setting the file. One moment. Telemetry · Clickhouse Js Node Rowbinary · ClickHouse/agent-skills · Skills Docssrc/examples/telemetry.ts
src/examples/telemetry.ts
TypeScript·102 lines·3 KB
import
{ readString }
from
"../readers/strings.js"
;
11import { readUVarint } from "../readers/varint.js";
12
13/**
14 * Example: a telemetry table — composite readers that nest.
15 *
16 * Columns (the trigger):
17 * host String
18 * tags Map(String, String)
19 * cpu Array(Float64)
20 * region Nullable(String)
21 * window Tuple(start UInt32, count UInt16)
22 *
23 * The combinators compose exactly the way the column type nests:
24 * `readMap(k, v)`, `readArray(elem)`, `readNullable(inner)`, and
25 * `readTupleNamed({...})` each take sub-readers and return a `Reader`. This is the
26 * generic (closure-per-element) API; a generated parser would monomorphize these
27 * into inlined per-type loops, but the result shape is exactly this.
28 */
29export type TelemetryRow = {
30 host: string;
31 tags: Map<string, string>;
32 cpu: number[];
33 region: string | null;
34 window: { start: number; count: number };
35};
36
37export const readTelemetryRow: Reader<TelemetryRow> = (s) => ({
38 host: readString(s),
39 tags: readMap(readString, readString)(s),
40 cpu: readArray(readFloat64)(s),
41 region: readNullable(readString)(s),
42 window: readTupleNamed({ start: readUInt32, count: readUInt16 })(s),
43});
44
45/**
46 * Optimized {@link readTelemetryRow}, fully monomorphized: the API version above
47 * builds FOUR combinator closures per row (`readMap(...)`, `readArray(...)`,
48 * `readNullable(...)`, `readTupleNamed(...)`) and, for the named tuple, iterates
49 * a keys array building an object field by field. Here every loop and branch is
50 * inlined and the `window` object is a flat literal — no per-row closures, no
51 * key iteration. The most composite-heavy example.
52 *
53 * MEASURED (Node 24 / V8, `telemetry.bench.ts`): ~1.4x faster — four per-row
54 * combinator closures and the named-tuple key iteration removed.
55 */
56export const readTelemetryRowFast: Reader<TelemetryRow> = (s) => {
57 const { buf, view } = s;
58
59 // host String: length prefix, then the bytes.
60 let len = readUVarint(s);
61 let start = advance(s, len);
62 const host = buf.toString("utf8", start, start + len);
63
64 // tags Map(String, String): count, then key/value strings.
65 const mapN = readUVarint(s);
66 const tags = new Map<string, string>();
67 for (let i = 0; i < mapN; i++) {
68 len = readUVarint(s);
69 start = advance(s, len);
70 const k = buf.toString("utf8", start, start + len);
71 len = readUVarint(s);
72 start = advance(s, len);
73 tags.set(k, buf.toString("utf8", start, start + len));
74 }
75
76 // cpu Array(Float64): count, then 8 bytes each.
77 const cpuN = readUVarint(s);
78 const cpu = new Array<number>(cpuN);
79 for (let i = 0; i < cpuN; i++) {
80 cpu[i] = view.getFloat64(advance(s, 8), true);
81 }
82
83 // region Nullable(String): null-flag byte, then if non-null a string.
84 let region: string | null;
85 if (buf[advance(s, 1)]! !== 0) {
86 region = null;
87 } else {
88 len = readUVarint(s);
89 start = advance(s, len);
90 region = buf.toString("utf8", start, start + len);
91 }
92
93 // window Tuple(start UInt32, count UInt16): two adjacent fixed-width fields,
94 // bounds-checked once (6 bytes), then read at literal offsets.
95 const w = advance(s, 6);
96 const window = {
97 start: view.getUint32(w, true),
98 count: view.getUint16(w + 4, true),
99 };
100
101 return { host, tags, cpu, region, window };
102};