@bli-cockpit/telemetry-core 0.1.31 → 0.1.33
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/dist/collector-heartbeat.d.ts +43 -0
- package/dist/collector-heartbeat.js +31 -0
- package/dist/evidence-completeness.d.ts +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ingest-dto.d.ts +2 -2
- package/dist/memory-hook-performance.d.ts +102 -0
- package/dist/memory-hook-performance.js +208 -0
- package/dist/memory-install-receipt.d.ts +41 -0
- package/dist/memory-install-receipt.js +3 -0
- package/package.json +1 -1
|
@@ -58,6 +58,8 @@ export declare const CollectorHeartbeatSchema: z.ZodObject<{
|
|
|
58
58
|
sessions_outside_root: z.ZodOptional<z.ZodNumber>;
|
|
59
59
|
sessions_new_this_tick: z.ZodOptional<z.ZodNumber>;
|
|
60
60
|
sessions_pending_upload: z.ZodOptional<z.ZodNumber>;
|
|
61
|
+
staging_uncommitted_bytes: z.ZodOptional<z.ZodNumber>;
|
|
62
|
+
staging_uncommitted_reason: z.ZodOptional<z.ZodString>;
|
|
61
63
|
memory_install: z.ZodOptional<z.ZodObject<{
|
|
62
64
|
schema_version: z.ZodLiteral<"memory-install-receipt.v1">;
|
|
63
65
|
checked_at: z.ZodString;
|
|
@@ -104,6 +106,47 @@ export declare const CollectorHeartbeatSchema: z.ZodObject<{
|
|
|
104
106
|
hook_printed_24h: z.ZodOptional<z.ZodNumber>;
|
|
105
107
|
hook_failed_24h: z.ZodOptional<z.ZodNumber>;
|
|
106
108
|
hook_stats_reason: z.ZodOptional<z.ZodString>;
|
|
109
|
+
hook_performance: z.ZodOptional<z.ZodObject<{
|
|
110
|
+
schema_version: z.ZodLiteral<"memory-hook-performance.v1">;
|
|
111
|
+
window_start: z.ZodString;
|
|
112
|
+
window_end: z.ZodString;
|
|
113
|
+
sampling_since: z.ZodNullable<z.ZodString>;
|
|
114
|
+
samples: z.ZodNumber;
|
|
115
|
+
outcomes: z.ZodObject<{
|
|
116
|
+
printed: z.ZodNumber;
|
|
117
|
+
empty: z.ZodNumber;
|
|
118
|
+
timeouts: z.ZodNumber;
|
|
119
|
+
failed: z.ZodNumber;
|
|
120
|
+
skipped: z.ZodNumber;
|
|
121
|
+
}, z.core.$strict>;
|
|
122
|
+
duration_by_cache: z.ZodObject<{
|
|
123
|
+
hit: z.ZodArray<z.ZodNumber>;
|
|
124
|
+
miss: z.ZodArray<z.ZodNumber>;
|
|
125
|
+
shared: z.ZodArray<z.ZodNumber>;
|
|
126
|
+
unknown: z.ZodArray<z.ZodNumber>;
|
|
127
|
+
}, z.core.$strict>;
|
|
128
|
+
producer_versions: z.ZodArray<z.ZodObject<{
|
|
129
|
+
version: z.ZodString;
|
|
130
|
+
samples: z.ZodNumber;
|
|
131
|
+
}, z.core.$strict>>;
|
|
132
|
+
invalid_samples: z.ZodNumber;
|
|
133
|
+
incomplete_samples: z.ZodNumber;
|
|
134
|
+
unreadable_samples: z.ZodNumber;
|
|
135
|
+
capped: z.ZodBoolean;
|
|
136
|
+
reasons: z.ZodArray<z.ZodEnum<{
|
|
137
|
+
no_samples: "no_samples";
|
|
138
|
+
partial_window: "partial_window";
|
|
139
|
+
lease_absent: "lease_absent";
|
|
140
|
+
lease_expired: "lease_expired";
|
|
141
|
+
lease_invalid: "lease_invalid";
|
|
142
|
+
read_failed: "read_failed";
|
|
143
|
+
read_limit: "read_limit";
|
|
144
|
+
invalid_samples: "invalid_samples";
|
|
145
|
+
incomplete_samples: "incomplete_samples";
|
|
146
|
+
unreadable_samples: "unreadable_samples";
|
|
147
|
+
cleanup_failed: "cleanup_failed";
|
|
148
|
+
}>>;
|
|
149
|
+
}, z.core.$strict>>;
|
|
107
150
|
}, z.core.$strict>>;
|
|
108
151
|
setup_receipt: z.ZodOptional<z.ZodObject<{
|
|
109
152
|
schema_version: z.ZodLiteral<"setup-receipt.v1">;
|
|
@@ -114,6 +114,37 @@ export const CollectorHeartbeatSchema = z
|
|
|
114
114
|
.nonnegative()
|
|
115
115
|
.max(1_000_000)
|
|
116
116
|
.optional(),
|
|
117
|
+
/**
|
|
118
|
+
* BLI-3797. How many staged raw-evidence bytes on this machine the upload
|
|
119
|
+
* ledger cannot vouch for, and the one label that explains most of them.
|
|
120
|
+
*
|
|
121
|
+
* These are the "collection has not landed" numbers. A machine can be
|
|
122
|
+
* alive, syncing, green on every other counter here and still be holding
|
|
123
|
+
* gigabytes of transcript that never reached storage — on the reference Mac
|
|
124
|
+
* 1,836 MB across 234 objects, the oldest staged 45 days earlier, with the
|
|
125
|
+
* server itself answering `unknown_to_server` when asked. Nothing in this
|
|
126
|
+
* heartbeat could say so, so `cockpit ops` could not either.
|
|
127
|
+
*
|
|
128
|
+
* `staging_uncommitted_reason` follows `last_sync_reason`'s
|
|
129
|
+
* `nothing_in_root:47` idiom — `<dominant reason>:<object count>` — so one
|
|
130
|
+
* bounded label carries both halves and a reader prints it verbatim rather
|
|
131
|
+
* than parsing it. Both fields are optional: a collector too old to compute
|
|
132
|
+
* them keeps posting, and an absent value reads as "this collector predates
|
|
133
|
+
* the counter", never as "this machine holds nothing".
|
|
134
|
+
*/
|
|
135
|
+
staging_uncommitted_bytes: z
|
|
136
|
+
.number()
|
|
137
|
+
.int()
|
|
138
|
+
.nonnegative()
|
|
139
|
+
.max(Number.MAX_SAFE_INTEGER)
|
|
140
|
+
.optional(),
|
|
141
|
+
staging_uncommitted_reason: z
|
|
142
|
+
.string()
|
|
143
|
+
.trim()
|
|
144
|
+
.min(1)
|
|
145
|
+
.max(120)
|
|
146
|
+
.regex(/^[a-z0-9_:.-]+$/iu)
|
|
147
|
+
.optional(),
|
|
117
148
|
/**
|
|
118
149
|
* BLI-3729. Is BLI Memory actually switched on for both agent hosts on
|
|
119
150
|
* this machine? Six words computed by reading the host configs BACK, not
|
|
@@ -2,9 +2,9 @@ import { z } from "zod";
|
|
|
2
2
|
export declare const EvidenceCompletenessStatusSchema: z.ZodEnum<{
|
|
3
3
|
unknown: "unknown";
|
|
4
4
|
failed: "failed";
|
|
5
|
+
empty: "empty";
|
|
5
6
|
complete: "complete";
|
|
6
7
|
partial: "partial";
|
|
7
|
-
empty: "empty";
|
|
8
8
|
unavailable: "unavailable";
|
|
9
9
|
}>;
|
|
10
10
|
export type EvidenceCompletenessStatus = z.infer<typeof EvidenceCompletenessStatusSchema>;
|
|
@@ -71,9 +71,9 @@ export declare const EvidenceCompletenessPayloadSchema: z.ZodObject<{
|
|
|
71
71
|
status: z.ZodEnum<{
|
|
72
72
|
unknown: "unknown";
|
|
73
73
|
failed: "failed";
|
|
74
|
+
empty: "empty";
|
|
74
75
|
complete: "complete";
|
|
75
76
|
partial: "partial";
|
|
76
|
-
empty: "empty";
|
|
77
77
|
unavailable: "unavailable";
|
|
78
78
|
}>;
|
|
79
79
|
generated_at: z.ZodString;
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from "./evidence-upload.js";
|
|
|
10
10
|
export * from "./ingest-dto.js";
|
|
11
11
|
export * from "./local-config.js";
|
|
12
12
|
export * from "./memory-hook-stats.js";
|
|
13
|
+
export * from "./memory-hook-performance.js";
|
|
13
14
|
export * from "./memory-install-receipt.js";
|
|
14
15
|
export * from "./paths.js";
|
|
15
16
|
export * from "./privacy.js";
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export * from "./evidence-upload.js";
|
|
|
10
10
|
export * from "./ingest-dto.js";
|
|
11
11
|
export * from "./local-config.js";
|
|
12
12
|
export * from "./memory-hook-stats.js";
|
|
13
|
+
export * from "./memory-hook-performance.js";
|
|
13
14
|
export * from "./memory-install-receipt.js";
|
|
14
15
|
export * from "./paths.js";
|
|
15
16
|
export * from "./privacy.js";
|
package/dist/ingest-dto.d.ts
CHANGED
|
@@ -105,9 +105,9 @@ export declare const TelemetryIngestEventDtoSchema: z.ZodObject<{
|
|
|
105
105
|
status: z.ZodEnum<{
|
|
106
106
|
unknown: "unknown";
|
|
107
107
|
failed: "failed";
|
|
108
|
+
empty: "empty";
|
|
108
109
|
complete: "complete";
|
|
109
110
|
partial: "partial";
|
|
110
|
-
empty: "empty";
|
|
111
111
|
unavailable: "unavailable";
|
|
112
112
|
}>;
|
|
113
113
|
generated_at: z.ZodString;
|
|
@@ -584,9 +584,9 @@ export declare const TelemetryIngestEnvelopeSchema: z.ZodObject<{
|
|
|
584
584
|
status: z.ZodEnum<{
|
|
585
585
|
unknown: "unknown";
|
|
586
586
|
failed: "failed";
|
|
587
|
+
empty: "empty";
|
|
587
588
|
complete: "complete";
|
|
588
589
|
partial: "partial";
|
|
589
|
-
empty: "empty";
|
|
590
590
|
unavailable: "unavailable";
|
|
591
591
|
}>;
|
|
592
592
|
generated_at: z.ZodString;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-free prompt-hook performance samples (BLI-3876).
|
|
3
|
+
*
|
|
4
|
+
* This module is the shared wire and local-file contract only. It deliberately
|
|
5
|
+
* has no filesystem effects: the hook writer and collector reader own I/O.
|
|
6
|
+
* Samples carry closed labels, durations and timestamps, never prompt text,
|
|
7
|
+
* hashes or memory content.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
export declare const MEMORY_HOOK_SAMPLE_SCHEMA_VERSION = "memory-hook-sample.v1";
|
|
11
|
+
export declare const MEMORY_HOOK_SAMPLING_LEASE_SCHEMA_VERSION = "memory-hook-sampling-lease.v1";
|
|
12
|
+
export declare const MEMORY_HOOK_PERFORMANCE_SCHEMA_VERSION = "memory-hook-performance.v1";
|
|
13
|
+
export declare const MEMORY_HOOK_CACHE_OUTCOMES: readonly ["hit", "miss", "shared", "unknown"];
|
|
14
|
+
export type MemoryHookCacheOutcome = (typeof MEMORY_HOOK_CACHE_OUTCOMES)[number];
|
|
15
|
+
export declare const MEMORY_HOOK_SAMPLE_OUTCOMES: readonly ["printed", "empty", "timeouts", "failed", "skipped"];
|
|
16
|
+
export type MemoryHookSampleOutcome = (typeof MEMORY_HOOK_SAMPLE_OUTCOMES)[number];
|
|
17
|
+
export declare const MemoryHookSampleSchema: z.ZodObject<{
|
|
18
|
+
schema_version: z.ZodLiteral<"memory-hook-sample.v1">;
|
|
19
|
+
recorded_at: z.ZodString;
|
|
20
|
+
elapsed_ms: z.ZodNumber;
|
|
21
|
+
outcome: z.ZodEnum<{
|
|
22
|
+
failed: "failed";
|
|
23
|
+
skipped: "skipped";
|
|
24
|
+
printed: "printed";
|
|
25
|
+
empty: "empty";
|
|
26
|
+
timeouts: "timeouts";
|
|
27
|
+
}>;
|
|
28
|
+
embed_cache: z.ZodEnum<{
|
|
29
|
+
unknown: "unknown";
|
|
30
|
+
hit: "hit";
|
|
31
|
+
miss: "miss";
|
|
32
|
+
shared: "shared";
|
|
33
|
+
}>;
|
|
34
|
+
producer_version: z.ZodString;
|
|
35
|
+
}, z.core.$strict>;
|
|
36
|
+
export type MemoryHookSample = z.infer<typeof MemoryHookSampleSchema>;
|
|
37
|
+
export declare const MemoryHookSamplingLeaseSchema: z.ZodObject<{
|
|
38
|
+
schema_version: z.ZodLiteral<"memory-hook-sampling-lease.v1">;
|
|
39
|
+
continuous_since: z.ZodString;
|
|
40
|
+
expires_at: z.ZodString;
|
|
41
|
+
}, z.core.$strict>;
|
|
42
|
+
export type MemoryHookSamplingLease = z.infer<typeof MemoryHookSamplingLeaseSchema>;
|
|
43
|
+
/** The samples directory below the user's existing Cockpit state directory. */
|
|
44
|
+
export declare const MEMORY_HOOK_SAMPLES_DIRECTORY_NAME = "memory-hook-samples";
|
|
45
|
+
export declare function memoryHookSamplesDirectory(homeDir: string): string;
|
|
46
|
+
export declare const MEMORY_HOOK_HISTOGRAM_BIN_WIDTH_MS = 100;
|
|
47
|
+
export declare const MEMORY_HOOK_HISTOGRAM_LENGTH = 51;
|
|
48
|
+
/** A fresh [0,100), ... [4900,5000), [5000,infinity) histogram. */
|
|
49
|
+
export declare function blankHistogram(): number[];
|
|
50
|
+
/** Add one valid duration and return the same histogram for convenient chaining. */
|
|
51
|
+
export declare function addDuration(histogram: number[], durationMs: number): number[];
|
|
52
|
+
/**
|
|
53
|
+
* Return the nearest-rank bin bounds. The result never interpolates between
|
|
54
|
+
* bins, and the open-ended final bin has no finite upper bound.
|
|
55
|
+
*/
|
|
56
|
+
export declare function histogramPercentileBounds(histogram: number[], percentile: number): {
|
|
57
|
+
lower_ms: number;
|
|
58
|
+
upper_ms: number | null;
|
|
59
|
+
} | null;
|
|
60
|
+
export declare const MEMORY_HOOK_PERFORMANCE_REASONS: readonly ["no_samples", "partial_window", "lease_absent", "lease_expired", "lease_invalid", "read_failed", "read_limit", "invalid_samples", "incomplete_samples", "unreadable_samples", "cleanup_failed"];
|
|
61
|
+
export declare const MemoryHookPerformanceSchema: z.ZodObject<{
|
|
62
|
+
schema_version: z.ZodLiteral<"memory-hook-performance.v1">;
|
|
63
|
+
window_start: z.ZodString;
|
|
64
|
+
window_end: z.ZodString;
|
|
65
|
+
sampling_since: z.ZodNullable<z.ZodString>;
|
|
66
|
+
samples: z.ZodNumber;
|
|
67
|
+
outcomes: z.ZodObject<{
|
|
68
|
+
printed: z.ZodNumber;
|
|
69
|
+
empty: z.ZodNumber;
|
|
70
|
+
timeouts: z.ZodNumber;
|
|
71
|
+
failed: z.ZodNumber;
|
|
72
|
+
skipped: z.ZodNumber;
|
|
73
|
+
}, z.core.$strict>;
|
|
74
|
+
duration_by_cache: z.ZodObject<{
|
|
75
|
+
hit: z.ZodArray<z.ZodNumber>;
|
|
76
|
+
miss: z.ZodArray<z.ZodNumber>;
|
|
77
|
+
shared: z.ZodArray<z.ZodNumber>;
|
|
78
|
+
unknown: z.ZodArray<z.ZodNumber>;
|
|
79
|
+
}, z.core.$strict>;
|
|
80
|
+
producer_versions: z.ZodArray<z.ZodObject<{
|
|
81
|
+
version: z.ZodString;
|
|
82
|
+
samples: z.ZodNumber;
|
|
83
|
+
}, z.core.$strict>>;
|
|
84
|
+
invalid_samples: z.ZodNumber;
|
|
85
|
+
incomplete_samples: z.ZodNumber;
|
|
86
|
+
unreadable_samples: z.ZodNumber;
|
|
87
|
+
capped: z.ZodBoolean;
|
|
88
|
+
reasons: z.ZodArray<z.ZodEnum<{
|
|
89
|
+
no_samples: "no_samples";
|
|
90
|
+
partial_window: "partial_window";
|
|
91
|
+
lease_absent: "lease_absent";
|
|
92
|
+
lease_expired: "lease_expired";
|
|
93
|
+
lease_invalid: "lease_invalid";
|
|
94
|
+
read_failed: "read_failed";
|
|
95
|
+
read_limit: "read_limit";
|
|
96
|
+
invalid_samples: "invalid_samples";
|
|
97
|
+
incomplete_samples: "incomplete_samples";
|
|
98
|
+
unreadable_samples: "unreadable_samples";
|
|
99
|
+
cleanup_failed: "cleanup_failed";
|
|
100
|
+
}>>;
|
|
101
|
+
}, z.core.$strict>;
|
|
102
|
+
export type MemoryHookPerformance = z.infer<typeof MemoryHookPerformanceSchema>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-free prompt-hook performance samples (BLI-3876).
|
|
3
|
+
*
|
|
4
|
+
* This module is the shared wire and local-file contract only. It deliberately
|
|
5
|
+
* has no filesystem effects: the hook writer and collector reader own I/O.
|
|
6
|
+
* Samples carry closed labels, durations and timestamps, never prompt text,
|
|
7
|
+
* hashes or memory content.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { IsoDateTimeSchema } from "./common.js";
|
|
11
|
+
import { getUserLocalCockpitPaths } from "./paths.js";
|
|
12
|
+
export const MEMORY_HOOK_SAMPLE_SCHEMA_VERSION = "memory-hook-sample.v1";
|
|
13
|
+
export const MEMORY_HOOK_SAMPLING_LEASE_SCHEMA_VERSION = "memory-hook-sampling-lease.v1";
|
|
14
|
+
export const MEMORY_HOOK_PERFORMANCE_SCHEMA_VERSION = "memory-hook-performance.v1";
|
|
15
|
+
export const MEMORY_HOOK_CACHE_OUTCOMES = [
|
|
16
|
+
"hit",
|
|
17
|
+
"miss",
|
|
18
|
+
"shared",
|
|
19
|
+
"unknown",
|
|
20
|
+
];
|
|
21
|
+
export const MEMORY_HOOK_SAMPLE_OUTCOMES = [
|
|
22
|
+
"printed",
|
|
23
|
+
"empty",
|
|
24
|
+
"timeouts",
|
|
25
|
+
"failed",
|
|
26
|
+
"skipped",
|
|
27
|
+
];
|
|
28
|
+
const CacheOutcomeSchema = z.enum(MEMORY_HOOK_CACHE_OUTCOMES);
|
|
29
|
+
const SampleOutcomeSchema = z.enum(MEMORY_HOOK_SAMPLE_OUTCOMES);
|
|
30
|
+
const CountSchema = z.number().int().nonnegative();
|
|
31
|
+
const SemverSchema = z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.max(40)
|
|
35
|
+
.regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u);
|
|
36
|
+
export const MemoryHookSampleSchema = z
|
|
37
|
+
.object({
|
|
38
|
+
schema_version: z.literal(MEMORY_HOOK_SAMPLE_SCHEMA_VERSION),
|
|
39
|
+
recorded_at: IsoDateTimeSchema,
|
|
40
|
+
elapsed_ms: z.number().finite().int().nonnegative().max(60_000),
|
|
41
|
+
outcome: SampleOutcomeSchema,
|
|
42
|
+
embed_cache: CacheOutcomeSchema,
|
|
43
|
+
producer_version: SemverSchema,
|
|
44
|
+
})
|
|
45
|
+
.strict();
|
|
46
|
+
export const MemoryHookSamplingLeaseSchema = z
|
|
47
|
+
.object({
|
|
48
|
+
schema_version: z.literal(MEMORY_HOOK_SAMPLING_LEASE_SCHEMA_VERSION),
|
|
49
|
+
continuous_since: IsoDateTimeSchema,
|
|
50
|
+
expires_at: IsoDateTimeSchema,
|
|
51
|
+
})
|
|
52
|
+
.strict();
|
|
53
|
+
/** The samples directory below the user's existing Cockpit state directory. */
|
|
54
|
+
export const MEMORY_HOOK_SAMPLES_DIRECTORY_NAME = "memory-hook-samples";
|
|
55
|
+
export function memoryHookSamplesDirectory(homeDir) {
|
|
56
|
+
const paths = getUserLocalCockpitPaths(homeDir);
|
|
57
|
+
const separator = paths.state_dir.includes("\\") ? "\\" : "/";
|
|
58
|
+
return `${paths.state_dir}${separator}${MEMORY_HOOK_SAMPLES_DIRECTORY_NAME}`;
|
|
59
|
+
}
|
|
60
|
+
export const MEMORY_HOOK_HISTOGRAM_BIN_WIDTH_MS = 100;
|
|
61
|
+
export const MEMORY_HOOK_HISTOGRAM_LENGTH = 51;
|
|
62
|
+
/** A fresh [0,100), ... [4900,5000), [5000,infinity) histogram. */
|
|
63
|
+
export function blankHistogram() {
|
|
64
|
+
return Array.from({ length: MEMORY_HOOK_HISTOGRAM_LENGTH }, () => 0);
|
|
65
|
+
}
|
|
66
|
+
function validateHistogram(histogram) {
|
|
67
|
+
if (histogram.length !== MEMORY_HOOK_HISTOGRAM_LENGTH) {
|
|
68
|
+
throw new RangeError(`histogram must have ${MEMORY_HOOK_HISTOGRAM_LENGTH} bins`);
|
|
69
|
+
}
|
|
70
|
+
for (const count of histogram) {
|
|
71
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
72
|
+
throw new RangeError("histogram counts must be non-negative integers");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Add one valid duration and return the same histogram for convenient chaining. */
|
|
77
|
+
export function addDuration(histogram, durationMs) {
|
|
78
|
+
validateHistogram(histogram);
|
|
79
|
+
if (!Number.isFinite(durationMs) || !Number.isInteger(durationMs) || durationMs < 0) {
|
|
80
|
+
throw new RangeError("duration must be a non-negative integer");
|
|
81
|
+
}
|
|
82
|
+
if (durationMs > 60_000) {
|
|
83
|
+
throw new RangeError("duration must be at most 60000 ms");
|
|
84
|
+
}
|
|
85
|
+
const bin = Math.min(Math.floor(durationMs / MEMORY_HOOK_HISTOGRAM_BIN_WIDTH_MS), MEMORY_HOOK_HISTOGRAM_LENGTH - 1);
|
|
86
|
+
histogram[bin] += 1;
|
|
87
|
+
return histogram;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Return the nearest-rank bin bounds. The result never interpolates between
|
|
91
|
+
* bins, and the open-ended final bin has no finite upper bound.
|
|
92
|
+
*/
|
|
93
|
+
export function histogramPercentileBounds(histogram, percentile) {
|
|
94
|
+
validateHistogram(histogram);
|
|
95
|
+
if (!Number.isFinite(percentile) || percentile < 0 || percentile > 100) {
|
|
96
|
+
throw new RangeError("percentile must be between 0 and 100");
|
|
97
|
+
}
|
|
98
|
+
const samples = histogram.reduce((sum, count) => sum + count, 0);
|
|
99
|
+
if (samples === 0)
|
|
100
|
+
return null;
|
|
101
|
+
const rank = Math.max(1, Math.ceil((percentile / 100) * samples));
|
|
102
|
+
let cumulative = 0;
|
|
103
|
+
for (let index = 0; index < histogram.length; index += 1) {
|
|
104
|
+
cumulative += histogram[index] ?? 0;
|
|
105
|
+
if (cumulative >= rank) {
|
|
106
|
+
return {
|
|
107
|
+
lower_ms: index * MEMORY_HOOK_HISTOGRAM_BIN_WIDTH_MS,
|
|
108
|
+
upper_ms: index === MEMORY_HOOK_HISTOGRAM_LENGTH - 1
|
|
109
|
+
? null
|
|
110
|
+
: (index + 1) * MEMORY_HOOK_HISTOGRAM_BIN_WIDTH_MS,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const OutcomeCountsSchema = z
|
|
117
|
+
.object({
|
|
118
|
+
printed: CountSchema,
|
|
119
|
+
empty: CountSchema,
|
|
120
|
+
timeouts: CountSchema,
|
|
121
|
+
failed: CountSchema,
|
|
122
|
+
skipped: CountSchema,
|
|
123
|
+
})
|
|
124
|
+
.strict();
|
|
125
|
+
const HistogramSchema = z
|
|
126
|
+
.array(CountSchema)
|
|
127
|
+
.length(MEMORY_HOOK_HISTOGRAM_LENGTH);
|
|
128
|
+
const DurationByCacheSchema = z
|
|
129
|
+
.object({
|
|
130
|
+
hit: HistogramSchema,
|
|
131
|
+
miss: HistogramSchema,
|
|
132
|
+
shared: HistogramSchema,
|
|
133
|
+
unknown: HistogramSchema,
|
|
134
|
+
})
|
|
135
|
+
.strict();
|
|
136
|
+
const ProducerVersionCountSchema = z
|
|
137
|
+
.object({
|
|
138
|
+
version: SemverSchema,
|
|
139
|
+
samples: CountSchema,
|
|
140
|
+
})
|
|
141
|
+
.strict();
|
|
142
|
+
export const MEMORY_HOOK_PERFORMANCE_REASONS = [
|
|
143
|
+
"no_samples",
|
|
144
|
+
"partial_window",
|
|
145
|
+
"lease_absent",
|
|
146
|
+
"lease_expired",
|
|
147
|
+
"lease_invalid",
|
|
148
|
+
"read_failed",
|
|
149
|
+
"read_limit",
|
|
150
|
+
"invalid_samples",
|
|
151
|
+
"incomplete_samples",
|
|
152
|
+
"unreadable_samples",
|
|
153
|
+
"cleanup_failed",
|
|
154
|
+
];
|
|
155
|
+
const PerformanceReasonSchema = z.enum(MEMORY_HOOK_PERFORMANCE_REASONS);
|
|
156
|
+
export const MemoryHookPerformanceSchema = z
|
|
157
|
+
.object({
|
|
158
|
+
schema_version: z.literal(MEMORY_HOOK_PERFORMANCE_SCHEMA_VERSION),
|
|
159
|
+
window_start: IsoDateTimeSchema,
|
|
160
|
+
window_end: IsoDateTimeSchema,
|
|
161
|
+
sampling_since: IsoDateTimeSchema.nullable(),
|
|
162
|
+
samples: CountSchema,
|
|
163
|
+
outcomes: OutcomeCountsSchema,
|
|
164
|
+
duration_by_cache: DurationByCacheSchema,
|
|
165
|
+
producer_versions: z.array(ProducerVersionCountSchema).max(16),
|
|
166
|
+
invalid_samples: CountSchema,
|
|
167
|
+
incomplete_samples: CountSchema,
|
|
168
|
+
unreadable_samples: CountSchema,
|
|
169
|
+
capped: z.boolean(),
|
|
170
|
+
reasons: z.array(PerformanceReasonSchema).max(MEMORY_HOOK_PERFORMANCE_REASONS.length),
|
|
171
|
+
})
|
|
172
|
+
.strict()
|
|
173
|
+
.superRefine((value, context) => {
|
|
174
|
+
const outcomeTotal = Object.values(value.outcomes).reduce((sum, count) => sum + count, 0);
|
|
175
|
+
if (outcomeTotal !== value.samples) {
|
|
176
|
+
context.addIssue({
|
|
177
|
+
code: "custom",
|
|
178
|
+
path: ["outcomes"],
|
|
179
|
+
message: "outcome counts must sum to samples",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const histogramTotal = Object.values(value.duration_by_cache)
|
|
183
|
+
.flat()
|
|
184
|
+
.reduce((sum, count) => sum + count, 0);
|
|
185
|
+
if (histogramTotal !== value.samples) {
|
|
186
|
+
context.addIssue({
|
|
187
|
+
code: "custom",
|
|
188
|
+
path: ["duration_by_cache"],
|
|
189
|
+
message: "duration histograms must sum to samples",
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
const versionTotal = value.producer_versions.reduce((sum, item) => sum + item.samples, 0);
|
|
193
|
+
if (versionTotal !== value.samples) {
|
|
194
|
+
context.addIssue({
|
|
195
|
+
code: "custom",
|
|
196
|
+
path: ["producer_versions"],
|
|
197
|
+
message: "producer version counts must sum to samples",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const versions = value.producer_versions.map((item) => item.version);
|
|
201
|
+
if (new Set(versions).size !== versions.length) {
|
|
202
|
+
context.addIssue({
|
|
203
|
+
code: "custom",
|
|
204
|
+
path: ["producer_versions"],
|
|
205
|
+
message: "producer versions must be unique",
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
});
|
|
@@ -114,6 +114,47 @@ export declare const MemoryInstallReceiptSchema: z.ZodObject<{
|
|
|
114
114
|
hook_printed_24h: z.ZodOptional<z.ZodNumber>;
|
|
115
115
|
hook_failed_24h: z.ZodOptional<z.ZodNumber>;
|
|
116
116
|
hook_stats_reason: z.ZodOptional<z.ZodString>;
|
|
117
|
+
hook_performance: z.ZodOptional<z.ZodObject<{
|
|
118
|
+
schema_version: z.ZodLiteral<"memory-hook-performance.v1">;
|
|
119
|
+
window_start: z.ZodString;
|
|
120
|
+
window_end: z.ZodString;
|
|
121
|
+
sampling_since: z.ZodNullable<z.ZodString>;
|
|
122
|
+
samples: z.ZodNumber;
|
|
123
|
+
outcomes: z.ZodObject<{
|
|
124
|
+
printed: z.ZodNumber;
|
|
125
|
+
empty: z.ZodNumber;
|
|
126
|
+
timeouts: z.ZodNumber;
|
|
127
|
+
failed: z.ZodNumber;
|
|
128
|
+
skipped: z.ZodNumber;
|
|
129
|
+
}, z.core.$strict>;
|
|
130
|
+
duration_by_cache: z.ZodObject<{
|
|
131
|
+
hit: z.ZodArray<z.ZodNumber>;
|
|
132
|
+
miss: z.ZodArray<z.ZodNumber>;
|
|
133
|
+
shared: z.ZodArray<z.ZodNumber>;
|
|
134
|
+
unknown: z.ZodArray<z.ZodNumber>;
|
|
135
|
+
}, z.core.$strict>;
|
|
136
|
+
producer_versions: z.ZodArray<z.ZodObject<{
|
|
137
|
+
version: z.ZodString;
|
|
138
|
+
samples: z.ZodNumber;
|
|
139
|
+
}, z.core.$strict>>;
|
|
140
|
+
invalid_samples: z.ZodNumber;
|
|
141
|
+
incomplete_samples: z.ZodNumber;
|
|
142
|
+
unreadable_samples: z.ZodNumber;
|
|
143
|
+
capped: z.ZodBoolean;
|
|
144
|
+
reasons: z.ZodArray<z.ZodEnum<{
|
|
145
|
+
no_samples: "no_samples";
|
|
146
|
+
partial_window: "partial_window";
|
|
147
|
+
lease_absent: "lease_absent";
|
|
148
|
+
lease_expired: "lease_expired";
|
|
149
|
+
lease_invalid: "lease_invalid";
|
|
150
|
+
read_failed: "read_failed";
|
|
151
|
+
read_limit: "read_limit";
|
|
152
|
+
invalid_samples: "invalid_samples";
|
|
153
|
+
incomplete_samples: "incomplete_samples";
|
|
154
|
+
unreadable_samples: "unreadable_samples";
|
|
155
|
+
cleanup_failed: "cleanup_failed";
|
|
156
|
+
}>>;
|
|
157
|
+
}, z.core.$strict>>;
|
|
117
158
|
}, z.core.$strict>;
|
|
118
159
|
export type MemoryInstallReceipt = z.infer<typeof MemoryInstallReceiptSchema>;
|
|
119
160
|
/** Every piece is `ok`. The one question the board asks first. */
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import { z } from "zod";
|
|
35
35
|
import { IsoDateTimeSchema } from "./common.js";
|
|
36
|
+
import { MemoryHookPerformanceSchema } from "./memory-hook-performance.js";
|
|
36
37
|
export const MEMORY_INSTALL_RECEIPT_SCHEMA_VERSION = "memory-install-receipt.v1";
|
|
37
38
|
/**
|
|
38
39
|
* What one piece of the installation is.
|
|
@@ -133,6 +134,8 @@ export const MemoryInstallReceiptSchema = z
|
|
|
133
134
|
hook_failed_24h: z.number().int().min(0).optional(),
|
|
134
135
|
/** Named when the counts are absent because the file could not be read. */
|
|
135
136
|
hook_stats_reason: ReasonLabelSchema.optional(),
|
|
137
|
+
/** Observed ordinary invocations, not legacy floors or a full host trace. */
|
|
138
|
+
hook_performance: MemoryHookPerformanceSchema.optional(),
|
|
136
139
|
})
|
|
137
140
|
.strict();
|
|
138
141
|
/** Every piece is `ok`. The one question the board asks first. */
|