@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,200 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export type RuntimeMemorySubsystemGroups = {
|
|
4
|
+
heap_estimates: Record<string, number>;
|
|
5
|
+
mapped_files: Record<string, number>;
|
|
6
|
+
disk_caches: Record<string, number>;
|
|
7
|
+
configured_budgets: Record<string, number>;
|
|
8
|
+
pipeline_buffers: Record<string, number>;
|
|
9
|
+
sqlite_runtime: Record<string, number>;
|
|
10
|
+
counts: Record<string, number>;
|
|
11
|
+
[kind: string]: Record<string, number>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type RuntimeMemoryProcessUsage = {
|
|
15
|
+
rss_bytes: number;
|
|
16
|
+
heap_total_bytes: number;
|
|
17
|
+
heap_used_bytes: number;
|
|
18
|
+
external_bytes: number;
|
|
19
|
+
array_buffers_bytes: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type RuntimeMemoryProcessBreakdown = {
|
|
23
|
+
source: "linux_proc_status" | "process_memory_usage_only";
|
|
24
|
+
rss_anon_bytes: number | null;
|
|
25
|
+
rss_file_bytes: number | null;
|
|
26
|
+
rss_shmem_bytes: number | null;
|
|
27
|
+
js_managed_bytes: number;
|
|
28
|
+
js_external_non_array_buffers_bytes: number;
|
|
29
|
+
mapped_file_bytes: number;
|
|
30
|
+
sqlite_runtime_bytes: number;
|
|
31
|
+
unattributed_anon_bytes: number | null;
|
|
32
|
+
unattributed_rss_bytes: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type SqliteRuntimeMemoryStats = {
|
|
36
|
+
available: boolean;
|
|
37
|
+
source: "sqlite3_status64" | "unavailable";
|
|
38
|
+
memory_used_bytes: number;
|
|
39
|
+
memory_highwater_bytes: number;
|
|
40
|
+
pagecache_used_slots: number;
|
|
41
|
+
pagecache_used_slots_highwater: number;
|
|
42
|
+
pagecache_overflow_bytes: number;
|
|
43
|
+
pagecache_overflow_highwater_bytes: number;
|
|
44
|
+
malloc_count: number;
|
|
45
|
+
malloc_count_highwater: number;
|
|
46
|
+
open_connections: number;
|
|
47
|
+
prepared_statements: number;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type RuntimeGcStats = {
|
|
51
|
+
forced_gc_count: number;
|
|
52
|
+
forced_gc_reclaimed_bytes_total: number;
|
|
53
|
+
last_forced_gc_at_ms: number | null;
|
|
54
|
+
last_forced_gc_before_bytes: number | null;
|
|
55
|
+
last_forced_gc_after_bytes: number | null;
|
|
56
|
+
last_forced_gc_reclaimed_bytes: number | null;
|
|
57
|
+
heap_snapshots_written: number;
|
|
58
|
+
last_heap_snapshot_at_ms: number | null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type RuntimeMemoryTotals = {
|
|
62
|
+
heap_estimate_bytes: number;
|
|
63
|
+
mapped_file_bytes: number;
|
|
64
|
+
disk_cache_bytes: number;
|
|
65
|
+
configured_budget_bytes: number;
|
|
66
|
+
pipeline_buffer_bytes: number;
|
|
67
|
+
sqlite_runtime_bytes: number;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type RuntimeMemorySubsystemSnapshot = {
|
|
71
|
+
subsystems: RuntimeMemorySubsystemGroups;
|
|
72
|
+
totals: RuntimeMemoryTotals;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type RuntimeMemorySnapshot = {
|
|
76
|
+
process: RuntimeMemoryProcessUsage;
|
|
77
|
+
process_breakdown: RuntimeMemoryProcessBreakdown;
|
|
78
|
+
sqlite: SqliteRuntimeMemoryStats;
|
|
79
|
+
gc: RuntimeGcStats;
|
|
80
|
+
} & RuntimeMemorySubsystemSnapshot;
|
|
81
|
+
|
|
82
|
+
export type RuntimeHighWaterMark = {
|
|
83
|
+
value: number;
|
|
84
|
+
at: string | null;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type RuntimeMemoryHighWaterSnapshot = {
|
|
88
|
+
process: Record<string, RuntimeHighWaterMark>;
|
|
89
|
+
process_breakdown: Record<string, RuntimeHighWaterMark>;
|
|
90
|
+
sqlite: Record<string, RuntimeHighWaterMark>;
|
|
91
|
+
runtime_bytes: Record<string, Record<string, RuntimeHighWaterMark>>;
|
|
92
|
+
runtime_totals: Record<string, RuntimeHighWaterMark>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type LinuxStatusRssBreakdown = {
|
|
96
|
+
rss_anon_bytes: number;
|
|
97
|
+
rss_file_bytes: number;
|
|
98
|
+
rss_shmem_bytes: number;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
type CachedLinuxStatusBreakdown = {
|
|
102
|
+
at_ms: number;
|
|
103
|
+
pid: number;
|
|
104
|
+
value: LinuxStatusRssBreakdown | null;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
let cachedLinuxStatusBreakdown: CachedLinuxStatusBreakdown | null = null;
|
|
108
|
+
|
|
109
|
+
export function sumRuntimeMemoryValues(values: Record<string, number>): number {
|
|
110
|
+
let total = 0;
|
|
111
|
+
for (const value of Object.values(values)) total += Math.max(0, Math.floor(value));
|
|
112
|
+
return total;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function parseLinuxStatusRssBreakdown(status: string): LinuxStatusRssBreakdown | null {
|
|
116
|
+
let anon: number | null = null;
|
|
117
|
+
let file: number | null = null;
|
|
118
|
+
let shmem: number | null = null;
|
|
119
|
+
for (const line of status.split(/\r?\n/)) {
|
|
120
|
+
let match = line.match(/^RssAnon:\s+([0-9]+)\s+kB$/i);
|
|
121
|
+
if (match) {
|
|
122
|
+
anon = Number(match[1]) * 1024;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
match = line.match(/^RssFile:\s+([0-9]+)\s+kB$/i);
|
|
126
|
+
if (match) {
|
|
127
|
+
file = Number(match[1]) * 1024;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
match = line.match(/^RssShmem:\s+([0-9]+)\s+kB$/i);
|
|
131
|
+
if (match) {
|
|
132
|
+
shmem = Number(match[1]) * 1024;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (anon == null || file == null || shmem == null) return null;
|
|
136
|
+
return {
|
|
137
|
+
rss_anon_bytes: Math.max(0, Math.floor(anon)),
|
|
138
|
+
rss_file_bytes: Math.max(0, Math.floor(file)),
|
|
139
|
+
rss_shmem_bytes: Math.max(0, Math.floor(shmem)),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function readLinuxStatusRssBreakdown(ttlMs = 1_000): LinuxStatusRssBreakdown | null {
|
|
144
|
+
if (process.platform !== "linux") return null;
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
if (
|
|
147
|
+
cachedLinuxStatusBreakdown &&
|
|
148
|
+
cachedLinuxStatusBreakdown.pid === process.pid &&
|
|
149
|
+
now - cachedLinuxStatusBreakdown.at_ms < Math.max(0, ttlMs)
|
|
150
|
+
) {
|
|
151
|
+
return cachedLinuxStatusBreakdown.value;
|
|
152
|
+
}
|
|
153
|
+
let value: LinuxStatusRssBreakdown | null = null;
|
|
154
|
+
try {
|
|
155
|
+
value = parseLinuxStatusRssBreakdown(readFileSync("/proc/self/status", "utf8"));
|
|
156
|
+
} catch {
|
|
157
|
+
value = null;
|
|
158
|
+
}
|
|
159
|
+
cachedLinuxStatusBreakdown = {
|
|
160
|
+
at_ms: now,
|
|
161
|
+
pid: process.pid,
|
|
162
|
+
value,
|
|
163
|
+
};
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function buildProcessMemoryBreakdown(args: {
|
|
168
|
+
process: RuntimeMemoryProcessUsage;
|
|
169
|
+
mappedFileBytes: number;
|
|
170
|
+
sqliteRuntimeBytes: number;
|
|
171
|
+
}): RuntimeMemoryProcessBreakdown {
|
|
172
|
+
const rssBreakdown = readLinuxStatusRssBreakdown();
|
|
173
|
+
const jsManagedBytes = Math.max(0, Math.floor(args.process.heap_used_bytes + args.process.external_bytes));
|
|
174
|
+
const jsExternalNonArrayBuffersBytes = Math.max(
|
|
175
|
+
0,
|
|
176
|
+
Math.floor(args.process.external_bytes - args.process.array_buffers_bytes)
|
|
177
|
+
);
|
|
178
|
+
const mappedFileBytes = Math.max(0, Math.floor(args.mappedFileBytes));
|
|
179
|
+
const sqliteRuntimeBytes = Math.max(0, Math.floor(args.sqliteRuntimeBytes));
|
|
180
|
+
const unattributedRssBytes = Math.max(
|
|
181
|
+
0,
|
|
182
|
+
Math.floor(args.process.rss_bytes - jsManagedBytes - mappedFileBytes - sqliteRuntimeBytes)
|
|
183
|
+
);
|
|
184
|
+
const unattributedAnonBytes =
|
|
185
|
+
rssBreakdown == null
|
|
186
|
+
? null
|
|
187
|
+
: Math.max(0, Math.floor(rssBreakdown.rss_anon_bytes - jsManagedBytes - sqliteRuntimeBytes));
|
|
188
|
+
return {
|
|
189
|
+
source: rssBreakdown ? "linux_proc_status" : "process_memory_usage_only",
|
|
190
|
+
rss_anon_bytes: rssBreakdown?.rss_anon_bytes ?? null,
|
|
191
|
+
rss_file_bytes: rssBreakdown?.rss_file_bytes ?? null,
|
|
192
|
+
rss_shmem_bytes: rssBreakdown?.rss_shmem_bytes ?? null,
|
|
193
|
+
js_managed_bytes: jsManagedBytes,
|
|
194
|
+
js_external_non_array_buffers_bytes: jsExternalNonArrayBuffersBytes,
|
|
195
|
+
mapped_file_bytes: mappedFileBytes,
|
|
196
|
+
sqlite_runtime_bytes: sqliteRuntimeBytes,
|
|
197
|
+
unattributed_anon_bytes: unattributedAnonBytes,
|
|
198
|
+
unattributed_rss_bytes: unattributedRssBytes,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { mkdirSync, createWriteStream, type WriteStream } from "node:fs";
|
|
2
|
+
import { dirname, extname } from "node:path";
|
|
3
|
+
import { isMainThread, threadId } from "node:worker_threads";
|
|
4
|
+
|
|
5
|
+
type BunJscModule = {
|
|
6
|
+
heapStats?: () => unknown;
|
|
7
|
+
memoryUsage?: () => unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type PhaseMeta = Record<string, unknown>;
|
|
11
|
+
|
|
12
|
+
type ActivePhase = {
|
|
13
|
+
key: number;
|
|
14
|
+
label: string;
|
|
15
|
+
startedAtMs: number;
|
|
16
|
+
meta: PhaseMeta;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function isPromiseLike<T>(value: unknown): value is PromiseLike<T> {
|
|
20
|
+
return typeof value === "object" && value != null && "then" in value && typeof (value as { then?: unknown }).then === "function";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function insertScopeSuffix(path: string, scope: string): string {
|
|
24
|
+
if (scope === "main") return path;
|
|
25
|
+
const extension = extname(path);
|
|
26
|
+
if (extension === "") return `${path}.${scope}`;
|
|
27
|
+
return `${path.slice(0, -extension.length)}.${scope}${extension}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class RuntimeMemorySampler {
|
|
31
|
+
private readonly outputPath: string;
|
|
32
|
+
private readonly intervalMs: number;
|
|
33
|
+
private readonly scope: string;
|
|
34
|
+
private stream: WriteStream | null = null;
|
|
35
|
+
private timer: any | null = null;
|
|
36
|
+
private activePhases = new Map<number, ActivePhase>();
|
|
37
|
+
private nextPhaseKey = 0;
|
|
38
|
+
private jscModule: BunJscModule | null | undefined;
|
|
39
|
+
private sampling = false;
|
|
40
|
+
private subsystemProvider: (() => unknown) | null = null;
|
|
41
|
+
|
|
42
|
+
constructor(path: string, opts: { intervalMs?: number; scope?: string } = {}) {
|
|
43
|
+
this.scope = opts.scope?.trim() || "main";
|
|
44
|
+
this.outputPath = insertScopeSuffix(path, this.scope);
|
|
45
|
+
this.intervalMs = Math.max(100, opts.intervalMs ?? 1_000);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
start(): void {
|
|
49
|
+
if (this.stream) return;
|
|
50
|
+
mkdirSync(dirname(this.outputPath), { recursive: true });
|
|
51
|
+
this.stream = createWriteStream(this.outputPath, { flags: "a" });
|
|
52
|
+
this.writeLine({
|
|
53
|
+
ts: new Date().toISOString(),
|
|
54
|
+
kind: "sampler_start",
|
|
55
|
+
scope: this.scope,
|
|
56
|
+
pid: process.pid,
|
|
57
|
+
thread_id: threadId,
|
|
58
|
+
is_main_thread: isMainThread,
|
|
59
|
+
});
|
|
60
|
+
void this.sample("startup");
|
|
61
|
+
this.timer = setInterval(() => {
|
|
62
|
+
void this.sample("interval");
|
|
63
|
+
}, this.intervalMs);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
stop(): void {
|
|
67
|
+
if (this.timer) clearInterval(this.timer);
|
|
68
|
+
this.timer = null;
|
|
69
|
+
void this.sample("shutdown");
|
|
70
|
+
this.writeLine({
|
|
71
|
+
ts: new Date().toISOString(),
|
|
72
|
+
kind: "sampler_stop",
|
|
73
|
+
scope: this.scope,
|
|
74
|
+
pid: process.pid,
|
|
75
|
+
thread_id: threadId,
|
|
76
|
+
is_main_thread: isMainThread,
|
|
77
|
+
});
|
|
78
|
+
this.stream?.end();
|
|
79
|
+
this.stream = null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
enter(label: string, meta: PhaseMeta = {}): () => void {
|
|
83
|
+
if (!this.stream) return () => {};
|
|
84
|
+
const key = ++this.nextPhaseKey;
|
|
85
|
+
const phase: ActivePhase = {
|
|
86
|
+
key,
|
|
87
|
+
label,
|
|
88
|
+
startedAtMs: Date.now(),
|
|
89
|
+
meta,
|
|
90
|
+
};
|
|
91
|
+
this.activePhases.set(key, phase);
|
|
92
|
+
this.writeLine({
|
|
93
|
+
ts: new Date().toISOString(),
|
|
94
|
+
kind: "phase_enter",
|
|
95
|
+
scope: this.scope,
|
|
96
|
+
pid: process.pid,
|
|
97
|
+
thread_id: threadId,
|
|
98
|
+
label,
|
|
99
|
+
meta,
|
|
100
|
+
active_phase_count: this.activePhases.size,
|
|
101
|
+
});
|
|
102
|
+
void this.sample("phase_enter", { label, meta });
|
|
103
|
+
return () => {
|
|
104
|
+
const current = this.activePhases.get(key);
|
|
105
|
+
if (!current) return;
|
|
106
|
+
this.activePhases.delete(key);
|
|
107
|
+
this.writeLine({
|
|
108
|
+
ts: new Date().toISOString(),
|
|
109
|
+
kind: "phase_exit",
|
|
110
|
+
scope: this.scope,
|
|
111
|
+
pid: process.pid,
|
|
112
|
+
thread_id: threadId,
|
|
113
|
+
label: current.label,
|
|
114
|
+
meta: current.meta,
|
|
115
|
+
duration_ms: Date.now() - current.startedAtMs,
|
|
116
|
+
active_phase_count: this.activePhases.size,
|
|
117
|
+
});
|
|
118
|
+
void this.sample("phase_exit", { label: current.label, meta: current.meta });
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
capture(reason: string, data: PhaseMeta = {}): void {
|
|
123
|
+
if (!this.stream) return;
|
|
124
|
+
this.writeLine({
|
|
125
|
+
ts: new Date().toISOString(),
|
|
126
|
+
kind: "marker",
|
|
127
|
+
scope: this.scope,
|
|
128
|
+
pid: process.pid,
|
|
129
|
+
thread_id: threadId,
|
|
130
|
+
reason,
|
|
131
|
+
data,
|
|
132
|
+
});
|
|
133
|
+
void this.sample(reason, data);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setSubsystemProvider(provider: (() => unknown) | null): void {
|
|
137
|
+
this.subsystemProvider = provider;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
track<T>(label: string, meta: PhaseMeta, fn: () => T): T {
|
|
141
|
+
const leave = this.enter(label, meta);
|
|
142
|
+
try {
|
|
143
|
+
const result = fn();
|
|
144
|
+
if (isPromiseLike(result)) {
|
|
145
|
+
return (Promise.resolve(result).finally(() => leave()) as unknown) as T;
|
|
146
|
+
}
|
|
147
|
+
leave();
|
|
148
|
+
return result;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
leave();
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private async sample(reason: string, data: PhaseMeta = {}): Promise<void> {
|
|
156
|
+
if (!this.stream || this.sampling) return;
|
|
157
|
+
this.sampling = true;
|
|
158
|
+
try {
|
|
159
|
+
const activePhases = Array.from(this.activePhases.values()).map((phase) => ({
|
|
160
|
+
key: phase.key,
|
|
161
|
+
label: phase.label,
|
|
162
|
+
started_at: new Date(phase.startedAtMs).toISOString(),
|
|
163
|
+
duration_ms: Date.now() - phase.startedAtMs,
|
|
164
|
+
meta: phase.meta,
|
|
165
|
+
}));
|
|
166
|
+
const primary = activePhases.length > 0 ? activePhases[activePhases.length - 1]! : null;
|
|
167
|
+
this.writeLine({
|
|
168
|
+
ts: new Date().toISOString(),
|
|
169
|
+
kind: "sample",
|
|
170
|
+
scope: this.scope,
|
|
171
|
+
pid: process.pid,
|
|
172
|
+
thread_id: threadId,
|
|
173
|
+
is_main_thread: isMainThread,
|
|
174
|
+
reason,
|
|
175
|
+
data,
|
|
176
|
+
process_memory_usage: process.memoryUsage(),
|
|
177
|
+
jsc_heap_stats: await this.readJscHeapStats(),
|
|
178
|
+
jsc_memory_usage: await this.readJscMemoryUsage(),
|
|
179
|
+
memory_subsystems: this.readSubsystems(),
|
|
180
|
+
active_phases: activePhases,
|
|
181
|
+
primary_phase: primary ? { label: primary.label, duration_ms: primary.duration_ms, meta: primary.meta } : null,
|
|
182
|
+
});
|
|
183
|
+
} finally {
|
|
184
|
+
this.sampling = false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private async readJscHeapStats(): Promise<unknown> {
|
|
189
|
+
const jsc = await this.loadJscModule();
|
|
190
|
+
if (!jsc || typeof jsc.heapStats !== "function") return null;
|
|
191
|
+
try {
|
|
192
|
+
return jsc.heapStats();
|
|
193
|
+
} catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private async readJscMemoryUsage(): Promise<unknown> {
|
|
199
|
+
const jsc = await this.loadJscModule();
|
|
200
|
+
if (!jsc || typeof jsc.memoryUsage !== "function") return null;
|
|
201
|
+
try {
|
|
202
|
+
return jsc.memoryUsage();
|
|
203
|
+
} catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async loadJscModule(): Promise<BunJscModule | null> {
|
|
209
|
+
if (this.jscModule !== undefined) return this.jscModule;
|
|
210
|
+
if (typeof (globalThis as { Bun?: unknown }).Bun === "undefined") {
|
|
211
|
+
this.jscModule = null;
|
|
212
|
+
return this.jscModule;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
this.jscModule = (await import("bun:jsc")) as BunJscModule;
|
|
216
|
+
} catch {
|
|
217
|
+
this.jscModule = null;
|
|
218
|
+
}
|
|
219
|
+
return this.jscModule;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private writeLine(payload: Record<string, unknown>): void {
|
|
223
|
+
if (!this.stream) return;
|
|
224
|
+
this.stream.write(`${JSON.stringify(payload)}\n`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private readSubsystems(): unknown {
|
|
228
|
+
if (!this.subsystemProvider) return null;
|
|
229
|
+
try {
|
|
230
|
+
return this.subsystemProvider();
|
|
231
|
+
} catch {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Result } from "better-result";
|
|
2
|
+
import { applyLensChainResult } from "../lens/lens";
|
|
3
|
+
import type { SchemaRegistry } from "./registry";
|
|
4
|
+
import { SchemaRegistryStore } from "./registry";
|
|
5
|
+
|
|
6
|
+
export function schemaVersionForOffset(reg: SchemaRegistry, offset: bigint): number {
|
|
7
|
+
if (!reg.boundaries || reg.boundaries.length === 0) return 0;
|
|
8
|
+
const off = Number(offset);
|
|
9
|
+
let version = 0;
|
|
10
|
+
for (const boundary of reg.boundaries) {
|
|
11
|
+
if (boundary.offset <= off) version = boundary.version;
|
|
12
|
+
else break;
|
|
13
|
+
}
|
|
14
|
+
return version;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function decodeJsonPayloadResult(
|
|
18
|
+
registry: SchemaRegistryStore,
|
|
19
|
+
stream: string,
|
|
20
|
+
offset: bigint,
|
|
21
|
+
payload: Uint8Array
|
|
22
|
+
): Result<any, { status: 400 | 500; message: string }> {
|
|
23
|
+
const regRes = registry.getRegistryResult(stream);
|
|
24
|
+
if (Result.isError(regRes)) return Result.err({ status: 500, message: regRes.error.message });
|
|
25
|
+
const reg = regRes.value;
|
|
26
|
+
try {
|
|
27
|
+
const text = new TextDecoder().decode(payload);
|
|
28
|
+
let value: any = JSON.parse(text);
|
|
29
|
+
if (reg.currentVersion > 0) {
|
|
30
|
+
const version = schemaVersionForOffset(reg, offset);
|
|
31
|
+
if (version < reg.currentVersion) {
|
|
32
|
+
const chainRes = registry.getLensChainResult(reg, version, reg.currentVersion);
|
|
33
|
+
if (Result.isError(chainRes)) return Result.err({ status: 500, message: chainRes.error.message });
|
|
34
|
+
const transformedRes = applyLensChainResult(chainRes.value, value);
|
|
35
|
+
if (Result.isError(transformedRes)) return Result.err({ status: 400, message: transformedRes.error.message });
|
|
36
|
+
value = transformedRes.value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return Result.ok(value);
|
|
40
|
+
} catch (e: unknown) {
|
|
41
|
+
return Result.err({ status: 400, message: String((e as any)?.message ?? e) });
|
|
42
|
+
}
|
|
43
|
+
}
|