Functional Weave
Code in TypeScript

net.device-status@1.0.0

impl/typescript.ts

2,811 bytes · the TypeScript implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { type LatencySummary } from "./net_latency_summary.ts";  ← from net.latency-summary ^1.0.0 · built alongside by fune
import { type StatusResult, type StatusThresholds } from "./net_device_status_types.ts";

// Integer arithmetic only, so the reason text is identical in every language.
function percent(basisPoints: number): string {
  return `${Math.floor(basisPoints / 100)}.${String(basisPoints % 100).padStart(2, "0")}%`;
}

function ms(us: number): string {
  return `${Math.floor(us / 1000)}.${String(us % 1000).padStart(3, "0")} ms`;
}

/**
 * Up, degraded or down, and every reason, from one latency summary.
 *
 * Down wins outright: once a device is down, its p95 and jitter are beside
 * the point. Otherwise every threshold crossed is listed, loss first, so an
 * alert says all that is wrong at once.
 */
export function deviceStatus(summary: LatencySummary, thresholds: StatusThresholds): StatusResult {
  const { sent, received, lost } = summary;
  if (sent < 1 || received < 0 || lost < 0 || received + lost !== sent) {
    throw new RangeError(`summary is inconsistent: sent ${sent}, received ${received}, lost ${lost}`);
  }
  const { degradedLossBasisPoints: degLoss, downLossBasisPoints: downLoss, degradedP95Us: p95Limit, degradedJitterUs: jitterLimit } = thresholds;
  if (!Number.isInteger(downLoss) || downLoss < 1 || downLoss > 10000) {
    throw new RangeError(`downLossBasisPoints must be 1 to 10000, received ${downLoss}`);
  }
  if (!Number.isInteger(degLoss) || degLoss < 1 || degLoss > downLoss) {
    throw new RangeError(`degradedLossBasisPoints must be 1 to downLossBasisPoints (${downLoss}), received ${degLoss}`);
  }
  if (!Number.isInteger(p95Limit) || p95Limit < 1) {
    throw new RangeError(`degradedP95Us must be at least 1, received ${p95Limit}`);
  }
  if (jitterLimit !== null && jitterLimit !== undefined && (!Number.isInteger(jitterLimit) || jitterLimit < 1)) {
    throw new RangeError(`degradedJitterUs must be null or at least 1, received ${jitterLimit}`);
  }

  if (received === 0) {
    return { status: "down", reasons: [`no replies: ${lost} of ${sent} probes lost`] };
  }
  const loss = summary.lossBasisPoints;
  if (loss >= downLoss) {
    return { status: "down", reasons: [`loss ${percent(loss)} at or above the down threshold ${percent(downLoss)}`] };
  }
  const reasons: string[] = [];
  if (loss >= degLoss) reasons.push(`loss ${percent(loss)} at or above ${percent(degLoss)}`);
  if (summary.p95Us !== null && summary.p95Us >= p95Limit) {
    reasons.push(`p95 ${ms(summary.p95Us)} at or above ${ms(p95Limit)}`);
  }
  if (jitterLimit !== null && jitterLimit !== undefined && summary.jitterUs !== null && summary.jitterUs >= jitterLimit) {
    reasons.push(`jitter ${ms(summary.jitterUs)} at or above ${ms(jitterLimit)}`);
  }
  return { status: reasons.length > 0 ? "degraded" : "up", reasons };
}