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();