Skip to main content

Agent collector design

The agent's collector module (agent/internal/collector/) is responsible for gathering all system data that makes up a snapshot.

Collection pipeline​

CollectSnapshot()
│
├── ReadOsRelease() → os-release ID, name, version
├── NormalizeOsFamily() → "apt" or "rpm"
├── ParseMajorVersion() → e.g., "10.2" → 10
├── ReadMachineID() → /etc/machine-id
├── os.Hostname()
├── getUnameInfo() → architecture, kernel release
├── DetectArchitecture() → x86_64, aarch64, riscv64
├── ReadUptime() → /proc/uptime → boot time
├── CollectUpgradablePackages() → apt list --upgradable / dnf check-update
├── RunningKernelNEVRA() → kernel name-epoch:version-release.arch
├── CollectInstalledPackages() → dpkg-query / rpm -qa
└── CollectEnabledRepos() → apt sources / dnf repolist

Package collection​

APT systems​

Uses dpkg-query with a pipe-delimited format:

dpkg-query -W -f='${Package}|${Version}|${Architecture}|${Maintainer}|${source:Package}\n'

Each line is parsed into name, epoch, version, release, architecture, vendor (maintainer), and source package. Debian version parsing handles epoch prefixes (1:) and upstream/release splitting on -.

RPM systems​

Uses rpm -qa with a custom query format:

rpm -qa --queryformat "%{NAME}|%{EPOCHNUM}|%{VERSION}|%{RELEASE}|%{ARCH}|%{SOURCERPM}|%{VENDOR}\n"

Each line is parsed into name, epoch, version, release, architecture, source RPM, and vendor.

Available updates​

APT​

apt list --upgradable

The output is parsed line by line, looking for entries containing / (repo separator) and [upgradable from:].

RPM​

dnf -q --cacheonly check-update
# or
yum check-update -q

The output is parsed to count available updates, filtering out metadata lines and obsolete notices.

Repository collection​

APT​

Reads deb lines from:

  • /etc/apt/sources.list
  • /etc/apt/sources.list.d/*.list
  • /etc/apt/sources.list.d/*.sources (deb822 format)

RPM​

Uses dnf repolist or yum repolist to list enabled repositories with their IDs and labels.

ExecRunner abstraction​

All shell command execution goes through an ExecRunner interface, which makes the collector testable with mock runners. In production, DefaultExecRunner uses os/exec.