1{ config, lib, pkgs, ... }:
2with lib;
3let
4 cfg = config.services.below;
5 cfgContents = concatStringsSep "\n" (
6 mapAttrsToList (n: v: ''${n} = "${v}"'') (filterAttrs (_k: v: v != null) {
7 log_dir = cfg.dirs.log;
8 store_dir = cfg.dirs.store;
9 cgroup_filter_out = cfg.cgroupFilterOut;
10 })
11 );
12
13 mkDisableOption = n: mkOption {
14 type = types.bool;
15 default = true;
16 description = mdDoc "Whether to enable ${n}.";
17 };
18 optionalType = ty: x: mkOption (x // {
19 description = mdDoc x.description;
20 type = (types.nullOr ty);
21 default = null;
22 });
23 optionalPath = optionalType types.path;
24 optionalStr = optionalType types.str;
25 optionalInt = optionalType types.int;
26in {
27 options = {
28 services.below = {
29 enable = mkEnableOption (mdDoc "'below' resource monitor");
30
31 cgroupFilterOut = optionalStr {
32 description = "A regexp matching the full paths of cgroups whose data shouldn't be collected";
33 example = "user.slice.*";
34 };
35 collect = {
36 diskStats = mkDisableOption "dist_stat collection";
37 ioStats = mkEnableOption (mdDoc "io.stat collection for cgroups");
38 exitStats = mkDisableOption "eBPF-based exitstats";
39 };
40 compression.enable = mkEnableOption (mdDoc "data compression");
41 retention = {
42 size = optionalInt {
43 description = ''
44 Size limit for below's data, in bytes. Data is deleted oldest-first, in 24h 'shards'.
45
46 ::: {.note}
47 The size limit may be exceeded by at most the size of the active shard, as:
48 - the active shard cannot be deleted;
49 - the size limit is only enforced when a new shard is created.
50 :::
51 '';
52 };
53 time = optionalInt {
54 description = ''
55 Retention time, in seconds.
56
57 ::: {.note}
58 As data is stored in 24 hour shards which are discarded as a whole,
59 only data expired by 24h (or more) is guaranteed to be discarded.
60 :::
61
62 ::: {.note}
63 If `retention.size` is set, data may be discarded earlier than the specified time.
64 :::
65 '';
66 };
67 };
68 dirs = {
69 log = optionalPath { description = "Where to store below's logs"; };
70 store = optionalPath {
71 description = "Where to store below's data";
72 example = "/var/lib/below";
73 };
74 };
75 };
76 };
77
78 config = mkIf cfg.enable {
79 environment.systemPackages = [ pkgs.below ];
80 # /etc/below.conf is also refered to by the `below` CLI tool,
81 # so this can't be a store-only file whose path is passed to the service
82 environment.etc."below/below.conf".text = cfgContents;
83
84 systemd = {
85 packages = [ pkgs.below ];
86 services.below = {
87 # Workaround for https://github.com/NixOS/nixpkgs/issues/81138
88 wantedBy = [ "multi-user.target" ];
89 restartTriggers = [ cfgContents ];
90
91 serviceConfig.ExecStart = [
92 ""
93 ("${lib.getExe pkgs.below} record " + (concatStringsSep " " (
94 optional (!cfg.collect.diskStats) "--disable-disk-stat" ++
95 optional cfg.collect.ioStats "--collect-io-stat" ++
96 optional (!cfg.collect.exitStats) "--disable-exitstats" ++
97 optional cfg.compression.enable "--compress" ++
98
99 optional (cfg.retention.size != null) "--store-size-limit ${toString cfg.retention.size}" ++
100 optional (cfg.retention.time != null) "--retain-for-s ${toString cfg.retention.time}"
101 )))
102 ];
103 };
104 };
105 };
106
107 meta.maintainers = with lib.maintainers; [ nicoo ];
108}