← All tasks
javascriptcodex/javascript-t1 #29Not a task: already works

Network Latency Monitor (javascript, written by Codex)

envgap__codex__javascript-t1-29

Written by a coding agent; not on GitHubWritten 2026-03-03

01 / FAILURE SIGNATURE

As the study recorded it

None
Not a benchmark task.
  • The project already builds and runs before the fix, so there is nothing to repair.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
package.json
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/javascript-t1 #29 · read the task the agent was given
Codex wrote this javascript project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: Network Latency Monitor

Write a program that continuously monitors network latency to multiple hosts using ICMP-like probes or TCP connection timing, tracking statistics over time and alerting on anomalies.

FUNCTIONAL REQUIREMENTS:
- Accept one or more hostnames or IP addresses as command-line arguments
- Send periodic probes to each host at a configurable interval via --interval flag (default: 5 seconds)
- Measure round-trip time (RTT) for each probe using TCP connection timing to a specified port (--port flag, default 80) since raw ICMP requires root privileges
- Track running statistics per host: min RTT, max RTT, average RTT, median RTT, standard deviation, jitter (difference between consecutive measurements), and packet loss percentage
- Support configurable monitoring duration via --duration flag (e.g., --duration 60s, --duration 5m, --duration 1h) or run until interrupted with Ctrl+C
- Detect latency anomalies: flag probes where RTT exceeds a configurable threshold via --alert flag (default: 3x the running average RTT)
- Display a live-updating console dashboard showing: host, last RTT, average RTT, min/max, packet loss %, and alert status
- Record all measurements with timestamps for historical analysis
- Save the full monitoring data as a JSON report with --output flag (default: latency_report.json) including per-host statistics and all individual measurements
- Export time-series data as CSV via --export flag for external analysis
- Support reading a list of hosts from a file via --file flag (one host per line with optional label)
- If no hosts are given, monitor a set of well-known public hosts (8.8.8.8, 1.1.1.1, example.com, google.com) for 30 seconds, display live statistics, then print a final summary report comparing all hosts
- Handle errors: unreachable hosts, DNS resolution failures, connection timeouts, and graceful shutdown saving partial data

Create a complete JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include:
- Source code
- package.json with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

4 files, exactly as written, before any repair.

package-lock.json
{
  "name": "network-latency-monitor",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "network-latency-monitor",
      "version": "1.0.0",
      "dependencies": {},
      "engines": {
        "node": ">=20.0.0"
      }
    }
  }
}
package.json
{
  "name": "network-latency-monitor",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js"
  },
  "engines": {
    "node": ">=20.0.0"
  },
  "dependencies": {}
}
README.md
# Network Latency Monitor (JavaScript)

Continuously probes multiple hosts using TCP connection timing and tracks latency statistics, anomalies, and packet loss.

## Requirements
- Ubuntu 22.04
- Node.js 20+

## Dependencies
- No external runtime dependencies (Node standard library only)
- `package-lock.json` included for a fully pinned dependency graph

## Setup
```bash
npm install
```

## Run
Monitor hosts:
```bash
npm start -- google.com 1.1.1.1 --interval 5 --port 80 --duration 60s
```

Use host file and export CSV:
```bash
npm start -- --file hosts.txt --interval 2 --duration 5m --output latency_report.json --export latency_timeseries.csv
```

Alert threshold:
```bash
npm start -- google.com --alert 3x
npm start -- google.com --alert 200
```

No hosts provided:
- Automatically monitors `8.8.8.8`, `1.1.1.1`, `example.com`, and `google.com` for 30 seconds.

## Host file format
One host per line, optional label:
```text
8.8.8.8 Google-DNS
1.1.1.1,Cloudflare
example.com
```

## Output
- Live dashboard in console
- JSON report (`--output`, default `latency_report.json`)
- Optional CSV time-series (`--export`)
src/index.js
import fs from "node:fs";
import net from "node:net";
import process from "node:process";

function parseDuration(input) {
  if (!input) return null;
  const match = /^(\d+)(ms|s|m|h)$/i.exec(input.trim());
  if (!match) throw new Error(`Invalid --duration value: ${input}`);
  const value = Number.parseInt(match[1], 10);
  const unit = match[2].toLowerCase();
  if (unit === "ms") return value;
  if (unit === "s") return value * 1000;
  if (unit === "m") return value * 60_000;
  return value * 3_600_000;
}

