@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.
- package/LICENSE +23 -0
- package/README.md +114 -0
- package/dist/cjs/exports/Monitor.d.ts +9 -0
- package/dist/cjs/exports/Monitor.js +28 -0
- package/dist/cjs/monitor/CpuMonitor.d.ts +60 -0
- package/dist/cjs/monitor/CpuMonitor.js +96 -0
- package/dist/cjs/monitor/EventLoopMonitor.d.ts +65 -0
- package/dist/cjs/monitor/EventLoopMonitor.js +92 -0
- package/dist/cjs/monitor/HttpRequestMonitor.d.ts +61 -0
- package/dist/cjs/monitor/HttpRequestMonitor.js +92 -0
- package/dist/cjs/monitor/MemoryMonitor.d.ts +61 -0
- package/dist/cjs/monitor/MemoryMonitor.js +152 -0
- package/dist/cjs/monitor/interfaces/ICpuMonitorStatistics.d.ts +40 -0
- package/dist/cjs/monitor/interfaces/ICpuMonitorStatistics.js +2 -0
- package/dist/cjs/monitor/interfaces/IEventLoopMonitorStatistics.d.ts +45 -0
- package/dist/cjs/monitor/interfaces/IEventLoopMonitorStatistics.js +2 -0
- package/dist/cjs/monitor/interfaces/IHttpRequestMonitorStatistics.d.ts +43 -0
- package/dist/cjs/monitor/interfaces/IHttpRequestMonitorStatistics.js +2 -0
- package/dist/cjs/monitor/interfaces/IMemoryMonitorStatistics.d.ts +124 -0
- package/dist/cjs/monitor/interfaces/IMemoryMonitorStatistics.js +2 -0
- package/dist/cjs/monitor/interfaces/IMonitor.d.ts +10 -0
- package/dist/cjs/monitor/interfaces/IMonitor.js +2 -0
- package/dist/cjs/monitor/lib/Samples.d.ts +57 -0
- package/dist/cjs/monitor/lib/Samples.js +119 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/exports/Monitor.js +9 -0
- package/dist/esm/monitor/CpuMonitor.js +92 -0
- package/dist/esm/monitor/EventLoopMonitor.js +88 -0
- package/dist/esm/monitor/HttpRequestMonitor.js +88 -0
- package/dist/esm/monitor/MemoryMonitor.js +115 -0
- package/dist/esm/monitor/interfaces/ICpuMonitorStatistics.js +1 -0
- package/dist/esm/monitor/interfaces/IEventLoopMonitorStatistics.js +1 -0
- package/dist/esm/monitor/interfaces/IHttpRequestMonitorStatistics.js +1 -0
- package/dist/esm/monitor/interfaces/IMemoryMonitorStatistics.js +1 -0
- package/dist/esm/monitor/interfaces/IMonitor.js +1 -0
- package/dist/esm/monitor/lib/Samples.js +114 -0
- package/dist/types/exports/Monitor.d.ts +9 -0
- package/dist/types/monitor/CpuMonitor.d.ts +60 -0
- package/dist/types/monitor/EventLoopMonitor.d.ts +65 -0
- package/dist/types/monitor/HttpRequestMonitor.d.ts +61 -0
- package/dist/types/monitor/MemoryMonitor.d.ts +61 -0
- package/dist/types/monitor/interfaces/ICpuMonitorStatistics.d.ts +40 -0
- package/dist/types/monitor/interfaces/IEventLoopMonitorStatistics.d.ts +45 -0
- package/dist/types/monitor/interfaces/IHttpRequestMonitorStatistics.d.ts +43 -0
- package/dist/types/monitor/interfaces/IMemoryMonitorStatistics.d.ts +124 -0
- package/dist/types/monitor/interfaces/IMonitor.d.ts +10 -0
- package/dist/types/monitor/lib/Samples.d.ts +57 -0
- 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;
|