From f19f64d583ab6e1ccf6f4b5e312425330ec1d97c Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Fri, 16 Dec 2011 20:10:05 +0000 Subject: [PATCH] Add cgroups_cpuacct plugin to collect CPU user/system time per cgroup This is handy for computers using systemd, as systemd puts each service into a separate cgroup. Signed-off-by: Florian Forster --- configure.in | 2 + src/Makefile.am | 8 ++ src/cgroups_cpuacct.c | 229 ++++++++++++++++++++++++++++++++++++++++++ src/collectd.conf.in | 1 + src/collectd.conf.pod | 21 ++++ src/types.db | 1 + 6 files changed, 262 insertions(+) create mode 100644 src/cgroups_cpuacct.c diff --git a/configure.in b/configure.in index 144f2897..7a25a0d3 100644 --- a/configure.in +++ b/configure.in @@ -4956,6 +4956,7 @@ AC_PLUGIN([csv], [yes], [CSV output plugin]) AC_PLUGIN([curl], [$with_libcurl], [CURL generic web statistics]) AC_PLUGIN([curl_json], [$plugin_curl_json], [CouchDB statistics]) AC_PLUGIN([curl_xml], [$plugin_curl_xml], [CURL generic xml statistics]) +AC_PLUGIN([cgroups_cpuacct], [yes], [CGroups CPU usage accounting]) AC_PLUGIN([dbi], [$with_libdbi], [General database statistics]) AC_PLUGIN([df], [$plugin_df], [Filesystem usage statistics]) AC_PLUGIN([disk], [$plugin_disk], [Disk usage statistics]) @@ -5286,6 +5287,7 @@ Configuration: bind . . . . . . . . $enable_bind conntrack . . . . . . $enable_conntrack contextswitch . . . . $enable_contextswitch + cgroups_cpuacct . . . $enable_cgroups_cpuacct cpu . . . . . . . . . $enable_cpu cpufreq . . . . . . . $enable_cpufreq csv . . . . . . . . . $enable_csv diff --git a/src/Makefile.am b/src/Makefile.am index e9698882..494b343b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -230,6 +230,14 @@ collectd_LDADD += "-dlopen" bind.la collectd_DEPENDENCIES += bind.la endif +if BUILD_PLUGIN_CGROUPS_CPUACCT +pkglib_LTLIBRARIES += cgroups_cpuacct.la +cgroups_cpuacct_la_SOURCES = cgroups_cpuacct.c utils_mount.c utils_mount.h +cgroups_cpuacct_la_LDFLAGS = -module -avoid-version +collectd_LDADD += "-dlopen" cgroups_cpuacct.la +collectd_DEPENDENCIES += cgroups_cpuacct.la +endif + if BUILD_PLUGIN_CONNTRACK pkglib_LTLIBRARIES += conntrack.la conntrack_la_SOURCES = conntrack.c diff --git a/src/cgroups_cpuacct.c b/src/cgroups_cpuacct.c new file mode 100644 index 00000000..d20ff4b4 --- /dev/null +++ b/src/cgroups_cpuacct.c @@ -0,0 +1,229 @@ +/** + * collectd - src/cgroups_cpuacct.c + * Copyright (C) 2011 Michael Stapelberg + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the license is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Michael Stapelberg + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" +#include "configfile.h" +#include "utils_mount.h" +#include "utils_ignorelist.h" + +static const char *config_keys[] = +{ + "CGroup", + "IgnoreSelected" +}; +static int config_keys_num = STATIC_ARRAY_SIZE (config_keys); + +static ignorelist_t *il_cgroup = NULL; + +__attribute__ ((nonnull(1))) +__attribute__ ((nonnull(2))) +static void cgroups_submit_one (const char *plugin_instance, + const char *type, const char *type_instance, + derive_t value) +{ + value_t values[1]; + value_list_t vl = VALUE_LIST_INIT; + + values[0].derive = value; + + vl.values = values; + vl.values_len = 1; + sstrncpy (vl.host, hostname_g, sizeof (vl.host)); + sstrncpy (vl.plugin, "cgroups_cpuacct", sizeof (vl.plugin)); + sstrncpy (vl.plugin_instance, plugin_instance, + sizeof (vl.plugin_instance)); + sstrncpy (vl.type, type, sizeof (vl.type)); + if (type_instance != NULL) + sstrncpy (vl.type_instance, type_instance, + sizeof (vl.type_instance)); + + plugin_dispatch_values (&vl); +} /* void cgroups_submit_one */ + + +/* + * This callback reads the user/system CPU time for each cgroup. + * + */ +static int read_cpuacct_procs (const char *dirname, const char *filename, + void *user_data) +{ + char abs_path[PATH_MAX]; + struct stat statbuf; + char buf[1024]; + int status; + + if (ignorelist_match (il_cgroup, filename)) + return (0); + + ssnprintf (abs_path, sizeof (abs_path), "%s/%s", dirname, filename); + + status = lstat (abs_path, &statbuf); + if (status != 0) + { + ERROR ("cgroups_cpuacct plugin: stat (%s) failed.", abs_path); + return (-1); + } + + /* We are only interested in directories, so skip everything else. */ + if (!S_ISDIR (statbuf.st_mode)) + { + return (0); + } + + ssnprintf (abs_path, sizeof (abs_path), "%s/%s/cpuacct.stat", dirname, filename); + int bytes_read; + if ((bytes_read = read_file_contents (abs_path, buf, sizeof(buf))) <= 0) + { + char errbuf[1024]; + ERROR ("cgroups_cpuacct plugin: read_file_contents(%s): %s", + abs_path, sstrerror (errno, errbuf, sizeof (errbuf))); + return (-1); + } + buf[bytes_read] = '\0'; + + char *fields[4]; + int numfields = 0; + if ((numfields = strsplit (buf, fields, 4)) != 4) + { + ERROR ("cgroups_cpuacct plugin: unexpected content in file %s", abs_path); + return (-1); + } + uint64_t usertime, systemtime; + usertime = atoll (fields[1]); + systemtime = atoll (fields[3]); + + cgroups_submit_one(filename, "cpuacct", "user", (derive_t)usertime); + cgroups_submit_one(filename, "cpuacct", "system", (derive_t)systemtime); + + return (0); +} + +/* + * Gets called for every file/folder in /sys/fs/cgroup/cpu,cpuacct (or + * whereever cpuacct is mounted on the system). Calls walk_directory with the + * read_cpuacct_procs callback on every folder it finds, such as "system". + * + */ +static int read_cpuacct_root (const char *dirname, const char *filename, + void *user_data) +{ + char abs_path[PATH_MAX]; + struct stat statbuf; + int status; + + ssnprintf (abs_path, sizeof (abs_path), "%s/%s", dirname, filename); + + status = lstat (abs_path, &statbuf); + if (status != 0) + { + ERROR ("cgroups_cpuacct plugin: stat (%s) failed.", abs_path); + return (-1); + } + + if (S_ISDIR (statbuf.st_mode)) + { + status = walk_directory (abs_path, read_cpuacct_procs, NULL, 0); + return (status); + } + + return (0); +} + +static int cgroups_init (void) +{ + if (il_cgroup == NULL) + il_cgroup = ignorelist_create (1); + + return (0); +} + +static int cgroups_config (const char *key, const char *value) +{ + cgroups_init (); + + if (strcasecmp (key, "CGroup") == 0) + { + if (ignorelist_add (il_cgroup, value)) + return (1); + return (0); + } + else if (strcasecmp (key, "IgnoreSelected") == 0) + { + if (IS_TRUE (value)) + { + ignorelist_set_invert (il_cgroup, 0); + } + else + { + ignorelist_set_invert (il_cgroup, 1); + } + return (0); + } + + return (-1); +} + +static int cgroups_read (void) +{ + cu_mount_t *mnt_list; + cu_mount_t *mnt_ptr; + int cgroup_found = 0; + + mnt_list = NULL; + if (cu_mount_getlist (&mnt_list) == NULL) + { + ERROR ("cgroups_cpuacct plugin: cu_mount_getlist failed."); + return (-1); + } + + for (mnt_ptr = mnt_list; mnt_ptr != NULL; mnt_ptr = mnt_ptr->next) + { + /* Find the cgroup mountpoint which contains the cpuacct + * controller. */ + if (strcmp(mnt_ptr->type, "cgroup") != 0 || + !cu_mount_getoptionvalue(mnt_ptr->options, "cpuacct")) + continue; + + walk_directory (mnt_ptr->dir, read_cpuacct_root, NULL, 0); + cgroup_found = 1; + /* It doesn't make sense to check other cpuacct mount-points + * (if any), they contain the same data. */ + break; + } + + if (!cgroup_found) + WARNING ("cpuacct mountpoint not found. Cannot collect any data."); + + cu_mount_freelist (mnt_list); + + return (0); +} /* int cgroup_read */ + +void module_register (void) +{ + plugin_register_config ("cgroups_cpuacct", cgroups_config, + config_keys, config_keys_num); + plugin_register_init ("cgroups_cpuacct", cgroups_init); + plugin_register_read ("cgroups_cpuacct", cgroups_read); +} /* void module_register */ diff --git a/src/collectd.conf.in b/src/collectd.conf.in index e95fae6a..736c9741 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -72,6 +72,7 @@ #@BUILD_PLUGIN_BIND_TRUE@LoadPlugin bind #@BUILD_PLUGIN_CONNTRACK_TRUE@LoadPlugin conntrack #@BUILD_PLUGIN_CONTEXTSWITCH_TRUE@LoadPlugin contextswitch +#@BUILD_PLUGIN_CGROUPS_CPUACCT_TRUE@LoadPlugin cgroups_cpuacct @BUILD_PLUGIN_CPU_TRUE@@BUILD_PLUGIN_CPU_TRUE@LoadPlugin cpu #@BUILD_PLUGIN_CPUFREQ_TRUE@LoadPlugin cpufreq @LOAD_PLUGIN_CSV@LoadPlugin csv diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 8606d3e0..a4542a02 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -852,6 +852,27 @@ By default no detailed zone information is collected. =back +=head2 Plugin C + +This plugin collects the CPU user/system time for each cgroup by reading the +cpuacct.stat files in the first cpuacct-mountpoint (typically +F on machines using systemd). + +=over 4 + +=item B I + +Select cgroup based on the name. + +=item B B|B + +Invert the selection: If set to true, all cgroups B the ones that +match any one of the criteria are collected. By default only selected +cgroups are collected if a selection is made. If no selection is configured +at all, B cgroups are selected. + +=back + =head2 Plugin C This plugin doesn't have any options. It reads diff --git a/src/types.db b/src/types.db index 6f1eaf61..679554e8 100644 --- a/src/types.db +++ b/src/types.db @@ -22,6 +22,7 @@ contextswitch value:DERIVE:0:U counter value:COUNTER:U:U cpufreq value:GAUGE:0:U cpu value:DERIVE:0:U +cpuacct value:DERIVE:0:U current_connections value:GAUGE:0:U current_sessions value:GAUGE:0:U current value:GAUGE:U:U -- 2.30.2