← All tasks
cppcodex/cpp-t1 #29Not a task: already works

Network Latency Monitor (cpp, written by Codex)

envgap__codex__cpp-t1-29

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

01 / FAILURE SIGNATURE

As the study recorded it

No identifying execution failure has been captured.
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
CMakeLists.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/cpp-t1 #29 · read the task the agent was given
Codex wrote this cpp 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 C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include:
- Source code
- CMakeLists.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.

CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(network_latency_monitor LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

include(FetchContent)

FetchContent_Declare(
  nlohmann_json
  GIT_REPOSITORY https://github.com/nlohmann/json.git
  GIT_TAG v3.11.3
)

FetchContent_MakeAvailable(nlohmann_json)

add_executable(network_latency_monitor src/main.cpp)
target_link_libraries(network_latency_monitor PRIVATE nlohmann_json::nlohmann_json)
README.md
# Network Latency Monitor (C++)

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

## Requirements
- Ubuntu 22.04
- G++ 12+
- CMake 3.22+

## Dependencies (Pinned)
- `nlohmann/json` `v3.11.3` for JSON report generation

## Build
```bash
cmake -S . -B build
cmake --build build -j
```

## Run
```bash
./build/network_latency_monitor google.com 1.1.1.1 --interval 5 --port 80 --duration 60s
```

With host file and CSV export:
```bash
./build/network_latency_monitor --file hosts.txt --interval 2 --duration 5m --output latency_report.json --export latency_series.csv
```

Alert threshold:
```bash
./build/network_latency_monitor google.com --alert 3x
./build/network_latency_monitor google.com --alert 200
```

## Host file format
```text
8.8.8.8 Google-DNS
1.1.1.1,Cloudflare
example.com
```

## Output
- Live terminal dashboard
- JSON report (`--output`, default `latency_report.json`)
- Optional CSV time-series (`--export`)
src/main.cpp
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <csignal>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <future>
#include <iomanip>
#include <iostream>
#include <map>
#include <numeric>
#include <optional>
#include <regex>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <thread>
#include <utility>
#include <vector>

#include <fcntl.h>
#include <netdb.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>

#include <nlohmann/json.hpp>

using json = nlohmann::json;
namespace fs = std::filesystem;

struct HostTarget {
  std::string host;
  std::string label;
};

struct Measurement {
  std::string timestamp;
  bool success;
  std::optional<double> rtt_ms;
  std::string error;
  bool alert;
};

struct HostState {
  std::string host;
  std::string label;
  int sent = 0;
  int lost = 0;
  std::optional<double> last_rtt;
  bool last_alert = false;
  std::vector<double> rtts;
  std::vector<double> jitters;
  std::vector<Measurement> measurements;
};

struct AlertConfig {
  std::string mode = "multiplier"; // multiplier|absolute
  double value = 3.0;
};

struct Config {
  double interval_sec = 5.0;
  int port = 80;
  std::optional<int64_t> duration_ms;
  AlertConfig alert;
  std::string output = "latency_report.json";
  std::optional<std::string> export_csv;
  std::optional<std::string> hosts_file;
  int timeout_ms = 5000;
  std::vector<HostTarget> hosts;
};

static std::atomic<bool> g_stop{false};

static std::string now_iso() {
  auto now = std::chrono::system_clock::now();
  std::time_t t = std::chrono::system_clock::to_time_t(now);
  std::tm tm {};
#if defined(_WIN32)
  gmtime_s(&tm, &t);
#else
  gmtime_r(&t, &tm);
#endif
  char buf[32];
  std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", &tm);
  return std::string(buf);
}

static std::string trim(const std::string& s) {
  size_t b = 0;
  while (b < s.size() && std::isspace(static_cast<unsigned char>(s[b]))) ++b;
  size_t e = s.size();
  while (e > b && std::isspace(static_cast<unsigned char>(s[e - 1]))) --e;
  return s.substr(b, e - b);
}

static std::optional<int64_t> parse_duration_ms(const std::optional<std::string>& raw) {
  if (!raw.has_value()) return std::nullopt;
  static const std::regex rx(R"(^(\d+)(ms|s|m|h)$)", std::regex::icase);
  std::smatch m;
  if (!std::regex_match(*raw, m, rx)) throw std::runtime_error("Invalid --duration value: " + *raw);
  int64_t value = std::stoll(m[1].str());
  std::string unit = m[2].str();
  std::transform(unit.begin(), unit.end(), unit.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
  if (unit == "ms") return value;
  if (unit == "s") return value * 1000;
  if (unit == "m") return value * 60 * 1000;
  return value * 60 * 60 * 1000;
}

static AlertConfig parse_alert(const std::string& raw) {
  AlertConfig a;
  std::string v = raw;
  std::transform(v.begin(), v.end(), v.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
  if (!v.empty() && v.back() == 'x') {
    a.mode = "multiplier";
    a.value = std::stod(v.substr(0, v.size() - 1));
  } else {
    a.mode = "absolute";
    a.value = std::stod(v);
  }
  if (a.value <= 0.0) throw std::runtime_error("--alert must be positive");
  return a;
}

static std::vector<HostTarget> parse_hosts_file(const std::string& path) {
  std::ifstream in(path);
  if (!in) throw std::runtime_error("Failed to open hosts file: " + path);
  std::vector<HostTarget> out;
  std::string line;
  while (std::getline(in, line)) {
    line = trim(line);
    if (line.empty() || line[0] == '#') continue;
    auto comma = line.find(',');
    if (comma != std::string::npos) {
      std::string host = trim(line.substr(0, comma));
      std::string label = trim(line.substr(comma + 1));
      if (!host.empty()) out.push_back({host, label.empty() ? host : label});
      continue;
    }
    std::istringstream iss(line);
    std::string host;
    iss >> host;
    std::string rest;
    std::getline(iss, rest);
    rest = trim(rest);
    if (!host.empty()) out.push_back({host, rest.empty() ? host : rest});
  }
  return out;
}

static Config parse_args(int argc, char** argv) {
  Config cfg;
  std::optional<std::string> raw_duration;
  for (int i = 1; i < argc; ++i) {
    std::string arg = argv[i];
    if (arg.rfind("--", 0) != 0) {
      cfg.hosts.push_back({arg, arg});
      continue;
    }
    if (i + 1 >= argc) throw std::runtime_error("Missing value for " + arg);
    std::string val = argv[++i];
    if (arg == "--interval") cfg.interval_sec = std::stod(val);
    else if (arg == "--port") cfg.port = std::stoi(val);
    else if (arg == "--duration") raw_duration = val;
    else if (arg == "--alert") cfg.alert = parse_alert(val);
    else if (arg == "--output") cfg.output = val;
    else if (arg == "--export") cfg.export_csv = val;
    else if (arg == "--file") cfg.hosts_file = val;
    else if (arg == "--timeout") cfg.timeout_ms = std::stoi(val);
    else throw std::runtime_error("Unknown option: " + arg);
  }
  cfg.duration_ms = parse_duration_ms(raw_duration);
  if (cfg.interval_sec <= 0) throw std::runtime_error("--interval must be > 0");
  if (cfg.port < 1 || cfg.port > 65535) throw std::runtime_error("--port must be 1..65535");
  if (cfg.timeout_ms <= 0) throw std::runtime_error("--timeout must be > 0");
  return cfg;
}

static std::optional<double> median(std::vector<double> values) {
  if (values.empty()) return std::nullopt;
  std::sort(values.begin(), values.end());
  size_t mid = values.size() / 2;
  if (values.size() % 2 == 0) return (values[mid - 1] + values[mid]) / 2.0;
  return values[mid];
}

static std::optional<double> stddev(const std::vector<double>& values, std::optional<double> mean) {
  if (values.empty() || !mean.has_value()) return std::nullopt;
  if (values.size() < 2) return 0.0;
  double sum = 0.0;
  for (double v : values) sum += (v - *mean) * (v - *mean);
  return std::sqrt(sum / static_cast<double>(values.size()));
}

static std::optional<double> average(const std::vector<double>& values) {
  if (values.empty()) return std::nullopt;
  return std::accumulate(values.begin(), values.end(), 0.0) / static_cast<double>(values.size());
}

struct ProbeResult {
  bool success = false;
  std::optional<double> rtt_ms;
  std::string error;
};

static ProbeResult probe_tcp(const std::string& host, int port, int timeout_ms) {
  addrinfo hints{};
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = IPPROTO_TCP;

  addrinfo* result = nullptr;
  int gai = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &result);
  if (gai != 0) {
    return {false, std::nullopt, std::string("dns_error:") + gai_strerror(gai)};
  }

  ProbeResult out{false, std::nullopt, "unreachable"};
  for (addrinfo* rp = result; rp != nullptr; rp = rp->ai_next) {
    int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
    if (fd < 0) continue;

    int flags = fcntl(fd, F_GETFL, 0);
    if (flags >= 0) fcntl(fd, F_SETFL, flags | O_NONBLOCK);

    auto start = std::chrono::steady_clock::now();
    int rc = connect(fd, rp->ai_addr, rp->ai_addrlen);
    if (rc == 0) {
      auto ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
      close(fd);
      freeaddrinfo(result);
      return {true, ms, ""};
    }

    if (errno == EINPROGRESS) {
      fd_set wfds;
      FD_ZERO(&wfds);
      FD_SET(fd, &wfds);
      timeval tv{};
      tv.tv_sec = timeout_ms / 1000;
      tv.tv_usec = (timeout_ms % 1000) * 1000;
      int sel = select(fd + 1, nullptr, &wfds, nullptr, &tv);
      if (sel > 0 && FD_ISSET(fd, &wfds)) {
        int so_err = 0;
        socklen_t len = sizeof(so_err);
        getsockopt(fd, SOL_SOCKET, SO_ERROR, &so_err, &len);
        if (so_err == 0) {
          auto ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
          close(fd);
          freeaddrinfo(result);
          return {true, ms, ""};
        }
        out.error = std::string("connect_error:") + std::strerror(so_err);
      } else if (sel == 0) {
        out.error = "timeout";
      } else {
        out.error = std::string("select_error:") + std::strerror(errno);
      }
    } else {
      out.error = std::string("connect_error:") + std::strerror(errno);
    }

    close(fd);
  }

  freeaddrinfo(result);
  return out;
}

static std::string fmt_ms(const std::optional<double>& v) {
  if (!v.has_value()) return "-";
  std::ostringstream oss;
  oss << std::fixed << std::setprecision(2) << *v << "ms";
  return oss.str();
}

static json stats_json(const HostState& st) {
  auto avg = average(st.rtts);
  auto med = median(st.rtts);
  auto sd = stddev(st.rtts, avg);
  auto jitter = average(st.jitters);
  json j;
  j["sent"] = st.sent;
  j["lost"] = st.lost;
  j["success_count"] = st.rtts.size();
  if (st.rtts.empty()) {
    j["min_rtt_ms"] = nullptr;
    j["max_rtt_ms"] = nullptr;
  } else {
    j["min_rtt_ms"] = *std::min_element(st.rtts.begin(), st.rtts.end());
    j["max_rtt_ms"] = *std::max_element(st.rtts.begin(), st.rtts.end());
  }
  j["avg_rtt_ms"] = avg.has_value() ? json(*avg) : json(nullptr);
  j["median_rtt_ms"] = med.has_value() ? json(*med) : json(nullptr);
  j["stddev_rtt_ms"] = sd.has_value() ? json(*sd) : json(nullptr);
  j["jitter_ms"] = jitter.has_value() ? json(*jitter) : json(nullptr);
  j["packet_loss_pct"] = st.sent == 0 ? 0.0 : (st.lost * 100.0 / st.sent);
  j["last_rtt_ms"] = st.last_rtt.has_value() ? json(*st.last_rtt) : json(nullptr);
  return j;
}

static bool should_alert(const HostState& st, const AlertConfig& alert, double rtt) {
  if (alert.mode == "absolute") return rtt > alert.value;
  auto avg = average(st.rtts);
  if (!avg.has_value()) return false;
  return rtt > (*avg * alert.value);
}

static void render_dashboard(const std::vector<HostState>& states, int64_t started_ms, std::optional<int64_t> end_ms) {
  std::cout << "\033[2J\033[H";
  std::cout << "Network Latency Monitor  " << now_iso() << "\n";
  int64_t now = std::chrono::duration_cast<std::chrono::milliseconds>(
      std::chrono::system_clock::now().time_since_epoch()).count();
  if (end_ms.has_value()) {
    int64_t rem = std::max<int64_t>(0, (*end_ms - now) / 1000);
    std::cout << "Remaining: " << rem << "s\n";
  } else {
    int64_t elapsed = (now - started_ms) / 1000;
    std::cout << "Elapsed: " << elapsed << "s\n";
  }
  std::cout << "--------------------------------------------------------------------------------------\n";
  std::cout << "Host                      Last RTT   Avg RTT    Min/Max            Loss %   Alert\n";
  std::cout << "--------------------------------------------------------------------------------------\n";
  for (const auto& st : states) {
    json s = stats_json(st);
    std::string host = st.label + " (" + st.host + ")";
    if (host.size() > 25) host = host.substr(0, 22) + "...";
    if (host.size() < 25) host += std::string(25 - host.size(), ' ');
    auto to_opt = [](const json& v) -> std::optional<double> {
      if (v.is_null()) return std::nullopt;
      return v.get<double>();
    };
    std::string minmax = fmt_ms(to_opt(s["min_rtt_ms"])) + "/" + fmt_ms(to_opt(s["max_rtt_ms"]));
    if (minmax.size() < 18) minmax += std::string(18 - minmax.size(), ' ');
    std::cout << host << " " << std::setw(10) << std::left << fmt_ms(to_opt(s["last_rtt_ms"]))
              << std::setw(10) << std::left << fmt_ms(to_opt(s["avg_rtt_ms"]))
              << minmax
              << std::setw(7) << std::right << std::fixed << std::setprecision(2) << s["packet_loss_pct"].get<double>() << "%   "
              << (st.last_alert ? "ALERT" : "OK") << "\n";
  }
  std::cout << "--------------------------------------------------------------------------------------\n";
}

static void print_summary(std::vector<HostState> states) {
  std::sort(states.begin(), states.end(), [](const HostState& a, const HostState& b) {
    auto av = average(a.rtts).value_or(std::numeric_limits<double>::infinity());
    auto bv = average(b.rtts).value_or(std::numeric_limits<double>::infinity());
    return av < bv;
  });
  std::cout << "\nFinal Summary (best average RTT first)\n";
  std::cout << "--------------------------------------------------------------------------\n";
  for (const auto& st : states) {
    json s = stats_json(st);
    auto to_opt = [](const json& v) -> std::optional<double> {
      if (v.is_null()) return std::nullopt;
      return v.get<double>();
    };
    std::cout << st.label << " (" << st.host << ")  avg=" << fmt_ms(to_opt(s["avg_rtt_ms"]))
              << "  min=" << fmt_ms(to_opt(s["min_rtt_ms"]))
              << "  max=" << fmt_ms(to_opt(s["max_rtt_ms"]))
              << "  loss=" << std::fixed << std::setprecision(2) << s["packet_loss_pct"].get<double>() << "%"
              << "  jitter=" << fmt_ms(to_opt(s["jitter_ms"])) << "\n";
  }
}

static void write_json_report(
    const std::vector<HostState>& states,
    const Config& cfg,
    int64_t started_ms,
    int64_t ended_ms,
    bool interrupted) {
  json root;
  root["generated_at"] = now_iso();
  root["monitoring"] = {
      {"started_at", started_ms},
      {"ended_at", ended_ms},
      {"interrupted", interrupted},
      {"interval_sec", cfg.interval_sec},
      {"port", cfg.port},
      {"timeout_ms", cfg.timeout_ms},
      {"duration_ms", cfg.duration_ms.has_value() ? json(*cfg.duration_ms) : json(nullptr)},
      {"alert", {{"mode", cfg.alert.mode}, {"value", cfg.alert.value}}}};

  root["hosts"] = json::array();
  for (const auto& st : states) {
    json h;
    h["host"] = st.host;
    h["label"] = st.label;
    h["stats"] = stats_json(st);
    h["measurements"] = json::array();
    for (const auto& m : st.measurements) {
      h["measurements"].push_back({
          {"timestamp", m.timestamp},
          {"success", m.success},
          {"rtt_ms", m.rtt_ms.has_value() ? json(*m.rtt_ms) : json(nullptr)},
          {"error", m.error.empty() ? json(nullptr) : json(m.error)},
          {"alert", m.alert},
      });
    }
    root["hosts"].push_back(h);
  }

  std::ofstream out(cfg.output);
  if (!out) throw std::runtime_error("Failed to write JSON output: " + cfg.output);
  out << root.dump(2) << "\n";
}

static void write_csv(const std::vector<HostState>& states, const std::string& path) {
  std::ofstream out(path);
  if (!out) throw std::runtime_error("Failed to write CSV output: " + path);
  out << "timestamp,host,label,success,rtt_ms,error,alert\n";
  for (const auto& st : states) {
    for (const auto& m : st.measurements) {
      out << "\"" << m.timestamp << "\","
          << "\"" << st.host << "\","
          << "\"" << st.label << "\","
          << (m.success ? "true" : "false") << ",";
      if (m.rtt_ms.has_value()) out << std::fixed << std::setprecision(3) << *m.rtt_ms;
      out << ",\"" << m.error << "\","
          << (m.alert ? "true" : "false") << "\n";
    }
  }
}

int main(int argc, char** argv) {
  try {
    std::signal(SIGINT, [](int) { g_stop.store(true); });
    Config cfg = parse_args(argc, argv);
    if (cfg.hosts_file.has_value()) {
      auto file_hosts = parse_hosts_file(*cfg.hosts_file);
      cfg.hosts.insert(cfg.hosts.end(), file_hosts.begin(), file_hosts.end());
    }
    if (cfg.hosts.empty()) {
      cfg.hosts = {
          {"8.8.8.8", "Google DNS"},
          {"1.1.1.1", "Cloudflare DNS"},
          {"example.com", "Example"},
          {"google.com", "Google"},
      };
      cfg.duration_ms = 30'000;
    }

    std::vector<HostState> states;
    for (const auto& h : cfg.hosts) states.push_back({h.host, h.label});

    auto now_ms = []() -> int64_t {
      return std::chrono::duration_cast<std::chrono::milliseconds>(
          std::chrono::system_clock::now().time_since_epoch()).count();
    };

    int64_t started_ms = now_ms();
    std::optional<int64_t> end_ms = cfg.duration_ms.has_value() ? std::optional<int64_t>(started_ms + *cfg.duration_ms) : std::nullopt;
    bool interrupted = false;

    while (!g_stop.load() && (!end_ms.has_value() || now_ms() < *end_ms)) {
      int64_t tick_start = now_ms();
      std::vector<std::future<ProbeResult>> futures;
      futures.reserve(states.size());
      for (const auto& st : states) {
        futures.emplace_back(std::async(std::launch::async, probe_tcp, st.host, cfg.port, cfg.timeout_ms));
      }

      for (size_t i = 0; i < states.size(); ++i) {
        auto& st = states[i];
        ProbeResult r = futures[i].get();
        st.sent += 1;

        Measurement m;
        m.timestamp = now_iso();
        m.success = r.success;
        m.rtt_ms = r.rtt_ms;
        m.error = r.error;
        m.alert = false;

        if (!r.success) {
          st.lost += 1;
          st.last_alert = false;
          st.measurements.push_back(m);
          continue;
        }

        double rtt = *r.rtt_ms;
        bool alert = should_alert(st, cfg.alert, rtt);
        if (st.last_rtt.has_value()) st.jitters.push_back(std::abs(rtt - *st.last_rtt));
        st.last_rtt = rtt;
        st.rtts.push_back(rtt);
        st.last_alert = alert;
        m.alert = alert;
        st.measurements.push_back(m);
      }

      render_dashboard(states, started_ms, end_ms);

      int64_t elapsed = now_ms() - tick_start;
      int64_t wait_ms = std::max<int64_t>(0, static_cast<int64_t>(cfg.interval_sec * 1000.0) - elapsed);
      std::this_thread::sleep_for(std::chrono::milliseconds(wait_ms));
    }

    if (g_stop.load()) interrupted = true;
    int64_t ended_ms = now_ms();
    render_dashboard(states, started_ms, end_ms);
    print_summary(states);
    write_json_report(states, cfg, started_ms, ended_ms, interrupted);
    if (cfg.export_csv.has_value()) write_csv(states, *cfg.export_csv);
    std::cout << "\nSaved JSON report: " << cfg.output << "\n";
    if (cfg.export_csv.has_value()) std::cout << "Saved CSV export: " << *cfg.export_csv << "\n";
    return 0;
  } catch (const std::exception& ex) {
    std::cerr << "Error: " << ex.what() << "\n";
    return 1;
  }
}