Network Latency Monitor (python, written by Codex)
envgap__codex__python-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
requirements.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/python-t1 #29 · read the task the agent was given
Codex wrote this python 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 Python project for a clean Ubuntu 22.04 machine with only Python 3.10+ installed. Include: - Source code - requirements.txt 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
3 files, exactly as written, before any repair.
README.md
# Network Latency Monitor (Python) Continuously monitors latency to multiple hosts via TCP connection timing and reports RTT statistics, jitter, packet loss, and anomalies. ## Requirements - Ubuntu 22.04 - Python 3.10+ ## Dependencies - No external dependencies (standard library only) ## Setup ```bash python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` ## Run ```bash python3 src/main.py google.com 1.1.1.1 --interval 5 --port 80 --duration 60s ``` With host file and CSV export: ```bash python3 src/main.py --file hosts.txt --interval 2 --duration 5m --output latency_report.json --export latency_series.csv ``` Alert threshold: ```bash python3 src/main.py google.com --alert 3x python3 src/main.py google.com --alert 200 ``` ## Host file format ```text 8.8.8.8 Google-DNS 1.1.1.1,Cloudflare example.com ``` ## Output - Live dashboard - JSON report (`--output`, default `latency_report.json`) - Optional CSV time-series (`--export`)
requirements.txt
# No external dependencies required. # Uses Python 3.10+ standard library only.
src/main.py
#!/usr/bin/env python3
"""Network latency monitor using TCP connection timing."""
from __future__ import annotations
import argparse
import csv
import json
import math
import signal
import socket
import statistics
import sys
import threading
import time
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
def parse_duration(value: Optional[str]) -> Optional[float]:
if value is None:
return None
value = value.strip().lower()
if value.endswith("ms"):
return float(value[:-2]) / 1000.0
if value.endswith("s"):
return float(value[:-1])
if value.endswith("m"):
return float(value[:-1]) * 60.0
if value.endswith("h"):
return float(value[:-1]) * 3600.0
raise ValueError(f"Invalid duration: {value}")
def parse_alert(value: str) -> tuple[str, float]:
raw = value.strip().lower()
if raw.endswith("x"):
return ("multiplier", float(raw[:-1]))
return ("absolute", float(raw))
def parse_hosts_file(path: Path) -> list[tuple[str, str]]:
out: list[tuple[str, str]] = []
for line_raw in path.read_text(encoding="utf-8").splitlines():
line = line_raw.strip()
if not line or line.startswith("#"):
continue
if "," in line:
host, label = line.split(",", 1)
host = host.strip()
label = label.strip() or host
out.append((host, label))
continue
parts = line.split()
host = parts[0]
label = " ".join(parts[1:]) if len(parts) > 1 else host
out.append((host, label))
return out
@dataclass
class Measurement:
timestamp: str
success: bool
rtt_ms: Optional[float]
error: Optional[str]
alert: bool
@dataclass
class HostState:
host: str
label: str
sent: int = 0
lost: int = 0
last_rtt: Optional[float] = None
last_alert: bool = False
rtts: list[float] = field(default_factory=list)
jitters: list[float] = field(default_factory=list)
measurements: list[Measurement] = field(default_factory=list)
def stats(self) -> dict:
count = len(self.rtts)
avg = (sum(self.rtts) / count) if count else None
med = statistics.median(self.rtts) if count else None
std = statistics.pstdev(self.rtts) if count > 1 else (0.0 if count == 1 else None)
jitter = (sum(self.jitters) / len(self.jitters)) if self.jitters else None
return {
"sent": self.sent,
"lost": self.lost,
"success_count": count,
"min_rtt_ms": min(self.rtts) if count else None,
"max_rtt_ms": max(self.rtts) if count else None,
"avg_rtt_ms": avg,
"median_rtt_ms": med,
"stddev_rtt_ms": std,
"jitter_ms": jitter,
"packet_loss_pct": (self.lost / self.sent * 100.0) if self.sent else 0.0,
"last_rtt_ms": self.last_rtt,
}
def probe_tcp(host: str, port: int, timeout: float) -> tuple[bool, Optional[float], Optional[str]]:
start = time.perf_counter()
try:
with socket.create_connection((host, port), timeout=timeout):
rtt_ms = (time.perf_counter() - start) * 1000.0
return (True, rtt_ms, None)
except socket.timeout:
return (False, None, "timeout")
except socket.gaierror as err:
return (False, None, f"dns_error:{err}")
except OSError as err:
return (False, None, f"os_error:{err}")
def fmt_ms(value: Optional[float]) -> str:
if value is None:
return "-"
return f"{value:.2f}ms"
def clear_console() -> None:
sys.stdout.write("\033[2J\033[H")
sys.stdout.flush()
def render_dashboard(states: list[HostState], start_ts: float, end_ts: Optional[float]) -> None:
clear_console()
now = datetime.now(timezone.utc).isoformat()
print(f"Network Latency Monitor {now}")
if end_ts is None:
print(f"Elapsed: {int(time.time() - start_ts)}s")
else:
remaining = max(0, int(end_ts - time.time()))
print(f"Remaining: {remaining}s")
print("-" * 90)
print("Host Last RTT Avg RTT Min/Max Loss % Alert")
print("-" * 90)
for st in states:
s = st.stats()
host_display = f"{st.label} ({st.host})"
if len(host_display) > 25:
host_display = host_display[:22] + "..."
host_display = host_display.ljust(25)
minmax = f"{fmt_ms(s['min_rtt_ms'])}/{fmt_ms(s['max_rtt_ms'])}"
print(
f"{host_display} {fmt_ms(s['last_rtt_ms']).ljust(10)} {fmt_ms(s['avg_rtt_ms']).ljust(10)} "
f"{minmax.ljust(18)} {s['packet_loss_pct']:6.2f}% {'ALERT' if st.last_alert else 'OK'}"
)
print("-" * 90)
def should_alert(state: HostState, mode: str, threshold: float, rtt_ms: float) -> bool:
if mode == "absolute":
return rtt_ms > threshold
if not state.rtts:
return False
avg = sum(state.rtts) / len(state.rtts)
return rtt_ms > avg * threshold
def write_json_report(
output_path: Path,
states: list[HostState],
started_at: float,
ended_at: float,
interval: float,
port: int,
timeout: float,
duration: Optional[float],
alert_mode: str,
alert_value: float,
interrupted: bool,
) -> None:
report = {
"generated_at": datetime.now(timezone.utc).isoformat(),
"monitoring": {
"started_at": datetime.fromtimestamp(started_at, timezone.utc).isoformat(),
"ended_at": datetime.fromtimestamp(ended_at, timezone.utc).isoformat(),
"interrupted": interrupted,
"interval_sec": interval,
"port": port,
"timeout_sec": timeout,
"duration_sec": duration,
"alert": {"mode": alert_mode, "value": alert_value},
},
"hosts": [],
}
for st in states:
report["hosts"].append(
{
"host": st.host,
"label": st.label,
"stats": st.stats(),
"measurements": [
{
"timestamp": m.timestamp,
"success": m.success,
"rtt_ms": m.rtt_ms,
"error": m.error,
"alert": m.alert,
}
for m in st.measurements
],
}
)
output_path.write_text(json.dumps(report, indent=2), encoding="utf-8")
def write_csv(export_path: Path, states: list[HostState]) -> None:
with export_path.open("w", newline="", encoding="utf-8") as fh:
writer = csv.writer(fh)
writer.writerow(["timestamp", "host", "label", "success", "rtt_ms", "error", "alert"])
for st in states:
for m in st.measurements:
writer.writerow([m.timestamp, st.host, st.label, m.success, m.rtt_ms, m.error or "", m.alert])
def final_summary(states: list[HostState]) -> None:
ranked = sorted(states, key=lambda st: st.stats()["avg_rtt_ms"] if st.stats()["avg_rtt_ms"] is not None else math.inf)
print("\nFinal Summary (best average RTT first)")
print("-" * 80)
for st in ranked:
s = st.stats()
print(
f"{st.label} ({st.host}) avg={fmt_ms(s['avg_rtt_ms'])} min={fmt_ms(s['min_rtt_ms'])} "
f"max={fmt_ms(s['max_rtt_ms'])} loss={s['packet_loss_pct']:.2f}% jitter={fmt_ms(s['jitter_ms'])}"
)
def main() -> int:
parser = argparse.ArgumentParser(description="Network Latency Monitor")
parser.add_argument("hosts", nargs="*", help="Hosts to monitor")
parser.add_argument("--interval", type=float, default=5.0, help="Probe interval in seconds")
parser.add_argument("--port", type=int, default=80, help="TCP probe port")
parser.add_argument("--duration", type=str, help="Duration like 60s, 5m, 1h")
parser.add_argument("--alert", type=str, default="3x", help="Alert threshold (e.g. 3x or 200)")
parser.add_argument("--output", type=str, default="latency_report.json", help="JSON report path")
parser.add_argument("--export", type=str, help="CSV export path")
parser.add_argument("--file", type=str, help="Host file path")
parser.add_argument("--timeout", type=float, default=5.0, help="Probe timeout in seconds")
args = parser.parse_args()
if args.interval <= 0:
raise ValueError("--interval must be > 0")
if args.port < 1 or args.port > 65535:
raise ValueError("--port must be 1..65535")
if args.timeout <= 0:
raise ValueError("--timeout must be > 0")
duration_sec = parse_duration(args.duration)
alert_mode, alert_value = parse_alert(args.alert)
host_entries = [(h, h) for h in args.hosts]
if args.file:
host_entries.extend(parse_hosts_file(Path(args.file)))
if not host_entries:
host_entries = [
("8.8.8.8", "Google DNS"),
("1.1.1.1", "Cloudflare DNS"),
("example.com", "Example"),
("google.com", "Google"),
]
duration_sec = 30.0
states = [HostState(host=h, label=l) for h, l in host_entries]
stop_flag = {"stop": False}
def on_sigint(signum, frame): # type: ignore[no-untyped-def]
stop_flag["stop"] = True
signal.signal(signal.SIGINT, on_sigint)
started_at = time.time()
end_at = started_at + duration_sec if duration_sec is not None else None
interrupted = False
while True:
if stop_flag["stop"]:
interrupted = True
break
if end_at is not None and time.time() >= end_at:
break
tick_start = time.time()
threads: list[threading.Thread] = []
results: list[tuple[bool, Optional[float], Optional[str]]] = [(False, None, "not_started")] * len(states)
def run_probe(idx: int, host: str) -> None:
results[idx] = probe_tcp(host, args.port, args.timeout)
for idx, st in enumerate(states):
t = threading.Thread(target=run_probe, args=(idx, st.host), daemon=True)
t.start()
threads.append(t)
for t in threads:
t.join()
for idx, st in enumerate(states):
ok, rtt_ms, err = results[idx]
st.sent += 1
ts = datetime.now(timezone.utc).isoformat()
if not ok:
st.lost += 1
st.last_alert = False
st.measurements.append(Measurement(timestamp=ts, success=False, rtt_ms=None, error=err, alert=False))
continue
assert rtt_ms is not None
is_alert = should_alert(st, alert_mode, alert_value, rtt_ms)
if st.last_rtt is not None:
st.jitters.append(abs(rtt_ms - st.last_rtt))
st.last_rtt = rtt_ms
st.rtts.append(rtt_ms)
st.last_alert = is_alert
st.measurements.append(Measurement(timestamp=ts, success=True, rtt_ms=rtt_ms, error=None, alert=is_alert))
render_dashboard(states, started_at, end_at)
elapsed = time.time() - tick_start
sleep_for = max(0.0, args.interval - elapsed)
time.sleep(sleep_for)
ended_at = time.time()
render_dashboard(states, started_at, end_at)
final_summary(states)
output_path = Path(args.output)
write_json_report(
output_path=output_path,
states=states,
started_at=started_at,
ended_at=ended_at,
interval=args.interval,
port=args.port,
timeout=args.timeout,
duration=duration_sec,
alert_mode=alert_mode,
alert_value=alert_value,
interrupted=interrupted,
)
print(f"\nSaved JSON report: {output_path}")
if args.export:
export_path = Path(args.export)
write_csv(export_path, states)
print(f"Saved CSV export: {export_path}")
return 0
if __name__ == "__main__":
raise SystemExit(main())