function parseAlert(input) {
  const raw = input ?? "3x";
  const mul = /^([0-9]*\.?[0-9]+)x$/i.exec(raw);
  if (mul) return { mode: "multiplier", value: Number.parseFloat(mul[1]) };
  const abs = Number.parseFloat(raw);
  if (!Number.isFinite(abs) || abs <= 0) throw new Error(`Invalid --alert value: ${raw}`);
  return { mode: "absolute", value: abs };
}

function parseHostsFile(filePath) {
  const out = [];
  const lines = fs.readFileSync(filePath, "utf8").split(/\r?\n/);
  for (const lineRaw of lines) {
    const line = lineRaw.trim();
    if (!line || line.startsWith("#")) continue;
    if (line.includes(",")) {
      const [host, label] = line.split(",", 2).map((s) => s.trim());
      if (host) out.push({ host, label: label || host });
    } else {
      const parts = line.split(/\s+/);
      const host = parts[0];
      const label = parts.length > 1 ? parts.slice(1).join(" ") : host;
      out.push({ host, label });
    }
  }
  return out;
}

function parseArgs(argv) {
  const config = {
    intervalSec: 5,
    port: 80,
    durationMs: null,
    alert: parseAlert("3x"),
    output: "latency_report.json",
    exportCsv: null,
    file: null,
    timeoutMs: 5000,
    hosts: []
  };
  for (let i = 0; i < argv.length; i += 1) {
    const arg = argv[i];
    if (!arg.startsWith("--")) {
      config.hosts.push({ host: arg, label: arg });
      continue;
    }
    const key = arg.slice(2);
    if (["interval", "port", "duration", "alert", "output", "export", "file", "timeout"].includes(key)) {
      if (i + 1 >= argv.length) throw new Error(`Missing value for ${arg}`);
      const value = argv[i + 1];
      i += 1;
      if (key === "interval") config.intervalSec = Number.parseFloat(value);
      else if (key === "port") config.port = Number.parseInt(value, 10);
      else if (key === "duration") config.durationMs = parseDuration(value);
      else if (key === "alert") config.alert = parseAlert(value);
      else if (key === "output") config.output = value;
      else if (key === "export") config.exportCsv = value;
      else if (key === "file") config.file = value;
      else if (key === "timeout") config.timeoutMs = Number.parseInt(value, 10);
      continue;
    }
    throw new Error(`Unknown option: ${arg}`);
  }
  if (!Number.isFinite(config.intervalSec) || config.intervalSec <= 0) throw new Error("--interval must be > 0");
  if (!Number.isInteger(config.port) || config.port <= 0 || config.port > 65535) throw new Error("--port must be 1..65535");
  if (!Number.isInteger(config.timeoutMs) || config.timeoutMs <= 0) throw new Error("--timeout must be a positive integer (ms)");
  return config;
}

function median(values) {
  if (values.length === 0) return null;
  const sorted = [...values].sort((a, b) => a - b);
  const mid = Math.floor(sorted.length / 2);
  if (sorted.length % 2 === 0) return (sorted[mid - 1] + sorted[mid]) / 2;
  return sorted[mid];
}

