@milaboratories/pl-flight-recorder 0.2.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 (72) hide show
  1. package/README.md +119 -0
  2. package/dist/analyze.d.ts +133 -0
  3. package/dist/analyze.d.ts.map +1 -0
  4. package/dist/analyze.js +525 -0
  5. package/dist/analyze.js.map +1 -0
  6. package/dist/data_summary.d.ts +28 -0
  7. package/dist/data_summary.d.ts.map +1 -0
  8. package/dist/data_summary.js +73 -0
  9. package/dist/data_summary.js.map +1 -0
  10. package/dist/digest.d.ts +28 -0
  11. package/dist/digest.d.ts.map +1 -0
  12. package/dist/digest.js +55 -0
  13. package/dist/digest.js.map +1 -0
  14. package/dist/events.d.ts +89 -0
  15. package/dist/events.d.ts.map +1 -0
  16. package/dist/events.js +12 -0
  17. package/dist/events.js.map +1 -0
  18. package/dist/index.d.ts +13 -0
  19. package/dist/index.js +13 -0
  20. package/dist/instrument.d.ts +82 -0
  21. package/dist/instrument.d.ts.map +1 -0
  22. package/dist/instrument.js +313 -0
  23. package/dist/instrument.js.map +1 -0
  24. package/dist/recorder.d.ts +82 -0
  25. package/dist/recorder.d.ts.map +1 -0
  26. package/dist/recorder.js +293 -0
  27. package/dist/recorder.js.map +1 -0
  28. package/dist/redact.d.ts +53 -0
  29. package/dist/redact.d.ts.map +1 -0
  30. package/dist/redact.js +145 -0
  31. package/dist/redact.js.map +1 -0
  32. package/dist/report.d.ts +6 -0
  33. package/dist/report.d.ts.map +1 -0
  34. package/dist/report.js +377 -0
  35. package/dist/report.js.map +1 -0
  36. package/dist/rules.d.ts +73 -0
  37. package/dist/rules.d.ts.map +1 -0
  38. package/dist/rules.js +245 -0
  39. package/dist/rules.js.map +1 -0
  40. package/dist/sampler.d.ts +22 -0
  41. package/dist/sampler.d.ts.map +1 -0
  42. package/dist/sampler.js +33 -0
  43. package/dist/sampler.js.map +1 -0
  44. package/dist/sampler_thread.d.ts +1 -0
  45. package/dist/sampler_thread.js +41 -0
  46. package/dist/sampler_thread.js.map +1 -0
  47. package/dist/session.d.ts +40 -0
  48. package/dist/session.d.ts.map +1 -0
  49. package/dist/session.js +50 -0
  50. package/dist/session.js.map +1 -0
  51. package/dist/supervisor.d.ts +58 -0
  52. package/dist/supervisor.d.ts.map +1 -0
  53. package/dist/supervisor.js +108 -0
  54. package/dist/supervisor.js.map +1 -0
  55. package/package.json +43 -0
  56. package/src/analyze.test.ts +539 -0
  57. package/src/analyze.ts +795 -0
  58. package/src/data_summary.ts +110 -0
  59. package/src/digest.ts +49 -0
  60. package/src/events.ts +102 -0
  61. package/src/index.ts +104 -0
  62. package/src/instrument.ts +442 -0
  63. package/src/recorder.ts +397 -0
  64. package/src/redact.test.ts +155 -0
  65. package/src/redact.ts +213 -0
  66. package/src/report.ts +512 -0
  67. package/src/rules.test.ts +182 -0
  68. package/src/rules.ts +383 -0
  69. package/src/sampler.ts +40 -0
  70. package/src/sampler_thread.ts +45 -0
  71. package/src/session.ts +69 -0
  72. package/src/supervisor.ts +150 -0
