170 lines · 5.4 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import * as DateTime from "effect/DateTime";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Stream from "effect/Stream"; import * as ResourceTelemetry from "../resourceTelemetry/ResourceTelemetry.ts";import type { ResourceTelemetryHistoryWithLegacyBuckets } from "../resourceTelemetry/ResourceTelemetryHistory.ts";import * as ProcessResourceMonitor from "./ProcessResourceMonitor.ts"; describe("ProcessResourceMonitor", () => {  it.effect("projects resource telemetry history into the legacy diagnostics contract", () =>    Effect.gen(function* () {      const readAt = DateTime.makeUnsafe("2026-05-05T10:00:00.000Z");      const history: ResourceTelemetryHistoryWithLegacyBuckets = {        readAt,        windowMs: 60_000,        bucketMs: 10_000,        sampleIntervalMs: 1_000,        retainedSampleCount: 2,        buckets: [          {            startedAt: DateTime.makeUnsafe("2026-05-05T09:59:50.000Z"),            endedAt: readAt,            avgCpuPercent: 15,            maxCpuPercent: 25,            maxRssBytes: 4_096,            ioReadBytes: 1_024,            ioWriteBytes: 2_048,            maxProcessCount: 2,          },        ],        legacyBackendBuckets: [          {            startedAt: DateTime.makeUnsafe("2026-05-05T09:59:50.000Z"),            endedAt: readAt,            avgCpuPercent: 5,            maxCpuPercent: 8,            maxRssBytes: 4_096,            ioReadBytes: 1_024,            ioWriteBytes: 2_048,            maxProcessCount: 1,          },        ],        topProcesses: [          {            identity: { pid: process.pid, startTimeMs: 100 },            ppid: 1,            depth: 0,            name: "node",            command: "t3 server",            category: "server",            firstSeenAt: DateTime.makeUnsafe("2026-05-05T09:59:55.000Z"),            lastSeenAt: readAt,            currentCpuPercent: 5,            avgCpuPercent: 4,            maxCpuPercent: 8,            cpuTimeMs: 1_500,            currentRssBytes: 2_048,            peakRssBytes: 4_096,            ioReadBytes: 1_024,            ioWriteBytes: 2_048,            ioSemantics: "storage",            sampleCount: 2,          },          {            identity: { pid: 5_000, startTimeMs: 200 },            ppid: 1,            depth: 0,            name: "electron",            command: "electron",            category: "electron-main",            firstSeenAt: DateTime.makeUnsafe("2026-05-05T09:59:55.000Z"),            lastSeenAt: readAt,            currentCpuPercent: 50,            avgCpuPercent: 40,            maxCpuPercent: 80,            cpuTimeMs: 15_000,            currentRssBytes: 20_480,            peakRssBytes: 40_960,            ioReadBytes: 10_240,            ioWriteBytes: 20_480,            ioSemantics: "storage",            sampleCount: 2,          },        ],        health: {          native: {            status: "degraded",            lastSampleAt: Option.some(readAt),            lastError: Option.some("collector stalled"),          },          desktop: {            status: "healthy",            lastSampleAt: Option.some(readAt),            lastError: Option.none(),          },          sidecarVersion: Option.some("0.1.0"),          sidecarPid: Option.some(9_000),          restartCount: 1,          collectionDurationMicros: 250,          scannedProcessCount: 80,          retainedProcessCount: 2,          inaccessibleProcessCount: 0,        },      };      const telemetry: ResourceTelemetry.ResourceTelemetry["Service"] = {        latest: Effect.die("unused"),        changes: Stream.empty,        subscribe: Effect.die("unused"),        readHistory: () => Effect.succeed(history),        refresh: Effect.die("unused"),        validateProcessIdentity: () => Effect.die("unused"),        retry: Effect.die("unused"),      };      const layer = ProcessResourceMonitor.layer.pipe(        Layer.provide(          Layer.succeed(            ResourceTelemetry.ResourceTelemetry,            ResourceTelemetry.ResourceTelemetry.of(telemetry),          ),        ),      );       const result = yield* Effect.service(ProcessResourceMonitor.ProcessResourceMonitor).pipe(        Effect.flatMap((monitor) =>          monitor.readHistory({            windowMs: 60_000,            bucketMs: 10_000,          }),        ),        Effect.provide(layer),      );       expect(result.totalCpuSecondsApprox).toBe(1.5);      expect(result.topProcesses).toEqual([        {          processKey: `${process.pid}:100`,          pid: process.pid,          ppid: 1,          command: "t3 server",          depth: 0,          isServerRoot: true,          firstSeenAt: DateTime.makeUnsafe("2026-05-05T09:59:55.000Z"),          lastSeenAt: readAt,          currentCpuPercent: 5,          avgCpuPercent: 4,          maxCpuPercent: 8,          cpuSecondsApprox: 1.5,          currentRssBytes: 2_048,          maxRssBytes: 4_096,          sampleCount: 2,        },      ]);      expect(result.buckets[0]).toMatchObject({        avgCpuPercent: 5,        maxCpuPercent: 8,        maxRssBytes: 4_096,        maxProcessCount: 1,      });      expect(result.error).toEqual(        Option.some({          failureTag: "ProcessDiagnosticsQueryFailedError",          message: "collector stalled",        }),      );    }),  );});