1 { config, lib, options, pkgs, ... }:
3 cfg = config.services.graphite;
4 opt = options.services.graphite;
5 writeTextOrNull = f: t: lib.mapNullable (pkgs.writeTextDir f) t;
8 staticDir = cfg.dataDir + "/static";
10 graphiteLocalSettingsDir = pkgs.runCommand "graphite_local_settings" {
11 inherit graphiteLocalSettings;
12 preferLocalBuild = true;
15 ln -s $graphiteLocalSettings $out/graphite_local_settings.py
18 graphiteLocalSettings = pkgs.writeText "graphite_local_settings.py" (
19 "STATIC_ROOT = '${staticDir}'\n" +
20 lib.optionalString (config.time.timeZone != null) "TIME_ZONE = '${config.time.timeZone}'\n"
25 SEYREN_URL = cfg.seyren.seyrenUrl;
26 MONGO_URL = cfg.seyren.mongoUrl;
27 GRAPHITE_URL = cfg.seyren.graphiteUrl;
28 } // cfg.seyren.extraConfig;
30 configDir = pkgs.buildEnv {
31 name = "graphite-config";
32 paths = lib.lists.filter (el: el != null) [
33 (writeTextOrNull "carbon.conf" cfg.carbon.config)
34 (writeTextOrNull "storage-aggregation.conf" cfg.carbon.storageAggregation)
35 (writeTextOrNull "storage-schemas.conf" cfg.carbon.storageSchemas)
36 (writeTextOrNull "blacklist.conf" cfg.carbon.blacklist)
37 (writeTextOrNull "whitelist.conf" cfg.carbon.whitelist)
38 (writeTextOrNull "rewrite-rules.conf" cfg.carbon.rewriteRules)
39 (writeTextOrNull "relay-rules.conf" cfg.carbon.relayRules)
40 (writeTextOrNull "aggregation-rules.conf" cfg.carbon.aggregationRules)
44 carbonOpts = name: with config.ids; ''
45 --nodaemon --syslog --prefix=${name} --pidfile /run/${name}/${name}.pid ${name}
50 cenv = pkgs.python3.buildEnv.override {
51 extraLibs = [ pkgs.python3Packages.carbon ];
53 in "${cenv}/${pkgs.python3.sitePackages}";
54 GRAPHITE_ROOT = dataDir;
55 GRAPHITE_CONF_DIR = configDir;
56 GRAPHITE_STORAGE_DIR = dataDir;
62 (lib.mkRemovedOptionModule ["services" "graphite" "api"] "")
63 (lib.mkRemovedOptionModule ["services" "graphite" "beacon"] "")
64 (lib.mkRemovedOptionModule ["services" "graphite" "pager"] "")
69 options.services.graphite = {
70 dataDir = lib.mkOption {
71 type = lib.types.path;
72 default = "/var/db/graphite";
74 Data directory for graphite.
79 enable = lib.mkOption {
80 description = "Whether to enable graphite web frontend.";
82 type = lib.types.bool;
85 listenAddress = lib.mkOption {
86 description = "Graphite web frontend listen address.";
87 default = "127.0.0.1";
92 description = "Graphite web frontend port.";
94 type = lib.types.port;
97 extraConfig = lib.mkOption {
101 Graphite webapp settings. See:
102 <https://graphite.readthedocs.io/en/latest/config-local-settings.html>
108 config = lib.mkOption {
109 description = "Content of carbon configuration file.";
112 # Listen on localhost by default for security reasons
113 UDP_RECEIVER_INTERFACE = 127.0.0.1
114 PICKLE_RECEIVER_INTERFACE = 127.0.0.1
115 LINE_RECEIVER_INTERFACE = 127.0.0.1
116 CACHE_QUERY_INTERFACE = 127.0.0.1
117 # Do not log every update
119 LOG_CACHE_HITS = False
121 type = lib.types.str;
124 enableCache = lib.mkOption {
125 description = "Whether to enable carbon cache, the graphite storage daemon.";
127 type = lib.types.bool;
130 storageAggregation = lib.mkOption {
131 description = "Defines how to aggregate data to lower-precision retentions.";
133 type = lib.types.nullOr lib.types.str;
138 aggregationMethod = min
142 storageSchemas = lib.mkOption {
143 description = "Defines retention rates for storing metrics.";
145 type = lib.types.nullOr lib.types.str;
148 pattern = ^servers\.www.*\.workers\.busyWorkers$
149 retentions = 15s:7d,1m:21d,15m:5y
153 blacklist = lib.mkOption {
154 description = "Any metrics received which match one of the expressions will be dropped.";
156 type = lib.types.nullOr lib.types.str;
157 example = "^some\\.noisy\\.metric\\.prefix\\..*";
160 whitelist = lib.mkOption {
161 description = "Only metrics received which match one of the expressions will be persisted.";
163 type = lib.types.nullOr lib.types.str;
167 rewriteRules = lib.mkOption {
169 Regular expression patterns that can be used to rewrite metric names
170 in a search and replace fashion.
173 type = lib.types.nullOr lib.types.str;
181 enableRelay = lib.mkOption {
182 description = "Whether to enable carbon relay, the carbon replication and sharding service.";
184 type = lib.types.bool;
187 relayRules = lib.mkOption {
188 description = "Relay rules are used to send certain metrics to a certain backend.";
190 type = lib.types.nullOr lib.types.str;
193 pattern = ^mydata\.foo\..+
194 servers = 10.1.2.3, 10.1.2.4:2004, myserver.mydomain.com
198 enableAggregator = lib.mkOption {
199 description = "Whether to enable carbon aggregator, the carbon buffering service.";
201 type = lib.types.bool;
204 aggregationRules = lib.mkOption {
205 description = "Defines if and how received metrics will be aggregated.";
207 type = lib.types.nullOr lib.types.str;
209 <env>.applications.<app>.all.requests (60) = sum <env>.applications.<app>.*.requests
210 <env>.applications.<app>.all.latency (60) = avg <env>.applications.<app>.*.latency
216 enable = lib.mkOption {
217 description = "Whether to enable seyren service.";
219 type = lib.types.bool;
222 port = lib.mkOption {
223 description = "Seyren listening port.";
225 type = lib.types.port;
228 seyrenUrl = lib.mkOption {
229 default = "http://localhost:${toString cfg.seyren.port}/";
230 defaultText = lib.literalExpression ''"http://localhost:''${toString config.${opt.seyren.port}}/"'';
231 description = "Host where seyren is accessible.";
232 type = lib.types.str;
235 graphiteUrl = lib.mkOption {
236 default = "http://${cfg.web.listenAddress}:${toString cfg.web.port}";
237 defaultText = lib.literalExpression ''"http://''${config.${opt.web.listenAddress}}:''${toString config.${opt.web.port}}"'';
238 description = "Host where graphite service runs.";
239 type = lib.types.str;
242 mongoUrl = lib.mkOption {
243 default = "mongodb://${config.services.mongodb.bind_ip}:27017/seyren";
244 defaultText = lib.literalExpression ''"mongodb://''${config.services.mongodb.bind_ip}:27017/seyren"'';
245 description = "Mongodb connection string.";
246 type = lib.types.str;
249 extraConfig = lib.mkOption {
252 Extra seyren configuration. See
253 <https://github.com/scobal/seyren#config>
255 type = lib.types.attrsOf lib.types.str;
256 example = lib.literalExpression ''
258 GRAPHITE_USERNAME = "user";
259 GRAPHITE_PASSWORD = "pass";
266 ###### implementation
268 config = lib.mkMerge [
269 (lib.mkIf cfg.carbon.enableCache {
270 systemd.services.carbonCache = let name = "carbon-cache"; in {
271 description = "Graphite Data Storage Backend";
272 wantedBy = [ "multi-user.target" ];
273 after = [ "network.target" ];
274 environment = carbonEnv;
276 Slice = "system-graphite.slice";
277 RuntimeDirectory = name;
278 ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
281 PermissionsStartOnly = true;
282 PIDFile="/run/${name}/${name}.pid";
285 install -dm0700 -o graphite -g graphite ${cfg.dataDir}
286 install -dm0700 -o graphite -g graphite ${cfg.dataDir}/whisper
291 (lib.mkIf cfg.carbon.enableAggregator {
292 systemd.services.carbonAggregator = let name = "carbon-aggregator"; in {
293 enable = cfg.carbon.enableAggregator;
294 description = "Carbon Data Aggregator";
295 wantedBy = [ "multi-user.target" ];
296 after = [ "network.target" ];
297 environment = carbonEnv;
299 Slice = "system-graphite.slice";
300 RuntimeDirectory = name;
301 ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
304 PIDFile="/run/${name}/${name}.pid";
309 (lib.mkIf cfg.carbon.enableRelay {
310 systemd.services.carbonRelay = let name = "carbon-relay"; in {
311 description = "Carbon Data Relay";
312 wantedBy = [ "multi-user.target" ];
313 after = [ "network.target" ];
314 environment = carbonEnv;
316 Slice = "system-graphite.slice";
317 RuntimeDirectory = name;
318 ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
321 PIDFile="/run/${name}/${name}.pid";
326 (lib.mkIf (cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay) {
327 environment.systemPackages = [
328 pkgs.python3Packages.carbon
332 (lib.mkIf cfg.web.enable ({
333 systemd.services.graphiteWeb = {
334 description = "Graphite Web Interface";
335 wantedBy = [ "multi-user.target" ];
336 after = [ "network.target" ];
337 path = [ pkgs.perl ];
340 penv = pkgs.python3.buildEnv.override {
342 pkgs.python3Packages.graphite-web
345 penvPack = "${penv}/${pkgs.python3.sitePackages}";
346 in lib.concatStringsSep ":" [
347 "${graphiteLocalSettingsDir}"
349 # explicitly adding pycairo in path because it cannot be imported via buildEnv
350 "${pkgs.python3Packages.pycairo}/${pkgs.python3.sitePackages}"
352 DJANGO_SETTINGS_MODULE = "graphite.settings";
353 GRAPHITE_SETTINGS_MODULE = "graphite_local_settings";
354 GRAPHITE_CONF_DIR = configDir;
355 GRAPHITE_STORAGE_DIR = dataDir;
356 LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib";
360 ${pkgs.python3Packages.waitress-django}/bin/waitress-serve-django \
361 --host=${cfg.web.listenAddress} --port=${toString cfg.web.port}
365 PermissionsStartOnly = true;
366 Slice = "system-graphite.slice";
369 if ! test -e ${dataDir}/db-created; then
370 mkdir -p ${dataDir}/{whisper/,log/webapp/}
371 chmod 0700 ${dataDir}/{whisper/,log/webapp/}
373 ${pkgs.python3Packages.django}/bin/django-admin.py migrate --noinput
375 chown -R graphite:graphite ${dataDir}
377 touch ${dataDir}/db-created
380 # Only collect static files when graphite_web changes.
381 if ! [ "${dataDir}/current_graphite_web" -ef "${pkgs.python3Packages.graphite-web}" ]; then
382 mkdir -p ${staticDir}
383 ${pkgs.python3Packages.django}/bin/django-admin.py collectstatic --noinput --clear
384 chown -R graphite:graphite ${staticDir}
385 ln -sfT "${pkgs.python3Packages.graphite-web}" "${dataDir}/current_graphite_web"
390 environment.systemPackages = [ pkgs.python3Packages.graphite-web ];
393 (lib.mkIf cfg.seyren.enable {
394 systemd.services.seyren = {
395 description = "Graphite Alerting Dashboard";
396 wantedBy = [ "multi-user.target" ];
397 after = [ "network.target" "mongodb.service" ];
398 environment = seyrenConfig;
400 ExecStart = "${pkgs.seyren}/bin/seyren -httpPort ${toString cfg.seyren.port}";
401 WorkingDirectory = dataDir;
404 Slice = "system-graphite.slice";
407 if ! test -e ${dataDir}/db-created; then
409 chown graphite:graphite ${dataDir}
414 services.mongodb.enable = lib.mkDefault true;
418 cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay ||
419 cfg.web.enable || cfg.seyren.enable
421 systemd.slices.system-graphite = {
422 description = "Graphite Graphing System Slice";
423 documentation = [ "https://graphite.readthedocs.io/en/latest/overview.html" ];
426 users.users.graphite = {
427 uid = config.ids.uids.graphite;
429 description = "Graphite daemon user";
432 users.groups.graphite.gid = config.ids.gids.graphite;