@naturalcycles/backend-lib 9.84.0 → 9.84.1
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.
|
@@ -10,13 +10,16 @@ import type { Integer, NonNegativeInteger, NumberOfMilliseconds, NumberOfPercent
|
|
|
10
10
|
* expect p50/mean near 0 on a healthy service, instead of the ~`resolution` baseline
|
|
11
11
|
* reported by older interval-based sampling.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* Beware of CPU-throttled processes (e.g. idle CloudRun instances with request-based billing):
|
|
14
|
+
* the interval timer fires late there, and the time the process spent runnable-but-unscheduled
|
|
15
|
+
* is recorded as event loop delay. Use `intervalMs` to detect such intervals.
|
|
14
16
|
*/
|
|
15
17
|
export declare class EventLoopMonitor {
|
|
16
18
|
constructor(cfg?: EventLoopMonitorCfg);
|
|
17
19
|
private interval;
|
|
18
20
|
private eld;
|
|
19
21
|
private lastElu;
|
|
22
|
+
private lastReportedAt;
|
|
20
23
|
/**
|
|
21
24
|
* Undefined until the first interval has completed.
|
|
22
25
|
*/
|
|
@@ -53,15 +56,15 @@ export interface EventLoopStats {
|
|
|
53
56
|
*/
|
|
54
57
|
elu: NumberOfPercent;
|
|
55
58
|
/**
|
|
56
|
-
* Number of times gc ran during the last
|
|
59
|
+
* Number of times gc ran during the last interval
|
|
57
60
|
*/
|
|
58
61
|
gcCount: NonNegativeInteger;
|
|
59
62
|
/**
|
|
60
|
-
* Total time spent on gc during the last
|
|
63
|
+
* Total time spent on gc during the last interval
|
|
61
64
|
*/
|
|
62
65
|
gcTotalTime: NumberOfMilliseconds;
|
|
63
66
|
/**
|
|
64
|
-
* CPU time spent on GC in the last `
|
|
67
|
+
* CPU time spent on GC in the last interval, relative to `intervalMs`.
|
|
65
68
|
* In "basis points", 100 basis points == 1%.
|
|
66
69
|
* E.g 125 means 1.25% of CPU spent on GC.
|
|
67
70
|
* It's reported in BPS to be able to do more accurate aggregate reporting
|
|
@@ -69,4 +72,12 @@ export interface EventLoopStats {
|
|
|
69
72
|
* too "lossy")
|
|
70
73
|
*/
|
|
71
74
|
gcCPU: Integer;
|
|
75
|
+
/**
|
|
76
|
+
* Actual wall-clock duration of the interval covered by these stats.
|
|
77
|
+
* Normally equals `measureInterval`, but is longer when the timer fired late,
|
|
78
|
+
* e.g. on a CloudRun instance that was CPU-throttled while idle (request-based billing).
|
|
79
|
+
* An interval much longer than `measureInterval` means the delay stats are dominated
|
|
80
|
+
* by that throttling rather than by the application.
|
|
81
|
+
*/
|
|
82
|
+
intervalMs: NumberOfMilliseconds;
|
|
72
83
|
}
|
|
@@ -26,7 +26,9 @@ import { monitorEventLoopDelay, performance, PerformanceObserver } from 'node:pe
|
|
|
26
26
|
* expect p50/mean near 0 on a healthy service, instead of the ~`resolution` baseline
|
|
27
27
|
* reported by older interval-based sampling.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
29
|
+
* Beware of CPU-throttled processes (e.g. idle CloudRun instances with request-based billing):
|
|
30
|
+
* the interval timer fires late there, and the time the process spent runnable-but-unscheduled
|
|
31
|
+
* is recorded as event loop delay. Use `intervalMs` to detect such intervals.
|
|
30
32
|
*/
|
|
31
33
|
export class EventLoopMonitor {
|
|
32
34
|
constructor(cfg = {}) {
|
|
@@ -41,6 +43,7 @@ export class EventLoopMonitor {
|
|
|
41
43
|
this.eld = monitorEventLoopDelay(eldOptions);
|
|
42
44
|
this.eld.enable();
|
|
43
45
|
this.lastElu = performance.eventLoopUtilization();
|
|
46
|
+
this.lastReportedAt = performance.now();
|
|
44
47
|
this.po = new PerformanceObserver(list => {
|
|
45
48
|
for (const entry of list.getEntries()) {
|
|
46
49
|
this.gcCount++;
|
|
@@ -56,11 +59,17 @@ export class EventLoopMonitor {
|
|
|
56
59
|
const p99 = Math.round(eld.percentile(99) / 1e6);
|
|
57
60
|
const max = Math.round(eld.max / 1e6);
|
|
58
61
|
const mean = Math.round(eld.mean / 1e6);
|
|
62
|
+
// Actual wall-clock length of this interval, as opposed to the nominal `measureInterval`.
|
|
63
|
+
// Timers fire late when the process is starved of CPU, so this can be much longer.
|
|
64
|
+
// Floored at 1 to never divide by zero (only reachable with fake timers).
|
|
65
|
+
const now = performance.now();
|
|
66
|
+
const intervalMs = Math.max(1, Math.round(now - this.lastReportedAt));
|
|
67
|
+
this.lastReportedAt = now;
|
|
59
68
|
const currentElu = performance.eventLoopUtilization();
|
|
60
69
|
const deltaElu = performance.eventLoopUtilization(this.lastElu, currentElu);
|
|
61
70
|
this.lastElu = currentElu;
|
|
62
71
|
const elu = Math.round(deltaElu.utilization * 100);
|
|
63
|
-
const gcCPU = Math.round((gcTotalTime /
|
|
72
|
+
const gcCPU = Math.round((gcTotalTime / intervalMs) * 10_000);
|
|
64
73
|
this.lastStats = {
|
|
65
74
|
p50,
|
|
66
75
|
p90,
|
|
@@ -71,6 +80,7 @@ export class EventLoopMonitor {
|
|
|
71
80
|
gcCount,
|
|
72
81
|
gcTotalTime,
|
|
73
82
|
gcCPU,
|
|
83
|
+
intervalMs,
|
|
74
84
|
};
|
|
75
85
|
cfg.onStats?.(this.lastStats);
|
|
76
86
|
eld.reset();
|
|
@@ -81,6 +91,7 @@ export class EventLoopMonitor {
|
|
|
81
91
|
interval;
|
|
82
92
|
eld;
|
|
83
93
|
lastElu;
|
|
94
|
+
lastReportedAt;
|
|
84
95
|
/**
|
|
85
96
|
* Undefined until the first interval has completed.
|
|
86
97
|
*/
|
package/package.json
CHANGED
|
@@ -35,7 +35,9 @@ import type {
|
|
|
35
35
|
* expect p50/mean near 0 on a healthy service, instead of the ~`resolution` baseline
|
|
36
36
|
* reported by older interval-based sampling.
|
|
37
37
|
*
|
|
38
|
-
*
|
|
38
|
+
* Beware of CPU-throttled processes (e.g. idle CloudRun instances with request-based billing):
|
|
39
|
+
* the interval timer fires late there, and the time the process spent runnable-but-unscheduled
|
|
40
|
+
* is recorded as event loop delay. Use `intervalMs` to detect such intervals.
|
|
39
41
|
*/
|
|
40
42
|
export class EventLoopMonitor {
|
|
41
43
|
constructor(cfg: EventLoopMonitorCfg = {}) {
|
|
@@ -52,6 +54,7 @@ export class EventLoopMonitor {
|
|
|
52
54
|
this.eld.enable()
|
|
53
55
|
|
|
54
56
|
this.lastElu = performance.eventLoopUtilization()
|
|
57
|
+
this.lastReportedAt = performance.now()
|
|
55
58
|
|
|
56
59
|
this.po = new PerformanceObserver(list => {
|
|
57
60
|
for (const entry of list.getEntries()) {
|
|
@@ -71,12 +74,19 @@ export class EventLoopMonitor {
|
|
|
71
74
|
const max = Math.round(eld.max / 1e6)
|
|
72
75
|
const mean = Math.round(eld.mean / 1e6)
|
|
73
76
|
|
|
77
|
+
// Actual wall-clock length of this interval, as opposed to the nominal `measureInterval`.
|
|
78
|
+
// Timers fire late when the process is starved of CPU, so this can be much longer.
|
|
79
|
+
// Floored at 1 to never divide by zero (only reachable with fake timers).
|
|
80
|
+
const now = performance.now()
|
|
81
|
+
const intervalMs = Math.max(1, Math.round(now - this.lastReportedAt))
|
|
82
|
+
this.lastReportedAt = now
|
|
83
|
+
|
|
74
84
|
const currentElu = performance.eventLoopUtilization()
|
|
75
85
|
const deltaElu = performance.eventLoopUtilization(this.lastElu, currentElu)
|
|
76
86
|
this.lastElu = currentElu
|
|
77
87
|
|
|
78
88
|
const elu = Math.round(deltaElu.utilization * 100)
|
|
79
|
-
const gcCPU = Math.round((gcTotalTime /
|
|
89
|
+
const gcCPU = Math.round((gcTotalTime / intervalMs) * 10_000)
|
|
80
90
|
|
|
81
91
|
this.lastStats = {
|
|
82
92
|
p50,
|
|
@@ -88,6 +98,7 @@ export class EventLoopMonitor {
|
|
|
88
98
|
gcCount,
|
|
89
99
|
gcTotalTime,
|
|
90
100
|
gcCPU,
|
|
101
|
+
intervalMs,
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
cfg.onStats?.(this.lastStats)
|
|
@@ -101,6 +112,7 @@ export class EventLoopMonitor {
|
|
|
101
112
|
private interval: NodeJS.Timeout
|
|
102
113
|
private eld: ELDHistogram
|
|
103
114
|
private lastElu: EventLoopUtilization
|
|
115
|
+
private lastReportedAt: NumberOfMilliseconds
|
|
104
116
|
/**
|
|
105
117
|
* Undefined until the first interval has completed.
|
|
106
118
|
*/
|
|
@@ -146,15 +158,15 @@ export interface EventLoopStats {
|
|
|
146
158
|
*/
|
|
147
159
|
elu: NumberOfPercent
|
|
148
160
|
/**
|
|
149
|
-
* Number of times gc ran during the last
|
|
161
|
+
* Number of times gc ran during the last interval
|
|
150
162
|
*/
|
|
151
163
|
gcCount: NonNegativeInteger
|
|
152
164
|
/**
|
|
153
|
-
* Total time spent on gc during the last
|
|
165
|
+
* Total time spent on gc during the last interval
|
|
154
166
|
*/
|
|
155
167
|
gcTotalTime: NumberOfMilliseconds
|
|
156
168
|
/**
|
|
157
|
-
* CPU time spent on GC in the last `
|
|
169
|
+
* CPU time spent on GC in the last interval, relative to `intervalMs`.
|
|
158
170
|
* In "basis points", 100 basis points == 1%.
|
|
159
171
|
* E.g 125 means 1.25% of CPU spent on GC.
|
|
160
172
|
* It's reported in BPS to be able to do more accurate aggregate reporting
|
|
@@ -162,4 +174,12 @@ export interface EventLoopStats {
|
|
|
162
174
|
* too "lossy")
|
|
163
175
|
*/
|
|
164
176
|
gcCPU: Integer
|
|
177
|
+
/**
|
|
178
|
+
* Actual wall-clock duration of the interval covered by these stats.
|
|
179
|
+
* Normally equals `measureInterval`, but is longer when the timer fired late,
|
|
180
|
+
* e.g. on a CloudRun instance that was CPU-throttled while idle (request-based billing).
|
|
181
|
+
* An interval much longer than `measureInterval` means the delay stats are dominated
|
|
182
|
+
* by that throttling rather than by the application.
|
|
183
|
+
*/
|
|
184
|
+
intervalMs: NumberOfMilliseconds
|
|
165
185
|
}
|