1 { config, lib, options, pkgs, ... }:
6 cfg = config.services.graphite;
7 opt = options.services.graphite;
8 writeTextOrNull = f: t: mapNullable (pkgs.writeTextDir f) t;
10 dataDir = cfg.dataDir;
11 staticDir = cfg.dataDir + "/static";
13 graphiteLocalSettingsDir = pkgs.runCommand "graphite_local_settings" {
14 inherit graphiteLocalSettings;
15 preferLocalBuild = true;
18 ln -s $graphiteLocalSettings $out/graphite_local_settings.py
21 graphiteLocalSettings = pkgs.writeText "graphite_local_settings.py" (
22 "STATIC_ROOT = '${staticDir}'\n" +
23 optionalString (config.time.timeZone != null) "TIME_ZONE = '${config.time.timeZone}'\n"
28 SEYREN_URL = cfg.seyren.seyrenUrl;
29 MONGO_URL = cfg.seyren.mongoUrl;
30 GRAPHITE_URL = cfg.seyren.graphiteUrl;
31 } // cfg.seyren.extraConfig;
33 configDir = pkgs.buildEnv {
34 name = "graphite-config";
35 paths = lists.filter (el: el != null) [
36 (writeTextOrNull "carbon.conf" cfg.carbon.config)
37 (writeTextOrNull "storage-aggregation.conf" cfg.carbon.storageAggregation)
38 (writeTextOrNull "storage-schemas.conf" cfg.carbon.storageSchemas)
39 (writeTextOrNull "blacklist.conf" cfg.carbon.blacklist)
40 (writeTextOrNull "whitelist.conf" cfg.carbon.whitelist)
41 (writeTextOrNull "rewrite-rules.conf" cfg.carbon.rewriteRules)
42 (writeTextOrNull "relay-rules.conf" cfg.carbon.relayRules)
43 (writeTextOrNull "aggregation-rules.conf" cfg.carbon.aggregationRules)
47 carbonOpts = name: with config.ids; ''
48 --nodaemon --syslog --prefix=${name} --pidfile /run/${name}/${name}.pid ${name}
53 cenv = pkgs.python3.buildEnv.override {
54 extraLibs = [ pkgs.python3Packages.carbon ];
56 in "${cenv}/${pkgs.python3.sitePackages}";
57 GRAPHITE_ROOT = dataDir;
58 GRAPHITE_CONF_DIR = configDir;
59 GRAPHITE_STORAGE_DIR = dataDir;
65 (mkRemovedOptionModule ["services" "graphite" "api"] "")
66 (mkRemovedOptionModule ["services" "graphite" "beacon"] "")
67 (mkRemovedOptionModule ["services" "graphite" "pager"] "")
72 options.services.graphite = {
75 default = "/var/db/graphite";
76 description = lib.mdDoc ''
77 Data directory for graphite.
83 description = lib.mdDoc "Whether to enable graphite web frontend.";
88 listenAddress = mkOption {
89 description = lib.mdDoc "Graphite web frontend listen address.";
90 default = "127.0.0.1";
95 description = lib.mdDoc "Graphite web frontend port.";
100 extraConfig = mkOption {
103 description = lib.mdDoc ''
104 Graphite webapp settings. See:
105 <http://graphite.readthedocs.io/en/latest/config-local-settings.html>
112 description = lib.mdDoc "Content of carbon configuration file.";
115 # Listen on localhost by default for security reasons
116 UDP_RECEIVER_INTERFACE = 127.0.0.1
117 PICKLE_RECEIVER_INTERFACE = 127.0.0.1
118 LINE_RECEIVER_INTERFACE = 127.0.0.1
119 CACHE_QUERY_INTERFACE = 127.0.0.1
120 # Do not log every update
122 LOG_CACHE_HITS = False
127 enableCache = mkOption {
128 description = lib.mdDoc "Whether to enable carbon cache, the graphite storage daemon.";
133 storageAggregation = mkOption {
134 description = lib.mdDoc "Defines how to aggregate data to lower-precision retentions.";
136 type = types.nullOr types.str;
141 aggregationMethod = min
145 storageSchemas = mkOption {
146 description = lib.mdDoc "Defines retention rates for storing metrics.";
148 type = types.nullOr types.str;
151 pattern = ^servers\.www.*\.workers\.busyWorkers$
152 retentions = 15s:7d,1m:21d,15m:5y
156 blacklist = mkOption {
157 description = lib.mdDoc "Any metrics received which match one of the experssions will be dropped.";
159 type = types.nullOr types.str;
160 example = "^some\\.noisy\\.metric\\.prefix\\..*";
163 whitelist = mkOption {
164 description = lib.mdDoc "Only metrics received which match one of the experssions will be persisted.";
166 type = types.nullOr types.str;
170 rewriteRules = mkOption {
171 description = lib.mdDoc ''
172 Regular expression patterns that can be used to rewrite metric names
173 in a search and replace fashion.
176 type = types.nullOr types.str;
184 enableRelay = mkOption {
185 description = lib.mdDoc "Whether to enable carbon relay, the carbon replication and sharding service.";
190 relayRules = mkOption {
191 description = lib.mdDoc "Relay rules are used to send certain metrics to a certain backend.";
193 type = types.nullOr types.str;
196 pattern = ^mydata\.foo\..+
197 servers = 10.1.2.3, 10.1.2.4:2004, myserver.mydomain.com
201 enableAggregator = mkOption {
202 description = lib.mdDoc "Whether to enable carbon aggregator, the carbon buffering service.";
207 aggregationRules = mkOption {
208 description = lib.mdDoc "Defines if and how received metrics will be aggregated.";
210 type = types.nullOr types.str;
212 <env>.applications.<app>.all.requests (60) = sum <env>.applications.<app>.*.requests
213 <env>.applications.<app>.all.latency (60) = avg <env>.applications.<app>.*.latency
220 description = lib.mdDoc "Whether to enable seyren service.";
226 description = lib.mdDoc "Seyren listening port.";
231 seyrenUrl = mkOption {
232 default = "http://localhost:${toString cfg.seyren.port}/";
233 defaultText = literalExpression ''"http://localhost:''${toString config.${opt.seyren.port}}/"'';
234 description = lib.mdDoc "Host where seyren is accessible.";
238 graphiteUrl = mkOption {
239 default = "http://${cfg.web.listenAddress}:${toString cfg.web.port}";
240 defaultText = literalExpression ''"http://''${config.${opt.web.listenAddress}}:''${toString config.${opt.web.port}}"'';
241 description = lib.mdDoc "Host where graphite service runs.";
245 mongoUrl = mkOption {
246 default = "mongodb://${config.services.mongodb.bind_ip}:27017/seyren";
247 defaultText = literalExpression ''"mongodb://''${config.services.mongodb.bind_ip}:27017/seyren"'';
248 description = lib.mdDoc "Mongodb connection string.";
252 extraConfig = mkOption {
254 description = lib.mdDoc ''
255 Extra seyren configuration. See
256 <https://github.com/scobal/seyren#config>
258 type = types.attrsOf types.str;
259 example = literalExpression ''
261 GRAPHITE_USERNAME = "user";
262 GRAPHITE_PASSWORD = "pass";
269 ###### implementation
272 (mkIf cfg.carbon.enableCache {
273 systemd.services.carbonCache = let name = "carbon-cache"; in {
274 description = "Graphite Data Storage Backend";
275 wantedBy = [ "multi-user.target" ];
276 after = [ "network.target" ];
277 environment = carbonEnv;
279 RuntimeDirectory = name;
280 ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
283 PermissionsStartOnly = true;
284 PIDFile="/run/${name}/${name}.pid";
287 install -dm0700 -o graphite -g graphite ${cfg.dataDir}
288 install -dm0700 -o graphite -g graphite ${cfg.dataDir}/whisper
293 (mkIf cfg.carbon.enableAggregator {
294 systemd.services.carbonAggregator = let name = "carbon-aggregator"; in {
295 enable = cfg.carbon.enableAggregator;
296 description = "Carbon Data Aggregator";
297 wantedBy = [ "multi-user.target" ];
298 after = [ "network.target" ];
299 environment = carbonEnv;
301 RuntimeDirectory = name;
302 ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
305 PIDFile="/run/${name}/${name}.pid";
310 (mkIf cfg.carbon.enableRelay {
311 systemd.services.carbonRelay = let name = "carbon-relay"; in {
312 description = "Carbon Data Relay";
313 wantedBy = [ "multi-user.target" ];
314 after = [ "network.target" ];
315 environment = carbonEnv;
317 RuntimeDirectory = name;
318 ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
321 PIDFile="/run/${name}/${name}.pid";
326 (mkIf (cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay) {
327 environment.systemPackages = [
328 pkgs.python3Packages.carbon
332 (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 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;
368 if ! test -e ${dataDir}/db-created; then
369 mkdir -p ${dataDir}/{whisper/,log/webapp/}
370 chmod 0700 ${dataDir}/{whisper/,log/webapp/}
372 ${pkgs.python3Packages.django}/bin/django-admin.py migrate --noinput
374 chown -R graphite:graphite ${dataDir}
376 touch ${dataDir}/db-created
379 # Only collect static files when graphite_web changes.
380 if ! [ "${dataDir}/current_graphite_web" -ef "${pkgs.python3Packages.graphite-web}" ]; then
381 mkdir -p ${staticDir}
382 ${pkgs.python3Packages.django}/bin/django-admin.py collectstatic --noinput --clear
383 chown -R graphite:graphite ${staticDir}
384 ln -sfT "${pkgs.python3Packages.graphite-web}" "${dataDir}/current_graphite_web"
389 environment.systemPackages = [ pkgs.python3Packages.graphite-web ];
392 (mkIf cfg.seyren.enable {
393 systemd.services.seyren = {
394 description = "Graphite Alerting Dashboard";
395 wantedBy = [ "multi-user.target" ];
396 after = [ "network.target" "mongodb.service" ];
397 environment = seyrenConfig;
399 ExecStart = "${pkgs.seyren}/bin/seyren -httpPort ${toString cfg.seyren.port}";
400 WorkingDirectory = dataDir;
405 if ! test -e ${dataDir}/db-created; then
407 chown graphite:graphite ${dataDir}
412 services.mongodb.enable = mkDefault true;
416 cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay ||
417 cfg.web.enable || cfg.seyren.enable
419 users.users.graphite = {
420 uid = config.ids.uids.graphite;
422 description = "Graphite daemon user";
425 users.groups.graphite.gid = config.ids.gids.graphite;