<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Snamellit - sysadmin</title>
      <link>https://www.snamellit.com</link>
      <description></description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://www.snamellit.com/categories/sysadmin/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Fri, 09 Oct 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>Measuring CI budgets with Guix and Grafana before setting quotas</title>
          <pubDate>Fri, 09 Oct 2026 00:00:00 +0000</pubDate>
          <author>Peter Tillemans</author>
          <link>https://www.snamellit.com/posts/measuring-ci-budgets-with-guix-and-grafana/</link>
          <guid>https://www.snamellit.com/posts/measuring-ci-budgets-with-guix-and-grafana/</guid>
          <description xml:base="https://www.snamellit.com/posts/measuring-ci-budgets-with-guix-and-grafana/">&lt;p&gt;Emma does several jobs for me. It runs &lt;a href=&quot;/posts/deploying-cuirass-on-guixsd/&quot;&gt;Cuirass&lt;/a&gt; for Guix builds and a Forgejo Actions runner, and I would like to add more services without discovering too late that CI ate the machine. My first instinct was to give each workload a quota: 11 GiB and four CPU cores for builds, 2 GiB and one core for the runner. Those are useful planning numbers, but applying them before measuring the jobs would have been guesswork.&lt;/p&gt;
&lt;p&gt;So this is the &lt;em&gt;measurement&lt;/em&gt; stage of setting a quota. The numbers appear as lines in Grafana; they are &lt;strong&gt;not&lt;/strong&gt; &lt;code&gt;memory.max&lt;/code&gt;, &lt;code&gt;memory.high&lt;/code&gt; or &lt;code&gt;cpu.max&lt;/code&gt;. A graph can show that a workload crossed a line without throttling or killing it. I will only consider limits after watching real, overlapping jobs, checking the host&#39;s spare capacity and comparing CI completion times.&lt;/p&gt;
&lt;h2 id=&quot;guix-owns-the-layout&quot;&gt;Guix owns the layout&lt;/h2&gt;
&lt;p&gt;Emma runs Guix System, so I put the plumbing in its &lt;code&gt;machines/emma/system.scm&lt;/code&gt; configuration rather than relying on a script somebody has to remember to rerun after a reboot. A one-shot Shepherd service prepares these cgroup-v2 paths:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;/sys/fs/cgroup/emma/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── ci-builds/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── cuirass/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── guix-daemon/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── emma-runner/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ├── runner/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    └── podman/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Its Python helper, &lt;code&gt;workload_cgroups.py&lt;/code&gt;, checks that the &lt;code&gt;cpu&lt;/code&gt;, &lt;code&gt;memory&lt;/code&gt; and &lt;code&gt;io&lt;/code&gt; controllers are available, enables them on the parents and creates the leaves. It is idempotent and refuses to proceed if the required controllers are missing. The Cuirass and Guix-daemon leaves stay root-owned; only the runner&#39;s own leaves are delegated to its service account. PostgreSQL and host-management processes do not belong to either workload.&lt;/p&gt;
&lt;p&gt;In Scheme, the dependency is explicit. The service graph requires the layout before the daemons can be started:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot; &gt;&lt;code data-lang=&quot;scheme&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;define&lt;/span&gt;&lt;span&gt; %emma-cgroup-layout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;  (&lt;/span&gt;&lt;span&gt;shepherd-service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;    (&lt;/span&gt;&lt;span&gt;provision &lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;(&lt;/span&gt;&lt;span&gt;emma-cgroup-layout&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;    (&lt;/span&gt;&lt;span&gt;requirement &lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;(&lt;/span&gt;&lt;span&gt;user-processes cgroups2-fs-owner&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                   file-system-/sys/fs/cgroup&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;    (&lt;/span&gt;&lt;span&gt;one-shot? &lt;/span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;#t&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;    (&lt;/span&gt;&lt;span&gt;start #~&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;/span&gt;&lt;span&gt;make-forkexec-constructor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;               (&lt;/span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;list&lt;/span&gt;&lt;span&gt; #$&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;/span&gt;&lt;span&gt;file-append python &lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;/bin/python3&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                     #$&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;/span&gt;&lt;span&gt;local-file &lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;workload_cgroups.py&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;))))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;    (&lt;/span&gt;&lt;span&gt;stop #~&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;/span&gt;&lt;span&gt;make-kill-destructor&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;))))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;/span&gt;&lt;span&gt;simple-service &lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;emma-cgroup-layout&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                shepherd-root-service-type&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;                (&lt;/span&gt;&lt;span style=&quot;color: #88C0D0;&quot;&gt;list&lt;/span&gt;&lt;span&gt; %emma-cgroup-layout&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;local-file&lt;/code&gt; matters. The helper becomes part of the system build, tied to the configuration generation. The service definitions and the private Guix channel supplying the launchers are versioned together. A deployment can be built and checked before switching the running host, and the previous system generation remains a separate rollback path from Git.&lt;/p&gt;
&lt;h2 id=&quot;put-processes-in-the-group-before-they-drop-privileges&quot;&gt;Put processes in the group before they drop privileges&lt;/h2&gt;
&lt;p&gt;Creating a cgroup is easy. Accounting for the &lt;em&gt;actual descendants&lt;/em&gt; is harder. Moving a running service supervisor into a group later can miss children it has already launched. The Guix channel therefore supplies a &lt;code&gt;cgroup-launch-overlay&lt;/code&gt;: a file-like package tree that preserves the original package but replaces one executable with an &lt;code&gt;exec&lt;/code&gt;-in-place launcher. The launcher writes its own PID to the leaf&#39;s &lt;code&gt;cgroup.procs&lt;/code&gt;, drops to the normal service account where needed, then executes the original daemon. If placement fails, the service does not silently run outside accounting.&lt;/p&gt;
&lt;p&gt;This ordering caught a real bug in the first draft. The normal Cuirass Shepherd start dropped to &lt;code&gt;cuirass&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; my wrapper tried to enter its root-owned leaf. It failed with &lt;code&gt;Permission denied&lt;/code&gt;. I adapted the Cuirass service type&#39;s two Shepherd starts instead, keeping the upstream account, activation sockets and other service extensions while entering the group as root and switching back to &lt;code&gt;cuirass&lt;/code&gt; before executing the daemon. The shared Guix daemon has its own leaf because builds it starts cannot honestly be counted as Cuirass-only work.&lt;/p&gt;
&lt;p&gt;The Forgejo runner has a separate leaf for host-mode jobs, and its rootless Podman socket service has another. The runner depends on the Podman socket and on &lt;code&gt;emma-cgroup-layout&lt;/code&gt;. Having the socket daemon in the right group is not proof that a container process lands there; descendant placement needs to be checked during a real container job.&lt;/p&gt;
&lt;h2 id=&quot;from-sys-fs-cgroup-to-grafana&quot;&gt;From &lt;code&gt;/sys/fs/cgroup&lt;/code&gt; to Grafana&lt;/h2&gt;
&lt;p&gt;A second Python helper reads each &lt;em&gt;parent&lt;/em&gt; group&#39;s &lt;code&gt;memory.current&lt;/code&gt;, &lt;code&gt;memory.swap.current&lt;/code&gt;, &lt;code&gt;cpu.stat&lt;/code&gt;, &lt;code&gt;memory.events&lt;/code&gt; and &lt;code&gt;io.stat&lt;/code&gt;. Parent counters include their descendant leaves. Guix installs an mcron job that runs the collector every minute and atomically replaces a &lt;code&gt;.prom&lt;/code&gt; file in node exporter&#39;s textfile directory:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot; &gt;&lt;code data-lang=&quot;scheme&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #81A1C1;&quot;&gt;define&lt;/span&gt;&lt;span&gt; %emma-workload-job&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  #~&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;/span&gt;&lt;span&gt;job &lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;#39;(&lt;/span&gt;&lt;span&gt;next-minute &lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;/span&gt;&lt;span&gt;range &lt;/span&gt;&lt;span style=&quot;color: #B48EAD;&quot;&gt;0 60&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;         (&lt;/span&gt;&lt;span&gt;string-append&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          #$&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;/span&gt;&lt;span&gt;file-append python &lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;/bin/python3&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;) &amp;quot; &amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          #$&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;(&lt;/span&gt;&lt;span&gt;local-file &lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt;workload_metrics.py&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;          &amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --output /var/lib/prometheus/node-exporter/emma-workloads.prom&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;          &amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --group ci-builds=emma/ci-builds&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;          &amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --budget ci-builds=11,4&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;          &amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --group emma-runner=emma/emma-runner&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;          &amp;quot;&lt;/span&gt;&lt;span style=&quot;color: #A3BE8C;&quot;&gt; --budget emma-runner=2,1&lt;/span&gt;&lt;span style=&quot;color: #ECEFF4;&quot;&gt;&amp;quot;)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The budget pairs mean GiB and CPU cores. The collector exports &lt;code&gt;emma_workload_budget_memory_bytes&lt;/code&gt; and &lt;code&gt;emma_workload_budget_cpu_cores&lt;/code&gt; as &lt;em&gt;reference gauges&lt;/em&gt;, alongside measured memory, swap, CPU time, throttling, OOM events and I/O bytes. It also emits &lt;code&gt;emma_workload_present&lt;/code&gt; and a collection timestamp. An incomplete or missing group gets &lt;code&gt;present=0&lt;/code&gt; &lt;strong&gt;without invented usage samples&lt;/strong&gt;. Node exporter exposes the textfile, Prometheus scrapes it, and Grafana reads Prometheus; Grafana does not create or enforce any quota. This follows node exporter&#39;s &lt;a rel=&quot;external&quot; href=&quot;https://github.com/prometheus/node_exporter#textfile-collector&quot;&gt;textfile-collector pattern&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I use the existing &lt;strong&gt;Emma Capacity &amp;amp; Budgets&lt;/strong&gt; dashboard. Here are its two workload panels from a live, two-hour Grafana view on 9 October 2026. The screenshot shows the &lt;code&gt;ci-builds&lt;/code&gt; CPU curve crossing its four-core planning line. That is an observation, not throttling or evidence that a quota has been applied. The capture predates the completed 24-hour review.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/images/emma-grafana-workload-budgets-2026-10-09.png&quot; alt=&quot;Two Grafana panels showing Emma workload memory and CPU measurements beside planning lines for ci-builds and emma-runner&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The memory panel plots actual usage and the planning line for each workload:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;emma_workload_memory_bytes{machine=&amp;quot;emma&amp;quot;,workload=~&amp;quot;ci-builds|emma-runner&amp;quot;} / 1024^3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;emma_workload_budget_memory_bytes{machine=&amp;quot;emma&amp;quot;,workload=~&amp;quot;ci-builds|emma-runner&amp;quot;} / 1024^3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The CPU panel uses a five-minute rate against the core budget:&lt;/p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #D8DEE9; background-color: #2E3440;&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;rate(emma_workload_cpu_seconds_total{machine=&amp;quot;emma&amp;quot;,workload=~&amp;quot;ci-builds|emma-runner&amp;quot;}[5m])&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;emma_workload_budget_cpu_cores{machine=&amp;quot;emma&amp;quot;,workload=~&amp;quot;ci-builds|emma-runner&amp;quot;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those are two Grafana queries per time-series panel, not a command that installs limits. I keep the host&#39;s available memory, root disk space, CPU execution versus I/O wait and OOM/swap activity alongside them. The coverage stat reads &lt;code&gt;emma_workload_present&lt;/code&gt;; a missing series is &lt;em&gt;No data&lt;/em&gt;, not zero usage. The dashboard was designed before the exporter existed and still contains some &quot;proposed / not yet verified&quot; explanatory text. Some per-workload curves are now live, but that text should only be revised once completed jobs and descendants have been checked. Until then, a pretty plot is not proof of correct attribution.&lt;/p&gt;
&lt;h2 id=&quot;rollout-not-just-reconfigure&quot;&gt;Rollout, not just reconfigure&lt;/h2&gt;
&lt;p&gt;I tested the layout and launch ordering in a disposable, network-disabled Guix VM. A read-only-store VM was enough to check placement but could not run an actual Cuirass register that wrote to the store, so I repeated that check with a guest-owned writable QCOW2 store. Both real Cuirass daemons then ran as &lt;code&gt;cuirass&lt;/code&gt; in the intended leaf. That still was not a representative online build.&lt;/p&gt;
&lt;p&gt;For Emma itself, I reviewed the channel and system changes on separate Git branches, ran the service regression check and &lt;code&gt;guix system shepherd-graph&lt;/code&gt;, and dry-ran the system build. A full build on Emma used one build job and one core after an earlier attempt ran into memory pressure. Only then did an operator reconfigure the host and, later, perform a normal reboot with the previous generation available at the console. After restarting the affected services, I checked actual PIDs in &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/cgroup&lt;/code&gt;, the collector file and successive healthy Prometheus scrapes. The post-boot 24-hour observation began on 9 October 2026 at 14:39 UTC, &lt;em&gt;after&lt;/em&gt; those checks.&lt;/p&gt;
&lt;p&gt;That reboot exposed a separate loose end: Cuirass, the runner, its Podman socket, git-pages and node exporter did not all start unattended. We brought them up manually. A Tailscale-address race might explain some listeners, but it does not explain every stopped CI service; the boot logs still need investigation. Manual recovery is not a successful boot test.&lt;/p&gt;
&lt;p&gt;The next pass is to match the curves against completed Cuirass builds and Forgejo jobs (including container descendants), inspect the same-window service logs and compare job duration with a baseline. The shared Guix daemon&#39;s counters measure &lt;em&gt;all&lt;/em&gt; its clients, not just Cuirass. Until that work is done, I have a useful accounting experiment and a rollback path, not a safe quota recommendation.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https://guix.gnu.org/manual/en/html_node/Invoking-guix-system.html&quot;&gt;Guix system manual&lt;/a&gt; covers generations and reconfiguration; the &lt;a rel=&quot;external&quot; href=&quot;https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html&quot;&gt;kernel cgroup-v2 documentation&lt;/a&gt; explains the counter and control files. Grafana&#39;s &lt;a rel=&quot;external&quot; href=&quot;https://grafana.com/docs/grafana/latest/visualizations/&quot;&gt;visualization documentation&lt;/a&gt; covers building panels from Prometheus queries.&lt;/p&gt;
</description>
      </item>
    </channel>
</rss>
