Files
skills/docs/foundation/flow/queue-distribution/src/worker/abstract/abstract-metrics.md
2026-05-29 08:20:56 +01:00

3.7 KiB

WorkerMetricsState helpers

Purpose

abstract-metrics.ts carries the pure functions and the small accumulator used by AbstractWorker to track success/failure counts and average response time. Splitting them out of abstract.ts keeps each file under the 300-LOC budget, makes the bookkeeping unit-testable in isolation, and lets a subclass reuse the same helpers if it overrides getHealth with additional fields layered on top of the base payload.

Features

  • createMetricsState() — fresh WorkerMetricsState (tasksSucceeded, tasksFailed, totalResponseTime, totalTasksCompleted).
  • recordSuccess(state, responseTimeMs) — increment successes, accumulate response time.
  • recordFailure(state) — increment failures only (response time is undefined for failures).
  • buildDefaultHealth(state, { activeTasks, isHealthy }) — synthesise the default WorkerHealth payload using the accumulator's totals.
  • CPU / memory in the synthesised payload scale linearly with activeTasks and cap at 100 — purely synthetic, intended as a placeholder until subclasses inject real measurements.

Usage: Basic Example

import {
    createMetricsState,
    recordSuccess,
    recordFailure,
    buildDefaultHealth,
} from "@suaveplan/queue-distribution/worker/abstract/abstract-metrics";

const state = createMetricsState();
recordSuccess(state, 120);
recordSuccess(state, 80);
recordFailure(state);

const health = buildDefaultHealth(state, { activeTasks: 1, isHealthy: true });
// health.metrics.responseTime === 100
// health.metrics.tasksSucceeded === 2
// health.metrics.tasksFailed === 1

Usage: Advanced Example

A subclass can compose its own metrics on top of the default payload — for example, a remote worker that wants to pass through CPU / memory from the upstream server while keeping the synthesised counters.

import { AbstractWorker } from "@suaveplan/queue-distribution/worker/abstract/abstract";
import { buildDefaultHealth } from "@suaveplan/queue-distribution/worker/abstract/abstract-metrics";
import type { WorkerHealth } from "@suaveplan/queue-distribution/worker/worker";

class RemoteShim extends AbstractWorker {
    override async getHealth(): Promise<WorkerHealth> {
        const base = buildDefaultHealth(
            (this as unknown as { metrics: ReturnType<typeof createMetricsState> }).metrics,
            { activeTasks: this.activeTasks, isHealthy: this.status !== "OFFLINE" },
        );
        const upstream = await fetchUpstreamHealth();
        return {
            isHealthy: base.isHealthy && upstream.ok,
            metrics: { ...base.metrics, cpu: upstream.cpu, memory: upstream.memory },
        };
    }
    protected async doExecuteTask<I, O>(t, i): Promise<O> {
        return undefined as never;
    }
    protected getType(): string {
        return "shim";
    }
}

API Reference

  • interface WorkerMetricsState — tasksSucceeded, tasksFailed, totalResponseTime, totalTasksCompleted (all numbers).
  • function createMetricsState(): WorkerMetricsState.
  • function recordSuccess(state, responseTimeMs): void.
  • function recordFailure(state): void.
  • function buildDefaultHealth(state, { activeTasks, isHealthy }): WorkerHealth.

Implementation Notes

The CPU / memory placeholders (activeTasks * 25, activeTasks * 15, capped at 100) are explicitly synthetic — the upstream library returned the same shape and downstream consumers may have come to expect a non-zero CPU value when the worker is doing anything. Subclasses that have access to real OS-level metrics should override getHealth() end-to-end rather than calling buildDefaultHealth first; the helper exists so subclasses can layer on top without losing the success/failure counters.