@camstack/system 1.2.221 → 1.2.223
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/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +25 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/index.js +4 -3
- package/dist/index.mjs +4 -3
- package/dist/kernel/event-plane-report.d.ts +52 -0
- package/dist/kernel/transport/local-child-registry.d.ts +59 -0
- package/dist/{manifest-system-deps-iadzxSBr.mjs → manifest-system-deps-D4cmEnqQ.mjs} +101 -5
- package/dist/{manifest-system-deps-DfpwaXkh.js → manifest-system-deps-YYKKCKSF.js} +101 -5
- package/package.json +1 -1
package/dist/addon-runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const require_manifest_system_deps = require("./manifest-system-deps-
|
|
2
|
+
const require_manifest_system_deps = require("./manifest-system-deps-YYKKCKSF.js");
|
|
3
3
|
const require_custom_action_registry = require("./custom-action-registry-DvVMHLfJ.js");
|
|
4
4
|
let node_path = require("node:path");
|
|
5
5
|
node_path = require_chunk.__toESM(node_path);
|
package/dist/addon-runner.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as createUdsLoggerWithControl, Ft as RUNNER_RSS_BUDGET_ENV, Jt as startRunnerHeapWatch, L as LocalChildClient, a as createUdsAddonContext, bt as resolveAddonClass, it as setWorkerNativeCapsChangeListener, n as installManifestPythonDeps, nt as getWorkerNativeCapSnapshot, on as emitHeapDiagnosticReport, ot as validateProviderRegistrations, t as installManifestSystemDeps, tt as getWorkerNativeCapProvider, yt as installManifestNativeDeps } from "./manifest-system-deps-
|
|
1
|
+
import { A as createUdsLoggerWithControl, Ft as RUNNER_RSS_BUDGET_ENV, Jt as startRunnerHeapWatch, L as LocalChildClient, a as createUdsAddonContext, bt as resolveAddonClass, it as setWorkerNativeCapsChangeListener, n as installManifestPythonDeps, nt as getWorkerNativeCapSnapshot, on as emitHeapDiagnosticReport, ot as validateProviderRegistrations, t as installManifestSystemDeps, tt as getWorkerNativeCapProvider, yt as installManifestNativeDeps } from "./manifest-system-deps-D4cmEnqQ.mjs";
|
|
2
2
|
import { t as CustomActionRegistry } from "./custom-action-registry-DvtOs-tp.mjs";
|
|
3
3
|
import { register } from "node:module";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -3270,7 +3270,7 @@ var SystemBackupService = class {
|
|
|
3270
3270
|
for (const dbPath of dbPaths) try {
|
|
3271
3271
|
const db = new Database(dbPath);
|
|
3272
3272
|
try {
|
|
3273
|
-
db.pragma("wal_checkpoint(
|
|
3273
|
+
db.pragma("wal_checkpoint(PASSIVE)");
|
|
3274
3274
|
} finally {
|
|
3275
3275
|
db.close();
|
|
3276
3276
|
}
|
|
@@ -3255,7 +3255,7 @@ var SystemBackupService = class {
|
|
|
3255
3255
|
for (const dbPath of dbPaths) try {
|
|
3256
3256
|
const db = new Database(dbPath);
|
|
3257
3257
|
try {
|
|
3258
|
-
db.pragma("wal_checkpoint(
|
|
3258
|
+
db.pragma("wal_checkpoint(PASSIVE)");
|
|
3259
3259
|
} finally {
|
|
3260
3260
|
db.close();
|
|
3261
3261
|
}
|
|
@@ -401,6 +401,31 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
|
|
|
401
401
|
* inside that blob and are reached with `json_extract`.
|
|
402
402
|
*/
|
|
403
403
|
private shapeOf;
|
|
404
|
+
/**
|
|
405
|
+
* Where a declaration LIVES, so it outlives the process that made it.
|
|
406
|
+
*
|
|
407
|
+
* `declaredCollections` used to be only an in-memory Map, and the
|
|
408
|
+
* declarations are made once, by each addon, at that addon's own boot.
|
|
409
|
+
* On 2026-09-12 this backend died with SIGBUS (a second connection
|
|
410
|
+
* truncated the WAL under its mmap) and `CrashSupervisor` respawned it: the
|
|
411
|
+
* new instance knew only the canonical KV collections, every other addon
|
|
412
|
+
* stayed up and never re-declared, and the cluster failed every settings
|
|
413
|
+
* read for six hours — the operator could not log in.
|
|
414
|
+
*
|
|
415
|
+
* The tables are durable. What the backend knows ABOUT them has to be too,
|
|
416
|
+
* because nothing in the system re-declares and nothing listens for this
|
|
417
|
+
* process restarting.
|
|
418
|
+
*/
|
|
419
|
+
private static readonly DECLARATIONS_TABLE;
|
|
420
|
+
/** Create the meta table. Idempotent; called before anything reads it. */
|
|
421
|
+
private ensureDeclarationsTable;
|
|
422
|
+
/** Persist one declaration, replacing any earlier shape for the same table. */
|
|
423
|
+
private persistDeclaration;
|
|
424
|
+
/**
|
|
425
|
+
* Reload every persisted declaration. Called at init, BEFORE any addon has
|
|
426
|
+
* had the chance to re-declare — which on a respawn none of them will.
|
|
427
|
+
*/
|
|
428
|
+
private restorePersistedDeclarations;
|
|
404
429
|
declareCollection(input: {
|
|
405
430
|
namespace?: string;
|
|
406
431
|
collection: string;
|
|
Binary file
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ require("./builtins/system-config/index.js");
|
|
|
27
27
|
const require_builtins_winston_logging_index = require("./builtins/winston-logging/index.js");
|
|
28
28
|
const require_event_loop_stall_monitor = require("./event-loop-stall-monitor-CAsHdMwP.js");
|
|
29
29
|
const require_tls$1 = require("./tls-BFs4PzoW.js");
|
|
30
|
-
const require_manifest_system_deps = require("./manifest-system-deps-
|
|
30
|
+
const require_manifest_system_deps = require("./manifest-system-deps-YYKKCKSF.js");
|
|
31
31
|
const require_resource_monitor = require("./resource-monitor-LVE1BoLs.js");
|
|
32
32
|
const require_custom_action_registry = require("./custom-action-registry-DvVMHLfJ.js");
|
|
33
33
|
let zod = require("zod");
|
|
@@ -78961,8 +78961,9 @@ var require_parser = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, modu
|
|
|
78961
78961
|
const type = resolvePacketType(packet[5]);
|
|
78962
78962
|
this.emit("data", type, msg);
|
|
78963
78963
|
const rest = packet.slice(length);
|
|
78964
|
-
|
|
78965
|
-
this.
|
|
78964
|
+
const carried = rest.length > 0 && rest.length * 8 < rest.buffer.byteLength ? Buffer.from(rest) : rest;
|
|
78965
|
+
this.bufs = carried.length > 0 ? [carried] : [];
|
|
78966
|
+
this.bufLen = carried.length;
|
|
78966
78967
|
}
|
|
78967
78968
|
cb();
|
|
78968
78969
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -26,7 +26,7 @@ import "./builtins/system-config/index.mjs";
|
|
|
26
26
|
import { WinstonDestination, WinstonLoggingAddon } from "./builtins/winston-logging/index.mjs";
|
|
27
27
|
import { _ as fetchJson, a as createFileDataPlaneHandler, c as parseRangeHeader, d as ModelDownloadService, f as collectModelFiles, g as ensureModel, h as downloadModel, i as startEventLoopStallMonitor, l as parseTokenizedUrl, m as downloadFile, n as STALL_TICK_MS, o as contentTypeFor, p as deleteModelFromDisk, r as attributeStall, s as createAuthenticatedFileServer, t as STALL_REPORT_MS, u as resolveFilePath, v as getModelFilePath, y as isModelDownloaded } from "./event-loop-stall-monitor-GPrVvx14.mjs";
|
|
28
28
|
import { C as registerLanHttpHandler, S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, b as bindPendingLanHttp, c as loadTlsCert, d as collectCertIdentity, f as CA_VALIDITY_DAYS, g as evaluateExistingCert, h as SERVER_AUTH_OID, i as writeExtraSans, l as reissueTlsLeaf, m as MAX_LEAF_VALIDITY_DAYS, n as readTlsAccessStatus, o as validateUploadedTls, p as LEAF_RENEWAL_WINDOW_DAYS, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, u as CA_COMMON_NAME, v as allFamiliesListenHost, x as closeLanHttp, y as applyLanHttp } from "./tls-2xbgg78V.mjs";
|
|
29
|
-
import { $ as buildUdsNativeCapProxy, $t as ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO, A as createUdsLoggerWithControl, An as sampleSocketDirection, At as HUB_MAIN_RSS_BUDGET_MB, B as readMoleculerFanoutMode, Bn as EVENT_PLANE_TOP_N, Bt as createV8Reclaimer, C as ipcParentLink, Cn as createSocketPlaneReader, Ct as createAddonDataPlaneFacility, D as createUdsEventBus, Dn as EMPTY_SOCKET_DIRECTION, Dt as HEAP_WATCH_INTERVAL_MS, E as createUdsEventBridge, En as readSocketQueues, Et as HEAP_RECLAIM_TRIGGER_MB, F as CapRouteError, Fn as EMPTY_CAP_ACTION_READING, Ft as RUNNER_RSS_BUDGET_ENV, G as createLocalTransport, Gt as reclaimIntervalMs, H as PARENT_ROUTED_PROVIDER_ADDON_ID, Hn as createEventPlaneReader, Ht as hubMainRssBudget, I as classifyCapRoute, In as MAX_CAP_ACTIONS_PER_PEER, It as ZERO_LOOP_DELAY, J as SocketChannel, Jt as startRunnerHeapWatch, K as UdsLocalTransportClient, Kt as shouldReclaim, L as LocalChildClient, Ln as capActionLabel, Lt as buildHeapSample, M as AGENT_CAP_FWD_SERVICE, Mn as socketDirectionMessages, Mt as RSS_BUDGET_REANNOUNCE_MIN_MS, N as CapRouteResolver, Nn as CAP_ACTION_OVERFLOW_LABEL, Nt as RSS_BUDGET_RELEASE_RATIO, O as udsChildLogToWorkerEntry, On as createSocketDirectionCounters, Ot as HEAP_WATCH_WARN_RATIO, P as callWithServiceDiscovery, Pn as CAP_ACTION_UNLABELLED, Pt as RUNNER_HEAP_WATCH_INTERVAL_MS, Q as buildNativeCapProxy, Qt as ARRAY_BUFFER_CENSUS_THRESHOLD_MB, R as LocalChildRegistry, Rn as createCapActionCensus, Rt as createDeferredHeapWatchSink, S as buildLinkChain, Sn as createSocketPlaneMeter, St as runNpm, T as createParentUnownedCallHandler, Tn as formatSocketPlane, Tt as HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS, U as __resetCapUsageRegistryForTests, Un as diffEventPlane, Ut as nextRssBudgetState, V as CapUsageRegistry, Vn as createEventPlaneMeter, Vt as describeRss, W as getCapUsageRegistry, Wn as formatEventPlane, Wt as parseRssBudgetMb, X as FrameDecoder, Xt as ARRAY_BUFFER_CENSUS_COOLDOWN_MS, Y as localEndpointPath, Yt as strandedMb, Z as encodeFrame, Zt as ARRAY_BUFFER_CENSUS_SUSTAIN_MS, _ as AddonDepsManager, _n as createMeshQueueReader, _t as CapabilityUnavailableError, a as createUdsAddonContext, an as HEAP_SPACE_REPORT_MIN_MB, at as createAddonService, b as brokerCallForCap, bn as SOCKET_PLANE_TOP_N, bt as resolveAddonClass, c as getOrInitReadinessRegistryForClient, cn as heapSpaceField, ct as capActionName, d as clusterEventTopic, dn as OUTBOUND_QUEUE_COOLDOWN_MS, dt as capServiceName, en as createArrayBufferCensusGate, et as createBrokerDeviceManagerApi, f as getBrokerEventBus, fn as OUTBOUND_QUEUE_SUSTAIN_MS, ft as parseCapAction, g as subscribePassthrough, gn as MESH_QUEUE_TOP_N, gt as CapabilityHandle, h as setNodeEventInterest, hn as formatOutboundQueueVerdict, ht as DeviceRegistry, i as createAddonContext, in as DECISIVE_HEAP_SPACES, j as AGENT_CAP_FWD_ACTION, jn as socketDirectionBytes, jt as HUB_RSS_BUDGET_ENV, k as createUdsLogger, kn as recordSocketFrame, kt as HUB_MAIN_HEAP_WATCH_LABEL, l as setHubConnected, ln as readHeapSpaces, lt as capActionSuffix, m as registerEventBusService, mn as createOutboundQueueGate, mt as serializeTypedArrays, n as installManifestPythonDeps, nn as formatArrayBufferCensus, o as getWorkerDeviceRegistry, on as emitHeapDiagnosticReport, ot as validateProviderRegistrations, p as getMoleculerEventStats, pn as OUTBOUND_QUEUE_THRESHOLD_BYTES, pt as deserializeTypedArrays, q as UdsLocalTransportServer, qt as startHeapWatch, r as adaptBrokerToCluster, rn as isArrayBufferCensusReport, rt as mountNativeCapService, s as getOrInitReadinessRegistry, sn as formatHeapSpaces, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestSystemDeps, tn as createInspectorArrayBufferCensus, u as EVENT_TOPIC_PREFIX, un as selectReportedSpaces, ut as capBareAction, v as createKernelHwAccel, vn as formatMeshQueue, vt as copyBundledNativeModules, w as localProviderLink, wn as diffSocketPlane, wt as HEAP_RECLAIM_MIN_INTERVAL_MS, x as brokerTransportLink, xn as SOCKET_QUEUE_TOP_N, xt as resolveNpmInvocation, y as resolveHwAccel, yn as EMPTY_SOCKET_PLANE_BASELINE, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX, zn as diffCapActionCensus, zt as createLoopDelayMeter } from "./manifest-system-deps-
|
|
29
|
+
import { $ as buildUdsNativeCapProxy, $t as ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO, A as createUdsLoggerWithControl, An as sampleSocketDirection, At as HUB_MAIN_RSS_BUDGET_MB, B as readMoleculerFanoutMode, Bn as EVENT_PLANE_TOP_N, Bt as createV8Reclaimer, C as ipcParentLink, Cn as createSocketPlaneReader, Ct as createAddonDataPlaneFacility, D as createUdsEventBus, Dn as EMPTY_SOCKET_DIRECTION, Dt as HEAP_WATCH_INTERVAL_MS, E as createUdsEventBridge, En as readSocketQueues, Et as HEAP_RECLAIM_TRIGGER_MB, F as CapRouteError, Fn as EMPTY_CAP_ACTION_READING, Ft as RUNNER_RSS_BUDGET_ENV, G as createLocalTransport, Gt as reclaimIntervalMs, H as PARENT_ROUTED_PROVIDER_ADDON_ID, Hn as createEventPlaneReader, Ht as hubMainRssBudget, I as classifyCapRoute, In as MAX_CAP_ACTIONS_PER_PEER, It as ZERO_LOOP_DELAY, J as SocketChannel, Jt as startRunnerHeapWatch, K as UdsLocalTransportClient, Kt as shouldReclaim, L as LocalChildClient, Ln as capActionLabel, Lt as buildHeapSample, M as AGENT_CAP_FWD_SERVICE, Mn as socketDirectionMessages, Mt as RSS_BUDGET_REANNOUNCE_MIN_MS, N as CapRouteResolver, Nn as CAP_ACTION_OVERFLOW_LABEL, Nt as RSS_BUDGET_RELEASE_RATIO, O as udsChildLogToWorkerEntry, On as createSocketDirectionCounters, Ot as HEAP_WATCH_WARN_RATIO, P as callWithServiceDiscovery, Pn as CAP_ACTION_UNLABELLED, Pt as RUNNER_HEAP_WATCH_INTERVAL_MS, Q as buildNativeCapProxy, Qt as ARRAY_BUFFER_CENSUS_THRESHOLD_MB, R as LocalChildRegistry, Rn as createCapActionCensus, Rt as createDeferredHeapWatchSink, S as buildLinkChain, Sn as createSocketPlaneMeter, St as runNpm, T as createParentUnownedCallHandler, Tn as formatSocketPlane, Tt as HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS, U as __resetCapUsageRegistryForTests, Un as diffEventPlane, Ut as nextRssBudgetState, V as CapUsageRegistry, Vn as createEventPlaneMeter, Vt as describeRss, W as getCapUsageRegistry, Wn as formatEventPlane, Wt as parseRssBudgetMb, X as FrameDecoder, Xt as ARRAY_BUFFER_CENSUS_COOLDOWN_MS, Y as localEndpointPath, Yt as strandedMb, Z as encodeFrame, Zt as ARRAY_BUFFER_CENSUS_SUSTAIN_MS, _ as AddonDepsManager, _n as createMeshQueueReader, _t as CapabilityUnavailableError, a as createUdsAddonContext, an as HEAP_SPACE_REPORT_MIN_MB, at as createAddonService, b as brokerCallForCap, bn as SOCKET_PLANE_TOP_N, bt as resolveAddonClass, c as getOrInitReadinessRegistryForClient, cn as heapSpaceField, ct as capActionName, d as clusterEventTopic, dn as OUTBOUND_QUEUE_COOLDOWN_MS, dt as capServiceName, en as createArrayBufferCensusGate, et as createBrokerDeviceManagerApi, f as getBrokerEventBus, fn as OUTBOUND_QUEUE_SUSTAIN_MS, ft as parseCapAction, g as subscribePassthrough, gn as MESH_QUEUE_TOP_N, gt as CapabilityHandle, h as setNodeEventInterest, hn as formatOutboundQueueVerdict, ht as DeviceRegistry, i as createAddonContext, in as DECISIVE_HEAP_SPACES, j as AGENT_CAP_FWD_ACTION, jn as socketDirectionBytes, jt as HUB_RSS_BUDGET_ENV, k as createUdsLogger, kn as recordSocketFrame, kt as HUB_MAIN_HEAP_WATCH_LABEL, l as setHubConnected, ln as readHeapSpaces, lt as capActionSuffix, m as registerEventBusService, mn as createOutboundQueueGate, mt as serializeTypedArrays, n as installManifestPythonDeps, nn as formatArrayBufferCensus, o as getWorkerDeviceRegistry, on as emitHeapDiagnosticReport, ot as validateProviderRegistrations, p as getMoleculerEventStats, pn as OUTBOUND_QUEUE_THRESHOLD_BYTES, pt as deserializeTypedArrays, q as UdsLocalTransportServer, qt as startHeapWatch, r as adaptBrokerToCluster, rn as isArrayBufferCensusReport, rt as mountNativeCapService, s as getOrInitReadinessRegistry, sn as formatHeapSpaces, st as NATIVE_PROVIDER_SERVICE_INFIX, t as installManifestSystemDeps, tn as createInspectorArrayBufferCensus, u as EVENT_TOPIC_PREFIX, un as selectReportedSpaces, ut as capBareAction, v as createKernelHwAccel, vn as formatMeshQueue, vt as copyBundledNativeModules, w as localProviderLink, wn as diffSocketPlane, wt as HEAP_RECLAIM_MIN_INTERVAL_MS, x as brokerTransportLink, xn as SOCKET_QUEUE_TOP_N, xt as resolveNpmInvocation, y as resolveHwAccel, yn as EMPTY_SOCKET_PLANE_BASELINE, yt as installManifestNativeDeps, z as UDS_NO_ROUTE_PREFIX, zn as diffCapActionCensus, zt as createLoopDelayMeter } from "./manifest-system-deps-D4cmEnqQ.mjs";
|
|
30
30
|
import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-Vp0bPV3U.mjs";
|
|
31
31
|
import { t as CustomActionRegistry } from "./custom-action-registry-DvtOs-tp.mjs";
|
|
32
32
|
import { z } from "zod";
|
|
@@ -78954,8 +78954,9 @@ var require_parser = /* @__PURE__ */ __commonJSMin$1(((exports, module) => {
|
|
|
78954
78954
|
const type = resolvePacketType(packet[5]);
|
|
78955
78955
|
this.emit("data", type, msg);
|
|
78956
78956
|
const rest = packet.slice(length);
|
|
78957
|
-
|
|
78958
|
-
this.
|
|
78957
|
+
const carried = rest.length > 0 && rest.length * 8 < rest.buffer.byteLength ? Buffer.from(rest) : rest;
|
|
78958
|
+
this.bufs = carried.length > 0 ? [carried] : [];
|
|
78959
|
+
this.bufLen = carried.length;
|
|
78959
78960
|
}
|
|
78960
78961
|
cb();
|
|
78961
78962
|
}
|
|
@@ -63,6 +63,25 @@
|
|
|
63
63
|
* — the same discipline the loop-delay histogram already follows, for the same
|
|
64
64
|
* reason.
|
|
65
65
|
*
|
|
66
|
+
* ## The unit trap (D467)
|
|
67
|
+
*
|
|
68
|
+
* `fanoutSent` / `fanoutSuppressed` are **(event × child) pairs**, not events:
|
|
69
|
+
* `broadcastEventToChildren` increments them inside the per-child loop, so on a
|
|
70
|
+
* 39-child node ONE emit produces up to 38 increments. A 2026-09-12 reading of
|
|
71
|
+
* `fanoutSuppressed=433461` was read as "433k events a minute, 97% discarded";
|
|
72
|
+
* the real production was **~11 700 events/min**. The line proves it itself —
|
|
73
|
+
* every pure-consumer child's `sent + suppressed` converges on 11 730, because
|
|
74
|
+
* that sum IS the distinct broadcast count, and the two rows that fall short
|
|
75
|
+
* (`pipeline-orchestrator`, `pipeline-analytics`) fall short by exactly what
|
|
76
|
+
* they originated. `fanoutEvents` is the production rate, counted once per emit
|
|
77
|
+
* outside that loop, and it is printed so the pair counters can never be read as
|
|
78
|
+
* one again.
|
|
79
|
+
*
|
|
80
|
+
* `fanoutCats` names PRODUCERS (`fanoutTop` names subscribers) and
|
|
81
|
+
* `fanoutOrphans` counts what was serialised and walked across every child with
|
|
82
|
+
* zero subscribers. A suppressed pair was measured at **255 ns** — 1.84 ms/s of
|
|
83
|
+
* event loop at this rate — so the filter is not the cost; the emit is.
|
|
84
|
+
*
|
|
66
85
|
* ## What this is NOT evidence of
|
|
67
86
|
*
|
|
68
87
|
* `shadow` is the CROSS-NODE plane. The local UDS plane defaults to `filter` and
|
|
@@ -93,11 +112,24 @@ export interface ChildFanoutRegistryStats {
|
|
|
93
112
|
readonly sent: number;
|
|
94
113
|
readonly suppressed: number;
|
|
95
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* D467 — one PRODUCED category, cumulative, as the registry keeps it.
|
|
117
|
+
*
|
|
118
|
+
* `broadcasts` is EMITS, not deliveries; `wanted` is the number of child
|
|
119
|
+
* subscriptions that matched, summed over those emits.
|
|
120
|
+
*/
|
|
121
|
+
export interface CategoryFanoutSample {
|
|
122
|
+
readonly category: string;
|
|
123
|
+
readonly broadcasts: number;
|
|
124
|
+
readonly wanted: number;
|
|
125
|
+
}
|
|
96
126
|
export interface ChildFanoutRegistry {
|
|
97
127
|
readonly listChildren: () => readonly ChildFanoutRegistryChild[];
|
|
98
128
|
readonly getChildEventStats: (childId: string) => ChildFanoutRegistryStats | null;
|
|
99
129
|
readonly undeclaredChildCount: () => number;
|
|
100
130
|
readonly eventFanoutMode: EventFanoutMode;
|
|
131
|
+
readonly categoryFanout: () => readonly CategoryFanoutSample[];
|
|
132
|
+
readonly categoryCensusOverflow: () => number;
|
|
101
133
|
}
|
|
102
134
|
/** What `getMoleculerEventStats` returns, structurally. */
|
|
103
135
|
export interface CrossNodeEventStats {
|
|
@@ -113,6 +145,8 @@ export interface EventPlaneCounters {
|
|
|
113
145
|
readonly childrenUndeclared: number;
|
|
114
146
|
readonly crossNodeDelivered: number;
|
|
115
147
|
readonly crossNodeSuppressed: number;
|
|
148
|
+
readonly categories: readonly CategoryFanoutSample[];
|
|
149
|
+
readonly categoryOverflow: number;
|
|
116
150
|
}
|
|
117
151
|
/** `undefined` = this process has no event plane to report (every forked
|
|
118
152
|
* runner, and hub-main before the registry exists). */
|
|
@@ -159,12 +193,30 @@ export interface EventPlaneWindow {
|
|
|
159
193
|
readonly crossNodeSuppressed: number;
|
|
160
194
|
/** Largest `sent` first, at most {@link EVENT_PLANE_TOP_N} entries. */
|
|
161
195
|
readonly top: readonly ChildFanoutDelta[];
|
|
196
|
+
/** Events PRODUCED in the window — emits, summed over categories. */
|
|
197
|
+
readonly categoryBroadcasts: number;
|
|
198
|
+
/** Produced events no child subscribed to. Pure waste. */
|
|
199
|
+
readonly orphanBroadcasts: number;
|
|
200
|
+
/** Largest `broadcasts` first, at most {@link EVENT_PLANE_TOP_N} entries. */
|
|
201
|
+
readonly topCategories: readonly CategoryFanoutDelta[];
|
|
202
|
+
/** The orphans alone, largest first, at most {@link EVENT_PLANE_TOP_N}. */
|
|
203
|
+
readonly topOrphans: readonly CategoryFanoutDelta[];
|
|
204
|
+
/** Broadcasts the census could not attribute because it was full. */
|
|
205
|
+
readonly categoryOverflow: number;
|
|
206
|
+
}
|
|
207
|
+
/** One category's production over the window since the previous read. */
|
|
208
|
+
export interface CategoryFanoutDelta {
|
|
209
|
+
readonly category: string;
|
|
210
|
+
readonly broadcasts: number;
|
|
211
|
+
readonly wanted: number;
|
|
162
212
|
}
|
|
163
213
|
/** Previous cumulative reading, kept between lines. */
|
|
164
214
|
export interface EventPlaneBaseline {
|
|
165
215
|
readonly children: ReadonlyMap<string, ChildFanoutSample>;
|
|
216
|
+
readonly categories: ReadonlyMap<string, CategoryFanoutSample>;
|
|
166
217
|
readonly crossNodeDelivered: number;
|
|
167
218
|
readonly crossNodeSuppressed: number;
|
|
219
|
+
readonly categoryOverflow: number;
|
|
168
220
|
}
|
|
169
221
|
/** A process that has never read a plane. */
|
|
170
222
|
export declare const EMPTY_EVENT_PLANE_BASELINE: EventPlaneBaseline;
|
|
@@ -44,6 +44,36 @@ export interface ChildEventStats {
|
|
|
44
44
|
sent: number;
|
|
45
45
|
suppressed: number;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* D467 — one PRODUCED category over the life of the process.
|
|
49
|
+
*
|
|
50
|
+
* `broadcasts` counts emits, not deliveries: one call to
|
|
51
|
+
* {@link LocalChildRegistry.broadcastEventToChildren} is one broadcast no
|
|
52
|
+
* matter how many children are connected. This is the number the per-child
|
|
53
|
+
* `sent`/`suppressed` counters cannot give — those are (event × child) pairs,
|
|
54
|
+
* so on a 39-child node they read ~38x the production rate and a reader who
|
|
55
|
+
* sums them concludes the cluster emits an order of magnitude more than it does.
|
|
56
|
+
*
|
|
57
|
+
* `wanted` is the number of (event × child) pairs whose SUBSCRIPTION matched —
|
|
58
|
+
* the gate answer, not the delivery answer, so `broadcast`/`shadow` mode (which
|
|
59
|
+
* deliver regardless) still report the truth. `wanted === 0` over a window means
|
|
60
|
+
* the category was serialised and walked across every child and **nobody asked
|
|
61
|
+
* for it**: pure waste, and the one finding worth acting on.
|
|
62
|
+
*/
|
|
63
|
+
export interface CategoryFanoutSample {
|
|
64
|
+
readonly category: string;
|
|
65
|
+
readonly broadcasts: number;
|
|
66
|
+
readonly wanted: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Cap on the census map. `EventCategory` is a closed enum of ~120 members, so
|
|
70
|
+
* 256 holds every legitimate category twice over; the cap exists because a
|
|
71
|
+
* hand-rolled topic (forbidden, but this is the meter that would SEE one) could
|
|
72
|
+
* otherwise grow the map without bound in a long-lived process. Overflow is not
|
|
73
|
+
* silent — {@link LocalChildRegistry.categoryCensusOverflow} counts it and the
|
|
74
|
+
* heartbeat prints it.
|
|
75
|
+
*/
|
|
76
|
+
export declare const CATEGORY_CENSUS_MAX = 256;
|
|
47
77
|
/**
|
|
48
78
|
* Sentinel prefix used in the no-route error thrown when `cap-call-out` has no
|
|
49
79
|
* local sibling and no `onUnownedCall` fallback. `ipcParentLink` detects this
|
|
@@ -252,6 +282,12 @@ export declare class LocalChildRegistry {
|
|
|
252
282
|
private readonly fanoutMode;
|
|
253
283
|
/** Per-child event fan-out counters (sent / suppressed). */
|
|
254
284
|
private readonly childEventStats;
|
|
285
|
+
/** D467: per-category production census (cumulative). See {@link CategoryFanoutSample}. */
|
|
286
|
+
private readonly categoryStats;
|
|
287
|
+
/** Broadcasts whose category did not fit in {@link CATEGORY_CENSUS_MAX}. */
|
|
288
|
+
private categoryOverflow;
|
|
289
|
+
/** One INFO line the first time the census cap trips, never per event. */
|
|
290
|
+
private categoryOverflowLogged;
|
|
255
291
|
/** Last pattern-set string logged per child, to dedup the INFO line. */
|
|
256
292
|
private readonly loggedPatternSet;
|
|
257
293
|
/**
|
|
@@ -279,6 +315,15 @@ export declare class LocalChildRegistry {
|
|
|
279
315
|
* surface (shadow burn-in + operator debug).
|
|
280
316
|
*/
|
|
281
317
|
getChildEventStats(childId: string): ChildEventStats | null;
|
|
318
|
+
/**
|
|
319
|
+
* D467: every category this node has broadcast, with its emit count and how
|
|
320
|
+
* many child subscriptions matched. Cumulative for the life of the process —
|
|
321
|
+
* the heartbeat differences it, the same discipline as the per-child
|
|
322
|
+
* counters. Largest `broadcasts` first.
|
|
323
|
+
*/
|
|
324
|
+
categoryFanout(): readonly CategoryFanoutSample[];
|
|
325
|
+
/** Broadcasts the census could not attribute because it was full (cumulative). */
|
|
326
|
+
categoryCensusOverflow(): number;
|
|
282
327
|
/**
|
|
283
328
|
* This child's socket traffic — bytes and messages, both directions, split by
|
|
284
329
|
* frame kind — or `null` when the child is gone or its channel keeps no
|
|
@@ -348,6 +393,15 @@ export declare class LocalChildRegistry {
|
|
|
348
393
|
* that explicitly subscribes to a ring-denied category must still RECEIVE it.
|
|
349
394
|
*/
|
|
350
395
|
private childWantsEvent;
|
|
396
|
+
/**
|
|
397
|
+
* The SUBSCRIPTION answer alone, with no fan-out-mode override.
|
|
398
|
+
*
|
|
399
|
+
* {@link childWantsEvent} answers "will this child be sent the event", which
|
|
400
|
+
* under `broadcast` is `true` for everything and therefore cannot say whether
|
|
401
|
+
* anyone actually asked. The D467 census needs the second question, so the
|
|
402
|
+
* two are separate functions and `broadcast` mode overrides only the first.
|
|
403
|
+
*/
|
|
404
|
+
private childSubscribesTo;
|
|
351
405
|
/**
|
|
352
406
|
* D2: this node's aggregate cross-node event interest — the UNION of every
|
|
353
407
|
* connected child's declared category patterns. Consumed by the parent's
|
|
@@ -513,6 +567,11 @@ export declare class LocalChildRegistry {
|
|
|
513
567
|
* one (the originating child, to avoid echo). Fire-and-forget.
|
|
514
568
|
*/
|
|
515
569
|
broadcastEventToChildren(event: SystemEvent, sourceNodeId: string, exceptChildId?: string): void;
|
|
570
|
+
/**
|
|
571
|
+
* Record one PRODUCED event against its category. Called once per broadcast,
|
|
572
|
+
* outside the per-child loop, because production is what the census measures.
|
|
573
|
+
*/
|
|
574
|
+
private recordCategoryBroadcast;
|
|
516
575
|
/**
|
|
517
576
|
* E2: Send a `set-log-level` control message to a specific child.
|
|
518
577
|
* Returns `true` if the child is currently connected and the message was
|
|
@@ -56,7 +56,9 @@ function createEventPlaneReader(sources) {
|
|
|
56
56
|
childrenConnected: listed.length,
|
|
57
57
|
childrenUndeclared: registry.undeclaredChildCount(),
|
|
58
58
|
crossNodeDelivered: crossNode?.delivered ?? 0,
|
|
59
|
-
crossNodeSuppressed: crossNode?.suppressed ?? 0
|
|
59
|
+
crossNodeSuppressed: crossNode?.suppressed ?? 0,
|
|
60
|
+
categories: registry.categoryFanout(),
|
|
61
|
+
categoryOverflow: registry.categoryCensusOverflow()
|
|
60
62
|
};
|
|
61
63
|
} catch {
|
|
62
64
|
return;
|
|
@@ -76,8 +78,10 @@ var EVENT_PLANE_TOP_N = 5;
|
|
|
76
78
|
/** A process that has never read a plane. */
|
|
77
79
|
var EMPTY_EVENT_PLANE_BASELINE = {
|
|
78
80
|
children: /* @__PURE__ */ new Map(),
|
|
81
|
+
categories: /* @__PURE__ */ new Map(),
|
|
79
82
|
crossNodeDelivered: 0,
|
|
80
|
-
crossNodeSuppressed: 0
|
|
83
|
+
crossNodeSuppressed: 0,
|
|
84
|
+
categoryOverflow: 0
|
|
81
85
|
};
|
|
82
86
|
/**
|
|
83
87
|
* Subtract the previous cumulative reading from the current one.
|
|
@@ -104,6 +108,23 @@ function diffEventPlane(baseline, current, topN = 5) {
|
|
|
104
108
|
suppressed
|
|
105
109
|
});
|
|
106
110
|
}
|
|
111
|
+
const categoryDeltas = [];
|
|
112
|
+
let categoryBroadcasts = 0;
|
|
113
|
+
let orphanBroadcasts = 0;
|
|
114
|
+
for (const sample of current.categories) {
|
|
115
|
+
const before = baseline.categories.get(sample.category);
|
|
116
|
+
const broadcasts = Math.max(0, sample.broadcasts - (before?.broadcasts ?? 0));
|
|
117
|
+
if (broadcasts === 0) continue;
|
|
118
|
+
const wanted = Math.max(0, sample.wanted - (before?.wanted ?? 0));
|
|
119
|
+
categoryBroadcasts += broadcasts;
|
|
120
|
+
if (wanted === 0) orphanBroadcasts += broadcasts;
|
|
121
|
+
categoryDeltas.push({
|
|
122
|
+
category: sample.category,
|
|
123
|
+
broadcasts,
|
|
124
|
+
wanted
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const byBroadcasts = categoryDeltas.toSorted((a, b) => b.broadcasts - a.broadcasts || a.category.localeCompare(b.category));
|
|
107
128
|
return {
|
|
108
129
|
udsMode: current.udsMode,
|
|
109
130
|
crossNodeMode: current.crossNodeMode,
|
|
@@ -113,7 +134,12 @@ function diffEventPlane(baseline, current, topN = 5) {
|
|
|
113
134
|
fanoutSuppressed,
|
|
114
135
|
crossNodeDelivered: Math.max(0, current.crossNodeDelivered - baseline.crossNodeDelivered),
|
|
115
136
|
crossNodeSuppressed: Math.max(0, current.crossNodeSuppressed - baseline.crossNodeSuppressed),
|
|
116
|
-
top: deltas.toSorted((a, b) => b.sent - a.sent || a.childId.localeCompare(b.childId)).slice(0, topN)
|
|
137
|
+
top: deltas.toSorted((a, b) => b.sent - a.sent || a.childId.localeCompare(b.childId)).slice(0, topN),
|
|
138
|
+
categoryBroadcasts,
|
|
139
|
+
orphanBroadcasts,
|
|
140
|
+
topCategories: byBroadcasts.slice(0, topN),
|
|
141
|
+
topOrphans: byBroadcasts.filter((c) => c.wanted === 0).slice(0, topN),
|
|
142
|
+
categoryOverflow: Math.max(0, current.categoryOverflow - baseline.categoryOverflow)
|
|
117
143
|
};
|
|
118
144
|
}
|
|
119
145
|
function createEventPlaneMeter(reader, topN = 5) {
|
|
@@ -124,8 +150,10 @@ function createEventPlaneMeter(reader, topN = 5) {
|
|
|
124
150
|
const window = diffEventPlane(baseline, current, topN);
|
|
125
151
|
baseline = {
|
|
126
152
|
children: new Map(current.children.map((s) => [s.childId, s])),
|
|
153
|
+
categories: new Map(current.categories.map((c) => [c.category, c])),
|
|
127
154
|
crossNodeDelivered: current.crossNodeDelivered,
|
|
128
|
-
crossNodeSuppressed: current.crossNodeSuppressed
|
|
155
|
+
crossNodeSuppressed: current.crossNodeSuppressed,
|
|
156
|
+
categoryOverflow: current.categoryOverflow
|
|
129
157
|
};
|
|
130
158
|
return window;
|
|
131
159
|
} };
|
|
@@ -149,7 +177,9 @@ function createEventPlaneMeter(reader, topN = 5) {
|
|
|
149
177
|
function formatEventPlane(window) {
|
|
150
178
|
if (window === void 0) return "";
|
|
151
179
|
const top = window.top.map((d) => `${d.childId}:${d.sent}/${d.suppressed}`).join(",");
|
|
152
|
-
|
|
180
|
+
const cats = window.topCategories.map((c) => `${c.category}:${c.broadcasts}/${c.wanted}`).join(",");
|
|
181
|
+
const orphans = window.topOrphans.map((c) => `${c.category}:${c.broadcasts}`).join(",");
|
|
182
|
+
return ` fanoutMode=uds:${window.udsMode}/cross-node:${window.crossNodeMode} children=${window.childrenConnected} childrenUndeclared=${window.childrenUndeclared} fanoutSent=${window.fanoutSent} fanoutSuppressed=${window.fanoutSuppressed} crossNodeDelivered=${window.crossNodeDelivered} crossNodeSuppressed=${window.crossNodeSuppressed} fanoutEvents=${window.categoryBroadcasts} fanoutOrphans=${window.orphanBroadcasts} fanoutCatsDropped=${window.categoryOverflow}` + (top.length === 0 ? "" : ` fanoutTop=${top}`) + (cats.length === 0 ? "" : ` fanoutCats=${cats}`) + (orphans.length === 0 ? "" : ` fanoutOrphanTop=${orphans}`);
|
|
153
183
|
}
|
|
154
184
|
//#endregion
|
|
155
185
|
//#region src/kernel/transport/cap-action-census.ts
|
|
@@ -7107,6 +7137,12 @@ var LocalChildRegistry = class {
|
|
|
7107
7137
|
fanoutMode = readFanoutMode();
|
|
7108
7138
|
/** Per-child event fan-out counters (sent / suppressed). */
|
|
7109
7139
|
childEventStats = /* @__PURE__ */ new Map();
|
|
7140
|
+
/** D467: per-category production census (cumulative). See {@link CategoryFanoutSample}. */
|
|
7141
|
+
categoryStats = /* @__PURE__ */ new Map();
|
|
7142
|
+
/** Broadcasts whose category did not fit in {@link CATEGORY_CENSUS_MAX}. */
|
|
7143
|
+
categoryOverflow = 0;
|
|
7144
|
+
/** One INFO line the first time the census cap trips, never per event. */
|
|
7145
|
+
categoryOverflowLogged = false;
|
|
7110
7146
|
/** Last pattern-set string logged per child, to dedup the INFO line. */
|
|
7111
7147
|
loggedPatternSet = /* @__PURE__ */ new Map();
|
|
7112
7148
|
/**
|
|
@@ -7162,6 +7198,25 @@ var LocalChildRegistry = class {
|
|
|
7162
7198
|
};
|
|
7163
7199
|
}
|
|
7164
7200
|
/**
|
|
7201
|
+
* D467: every category this node has broadcast, with its emit count and how
|
|
7202
|
+
* many child subscriptions matched. Cumulative for the life of the process —
|
|
7203
|
+
* the heartbeat differences it, the same discipline as the per-child
|
|
7204
|
+
* counters. Largest `broadcasts` first.
|
|
7205
|
+
*/
|
|
7206
|
+
categoryFanout() {
|
|
7207
|
+
const out = [];
|
|
7208
|
+
for (const [category, s] of this.categoryStats) out.push({
|
|
7209
|
+
category,
|
|
7210
|
+
broadcasts: s.broadcasts,
|
|
7211
|
+
wanted: s.wanted
|
|
7212
|
+
});
|
|
7213
|
+
return out.toSorted((a, b) => b.broadcasts - a.broadcasts || a.category.localeCompare(b.category));
|
|
7214
|
+
}
|
|
7215
|
+
/** Broadcasts the census could not attribute because it was full (cumulative). */
|
|
7216
|
+
categoryCensusOverflow() {
|
|
7217
|
+
return this.categoryOverflow;
|
|
7218
|
+
}
|
|
7219
|
+
/**
|
|
7165
7220
|
* This child's socket traffic — bytes and messages, both directions, split by
|
|
7166
7221
|
* frame kind — or `null` when the child is gone or its channel keeps no
|
|
7167
7222
|
* counters (an in-process or test transport).
|
|
@@ -7252,6 +7307,17 @@ var LocalChildRegistry = class {
|
|
|
7252
7307
|
*/
|
|
7253
7308
|
childWantsEvent(entry, category) {
|
|
7254
7309
|
if (this.fanoutMode === "broadcast") return true;
|
|
7310
|
+
return this.childSubscribesTo(entry, category);
|
|
7311
|
+
}
|
|
7312
|
+
/**
|
|
7313
|
+
* The SUBSCRIPTION answer alone, with no fan-out-mode override.
|
|
7314
|
+
*
|
|
7315
|
+
* {@link childWantsEvent} answers "will this child be sent the event", which
|
|
7316
|
+
* under `broadcast` is `true` for everything and therefore cannot say whether
|
|
7317
|
+
* anyone actually asked. The D467 census needs the second question, so the
|
|
7318
|
+
* two are separate functions and `broadcast` mode overrides only the first.
|
|
7319
|
+
*/
|
|
7320
|
+
childSubscribesTo(entry, category) {
|
|
7255
7321
|
if (entry.eventPatterns === null) return true;
|
|
7256
7322
|
return entry.eventPatterns.some((p) => matchesPattern(p, category));
|
|
7257
7323
|
}
|
|
@@ -7553,8 +7619,10 @@ var LocalChildRegistry = class {
|
|
|
7553
7619
|
event,
|
|
7554
7620
|
sourceNodeId
|
|
7555
7621
|
};
|
|
7622
|
+
let subscribed = 0;
|
|
7556
7623
|
for (const entry of this.children.values()) {
|
|
7557
7624
|
if (entry.childId === exceptChildId) continue;
|
|
7625
|
+
if (this.childSubscribesTo(entry, event.category)) subscribed += 1;
|
|
7558
7626
|
const wants = this.childWantsEvent(entry, event.category);
|
|
7559
7627
|
const stats = this.statsFor(entry.childId);
|
|
7560
7628
|
if (!wants) stats.suppressed++;
|
|
@@ -7563,6 +7631,34 @@ var LocalChildRegistry = class {
|
|
|
7563
7631
|
entry.channel.emit(msg);
|
|
7564
7632
|
}
|
|
7565
7633
|
}
|
|
7634
|
+
this.recordCategoryBroadcast(event.category, subscribed);
|
|
7635
|
+
}
|
|
7636
|
+
/**
|
|
7637
|
+
* Record one PRODUCED event against its category. Called once per broadcast,
|
|
7638
|
+
* outside the per-child loop, because production is what the census measures.
|
|
7639
|
+
*/
|
|
7640
|
+
recordCategoryBroadcast(category, subscribed) {
|
|
7641
|
+
const existing = this.categoryStats.get(category);
|
|
7642
|
+
if (existing !== void 0) {
|
|
7643
|
+
existing.broadcasts += 1;
|
|
7644
|
+
existing.wanted += subscribed;
|
|
7645
|
+
return;
|
|
7646
|
+
}
|
|
7647
|
+
if (this.categoryStats.size >= 256) {
|
|
7648
|
+
this.categoryOverflow += 1;
|
|
7649
|
+
if (!this.categoryOverflowLogged) {
|
|
7650
|
+
this.categoryOverflowLogged = true;
|
|
7651
|
+
this.logger?.info("event category census full — further new categories are unattributed", {
|
|
7652
|
+
max: 256,
|
|
7653
|
+
firstDropped: category
|
|
7654
|
+
});
|
|
7655
|
+
}
|
|
7656
|
+
return;
|
|
7657
|
+
}
|
|
7658
|
+
this.categoryStats.set(category, {
|
|
7659
|
+
broadcasts: 1,
|
|
7660
|
+
wanted: subscribed
|
|
7661
|
+
});
|
|
7566
7662
|
}
|
|
7567
7663
|
/**
|
|
7568
7664
|
* E2: Send a `set-log-level` control message to a specific child.
|
|
@@ -59,7 +59,9 @@ function createEventPlaneReader(sources) {
|
|
|
59
59
|
childrenConnected: listed.length,
|
|
60
60
|
childrenUndeclared: registry.undeclaredChildCount(),
|
|
61
61
|
crossNodeDelivered: crossNode?.delivered ?? 0,
|
|
62
|
-
crossNodeSuppressed: crossNode?.suppressed ?? 0
|
|
62
|
+
crossNodeSuppressed: crossNode?.suppressed ?? 0,
|
|
63
|
+
categories: registry.categoryFanout(),
|
|
64
|
+
categoryOverflow: registry.categoryCensusOverflow()
|
|
63
65
|
};
|
|
64
66
|
} catch {
|
|
65
67
|
return;
|
|
@@ -79,8 +81,10 @@ var EVENT_PLANE_TOP_N = 5;
|
|
|
79
81
|
/** A process that has never read a plane. */
|
|
80
82
|
var EMPTY_EVENT_PLANE_BASELINE = {
|
|
81
83
|
children: /* @__PURE__ */ new Map(),
|
|
84
|
+
categories: /* @__PURE__ */ new Map(),
|
|
82
85
|
crossNodeDelivered: 0,
|
|
83
|
-
crossNodeSuppressed: 0
|
|
86
|
+
crossNodeSuppressed: 0,
|
|
87
|
+
categoryOverflow: 0
|
|
84
88
|
};
|
|
85
89
|
/**
|
|
86
90
|
* Subtract the previous cumulative reading from the current one.
|
|
@@ -107,6 +111,23 @@ function diffEventPlane(baseline, current, topN = 5) {
|
|
|
107
111
|
suppressed
|
|
108
112
|
});
|
|
109
113
|
}
|
|
114
|
+
const categoryDeltas = [];
|
|
115
|
+
let categoryBroadcasts = 0;
|
|
116
|
+
let orphanBroadcasts = 0;
|
|
117
|
+
for (const sample of current.categories) {
|
|
118
|
+
const before = baseline.categories.get(sample.category);
|
|
119
|
+
const broadcasts = Math.max(0, sample.broadcasts - (before?.broadcasts ?? 0));
|
|
120
|
+
if (broadcasts === 0) continue;
|
|
121
|
+
const wanted = Math.max(0, sample.wanted - (before?.wanted ?? 0));
|
|
122
|
+
categoryBroadcasts += broadcasts;
|
|
123
|
+
if (wanted === 0) orphanBroadcasts += broadcasts;
|
|
124
|
+
categoryDeltas.push({
|
|
125
|
+
category: sample.category,
|
|
126
|
+
broadcasts,
|
|
127
|
+
wanted
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const byBroadcasts = categoryDeltas.toSorted((a, b) => b.broadcasts - a.broadcasts || a.category.localeCompare(b.category));
|
|
110
131
|
return {
|
|
111
132
|
udsMode: current.udsMode,
|
|
112
133
|
crossNodeMode: current.crossNodeMode,
|
|
@@ -116,7 +137,12 @@ function diffEventPlane(baseline, current, topN = 5) {
|
|
|
116
137
|
fanoutSuppressed,
|
|
117
138
|
crossNodeDelivered: Math.max(0, current.crossNodeDelivered - baseline.crossNodeDelivered),
|
|
118
139
|
crossNodeSuppressed: Math.max(0, current.crossNodeSuppressed - baseline.crossNodeSuppressed),
|
|
119
|
-
top: deltas.toSorted((a, b) => b.sent - a.sent || a.childId.localeCompare(b.childId)).slice(0, topN)
|
|
140
|
+
top: deltas.toSorted((a, b) => b.sent - a.sent || a.childId.localeCompare(b.childId)).slice(0, topN),
|
|
141
|
+
categoryBroadcasts,
|
|
142
|
+
orphanBroadcasts,
|
|
143
|
+
topCategories: byBroadcasts.slice(0, topN),
|
|
144
|
+
topOrphans: byBroadcasts.filter((c) => c.wanted === 0).slice(0, topN),
|
|
145
|
+
categoryOverflow: Math.max(0, current.categoryOverflow - baseline.categoryOverflow)
|
|
120
146
|
};
|
|
121
147
|
}
|
|
122
148
|
function createEventPlaneMeter(reader, topN = 5) {
|
|
@@ -127,8 +153,10 @@ function createEventPlaneMeter(reader, topN = 5) {
|
|
|
127
153
|
const window = diffEventPlane(baseline, current, topN);
|
|
128
154
|
baseline = {
|
|
129
155
|
children: new Map(current.children.map((s) => [s.childId, s])),
|
|
156
|
+
categories: new Map(current.categories.map((c) => [c.category, c])),
|
|
130
157
|
crossNodeDelivered: current.crossNodeDelivered,
|
|
131
|
-
crossNodeSuppressed: current.crossNodeSuppressed
|
|
158
|
+
crossNodeSuppressed: current.crossNodeSuppressed,
|
|
159
|
+
categoryOverflow: current.categoryOverflow
|
|
132
160
|
};
|
|
133
161
|
return window;
|
|
134
162
|
} };
|
|
@@ -152,7 +180,9 @@ function createEventPlaneMeter(reader, topN = 5) {
|
|
|
152
180
|
function formatEventPlane(window) {
|
|
153
181
|
if (window === void 0) return "";
|
|
154
182
|
const top = window.top.map((d) => `${d.childId}:${d.sent}/${d.suppressed}`).join(",");
|
|
155
|
-
|
|
183
|
+
const cats = window.topCategories.map((c) => `${c.category}:${c.broadcasts}/${c.wanted}`).join(",");
|
|
184
|
+
const orphans = window.topOrphans.map((c) => `${c.category}:${c.broadcasts}`).join(",");
|
|
185
|
+
return ` fanoutMode=uds:${window.udsMode}/cross-node:${window.crossNodeMode} children=${window.childrenConnected} childrenUndeclared=${window.childrenUndeclared} fanoutSent=${window.fanoutSent} fanoutSuppressed=${window.fanoutSuppressed} crossNodeDelivered=${window.crossNodeDelivered} crossNodeSuppressed=${window.crossNodeSuppressed} fanoutEvents=${window.categoryBroadcasts} fanoutOrphans=${window.orphanBroadcasts} fanoutCatsDropped=${window.categoryOverflow}` + (top.length === 0 ? "" : ` fanoutTop=${top}`) + (cats.length === 0 ? "" : ` fanoutCats=${cats}`) + (orphans.length === 0 ? "" : ` fanoutOrphanTop=${orphans}`);
|
|
156
186
|
}
|
|
157
187
|
//#endregion
|
|
158
188
|
//#region src/kernel/transport/cap-action-census.ts
|
|
@@ -7110,6 +7140,12 @@ var LocalChildRegistry = class {
|
|
|
7110
7140
|
fanoutMode = readFanoutMode();
|
|
7111
7141
|
/** Per-child event fan-out counters (sent / suppressed). */
|
|
7112
7142
|
childEventStats = /* @__PURE__ */ new Map();
|
|
7143
|
+
/** D467: per-category production census (cumulative). See {@link CategoryFanoutSample}. */
|
|
7144
|
+
categoryStats = /* @__PURE__ */ new Map();
|
|
7145
|
+
/** Broadcasts whose category did not fit in {@link CATEGORY_CENSUS_MAX}. */
|
|
7146
|
+
categoryOverflow = 0;
|
|
7147
|
+
/** One INFO line the first time the census cap trips, never per event. */
|
|
7148
|
+
categoryOverflowLogged = false;
|
|
7113
7149
|
/** Last pattern-set string logged per child, to dedup the INFO line. */
|
|
7114
7150
|
loggedPatternSet = /* @__PURE__ */ new Map();
|
|
7115
7151
|
/**
|
|
@@ -7165,6 +7201,25 @@ var LocalChildRegistry = class {
|
|
|
7165
7201
|
};
|
|
7166
7202
|
}
|
|
7167
7203
|
/**
|
|
7204
|
+
* D467: every category this node has broadcast, with its emit count and how
|
|
7205
|
+
* many child subscriptions matched. Cumulative for the life of the process —
|
|
7206
|
+
* the heartbeat differences it, the same discipline as the per-child
|
|
7207
|
+
* counters. Largest `broadcasts` first.
|
|
7208
|
+
*/
|
|
7209
|
+
categoryFanout() {
|
|
7210
|
+
const out = [];
|
|
7211
|
+
for (const [category, s] of this.categoryStats) out.push({
|
|
7212
|
+
category,
|
|
7213
|
+
broadcasts: s.broadcasts,
|
|
7214
|
+
wanted: s.wanted
|
|
7215
|
+
});
|
|
7216
|
+
return out.toSorted((a, b) => b.broadcasts - a.broadcasts || a.category.localeCompare(b.category));
|
|
7217
|
+
}
|
|
7218
|
+
/** Broadcasts the census could not attribute because it was full (cumulative). */
|
|
7219
|
+
categoryCensusOverflow() {
|
|
7220
|
+
return this.categoryOverflow;
|
|
7221
|
+
}
|
|
7222
|
+
/**
|
|
7168
7223
|
* This child's socket traffic — bytes and messages, both directions, split by
|
|
7169
7224
|
* frame kind — or `null` when the child is gone or its channel keeps no
|
|
7170
7225
|
* counters (an in-process or test transport).
|
|
@@ -7255,6 +7310,17 @@ var LocalChildRegistry = class {
|
|
|
7255
7310
|
*/
|
|
7256
7311
|
childWantsEvent(entry, category) {
|
|
7257
7312
|
if (this.fanoutMode === "broadcast") return true;
|
|
7313
|
+
return this.childSubscribesTo(entry, category);
|
|
7314
|
+
}
|
|
7315
|
+
/**
|
|
7316
|
+
* The SUBSCRIPTION answer alone, with no fan-out-mode override.
|
|
7317
|
+
*
|
|
7318
|
+
* {@link childWantsEvent} answers "will this child be sent the event", which
|
|
7319
|
+
* under `broadcast` is `true` for everything and therefore cannot say whether
|
|
7320
|
+
* anyone actually asked. The D467 census needs the second question, so the
|
|
7321
|
+
* two are separate functions and `broadcast` mode overrides only the first.
|
|
7322
|
+
*/
|
|
7323
|
+
childSubscribesTo(entry, category) {
|
|
7258
7324
|
if (entry.eventPatterns === null) return true;
|
|
7259
7325
|
return entry.eventPatterns.some((p) => matchesPattern(p, category));
|
|
7260
7326
|
}
|
|
@@ -7556,8 +7622,10 @@ var LocalChildRegistry = class {
|
|
|
7556
7622
|
event,
|
|
7557
7623
|
sourceNodeId
|
|
7558
7624
|
};
|
|
7625
|
+
let subscribed = 0;
|
|
7559
7626
|
for (const entry of this.children.values()) {
|
|
7560
7627
|
if (entry.childId === exceptChildId) continue;
|
|
7628
|
+
if (this.childSubscribesTo(entry, event.category)) subscribed += 1;
|
|
7561
7629
|
const wants = this.childWantsEvent(entry, event.category);
|
|
7562
7630
|
const stats = this.statsFor(entry.childId);
|
|
7563
7631
|
if (!wants) stats.suppressed++;
|
|
@@ -7566,6 +7634,34 @@ var LocalChildRegistry = class {
|
|
|
7566
7634
|
entry.channel.emit(msg);
|
|
7567
7635
|
}
|
|
7568
7636
|
}
|
|
7637
|
+
this.recordCategoryBroadcast(event.category, subscribed);
|
|
7638
|
+
}
|
|
7639
|
+
/**
|
|
7640
|
+
* Record one PRODUCED event against its category. Called once per broadcast,
|
|
7641
|
+
* outside the per-child loop, because production is what the census measures.
|
|
7642
|
+
*/
|
|
7643
|
+
recordCategoryBroadcast(category, subscribed) {
|
|
7644
|
+
const existing = this.categoryStats.get(category);
|
|
7645
|
+
if (existing !== void 0) {
|
|
7646
|
+
existing.broadcasts += 1;
|
|
7647
|
+
existing.wanted += subscribed;
|
|
7648
|
+
return;
|
|
7649
|
+
}
|
|
7650
|
+
if (this.categoryStats.size >= 256) {
|
|
7651
|
+
this.categoryOverflow += 1;
|
|
7652
|
+
if (!this.categoryOverflowLogged) {
|
|
7653
|
+
this.categoryOverflowLogged = true;
|
|
7654
|
+
this.logger?.info("event category census full — further new categories are unattributed", {
|
|
7655
|
+
max: 256,
|
|
7656
|
+
firstDropped: category
|
|
7657
|
+
});
|
|
7658
|
+
}
|
|
7659
|
+
return;
|
|
7660
|
+
}
|
|
7661
|
+
this.categoryStats.set(category, {
|
|
7662
|
+
broadcasts: 1,
|
|
7663
|
+
wanted: subscribed
|
|
7664
|
+
});
|
|
7569
7665
|
}
|
|
7570
7666
|
/**
|
|
7571
7667
|
* E2: Send a `set-log-level` control message to a specific child.
|