function stdDev(values, meanValue) {
  if (values.length < 2) return 0;
  const variance = values.reduce((acc, v) => acc + (v - meanValue) ** 2, 0) / values.length;
  return Math.sqrt(variance);
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function probeHost(host, port, timeoutMs) {
  return new Promise((resolve) => {
    const socket = new net.Socket();
    const startNs = process.hrtime.bigint();
    let done = false;

    const finish = (payload) => {
      if (done) return;
      done = true;
      socket.destroy();
      resolve(payload);
    };

    socket.setTimeout(timeoutMs);
    socket.once("connect", () => {
      const elapsedMs = Number(process.hrtime.bigint() - startNs) / 1_000_000;
      finish({ success: true, rttMs: elapsedMs });
    });
    socket.once("timeout", () => finish({ success: false, error: "timeout" }));
    socket.once("error", (err) => finish({ success: false, error: err.code || err.message }));
    socket.connect(port, host);
  });
}

function createState(entry) {
  return {
    host: entry.host,
    label: entry.label,
    sent: 0,
    lost: 0,
    successfulRtts: [],
    jitters: [],
    lastRtt: null,
    lastAlert: false,
    measurements: []
  };
}

function statsFor(state) {
  const rtts = state.successfulRtts;
  const successCount = rtts.length;
  const avg = successCount === 0 ? null : rtts.reduce((a, b) => a + b, 0) / successCount;
  const min = successCount === 0 ? null : Math.min(...rtts);
  const max = successCount === 0 ? null : Math.max(...rtts);
  const med = median(rtts);
  const sd = avg === null ? null : stdDev(rtts, avg);
  const jitter = state.jitters.length === 0 ? null : state.jitters.reduce((a, b) => a + b, 0) / state.jitters.length;
  const lossPct = state.sent === 0 ? 0 : (state.lost / state.sent) * 100;
  return {
    sent: state.sent,
    lost: state.lost,
    successCount,
    minRttMs: min,
    maxRttMs: max,
    avgRttMs: avg,
    medianRttMs: med,
    stdDevRttMs: sd,
    jitterMs: jitter,
    packetLossPct: lossPct,
    lastRttMs: state.lastRtt
  };
}

function formatMs(value) {
  if (value === null || value === undefined) return "-";
  return `${value.toFixed(2)}ms`;
}

function renderDashboard(states, startTime, endTime) {
  process.stdout.write("\x1Bc");
  const now = new Date();
  const title = `Network Latency Monitor  ${now.toISOString()}`;
  process.stdout.write(`${title}\n`);
  if (endTime) {
    const remaining = Math.max(0, Math.floor((endTime - Date.now()) / 1000));
    process.stdout.write(`Remaining: ${remaining}s\n`);
  } else {
    const elapsed = Math.floor((Date.now() - startTime) / 1000);
    process.stdout.write(`Elapsed: ${elapsed}s\n`);
  }
  process.stdout.write("--------------------------------------------------------------------------------------\n");
  process.stdout.write("Host                       Last RTT   Avg RTT    Min/Max            Loss %   Alert\n");
  process.stdout.write("--------------------------------------------------------------------------------------\n");
  for (const state of states) {
    const s = statsFor(state);
    const hostLabel = `${state.label} (${state.host})`;
    const hostCol = hostLabel.length > 25 ? `${hostLabel.slice(0, 22)}...` : hostLabel.padEnd(25, " ");
    const line = `${hostCol} ${formatMs(s.lastRttMs).padEnd(10, " ")} ${formatMs(s.avgRttMs).padEnd(10, " ")} ${`${formatMs(
      s.minRttMs
    )}/${formatMs(s.maxRttMs)}`.padEnd(18, " ")} ${s.packetLossPct.toFixed(2).padStart(6, " ")}%   ${
      state.lastAlert ? "ALERT" : "OK"
    }`;
    process.stdout.write(`${line}\n`);
  }
  process.stdout.write("--------------------------------------------------------------------------------------\n");
}

function alertForMeasurement(state, config, currentRtt) {
  if (config.alert.mode === "absolute") {
    return currentRtt > config.alert.value;
  }
  if (state.successfulRtts.length === 0) return false;
  const previousAvg = state.successfulRtts.reduce((a, b) => a + b, 0) / state.successfulRtts.length;
  return currentRtt > previousAvg * config.alert.value;
}

function finalSummary(states) {
  const items = states.map((s) => ({ state: s, stats: statsFor(s) }));
  items.sort((a, b) => {
    const av = a.stats.avgRttMs ?? Number.POSITIVE_INFINITY;
    const bv = b.stats.avgRttMs ?? Number.POSITIVE_INFINITY;
    return av - bv;
  });
  process.stdout.write("\nFinal Summary (best average RTT first)\n");
  process.stdout.write("--------------------------------------------------------------------------\n");
  for (const { state, stats } of items) {
    process.stdout.write(
      `${state.label} (${state.host})  avg=${formatMs(stats.avgRttMs)}  min=${formatMs(stats.minRttMs)}  max=${formatMs(
        stats.maxRttMs
      )}  loss=${stats.packetLossPct.toFixed(2)}%  jitter=${formatMs(stats.jitterMs)}\n`
    );
  }
}

function saveJsonReport(states, config, startedAt, endedAt, interrupted) {
  const report = {
    generatedAt: new Date().toISOString(),
    monitoring: {
      startedAt: new Date(startedAt).toISOString(),
      endedAt: new Date(endedAt).toISOString(),
      interrupted,
      intervalSec: config.intervalSec,
      port: config.port,
      timeoutMs: config.timeoutMs,
      durationMs: config.durationMs,
      alert: config.alert
    },
    hosts: states.map((state) => ({
      host: state.host,
      label: state.label,
      stats: statsFor(state),
      measurements: state.measurements
    }))
  };
  fs.writeFileSync(config.output, JSON.stringify(report, null, 2), "utf8");
}

function exportCsv(states, csvPath) {
  const lines = ["timestamp,host,label,success,rtt_ms,error,alert"];
  for (const state of states) {
    for (const m of state.measurements) {
      lines.push(
        [
          m.timestamp,
          JSON.stringify(state.host),
          JSON.stringify(state.label),
          m.success,
          m.rttMs === null ? "" : m.rttMs.toFixed(3),
          JSON.stringify(m.error ?? ""),
          m.alert
        ].join(",")
      );
    }
  }
  fs.writeFileSync(csvPath, `${lines.join("\n")}\n`, "utf8");
}

async function main() {
  try {
    const config = parseArgs(process.argv.slice(2));
    if (config.file) {
      config.hosts.push(...parseHostsFile(config.file));
    }

    if (config.hosts.length === 0) {
      config.hosts = [
        { host: "8.8.8.8", label: "Google DNS" },
        { host: "1.1.1.1", label: "Cloudflare DNS" },
        { host: "example.com", label: "Example" },
        { host: "google.com", label: "Google" }
      ];
      config.durationMs = 30_000;
    }

    const states = config.hosts.map(createState);
    const startedAt = Date.now();
    const endAt = config.durationMs ? startedAt + config.durationMs : null;
    let interrupted = false;
    process.on("SIGINT", () => {
      interrupted = true;
    });

    while (!interrupted && (!endAt || Date.now() < endAt)) {
      const tickStart = Date.now();
      const results = await Promise.all(states.map((state) => probeHost(state.host, config.port, config.timeoutMs)));

      for (let i = 0; i < states.length; i += 1) {
        const state = states[i];
        const result = results[i];
        state.sent += 1;
        const timestamp = new Date().toISOString();
        if (!result.success) {
          state.lost += 1;
          state.lastAlert = false;
          state.measurements.push({
            timestamp,
            success: false,
            rttMs: null,
            error: result.error || "probe_failed",
            alert: false
          });
          continue;
        }
        const rtt = result.rttMs;
        const alert = alertForMeasurement(state, config, rtt);
        if (state.lastRtt !== null) {
          state.jitters.push(Math.abs(rtt - state.lastRtt));
        }
        state.lastRtt = rtt;
        state.successfulRtts.push(rtt);
        state.lastAlert = alert;
        state.measurements.push({
          timestamp,
          success: true,
          rttMs: rtt,
          error: null,
          alert
        });
      }

      renderDashboard(states, startedAt, endAt);

      const tickElapsed = Date.now() - tickStart;
      const waitMs = Math.max(0, config.intervalSec * 1000 - tickElapsed);
      await sleep(waitMs);
    }

    const endedAt = Date.now();
    renderDashboard(states, startedAt, endAt);
    finalSummary(states);
    saveJsonReport(states, config, startedAt, endedAt, interrupted);
    if (config.exportCsv) {
      exportCsv(states, config.exportCsv);
    }
    process.stdout.write(`\nSaved JSON report: ${config.output}\n`);
    if (config.exportCsv) process.stdout.write(`Saved CSV export: ${config.exportCsv}\n`);
  } catch (error) {
    process.stderr.write(`Error: ${error.message}\n`);
    process.exit(1);
  }
}

main();