# build/nix/bpf.nix
#
# NixOS module for BPF PMDA (pre-compiled eBPF metrics).
#
# This module uses pmdabpf which loads pre-compiled CO-RE (Compile Once, Run Everywhere)
# eBPF programs.  No runtime compilation is needed — the eBPF bytecode is compiled at
# Nix build time and loaded directly by the kernel via libbpf.
#
# Characteristics:
#   - Fast startup: No eBPF compilation at runtime
#   - Low memory: No clang/LLVM needed in the VM (~512MB)
#   - Simple: Just needs libbpf + BTF-enabled kernel
#
# Note: pmdabcc (runtime BCC compilation) was removed from PCP upstream.
# pmdabpf is the only supported eBPF PMDA.
#
# Available pmdabpf modules:
#   - biolatency: Block device I/O latency histogram
#   - runqlat:    Scheduler run queue latency histogram
#   - netatop:    Per-process TCP/UDP network statistics
#   - oomkill:    OOM killer event tracing
#   - execsnoop:  New process tracing
#   - exitsnoop:  Process exit tracing
#   - opensnoop:  open(2) syscall tracing
#   - vfsstat:    VFS operation statistics
#   - tcpconnlat: TCP connection latency
#   - tcpconnect: TCP connection tracing
#   - biosnoop:   Block I/O tracing
#   - fsslower:   Slow filesystem operations
#   - statsnoop:  stat(2) family tracing
#   - mountsnoop: mount/umount tracing
#   - bashreadline: Bash command tracing
#
{ config, lib, pkgs, ... }:
with lib;
let
  constants = import ./constants.nix;
  cfg = config.services.pcp;
  bpfCfg = cfg.bpf;

  # Generate bpf.conf from enabled modules
  bpfConf = pkgs.writeText "bpf.conf" ''
    #
    # PCP BPF PMDA config - generated by NixOS
    # See pmdabpf(1) for module configuration
    #

    ${concatStringsSep "\n" (map (mod: ''
      [${mod.name}.so]
      enabled = ${if mod.enable then "true" else "false"}
      ${optionalString (mod.extraConfig != "") mod.extraConfig}
    '') bpfCfg.modules)}
  '';

in {
  # ═══════════════════════════════════════════════════════════════════════
  # Options interface
  # ═══════════════════════════════════════════════════════════════════════

  options.services.pcp.bpf = {
    enable = mkEnableOption "BPF PMDA for pre-compiled eBPF metrics (requires BTF-enabled kernel)";

    modules = mkOption {
      type = types.listOf (types.submodule {
        options = {
          name = mkOption {
            type = types.str;
            description = "Module name (without .so suffix)";
          };
          enable = mkOption {
            type = types.bool;
            default = true;
            description = "Whether this module is enabled";
          };
          extraConfig = mkOption {
            type = types.str;
            default = "";
            description = "Additional config lines for this module";
          };
        };
      });
      default = [
        # Default modules for system monitoring
        { name = "biolatency"; }  # Block I/O latency histogram
        { name = "runqlat"; }     # Scheduler run queue latency
        { name = "netatop"; }     # Per-process network stats
        { name = "oomkill"; }     # OOM killer events
      ];
      description = ''
        BPF modules to enable. These are pre-compiled CO-RE eBPF programs
        that load quickly without runtime compilation.
      '';
    };

    extraModules = mkOption {
      type = types.listOf (types.submodule {
        options = {
          name = mkOption { type = types.str; };
          enable = mkOption { type = types.bool; default = true; };
          extraConfig = mkOption { type = types.str; default = ""; };
        };
      });
      default = [];
      description = "Additional BPF modules beyond the defaults";
      example = literalExpression ''
        [
          { name = "execsnoop"; }
          { name = "tcpconnlat"; extraConfig = "min_us = 100"; }
        ]
      '';
    };
  };

  # ═══════════════════════════════════════════════════════════════════════
  # Implementation
  # ═══════════════════════════════════════════════════════════════════════

  config = mkIf (cfg.enable && bpfCfg.enable) {
    # Informational message about BTF requirement
    warnings = [
      "BPF PMDA requires BTF-enabled kernel (CONFIG_DEBUG_INFO_BTF=y). Verify with: ls /sys/kernel/btf/vmlinux"
    ];

    # Register BPF PMDA with pmcd via the pmdas option
    # pmdabpf is a DSO PMDA (shared library loaded by pmcd)
    services.pcp.pmdas.bpf = {
      domain = constants.pmdaDomains.bpf;
      type = "dso";
      init = "bpf_init";
      path = "${cfg.package}/var/lib/pcp/pmdas/bpf/pmda_bpf.so";
    };

    # Create BPF config directory and files
    # Note: pmdabpf looks for bpf.conf in $PCP_PMDAS_DIR/bpf/ (i.e., /var/lib/pcp/pmdas/bpf/)
    # We also create a symlink in config/bpf for consistency with other PMDAs
    systemd.tmpfiles.rules = [
      # Writable config directory for BPF
      "d  /var/lib/pcp/config/bpf  0755 pcp pcp -"

      # Symlink the generated config to where pmdabpf looks for it
      "L+ /var/lib/pcp/pmdas/bpf/bpf.conf - - - - ${bpfConf}"

      # Also symlink to config dir for consistency
      "L+ /var/lib/pcp/config/bpf/bpf.conf - - - - ${bpfConf}"

      # Symlink the pre-compiled module .so files from package
      "L+ /var/lib/pcp/pmdas/bpf/modules - - - - ${cfg.package}/var/lib/pcp/pmdas/bpf/modules"
    ];

    # Ensure libbpf is available
    environment.systemPackages = [ pkgs.libbpf ];
  };
}
