From 0218c4136a63387f122301d9dfb5b71c310d0807 Mon Sep 17 00:00:00 2001 From: Ian Eure Date: Sun, 11 Jan 2026 08:44:38 -0800 Subject: [PATCH] gnu: Add collectd-service-type. * gnu/services/monitoring.scm: (collectd-plugin-generic, collectd-plugin-generic?, collectd-plugin-generic-load-plugin?, collectd-plugin-generic-name, collectd-plugin-generic-options, collectd-plugin-python, collectd-plugin-python?, collectd-plugin-python-load-plugin?, collectd-plugin-python-type-databases, collectd-plugin-python-type-packages, collectd-plugin-python-module-paths, collectd-plugin-python-log-traces?, collectd-plugin-python-log-interactive?, collectd-plugin-python-import, collectd-plugin-python-module, collectd-plugin?, %collectd-default-type-database, %collectd-pid-file, collectd-configuration, collectd-configuration?, collectd-configuration-collectd, collectd-configuration-base-directory, collectd-configuration-auto-load-plugin?, collectd-configuration-collect-internal-stats?, collectd-configuration-type-databases, collectd-configuration-interval, collectd-configuration-max-read-interval, collectd-configuration-timeout, collectd-configuration-read-threads, collectd-configuration-write-threads, collectd-configuration-write-queue-limit-high, collectd-configuration-write-queue-limit-low, collectd-configuration-host-name, collectd-configuration-fully-qualified-domain-name-lookup?, collectd-configuration-plugins, collectd-service-type): New variable. * doc/guix.texi (Monitoring Services): Document it. Change-Id: I18d581292979e85603e679b9441be3eeb1856949 --- doc/guix.texi | 294 +++++++++++++++++++ gnu/services/monitoring.scm | 571 +++++++++++++++++++++++++++++++++++- 2 files changed, 859 insertions(+), 6 deletions(-) diff --git a/doc/guix.texi b/doc/guix.texi index 266012a7e8..41ddce9792 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -34106,6 +34106,300 @@ Zabbix server port. @c %end of fragment +@subsubheading collectd Service +@cindex collectd +collectd is a daemon which collects, stores, transports, and visualizes +system and application performance metrics in a variety of ways. + +@defvar collectd-service-type +This is the service type for the @uref{https://collectd.org/, collectd} +service. Its value must be a @code{collectd-configuration} record: + +@lisp +(service collectd-service-type + (collectd-configuration + (plugins + (list + (collectd-plugin-generic + (name "cpu") + (options '((ReportByCpu . #t) + (ReportByState . #t) + (ValuesPercentage . #t)))))))) +@end lisp + +The service may be extended to add new plugins: + +@lisp +(simple-service 'collectd-memory + collectd-service-type + (list + (collectd-plugin-generic + (name "memory") + (options '((ValuesAbsolute . #t) + (ValusPercentage . #t)))))) +@end lisp +@end defvar + + +@c %start of fragment + +@deftp {Data Type} collectd-configuration +Available @code{collectd-configuration} fields are: + +@table @asis +@item @code{collectd} (default: @code{collectd}) (type: package) +The collectd package to use. + +@item @code{base-directory} (default: @code{"/var/lib/collectd"}) (type: string) +Sets the base directory. This is the directory beneath which all +@acronym{RRD,round-robin database} files are created. Possibly more +subdirectories are created. This is also the working directory for +collectd. + +@item @code{auto-load-plugins?} (default: @code{#f}) (type: boolean) +When set to @code{#f}, plugins must be loaded explicitly, by setting the +@code{load-plugin?} field of each plugin configuration to @code{#t}. If +a @code{} block is encountered and no configuration handling +callback for this plugin has been registered, a warning is logged and +the block is ignored. + +When set to @code{#t}, each @code{} block acts as if it was +immediately preceded by a @code{LoadPlugin} statement. @code{LoadPlugin} +statements are still required for plugins that don't provide any +configuration, e.g. the @code{Load} plugin. + +@item @code{collect-internal-stats?} (default: @code{#f}) (type: boolean) +When @code{#t}, various statistics about the collectd daemon will be +collected, with "collectd" as the plugin name. + +@item @code{type-databases} (type: list-of-file-likes) +One or more files that contain the data-set descriptions. See +@code{types.db(5)} for a description of the format of these files. + +@item @code{interval} (default: @code{60}) (type: maybe-seconds) +Configures the interval in which to query the read plugins. Smaller +values lead to a higher system load produced by collectd, while higher +values lead to more coarse statistics. Warning: You should set this +once and then never touch it again. If you do, you will have to delete +all your RRD files or know some serious RRDtool magic! (Assuming you're +using the RRDtool or RRDCacheD plugin.) + +@item @code{max-read-interval} (default: @code{86400}) (type: seconds) +A read plugin doubles the interval between queries after each failed +attempt to get data. This options limits the maximum value of the +interval. + +@item @code{timeout} (type: maybe-integer) +Consider a value list "missing" when no update has been read or received +for @code{Iterations} iterations. By default, collectd considers a +value list missing when no update has been received for twice the update +interval. Since this setting uses iterations, the maximum allowed time +without update depends on the @code{Interval} information contained in +each value list. This is used in the @code{Threshold} configuration to +dispatch notifications about missing values, see +@code{collectd-threshold(5)} for details. + +@item @code{read-threads} (default: @code{5}) (type: integer) +Number of threads to start for reading plugins. You may want to +increase this if you have more than five plugins that take a long time +to read. Mostly those are plugins that do network-IO. Setting this to +a value higher than the number of registered read callbacks is not +recommended. + +@item @code{write-threads} (default: @code{5}) (type: integer) +Number of threads to start for dispatching value lists to write plugins. +You may want to increase this if you have more than five plugins that +may take relatively long to write to. + +@item @code{write-queue-limit-high} (type: maybe-integer) +Metrics are read by the read threads and then put into a queue to be +handled by the write threads. If one of the write plugins is slow (e.g. +network timeouts, I/O saturation of the disk) this queue will grow. In +order to avoid running into memory issues in such a case, you can limit +the size of this queue. If there are @code{write-queue-limit-high} +metrics in the queue, any new metrics will be dropped. If the number of +metrics currently in the queue is between @code{write-queue-limit-low} +and @code{write-queue-limit-high}, the metric is dropped with a +probability that is proportional to the number of metrics in the queue +(i.e. it increases linearly until it reaches 100%). + +@item @code{write-queue-limit-low} (type: maybe-integer) +If there are less than @code{write-queue-limit-low} metrics in the +queue, all new metrics will be enqueued. If +@code{write-queue-limit-high} is set to non-zero and +@code{write-queue-limit-low} is unset, the latter will default to half +of @code{write-queue-limit-high}. + +@item @code{host-name} (type: maybe-string) +Sets the hostname that identifies a host. If you omit this setting, the +hostname will be determined using the @code{gethostname(2)} system call. + +@item @code{fully-qualified-domain-name-lookup?} (default: @code{#t}) (type: boolean) +If @code{host-name} is determined automatically, this setting controls +whether or not the daemon should try to figure out the +@acronym{FQDN,fully qualified domain name}. This is done using a lookup +of the name returned by @code{gethostname(2)}. + +@item @code{plugins} (default: @code{()}) (type: list-of-collectd-plugins) +Configurations for collectd plugins. + +@end table + +@end deftp + + +@c %end of fragment + +@c %start of fragment + +@deftp {Data Type} collectd-plugin-generic + +The @code{collectd-plugin-generic} record can represent most collectd +plugins. + +This example configuration will enable the CPU usage reporting plugin. +See the @code{collectd.conf(5)} man page for a list of plugins and their +options. + +@lisp +(collectd-plugin-generic + (name "cpu") + (options '((ReportByCpu . #t) + (ReportByState . #t) + (ValuesPercentage . #t)))) +@end lisp + +Available @code{collectd-plugin-generic} fields are: + +@table @asis +@item @code{load-plugin?} (default: @code{#t}) (type: boolean) +When @code{#t}, include a @code{LoadPlugin} directive in the +configuration. This interacts with @code{auto-load-plugins?} in +@code{collectd-configuration}; if @code{auto-load-plugins?} is +@code{#f}, all plugins should set this to @code{#t}. + +@item @code{name} (type: string) +The name of the plugin to configure. + +@item @code{options} (default: @code{()}) (type: plugin-options) +Configuration options for the plugin, as an alist. See +@code{collect.conf(5)} for the available plugin options. + +@end table + +@end deftp + + +@c %end of fragment + + + +@c %start of fragment + +@deftp {Data Type} collectd-plugin-python + +The @code{collectd-plugin-python} configuration record holds +configuration for collectd plugins written in Python. See the +@code{collectd-python(5)} man page for more information on Python +support in collectd. + +Guix packages listed in the @code{packages} field will be added to the +profile used to run collectd. This example configuration will load a +Python plugin named @code{myplugin}, whose code is in the +@code{python-myplugin} Guix package. + +@lisp +(collectd-plugin-python + (packages (list python-myplugin)) + (module "myplugin") + (module-options '((SomeOption . "hi") + (AnotherOption . 123)))) +@end lisp + +The @code{module-paths} field will emit verbatim @code{ModulePath} +lines, for plugins not packaged for Guix. This example configuration +loads the @code{gpio} plugin from +@code{/home/user/projects/collectd-gpio/gpio}. + +@lisp +(collectd-plugin-python + (module-paths + (list + "/home/user/projects/collectd-gpio/gpio")) + (module "gpio") + (module-options '((MonitorGPIOs 1 2 3 4)))) +@end lisp + +The two mechanisms may be combined: a Guix-packaged Python library may +be used by unpackaged plugin code. This example loads a +@code{mongometrics} plugin from +@code{/opt/collectd-mongodb/mongometrics}, and also makes the +@code{python-pymongo} Python package available for it to use. + +@lisp +(collectd-plugin-python + (packages (list python-pymongo)) + (module-paths + (list + "/opt/collectd-mongodb/mongometrics")) + (module "mongometrics") + (module-options '((Host . "localhost")))) +@end lisp + +Available @code{collectd-plugin-python} fields are: + +@table @asis +@item @code{load-plugin?} (default: @code{#t}) (type: boolean) +When @code{#t}, include a @code{LoadPlugin} directive in the +configuration. This interacts with @code{auto-load-plugins?} in +@code{collectd-configuration}; if @code{collectd-configuration}'s +@code{auto-load-plugins?} is @code{#f}, all plugins should set this to +@code{#t}. + +@item @code{type-databases} (default: @code{()}) (type: list-of-file-likes) +One or more files that contain the data-set descriptions. See +@code{types.db(5)} for a description of the format of these files. + +@item @code{packages} (default: @code{()}) (type: list-of-packages) +Packages to make available to the Python plugin. These can be +dependencies of the plugin code, or may contain the plugin. + +@item @code{module-paths} (default: @code{()}) (type: list-of-string) +Entries to prepend to @code{sys.path}. + +@item @code{log-traces?} (default: @code{#f}) (type: boolean) +If a Python script throws an exception it will be logged by collectd +with the name of the exception and the message. If you set this option +to true it will also log the full stacktrace just like the default +output of an interactive Python interpreter. This does not apply to the +@code{CollectError} exception, which will never log a stacktrace. This +should probably be set to false most of the time but is very useful for +development and debugging of new modules. + +@item @code{interactive?} (default: @code{#f}) (type: boolean) +This option will cause the module to launch an interactive Python +interpreter that reads from and writes to the terminal. Note that +collectd will terminate right after starting up if you try to run it as +a daemon while this option is enabled so make sure to start collectd +with the @code{-f} option. See the @code{collectd-python(5)} man page +for more information on this option. + +@item @code{module} (type: string) +The name of the Python module to import into the collectd Python +process. The module must be available in @code{packages} or +@code{module-paths}, and register a MPD callback. + +@item @code{module-options} (default: @code{()}) (type: alist) +Configuration options for the module. + +@end table + +@end deftp + + +@c %end of fragment + + @node Kerberos Services @subsection Kerberos Services @cindex Kerberos diff --git a/gnu/services/monitoring.scm b/gnu/services/monitoring.scm index 8ba19ddd7e..66bdc06d04 100644 --- a/gnu/services/monitoring.scm +++ b/gnu/services/monitoring.scm @@ -21,24 +21,39 @@ ;;; along with GNU Guix. If not, see . (define-module (gnu services monitoring) - #:use-module (gnu services) + #:use-module (gnu packages admin) + #:use-module (gnu packages bash) + #:use-module (gnu packages monitoring) + #:use-module (gnu packages networking) + #:use-module (gnu packages python) #:use-module (gnu services configuration) #:use-module (gnu services shepherd) #:use-module (gnu services web) - #:use-module (gnu packages admin) - #:use-module (gnu packages monitoring) - #:use-module (gnu packages networking) + #:use-module (gnu services) #:use-module (gnu system shadow) #:use-module (guix gexp) + #:use-module ((guix modules) #:select (source-module-closure)) #:use-module (guix packages) + #:use-module ((guix profiles) #:select (packages->manifest + profile + profile-search-paths)) #:use-module (guix records) - #:use-module (guix utils) + #:use-module ((guix search-paths) #:select (search-path-specification-variable)) + #:use-module ((guix self) #:select (make-config.scm)) #:use-module ((guix ui) #:select (display-hint G_)) + #:use-module (guix utils) #:use-module (ice-9 match) #:use-module (ice-9 rdelim) #:use-module (srfi srfi-1) + #:use-module ((srfi srfi-2) #:select (and-let*)) #:use-module (srfi srfi-26) #:use-module (srfi srfi-35) + #:use-module ((srfi srfi-171) #:select (list-transduce + rcons + tconcatenate + tfilter + tmap)) + #:export (darkstat-configuration darkstat-service-type @@ -94,7 +109,49 @@ zabbix-agent-service-type zabbix-front-end-configuration zabbix-front-end-service-type - %zabbix-front-end-configuration-nginx)) + %zabbix-front-end-configuration-nginx + + collectd-plugin-generic + collectd-plugin-generic? + collectd-plugin-generic-load-plugin? + collectd-plugin-generic-name + collectd-plugin-generic-options + + collectd-plugin-python + collectd-plugin-python? + collectd-plugin-python-load-plugin? + collectd-plugin-python-type-databases + collectd-plugin-python-type-packages + collectd-plugin-python-module-paths + collectd-plugin-python-log-traces? + collectd-plugin-python-log-interactive? + collectd-plugin-python-import + collectd-plugin-python-module + + collectd-plugin? + + %collectd-default-type-database + %collectd-pid-file + + collectd-configuration + collectd-configuration? + collectd-configuration-collectd + collectd-configuration-base-directory + collectd-configuration-auto-load-plugins? + collectd-configuration-collect-internal-stats? + collectd-configuration-type-databases + collectd-configuration-interval + collectd-configuration-max-read-interval + collectd-configuration-timeout + collectd-configuration-read-threads + collectd-configuration-write-threads + collectd-configuration-write-queue-limit-high + collectd-configuration-write-queue-limit-low + collectd-configuration-host-name + collectd-configuration-fully-qualified-domain-name-lookup? + collectd-configuration-plugins + + collectd-service-type)) ;;; @@ -1186,3 +1243,505 @@ with Zabbix server."))) `((zabbix-front-end-configuration ,zabbix-front-end-configuration-fields)) 'zabbix-front-end-configuration)) + + +;; +;; collectd +;; + +(define *indent* (make-parameter "")) + +(defmacro with-indent (. body) + `(parameterize ((*indent* (string-append (*indent*) " "))) + ,@body)) + +(define seconds? integer?) + +(define-maybe/no-serialization seconds) + +(define list-of-string? (list-of string?)) + +(define list-of-file-likes? (list-of file-like?)) + +(define (collectd-serialize-list-of-file-likes name value) + #~(string-join + (map + (lambda (v) (format #f "~a~a \"~a\"" #$name #$(*indent*) v)) + '#$value) + "\n" 'suffix)) + +(define (collectd-serialize-file-like name value) + (collectd-serialize-list-of-file-likes name (list value))) + +(define list-of-packages? (list-of package?)) + +(define (collectd-serialize-value value) + (cond + ;; Strings get quoted and escaped. + ((string? value) + (format #f "~s" value)) + + ;; Keywords become unquoted strings (#:foo -> foo). + ((keyword? value) + (collectd-serialize-value + (keyword->symbol value))) + + ;; Booleans become bare words. + ((eq? value #t) "true") + ((eq? value #f) "false") + + ;; Alists serialize to lines of KEY VALUE. + ((alist? value) + (apply string-append + (map + (generic-serialize-alist-entry collectd-serialize-field) + value))) + + ;; Lists get their elements serialized and joined with a space. + ((list? value) (string-join (map collectd-serialize-value value) " ")) + + ;; Other types (numbers etc) turn into bare strings. + (else (object->string value)))) + +(define (collectd-serialize-field name value) + (if (and (list? value) (null-list? value)) + "" + (format #nil "~a~a ~a~%" (*indent*) name + (collectd-serialize-value value)))) + +(define (remap-names name-map serializer) + "Renaming serializer. + +Wraps `serializer', renaming fields according to `name-map', an alist of +'((field-name . serialized-name))." + (lambda (name value) + (serializer (or (and=> (assoc name name-map) cdr) name) value))) + +(define (collectd-make-load-plugin?-serializer plugin-name) + (lambda (_ load-plugin?) + (if load-plugin? + (collectd-serialize-field "LoadPlugin" plugin-name) + ""))) + +;; Generic plugin support. + +(define-configuration/no-serialization collectd-plugin-generic + (load-plugin? + (boolean #t) + "When @code{#t}, include a @code{LoadPlugin} directive in the +configuration. This interacts with @code{auto-load-plugins?} in +@code{collectd-configuration}; if @code{auto-load-plugins?} is @code{#f}, all +plugins should set this to @code{#t}.") + + (name + string + "The name of the plugin to configure. See @code{collect.conf(5)} for the +list of plugins.") + + (options + (alist '()) + "Configuration options for the plugin, as an alist. See +@code{collect.conf(5)} for the available plugin options.")) + +(define (collectd-serialize-plugin-generic _ value) + (match-record value + (name load-plugin? options) + #~(string-join + `("\n" + ,#$((collectd-make-load-plugin?-serializer name) #nil load-plugin?) + ,(string-append "\n") + ,#$(with-indent (collectd-serialize-value options)) + "\n") + ""))) + +;; Python plugin support. + +(define (collectd-serialize-python-module name value) + (apply string-append + `(,(string-append (*indent*) "") + "\n" + ,@(with-indent + (map + (lambda (kvs) + (collectd-serialize-field (car kvs) (cdr kvs))) + value)) + ,(string-append (*indent*) "")))) + +(define collectd-plugin-python-remap + '((module-paths . "ModulePath") + (load-plugin? . "LoadPlugin") + (log-traces? . "LogTraces") + (interactive? . "Interactive") + (type-databases . "TypesDB") + (packages . "ModulePath") + (module . "Import"))) + +(define collectd-serialize-plugin-python-field + (remap-names collectd-plugin-python-remap + collectd-serialize-field)) + +(define-configuration collectd-plugin-python + (load-plugin? + (boolean #t) + "When @code{#t}, include a @code{LoadPlugin} directive in the +configuration. This interacts with @code{auto-load-plugins?} in +@code{collectd-configuration}; if @code{collectd-configuration}'s +@code{auto-load-plugins?} is @code{#f}, all plugins should set this to +@code{#t}." + (serializer (collectd-make-load-plugin?-serializer "python"))) + + (type-databases + (list-of-file-likes '()) + "One or more files that contain the data-set descriptions. See +@code{types.db(5)} for a description of the format of these files." + (serializer (remap-names collectd-plugin-python-remap + collectd-serialize-list-of-file-likes))) + + (packages + (list-of-packages '()) + "Packages to make available to the Python plugin. These can be +dependencies of the plugin code, or may contain the plugin." + (serializer empty-serializer)) + + (module-paths + (list-of-string '()) + "Entries to prepend to @code{sys.path}." + (serializer collectd-serialize-plugin-python-field)) + + (log-traces? + (boolean #f) + "If a Python script throws an exception it will be logged by +collectd with the name of the exception and the message. If you set +this option to true it will also log the full stacktrace just like the +default output of an interactive Python interpreter. This does not +apply to the @code{CollectError} exception, which will never log a +stacktrace. This should probably be set to false most of the time but +is very useful for development and debugging of new modules." + (serializer collectd-serialize-plugin-python-field)) + + (interactive? + (boolean #f) + "This option will cause the module to launch an interactive Python +interpreter that reads from and writes to the terminal. Note that collectd +will terminate right after starting up if you try to run it as a daemon while +this option is enabled so make sure to start collectd with the @code{-f} +option. See the @code{collectd-python(5)} man page for more information on +this option." + (serializer collectd-serialize-plugin-python-field)) + + (module + string + "The name of the Python module to import into the collectd Python +process. The module must be available in @code{packages} or +@code{module-paths}, and register a MPD callback." + (serializer collectd-serialize-plugin-python-field)) + + (module-options + (alist '()) + "Configuration options for the module." + (serializer (lambda (_ value) (collectd-serialize-value value))))) + +(define (collectd-serialize-plugin-python _ value) + (define (serialize-fields fields) + (list-transduce + (base-transducer value) rcons + (filter-configuration-fields collectd-plugin-python-fields fields))) + + (match-record + value (module) + #~(string-append + "\n" + #$@(serialize-fields '(load-plugin? type-databases)) + "\n" + #$@(with-indent + (append + (serialize-fields '(log-traces? interactive? module-paths module)) + (list (*indent*) "\n") + (with-indent (serialize-fields '(module-options))) + (list (*indent*) "\n"))) + "\n"))) + +(define (collectd-plugin? x) + ;; XXX: Extend this if plugin-specific configuration records are + ;; added. + (or (collectd-plugin-generic? x) + (collectd-plugin-python? x))) + +(define list-of-collectd-plugins? (list-of collectd-plugin?)) + +(define (collectd-serialize-plugin name value) + ((match value + ;; XXX: Extend this if plugin-specific configuration records are + ;; added. + ;; Note that these *must* return gexps, not strings, otherwise things + ;; like type-databases won't work. + (($ ) collectd-serialize-plugin-generic) + (($ ) collectd-serialize-plugin-python)) + name value)) + +(define (collectd-serialize-list-of-plugins name value) + #~(string-append + #$@(map + (lambda (v) (collectd-serialize-plugin name v)) + value))) + +(define collectd-configuration-remap + '((base-directory . "BaseDir") + (auto-load-plugins? . "AutoLoadPlugin") + (collect-internal-stats? . "CollectInternalStats") + (type-databases . "TypesDB") + (interval . "Interval") + (max-read-interval . "MaxReadInterval") + (timeout . "Timeout") + (read-threads . "ReadThreads") + (write-threads . "WriteThreads") + (write-queue-limit-high . "WriteQueueLimitHigh") + (write-queue-limit-low . "WriteQueueLimitLow") + (host-name . "Hostname") + (fully-qualified-domain-name-lookup? . "FQDNLookup"))) + +(define collectd-configuration-serialize-field + (remap-names collectd-configuration-remap + collectd-serialize-field)) + +(define %collectd-default-type-database + (file-append collectd "/share/collectd/types.db")) + +(define-configuration collectd-configuration + (collectd + (package collectd) + "The collectd package to use." + (serializer empty-serializer)) + + (base-directory + (string "/var/lib/collectd") + "Sets the base directory. This is the directory beneath which all +@acronym{RRD, round-robin database} files are created. Possibly more +subdirectories are created. This is also the working directory for +collectd." + (serializer collectd-configuration-serialize-field)) + + (auto-load-plugins? + (boolean #f) + "When set to @code{#f}, plugins must be loaded explicitly, by setting the +@code{load-plugin?} field of each plugin configuration to @code{#t}. If a +@code{} block is encountered and no configuration handling +callback for this plugin has been registered, a warning is logged and the +block is ignored. + +When set to @code{#t}, each @code{} block acts as if it was +immediately preceded by a @code{LoadPlugin} statement. @code{LoadPlugin} +statements are still required for plugins that don't provide any +configuration, e.g. the @code{Load} plugin." + (serializer collectd-configuration-serialize-field)) + + (collect-internal-stats? + (boolean #f) + "When @code{#t}, various statistics about the collectd daemon will +be collected, with \"collectd\" as the plugin name." + (serializer collectd-configuration-serialize-field)) + + (type-databases + (list-of-file-likes (list %collectd-default-type-database)) + "One or more files that contain the data-set descriptions. See +@code{types.db(5)} for a description of the format of these files." + (serializer (remap-names collectd-configuration-remap + collectd-serialize-list-of-file-likes))) + + (interval + (seconds 60) + "Configures the interval in which to query the read plugins. +Smaller values lead to a higher system load produced by +collectd, while higher values lead to more coarse statistics. + +Warning: You should set this once and then never touch it again. If +you do, you will have to delete all your RRD files or know some +serious RRDtool magic! (Assuming you're using the RRDtool or +RRDCacheD plugin.)" + (serializer collectd-configuration-serialize-field)) + + (max-read-interval + (seconds 86400) + "A read plugin doubles the interval between queries after each +failed attempt to get data. + +This options limits the maximum value of the interval." + (serializer collectd-configuration-serialize-field)) + + (timeout + maybe-integer + "Consider a value list \"missing\" when no update has been read or +received for @code{Iterations} iterations. By default, collectd considers +a value list missing when no update has been received for twice the +update interval. Since this setting uses iterations, the maximum +allowed time without update depends on the @code{Interval} information +contained in each value list. This is used in the @code{Threshold} +configuration to dispatch notifications about missing values, see +@code{collectd-threshold(5)} for details." + (serializer collectd-configuration-serialize-field)) + + (read-threads + (integer 5) + "Number of threads to start for reading plugins. You may want to +increase this if you have more than five plugins that take a long time +to read. Mostly those are plugins that do network-IO. Setting this to +a value higher than the number of registered read callbacks is not +recommended." + (serializer collectd-configuration-serialize-field)) + + (write-threads + (integer 5) + "Number of threads to start for dispatching value lists to write +plugins. You may want to increase this if you have more than five +plugins that may take relatively long to write to." + (serializer collectd-configuration-serialize-field)) + + (write-queue-limit-high + maybe-integer + "Metrics are read by the read threads and then put into a queue to +be handled by the write threads. If one of the write plugins is +slow (e.g. network timeouts, I/O saturation of the disk) this queue +will grow. In order to avoid running into memory issues in such a +case, you can limit the size of this queue. + +If there are @code{write-queue-limit-high} metrics in the queue, any +new metrics will be dropped. + +If the number of metrics currently in the queue is between +@code{write-queue-limit-low} and @code{write-queue-limit-high}, the +metric is dropped with a probability that is proportional to the +number of metrics in the queue (i.e. it increases linearly until it +reaches 100%)." + (serializer collectd-configuration-serialize-field)) + + (write-queue-limit-low + maybe-integer + "If there are less than @code{write-queue-limit-low} metrics in the +queue, all new metrics will be enqueued. + +If @code{write-queue-limit-high} is set to non-zero and +@code{write-queue-limit-low} is unset, the latter will default to half +of @code{write-queue-limit-high}." + (serializer collectd-configuration-serialize-field)) + + (host-name + maybe-string + "Sets the hostname that identifies a host. If you omit this setting, +the hostname will be determined using the @code{gethostname(2)} system +call." + (serializer collectd-configuration-serialize-field)) + + (fully-qualified-domain-name-lookup? + (boolean #t) + "If @code{host-name} is determined automatically, this setting +controls whether or not the daemon should try to figure out the +@acronym{FQDN, fully qualified domain name}. This is done using a +lookup of the name returned by @code{gethostname(2)}." + (serializer collectd-configuration-serialize-field)) + + (plugins + (list-of-collectd-plugins '()) + "Configurations for collectd plugins." + (serializer collectd-serialize-list-of-plugins)) + + (prefix collectd-)) + +(define (collectd-configuration-python config) + "Return the Python package collectd was built with, or #f." + (and=> (assoc-ref (package-native-inputs + (collectd-configuration-collectd config)) + "python") + car)) + +(define (collectd-python-packages config) + "Return a list of all packages in Python plugins." + (append-map + (lambda (plugin) + (if (collectd-plugin-python? plugin) + (collectd-plugin-python-packages plugin) + '())) + (collectd-configuration-plugins config))) + +(define (collectd-profile-packages config) + "Return packages to include in the profile for collectd." + (let ((python (collectd-configuration-python config)) + (python-packages (collectd-python-packages config))) + (cons + (collectd-configuration-collectd config) + ;; Only include Python and packages if collectd has support. + (if python + (cons python python-packages) + '())))) + +(define (make-collectd-profile-wrapper config) + "Return a gexp representing a wrapper for collectd, which runs it in a profile." + (let* ((collectd (collectd-configuration-collectd config)) + (packages (collectd-profile-packages config)) + (collectd-profile (profile + (content + (packages->manifest packages))))) + (computed-file + "collectd-profile-wrapper" + (with-imported-modules '((guix build utils)) + #~(begin + (use-modules (guix build utils)) + (call-with-output-file #$output + (lambda (output) + (format output + (string-append + "#! ~a~%~%" + ". ~a~%" + "exec -a \"${0##*/}\" ~a \"$@\"~%") + #$(file-append bash "/bin/bash") + #$(file-append collectd-profile "/etc/profile") + #$(file-append collectd "/sbin/collectd")))) + (chmod #$output #o755)))))) + +(define %collectd-pid-file "/var/run/collectd.pid") + +(define (make-collectd-shepherd-service config) + (let ((config-file + (mixed-text-file + "collectd.conf" + (serialize-configuration config collectd-configuration-fields)))) + (match-record config (collectd) + (shepherd-service + (provision '(collectd)) + (documentation "Run collectd.") + (requirement '(user-processes networking)) + (start + #~(make-forkexec-constructor + (list #$(make-collectd-profile-wrapper config) + "-C" #$config-file + "-B" ; Don't create base dir. + "-P" #$%collectd-pid-file) + #:pid-file #$%collectd-pid-file)) + (stop #~(make-kill-destructor)))))) + +(define (collectd-activation config) + (match-record config (base-directory) + (with-imported-modules + (source-module-closure '((guix build utils))) + #~(begin + (use-modules (guix build utils)) + (mkdir-p #$base-directory))))) + +(define collectd-service-type + (service-type + (name 'collectd) + (description "Run collectd") + (extensions + (list + (service-extension shepherd-root-service-type + (compose list make-collectd-shepherd-service)) + (service-extension activation-service-type + collectd-activation))) + (compose concatenate) + (extend + (lambda (config plugins) + (collectd-configuration + (inherit config) + (plugins (append (collectd-configuration-plugins config) plugins))))) + (default-value (collectd-configuration))))