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