diff --git a/.gitignore b/.gitignore
index bd6dba61e34..7d490fdcdc0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,4 +51,5 @@ CmakeLists.txt
provisioning/osxsierra.legally.ok
*.gdb_history
*vgcore.*
-tmpfiles.init.setup
+tmpfiles.init.setup
+result*
\ No newline at end of file
diff --git a/docs/HowTos/nix/index.rst b/docs/HowTos/nix/index.rst
new file mode 100644
index 00000000000..54327835dd9
--- /dev/null
+++ b/docs/HowTos/nix/index.rst
@@ -0,0 +1,617 @@
+.. _nix-packaging:
+
+Building PCP with Nix
+=====================
+
+This guide documents how to build Performance Co-Pilot (PCP) using the Nix
+package manager and the NixOS Linux distribution.
+
+.. important::
+
+ This documentation explains why ``flake.nix`` was created and the technical
+ decisions behind it, to help others maintain and update the Nix packaging in
+ the future. If/when a PCP package is added to nixpkgs, this document will
+ also serve as a reference for that Nix configuration.
+
+What is Nix?
+------------
+
+`Nix `_ is a purely functional package manager that provides
+reproducible, declarative, and reliable software builds. Key features include:
+
+- **Reproducibility**: Builds are isolated and deterministic - the same inputs
+ always produce the same outputs, regardless of the host system state.
+- **Atomic upgrades and rollbacks**: Packages are installed in isolation,
+ allowing instant rollback to previous versions.
+- **Multiple versions**: Different versions of the same package can coexist
+ without conflicts.
+- **NixOS**: A Linux distribution built entirely on Nix, where the entire
+ operating system configuration is declarative and reproducible.
+
+Nix stores all packages in ``/nix/store/`` with cryptographic hashes in the
+path (e.g., ``/nix/store/abc123...-pcp-7.0.5/``), ensuring complete isolation
+between package versions and configurations.
+
+For more information, visit:
+
+- `Nix website `_
+- `Nix manual `_
+- `Nixpkgs repository `_
+
+.. contents:: Table of Contents
+ :depth: 3
+ :local:
+
+Introduction
+------------
+
+PCP can be built using the Nix package manager, providing reproducible builds
+and easy dependency management. This documentation covers:
+
+- Using the flake to build from source
+- Available build options and feature flags
+- Technical details of Nix-specific patches
+- Known limitations and future improvements
+
+The Nix packaging work was initiated to bring PCP into the `nixpkgs
+`_ repository, enabling NixOS users to easily
+install and run PCP.
+
+Quick Start
+-----------
+
+Building with Flakes (Recommended)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+From the PCP repository root::
+
+ # Build PCP
+ nix build
+
+ # Run pminfo
+ nix run .#pcp -- pminfo --version
+
+ # Enter a development shell
+ nix develop
+
+Feature Flags
+-------------
+
+Default Features
+^^^^^^^^^^^^^^^^
+
+The following features are enabled by default:
+
+- **Core PCP tools and libraries**: pminfo, pmstat, pmrep, pmcd, etc.
+- **Python3 and Perl language bindings**
+- **Secure sockets**: SSL/TLS via OpenSSL
+- **Service discovery**: via Avahi/mDNS
+- **Transparent archive decompression**: via xz/lzma
+- **Systemd integration**: unit files, tmpfiles, sysusers
+- **Performance events**: via libpfm4
+- **BPF/BCC PMDAs**: kernel tracing via eBPF
+- **SNMP PMDA**: network device monitoring
+- **Device mapper metrics**: LVM thin/cache via lvm2
+- **RRDtool Perl bindings**: for RRD-based PMDAs
+
+Optional Features
+^^^^^^^^^^^^^^^^^
+
+These can be enabled by overriding the nixpkgs package:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - Flag
+ - Description
+ * - ``withQt = true``
+ - Qt5 GUI tools (pmchart, pmview, etc.)
+ * - ``withAmdgpu = true``
+ - AMD GPU metrics (requires libdrm)
+ * - ``withInfiniband = true``
+ - Infiniband fabric metrics (requires rdma-core)
+ * - ``withSelinux = true``
+ - SELinux policy support
+ * - ``withPythonExport = true``
+ - pcp2arrow (pyarrow), pcp2xlsx (openpyxl)
+ * - ``withPythonDatabase = true``
+ - pmdapostgresql, pmdamongodb
+ * - ``withPythonInfra = true``
+ - pmdalibvirt (requires libvirt, lxml)
+ * - ``withPerlDatabase = true``
+ - DBI for pmdaoracle, pmdamysql
+ * - ``withPerlNetwork = true``
+ - Net::SNMP, Cache::Memcached, XML::LibXML
+ * - ``withPerlMisc = true``
+ - Date::Manip, Spreadsheet::WriteExcel
+
+Example with overrides (nixpkgs only)::
+
+ nix-build -A pcp.override { withQt = true; withPythonExport = true; }
+
+.. note::
+
+ The in-tree ``flake.nix`` provides a simplified build with default features
+ enabled (BPF, systemd, SNMP, etc.) but does not expose all optional flags.
+ For full customization, use the nixpkgs package with overrides.
+
+PMDAs Requiring Unavailable Dependencies
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Some PMDAs require Python/Perl packages not yet available in nixpkgs:
+
+- **pcp2spark**: requires pyspark
+- **pmdamssql**: requires pyodbc + unixODBC setup
+- **pmdalio**: requires rtslib-fb
+- **pmdahdb**: requires hdbcli/pyhdbcli (SAP HANA)
+- **pmdaslurm**: requires Slurm::Sacctmgr Perl module
+
+Package Output Structure
+------------------------
+
+After building with ``nix build``, the ``result`` symlink points to the package
+in the Nix store. The package follows a modified FHS-like structure:
+
+::
+
+ result/
+ ├── bin/ # User-facing CLI tools
+ │ ├── pminfo # Query metric information
+ │ ├── pmstat # System performance overview
+ │ ├── pmrep # Customizable reporting
+ │ ├── pmlogger # Archive performance data
+ │ ├── pmie # Inference engine
+ │ ├── pcp2* # Export tools (json, xml, elasticsearch, etc.)
+ │ └── ... # ~70 tools total
+ │
+ ├── lib/ # Shared libraries
+ │ ├── libpcp.so.* # Core PCP library
+ │ ├── libpcp_pmda.so.* # PMDA development library
+ │ ├── libpcp_web.so.* # Web/REST API support
+ │ ├── libpcp_gui.so.* # GUI toolkit integration
+ │ ├── python3.*/ # Python bindings (pcp module)
+ │ └── perl5/ # Perl bindings (PCP::PMDA, etc.)
+ │
+ ├── libexec/pcp/ # Internal executables
+ │ ├── bin/ # pmcd, pmproxy, pmnsmerge, etc.
+ │ ├── pmdas/ # PMDA binaries and scripts
+ │ ├── pmns/ # PMNS source files (root_*)
+ │ └── services/ # Service control scripts
+ │
+ ├── share/pcp/ # Shared data and config templates
+ │ ├── etc/ # Configuration files (moved from /etc)
+ │ │ ├── pcp.conf # Main PCP configuration
+ │ │ └── pcp/ # Per-service configs (pmcd, pmlogger, etc.)
+ │ ├── lib/ # Shell function libraries
+ │ ├── demos/ # Example code and tutorials
+ │ └── htop/ # pcp-htop configuration
+ │
+ ├── var/lib/pcp/ # Variable data (static portions only)
+ │ ├── pmns/ # Combined PMNS root file
+ │ ├── pmdas/ # PMDA installation state
+ │ └── config/ # Runtime configuration templates
+ │
+ └── include/ # C header files for development
+ └── pcp/ # pcp/*.h headers
+
+**Key differences from traditional installation:**
+
+- Configuration moved from ``/etc`` to ``share/pcp/etc`` (NixOS module
+ materializes to ``/etc`` at activation time)
+- Runtime state directories (``/var/run``, ``/var/log``) removed (created
+ by systemd tmpfiles at runtime)
+- All paths are absolute to the Nix store (``/nix/store/-pcp-x.y.z/``)
+
+Technical Details
+-----------------
+
+This section documents the Nix-specific patches and workarounds required to
+build PCP in the Nix sandbox environment.
+
+Why Nix Packaging Can Be Challenging
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Nix's approach to reproducible builds imposes constraints that differ
+significantly from traditional Linux distributions. These constraints often
+expose assumptions and hardcoded values in build systems that would otherwise
+go unnoticed:
+
+**Non-standard Filesystem Hierarchy**
+ Nix does not follow the Filesystem Hierarchy Standard (FHS). Binaries,
+ libraries, and configuration files live in ``/nix/store/-/``
+ rather than ``/usr/bin``, ``/usr/lib``, or ``/etc``. This breaks:
+
+ - Hardcoded paths like ``/usr/bin/ar`` or ``/usr/lib/tmpfiles.d``
+ - Scripts that assume tools are in ``PATH`` at standard locations
+ - Build systems that probe for dependencies in fixed paths
+
+**Sandboxed Builds**
+ Nix builds run in a sandbox with restricted filesystem access. The build
+ environment cannot write to locations like ``/tmp`` or ``/var/tmp`` - instead,
+ ``$TMPDIR`` points to an isolated temporary directory. This reveals:
+
+ - Hardcoded temporary paths (e.g., ``/var/tmp`` in PCP's ``mk.rewrite`` scripts)
+ - Assumptions about writable system directories
+ - Network access attempts during builds (blocked by default)
+
+**Pure Build Environment**
+ Each build starts with a minimal, reproducible environment. There are no
+ implicit dependencies from the host system:
+
+ - ``pkg-config`` returns paths inside Nix store (read-only for other packages)
+ - Compilers include hardening flags that may conflict with specialized targets
+ (e.g., BPF's ``-target bpf`` doesn't support ``-fstack-protector``)
+ - Environment variables like ``PATH`` contain only explicitly declared inputs
+
+**Static Analysis Benefits**
+ While these constraints require extra packaging work, they provide significant
+ benefits:
+
+ - Exposes portability issues and implicit dependencies
+ - Forces explicit declaration of all build requirements
+ - Catches hardcoded paths that may break on other systems
+ - Results in packages that are more likely to build correctly on any platform
+
+The patches documented below address these Nix-specific challenges for PCP,
+and many would benefit upstream by improving portability across all platforms.
+
+BPF Compilation
+^^^^^^^^^^^^^^^
+
+PCP's BPF PMDAs (pmdabpf, pmdabcc, pmdabpftrace) compile ``.bpf.c`` files to
+BPF bytecode using clang with ``-target bpf``. Nix's wrapped clang adds
+hardening flags that the BPF backend doesn't support::
+
+ clang: error: unsupported option '-fzero-call-used-regs=used-gpr' for target 'bpf'
+ clang: warning: ignoring '-fstack-protector-strong' option as it is not
+ currently supported for target 'bpf'
+
+**Solution**:
+
+1. Disable the unsupported hardening flag::
+
+ hardeningDisable = [ "zerocallusedregs" ];
+
+2. Suppress stack protector warnings via ``BPF_CFLAGS``::
+
+ BPF_CFLAGS = "-fno-stack-protector -Wno-error=unused-command-line-argument";
+
+3. Use ``lib.getExe`` to get the wrapped clang path::
+
+ CLANG = lib.getExe llvmPackages.clang;
+
+This approach was adapted from the ``xdp-tools`` package in nixpkgs, which
+faces similar BPF compilation challenges.
+
+Nix Sandbox /var/tmp Fix
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The Nix build sandbox restricts filesystem access. Several PCP build scripts
+hardcode ``/var/tmp`` for temporary files, causing errors::
+
+ ./mk.rewrite: line 12: /var/tmp/37183.c: No such file or directory
+
+**Affected scripts**:
+
+- ``src/pmdas/bind2/mk.rewrite``
+- ``src/pmdas/jbd2/mk.rewrite``
+- ``src/pmdas/linux/mk.rewrite``
+- ``src/pmdas/linux_proc/mk.rewrite``
+- ``src/bashrc/getargs``
+- ``src/libpcp/src/check-errorcodes``
+- ``src/libpcp3/src/check-errorcodes``
+- ``src/libpcp/doc/mk.cgraph``
+- ``src/pmlogcompress/check-optimize``
+
+**Solution**: The patch file ``nix/patches/tmpdir-portability.patch`` replaces
+``/var/tmp`` with ``${TMPDIR:-/tmp}`` in all affected scripts. This is portable:
+
+- On Nix, ``$TMPDIR`` is set to a writable sandbox directory
+- On traditional systems, it falls back to ``/tmp``
+
+This fix could be submitted upstream to improve portability across all build
+environments.
+
+Systemd Integration Paths
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+PCP uses ``pkg-config`` to find systemd directories from ``systemd.pc``. On
+NixOS, this returns paths inside the systemd store path (read-only)::
+
+ mkdir: cannot create directory '/nix/store/...-systemd-.../lib/systemd/system': Permission denied
+
+Additionally, PCP's ``GNUmakefile`` has a hardcoded fallback to
+``/usr/lib/tmpfiles.d``.
+
+**Solutions**:
+
+1. Override paths via environment variables::
+
+ SYSTEMD_SYSTEMUNITDIR = "${placeholder "out"}/lib/systemd/system";
+ SYSTEMD_TMPFILESDIR = "${placeholder "out"}/lib/tmpfiles.d";
+ SYSTEMD_SYSUSERSDIR = "${placeholder "out"}/lib/sysusers.d";
+
+2. The patch file ``nix/patches/gnumakefile-nix-fixes.patch`` redirects the
+ tmpfiles installation from ``/usr/lib/tmpfiles.d`` to
+ ``$(PCP_SHARE_DIR)/tmpfiles.d``.
+
+Configure Script Fixes
+^^^^^^^^^^^^^^^^^^^^^^
+
+PCP's configure script has a hardcoded fallback for the ``ar`` archiver::
+
+ test -n "$AR" || AR="/usr/bin/ar"
+
+On NixOS, ``/usr/bin/ar`` doesn't exist.
+
+**Solution**: Export ``AR`` in ``preConfigure`` before configure runs::
+
+ preConfigure = ''
+ export AR="${stdenv.cc.bintools}/bin/ar"
+ '';
+
+Since ``AR`` is already set when configure runs, the hardcoded fallback is never
+reached. No patch is needed - the ``preConfigure`` export handles it completely.
+
+.. note::
+
+ The patch file ``nix/patches/configure-ar-portable.patch`` exists but is not
+ applied in ``flake.nix``. It changes the fallback to ``ar`` (PATH lookup)
+ and could be submitted upstream to improve portability for non-Nix builds.
+
+Install Ownership
+^^^^^^^^^^^^^^^^^
+
+PCP's ``GNUmakefile`` uses ``-o $(PCP_USER) -g $(PCP_GROUP)`` flags with
+``install(1)`` to set file ownership. This requires root privileges and fails
+in the Nix sandbox::
+
+ install: cannot change ownership: Operation not permitted
+
+**Solution**: The patch file ``nix/patches/gnumakefile-nix-fixes.patch`` removes
+the ownership flags from install commands. The NixOS module (when created) will
+handle proper ownership at activation time.
+
+Broken Symlinks Fix
+^^^^^^^^^^^^^^^^^^^
+
+PCP creates some symlinks with malformed targets containing double path
+prefixes like ``/nix/store/nix/store/...``. This happens when the build system
+prepends a path to an already-absolute path.
+
+**Affected symlinks** (after ``/etc`` is moved to ``share/pcp/etc/``):
+
+- ``share/pcp/etc/pcp/pmsearch/pmsearch.conf`` → should point to ``pmproxy/pmproxy.conf``
+- ``share/pcp/etc/pcp/pmseries/pmseries.conf`` → should point to ``pmproxy/pmproxy.conf``
+- ``share/pcp/etc/pcp/pmcd/rc.local`` → should point to ``libexec/pcp/services/local``
+
+**Solution**: Fix in postInstall by removing and recreating with correct targets::
+
+ # Fix pmsearch/pmseries config symlinks
+ for broken_link in "$out/share/pcp/etc/pcp/pm"{search/pmsearch,series/pmseries}.conf; do
+ [[ -L "$broken_link" ]] && rm "$broken_link" && \
+ ln -sf "$out/share/pcp/etc/pcp/pmproxy/pmproxy.conf" "$broken_link"
+ done
+
+ # Fix pmcd/rc.local symlink
+ if [[ -L "$out/share/pcp/etc/pcp/pmcd/rc.local" ]]; then
+ rm "$out/share/pcp/etc/pcp/pmcd/rc.local"
+ ln -sf "$out/libexec/pcp/services/local" "$out/share/pcp/etc/pcp/pmcd/rc.local"
+ fi
+
+Patch Files
+^^^^^^^^^^^
+
+The Nix-specific fixes have been implemented as proper ``.patch`` files in
+``nix/patches/`` rather than inline ``substituteInPlace`` calls:
+
+**Applied in flake.nix:**
+
+- ``gnumakefile-nix-fixes.patch`` - Ownership flags, tmpfiles path, qa exclusion
+- ``tmpdir-portability.patch`` - Use ``${TMPDIR:-/tmp}`` instead of ``/var/tmp``
+
+**Available but not applied** (handled by ``preConfigure`` instead):
+
+- ``configure-ar-portable.patch`` - Portable ar fallback (useful for upstream)
+
+Benefits of using patch files:
+
+- Patches fail loudly when they don't apply (easier version upgrades)
+- Easier to submit upstream for review
+- Clear documentation of what changes are needed
+- Better for code review
+
+Only ``patchShebangs`` remains in ``postPatch`` as it requires Nix store paths.
+
+Package Outputs
+---------------
+
+The Nix package is split into multiple outputs to reduce closure size:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 15 85
+
+ * - Output
+ - Contents
+ * - ``out``
+ - Main binaries, libraries, PMDAs, examples, configuration templates
+ * - ``man``
+ - Man pages (~15MB compressed)
+ * - ``doc``
+ - Books, tutorials, programmer's guides
+
+Users who only need the binaries can install just ``out``, saving significant
+disk space.
+
+Testing
+-------
+
+QA Suite (Disabled)
+^^^^^^^^^^^^^^^^^^^
+
+The full QA test suite is disabled by default (``doCheck = false``) because:
+
+1. Tests require a running ``pmcd`` daemon
+2. The QA suite has thousands of scripts that significantly slow builds
+
+The ``qa`` directory is patched out of ``SUBDIRS`` to prevent installation
+entirely, avoiding slow shebang patching of test scripts.
+
+NixOS VM Test
+^^^^^^^^^^^^^
+
+A NixOS VM integration test is provided in ``nix/vm-test.nix`` to verify the
+package works correctly in a real NixOS environment. The test:
+
+1. Boots a NixOS VM with PCP installed
+2. Creates the required ``pcp`` user/group and runtime directories
+3. Starts the ``pmcd`` daemon
+4. Waits for pmcd to listen on port 44321
+5. Queries ``pminfo -f kernel.all.load`` to verify metrics collection works
+
+**Running the VM test**::
+
+ # Run via flake check (runs all checks)
+ nix flake check
+
+ # Or build just the VM test
+ nix build .#checks.x86_64-linux.vm-test
+
+ # Then to view the logs
+ nix log .#checks.x86_64-linux.vm-test
+
+ # For aarch64 systems
+ nix build .#checks.aarch64-linux.vm-test
+
+Use ``-L`` to see test output in real-time::
+
+ nix build .#checks.x86_64-linux.vm-test -L
+
+**Example successful test output**::
+
+ === Checking listening ports ===
+ machine: must succeed: ss -tlnp | grep -E '44321|pmcd' || echo 'No pmcd ports found'
+ machine: (finished: must succeed: ..., in 0.05 seconds)
+ LISTEN 0 5 0.0.0.0:44321 0.0.0.0:* users:(("pmcd",pid=947,fd=3))
+ LISTEN 0 5 [::]:44321 [::]:* users:(("pmcd",pid=947,fd=4))
+
+ machine: waiting for TCP port 44321 on localhost
+ machine # Connection to localhost (::1) 44321 port [tcp/pmcd] succeeded!
+ machine: (finished: waiting for TCP port 44321 on localhost, in 0.05 seconds)
+
+ machine: must succeed: PCP_CONF=/.../pcp.conf pminfo -f kernel.all.load
+ machine: (finished: must succeed: ... pminfo -f kernel.all.load, in 0.04 seconds)
+
+ machine: must succeed: PCP_CONF=/.../pcp.conf pminfo -h localhost kernel.all.cpu.user
+ machine: (finished: must succeed: ... pminfo -h localhost kernel.all.cpu.user, in 0.03 seconds)
+
+ === PCP VM test passed! ===
+ (finished: run the VM test script, in 18.65 seconds)
+ test script finished in 18.69s
+ cleanup
+ kill machine (pid 9)
+
+**Running the VM interactively** (for debugging)::
+
+ # Build the test driver
+ nix build .#checks.x86_64-linux.vm-test.driverInteractive
+
+ # Run the interactive VM
+ ./result/bin/nixos-test-driver --interactive
+
+ # Inside the Python REPL, you can run commands like:
+ # >>> machine.succeed("pminfo --version")
+ # >>> machine.shell_interact() # Get a shell in the VM
+
+**Test configuration** (from ``nix/vm-test.nix``):
+
+The VM is configured with:
+
+- PCP package installed via ``environment.systemPackages``
+- ``pcp`` user/group created as a system user
+- Runtime directories (``/var/lib/pcp``, ``/var/log/pcp``, ``/run/pcp``)
+ created via systemd tmpfiles
+
+This test configuration serves as a reference for how to deploy PCP on NixOS
+until a proper NixOS module is created.
+
+Future Improvements
+-------------------
+
+Unbundle Vendored Dependencies
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+PCP vendors several libraries that already exist as packages in nixpkgs:
+
+- **inih**: INI file parser (``pkgs.inih``)
+- **hiredis/hiredis-cluster**: Redis clients (``pkgs.hiredis``)
+- **jsonsl**: JSON streaming parser
+- **libbpf/bpftool**: BPF tooling (``pkgs.libbpf``, complex due to kernel version coupling)
+- **htop source**: for pcp-htop (modified fork, ``pkgs.htop``)
+- **BCC libbpf-tools**: BPF CO-RE tools (``pkgs.bcc``)
+
+Since nixpkgs already provides packages for most of these libraries, they could
+be used directly instead of compiling the vendored copies. This would:
+
+- **Improve build performance**: Skip recompiling libraries that are already
+ built and cached in the Nix store
+- **Reduce closure size**: Share libraries with other packages rather than
+ bundling duplicates
+- **Automatically propagate security updates**: When nixpkgs updates a library,
+ PCP would automatically receive the fix
+- **Follow nixpkgs best practices**: Vendoring is discouraged in favor of
+ explicit dependencies
+
+This requires upstream cooperation to ensure clean library boundaries and
+configure flags to use system libraries.
+
+PMDA Extensibility
+^^^^^^^^^^^^^^^^^^
+
+Consider using ``lib.makeSearchPath`` to allow users to add custom PMDAs
+without rebuilding the package, via ``PCP_PMDA_PATH`` or similar mechanism.
+
+NixOS Module
+^^^^^^^^^^^^
+
+A NixOS module (``services.pcp``) would provide:
+
+- Systemd service management for ``pmcd``, ``pmlogger``, ``pmproxy``
+- Declarative PMDA configuration
+- Proper user/group creation
+- Log rotation and retention policies
+- Integration with ``services.prometheus`` for exporters
+
+Integrate NixOS Test
+^^^^^^^^^^^^^^^^^^^^
+
+The VM test currently exists as a standalone file. To fully integrate into
+nixpkgs:
+
+1. Add to ``nixos/tests/all-tests.nix``
+2. Reference via ``passthru.tests = { inherit (nixosTests) pcp; };`` in the
+ package
+
+This would allow running ``nix-build -A nixosTests.pcp`` and ensure the test
+runs automatically in nixpkgs CI.
+
+Summary
+-------
+
+The Nix packaging of PCP provides:
+
+✅ Reproducible builds from source
+✅ All core PMDAs and tools
+✅ BPF/BCC kernel tracing support
+✅ Python and Perl language bindings
+✅ Systemd integration
+✅ Split outputs for minimal installations
+
+Some features require enabling optional flags, and a few PMDAs need packages
+not yet available in nixpkgs.
+
+For questions or contributions, please open an issue on the `PCP GitHub
+repository `_ or the `nixpkgs
+repository `_.
diff --git a/docs/index.rst b/docs/index.rst
index 661a114cbd7..92fd4c2db29 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -15,18 +15,20 @@ PCP is a feature-rich, mature, extensible, cross-platform toolkit supporting bot
* :doc:`QG/QuickGuides`
* :doc:`UAG/AboutUserAdministratorsGuide`
* :doc:`PG/AboutProgrammersGuide`
+* :doc:`HowTos/nix/index`
* :doc:`HowTos/scaling/index`
* `REST API Specification `_
.. toctree::
:caption: Guides
:hidden:
-
+
HowTos/installation/index
QG/QuickReferenceGuide
QG/QuickGuides
UAG/AboutUserAdministratorsGuide
PG/AboutProgrammersGuide
+ HowTos/nix/index
HowTos/scaling/index
REST API Specification
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 00000000000..d8142e7a812
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,61 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1767640445,
+ "narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 00000000000..ccdd7e726be
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,310 @@
+#
+# flake.nix - PCP Nix packaging
+#
+# See also: ./docs/HowTos/nix/index.rst
+{
+ description = "Performance Co-Pilot (PCP) - system performance monitoring toolkit";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ flake-utils.url = "github:numtide/flake-utils";
+ };
+
+ outputs =
+ {
+ self,
+ nixpkgs,
+ flake-utils,
+ }:
+ flake-utils.lib.eachDefaultSystem (
+ system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+ lib = pkgs.lib;
+
+ pcp = pkgs.stdenv.mkDerivation rec {
+ pname = "pcp";
+ version = "7.0.5";
+
+ src = ./.;
+
+ outputs = [
+ "out"
+ "man"
+ "doc"
+ ];
+
+ nativeBuildInputs = with pkgs; [
+ autoconf
+ automake
+ pkg-config
+ bison
+ flex
+ which
+ perl
+ python3
+ python3.pkgs.setuptools
+ ] ++ lib.optionals withBpf [
+ llvmPackages.clang
+ llvmPackages.llvm
+ ];
+
+ buildInputs = with pkgs; [
+ zlib
+ ncurses
+ readline
+ openssl
+ libuv
+ cyrus_sasl
+ inih
+ xz
+ python3
+ perl
+ rrdtool
+ ] ++ lib.optionals pkgs.stdenv.isLinux [
+ avahi
+ lvm2
+ ] ++ lib.optionals withSystemd [
+ systemd
+ ] ++ lib.optionals withPfm [
+ libpfm
+ ] ++ lib.optionals withBpf [
+ libbpf
+ bcc
+ elfutils
+ ] ++ lib.optionals withSnmp [
+ net-snmp
+ ] ++ lib.optionals withPythonHttp [
+ python3.pkgs.requests
+ ] ++ lib.optionals withPerlHttp [
+ perlPackages.JSON
+ perlPackages.LWPUserAgent
+ ];
+
+ withSystemd = pkgs.stdenv.isLinux;
+ withPfm = pkgs.stdenv.isLinux;
+ withBpf = pkgs.stdenv.isLinux;
+ withSnmp = true;
+ withPythonHttp = true;
+ withPerlHttp = true;
+
+ configureFlags = lib.concatLists [
+
+ [
+ "--prefix=${placeholder "out"}"
+ "--sysconfdir=${placeholder "out"}/etc"
+ "--localstatedir=${placeholder "out"}/var"
+ "--with-rcdir=${placeholder "out"}/etc/init.d"
+ "--with-tmpdir=/tmp"
+ "--with-logdir=${placeholder "out"}/var/log/pcp"
+ "--with-rundir=/run/pcp"
+ ]
+
+ [
+ "--with-user=pcp"
+ "--with-group=pcp"
+ ]
+
+ [
+ "--with-make=make"
+ "--with-tar=tar"
+ "--with-python3=${lib.getExe pkgs.python3}"
+ ]
+
+ [
+ "--with-perl=yes"
+ "--with-threads=yes"
+ ]
+
+ [
+ "--with-secure-sockets=yes"
+ "--with-transparent-decompression=yes"
+ ]
+
+ (if pkgs.stdenv.isLinux then [ "--with-discovery=yes" ] else [ "--with-discovery=no" ])
+
+ [
+ "--with-dstat-symlink=no"
+ "--with-pmdamongodb=no"
+ "--with-pmdamysql=no"
+ "--with-pmdanutcracker=no"
+ "--with-qt=no"
+ "--with-infiniband=no"
+ "--with-selinux=no"
+ ]
+
+ (if withSystemd then [ "--with-systemd=yes" ] else [ "--with-systemd=no" ])
+ (if withPfm then [ "--with-perfevent=yes" ] else [ "--with-perfevent=no" ])
+ (
+ if withBpf then
+ [
+ "--with-pmdabcc=yes"
+ "--with-pmdabpf=yes"
+ "--with-pmdabpftrace=yes"
+ ]
+ else
+ [
+ "--with-pmdabcc=no"
+ "--with-pmdabpf=no"
+ "--with-pmdabpftrace=no"
+ ]
+ )
+
+ (if pkgs.stdenv.isLinux then [ "--with-devmapper=yes" ] else [ "--with-devmapper=no" ])
+
+ (if withSnmp then [ "--with-pmdasnmp=yes" ] else [ "--with-pmdasnmp=no" ])
+ ];
+
+
+ patches = [
+ ./nix/patches/gnumakefile-nix-fixes.patch
+ ./nix/patches/tmpdir-portability.patch
+ ];
+
+ postPatch = ''
+ # Fix shebangs (can't be done as static patch - needs Nix store paths)
+ patchShebangs src build configure scripts man
+ '';
+
+ hardeningDisable = lib.optionals withBpf [ "zerocallusedregs" ];
+
+ BPF_CFLAGS = lib.optionalString withBpf "-fno-stack-protector -Wno-error=unused-command-line-argument";
+ CLANG = lib.optionalString withBpf (lib.getExe pkgs.llvmPackages.clang);
+
+ SYSTEMD_SYSTEMUNITDIR = lib.optionalString withSystemd "${placeholder "out"}/lib/systemd/system";
+ SYSTEMD_TMPFILESDIR = lib.optionalString withSystemd "${placeholder "out"}/lib/tmpfiles.d";
+ SYSTEMD_SYSUSERSDIR = lib.optionalString withSystemd "${placeholder "out"}/lib/sysusers.d";
+
+ preConfigure = ''
+ export AR="${pkgs.stdenv.cc.bintools}/bin/ar"
+ '';
+
+ postInstall = ''
+ # Build the combined PMNS root file
+ # The individual root_* files exist but pmcd needs a combined 'root' file
+ # Use pmnsmerge to combine all the root_* files into one
+ (
+ cd $out/var/lib/pcp/pmns
+ export PCP_DIR=$out
+ export PCP_CONF=$out/etc/pcp.conf
+ . $out/etc/pcp.env
+
+ # Merge all the root_* files into the combined root file
+ # Order matters: root_root first (base), then others
+ $out/libexec/pcp/bin/pmnsmerge -a \
+ $out/libexec/pcp/pmns/root_root \
+ $out/libexec/pcp/pmns/root_pmcd \
+ $out/libexec/pcp/pmns/root_linux \
+ $out/libexec/pcp/pmns/root_proc \
+ $out/libexec/pcp/pmns/root_xfs \
+ $out/libexec/pcp/pmns/root_jbd2 \
+ $out/libexec/pcp/pmns/root_kvm \
+ $out/libexec/pcp/pmns/root_mmv \
+ $out/libexec/pcp/pmns/root_bpf \
+ $out/libexec/pcp/pmns/root_pmproxy \
+ root
+ )
+
+ # Remove runtime state directories
+ rm -rf $out/var/{run,log} $out/var/lib/pcp/tmp || true
+
+ # Move vendor config to share
+ if [ -d "$out/etc" ]; then
+ mkdir -p $out/share/pcp/etc
+ mv $out/etc/* $out/share/pcp/etc/
+ rmdir $out/etc || true
+
+ # Fix paths in pcp.conf to point to new locations
+ substituteInPlace $out/share/pcp/etc/pcp.conf \
+ --replace-quiet "$out/etc/pcp" "$out/share/pcp/etc/pcp" \
+ --replace-quiet "$out/etc/sysconfig" "$out/share/pcp/etc/sysconfig" \
+ --replace-quiet "PCP_ETC_DIR=$out/etc" "PCP_ETC_DIR=$out/share/pcp/etc"
+
+ # Fix symlinks that pointed to /etc/pcp/...
+ find $out/var/lib/pcp -type l | while read link; do
+ target=$(readlink "$link")
+ if [[ "$target" == *"/etc/pcp/"* ]]; then
+ suffix="''${target#*/etc/pcp/}"
+ rm "$link"
+ ln -sf "$out/share/pcp/etc/pcp/$suffix" "$link"
+ fi
+ done
+ fi
+
+ # Fix broken symlinks with double /nix/store prefix
+ # These occur when the build system prepends a path to an already-absolute path
+ for broken_link in "$out/share/pcp/etc/pcp/pm"{search/pmsearch,series/pmseries}.conf; do
+ [[ -L "$broken_link" ]] && rm "$broken_link" && \
+ ln -sf "$out/share/pcp/etc/pcp/pmproxy/pmproxy.conf" "$broken_link"
+ done
+
+ # Fix pmcd/rc.local symlink (points to libexec/pcp/services/local)
+ if [[ -L "$out/share/pcp/etc/pcp/pmcd/rc.local" ]]; then
+ rm "$out/share/pcp/etc/pcp/pmcd/rc.local"
+ ln -sf "$out/libexec/pcp/services/local" "$out/share/pcp/etc/pcp/pmcd/rc.local"
+ fi
+
+ # Move man pages to $man output
+ if [ -d "$out/share/man" ]; then
+ mkdir -p $man/share
+ mv $out/share/man $man/share/
+ fi
+
+ # Move documentation to $doc output
+ for docdir in $out/share/doc/pcp*; do
+ if [ -d "$docdir" ]; then
+ mkdir -p $doc/share/doc
+ mv "$docdir" $doc/share/doc/
+ fi
+ done
+ '';
+
+ doCheck = false;
+ enableParallelBuilding = true;
+
+ meta = with lib; {
+ description = "Performance Co-Pilot - system performance monitoring toolkit";
+ homepage = "https://pcp.io";
+ license = licenses.gpl2Plus;
+ platforms = platforms.linux ++ platforms.darwin;
+ mainProgram = "pminfo";
+ };
+ };
+ in
+ {
+ packages = {
+ default = pcp;
+ inherit pcp;
+ };
+
+ checks = lib.optionalAttrs pkgs.stdenv.isLinux {
+ vm-test = import ./nix/vm-test.nix {
+ inherit pkgs pcp;
+ };
+ };
+
+ devShells.default = pkgs.mkShell {
+ inputsFrom = [ pcp ];
+ packages = with pkgs; [
+ gdb
+ jp2a
+ ] ++ lib.optionals pkgs.stdenv.isLinux [
+ valgrind
+ ] ++ lib.optionals pkgs.stdenv.isDarwin [
+ lldb
+ ];
+
+ shellHook = ''
+ # Display PCP logo on shell entry
+ if [[ -f ./images/pcpicon-light.png ]]; then
+ jp2a --colors ./images/pcpicon-light.png 2>/dev/null || true
+ fi
+ echo "PCP Development Shell"
+ echo "Run './configure --help' to see build options"
+ echo "Otherwise use 'nix build' to build the package"
+ '';
+ };
+ }
+ );
+}
+
diff --git a/nix/patches/configure-ar-portable.patch b/nix/patches/configure-ar-portable.patch
new file mode 100644
index 00000000000..3da17ce3080
--- /dev/null
+++ b/nix/patches/configure-ar-portable.patch
@@ -0,0 +1,23 @@
+From: Nix Packaging
+Subject: Use portable ar fallback in configure script
+
+The configure script has a hardcoded fallback to /usr/bin/ar which doesn't
+exist on NixOS and other non-FHS systems. Change the fallback to just "ar"
+which will be found via PATH.
+
+This is a more portable default that works on any system where ar is in PATH.
+For Nix builds, AR is explicitly set in preConfigure for reproducibility,
+so this fallback only affects non-Nix builds.
+
+--- a/configure
++++ b/configure
+@@ -5947,7 +5947,7 @@ done
+
+ test -n "$AR" && break
+ done
+-test -n "$AR" || AR="/usr/bin/ar"
++test -n "$AR" || AR="ar"
+
+ fi
+ if test ! -x "$AR"; then
+
diff --git a/nix/patches/gnumakefile-nix-fixes.patch b/nix/patches/gnumakefile-nix-fixes.patch
new file mode 100644
index 00000000000..182b268a8f0
--- /dev/null
+++ b/nix/patches/gnumakefile-nix-fixes.patch
@@ -0,0 +1,57 @@
+From: Nix Packaging
+Subject: Fix GNUmakefile for Nix build environment
+
+This patch makes three changes to GNUmakefile for Nix compatibility:
+
+1. Remove -o $(PCP_USER) -g $(PCP_GROUP) ownership flags from install commands.
+ The Nix sandbox cannot change file ownership (requires root). The NixOS
+ module handles proper ownership at activation time.
+
+2. Change the tmpfiles.d installation path from /usr/lib/tmpfiles.d to
+ $(PCP_SHARE_DIR)/tmpfiles.d. On NixOS, /usr/lib doesn't exist and we
+ need to install to the package output directory.
+
+3. Comment out 'SUBDIRS += qa' to skip testsuite installation. The QA suite
+ has thousands of scripts that significantly slow builds due to shebang
+ patching, and tests require a running pmcd daemon anyway.
+
+These changes improve portability and would benefit other non-FHS systems.
+
+--- a/GNUmakefile
++++ b/GNUmakefile
+@@ -38,7 +38,8 @@ LDIRDIRT = pcp-[0-9]*.[0-9]*.[0-9]* pcp-*-[0-9]*.[0-9]*.[0-9]*
+
+ SUBDIRS = vendor src
+ ifneq ($(TARGET_OS),mingw)
+-SUBDIRS += qa
++# SUBDIRS += qa # Disabled for Nix - testsuite not needed in package
++# To run tests, build from source and use: cd qa && ./check
+ endif
+ SUBDIRS += man html images build debian
+
+@@ -80,9 +81,9 @@ endif
+ $(INSTALL) -m 755 -d $(PCP_PMNSADM_DIR)
+ $(INSTALL) -m 755 -d $(PCP_PMDASADM_DIR)
+ $(INSTALL) -m 755 -d $(PCP_SERVICES_DIR)
+- $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_TMP_DIR)
++ $(INSTALL) -m 775 -d $(PCP_TMP_DIR)
+ ifeq "$(ENABLE_SYSTEMD)" "true"
+- # this works if PCP_RUN_DIR (and friends) are within a tmpfs that
++ # This works if PCP_RUN_DIR (and friends) are within a tmpfs that
+ # is mounted empty on re-boot and managed by systemd-tmpfiles(8)
+- $(INSTALL) -m 644 tmpfiles.init.setup /usr/lib/tmpfiles.d/pcp-reboot-init.conf
++ $(INSTALL) -m 644 tmpfiles.init.setup $(PCP_SHARE_DIR)/tmpfiles.d/pcp-reboot-init.conf
+ endif
+@@ -102,8 +103,8 @@ endif
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmchart
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmieconf
+ $(INSTALL) -m 755 -d $(PCP_VAR_DIR)/config/pmlogconf
+- $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_VAR_DIR)/config/pmda
+- $(INSTALL) -m 775 -o $(PCP_USER) -g $(PCP_GROUP) -d $(PCP_LOG_DIR)
++ $(INSTALL) -m 775 -d $(PCP_VAR_DIR)/config/pmda
++ $(INSTALL) -m 775 -d $(PCP_LOG_DIR)
+ $(INSTALL) -m 755 -d $(PCP_PMDAS_DIR)
+ $(INSTALL) -m 755 -d $(PCP_DOC_DIR)
+ $(INSTALL) -m 755 -d $(PCP_DEMOS_DIR)
+
diff --git a/nix/patches/tmpdir-portability.patch b/nix/patches/tmpdir-portability.patch
new file mode 100644
index 00000000000..91b0b1c567a
--- /dev/null
+++ b/nix/patches/tmpdir-portability.patch
@@ -0,0 +1,123 @@
+From: Nix Packaging
+Subject: Use portable TMPDIR instead of hardcoded /var/tmp
+
+Several PCP build scripts hardcode /var/tmp for temporary files. This fails
+in the Nix sandbox which restricts filesystem access - /var/tmp is not
+writable during Nix builds.
+
+This patch changes /var/tmp to ${TMPDIR:-/tmp} which:
+- Uses $TMPDIR when set (Nix sets this to a writable sandbox directory)
+- Falls back to /tmp on traditional systems where TMPDIR may not be set
+- Is POSIX-compliant and improves portability across build environments
+
+Affected files (build-time scripts):
+- src/pmdas/bind2/mk.rewrite
+- src/pmdas/jbd2/mk.rewrite
+- src/pmdas/linux/mk.rewrite
+- src/pmdas/linux_proc/mk.rewrite
+- src/bashrc/getargs
+- src/libpcp/src/check-errorcodes
+- src/libpcp3/src/check-errorcodes
+- src/libpcp/doc/mk.cgraph
+- src/pmlogcompress/check-optimize
+
+--- a/src/pmdas/bind2/mk.rewrite
++++ b/src/pmdas/bind2/mk.rewrite
+@@ -11,7 +11,7 @@
+ # and generate pmlogrewrite(1) rules to force the PMIDs to be correct.
+ #
+
+-tmp=/var/tmp/mk.rewrite-$$
++tmp=${TMPDIR:-/tmp}/mk.rewrite-$$
+ status=0
+ trap "rm -f $tmp.*; exit \$status" 0 1 2 3 15
+
+--- a/src/pmdas/jbd2/mk.rewrite
++++ b/src/pmdas/jbd2/mk.rewrite
+@@ -6,7 +6,7 @@
+ # platform dependent.
+ #
+
+-tmp=/var/tmp/$$
++tmp=${TMPDIR:-/tmp}/$$
+ trap "rm -f $tmp.*; exit 0" 0 1 2 3 15
+
+ cat <$tmp.c
+--- a/src/pmdas/linux/mk.rewrite
++++ b/src/pmdas/linux/mk.rewrite
+@@ -6,7 +6,7 @@
+ # platform dependent.
+ #
+
+-tmp=/var/tmp/$$
++tmp=${TMPDIR:-/tmp}/$$
+ trap "rm -f $tmp.*; exit 0" 0 1 2 3 15
+
+ cat <$tmp.c
+--- a/src/pmdas/linux_proc/mk.rewrite
++++ b/src/pmdas/linux_proc/mk.rewrite
+@@ -6,7 +6,7 @@
+ # platform dependent.
+ #
+
+-tmp=/var/tmp/$$
++tmp=${TMPDIR:-/tmp}/$$
+ trap "rm -f $tmp.*; exit 0" 0 1 2 3 15
+
+ cat <$tmp.c
+--- a/src/bashrc/getargs
++++ b/src/bashrc/getargs
+@@ -17,7 +17,7 @@ then
+ exit 1
+ fi
+
+-tmp=/var/tmp/bashrc-getargs-$$
++tmp=${TMPDIR:-/tmp}/bashrc-getargs-$$
+ status=1
+ trap "rm -f $tmp.*; exit \$status" 0 1 2 3 15
+
+--- a/src/libpcp/src/check-errorcodes
++++ b/src/libpcp/src/check-errorcodes
+@@ -6,7 +6,7 @@
+ # Check error codes exposure in QA tests.
+ #
+
+-tmp=/var/tmp/check-errorcodes-$$
++tmp=${TMPDIR:-/tmp}/check-errorcodes-$$
+ rm -f $tmp.err
+ trap "sts=0; [ -f $tmp.err ] && sts=1; rm -f $tmp.*; exit \$sts" 0 1 2 3 15
+
+--- a/src/libpcp3/src/check-errorcodes
++++ b/src/libpcp3/src/check-errorcodes
+@@ -6,7 +6,7 @@
+ # Check error codes exposure in QA tests.
+ #
+
+-tmp=/var/tmp/check-errorcodes-$$
++tmp=${TMPDIR:-/tmp}/check-errorcodes-$$
+ rm -f $tmp.err
+ trap "sts=0; [ -f $tmp.err ] && sts=1; rm -f $tmp.*; exit \$sts" 0 1 2 3 15
+
+--- a/src/libpcp/doc/mk.cgraph
++++ b/src/libpcp/doc/mk.cgraph
+@@ -5,7 +5,7 @@
+ # Assumes libpcp source and cscope index are in ../src
+ #
+
+-tmp=/var/tmp/$$
++tmp=${TMPDIR:-/tmp}/$$
+ trap "rm -f $tmp.*; exit \$sts" 0 1 2 3 15
+
+ rm -f $tmp.*
+--- a/src/pmlogcompress/check-optimize
++++ b/src/pmlogcompress/check-optimize
+@@ -9,7 +9,7 @@
+ # below there)
+ #
+
+-tmp=/var/tmp/check-optimize-$$
++tmp=${TMPDIR:-/tmp}/check-optimize-$$
+ here=`pwd`
+
+ verbose=false
+
diff --git a/nix/vm-test.nix b/nix/vm-test.nix
new file mode 100644
index 00000000000..750ecaf28eb
--- /dev/null
+++ b/nix/vm-test.nix
@@ -0,0 +1,95 @@
+# NixOS VM test for Performance Co-Pilot (PCP)
+#
+# This test verifies:
+# - PCP package builds and installs correctly
+# - pmcd daemon can start and listen on port 44321
+# - Basic metrics can be queried via pminfo
+#
+# Run via flake: nix flake check
+# Or standalone: nix build .#checks.x86_64-linux.vm-test
+{
+ pkgs,
+ pcp,
+}:
+
+pkgs.testers.nixosTest {
+ name = "pcp-vm-test";
+
+ nodes.machine =
+ { pkgs, ... }:
+ {
+ environment.systemPackages = [ pcp ];
+
+ # Create pcp user/group required by pmcd
+ users.users.pcp = {
+ isSystemUser = true;
+ group = "pcp";
+ description = "Performance Co-Pilot daemon user";
+ };
+ users.groups.pcp = { };
+
+ # Create required runtime directories
+ systemd.tmpfiles.rules = [
+ "d /var/lib/pcp 0755 pcp pcp -"
+ "d /var/log/pcp 0755 pcp pcp -"
+ "d /run/pcp 0755 pcp pcp -"
+ ];
+ };
+
+ testScript = ''
+ machine.wait_for_unit("multi-user.target")
+
+ # Verify the package is installed and pminfo works
+ machine.succeed("pminfo --version")
+
+ # Find the actual pcp package path by resolving pminfo symlink
+ # pminfo is at /nix/store/xxx-pcp-7.0.5/bin/pminfo
+ # We need to get the package root to find libexec/pcp/bin/pmcd
+ pminfo_real = machine.succeed("realpath $(which pminfo)").strip()
+ print(f"pminfo real path: {pminfo_real}")
+
+ # Get package root (two levels up from bin/pminfo)
+ pkg_root = machine.succeed(f"dirname $(dirname {pminfo_real})").strip()
+ pmcd_path = f"{pkg_root}/libexec/pcp/bin/pmcd"
+ print(f"pmcd expected at: {pmcd_path}")
+
+ # Verify pmcd exists
+ machine.succeed(f"test -x {pmcd_path}")
+
+ # PCP_CONF points to the main configuration file
+ # After our postInstall, config is at share/pcp/etc/pcp.conf
+ pcp_conf = f"{pkg_root}/share/pcp/etc/pcp.conf"
+
+ # Start pmcd daemon in background with PCP_CONF set
+ machine.succeed(f"PCP_CONF={pcp_conf} setsid {pmcd_path} -f > /tmp/pmcd.log 2>&1 &")
+
+ # Give pmcd a moment to start or fail
+ import time
+ time.sleep(3)
+
+ # Debug: check if pmcd is running and show log
+ print("=== Checking pmcd process ===")
+ ps_out = machine.succeed("ps aux | grep pmcd || true")
+ print(ps_out)
+
+ print("=== pmcd log contents ===")
+ log_out = machine.succeed("cat /tmp/pmcd.log 2>/dev/null || echo 'No log file'")
+ print(log_out)
+
+ print("=== Checking listening ports ===")
+ ports_out = machine.succeed("ss -tlnp | grep -E '44321|pmcd' || echo 'No pmcd ports found'")
+ print(ports_out)
+
+ # Wait for pmcd to start listening on its default port
+ machine.wait_for_open_port(44321, timeout=30)
+
+ # Query basic kernel metrics to verify pmcd is working
+ machine.succeed(f"PCP_CONF={pcp_conf} pminfo -f kernel.all.load")
+
+ # Additional verification: check pmcd is responding
+ machine.succeed(f"PCP_CONF={pcp_conf} pminfo -h localhost kernel.all.cpu.user")
+
+ print("=== PCP VM test passed! ===")
+ '';
+}
+