@teamscala/service-boot 0.23.337 → 0.23.339

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/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "name": "@teamscala/service-boot",
8
8
  "type": "module",
9
- "version": "0.23.337",
9
+ "version": "0.23.339",
10
10
  "sideEffects": false,
11
11
  "files": [
12
12
  "src"
@@ -19,3 +19,6 @@ export declare function checkDirStale(dir: string, cutoffMs: number): Promise<{
19
19
  bytes: number;
20
20
  fileCount: number;
21
21
  }>;
22
+ /** Parse the root-fs Use% from `df --output=pcent,target` output (pure, for tests).
23
+ * Returns null if no root-mount line is found. */
24
+ export declare function parseRootDiskPct(out: string): number | null;
@@ -14,7 +14,7 @@ import { describe, expect, test, beforeEach, afterEach } from "bun:test";
14
14
  import { mkdtempSync, mkdirSync, writeFileSync, rmSync, utimesSync } from "node:fs";
15
15
  import { join } from "node:path";
16
16
  import { tmpdir } from "node:os";
17
- import { expandGlob, checkDirStale } from "./sweep-host-cache.ts";
17
+ import { expandGlob, checkDirStale, parseRootDiskPct } from "./sweep-host-cache.ts";
18
18
 
19
19
  const DAY = 86_400_000;
20
20
  const now = Date.now();
@@ -83,3 +83,28 @@ describe("checkDirStale", () => {
83
83
  expect(res.fileCount).toBe(0);
84
84
  });
85
85
  });
86
+
87
+ describe("parseRootDiskPct", () => {
88
+ test("extracts root-fs Use% from df --output=pcent,target output", () => {
89
+ const df =
90
+ "Use% Mounted on\n" +
91
+ " 97% /\n" +
92
+ " 45% /home\n";
93
+ expect(parseRootDiskPct(df)).toBe(97);
94
+ });
95
+ test("picks the root mount (/) over nested mounts", () => {
96
+ const df =
97
+ "Use% Mounted on\n" +
98
+ " 10% /proc/x\n" +
99
+ " 76% /\n";
100
+ expect(parseRootDiskPct(df)).toBe(76);
101
+ });
102
+ test("returns null when no root-mount line present", () => {
103
+ const df = "Use% Mounted on\n 40% /mnt/other\n";
104
+ expect(parseRootDiskPct(df)).toBeNull();
105
+ });
106
+ test("returns null on empty / malformed output", () => {
107
+ expect(parseRootDiskPct("")).toBeNull();
108
+ expect(parseRootDiskPct("Use% Mounted on")).toBeNull();
109
+ });
110
+ });
@@ -200,6 +200,14 @@ export async function checkDirStale(
200
200
  return { stale: true, bytes, fileCount };
201
201
  }
202
202
 
203
+ /** Parse the root-fs Use% from `df --output=pcent,target` output (pure, for tests).
204
+ * Returns null if no root-mount line is found. */
205
+ export function parseRootDiskPct(out: string): number | null {
206
+ const rootLine = out.split("\n").find((l) => l.trim().endsWith("/"));
207
+ const m = rootLine?.match(/(\d+)%/);
208
+ return m ? Number(m[1]) : null;
209
+ }
210
+
203
211
  async function sweepHostCache(ctx: {
204
212
  config: { retention?: Record<string, string>; retention_dirs?: Record<string, string> };
205
213
  }): Promise<void> {
@@ -290,32 +298,27 @@ async function sweepHostCache(ctx: {
290
298
  },
291
299
  });
292
300
 
293
- // Disk-threshold monitor — piggybacks on this nightly sweep event (no separate
294
- // interval/infra: `guards-never-run-on-interval`). If `/` usage crosses the
295
- // configured threshold, emit `host.disk.high` (-> event-log/GlitchTip) so a fill
296
- // the sweep can't absorb (external dump, runaway log) is caught within a day.
297
- const highPercent = 90;
301
+ // Disk-threshold observability — rides this periodic sweep (NOT an interval:
302
+ // this job is genuinely periodic application-DATA work — deletion — and the
303
+ // df read piggybacks once per run). Emits through the event-driven surface:
304
+ // logger.warn -> GlitchTip (the paging path, same as host.pressure) +
305
+ // recordEvent for the durable event_log record. Catches a fill faster than
306
+ // the nightly prune can absorb (a cache with no retention entry, an external
307
+ // dump, a runaway log) so the operator learns it within a day, not at 100%.
308
+ const diskHighPct = 90;
298
309
  try {
299
- const dfRes = spawnSync({ name: "df:/", command: ["df", "--output=pcent,target"], timeoutMs: 5000 });
310
+ const dfRes = spawnSync({ name: "df:/", command: ["df", "--output=pcent,target"], timeoutMs: 5_000 });
300
311
  const dfOut = (dfRes.stdout ?? "").trim();
301
- const rootLine = dfOut
302
- .split("\n")
303
- .find((l) => l.trim().endsWith("/"));
304
- const pctMatch = rootLine?.match(/(\d+)%/);
305
- if (pctMatch) {
306
- const usePct = Number(pctMatch[1]);
307
- if (usePct >= highPercent) {
308
- log.warn(
309
- `[host-cache-sweep] disk usage ${usePct}% >= ${highPercent}% threshold — emitting host.disk.high`,
310
- );
311
- recordEvent({
312
- kind: "host.disk.high",
313
- payload: { usePct, threshold: highPercent, df: dfOut, completedAt: new Date().toISOString() },
314
- });
315
- }
312
+ const usePct = parseRootDiskPct(dfOut);
313
+ if (usePct !== null && usePct >= diskHighPct) {
314
+ log.warn("host.disk.high", { usePct, threshold: diskHighPct, df: dfOut });
315
+ recordEvent({
316
+ kind: "host.disk.high",
317
+ payload: { usePct, threshold: diskHighPct, df: dfOut, completedAt: new Date().toISOString() },
318
+ });
316
319
  }
317
320
  } catch (error) {
318
- log.warn(`[host-cache-sweep] disk-threshold check failed: ${String(error)}`);
321
+ log.warn("[host-cache-sweep] disk-threshold read failed: " + String(error));
319
322
  }
320
323
  }
321
324