@camstack/system 1.2.102 → 1.2.103
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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.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/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +150 -22
- package/dist/builtins/snapshot/index.mjs +150 -22
- package/dist/builtins/snapshot/snapshot-cache.d.ts +63 -0
- package/dist/builtins/snapshot/snapshot-resize.d.ts +33 -1
- package/dist/builtins/snapshot/snapshot.addon.d.ts +6 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-3wh5LCad.mjs → dist-DHKXob1m.mjs} +124 -20
- package/dist/{dist-9sC0wZw6.js → dist-qnwy_iWM.js} +124 -20
- package/dist/index.js +264 -62
- package/dist/index.mjs +264 -62
- package/dist/kernel/config-schema.d.ts +14 -10
- package/dist/kernel/heap-watch.d.ts +39 -7
- package/dist/kernel/moleculer/process-service.d.ts +34 -0
- package/dist/logging/log-manager.d.ts +7 -5
- package/dist/logging/partitioned-log-buffer.d.ts +95 -16
- package/package.json +1 -1
|
@@ -1,14 +1,52 @@
|
|
|
1
1
|
import { LogEntry, LogFilter, LogLevel } from '@camstack/types';
|
|
2
|
+
/**
|
|
3
|
+
* Default ceiling on retained entries across every bucket.
|
|
4
|
+
*
|
|
5
|
+
* This used to be `null` — no aggregate bound at all — and the only thing
|
|
6
|
+
* holding hub-main's log memory was `perAddonCapacity x buckets`, a product
|
|
7
|
+
* that grows with the roster. Measured on the live cluster: 37 forked runners
|
|
8
|
+
* on the hub plus its in-process builtins, and hub-main additionally ingests
|
|
9
|
+
* every line from both agents, so ~60 buckets is the ordinary case and
|
|
10
|
+
* 60 x 5000 = 300k retained entries.
|
|
11
|
+
*
|
|
12
|
+
* A realistic entry (timestamp, level, scope, ~70-char message, three tags, no
|
|
13
|
+
* `meta`) measures **~306 B retained** in V8. So the old unbounded case is
|
|
14
|
+
* ~92MB of a hub process that is being asked to fit inside a cgroup with a
|
|
15
|
+
* neighbour to protect, and 50_000 is **~15MB**.
|
|
16
|
+
*
|
|
17
|
+
* 50_000 across a ~60-bucket roster is ~830 entries per addon on average while
|
|
18
|
+
* still letting a single chatty addon hold its full 5000-entry ring — the
|
|
19
|
+
* partitioning is not weakened, only the total is held.
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_MAX_TOTAL_LOG_ENTRIES = 50000;
|
|
22
|
+
/**
|
|
23
|
+
* Default ceiling on the NUMBER of buckets.
|
|
24
|
+
*
|
|
25
|
+
* Buckets were created on first write and never removed, so every addon id
|
|
26
|
+
* that ever logged — a removed addon, a renamed one, a one-off worker id —
|
|
27
|
+
* kept a ring alive for the life of the process. {@link DEFAULT_MAX_TOTAL_LOG_ENTRIES}
|
|
28
|
+
* alone does not fix that: the total sweep may only discard entries at or below
|
|
29
|
+
* `pruneLevel`, so a dead addon's 5000 `info` lines are untouchable by it.
|
|
30
|
+
*
|
|
31
|
+
* 128 is ~2x the ~60-bucket live roster, so no real deployment ever evicts on
|
|
32
|
+
* bucket count alone; it exists to make an unbounded id space impossible.
|
|
33
|
+
*/
|
|
34
|
+
export declare const DEFAULT_MAX_LOG_BUCKETS = 128;
|
|
2
35
|
export interface PartitionedLogBufferOptions {
|
|
3
36
|
/** Hard ring size for EACH addon's bucket. */
|
|
4
37
|
readonly perAddonCapacity?: number;
|
|
5
38
|
/**
|
|
6
|
-
* Soft ceiling on entries across all buckets. `null`
|
|
7
|
-
*
|
|
39
|
+
* Soft ceiling on entries across all buckets. `null` means the only bound is
|
|
40
|
+
* the per-addon rings. Defaults to {@link DEFAULT_MAX_TOTAL_LOG_ENTRIES}.
|
|
8
41
|
*/
|
|
9
42
|
readonly maxTotalEntries?: number | null;
|
|
10
43
|
/** Highest severity the total-cap sweep may discard. Default `debug`. */
|
|
11
44
|
readonly pruneLevel?: LogLevel;
|
|
45
|
+
/**
|
|
46
|
+
* Hard ceiling on the number of buckets. `null` means unbounded. Defaults to
|
|
47
|
+
* {@link DEFAULT_MAX_LOG_BUCKETS}.
|
|
48
|
+
*/
|
|
49
|
+
readonly maxBuckets?: number | null;
|
|
12
50
|
}
|
|
13
51
|
/**
|
|
14
52
|
* A log buffer partitioned into one fixed-capacity {@link LogRingBuffer} per
|
|
@@ -19,32 +57,48 @@ export interface PartitionedLogBufferOptions {
|
|
|
19
57
|
* Drop-in for {@link LogRingBuffer} from {@link LogManager}'s perspective —
|
|
20
58
|
* exposes the same `push` / `getAll` / `query` / `clear` surface.
|
|
21
59
|
*
|
|
22
|
-
* ##
|
|
60
|
+
* ## Three bounds, doing different jobs
|
|
23
61
|
*
|
|
24
62
|
* **`perAddonCapacity`** is a HARD bound: each bucket is a fixed-size ring, so
|
|
25
63
|
* total memory can never exceed `perAddonCapacity × buckets` whatever else
|
|
26
64
|
* happens. This is the safety net.
|
|
27
65
|
*
|
|
28
|
-
* **`maxTotalEntries`**
|
|
29
|
-
* the hard bound scales with addon count —
|
|
30
|
-
* entries on
|
|
31
|
-
* entries at or below `pruneLevel` (default `debug`), oldest first, from the
|
|
66
|
+
* **`maxTotalEntries`** targets the total across all buckets, and it exists because
|
|
67
|
+
* the hard bound scales with addon count — 60 buckets × 5000 was 300k retained
|
|
68
|
+
* entries on the live hub. When the total is exceeded, the sweep first discards
|
|
69
|
+
* only entries at or below `pruneLevel` (default `debug`), oldest first, from the
|
|
32
70
|
* largest buckets first.
|
|
33
71
|
*
|
|
34
|
-
* It is deliberately soft. Meeting a total cap by discarding
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* that can evict an error is not a feature.
|
|
72
|
+
* It is deliberately soft ON A LIVE BUCKET. Meeting a total cap by discarding
|
|
73
|
+
* `warn` and `error` from an addon that is still logging would trade the entries
|
|
74
|
+
* that explain an incident for entries about frame timings. A cap that can evict
|
|
75
|
+
* a live addon's error is not a feature.
|
|
39
76
|
*
|
|
40
77
|
* Largest-bucket-first because the noisy addon is the one creating the pressure —
|
|
41
78
|
* the same principle that motivated partitioning in the first place.
|
|
79
|
+
*
|
|
80
|
+
* **`maxBuckets`, and bucket eviction, are the hard backstop.** Pruning alone
|
|
81
|
+
* cannot bound this structure: it may only discard at or below `pruneLevel`, so
|
|
82
|
+
* a bucket full of `info` from an addon that no longer exists was immortal, and
|
|
83
|
+
* the roster the product scales with is not fixed. When pruning cannot reach the
|
|
84
|
+
* target — or the bucket count exceeds `maxBuckets` — whole buckets are dropped
|
|
85
|
+
* **least-recently-WRITTEN first**, and never the bucket currently being written
|
|
86
|
+
* to. That is precisely the dead addon's bucket, and it is precisely not the
|
|
87
|
+
* bucket of the addon whose incident is in progress.
|
|
42
88
|
*/
|
|
43
89
|
export declare class PartitionedLogBuffer {
|
|
44
90
|
private readonly buffers;
|
|
91
|
+
/**
|
|
92
|
+
* Bucket key → the write ordinal of its last `push`. A monotonic counter, not
|
|
93
|
+
* a clock: eviction order must follow the order writes actually happened, and
|
|
94
|
+
* a wall clock that steps backwards would pick the wrong victim.
|
|
95
|
+
*/
|
|
96
|
+
private readonly lastWriteSeq;
|
|
97
|
+
private writeSeq;
|
|
45
98
|
private perAddonCapacity;
|
|
46
99
|
private maxTotalEntries;
|
|
47
100
|
private pruneLevel;
|
|
101
|
+
private maxBuckets;
|
|
48
102
|
/** Running total, so `push` does not walk every bucket to decide. */
|
|
49
103
|
private totalEntries;
|
|
50
104
|
constructor(perAddonCapacity?: number, options?: PartitionedLogBufferOptions);
|
|
@@ -59,18 +113,40 @@ export declare class PartitionedLogBuffer {
|
|
|
59
113
|
readonly perAddonCapacity?: number;
|
|
60
114
|
readonly maxTotalEntries?: number | null;
|
|
61
115
|
readonly pruneLevel?: LogLevel;
|
|
116
|
+
readonly maxBuckets?: number | null;
|
|
62
117
|
}): void;
|
|
63
118
|
private bufferFor;
|
|
64
119
|
push(entry: LogEntry): void;
|
|
65
120
|
/**
|
|
66
|
-
* Bring the total down to `PRUNE_TARGET_RATIO × cap
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
121
|
+
* Bring the total down to `PRUNE_TARGET_RATIO × cap`.
|
|
122
|
+
*
|
|
123
|
+
* Two passes, in this order and no other: first discard prunable entries,
|
|
124
|
+
* largest bucket first, which is free of information cost. Only if that
|
|
125
|
+
* cannot reach the target does the second pass drop WHOLE buckets,
|
|
126
|
+
* least-recently-written first — the bound has to hold even when every
|
|
127
|
+
* retained entry is above `pruneLevel`, and a bucket nobody has written to is
|
|
128
|
+
* the cheapest thing in the structure to lose.
|
|
70
129
|
*/
|
|
71
130
|
private enforceTotalCap;
|
|
131
|
+
/**
|
|
132
|
+
* Drop whole buckets, least-recently-WRITTEN first, until the total is at or
|
|
133
|
+
* below `target`. Never touches `protectedKey` (the bucket being written to
|
|
134
|
+
* right now) and always leaves at least one bucket standing.
|
|
135
|
+
*/
|
|
136
|
+
private evictBucketsUntil;
|
|
137
|
+
/** Drop exactly `count` buckets, least-recently-written first. */
|
|
138
|
+
private evictBuckets;
|
|
139
|
+
/** Bucket keys ordered least-recently-written first, excluding `protectedKey`.
|
|
140
|
+
* A bucket with no recorded write sorts first — it can only be a leftover. */
|
|
141
|
+
private evictionOrder;
|
|
142
|
+
/** Remove a bucket and everything that indexes it. An empty ring left behind
|
|
143
|
+
* is the leak this class shipped with — the map only ever grew. */
|
|
144
|
+
private dropBucket;
|
|
72
145
|
/** Entries retained across every bucket. */
|
|
73
146
|
size(): number;
|
|
147
|
+
/** Buckets currently retained. Observability for the aggregate bound — an
|
|
148
|
+
* addon roster that only ever grows is what this class had to stop doing. */
|
|
149
|
+
bucketCount(): number;
|
|
74
150
|
/** Every retained entry across all buckets, newest-first (mirrors
|
|
75
151
|
* {@link LogRingBuffer.getAll}). Used to replay history to a destination. */
|
|
76
152
|
getAll(): LogEntry[];
|
|
@@ -83,6 +159,9 @@ export declare class PartitionedLogBuffer {
|
|
|
83
159
|
*/
|
|
84
160
|
query(filter: LogFilter): LogEntry[];
|
|
85
161
|
clear(filter?: LogFilter): number;
|
|
162
|
+
/** An emptied bucket is removed rather than left as an empty ring: the map
|
|
163
|
+
* used to grow with every addon id that ever logged and never shrink. */
|
|
164
|
+
private dropIfEmpty;
|
|
86
165
|
/** The bucket key a filter pins to via `tags.addonId`, or null when the
|
|
87
166
|
* filter doesn't constrain the addon (→ scan all buckets). */
|
|
88
167
|
private pinnedAddonKey;
|