@openstatus/health-memory 0.1.4-dev.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/README.md +73 -0
- package/dist/_virtual/rolldown_runtime.cjs +30 -0
- package/dist/mod.cjs +63 -0
- package/dist/mod.d.cts +55 -0
- package/dist/mod.d.cts.map +1 -0
- package/dist/mod.d.ts +55 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +60 -0
- package/dist/mod.js.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @openstatus/health-memory
|
|
2
|
+
|
|
3
|
+
Memory probe for [`@openstatus/health`](https://jsr.io/@openstatus/health).
|
|
4
|
+
Compares the process's heap in use with the V8 heap limit — and its
|
|
5
|
+
resident set size with a byte budget — and fails the check above either
|
|
6
|
+
threshold, so a leak turns the report `degraded` before the process is
|
|
7
|
+
killed for running out of memory.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
deno add jsr:@openstatus/health jsr:@openstatus/health-memory
|
|
11
|
+
npm install @openstatus/health @openstatus/health-memory
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { createHealthHandler } from "@openstatus/health";
|
|
16
|
+
import { memoryProbe } from "@openstatus/health-memory";
|
|
17
|
+
|
|
18
|
+
Deno.serve(
|
|
19
|
+
createHealthHandler({
|
|
20
|
+
probes: [memoryProbe({ maxHeapUsedPercent: 90 })],
|
|
21
|
+
}),
|
|
22
|
+
);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The heap percentage is `heapUsed` from `process.memoryUsage()` over
|
|
26
|
+
`heap_size_limit` from `v8.getHeapStatistics()` — the limit V8 will
|
|
27
|
+
actually grow to, which honours `--max-old-space-size`. It is the number
|
|
28
|
+
that predicts an out-of-memory crash. `maxRssBytes` bounds the whole
|
|
29
|
+
resident set instead, which is what a container limit or an OOM killer
|
|
30
|
+
measures; set it a little under that limit. The check fails above
|
|
31
|
+
`maxHeapUsedPercent` (90% by default) or above `maxRssBytes` when that is
|
|
32
|
+
set instead; pass both to enforce both. A failing check throws
|
|
33
|
+
`MemoryPressureError`, which carries the `reading`, and a healthy one
|
|
34
|
+
reports `heapUsedBytes`, `heapLimitBytes`, `heapUsedPercent` and `rssBytes`.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
memoryProbe({
|
|
38
|
+
maxHeapUsedPercent: 85,
|
|
39
|
+
maxRssBytes: 900 * 1024 * 1024,
|
|
40
|
+
// optional overrides from the Probe contract
|
|
41
|
+
name: "heap",
|
|
42
|
+
critical: true,
|
|
43
|
+
timeoutMs: 100,
|
|
44
|
+
skip: () => env.NODE_ENV !== "production",
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Non-critical by default: memory pressure is an alert to act on, and taking
|
|
49
|
+
a leaking instance out of rotation only shifts its traffic onto the others.
|
|
50
|
+
Set `critical: true` when your platform restarts unhealthy instances and
|
|
51
|
+
that is the recovery you want.
|
|
52
|
+
|
|
53
|
+
This probe uses `node:process` and `node:v8`, so it runs on Node.js, Deno
|
|
54
|
+
and Bun but not on edge runtimes. Both readers are typed structurally and
|
|
55
|
+
can be replaced for tests.
|
|
56
|
+
|
|
57
|
+
## About openstatus
|
|
58
|
+
|
|
59
|
+
[openstatus](https://www.openstatus.dev/) is the open-source uptime monitoring
|
|
60
|
+
and status page platform. This package is part of
|
|
61
|
+
[`@openstatus/health`](https://github.com/openstatusHQ/health), the `/health`
|
|
62
|
+
endpoints behind openstatus's own services, extracted so any JavaScript server
|
|
63
|
+
can expose one. Point an
|
|
64
|
+
[openstatus monitor](https://www.openstatus.dev/docs/reference/http-monitor)
|
|
65
|
+
at the endpoint and assert on `status` in the body to be alerted on
|
|
66
|
+
`degraded` before it becomes `unhealthy`.
|
|
67
|
+
|
|
68
|
+
Source: [github.com/openstatusHQ/health](https://github.com/openstatusHQ/health).
|
|
69
|
+
Issues and PRs welcome.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
[MIT](https://github.com/openstatusHQ/health/blob/main/LICENSE)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
|
|
25
|
+
Object.defineProperty(exports, '__toESM', {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: function () {
|
|
28
|
+
return __toESM;
|
|
29
|
+
}
|
|
30
|
+
});
|
package/dist/mod.cjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const node_process = require_rolldown_runtime.__toESM(require("node:process"));
|
|
3
|
+
const node_v8 = require_rolldown_runtime.__toESM(require("node:v8"));
|
|
4
|
+
const __openstatus_health = require_rolldown_runtime.__toESM(require("@openstatus/health"));
|
|
5
|
+
|
|
6
|
+
//#region src/mod.ts
|
|
7
|
+
/** Probe name when `name` is unset. */
|
|
8
|
+
const memoryDefaultName = "memory";
|
|
9
|
+
/** Threshold when neither `maxHeapUsedPercent` nor `maxRssBytes` is set. */
|
|
10
|
+
const memoryDefaultMaxHeapUsedPercent = 90;
|
|
11
|
+
/** Thrown by the probe when usage is above a threshold. */
|
|
12
|
+
var MemoryPressureError = class extends Error {
|
|
13
|
+
/** The reading that crossed the threshold. */
|
|
14
|
+
reading;
|
|
15
|
+
/** Build the error for `reading` against the threshold described by `reason`. */
|
|
16
|
+
constructor(reading, reason) {
|
|
17
|
+
super(reason);
|
|
18
|
+
this.name = "MemoryPressureError";
|
|
19
|
+
this.reading = reading;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
/** A probe that fails above `maxHeapUsedPercent` or `maxRssBytes`; non-critical by default. Throws `ProbeConfigError` for a negative or non-finite threshold. */
|
|
23
|
+
function memoryProbe(options = {}) {
|
|
24
|
+
const maxRssBytes = options.maxRssBytes;
|
|
25
|
+
const maxHeapUsedPercent = options.maxHeapUsedPercent ?? (maxRssBytes == null ? memoryDefaultMaxHeapUsedPercent : void 0);
|
|
26
|
+
for (const [field, value] of [["maxHeapUsedPercent", maxHeapUsedPercent], ["maxRssBytes", maxRssBytes]]) if (value != null && (!Number.isFinite(value) || value < 0)) throw new __openstatus_health.ProbeConfigError("memoryProbe", field, `must be a non-negative number, got ${String(value)}`);
|
|
27
|
+
const memoryUsage = options.memoryUsage ?? (() => node_process.default.memoryUsage());
|
|
28
|
+
const heapStatistics = options.heapStatistics ?? node_v8.getHeapStatistics;
|
|
29
|
+
return {
|
|
30
|
+
name: options.name ?? memoryDefaultName,
|
|
31
|
+
critical: options.critical ?? false,
|
|
32
|
+
timeoutMs: options.timeoutMs,
|
|
33
|
+
skip: options.skip,
|
|
34
|
+
run: () => {
|
|
35
|
+
const reading = read(memoryUsage(), heapStatistics());
|
|
36
|
+
if (maxRssBytes != null && reading.rssBytes > maxRssBytes) throw new MemoryPressureError(reading, `rss ${reading.rssBytes} bytes exceeds ${maxRssBytes}`);
|
|
37
|
+
if (maxHeapUsedPercent != null && reading.heapUsedBytes / reading.heapLimitBytes * 100 > maxHeapUsedPercent) throw new MemoryPressureError(reading, `heap ${reading.heapUsedPercent}% used exceeds ${maxHeapUsedPercent}%`);
|
|
38
|
+
return reading;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function read(usage, heap) {
|
|
43
|
+
const heapUsedBytes = usage.heapUsed;
|
|
44
|
+
const heapLimitBytes = heap.heap_size_limit;
|
|
45
|
+
const rssBytes = usage.rss;
|
|
46
|
+
if (![
|
|
47
|
+
heapUsedBytes,
|
|
48
|
+
heapLimitBytes,
|
|
49
|
+
rssBytes
|
|
50
|
+
].every((n) => Number.isFinite(n) && n >= 0) || heapLimitBytes === 0) throw new Error("unexpected memory statistics");
|
|
51
|
+
return {
|
|
52
|
+
heapUsedBytes,
|
|
53
|
+
heapLimitBytes,
|
|
54
|
+
heapUsedPercent: Math.round(heapUsedBytes / heapLimitBytes * 1e3) / 10,
|
|
55
|
+
rssBytes
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
exports.MemoryPressureError = MemoryPressureError;
|
|
61
|
+
exports.memoryDefaultMaxHeapUsedPercent = memoryDefaultMaxHeapUsedPercent;
|
|
62
|
+
exports.memoryDefaultName = memoryDefaultName;
|
|
63
|
+
exports.memoryProbe = memoryProbe;
|
package/dist/mod.d.cts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Probe, ProbeOverrides } from "@openstatus/health";
|
|
2
|
+
|
|
3
|
+
//#region src/mod.d.ts
|
|
4
|
+
|
|
5
|
+
/** Probe name when `name` is unset. */
|
|
6
|
+
declare const memoryDefaultName = "memory";
|
|
7
|
+
/** Threshold when neither `maxHeapUsedPercent` nor `maxRssBytes` is set. */
|
|
8
|
+
declare const memoryDefaultMaxHeapUsedPercent = 90;
|
|
9
|
+
/** The subset of `process.memoryUsage()` the probe reads. */
|
|
10
|
+
interface MemoryUsageSample {
|
|
11
|
+
/** Bytes of V8 heap in use. */
|
|
12
|
+
readonly heapUsed: number;
|
|
13
|
+
/** Resident set size in bytes. */
|
|
14
|
+
readonly rss: number;
|
|
15
|
+
}
|
|
16
|
+
/** The subset of `v8.getHeapStatistics()` the probe reads. */
|
|
17
|
+
interface HeapStatisticsSample {
|
|
18
|
+
/** The maximum heap size V8 will grow to. */
|
|
19
|
+
readonly heap_size_limit: number;
|
|
20
|
+
}
|
|
21
|
+
/** Options for `memoryProbe()`. */
|
|
22
|
+
interface MemoryProbeOptions extends ProbeOverrides {
|
|
23
|
+
/** Fail when the heap in use exceeds this percentage of the heap limit. Default `memoryDefaultMaxHeapUsedPercent` unless `maxRssBytes` is set. */
|
|
24
|
+
readonly maxHeapUsedPercent?: number;
|
|
25
|
+
/** Fail when the resident set size exceeds this many bytes. */
|
|
26
|
+
readonly maxRssBytes?: number;
|
|
27
|
+
/** Replacement `process.memoryUsage`, for tests. */
|
|
28
|
+
readonly memoryUsage?: () => MemoryUsageSample;
|
|
29
|
+
/** Replacement `v8.getHeapStatistics`, for tests. */
|
|
30
|
+
readonly heapStatistics?: () => HeapStatisticsSample;
|
|
31
|
+
}
|
|
32
|
+
/** What a healthy check resolves with. */
|
|
33
|
+
interface MemoryReading {
|
|
34
|
+
/** Bytes of heap in use. */
|
|
35
|
+
readonly heapUsedBytes: number;
|
|
36
|
+
/** The heap limit in bytes. */
|
|
37
|
+
readonly heapLimitBytes: number;
|
|
38
|
+
/** `heapUsedBytes` as a percentage of `heapLimitBytes`, rounded to one decimal. */
|
|
39
|
+
readonly heapUsedPercent: number;
|
|
40
|
+
/** Resident set size in bytes. */
|
|
41
|
+
readonly rssBytes: number;
|
|
42
|
+
}
|
|
43
|
+
/** Thrown by the probe when usage is above a threshold. */
|
|
44
|
+
declare class MemoryPressureError extends Error {
|
|
45
|
+
/** The reading that crossed the threshold. */
|
|
46
|
+
readonly reading: MemoryReading;
|
|
47
|
+
/** Build the error for `reading` against the threshold described by `reason`. */
|
|
48
|
+
constructor(reading: MemoryReading, reason: string);
|
|
49
|
+
}
|
|
50
|
+
/** A probe that fails above `maxHeapUsedPercent` or `maxRssBytes`; non-critical by default. Throws `ProbeConfigError` for a negative or non-finite threshold. */
|
|
51
|
+
declare function memoryProbe(options?: MemoryProbeOptions): Probe;
|
|
52
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
53
|
+
//#endregion
|
|
54
|
+
export { HeapStatisticsSample, MemoryPressureError, MemoryProbeOptions, MemoryReading, MemoryUsageSample, memoryDefaultMaxHeapUsedPercent, memoryDefaultName, memoryProbe };
|
|
55
|
+
//# sourceMappingURL=mod.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.cts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;AAyEuB,cAhDV,iBAAA,GAgDU,QAAA;;AALuB,cAzCjC,+BAAA,GAyCiC,EAAA;AAa9C;AAA2B,UAnDV,iBAAA,CAmDU;EAAA;EAAiC,SAAG,QAAA,EAAA,MAAA;EAAK;;;;UA3CnD,oBAAA;;;;;UAMA,kBAAA,SAA2B;;;;;;+BAMb;;kCAEG;;;UAIjB,aAAA;;;;;;;;;;;cAYJ,mBAAA,SAA4B,KAAA;;oBAErB;;uBAGG;;;iBAQP,WAAA,WAAqB,qBAA0B"}
|
package/dist/mod.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Probe, ProbeOverrides } from "@openstatus/health";
|
|
2
|
+
|
|
3
|
+
//#region src/mod.d.ts
|
|
4
|
+
|
|
5
|
+
/** Probe name when `name` is unset. */
|
|
6
|
+
declare const memoryDefaultName = "memory";
|
|
7
|
+
/** Threshold when neither `maxHeapUsedPercent` nor `maxRssBytes` is set. */
|
|
8
|
+
declare const memoryDefaultMaxHeapUsedPercent = 90;
|
|
9
|
+
/** The subset of `process.memoryUsage()` the probe reads. */
|
|
10
|
+
interface MemoryUsageSample {
|
|
11
|
+
/** Bytes of V8 heap in use. */
|
|
12
|
+
readonly heapUsed: number;
|
|
13
|
+
/** Resident set size in bytes. */
|
|
14
|
+
readonly rss: number;
|
|
15
|
+
}
|
|
16
|
+
/** The subset of `v8.getHeapStatistics()` the probe reads. */
|
|
17
|
+
interface HeapStatisticsSample {
|
|
18
|
+
/** The maximum heap size V8 will grow to. */
|
|
19
|
+
readonly heap_size_limit: number;
|
|
20
|
+
}
|
|
21
|
+
/** Options for `memoryProbe()`. */
|
|
22
|
+
interface MemoryProbeOptions extends ProbeOverrides {
|
|
23
|
+
/** Fail when the heap in use exceeds this percentage of the heap limit. Default `memoryDefaultMaxHeapUsedPercent` unless `maxRssBytes` is set. */
|
|
24
|
+
readonly maxHeapUsedPercent?: number;
|
|
25
|
+
/** Fail when the resident set size exceeds this many bytes. */
|
|
26
|
+
readonly maxRssBytes?: number;
|
|
27
|
+
/** Replacement `process.memoryUsage`, for tests. */
|
|
28
|
+
readonly memoryUsage?: () => MemoryUsageSample;
|
|
29
|
+
/** Replacement `v8.getHeapStatistics`, for tests. */
|
|
30
|
+
readonly heapStatistics?: () => HeapStatisticsSample;
|
|
31
|
+
}
|
|
32
|
+
/** What a healthy check resolves with. */
|
|
33
|
+
interface MemoryReading {
|
|
34
|
+
/** Bytes of heap in use. */
|
|
35
|
+
readonly heapUsedBytes: number;
|
|
36
|
+
/** The heap limit in bytes. */
|
|
37
|
+
readonly heapLimitBytes: number;
|
|
38
|
+
/** `heapUsedBytes` as a percentage of `heapLimitBytes`, rounded to one decimal. */
|
|
39
|
+
readonly heapUsedPercent: number;
|
|
40
|
+
/** Resident set size in bytes. */
|
|
41
|
+
readonly rssBytes: number;
|
|
42
|
+
}
|
|
43
|
+
/** Thrown by the probe when usage is above a threshold. */
|
|
44
|
+
declare class MemoryPressureError extends Error {
|
|
45
|
+
/** The reading that crossed the threshold. */
|
|
46
|
+
readonly reading: MemoryReading;
|
|
47
|
+
/** Build the error for `reading` against the threshold described by `reason`. */
|
|
48
|
+
constructor(reading: MemoryReading, reason: string);
|
|
49
|
+
}
|
|
50
|
+
/** A probe that fails above `maxHeapUsedPercent` or `maxRssBytes`; non-critical by default. Throws `ProbeConfigError` for a negative or non-finite threshold. */
|
|
51
|
+
declare function memoryProbe(options?: MemoryProbeOptions): Probe;
|
|
52
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
53
|
+
//#endregion
|
|
54
|
+
export { HeapStatisticsSample, MemoryPressureError, MemoryProbeOptions, MemoryReading, MemoryUsageSample, memoryDefaultMaxHeapUsedPercent, memoryDefaultName, memoryProbe };
|
|
55
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;AAyEuB,cAhDV,iBAAA,GAgDU,QAAA;;AALuB,cAzCjC,+BAAA,GAyCiC,EAAA;AAa9C;AAA2B,UAnDV,iBAAA,CAmDU;EAAA;EAAiC,SAAG,QAAA,EAAA,MAAA;EAAK;;;;UA3CnD,oBAAA;;;;;UAMA,kBAAA,SAA2B;;;;;;+BAMb;;kCAEG;;;UAIjB,aAAA;;;;;;;;;;;cAYJ,mBAAA,SAA4B,KAAA;;oBAErB;;uBAGG;;;iBAQP,WAAA,WAAqB,qBAA0B"}
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { getHeapStatistics } from "node:v8";
|
|
3
|
+
import { ProbeConfigError } from "@openstatus/health";
|
|
4
|
+
|
|
5
|
+
//#region src/mod.ts
|
|
6
|
+
/** Probe name when `name` is unset. */
|
|
7
|
+
const memoryDefaultName = "memory";
|
|
8
|
+
/** Threshold when neither `maxHeapUsedPercent` nor `maxRssBytes` is set. */
|
|
9
|
+
const memoryDefaultMaxHeapUsedPercent = 90;
|
|
10
|
+
/** Thrown by the probe when usage is above a threshold. */
|
|
11
|
+
var MemoryPressureError = class extends Error {
|
|
12
|
+
/** The reading that crossed the threshold. */
|
|
13
|
+
reading;
|
|
14
|
+
/** Build the error for `reading` against the threshold described by `reason`. */
|
|
15
|
+
constructor(reading, reason) {
|
|
16
|
+
super(reason);
|
|
17
|
+
this.name = "MemoryPressureError";
|
|
18
|
+
this.reading = reading;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/** A probe that fails above `maxHeapUsedPercent` or `maxRssBytes`; non-critical by default. Throws `ProbeConfigError` for a negative or non-finite threshold. */
|
|
22
|
+
function memoryProbe(options = {}) {
|
|
23
|
+
const maxRssBytes = options.maxRssBytes;
|
|
24
|
+
const maxHeapUsedPercent = options.maxHeapUsedPercent ?? (maxRssBytes == null ? memoryDefaultMaxHeapUsedPercent : void 0);
|
|
25
|
+
for (const [field, value] of [["maxHeapUsedPercent", maxHeapUsedPercent], ["maxRssBytes", maxRssBytes]]) if (value != null && (!Number.isFinite(value) || value < 0)) throw new ProbeConfigError("memoryProbe", field, `must be a non-negative number, got ${String(value)}`);
|
|
26
|
+
const memoryUsage = options.memoryUsage ?? (() => process.memoryUsage());
|
|
27
|
+
const heapStatistics = options.heapStatistics ?? getHeapStatistics;
|
|
28
|
+
return {
|
|
29
|
+
name: options.name ?? memoryDefaultName,
|
|
30
|
+
critical: options.critical ?? false,
|
|
31
|
+
timeoutMs: options.timeoutMs,
|
|
32
|
+
skip: options.skip,
|
|
33
|
+
run: () => {
|
|
34
|
+
const reading = read(memoryUsage(), heapStatistics());
|
|
35
|
+
if (maxRssBytes != null && reading.rssBytes > maxRssBytes) throw new MemoryPressureError(reading, `rss ${reading.rssBytes} bytes exceeds ${maxRssBytes}`);
|
|
36
|
+
if (maxHeapUsedPercent != null && reading.heapUsedBytes / reading.heapLimitBytes * 100 > maxHeapUsedPercent) throw new MemoryPressureError(reading, `heap ${reading.heapUsedPercent}% used exceeds ${maxHeapUsedPercent}%`);
|
|
37
|
+
return reading;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function read(usage, heap) {
|
|
42
|
+
const heapUsedBytes = usage.heapUsed;
|
|
43
|
+
const heapLimitBytes = heap.heap_size_limit;
|
|
44
|
+
const rssBytes = usage.rss;
|
|
45
|
+
if (![
|
|
46
|
+
heapUsedBytes,
|
|
47
|
+
heapLimitBytes,
|
|
48
|
+
rssBytes
|
|
49
|
+
].every((n) => Number.isFinite(n) && n >= 0) || heapLimitBytes === 0) throw new Error("unexpected memory statistics");
|
|
50
|
+
return {
|
|
51
|
+
heapUsedBytes,
|
|
52
|
+
heapLimitBytes,
|
|
53
|
+
heapUsedPercent: Math.round(heapUsedBytes / heapLimitBytes * 1e3) / 10,
|
|
54
|
+
rssBytes
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
export { MemoryPressureError, memoryDefaultMaxHeapUsedPercent, memoryDefaultName, memoryProbe };
|
|
60
|
+
//# sourceMappingURL=mod.js.map
|
package/dist/mod.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.js","names":["reading: MemoryReading","reason: string","options: MemoryProbeOptions","usage: MemoryUsageSample","heap: HeapStatisticsSample"],"sources":["../src/mod.ts"],"sourcesContent":["/**\n * Memory probe for `@openstatus/health`: compares the process's heap usage\n * with the V8 heap limit, and its resident set size with a byte budget,\n * and fails above either threshold.\n *\n * ```ts\n * import { memoryProbe } from \"@openstatus/health-memory\";\n *\n * const probe = memoryProbe({ maxHeapUsedPercent: 90 });\n * ```\n *\n * Node.js, Deno and Bun only: it uses `node:process` and `node:v8`.\n *\n * @module\n */\n\nimport process from \"node:process\";\nimport { getHeapStatistics } from \"node:v8\";\nimport {\n type Probe,\n ProbeConfigError,\n type ProbeOverrides,\n} from \"@openstatus/health\";\n\n/** Probe name when `name` is unset. */\nexport const memoryDefaultName = \"memory\";\n/** Threshold when neither `maxHeapUsedPercent` nor `maxRssBytes` is set. */\nexport const memoryDefaultMaxHeapUsedPercent = 90;\n\n/** The subset of `process.memoryUsage()` the probe reads. */\nexport interface MemoryUsageSample {\n /** Bytes of V8 heap in use. */\n readonly heapUsed: number;\n /** Resident set size in bytes. */\n readonly rss: number;\n}\n\n/** The subset of `v8.getHeapStatistics()` the probe reads. */\nexport interface HeapStatisticsSample {\n /** The maximum heap size V8 will grow to. */\n readonly heap_size_limit: number;\n}\n\n/** Options for `memoryProbe()`. */\nexport interface MemoryProbeOptions extends ProbeOverrides {\n /** Fail when the heap in use exceeds this percentage of the heap limit. Default `memoryDefaultMaxHeapUsedPercent` unless `maxRssBytes` is set. */\n readonly maxHeapUsedPercent?: number;\n /** Fail when the resident set size exceeds this many bytes. */\n readonly maxRssBytes?: number;\n /** Replacement `process.memoryUsage`, for tests. */\n readonly memoryUsage?: () => MemoryUsageSample;\n /** Replacement `v8.getHeapStatistics`, for tests. */\n readonly heapStatistics?: () => HeapStatisticsSample;\n}\n\n/** What a healthy check resolves with. */\nexport interface MemoryReading {\n /** Bytes of heap in use. */\n readonly heapUsedBytes: number;\n /** The heap limit in bytes. */\n readonly heapLimitBytes: number;\n /** `heapUsedBytes` as a percentage of `heapLimitBytes`, rounded to one decimal. */\n readonly heapUsedPercent: number;\n /** Resident set size in bytes. */\n readonly rssBytes: number;\n}\n\n/** Thrown by the probe when usage is above a threshold. */\nexport class MemoryPressureError extends Error {\n /** The reading that crossed the threshold. */\n readonly reading: MemoryReading;\n\n /** Build the error for `reading` against the threshold described by `reason`. */\n constructor(reading: MemoryReading, reason: string) {\n super(reason);\n this.name = \"MemoryPressureError\";\n this.reading = reading;\n }\n}\n\n/** A probe that fails above `maxHeapUsedPercent` or `maxRssBytes`; non-critical by default. Throws `ProbeConfigError` for a negative or non-finite threshold. */\nexport function memoryProbe(options: MemoryProbeOptions = {}): Probe {\n const maxRssBytes = options.maxRssBytes;\n const maxHeapUsedPercent = options.maxHeapUsedPercent ??\n (maxRssBytes == null ? memoryDefaultMaxHeapUsedPercent : undefined);\n for (\n const [field, value] of [\n [\"maxHeapUsedPercent\", maxHeapUsedPercent],\n [\"maxRssBytes\", maxRssBytes],\n ] as const\n ) {\n if (value != null && (!Number.isFinite(value) || value < 0)) {\n throw new ProbeConfigError(\n \"memoryProbe\",\n field,\n `must be a non-negative number, got ${String(value)}`,\n );\n }\n }\n const memoryUsage = options.memoryUsage ?? (() => process.memoryUsage());\n const heapStatistics = options.heapStatistics ?? getHeapStatistics;\n return {\n name: options.name ?? memoryDefaultName,\n critical: options.critical ?? false,\n timeoutMs: options.timeoutMs,\n skip: options.skip,\n run: () => {\n const reading = read(memoryUsage(), heapStatistics());\n if (maxRssBytes != null && reading.rssBytes > maxRssBytes) {\n throw new MemoryPressureError(\n reading,\n `rss ${reading.rssBytes} bytes exceeds ${maxRssBytes}`,\n );\n }\n if (\n maxHeapUsedPercent != null &&\n (reading.heapUsedBytes / reading.heapLimitBytes) * 100 >\n maxHeapUsedPercent\n ) {\n throw new MemoryPressureError(\n reading,\n `heap ${reading.heapUsedPercent}% used exceeds ${maxHeapUsedPercent}%`,\n );\n }\n return reading;\n },\n };\n}\n\nfunction read(\n usage: MemoryUsageSample,\n heap: HeapStatisticsSample,\n): MemoryReading {\n const heapUsedBytes = usage.heapUsed;\n const heapLimitBytes = heap.heap_size_limit;\n const rssBytes = usage.rss;\n if (\n ![heapUsedBytes, heapLimitBytes, rssBytes].every((n) =>\n Number.isFinite(n) && n >= 0\n ) || heapLimitBytes === 0\n ) {\n throw new Error(\"unexpected memory statistics\");\n }\n return {\n heapUsedBytes,\n heapLimitBytes,\n heapUsedPercent: Math.round((heapUsedBytes / heapLimitBytes) * 1000) / 10,\n rssBytes,\n };\n}\n"],"mappings":";;;;;;AAyBA,MAAa,oBAAoB;;AAEjC,MAAa,kCAAkC;;AAyC/C,IAAa,sBAAb,cAAyC,MAAM;;CAE7C,AAAS;;CAGT,YAAYA,SAAwBC,QAAgB;AAClD,QAAM,OAAO;AACb,OAAK,OAAO;AACZ,OAAK,UAAU;CAChB;AACF;;AAGD,SAAgB,YAAYC,UAA8B,CAAE,GAAS;CACnE,MAAM,cAAc,QAAQ;CAC5B,MAAM,qBAAqB,QAAQ,uBAChC,eAAe,OAAO;AACzB,MACE,MAAM,CAAC,OAAO,MAAM,IAAI,CACtB,CAAC,sBAAsB,kBAAmB,GAC1C,CAAC,eAAe,WAAY,CAC7B,EAED,KAAI,SAAS,UAAU,OAAO,SAAS,MAAM,IAAI,QAAQ,GACvD,OAAM,IAAI,iBACR,eACA,QACC,qCAAqC,OAAO,MAAM,CAAC;CAI1D,MAAM,cAAc,QAAQ,gBAAgB,MAAM,QAAQ,aAAa;CACvE,MAAM,iBAAiB,QAAQ,kBAAkB;AACjD,QAAO;EACL,MAAM,QAAQ,QAAQ;EACtB,UAAU,QAAQ,YAAY;EAC9B,WAAW,QAAQ;EACnB,MAAM,QAAQ;EACd,KAAK,MAAM;GACT,MAAM,UAAU,KAAK,aAAa,EAAE,gBAAgB,CAAC;AACrD,OAAI,eAAe,QAAQ,QAAQ,WAAW,YAC5C,OAAM,IAAI,oBACR,UACC,MAAM,QAAQ,SAAS,iBAAiB,YAAY;AAGzD,OACE,sBAAsB,QACrB,QAAQ,gBAAgB,QAAQ,iBAAkB,MACjD,mBAEF,OAAM,IAAI,oBACR,UACC,OAAO,QAAQ,gBAAgB,iBAAiB,mBAAmB;AAGxE,UAAO;EACR;CACF;AACF;AAED,SAAS,KACPC,OACAC,MACe;CACf,MAAM,gBAAgB,MAAM;CAC5B,MAAM,iBAAiB,KAAK;CAC5B,MAAM,WAAW,MAAM;AACvB,MACG;EAAC;EAAe;EAAgB;CAAS,EAAC,MAAM,CAAC,MAChD,OAAO,SAAS,EAAE,IAAI,KAAK,EAC5B,IAAI,mBAAmB,EAExB,OAAM,IAAI,MAAM;AAElB,QAAO;EACL;EACA;EACA,iBAAiB,KAAK,MAAO,gBAAgB,iBAAkB,IAAK,GAAG;EACvE;CACD;AACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openstatus/health-memory",
|
|
3
|
+
"version": "0.1.4-dev.0",
|
|
4
|
+
"description": "Memory pressure probe for @openstatus/health",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"openstatus",
|
|
7
|
+
"health",
|
|
8
|
+
"healthcheck",
|
|
9
|
+
"memory",
|
|
10
|
+
"heap",
|
|
11
|
+
"rss",
|
|
12
|
+
"system"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "openstatus",
|
|
17
|
+
"url": "https://www.openstatus.dev/"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/openstatusHQ/health",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/openstatusHQ/health.git",
|
|
23
|
+
"directory": "packages/memory/"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/openstatusHQ/health/issues"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"module": "./dist/mod.js",
|
|
30
|
+
"main": "./dist/mod.cjs",
|
|
31
|
+
"types": "./dist/mod.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": {
|
|
35
|
+
"import": "./dist/mod.d.ts",
|
|
36
|
+
"require": "./dist/mod.d.cts"
|
|
37
|
+
},
|
|
38
|
+
"import": "./dist/mod.js",
|
|
39
|
+
"require": "./dist/mod.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"files": [
|
|
45
|
+
"dist/"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=22"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@openstatus/health": "^0.1.4-dev.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": ">=22",
|
|
55
|
+
"tsdown": "^0.12.7",
|
|
56
|
+
"typescript": "^5.8.3"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsdown",
|
|
60
|
+
"prepack": "tsdown",
|
|
61
|
+
"test": "node --experimental-transform-types --test"
|
|
62
|
+
}
|
|
63
|
+
}
|