@lakutata/monitor 3.0.0-beta.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.
Files changed (48) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +114 -0
  3. package/dist/cjs/exports/Monitor.d.ts +9 -0
  4. package/dist/cjs/exports/Monitor.js +28 -0
  5. package/dist/cjs/monitor/CpuMonitor.d.ts +60 -0
  6. package/dist/cjs/monitor/CpuMonitor.js +96 -0
  7. package/dist/cjs/monitor/EventLoopMonitor.d.ts +65 -0
  8. package/dist/cjs/monitor/EventLoopMonitor.js +92 -0
  9. package/dist/cjs/monitor/HttpRequestMonitor.d.ts +61 -0
  10. package/dist/cjs/monitor/HttpRequestMonitor.js +92 -0
  11. package/dist/cjs/monitor/MemoryMonitor.d.ts +61 -0
  12. package/dist/cjs/monitor/MemoryMonitor.js +152 -0
  13. package/dist/cjs/monitor/interfaces/ICpuMonitorStatistics.d.ts +40 -0
  14. package/dist/cjs/monitor/interfaces/ICpuMonitorStatistics.js +2 -0
  15. package/dist/cjs/monitor/interfaces/IEventLoopMonitorStatistics.d.ts +45 -0
  16. package/dist/cjs/monitor/interfaces/IEventLoopMonitorStatistics.js +2 -0
  17. package/dist/cjs/monitor/interfaces/IHttpRequestMonitorStatistics.d.ts +43 -0
  18. package/dist/cjs/monitor/interfaces/IHttpRequestMonitorStatistics.js +2 -0
  19. package/dist/cjs/monitor/interfaces/IMemoryMonitorStatistics.d.ts +124 -0
  20. package/dist/cjs/monitor/interfaces/IMemoryMonitorStatistics.js +2 -0
  21. package/dist/cjs/monitor/interfaces/IMonitor.d.ts +10 -0
  22. package/dist/cjs/monitor/interfaces/IMonitor.js +2 -0
  23. package/dist/cjs/monitor/lib/Samples.d.ts +57 -0
  24. package/dist/cjs/monitor/lib/Samples.js +119 -0
  25. package/dist/cjs/package.json +1 -0
  26. package/dist/esm/exports/Monitor.js +9 -0
  27. package/dist/esm/monitor/CpuMonitor.js +92 -0
  28. package/dist/esm/monitor/EventLoopMonitor.js +88 -0
  29. package/dist/esm/monitor/HttpRequestMonitor.js +88 -0
  30. package/dist/esm/monitor/MemoryMonitor.js +115 -0
  31. package/dist/esm/monitor/interfaces/ICpuMonitorStatistics.js +1 -0
  32. package/dist/esm/monitor/interfaces/IEventLoopMonitorStatistics.js +1 -0
  33. package/dist/esm/monitor/interfaces/IHttpRequestMonitorStatistics.js +1 -0
  34. package/dist/esm/monitor/interfaces/IMemoryMonitorStatistics.js +1 -0
  35. package/dist/esm/monitor/interfaces/IMonitor.js +1 -0
  36. package/dist/esm/monitor/lib/Samples.js +114 -0
  37. package/dist/types/exports/Monitor.d.ts +9 -0
  38. package/dist/types/monitor/CpuMonitor.d.ts +60 -0
  39. package/dist/types/monitor/EventLoopMonitor.d.ts +65 -0
  40. package/dist/types/monitor/HttpRequestMonitor.d.ts +61 -0
  41. package/dist/types/monitor/MemoryMonitor.d.ts +61 -0
  42. package/dist/types/monitor/interfaces/ICpuMonitorStatistics.d.ts +40 -0
  43. package/dist/types/monitor/interfaces/IEventLoopMonitorStatistics.d.ts +45 -0
  44. package/dist/types/monitor/interfaces/IHttpRequestMonitorStatistics.d.ts +43 -0
  45. package/dist/types/monitor/interfaces/IMemoryMonitorStatistics.d.ts +124 -0
  46. package/dist/types/monitor/interfaces/IMonitor.d.ts +10 -0
  47. package/dist/types/monitor/lib/Samples.d.ts +57 -0
  48. package/package.json +44 -0
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-present Lakutata
4
+
5
+ All rights reserved
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # @lakutata/monitor
2
+
3
+ The monitors of the lakutata framework: components measuring the process's CPU usage, memory, event loop delay and
4
+ utilization, and the durations of the HTTP requests it receives, on the histograms of Node.js, in constant memory. Each
5
+ exposes a `statistics` getter (the latest value, minimum, maximum, mean and percentiles since its creation or its last
6
+ `reset()`). The umbrella package `lakutata` re-exports it as `lakutata/com/monitor`.
7
+
8
+ ## Installation
9
+
10
+ ```shell
11
+ npm install lakutata
12
+ ```
13
+
14
+ It has no optional dependencies: the measures come from `node:process`, `node:os` and `node:perf_hooks`.
15
+
16
+ ## Usage
17
+
18
+ Register the monitors in the `components` of the application and list them in `bootstrap`: a monitor starts measuring
19
+ when it is created. Then read their `statistics` with `getObject()` or `@Inject`; they are singletons.
20
+
21
+ ```typescript
22
+ import {Application} from 'lakutata'
23
+ import {
24
+ CpuMonitor,
25
+ EventLoopMonitor,
26
+ HttpRequestMonitor,
27
+ MemoryMonitor,
28
+ type ICpuMonitorStatistics,
29
+ type IEventLoopMonitorStatistics,
30
+ type IHttpRequestMonitorStatistics,
31
+ type IMemoryMonitorStatistics
32
+ } from 'lakutata/com/monitor'
33
+
34
+ Application
35
+ .run(() => ({
36
+ id: 'monitor.app',
37
+ name: 'Monitor',
38
+ components: {
39
+ cpu: {class: CpuMonitor},
40
+ memory: {class: MemoryMonitor},
41
+ eventLoop: {class: EventLoopMonitor},
42
+ httpRequests: {class: HttpRequestMonitor}
43
+ },
44
+ bootstrap: ['cpu', 'memory', 'eventLoop', 'httpRequests']
45
+ }))
46
+ .onLaunched(async (app: Application): Promise<void> => {
47
+ const cpu: CpuMonitor = await app.getObject('cpu')
48
+ const memory: MemoryMonitor = await app.getObject('memory')
49
+ const eventLoop: EventLoopMonitor = await app.getObject('eventLoop')
50
+ const httpRequests: HttpRequestMonitor = await app.getObject('httpRequests')
51
+ //A report every minute, each covering the last minute
52
+ setInterval((): void => {
53
+ const cpuStatistics: ICpuMonitorStatistics = cpu.statistics
54
+ const memoryStatistics: IMemoryMonitorStatistics = memory.statistics
55
+ const loopStatistics: IEventLoopMonitorStatistics = eventLoop.statistics
56
+ const httpStatistics: IHttpRequestMonitorStatistics = httpRequests.statistics
57
+ console.log({
58
+ cpuPercent: cpuStatistics.usageAvg,
59
+ rssBytes: memoryStatistics.physicalUsed,
60
+ heapPercent: memoryStatistics.heapUsage,
61
+ loopDelayP99Ms: loopStatistics.p99,
62
+ loopBusyPercent: loopStatistics.utilRate,
63
+ requests: httpStatistics.count,
64
+ requestP95Ms: httpStatistics.p95
65
+ })
66
+ for (const monitor of [cpu, memory, eventLoop, httpRequests]) monitor.reset()
67
+ }, 60000).unref()
68
+ })
69
+ ```
70
+
71
+ | Monitor | Measures | Unit | Sampling | Rounded to |
72
+ |---|---|---|---|---|
73
+ | `CpuMonitor` | User plus system CPU time of the process over each second | percent of one core (above 100 with several cores) | every second, first sample 1 s after creation | 5 decimals |
74
+ | `MemoryMonitor` | `process.memoryUsage()`: resident set, V8 heap, external memory; the host's total memory | bytes (the `*Usage` fields in percent) | at creation, then every second | 2 decimals for the means and percentages |
75
+ | `EventLoopMonitor` | How late the main event loop runs a 1 ms timer; the share of time it is busy | milliseconds; percent | continuously (Node's `monitorEventLoopDelay`, 1 ms resolution) | not rounded |
76
+ | `HttpRequestMonitor` | The duration of each request received by a `node:http` server (Express, Fastify), from its arrival to the end of its response | milliseconds; a count | each request, delivered in batches shortly after its response | 2 decimals |
77
+
78
+ - **Reading the statistics**: every getter call computes them anew. The minimum, maximum, mean and percentiles cover
79
+ every sample since the monitor was created or reset: call `reset()` after each report to read a period. The
80
+ percentiles come from histograms (3 significant digits); the minimum, maximum and mean are exact.
81
+ - **Before the first sample**: the statistics are 0 (the CPU monitor's for its first second, the HTTP monitor's until a
82
+ request ends).
83
+ - **Lifecycle**: the timers do not keep the process alive; the monitors stop when the application is destroyed.
84
+ - **Scope**: the monitors measure the current process only: under a cluster or several instances, each has its own.
85
+ `physicalTotal` is the host's memory, not a container's limit. The requests the process sends, and HTTP/2 requests,
86
+ are not counted.
87
+
88
+ ## API
89
+
90
+ | Export | What it is |
91
+ |---|---|
92
+ | `CpuMonitor` | The CPU usage component: `statistics`, `reset()` |
93
+ | `MemoryMonitor` | The memory component: `statistics`, `reset()` |
94
+ | `EventLoopMonitor` | The event loop component: `statistics`, `reset()` |
95
+ | `HttpRequestMonitor` | The received HTTP requests component: `statistics`, `reset()` |
96
+ | `ICpuMonitorStatistics` | `usage` and `usageMin`/`Max`/`Avg`/`P50`/`P90`/`P95`/`P99`, in percent of one core |
97
+ | `IMemoryMonitorStatistics` | `physical*` (resident set), `heap*` (V8 heap), `external*`, in bytes, plus `physicalUsage` and `heapUsage` in percent |
98
+ | `IEventLoopMonitorStatistics` | `min`, `max`, `avg`, `stdDev`, `p50`, `p90`, `p95`, `p99` in milliseconds, `utilRate` in percent |
99
+ | `IHttpRequestMonitorStatistics` | `count`, then `min`, `max`, `avg`, `stdDev`, `p50`, `p90`, `p95`, `p99` in milliseconds |
100
+ | `IMonitor` | The interface of the monitors: a `statistics` property |
101
+
102
+ All are exported by `lakutata/com/monitor` and `@lakutata/monitor`. The typings document each declaration with
103
+ examples.
104
+
105
+ ## Errors
106
+
107
+ The monitors throw no exception of their own: reading `statistics` and calling `reset()` never fail. Declaring a
108
+ monitor with options it does not have is warned of when the application starts.
109
+
110
+ ## See also
111
+
112
+ - `doc/en/Core.md` of the package `lakutata` (Chinese in `doc/zh`): the components and the bootstrap.
113
+ - `@lakutata/core` (`lakutata`): the components, `Application` and the dependency injection.
114
+ - `@lakutata/logger` (`lakutata/com/logger`): the logger, to write the reports.
@@ -0,0 +1,9 @@
1
+ export * from '../monitor/CpuMonitor.js';
2
+ export * from '../monitor/EventLoopMonitor.js';
3
+ export * from '../monitor/HttpRequestMonitor.js';
4
+ export * from '../monitor/MemoryMonitor.js';
5
+ export * from '../monitor/interfaces/IMonitor.js';
6
+ export * from '../monitor/interfaces/ICpuMonitorStatistics.js';
7
+ export * from '../monitor/interfaces/IEventLoopMonitorStatistics.js';
8
+ export * from '../monitor/interfaces/IHttpRequestMonitorStatistics.js';
9
+ export * from '../monitor/interfaces/IMemoryMonitorStatistics.js';
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("../monitor/CpuMonitor.js"), exports);
18
+ __exportStar(require("../monitor/EventLoopMonitor.js"), exports);
19
+ __exportStar(require("../monitor/HttpRequestMonitor.js"), exports);
20
+ __exportStar(require("../monitor/MemoryMonitor.js"), exports);
21
+ __exportStar(require("../monitor/interfaces/IMonitor.js"), exports);
22
+ __exportStar(require("../monitor/interfaces/ICpuMonitorStatistics.js"), exports);
23
+ __exportStar(require("../monitor/interfaces/IEventLoopMonitorStatistics.js"), exports);
24
+ __exportStar(require("../monitor/interfaces/IHttpRequestMonitorStatistics.js"), exports);
25
+ __exportStar(require("../monitor/interfaces/IMemoryMonitorStatistics.js"), exports);
26
+
27
+ //The default of the entry: the entry itself (scripts/package-build/cjs-package.mjs)
28
+ if (!Object.prototype.hasOwnProperty.call(exports, 'default')) Object.defineProperty(exports, 'default', {value: exports})
@@ -0,0 +1,60 @@
1
+ import { Component } from '@lakutata/core';
2
+ import { IMonitor } from './interfaces/IMonitor.js';
3
+ import { ICpuMonitorStatistics } from './interfaces/ICpuMonitorStatistics.js';
4
+ /**
5
+ * The CPU monitor: measures the CPU time the process uses (user plus system, all its threads), as a percentage of one
6
+ * core: 100 is one core fully busy, and a process using several cores (worker threads, the libuv thread pool) goes
7
+ * above 100. It takes a sample every second, each the average usage over that second, and keeps the statistics of
8
+ * every sample since it was created or last {@link CpuMonitor.reset}: the latest, the minimum, the maximum, the mean
9
+ * and percentiles, in constant memory.
10
+ *
11
+ * Declare it in the `components` of the application (`{class: CpuMonitor}`) and get it with `@Inject` or
12
+ * `getObject()`; it is a singleton. It starts sampling when it is created: list it in `bootstrap` to measure from the
13
+ * launch. The statistics are all 0 until the first sample, one second after its creation. Its timer does not keep the
14
+ * process alive, and stops when the component is destroyed.
15
+ * @example
16
+ * ```typescript
17
+ * import {Application} from 'lakutata'
18
+ * import {CpuMonitor, type ICpuMonitorStatistics} from 'lakutata/com/monitor'
19
+ *
20
+ * Application
21
+ * .run(() => ({
22
+ * id: 'cpu.app',
23
+ * name: 'Cpu',
24
+ * components: {
25
+ * cpu: {class: CpuMonitor}
26
+ * },
27
+ * //Created at the launch, so that it samples from the start
28
+ * bootstrap: ['cpu']
29
+ * }))
30
+ * .onLaunched(async (app: Application): Promise<void> => {
31
+ * const cpu: CpuMonitor = await app.getObject('cpu')
32
+ * setInterval((): void => {
33
+ * const statistics: ICpuMonitorStatistics = cpu.statistics
34
+ * console.log(`CPU ${statistics.usage}% (p95 ${statistics.usageP95}%, max ${statistics.usageMax}%)`)
35
+ * }, 60000)
36
+ * })
37
+ * ```
38
+ */
39
+ export declare class CpuMonitor extends Component implements IMonitor<ICpuMonitorStatistics> {
40
+ #private;
41
+ /**
42
+ * The statistics of the CPU samples since the monitor was created or reset, computed at each read: the latest
43
+ * usage and the minimum, maximum, mean and percentiles (P50 to P99) of the samples, in percent of one core, rounded
44
+ * to 5 decimals. The percentiles are approximate (3 significant digits). All 0 before the first sample.
45
+ */
46
+ get statistics(): ICpuMonitorStatistics;
47
+ /**
48
+ * Take a sample: the CPU usage since the previous sample (user plus system time over the elapsed time, in percent of
49
+ * one core). Called every second.
50
+ * @protected
51
+ */
52
+ protected sampleCpuUsage(): void;
53
+ protected init(): Promise<void>;
54
+ protected destroy(): Promise<void>;
55
+ /**
56
+ * Forget the samples: the statistics are 0 until the next sample, then cover the samples taken since. Use it to
57
+ * measure a period, such as between two reports.
58
+ */
59
+ reset(): void;
60
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CpuMonitor = void 0;
4
+ const core_1 = require("@lakutata/core");
5
+ const Samples_js_1 = require("./lib/Samples.js");
6
+ /**
7
+ * The CPU monitor: measures the CPU time the process uses (user plus system, all its threads), as a percentage of one
8
+ * core: 100 is one core fully busy, and a process using several cores (worker threads, the libuv thread pool) goes
9
+ * above 100. It takes a sample every second, each the average usage over that second, and keeps the statistics of
10
+ * every sample since it was created or last {@link CpuMonitor.reset}: the latest, the minimum, the maximum, the mean
11
+ * and percentiles, in constant memory.
12
+ *
13
+ * Declare it in the `components` of the application (`{class: CpuMonitor}`) and get it with `@Inject` or
14
+ * `getObject()`; it is a singleton. It starts sampling when it is created: list it in `bootstrap` to measure from the
15
+ * launch. The statistics are all 0 until the first sample, one second after its creation. Its timer does not keep the
16
+ * process alive, and stops when the component is destroyed.
17
+ * @example
18
+ * ```typescript
19
+ * import {Application} from 'lakutata'
20
+ * import {CpuMonitor, type ICpuMonitorStatistics} from 'lakutata/com/monitor'
21
+ *
22
+ * Application
23
+ * .run(() => ({
24
+ * id: 'cpu.app',
25
+ * name: 'Cpu',
26
+ * components: {
27
+ * cpu: {class: CpuMonitor}
28
+ * },
29
+ * //Created at the launch, so that it samples from the start
30
+ * bootstrap: ['cpu']
31
+ * }))
32
+ * .onLaunched(async (app: Application): Promise<void> => {
33
+ * const cpu: CpuMonitor = await app.getObject('cpu')
34
+ * setInterval((): void => {
35
+ * const statistics: ICpuMonitorStatistics = cpu.statistics
36
+ * console.log(`CPU ${statistics.usage}% (p95 ${statistics.usageP95}%, max ${statistics.usageMax}%)`)
37
+ * }, 60000)
38
+ * })
39
+ * ```
40
+ */
41
+ class CpuMonitor extends core_1.Component {
42
+ #fractionDigits = 5;
43
+ #intervalDelay = 1000;
44
+ #samples = new Samples_js_1.Samples(1e5);
45
+ #sampleInterval;
46
+ #previousUsage = process.cpuUsage();
47
+ #previousTime = process.hrtime.bigint();
48
+ /**
49
+ * The statistics of the CPU samples since the monitor was created or reset, computed at each read: the latest
50
+ * usage and the minimum, maximum, mean and percentiles (P50 to P99) of the samples, in percent of one core, rounded
51
+ * to 5 decimals. The percentiles are approximate (3 significant digits). All 0 before the first sample.
52
+ */
53
+ get statistics() {
54
+ return {
55
+ usage: (0, Samples_js_1.round)(this.#samples.latest, this.#fractionDigits),
56
+ usageMin: (0, Samples_js_1.round)(this.#samples.min, this.#fractionDigits),
57
+ usageMax: (0, Samples_js_1.round)(this.#samples.max, this.#fractionDigits),
58
+ usageAvg: (0, Samples_js_1.round)(this.#samples.mean, this.#fractionDigits),
59
+ usageP50: (0, Samples_js_1.round)(this.#samples.percentile(50), this.#fractionDigits),
60
+ usageP90: (0, Samples_js_1.round)(this.#samples.percentile(90), this.#fractionDigits),
61
+ usageP95: (0, Samples_js_1.round)(this.#samples.percentile(95), this.#fractionDigits),
62
+ usageP99: (0, Samples_js_1.round)(this.#samples.percentile(99), this.#fractionDigits)
63
+ };
64
+ }
65
+ /**
66
+ * Take a sample: the CPU usage since the previous sample (user plus system time over the elapsed time, in percent of
67
+ * one core). Called every second.
68
+ * @protected
69
+ */
70
+ sampleCpuUsage() {
71
+ const now = process.hrtime.bigint();
72
+ const usage = process.cpuUsage();
73
+ const cpuMicroseconds = (usage.user - this.#previousUsage.user) + (usage.system - this.#previousUsage.system);
74
+ const elapsedMicroseconds = Number(now - this.#previousTime) / 1000;
75
+ this.#previousUsage = usage;
76
+ this.#previousTime = now;
77
+ if (elapsedMicroseconds > 0)
78
+ this.#samples.record(cpuMicroseconds / elapsedMicroseconds * 100);
79
+ }
80
+ async init() {
81
+ this.#previousUsage = process.cpuUsage();
82
+ this.#previousTime = process.hrtime.bigint();
83
+ this.#sampleInterval = setInterval(() => this.sampleCpuUsage(), this.#intervalDelay).unref();
84
+ }
85
+ async destroy() {
86
+ clearInterval(this.#sampleInterval);
87
+ }
88
+ /**
89
+ * Forget the samples: the statistics are 0 until the next sample, then cover the samples taken since. Use it to
90
+ * measure a period, such as between two reports.
91
+ */
92
+ reset() {
93
+ this.#samples.reset();
94
+ }
95
+ }
96
+ exports.CpuMonitor = CpuMonitor;
@@ -0,0 +1,65 @@
1
+ import { type EventLoopUtilization, type IntervalHistogram } from 'node:perf_hooks';
2
+ import { Component } from '@lakutata/core';
3
+ import { IEventLoopMonitorStatistics } from './interfaces/IEventLoopMonitorStatistics.js';
4
+ import { IMonitor } from './interfaces/IMonitor.js';
5
+ /**
6
+ * The event loop monitor: measures how late the event loop of the main thread runs its timers (the event loop delay,
7
+ * in milliseconds, with Node's `monitorEventLoopDelay` at a 1 ms resolution) and how busy it is (its utilization, in
8
+ * percent of the time). A growing delay means synchronous work (CPU-bound code, synchronous I/O, large JSON) blocks the
9
+ * other requests; a utilization near 100 means the loop has no idle time left.
10
+ *
11
+ * Both cover the time since the monitor was created or last {@link EventLoopMonitor.reset}: the delays are a
12
+ * histogram of every measure, the utilization is the share of that whole time the loop was busy. Declare it in the
13
+ * `components` of the application (`{class: EventLoopMonitor}`) and get it with `@Inject` or `getObject()`; it is a
14
+ * singleton. It starts measuring when it is created: list it in `bootstrap` to measure from the launch; it stops when
15
+ * the component is destroyed.
16
+ * @example
17
+ * ```typescript
18
+ * import {Application} from 'lakutata'
19
+ * import {EventLoopMonitor, type IEventLoopMonitorStatistics} from 'lakutata/com/monitor'
20
+ *
21
+ * Application
22
+ * .run(() => ({
23
+ * id: 'loop.app',
24
+ * name: 'Loop',
25
+ * components: {
26
+ * eventLoop: {class: EventLoopMonitor}
27
+ * },
28
+ * bootstrap: ['eventLoop']
29
+ * }))
30
+ * .onLaunched(async (app: Application): Promise<void> => {
31
+ * const eventLoop: EventLoopMonitor = await app.getObject('eventLoop')
32
+ * setInterval((): void => {
33
+ * const statistics: IEventLoopMonitorStatistics = eventLoop.statistics
34
+ * if (statistics.p99 > 100) console.log(`The event loop lags: p99 ${statistics.p99} ms, ${statistics.utilRate}% busy`)
35
+ * //Each report covers the last minute
36
+ * eventLoop.reset()
37
+ * }, 60000)
38
+ * })
39
+ * ```
40
+ */
41
+ export declare class EventLoopMonitor extends Component implements IMonitor<IEventLoopMonitorStatistics> {
42
+ /**
43
+ * The histogram of the event loop delays, in nanoseconds, sampled every millisecond while enabled (from the
44
+ * monitor's initialization to its destruction).
45
+ */
46
+ protected readonly histogram: IntervalHistogram;
47
+ /**
48
+ * The event loop utilization when the monitor was created or reset: the start of the period that
49
+ * {@link IEventLoopMonitorStatistics.utilRate} covers.
50
+ */
51
+ protected initUtil: EventLoopUtilization;
52
+ /**
53
+ * The event loop statistics since the monitor was created or reset, computed at each read: the minimum, maximum,
54
+ * mean, standard deviation and percentiles of the delays in milliseconds (not rounded; all 0 before the first
55
+ * measure), and the utilization in percent.
56
+ */
57
+ get statistics(): IEventLoopMonitorStatistics;
58
+ protected init(): Promise<void>;
59
+ protected destroy(): Promise<void>;
60
+ /**
61
+ * Forget the delays and restart the utilization: the statistics then cover the time since this call. Use it to
62
+ * measure a period, such as between two reports.
63
+ */
64
+ reset(): void;
65
+ }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventLoopMonitor = void 0;
4
+ const node_perf_hooks_1 = require("node:perf_hooks");
5
+ const core_1 = require("@lakutata/core");
6
+ /**
7
+ * The event loop monitor: measures how late the event loop of the main thread runs its timers (the event loop delay,
8
+ * in milliseconds, with Node's `monitorEventLoopDelay` at a 1 ms resolution) and how busy it is (its utilization, in
9
+ * percent of the time). A growing delay means synchronous work (CPU-bound code, synchronous I/O, large JSON) blocks the
10
+ * other requests; a utilization near 100 means the loop has no idle time left.
11
+ *
12
+ * Both cover the time since the monitor was created or last {@link EventLoopMonitor.reset}: the delays are a
13
+ * histogram of every measure, the utilization is the share of that whole time the loop was busy. Declare it in the
14
+ * `components` of the application (`{class: EventLoopMonitor}`) and get it with `@Inject` or `getObject()`; it is a
15
+ * singleton. It starts measuring when it is created: list it in `bootstrap` to measure from the launch; it stops when
16
+ * the component is destroyed.
17
+ * @example
18
+ * ```typescript
19
+ * import {Application} from 'lakutata'
20
+ * import {EventLoopMonitor, type IEventLoopMonitorStatistics} from 'lakutata/com/monitor'
21
+ *
22
+ * Application
23
+ * .run(() => ({
24
+ * id: 'loop.app',
25
+ * name: 'Loop',
26
+ * components: {
27
+ * eventLoop: {class: EventLoopMonitor}
28
+ * },
29
+ * bootstrap: ['eventLoop']
30
+ * }))
31
+ * .onLaunched(async (app: Application): Promise<void> => {
32
+ * const eventLoop: EventLoopMonitor = await app.getObject('eventLoop')
33
+ * setInterval((): void => {
34
+ * const statistics: IEventLoopMonitorStatistics = eventLoop.statistics
35
+ * if (statistics.p99 > 100) console.log(`The event loop lags: p99 ${statistics.p99} ms, ${statistics.utilRate}% busy`)
36
+ * //Each report covers the last minute
37
+ * eventLoop.reset()
38
+ * }, 60000)
39
+ * })
40
+ * ```
41
+ */
42
+ class EventLoopMonitor extends core_1.Component {
43
+ constructor() {
44
+ super(...arguments);
45
+ /**
46
+ * The histogram of the event loop delays, in nanoseconds, sampled every millisecond while enabled (from the
47
+ * monitor's initialization to its destruction).
48
+ */
49
+ this.histogram = (0, node_perf_hooks_1.monitorEventLoopDelay)({ resolution: 1 });
50
+ /**
51
+ * The event loop utilization when the monitor was created or reset: the start of the period that
52
+ * {@link IEventLoopMonitorStatistics.utilRate} covers.
53
+ */
54
+ this.initUtil = node_perf_hooks_1.performance.eventLoopUtilization();
55
+ }
56
+ /**
57
+ * The event loop statistics since the monitor was created or reset, computed at each read: the minimum, maximum,
58
+ * mean, standard deviation and percentiles of the delays in milliseconds (not rounded; all 0 before the first
59
+ * measure), and the utilization in percent.
60
+ */
61
+ get statistics() {
62
+ //Without sample yet, the histogram gives no meaningful value
63
+ const sampled = this.histogram.count > 0;
64
+ const delay = (nanoseconds) => sampled ? nanoseconds / 1e6 : 0;
65
+ return {
66
+ min: delay(this.histogram.min),
67
+ max: delay(this.histogram.max),
68
+ avg: delay(this.histogram.mean),
69
+ stdDev: delay(this.histogram.stddev),
70
+ p50: delay(this.histogram.percentile(50)),
71
+ p90: delay(this.histogram.percentile(90)),
72
+ p95: delay(this.histogram.percentile(95)),
73
+ p99: delay(this.histogram.percentile(99)),
74
+ utilRate: node_perf_hooks_1.performance.eventLoopUtilization(this.initUtil).utilization * 100
75
+ };
76
+ }
77
+ async init() {
78
+ this.histogram.enable();
79
+ }
80
+ async destroy() {
81
+ this.histogram.disable();
82
+ }
83
+ /**
84
+ * Forget the delays and restart the utilization: the statistics then cover the time since this call. Use it to
85
+ * measure a period, such as between two reports.
86
+ */
87
+ reset() {
88
+ this.histogram.reset();
89
+ this.initUtil = node_perf_hooks_1.performance.eventLoopUtilization();
90
+ }
91
+ }
92
+ exports.EventLoopMonitor = EventLoopMonitor;
@@ -0,0 +1,61 @@
1
+ import { PerformanceObserver } from 'node:perf_hooks';
2
+ import { Component } from '@lakutata/core';
3
+ import { IMonitor } from './interfaces/IMonitor.js';
4
+ import { IHttpRequestMonitorStatistics } from './interfaces/IHttpRequestMonitorStatistics.js';
5
+ /**
6
+ * The HTTP request monitor: measures how long the HTTP requests received by the process take, in milliseconds, from
7
+ * their arrival to the end of their response, through the performance entries of `node:http` (so every server built on
8
+ * it is counted: the Express and Fastify adapters, and any other server of the process). The requests the process
9
+ * sends are not counted, nor are HTTP/2 requests.
10
+ *
11
+ * It keeps the count and the statistics of the durations since it was created or last {@link HttpRequestMonitor.reset},
12
+ * in constant memory. The entries reach it in batches, shortly after the responses end, so a request just answered may
13
+ * not be counted yet. Declare it in the `components` of the application (`{class: HttpRequestMonitor}`) and get it with
14
+ * `@Inject` or `getObject()`; it is a singleton. It only sees the requests ending after its creation: list it in
15
+ * `bootstrap` to measure from the launch; it stops when the component is destroyed.
16
+ * @example
17
+ * ```typescript
18
+ * import {Application} from 'lakutata'
19
+ * import {HttpRequestMonitor, type IHttpRequestMonitorStatistics} from 'lakutata/com/monitor'
20
+ *
21
+ * Application
22
+ * .run(() => ({
23
+ * id: 'http.app',
24
+ * name: 'Http',
25
+ * components: {
26
+ * httpRequests: {class: HttpRequestMonitor}
27
+ * },
28
+ * bootstrap: ['httpRequests']
29
+ * }))
30
+ * .onLaunched(async (app: Application): Promise<void> => {
31
+ * const httpRequests: HttpRequestMonitor = await app.getObject('httpRequests')
32
+ * setInterval((): void => {
33
+ * const statistics: IHttpRequestMonitorStatistics = httpRequests.statistics
34
+ * console.log(`${statistics.count} requests, avg ${statistics.avg} ms, p99 ${statistics.p99} ms`)
35
+ * httpRequests.reset()
36
+ * }, 60000)
37
+ * })
38
+ * ```
39
+ */
40
+ export declare class HttpRequestMonitor extends Component implements IMonitor<IHttpRequestMonitorStatistics> {
41
+ #private;
42
+ /**
43
+ * The observer of the `http` performance entries, recording the duration of each received request (the
44
+ * `HttpRequest` entries); observing from the monitor's initialization to its destruction.
45
+ */
46
+ protected readonly observer: PerformanceObserver;
47
+ /**
48
+ * The statistics of the request durations since the monitor was created or reset, computed at each read: the
49
+ * number of requests, and the minimum, maximum, mean, standard deviation and percentiles of their durations in
50
+ * milliseconds, rounded to 2 decimals. The percentiles are approximate (3 significant digits). All 0 before the first
51
+ * request.
52
+ */
53
+ get statistics(): IHttpRequestMonitorStatistics;
54
+ protected init(): Promise<void>;
55
+ protected destroy(): Promise<void>;
56
+ /**
57
+ * Forget the requests: the count and the statistics restart from 0. Use it to measure a period, such as between
58
+ * two reports.
59
+ */
60
+ reset(): void;
61
+ }
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpRequestMonitor = void 0;
4
+ const node_perf_hooks_1 = require("node:perf_hooks");
5
+ const core_1 = require("@lakutata/core");
6
+ const Samples_js_1 = require("./lib/Samples.js");
7
+ /**
8
+ * The HTTP request monitor: measures how long the HTTP requests received by the process take, in milliseconds, from
9
+ * their arrival to the end of their response, through the performance entries of `node:http` (so every server built on
10
+ * it is counted: the Express and Fastify adapters, and any other server of the process). The requests the process
11
+ * sends are not counted, nor are HTTP/2 requests.
12
+ *
13
+ * It keeps the count and the statistics of the durations since it was created or last {@link HttpRequestMonitor.reset},
14
+ * in constant memory. The entries reach it in batches, shortly after the responses end, so a request just answered may
15
+ * not be counted yet. Declare it in the `components` of the application (`{class: HttpRequestMonitor}`) and get it with
16
+ * `@Inject` or `getObject()`; it is a singleton. It only sees the requests ending after its creation: list it in
17
+ * `bootstrap` to measure from the launch; it stops when the component is destroyed.
18
+ * @example
19
+ * ```typescript
20
+ * import {Application} from 'lakutata'
21
+ * import {HttpRequestMonitor, type IHttpRequestMonitorStatistics} from 'lakutata/com/monitor'
22
+ *
23
+ * Application
24
+ * .run(() => ({
25
+ * id: 'http.app',
26
+ * name: 'Http',
27
+ * components: {
28
+ * httpRequests: {class: HttpRequestMonitor}
29
+ * },
30
+ * bootstrap: ['httpRequests']
31
+ * }))
32
+ * .onLaunched(async (app: Application): Promise<void> => {
33
+ * const httpRequests: HttpRequestMonitor = await app.getObject('httpRequests')
34
+ * setInterval((): void => {
35
+ * const statistics: IHttpRequestMonitorStatistics = httpRequests.statistics
36
+ * console.log(`${statistics.count} requests, avg ${statistics.avg} ms, p99 ${statistics.p99} ms`)
37
+ * httpRequests.reset()
38
+ * }, 60000)
39
+ * })
40
+ * ```
41
+ */
42
+ class HttpRequestMonitor extends core_1.Component {
43
+ constructor() {
44
+ super(...arguments);
45
+ this.#fractionDigits = 2;
46
+ this.#durations = new Samples_js_1.Samples(100);
47
+ /**
48
+ * The observer of the `http` performance entries, recording the duration of each received request (the
49
+ * `HttpRequest` entries); observing from the monitor's initialization to its destruction.
50
+ */
51
+ this.observer = new node_perf_hooks_1.PerformanceObserver((list) => {
52
+ //Every request of the batch, the received ones only (HttpClient entries are the requests sent)
53
+ for (const entry of list.getEntriesByName('HttpRequest'))
54
+ this.#durations.record(entry.duration);
55
+ });
56
+ }
57
+ #fractionDigits;
58
+ #durations;
59
+ /**
60
+ * The statistics of the request durations since the monitor was created or reset, computed at each read: the
61
+ * number of requests, and the minimum, maximum, mean, standard deviation and percentiles of their durations in
62
+ * milliseconds, rounded to 2 decimals. The percentiles are approximate (3 significant digits). All 0 before the first
63
+ * request.
64
+ */
65
+ get statistics() {
66
+ return {
67
+ count: this.#durations.count,
68
+ min: (0, Samples_js_1.round)(this.#durations.min, this.#fractionDigits),
69
+ max: (0, Samples_js_1.round)(this.#durations.max, this.#fractionDigits),
70
+ avg: (0, Samples_js_1.round)(this.#durations.mean, this.#fractionDigits),
71
+ stdDev: (0, Samples_js_1.round)(this.#durations.stdDev, this.#fractionDigits),
72
+ p50: (0, Samples_js_1.round)(this.#durations.percentile(50), this.#fractionDigits),
73
+ p90: (0, Samples_js_1.round)(this.#durations.percentile(90), this.#fractionDigits),
74
+ p95: (0, Samples_js_1.round)(this.#durations.percentile(95), this.#fractionDigits),
75
+ p99: (0, Samples_js_1.round)(this.#durations.percentile(99), this.#fractionDigits)
76
+ };
77
+ }
78
+ async init() {
79
+ this.observer.observe({ entryTypes: ['http'] });
80
+ }
81
+ async destroy() {
82
+ this.observer.disconnect();
83
+ }
84
+ /**
85
+ * Forget the requests: the count and the statistics restart from 0. Use it to measure a period, such as between
86
+ * two reports.
87
+ */
88
+ reset() {
89
+ this.#durations.reset();
90
+ }
91
+ }
92
+ exports.HttpRequestMonitor = HttpRequestMonitor;