dsh-flash-mem-mon 0.1.4 → 0.1.7

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/pure.js ADDED
@@ -0,0 +1,66 @@
1
+ // Pure, dependency-free helpers extracted from index.ts so they can be unit
2
+ // tested without pulling in cordis / node HTTP / GC-entry types. Everything
3
+ // here is a pure function of its inputs.
4
+ export const DEFAULT_ALERT_CONFIG = {
5
+ memThresholdInfo: 256,
6
+ memThresholdWarning: 512,
7
+ memThresholdError: 1024,
8
+ memThresholdCritical: 1536,
9
+ memPollBase: 30000,
10
+ memPollMin: 2000,
11
+ gcThresholdInfo: 2,
12
+ gcThresholdWarning: 5,
13
+ gcThresholdError: 10,
14
+ memGrowthAlertPerMin: 1,
15
+ };
16
+ /**
17
+ * Classify a V8 GC `kind` bitmask as a full (major) cycle.
18
+ *
19
+ * V8 GC entry `kind` is a bitmask:
20
+ * 1 << 0 = Scavenge (minor / young-gen)
21
+ * 1 << 1 = MarkSweepCompact (major / full)
22
+ * 1 << 2 = IncrementalMarking
23
+ * 1 << 3 = ProcessWeakCallbacks
24
+ *
25
+ * In practice modern V8/Node reports a full MarkSweepCompact cycle through the
26
+ * IncrementalMarking (4) and ProcessWeakCallbacks (8) phases — the standalone
27
+ * "major" bit (2) is almost never emitted on its own. Classifying Full GC by
28
+ * `kind === 2` alone therefore misses (nearly) every real full collection.
29
+ * The robust test: a Full GC is any NON-scavenge cycle, i.e. the Scavenge bit
30
+ * (1) is NOT set. This captures 2, 4, 6, 8, 10, 12, 14 while excluding minor
31
+ * (1) scavenges.
32
+ */
33
+ export function isFullGc(kind) {
34
+ return (kind & 1) === 0 && kind > 0;
35
+ }
36
+ /**
37
+ * Linear-regression slope of `rss` over the last `tailCount` samples, expressed
38
+ * in MB/min. Returns 0 when there are not enough samples (fewer than 3) or the
39
+ * tail time span is not positive.
40
+ *
41
+ * The average sample interval MUST be derived from the tail's OWN first/last
42
+ * timestamps, NOT from the whole buffer span (see index.ts summary()). Using a
43
+ * larger span inflates the interval, deflates samplesPerMin, and under-reports
44
+ * MB/min — a monotonic-leak false negative.
45
+ */
46
+ export function rssSlopePerMin(samples, tailCount = 20) {
47
+ const tail = samples.slice(-tailCount);
48
+ if (tail.length < 3)
49
+ return 0;
50
+ const n = tail.length;
51
+ let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
52
+ for (let i = 0; i < n; i++) {
53
+ sumX += i;
54
+ sumY += tail[i].rss;
55
+ sumXY += i * tail[i].rss;
56
+ sumXX += i * i;
57
+ }
58
+ // slope = bytes per sample-index step.
59
+ const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
60
+ const tailSpanMs = Math.max(tail[tail.length - 1].ts - tail[0].ts, 0);
61
+ if (tailSpanMs <= 0)
62
+ return 0;
63
+ const avgIntervalMs = tailSpanMs / (n - 1);
64
+ const samplesPerMinute = 60000 / avgIntervalMs;
65
+ return Math.round((slope * samplesPerMinute / 1048576) * 100) / 100;
66
+ }