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.