@@ -0,0 +1,73 @@
1
+ //#region src/data_summary.ts
2
+ function summarizeData(data) {
3
+ if (data === null || data === void 0) return { kind: "absent" };
4
+ if (Array.isArray(data)) return {
5
+ kind: "inline",
6
+ entries: data.length,
7
+ approxBytes: approxInlineBytes(data)
8
+ };
9
+ if (typeof data !== "object") return { kind: typeof data };
10
+ const info = data;
11
+ switch (info.type) {
12
+ case "Json": return {
13
+ kind: "Json",
14
+ keyLength: numberOr(info.keyLength),
15
+ entries: countKeys(info.data)
16
+ };
17
+ case "JsonPartitioned":
18
+ case "BinaryPartitioned": return {
19
+ kind: info.type,
20
+ partitionKeyLength: numberOr(info.partitionKeyLength),
21
+ parts: countKeys(info.parts)
22
+ };
23
+ case "ParquetPartitioned": return summarizeParquet(info);
24
+ default: return { kind: info.type ?? opaqueKind(data) };
25
+ }
26
+ }
27
+ function summarizeParquet(info) {
28
+ const parts = Object.values(info.parts ?? {});
29
+ let rows = 0;
30
+ let bytes = 0;
31
+ let withStats = 0;
32
+ for (const part of parts) {
33
+ const stats = part?.stats;
34
+ if (!stats) continue;
35
+ withStats++;
36
+ if (typeof stats.numberOfRows === "number") rows += stats.numberOfRows;
37
+ if (stats.size) bytes += (stats.size.column ?? 0) + sum(stats.size.axes ?? []);
38
+ }
39
+ return {
40
+ kind: "ParquetPartitioned",
41
+ partitionKeyLength: numberOr(info.partitionKeyLength),
42
+ parts: parts.length,
43
+ partsWithStats: withStats,
44
+ rows: withStats > 0 ? rows : void 0,
45
+ bytes: withStats > 0 ? bytes : void 0
46
+ };
47
+ }
48
+ function approxInlineBytes(values) {
49
+ const sampleSize = Math.min(values.length, 64);
50
+ if (sampleSize === 0) return 0;
51
+ let bytes = 0;
52
+ for (let i = 0; i < sampleSize; i++) {
53
+ const value = values[Math.floor(i * values.length / sampleSize)];
54
+ bytes += JSON.stringify(value ?? null)?.length ?? 0;
55
+ }
56
+ return Math.round(bytes / sampleSize * values.length);
57
+ }
58
+ function countKeys(value) {
59
+ return value && typeof value === "object" ? Object.keys(value).length : void 0;
60
+ }
61
+ function numberOr(value) {
62
+ return typeof value === "number" ? value : void 0;
63
+ }
64
+ function opaqueKind(value) {
65
+ return value.constructor?.name ?? "opaque";
66
+ }
67
+ function sum(values) {
68
+ return values.reduce((acc, value) => acc + value, 0);
69
+ }
70
+ //#endregion
71
+ export { summarizeData };
72
+
73
+ //# sourceMappingURL=data_summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data_summary.js","names":[],"sources":["../src/data_summary.ts"],"sourcesContent":["/**\n * Counts and sizes for a column payload, never the payload.\n *\n * This is the one place that has to know the shape of `DataInfo`, because the\n * numbers that predict a join's cost — rows per partition and their byte sizes —\n * live at type-specific positions inside it. Everything else about a definition\n * is recorded structurally.\n *\n * Chunk statistics are optional: the producing workflow fills them in, so row\n * counts are reported when present and left unknown otherwise rather than\n * guessed.\n */\n\nexport type DataSummary = {\n kind: string;\n /** Entries for inline or JSON payloads. */\n entries?: number;\n approxBytes?: number;\n keyLength?: number;\n partitionKeyLength?: number;\n parts?: number;\n partsWithStats?: number;\n rows?: number;\n bytes?: number;\n};\n\nexport function summarizeData(data: unknown): DataSummary {\n if (data === null || data === undefined) return { kind: \"absent\" };\n if (Array.isArray(data)) {\n // Inline values, built inside the model sandbox.\n return { kind: \"inline\", entries: data.length, approxBytes: approxInlineBytes(data) };\n }\n if (typeof data !== \"object\") return { kind: typeof data };\n\n const info = data as { type?: string; [key: string]: unknown };\n switch (info.type) {\n case \"Json\":\n return {\n kind: \"Json\",\n keyLength: numberOr(info.keyLength),\n entries: countKeys(info.data),\n };\n case \"JsonPartitioned\":\n case \"BinaryPartitioned\":\n return {\n kind: info.type,\n partitionKeyLength: numberOr(info.partitionKeyLength),\n parts: countKeys(info.parts),\n };\n case \"ParquetPartitioned\":\n return summarizeParquet(info);\n default:\n return { kind: info.type ?? opaqueKind(data) };\n }\n}\n\n// Internals\n\nfunction summarizeParquet(info: { [key: string]: unknown }): DataSummary {\n const parts = Object.values((info.parts ?? {}) as Record<string, unknown>);\n let rows = 0;\n let bytes = 0;\n let withStats = 0;\n for (const part of parts) {\n const stats = (\n part as { stats?: { numberOfRows?: number; size?: { axes?: number[]; column?: number } } }\n )?.stats;\n if (!stats) continue;\n withStats++;\n if (typeof stats.numberOfRows === \"number\") rows += stats.numberOfRows;\n if (stats.size) bytes += (stats.size.column ?? 0) + sum(stats.size.axes ?? []);\n }\n return {\n kind: \"ParquetPartitioned\",\n partitionKeyLength: numberOr(info.partitionKeyLength),\n parts: parts.length,\n partsWithStats: withStats,\n rows: withStats > 0 ? rows : undefined,\n bytes: withStats > 0 ? bytes : undefined,\n };\n}\n\n// Sampled rather than measured: walking millions of entries to size them is\n// itself a memory risk in the situation this code exists to diagnose.\nfunction approxInlineBytes(values: unknown[]): number {\n const sampleSize = Math.min(values.length, 64);\n if (sampleSize === 0) return 0;\n let bytes = 0;\n for (let i = 0; i < sampleSize; i++) {\n const value = values[Math.floor((i * values.length) / sampleSize)];\n bytes += JSON.stringify(value ?? null)?.length ?? 0;\n }\n return Math.round((bytes / sampleSize) * values.length);\n}\n\nfunction countKeys(value: unknown): number | undefined {\n return value && typeof value === \"object\" ? Object.keys(value).length : undefined;\n}\n\nfunction numberOr(value: unknown): number | undefined {\n return typeof value === \"number\" ? value : undefined;\n}\n\nfunction opaqueKind(value: object): string {\n return (value as { constructor?: { name?: string } }).constructor?.name ?? \"opaque\";\n}\n\nfunction sum(values: number[]): number {\n return values.reduce((acc, value) => acc + value, 0);\n}\n"],"mappings":";AA0BA,SAAgB,cAAc,MAA4B;CACxD,IAAI,SAAS,QAAQ,SAAS,KAAA,GAAW,OAAO,EAAE,MAAM,SAAS;CACjE,IAAI,MAAM,QAAQ,IAAI,GAEpB,OAAO;EAAE,MAAM;EAAU,SAAS,KAAK;EAAQ,aAAa,kBAAkB,IAAI;CAAE;CAEtF,IAAI,OAAO,SAAS,UAAU,OAAO,EAAE,MAAM,OAAO,KAAK;CAEzD,MAAM,OAAO;CACb,QAAQ,KAAK,MAAb;EACE,KAAK,QACH,OAAO;GACL,MAAM;GACN,WAAW,SAAS,KAAK,SAAS;GAClC,SAAS,UAAU,KAAK,IAAI;EAC9B;EACF,KAAK;EACL,KAAK,qBACH,OAAO;GACL,MAAM,KAAK;GACX,oBAAoB,SAAS,KAAK,kBAAkB;GACpD,OAAO,UAAU,KAAK,KAAK;EAC7B;EACF,KAAK,sBACH,OAAO,iBAAiB,IAAI;EAC9B,SACE,OAAO,EAAE,MAAM,KAAK,QAAQ,WAAW,IAAI,EAAE;CACjD;AACF;AAIA,SAAS,iBAAiB,MAA+C;CACvE,MAAM,QAAQ,OAAO,OAAQ,KAAK,SAAS,CAAC,CAA6B;CACzE,IAAI,OAAO;CACX,IAAI,QAAQ;CACZ,IAAI,YAAY;CAChB,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QACJ,MACC;EACH,IAAI,CAAC,OAAO;EACZ;EACA,IAAI,OAAO,MAAM,iBAAiB,UAAU,QAAQ,MAAM;EAC1D,IAAI,MAAM,MAAM,UAAU,MAAM,KAAK,UAAU,KAAK,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC;CAC/E;CACA,OAAO;EACL,MAAM;EACN,oBAAoB,SAAS,KAAK,kBAAkB;EACpD,OAAO,MAAM;EACb,gBAAgB;EAChB,MAAM,YAAY,IAAI,OAAO,KAAA;EAC7B,OAAO,YAAY,IAAI,QAAQ,KAAA;CACjC;AACF;AAIA,SAAS,kBAAkB,QAA2B;CACpD,MAAM,aAAa,KAAK,IAAI,OAAO,QAAQ,EAAE;CAC7C,IAAI,eAAe,GAAG,OAAO;CAC7B,IAAI,QAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,KAAK;EACnC,MAAM,QAAQ,OAAO,KAAK,MAAO,IAAI,OAAO,SAAU,UAAU;EAChE,SAAS,KAAK,UAAU,SAAS,IAAI,CAAC,EAAE,UAAU;CACpD;CACA,OAAO,KAAK,MAAO,QAAQ,aAAc,OAAO,MAAM;AACxD;AAEA,SAAS,UAAU,OAAoC;CACrD,OAAO,SAAS,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,KAAA;AAC1E;AAEA,SAAS,SAAS,OAAoC;CACpD,OAAO,OAAO,UAAU,WAAW,QAAQ,KAAA;AAC7C;AAEA,SAAS,WAAW,OAAuB;CACzC,OAAQ,MAA8C,aAAa,QAAQ;AAC7E;AAEA,SAAS,IAAI,QAA0B;CACrC,OAAO,OAAO,QAAQ,KAAK,UAAU,MAAM,OAAO,CAAC;AACrD"}
@@ -0,0 +1,28 @@
1
+ import { RedactionStats } from "./redact.js";
2
+ //#region src/digest.d.ts
3
+ /**
4
+ * What a definition record carries.
5
+ *
6
+ * The definition is recorded structurally (see `redact`) rather than through a
7
+ * hand-written digest per definition type. Structural rules are not run here:
8
+ * they belong to the analyzer, so nothing is computed on the hot path and the
9
+ * rules can be revised against logs that already exist.
10
+ */
11
+ export declare const REDACTION: {
12
+ readonly kept: readonly ["definition shape", "column and axis names", "value types", "axis domains", "filter operators", "row, byte and partition counts"];
13
+ readonly hashed: readonly ["filter reference values", "annotation values", "column ids", "every other string"];
14
+ readonly dropped: readonly ["cell values", "inline column payloads", "partition keys"];
15
+ };
16
+ export type DefKind = "PTableDef" | "PTableDefV2" | "PFrameDef";
17
+ export type DefDigest = {
18
+ kind: DefKind;
19
+ /** Redacted definition, same shape as the original. */
20
+ def: unknown;
21
+ redaction: RedactionStats & {
22
+ bytes: number;
23
+ };
24
+ };
25
+ /** Records one definition: redacted, measured, and tagged with its API shape. */
26
+ export declare function digestDef(kind: DefKind, def: unknown): DefDigest;
27
+ //#endregion
28
+ //# sourceMappingURL=digest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.d.ts","names":[],"sources":["../src/digest.ts"],"mappings":";;;;;;;;;;qBAWa;WACX;WAQA;WACA;;YAGU;YAEA;EACV,MAAM;;EAEN;EACA,WAAW;IAAmB;;;;wBAIhB,UAAU,MAAM,SAAS,eAAe"}
package/dist/digest.js ADDED
@@ -0,0 +1,55 @@
1
+ import { redact } from "./redact.js";
2
+ //#region src/digest.ts
3
+ /**
4
+ * What a definition record carries.
5
+ *
6
+ * The definition is recorded structurally (see `redact`) rather than through a
7
+ * hand-written digest per definition type. Structural rules are not run here:
8
+ * they belong to the analyzer, so nothing is computed on the hot path and the
9
+ * rules can be revised against logs that already exist.
10
+ */
11
+ const REDACTION = {
12
+ kept: [
13
+ "definition shape",
14
+ "column and axis names",
15
+ "value types",
16
+ "axis domains",
17
+ "filter operators",
18
+ "row, byte and partition counts"
19
+ ],
20
+ hashed: [
21
+ "filter reference values",
22
+ "annotation values",
23
+ "column ids",
24
+ "every other string"
25
+ ],
26
+ dropped: [
27
+ "cell values",
28
+ "inline column payloads",
29
+ "partition keys"
30
+ ]
31
+ };
32
+ /** Records one definition: redacted, measured, and tagged with its API shape. */
33
+ function digestDef(kind, def) {
34
+ const { value, stats } = redact(def);
35
+ const json = safeLength(value);
36
+ return {
37
+ kind,
38
+ def: value,
39
+ redaction: {
40
+ ...stats,
41
+ bytes: json
42
+ }
43
+ };
44
+ }
45
+ function safeLength(value) {
46
+ try {
47
+ return JSON.stringify(value)?.length ?? 0;
48
+ } catch {
49
+ return 0;
50
+ }
51
+ }
52
+ //#endregion
53
+ export { REDACTION, digestDef };
54
+
55
+ //# sourceMappingURL=digest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.js","names":[],"sources":["../src/digest.ts"],"sourcesContent":["import { redact, type RedactionStats } from \"./redact\";\n\n/**\n * What a definition record carries.\n *\n * The definition is recorded structurally (see `redact`) rather than through a\n * hand-written digest per definition type. Structural rules are not run here:\n * they belong to the analyzer, so nothing is computed on the hot path and the\n * rules can be revised against logs that already exist.\n */\n\nexport const REDACTION = {\n kept: [\n \"definition shape\",\n \"column and axis names\",\n \"value types\",\n \"axis domains\",\n \"filter operators\",\n \"row, byte and partition counts\",\n ],\n hashed: [\"filter reference values\", \"annotation values\", \"column ids\", \"every other string\"],\n dropped: [\"cell values\", \"inline column payloads\", \"partition keys\"],\n} as const;\n\nexport type DefKind = \"PTableDef\" | \"PTableDefV2\" | \"PFrameDef\";\n\nexport type DefDigest = {\n kind: DefKind;\n /** Redacted definition, same shape as the original. */\n def: unknown;\n redaction: RedactionStats & { bytes: number };\n};\n\n/** Records one definition: redacted, measured, and tagged with its API shape. */\nexport function digestDef(kind: DefKind, def: unknown): DefDigest {\n const { value, stats } = redact(def);\n const json = safeLength(value);\n return { kind, def: value, redaction: { ...stats, bytes: json } };\n}\n\n// Internals\n\nfunction safeLength(value: unknown): number {\n try {\n return JSON.stringify(value)?.length ?? 0;\n } catch {\n return 0;\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,MAAa,YAAY;CACvB,MAAM;EACJ;EACA;EACA;EACA;EACA;EACA;CACF;CACA,QAAQ;EAAC;EAA2B;EAAqB;EAAc;CAAoB;CAC3F,SAAS;EAAC;EAAe;EAA0B;CAAgB;AACrE;;AAYA,SAAgB,UAAU,MAAe,KAAyB;CAChE,MAAM,EAAE,OAAO,UAAU,OAAO,GAAG;CACnC,MAAM,OAAO,WAAW,KAAK;CAC7B,OAAO;EAAE;EAAM,KAAK;EAAO,WAAW;GAAE,GAAG;GAAO,OAAO;EAAK;CAAE;AAClE;AAIA,SAAS,WAAW,OAAwB;CAC1C,IAAI;EACF,OAAO,KAAK,UAAU,KAAK,CAAC,EAAE,UAAU;CAC1C,QAAQ;EACN,OAAO;CACT;AACF"}
@@ -0,0 +1,89 @@
1
+ //#region src/events.d.ts
2
+ /**
3
+ * Record types written to a flight log.
4
+ *
5
+ * The log is append-only NDJSON, one record per line, and is read back by
6
+ * tooling that may be older or newer than the writer, so every field beyond
7
+ * {@link FlightRecordBase} is optional and unknown record types are skipped
8
+ * rather than rejected.
9
+ */
10
+ /** Memory reading taken by the thread that wrote the record. */
11
+ export type MemorySnapshot = {
12
+ /** Resident set size of the whole process. */
13
+ rss: number;
14
+ /** Heap in use by the writing thread's isolate. */
15
+ heapUsed: number;
16
+ heapTotal: number;
17
+ external: number;
18
+ arrayBuffers: number;
19
+ /** V8 heap ceiling for the writing thread's isolate. */
20
+ heapLimit: number;
21
+ };
22
+ export type FlightRecordBase = {
23
+ /** Monotonically increasing within one session; used to pair begin with end. */
24
+ seq: number;
25
+ /** Milliseconds since process start, for durations. */
26
+ t: number;
27
+ /** Wall clock, for correlating with the sampler series and crash markers. */
28
+ wall: number;
29
+ type: string;
30
+ };
31
+ export type FlightRecord = FlightRecordBase & {
32
+ mem?: MemorySnapshot;
33
+ /** Sequence number of the matching begin record, on end and error records. */
34
+ begin?: number;
35
+ [key: string]: unknown;
36
+ };
37
+ /** Session header, always the first record. */
38
+ export type SessionEnvironment = {
39
+ node: string;
40
+ platform: string;
41
+ cpus: number;
42
+ totalMemory: number;
43
+ heapLimit: number;
44
+ execArgv: string[];
45
+ maxOldSpaceSize?: number;
46
+ };
47
+ /** Written by the sampler thread to its own sibling file. */
48
+ export type SamplerRecord = FlightRecordBase & {
49
+ type: "mem-sampler";
50
+ rss: number;
51
+ peakRss: number;
52
+ freeMemory: number;
53
+ totalMemory: number;
54
+ };
55
+ /** Written by the parent when a supervised thread or process dies. */
56
+ export type CrashMarker = {
57
+ type: "external-crash";
58
+ wall: number;
59
+ /**
60
+ * Session the marker belongs to. Present only when the parent assigned the id
61
+ * to the worker and therefore knows it; never inferred, because a wrong id
62
+ * here would both misattribute the death and stop the right session from
63
+ * claiming it.
64
+ */
65
+ sessionId?: string;
66
+ /**
67
+ * Advisory only: the newest open flight log at the moment of death. A
68
+ * concurrent live session can make this wrong, so it is never matched against
69
+ * — it exists to help a human read a directory by hand.
70
+ */
71
+ guessedSessionId?: string;
72
+ /** Written by an older recorder that put a guess in `sessionId`. */
73
+ sessionIdSource?: "assigned" | "guessed";
74
+ reason: CrashReason;
75
+ errorCode?: string;
76
+ errorName?: string;
77
+ message?: string;
78
+ exitCode?: number;
79
+ signal?: string;
80
+ stderrTail?: string;
81
+ };
82
+ export type CrashReason = "js-heap-out-of-memory" | "killed-by-os" | "abort-or-fatal-allocation-failure" | "worker-exit" | "nonzero-exit" | "unknown";
83
+ export declare const SESSION_RECORD = "session";
84
+ export declare const SESSION_END_RECORD = "session-end";
85
+ export declare const FLIGHT_FILE_PREFIX = "flight";
86
+ export declare const SAMPLER_FILE_PREFIX = "mem";
87
+ export declare const CRASH_FILE_PREFIX = "crash";
88
+ //#endregion
89
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","names":[],"sources":["../src/events.ts"],"mappings":";;;;;;;;;;YAUY;;EAEV;;EAEA;EACA;EACA;EACA;;EAEA;;YAGU;;EAEV;;EAEA;;EAEA;EACA;;YAGU,eAAe;EACzB,MAAM;;EAEN;GACC;;;YAIS;EACV;EACA;EACA;EACA;EACA;EACA;EACA;;;YAIU,gBAAgB;EAC1B;EACA;EACA;EACA;EACA;;;YAIU;EACV;EACA;;;;;;;EAOA;;;;;;EAMA;;EAEA;EACA,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;;YAGU;qBAQC;qBAGA;qBACA;qBACA;qBACA"}
package/dist/events.js ADDED
@@ -0,0 +1,12 @@
1
+ //#region src/events.ts
2
+ const SESSION_RECORD = "session";
3
+ /** Earliest memory reading of a session, rewritten into every rotated segment. */
4
+ const MEM_BASELINE_RECORD = "mem-baseline";
5
+ const SESSION_END_RECORD = "session-end";
6
+ const FLIGHT_FILE_PREFIX = "flight";
7
+ const SAMPLER_FILE_PREFIX = "mem";
8
+ const CRASH_FILE_PREFIX = "crash";
9
+ //#endregion
10
+ export { CRASH_FILE_PREFIX, FLIGHT_FILE_PREFIX, MEM_BASELINE_RECORD, SAMPLER_FILE_PREFIX, SESSION_END_RECORD, SESSION_RECORD };
11
+
12
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","names":[],"sources":["../src/events.ts"],"sourcesContent":["/**\n * Record types written to a flight log.\n *\n * The log is append-only NDJSON, one record per line, and is read back by\n * tooling that may be older or newer than the writer, so every field beyond\n * {@link FlightRecordBase} is optional and unknown record types are skipped\n * rather than rejected.\n */\n\n/** Memory reading taken by the thread that wrote the record. */\nexport type MemorySnapshot = {\n /** Resident set size of the whole process. */\n rss: number;\n /** Heap in use by the writing thread's isolate. */\n heapUsed: number;\n heapTotal: number;\n external: number;\n arrayBuffers: number;\n /** V8 heap ceiling for the writing thread's isolate. */\n heapLimit: number;\n};\n\nexport type FlightRecordBase = {\n /** Monotonically increasing within one session; used to pair begin with end. */\n seq: number;\n /** Milliseconds since process start, for durations. */\n t: number;\n /** Wall clock, for correlating with the sampler series and crash markers. */\n wall: number;\n type: string;\n};\n\nexport type FlightRecord = FlightRecordBase & {\n mem?: MemorySnapshot;\n /** Sequence number of the matching begin record, on end and error records. */\n begin?: number;\n [key: string]: unknown;\n};\n\n/** Session header, always the first record. */\nexport type SessionEnvironment = {\n node: string;\n platform: string;\n cpus: number;\n totalMemory: number;\n heapLimit: number;\n execArgv: string[];\n maxOldSpaceSize?: number;\n};\n\n/** Written by the sampler thread to its own sibling file. */\nexport type SamplerRecord = FlightRecordBase & {\n type: \"mem-sampler\";\n rss: number;\n peakRss: number;\n freeMemory: number;\n totalMemory: number;\n};\n\n/** Written by the parent when a supervised thread or process dies. */\nexport type CrashMarker = {\n type: \"external-crash\";\n wall: number;\n /**\n * Session the marker belongs to. Present only when the parent assigned the id\n * to the worker and therefore knows it; never inferred, because a wrong id\n * here would both misattribute the death and stop the right session from\n * claiming it.\n */\n sessionId?: string;\n /**\n * Advisory only: the newest open flight log at the moment of death. A\n * concurrent live session can make this wrong, so it is never matched against\n * — it exists to help a human read a directory by hand.\n */\n guessedSessionId?: string;\n /** Written by an older recorder that put a guess in `sessionId`. */\n sessionIdSource?: \"assigned\" | \"guessed\";\n reason: CrashReason;\n errorCode?: string;\n errorName?: string;\n message?: string;\n exitCode?: number;\n signal?: string;\n stderrTail?: string;\n};\n\nexport type CrashReason =\n | \"js-heap-out-of-memory\"\n | \"killed-by-os\"\n | \"abort-or-fatal-allocation-failure\"\n | \"worker-exit\"\n | \"nonzero-exit\"\n | \"unknown\";\n\nexport const SESSION_RECORD = \"session\";\n/** Earliest memory reading of a session, rewritten into every rotated segment. */\nexport const MEM_BASELINE_RECORD = \"mem-baseline\";\nexport const SESSION_END_RECORD = \"session-end\";\nexport const FLIGHT_FILE_PREFIX = \"flight\";\nexport const SAMPLER_FILE_PREFIX = \"mem\";\nexport const CRASH_FILE_PREFIX = \"crash\";\n"],"mappings":";AA+FA,MAAa,iBAAiB;;AAE9B,MAAa,sBAAsB;AACnC,MAAa,qBAAqB;AAClC,MAAa,qBAAqB;AAClC,MAAa,sBAAsB;AACnC,MAAa,oBAAoB"}
@@ -0,0 +1,13 @@
1
+ import { CRASH_FILE_PREFIX, CrashMarker, CrashReason, FLIGHT_FILE_PREFIX, FlightRecord, MemorySnapshot, SAMPLER_FILE_PREFIX, SESSION_END_RECORD, SESSION_RECORD, SamplerRecord, SessionEnvironment } from "./events.js";
2
+ import { ParsedSession, Recorder, RecorderOptions, SessionFileInfo, listSessions, newSessionId, openRecorder, readSession, sessionIdFromFile, startSelfSampler } from "./recorder.js";
3
+ import { MemorySampler, MemorySamplerOptions, startMemorySampler } from "./sampler.js";
4
+ import { HandleOrigin, HandleRegistry, RenderInfo, createHandleRegistry, recordModelRender, recordModelRenderSync, wrapDataDriver, wrapModelDriver } from "./instrument.js";
5
+ import { FLIGHT_DIR_ENV, FLIGHT_SESSION_ENV, FlightSession, FlightSessionOptions, openFlightSession } from "./session.js";
6
+ import { CrashMarkerInput, SuperviseOptions, SupervisedWorker, readCrashMarkers, superviseWorker, writeCrashMarker } from "./supervisor.js";
7
+ import { DataSummary, summarizeData } from "./data_summary.js";
8
+ import { COUNTED_KEYS, HashedString, RedactOptions, RedactionStats, SCHEMA_KEYS, SCHEMA_SUBTREE_KEYS, SUMMARISED_KEYS, hashString, isHashedString, redact } from "./redact.js";
9
+ import { DefDigest, DefKind, REDACTION, digestDef } from "./digest.js";
10
+ import { AxisDescriptor, FindingSeverity, JoinShape, StructuralFinding, axesUnder, axisKey, axisNameKey, inputRowsMax, isJoinNode, joinChildren, joinShapes, structuralFindings } from "./rules.js";
11
+ import { Finding, MemoryAnalysis, OperationSummary, RenderSummary, SessionAnalysis, THRESHOLDS, Verdict, analyzeLatest, analyzeSession, formatBytes, formatCount } from "./analyze.js";
12
+ import { renderReport } from "./report.js";
13
+ export { type AxisDescriptor, COUNTED_KEYS, CRASH_FILE_PREFIX, type CrashMarker, type CrashMarkerInput, type CrashReason, type DataSummary, type DefDigest, type DefKind, FLIGHT_DIR_ENV, FLIGHT_FILE_PREFIX, FLIGHT_SESSION_ENV, type Finding, type FindingSeverity, type FlightRecord, type FlightSession, type FlightSessionOptions, type HandleOrigin, type HandleRegistry, type HashedString, type JoinShape, type MemoryAnalysis, type MemorySampler, type MemorySamplerOptions, type MemorySnapshot, type OperationSummary, type ParsedSession, REDACTION, type Recorder, type RecorderOptions, type RedactOptions, type RedactionStats, type RenderInfo, type RenderSummary, SAMPLER_FILE_PREFIX, SCHEMA_KEYS, SCHEMA_SUBTREE_KEYS, SESSION_END_RECORD, SESSION_RECORD, SUMMARISED_KEYS, type SamplerRecord, type SessionAnalysis, type SessionEnvironment, type SessionFileInfo, type StructuralFinding, type SuperviseOptions, type SupervisedWorker, THRESHOLDS, type Verdict, analyzeLatest, analyzeSession, axesUnder, axisKey, axisNameKey, createHandleRegistry, digestDef, formatBytes, formatCount, hashString, inputRowsMax, isHashedString, isJoinNode, joinChildren, joinShapes, listSessions, newSessionId, openFlightSession, openRecorder, readCrashMarkers, readSession, recordModelRender, recordModelRenderSync, redact, renderReport, sessionIdFromFile, startMemorySampler, startSelfSampler, structuralFindings, summarizeData, superviseWorker, wrapDataDriver, wrapModelDriver, writeCrashMarker };
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ import { CRASH_FILE_PREFIX, FLIGHT_FILE_PREFIX, SAMPLER_FILE_PREFIX, SESSION_END_RECORD, SESSION_RECORD } from "./events.js";
2
+ import { listSessions, newSessionId, openRecorder, readSession, sessionIdFromFile, startSelfSampler } from "./recorder.js";
3
+ import { startMemorySampler } from "./sampler.js";
4
+ import { summarizeData } from "./data_summary.js";
5
+ import { COUNTED_KEYS, SCHEMA_KEYS, SCHEMA_SUBTREE_KEYS, SUMMARISED_KEYS, hashString, isHashedString, redact } from "./redact.js";
6
+ import { REDACTION, digestDef } from "./digest.js";
7
+ import { createHandleRegistry, recordModelRender, recordModelRenderSync, wrapDataDriver, wrapModelDriver } from "./instrument.js";
8
+ import { FLIGHT_DIR_ENV, FLIGHT_SESSION_ENV, openFlightSession } from "./session.js";
9
+ import { readCrashMarkers, superviseWorker, writeCrashMarker } from "./supervisor.js";
10
+ import { axesUnder, axisKey, axisNameKey, inputRowsMax, isJoinNode, joinChildren, joinShapes, structuralFindings } from "./rules.js";
11
+ import { THRESHOLDS, analyzeLatest, analyzeSession, formatBytes, formatCount } from "./analyze.js";
12
+ import { renderReport } from "./report.js";
13
+ export { COUNTED_KEYS, CRASH_FILE_PREFIX, FLIGHT_DIR_ENV, FLIGHT_FILE_PREFIX, FLIGHT_SESSION_ENV, REDACTION, SAMPLER_FILE_PREFIX, SCHEMA_KEYS, SCHEMA_SUBTREE_KEYS, SESSION_END_RECORD, SESSION_RECORD, SUMMARISED_KEYS, THRESHOLDS, analyzeLatest, analyzeSession, axesUnder, axisKey, axisNameKey, createHandleRegistry, digestDef, formatBytes, formatCount, hashString, inputRowsMax, isHashedString, isJoinNode, joinChildren, joinShapes, listSessions, newSessionId, openFlightSession, openRecorder, readCrashMarkers, readSession, recordModelRender, recordModelRenderSync, redact, renderReport, sessionIdFromFile, startMemorySampler, startSelfSampler, structuralFindings, summarizeData, superviseWorker, wrapDataDriver, wrapModelDriver, writeCrashMarker };
@@ -0,0 +1,82 @@
1
+ import { Recorder } from "./recorder.js";
2
+ //#region src/instrument.d.ts
3
+ /**
4
+ * Wrappers for the seams the model layer passes through.
5
+ *
6
+ * Every operation writes a begin record and an end record. That pairing is what
7
+ * makes a crash legible: when the process dies mid-operation the end record is
8
+ * missing, so the log names the exact call that was running when memory ran out
9
+ * — the question a post-crash report has to answer.
10
+ *
11
+ * The wrappers are structural rather than tied to one driver interface, because
12
+ * the same three creation methods appear twice with different return types: the
13
+ * model-facing driver hands back a bare handle, the internal one hands back a
14
+ * pool entry.
15
+ */
16
+ export type HandleOrigin = {
17
+ /** Sequence number of the record holding the definition. */
18
+ seq: number;
19
+ op: string;
20
+ observed?: {
21
+ rows?: number;
22
+ columns?: number;
23
+ };
24
+ };
25
+ export type HandleRegistry = {
26
+ put(handle: string, origin: HandleOrigin): void;
27
+ get(handle: string): HandleOrigin | undefined;
28
+ observe(handle: string, observed: {
29
+ rows?: number;
30
+ columns?: number;
31
+ }): void;
32
+ };
33
+ export type ModelDriverLike<H> = {
34
+ createPFrame(def: never): H;
35
+ createPTable(def: never): H;
36
+ createPTableV2(def: never): H;
37
+ };
38
+ export type RenderInfo = {
39
+ blockId?: string;
40
+ block?: string;
41
+ blockVersion?: string;
42
+ key?: string;
43
+ argsHash?: string;
44
+ /** Which lambda of the block's model is being rendered. */
45
+ lambda?: string;
46
+ /** Nth resumption of a deferred render, counted from one. */
47
+ recalculation?: number;
48
+ /** Read after the render, so sandbox counters cover the whole call. */
49
+ getStats?: () => unknown;
50
+ /** Any further context the call site wants on the record. */
51
+ [key: string]: unknown;
52
+ };
53
+ /** Maps driver handles back to the join that produced them. */
54
+ export declare function createHandleRegistry(limit?: number): HandleRegistry;
55
+ /**
56
+ * Wraps the driver that block models call to build frames and tables.
57
+ *
58
+ * Records the redacted join tree and its structural findings, then remembers
59
+ * which handle came from which join so later data calls can be attributed back
60
+ * to the definition that caused them.
61
+ */
62
+ export declare function wrapModelDriver<D extends ModelDriverLike<unknown>>(driver: D, recorder: Recorder, registry: HandleRegistry, handleOf?: (result: unknown) => string): D;
63
+ /**
64
+ * Wraps the asynchronous data-access driver.
65
+ *
66
+ * Adds the observed table shape, the size of what crossed back into JavaScript,
67
+ * and the amplification between input and output rows — the empirical
68
+ * counterpart to the structural findings taken from the join tree.
69
+ */
70
+ export declare function wrapDataDriver<D extends object>(driver: D, recorder: Recorder, registry: HandleRegistry): D;
71
+ /**
72
+ * Records one block model render.
73
+ *
74
+ * `getStats` exposes the middle layer's own sandbox accounting, whose
75
+ * serialisation byte counts show how much data the model moved across the
76
+ * QuickJS boundary — the model-layer memory cost that no driver call reports.
77
+ */
78
+ export declare function recordModelRender<T>(recorder: Recorder | undefined, info: RenderInfo, fn: () => Promise<T>): Promise<T>;
79
+ /** Synchronous variant, for a render that is not driven by a promise. */
80
+ export declare function recordModelRenderSync<T>(recorder: Recorder | undefined, info: RenderInfo, fn: () => T): T;
81
+ //#endregion
82
+ //# sourceMappingURL=instrument.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrument.d.ts","names":[],"sources":["../src/instrument.ts"],"mappings":";;;;;;;;;;;;;;;YAmBY;;EAEV;EACA;EACA;IAAa;IAAe;;;YAGlB;EACV,IAAI,gBAAgB,QAAQ;EAC5B,IAAI,iBAAiB;EACrB,QAAQ,gBAAgB;IAAY;IAAe;;;YAGzC,gBAAgB;EAC1B,aAAa,aAAa;EAC1B,aAAa,aAAa;EAC1B,eAAe,aAAa;;YAGlB;EACV;EACA;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;;GAEC;;;wBAIa,qBAAqB,iBAAc;;;;;;;;wBA2BnC,gBAAgB,UAAU,0BACxC,QAAQ,GACR,UAAU,UACV,UAAU,gBACV,YAAW,6BACV;;;;;;;;wBA8Ba,eAAe,kBAC7B,QAAQ,GACR,UAAU,UACV,UAAU,iBACT;;;;;;;;wBAgImB,kBAAkB,GACtC,UAAU,sBACV,MAAM,YACN,UAAU,QAAQ,KACjB,QAAQ;;wBA4BK,sBAAsB,GACpC,UAAU,sBACV,MAAM,YACN,UAAU,IACT"}