diff --git a/doc/guix.texi b/doc/guix.texi index a6d6d655fb5..b6a68e30b87 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -33630,6 +33630,300 @@ Zabbix server port. @c %end of fragment +@subsubheading collectd Service +@cindex collectd +collectd is a daemon collecting system and application performance +metrics periodically and provides mechanisms to store and transport the +values 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-plugin?} (default: @code{#f}) (type: boolean) +When set to @code{#f}, each plugin needs to be loaded explicitly, using +@code{collectd-load-plugin}. 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 true, explicit @code{LoadPlugin} statements are not required. Each +@code{} block acts as if it was immediately preceded by a +LoadPlugin statement. 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) +Plugins to load, and their configurations. + +@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. + +As an example, this 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-plugin?} in +@code{collectd-configuration}; if @code{collectd-configuration}'s +@code{auto-load-plugin?} is @code{#f}, all plugins should set this to +@code{#t}. + +@item @code{name} (type: string) +The name of the plugin. + +@item @code{options} (default: @code{()}) (type: plugin-options) +Options for the plugin. + +@end table + +@end deftp + + +@c %end of fragment + +@c %start of fragment + +@deftp {Data Type} collectd-plugin-python + +Users may extend collectd by writing plugins in Python, which run in a +Python interpreter embedded into collectd. The +@code{collectd-plugin-python} configuration record configures these +plugins. See the @code{collectd-python(5)} man page for more +information on Python support in collectd. + +Guix-packaged plugins and dependencies listed in the @code{packages} +field will be available in the Python plugin environment. + +@lisp +(collectd-plugin-python + (packages (list python-myplugin)) + (module "myplugin") + (module-options '((SomeOption . "hi") + (AnotherOption . 123)))) +@end lisp + +This configuration will emit a @code{ModulePath} line in the collectd +configuration pointing at a profile containing Python, @code{python-myplugin}, +and any of its propagated inputs. + +For plugin code not packaged for Guix, the @code{module-paths} field +will emit verbatim @code{ModulePath} lines. + +@lisp +(collectd-plugin-python + (module-paths + (list + "/home/users/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. + +@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-plugin?} in +@code{collectd-configuration}; if @code{collectd-configuration}'s +@code{auto-load-plugin?} 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. The +plugin's @code{ModulePath} will point to a profile containing these +packages. + +@item @code{module-paths} (default: @code{()}) (type: list-of-string) +Prepends entries to @code{sys.path}. You won’t be able to import any +scripts you wrote unless they are located in one of the directories in +this list. Please note that it only has effect on plugins loaded after +this option. + +@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 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) +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 f504c61afdf..64090f5b1fa 100644 --- a/gnu/services/monitoring.scm +++ b/gnu/services/monitoring.scm @@ -21,24 +21,38 @@ ;;; along with GNU Guix. If not, see . (define-module (gnu services monitoring) - #:use-module (gnu services) - #: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 packages python) + #:use-module (gnu services configuration) + #:use-module (gnu services shepherd) + #:use-module (gnu services web) + #: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 +108,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-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)) ;;; @@ -1194,3 +1250,536 @@ 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 plugin-options? alist?) + +(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-plugin?} in +@code{collectd-configuration}; if @code{collectd-configuration}'s +@code{auto-load-plugin?} is @code{#f}, all plugins should set this to +@code{#t}.") + + (name + string + "The name of the plugin.") + + (options + (plugin-options '()) + "Options for the plugin.")) + +(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 (collectd-plugin-python-serialize-packages name value) + (define not-config? + ;; Select (guix …) and (gnu …) modules, except (guix config). + (match-lambda + (('guix 'config) #f) + (('guix _ ...) #t) + (('gnu _ ...) #t) + (_ #f))) + + ;; Note that for this to work, `collectd-preprocess-config' must have + ;; propagated a Python package into the packages field being serialized. + (define python-profile + (profile + (content + (packages->manifest value)))) + + (with-imported-modules `(((guix config) => ,(make-config.scm)) + ,@(source-module-closure + '((guix profiles) + (guix build utils)) + #:select? not-config?)) + #~(eval + '(begin + (use-modules (guix build utils) + ((guix profiles) #:select (profile-search-paths)) + ((guix search-paths) + #:select (search-path-specification-variable)) + (srfi srfi-1)) + (let ((guix-pythonpath + (and=> + (find + (lambda (psp) + (string=? "GUIX_PYTHONPATH" + (search-path-specification-variable (car psp)))) + (profile-search-paths #$python-profile)) + cdr))) + (unless guix-pythonpath + (error "Profile contained no $GUIX_PYTHONPATH!")) + (format #f "~a~a ~s~%" + #$(*indent*) + #$name + guix-pythonpath))) + (current-module)))) + +(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-plugin?} in +@code{collectd-configuration}; if @code{collectd-configuration}'s +@code{auto-load-plugin?} 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. The plugin's +@code{ModulePath} will point to a profile containing these packages." + (serializer (remap-names collectd-plugin-python-remap + collectd-plugin-python-serialize-packages))) + + (module-paths + (list-of-string '()) + "Prepends entries to @code{sys.path}. You won’t be able to import +any scripts you wrote unless they are located in one of the +directories in this list. Please note that it only has effect on +plugins loaded after this option." + (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 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 '()) + "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 packages 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-plugin? . "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-plugin? + (boolean #f) + "When set to @code{#f}, each plugin needs to be loaded explicitly, +using @code{collectd-load-plugin}. 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 true, explicit @code{LoadPlugin} statements are not +required. Each @code{} block acts as if it was +immediately preceded by a LoadPlugin statement. 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 + (maybe-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 '()) + "Plugins to load, and their configurations." + (serializer collectd-serialize-list-of-plugins)) + + (prefix collectd-)) + +(define (collectd-propagate-python config python) + "Rewrite config, adding python to Python plugins' packages." + (collectd-configuration + (inherit config) + (plugins + (map + (lambda (plugin) + (if (collectd-plugin-python? plugin) + (collectd-plugin-python + (inherit plugin) + (packages + ;; Add the Python package collectd was built with to the + ;; plugin's package list. This allows profiles built from its + ;; packages field to compute GUIX_PYTHONPATH. + (cons python (collectd-plugin-python-packages plugin)))) + plugin)) + (collectd-configuration-plugins config))))) + +(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-preprocess-config config) + "Determine Python package from collectd and propagate it to Python plugins." + (or (and=> (collectd-configuration-python config) + (cut collectd-propagate-python config <>)) + config)) + +(define %collectd-pid-file "/var/run/collectd.pid") + +(define (make-collectd-shepherd-service config) + (let ((config-file + (mixed-text-file + "collectd.conf" + (serialize-configuration (collectd-preprocess-config config) + collectd-configuration-fields)))) + (match-record config (collectd) + (shepherd-service + (provision '(collectd)) + (documentation "Run collectd.") + (requirement '(user-processes networking)) + (start + #~(make-forkexec-constructor + (list #$(file-append collectd "/sbin/collectd") + "-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))))