// ganyant-probe — tiny Linux metrics collector (C++20, no dependencies).
// Prints one JSON object per line to stdout every <interval> seconds; ganyant-agent ships them.
// Build: g++ -std=c++20 -O2 -s -o ganyant-probe ganyant_probe.cpp
#include <sys/statvfs.h>
#include <unistd.h>

#include <chrono>
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <set>
#include <sstream>
#include <string>
#include <thread>
#include <vector>

namespace {

struct Cpu { std::uint64_t idle = 0, total = 0; };

Cpu read_cpu() {
  std::ifstream f("/proc/stat");
  std::string tag;
  std::uint64_t v[10]{};
  f >> tag;
  for (auto& x : v) f >> x;
  Cpu c;
  for (auto x : v) c.total += x;
  c.idle = v[3] + v[4];  // idle + iowait
  return c;
}

std::uint64_t meminfo(const std::string& text, std::string_view key) {
  auto p = text.find(key);
  if (p == std::string::npos) return 0;
  return std::stoull(text.substr(p + key.size())) * 1024ULL;
}

std::string slurp(const char* path) {
  std::ifstream f(path);
  std::stringstream ss;
  ss << f.rdbuf();
  return ss.str();
}

std::string json_escape(const std::string& s) {
  std::string o;
  for (char c : s) {
    if (c == '"' || c == '\\') { o += '\\'; o += c; }
    else if (static_cast<unsigned char>(c) < 0x20) continue;
    else o += c;
  }
  return o;
}

struct Net { std::uint64_t rx = 0, tx = 0; };

Net read_net() {
  std::ifstream f("/proc/net/dev");
  std::string line;
  Net n;
  std::getline(f, line);
  std::getline(f, line);
  while (std::getline(f, line)) {
    auto colon = line.find(':');
    if (colon == std::string::npos) continue;
    std::string iface = line.substr(0, colon);
    iface.erase(0, iface.find_first_not_of(' '));
    if (iface == "lo" || iface.starts_with("docker") || iface.starts_with("veth") || iface.starts_with("br-")) continue;
    std::istringstream ss(line.substr(colon + 1));
    std::uint64_t rx, x, tx;
    ss >> rx;
    for (int i = 0; i < 7; ++i) ss >> x;
    ss >> tx;
    n.rx += rx;
    n.tx += tx;
  }
  return n;
}

std::string disks_json() {
  static const std::set<std::string> real{"ext4", "ext3", "xfs", "btrfs", "zfs", "f2fs", "vfat", "ntfs", "ntfs3", "exfat"};
  std::ifstream f("/proc/mounts");
  std::string dev, mnt, fs, rest;
  std::set<std::string> seen;
  std::ostringstream o;
  bool first = true;
  o << '[';
  while (f >> dev >> mnt >> fs && std::getline(f, rest)) {
    if (!real.contains(fs) || seen.contains(dev)) continue;
    struct statvfs st {};
    if (statvfs(mnt.c_str(), &st) != 0 || st.f_blocks == 0) continue;
    seen.insert(dev);
    const std::uint64_t total = st.f_blocks * st.f_frsize;
    const std::uint64_t used = total - st.f_bavail * st.f_frsize;
    o << (first ? "" : ",") << R"({"mount":")" << json_escape(mnt) << R"(","used":)" << used << R"(,"total":)" << total << '}';
    first = false;
  }
  o << ']';
  return o.str();
}

}  // namespace

int main(int argc, char** argv) {
  const int interval = argc > 1 ? std::max(1, std::atoi(argv[1])) : 15;
  std::ios::sync_with_stdio(false);
  Cpu prev = read_cpu();
  Net prevNet = read_net();
  auto prevT = std::chrono::steady_clock::now();
  for (;;) {
    std::this_thread::sleep_for(std::chrono::seconds(interval));
    const Cpu cur = read_cpu();
    const Net net = read_net();
    const auto now = std::chrono::steady_clock::now();
    const double secs = std::chrono::duration<double>(now - prevT).count();
    const double dt = static_cast<double>(cur.total - prev.total);
    const double cpu = dt > 0 ? 100.0 * (1.0 - static_cast<double>(cur.idle - prev.idle) / dt) : 0.0;

    const std::string mi = slurp("/proc/meminfo");
    const auto memTotal = meminfo(mi, "MemTotal:");
    const auto memAvail = meminfo(mi, "MemAvailable:");
    const auto swapTotal = meminfo(mi, "SwapTotal:");
    const auto swapFree = meminfo(mi, "SwapFree:");
    double l1 = 0, l5 = 0, l15 = 0;
    std::ifstream("/proc/loadavg") >> l1 >> l5 >> l15;

    std::cout << R"({"cpu_pct":)" << cpu << R"(,"mem_used_bytes":)" << (memTotal - memAvail) << R"(,"mem_total_bytes":)" << memTotal
              << R"(,"swap_used_bytes":)" << (swapTotal - swapFree) << R"(,"swap_total_bytes":)" << swapTotal
              << R"(,"load1":)" << l1 << R"(,"load5":)" << l5 << R"(,"load15":)" << l15
              << R"(,"net_rx_bps":)" << static_cast<std::uint64_t>((net.rx - prevNet.rx) / secs)
              << R"(,"net_tx_bps":)" << static_cast<std::uint64_t>((net.tx - prevNet.tx) / secs)
              << R"(,"disks":)" << disks_json() << "}\n"
              << std::flush;
    prev = cur;
    prevNet = net;
    prevT = now;
  }
}
