@naturalcycles/backend-lib 9.64.0 → 9.65.0

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.
@@ -5,6 +5,11 @@ import type { Integer, NonNegativeInteger, NumberOfMilliseconds, NumberOfPercent
5
5
  * Also, monitors GC performance.
6
6
  * Once per `measureInterval` sends a callback with stats.
7
7
  *
8
+ * On Node >=24.19 (or >=26.5) delays are sampled once per event loop iteration
9
+ * (`samplePerIteration`), so stats reflect true per-iteration delay:
10
+ * expect p50/mean near 0 on a healthy service, instead of the ~`resolution` baseline
11
+ * reported by older interval-based sampling.
12
+ *
8
13
  * @experimental
9
14
  */
10
15
  export declare class EventLoopMonitor {
@@ -22,10 +27,6 @@ export declare class EventLoopMonitor {
22
27
  stop(): void;
23
28
  }
24
29
  export interface EventLoopMonitorCfg {
25
- /**
26
- * Defaults to 20.
27
- */
28
- resolution?: NumberOfMilliseconds;
29
30
  /**
30
31
  * Defaults to 60_000 ms
31
32
  */
@@ -21,12 +21,24 @@ import { monitorEventLoopDelay, performance, PerformanceObserver } from 'node:pe
21
21
  * Also, monitors GC performance.
22
22
  * Once per `measureInterval` sends a callback with stats.
23
23
  *
24
+ * On Node >=24.19 (or >=26.5) delays are sampled once per event loop iteration
25
+ * (`samplePerIteration`), so stats reflect true per-iteration delay:
26
+ * expect p50/mean near 0 on a healthy service, instead of the ~`resolution` baseline
27
+ * reported by older interval-based sampling.
28
+ *
24
29
  * @experimental
25
30
  */
26
31
  export class EventLoopMonitor {
27
32
  constructor(cfg = {}) {
28
- const { resolution = 20, measureInterval = 60_000 } = cfg;
29
- this.eld = monitorEventLoopDelay({ resolution });
33
+ const { measureInterval = 60_000 } = cfg;
34
+ const eldOptions = {
35
+ // Node >=24.19 / >=26.5: sample once per event loop iteration (uv_prepare/uv_check hooks),
36
+ // which measures actual per-iteration delay instead of timer jitter.
37
+ // More accurate (no `resolution` offset baked into every percentile) and cheaper.
38
+ // Older Node silently ignores this option and falls back to interval-based sampling.
39
+ samplePerIteration: true,
40
+ };
41
+ this.eld = monitorEventLoopDelay(eldOptions);
30
42
  this.eld.enable();
31
43
  this.lastElu = performance.eventLoopUtilization();
32
44
  this.po = new PerformanceObserver(list => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/backend-lib",
3
3
  "type": "module",
4
- "version": "9.64.0",
4
+ "version": "9.65.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/db-lib": "^10",
7
7
  "@naturalcycles/js-lib": "^15",
@@ -45,7 +45,7 @@
45
45
  "devDependencies": {
46
46
  "@sentry/node-core": "^10",
47
47
  "@types/busboy": "^1",
48
- "typescript": "rc",
48
+ "typescript": "^7",
49
49
  "fastify": "^5",
50
50
  "@naturalcycles/dev-lib": "0.0.0"
51
51
  },
@@ -1,4 +1,8 @@
1
- import type { EventLoopUtilization, IntervalHistogram } from 'node:perf_hooks'
1
+ import type {
2
+ EventLoopMonitorOptions,
3
+ EventLoopUtilization,
4
+ IntervalHistogram,
5
+ } from 'node:perf_hooks'
2
6
  import { monitorEventLoopDelay, performance, PerformanceObserver } from 'node:perf_hooks'
3
7
  import type {
4
8
  Integer,
@@ -30,13 +34,25 @@ import type {
30
34
  * Also, monitors GC performance.
31
35
  * Once per `measureInterval` sends a callback with stats.
32
36
  *
37
+ * On Node >=24.19 (or >=26.5) delays are sampled once per event loop iteration
38
+ * (`samplePerIteration`), so stats reflect true per-iteration delay:
39
+ * expect p50/mean near 0 on a healthy service, instead of the ~`resolution` baseline
40
+ * reported by older interval-based sampling.
41
+ *
33
42
  * @experimental
34
43
  */
35
44
  export class EventLoopMonitor {
36
45
  constructor(cfg: EventLoopMonitorCfg = {}) {
37
- const { resolution = 20, measureInterval = 60_000 } = cfg
38
-
39
- this.eld = monitorEventLoopDelay({ resolution })
46
+ const { measureInterval = 60_000 } = cfg
47
+
48
+ const eldOptions: EventLoopMonitorOptions2 = {
49
+ // Node >=24.19 / >=26.5: sample once per event loop iteration (uv_prepare/uv_check hooks),
50
+ // which measures actual per-iteration delay instead of timer jitter.
51
+ // More accurate (no `resolution` offset baked into every percentile) and cheaper.
52
+ // Older Node silently ignores this option and falls back to interval-based sampling.
53
+ samplePerIteration: true,
54
+ }
55
+ this.eld = monitorEventLoopDelay(eldOptions)
40
56
  this.eld.enable()
41
57
 
42
58
  this.lastElu = performance.eventLoopUtilization()
@@ -107,11 +123,6 @@ export class EventLoopMonitor {
107
123
  }
108
124
 
109
125
  export interface EventLoopMonitorCfg {
110
- /**
111
- * Defaults to 20.
112
- */
113
- resolution?: NumberOfMilliseconds
114
-
115
126
  /**
116
127
  * Defaults to 60_000 ms
117
128
  */
@@ -156,3 +167,11 @@ export interface EventLoopStats {
156
167
  */
157
168
  gcCPU: Integer
158
169
  }
170
+
171
+ /**
172
+ * Extends the Node types with `samplePerIteration` (added in Node 24.19 / 26.5).
173
+ * Remove once @types/node declares it.
174
+ */
175
+ interface EventLoopMonitorOptions2 extends EventLoopMonitorOptions {
176
+ samplePerIteration?: boolean
177
+ }