From 9ce2a30d4ef0e9763a245d16da98afc626c10846 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Wed, 23 Feb 2011 00:18:12 +0100 Subject: [PATCH] Plugin for Linux Software-RAID devices This is an initial implementation of a plugin to collect information about Linux Software-RAID (md) devices. It reports the number of component devices, number of devices in array, number of active, working, failed and spare disks. Change-Id: Id0ba8e678f33153112e9999c6341dd2ce71b10c0 Signed-off-by: Michael Hanselmann Signed-off-by: Florian Forster --- configure.in | 17 ++++ src/Makefile.am | 8 ++ src/collectd.conf.in | 6 ++ src/collectd.conf.pod | 26 ++++++ src/md.c | 204 ++++++++++++++++++++++++++++++++++++++++++ src/types.db | 1 + 6 files changed, 262 insertions(+) create mode 100644 src/md.c diff --git a/configure.in b/configure.in index 9e1bf094..61e61fea 100644 --- a/configure.in +++ b/configure.in @@ -299,6 +299,21 @@ fi # For hddtemp module AC_CHECK_HEADERS(linux/major.h libgen.h) +# For md module (Linux only) +if test "x$ac_system" = "xLinux" +then + AC_CHECK_HEADERS(linux/raid/md_u.h, + [have_linux_raid_md_u_h="yes"], + [have_linux_raid_md_u_h="no"], +[ +#include +#include +#include +]) +else + have_linux_raid_md_u_h="no" +fi + # For the battery plugin AC_CHECK_HEADERS(IOKit/ps/IOPowerSources.h, [], [], [ @@ -4750,6 +4765,7 @@ AC_PLUGIN([match_regex], [yes], [The regex match]) AC_PLUGIN([match_timediff], [yes], [The timediff match]) AC_PLUGIN([match_value], [yes], [The value match]) AC_PLUGIN([mbmon], [yes], [Query mbmond]) +AC_PLUGIN([md], [$have_linux_raid_md_u_h], [md (Linux software RAID) devices]) AC_PLUGIN([memcachec], [$with_libmemcached], [memcachec statistics]) AC_PLUGIN([memcached], [yes], [memcached statistics]) AC_PLUGIN([memory], [$plugin_memory], [Memory usage]) @@ -5079,6 +5095,7 @@ Configuration: match_timediff . . . $enable_match_timediff match_value . . . . . $enable_match_value mbmon . . . . . . . . $enable_mbmon + md . . . . . . . . . $enable_md memcachec . . . . . . $enable_memcachec memcached . . . . . . $enable_memcached memory . . . . . . . $enable_memory diff --git a/src/Makefile.am b/src/Makefile.am index 2f2acc0f..0869a086 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -587,6 +587,14 @@ collectd_LDADD += "-dlopen" mbmon.la collectd_DEPENDENCIES += mbmon.la endif +if BUILD_PLUGIN_MD +pkglib_LTLIBRARIES += md.la +md_la_SOURCES = md.c +md_la_LDFLAGS = -module -avoid-version +collectd_LDADD += "-dlopen" md.la +collectd_DEPENDENCIES += md.la +endif + if BUILD_PLUGIN_MEMCACHEC pkglib_LTLIBRARIES += memcachec.la memcachec_la_SOURCES = memcachec.c diff --git a/src/collectd.conf.in b/src/collectd.conf.in index 983f5f16..a6aa89c3 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -89,6 +89,7 @@ #@BUILD_PLUGIN_LPAR_TRUE@LoadPlugin lpar #@BUILD_PLUGIN_MADWIFI_TRUE@LoadPlugin madwifi #@BUILD_PLUGIN_MBMON_TRUE@LoadPlugin mbmon +#@BUILD_PLUGIN_MD_TRUE@LoadPlugin md #@BUILD_PLUGIN_MEMCACHEC_TRUE@LoadPlugin memcachec #@BUILD_PLUGIN_MEMCACHED_TRUE@LoadPlugin memcached @BUILD_PLUGIN_MEMORY_TRUE@@BUILD_PLUGIN_MEMORY_TRUE@LoadPlugin memory @@ -435,6 +436,11 @@ # Port "411" # +# +# Device "/dev/md0" +# IgnoreSelected false +# + # # # Server "localhost" diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 2be3c939..a90826a8 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -1822,6 +1822,32 @@ TCP-Port to connect to. Defaults to B<411>. =back +=head2 Plugin C + +The C collects information from Linux Software-RAID devices (md). + +All reported values are of the type C. Reported type instances are +"number" (number of component devices), "raid" (number of devices in the +array), "active", "working", "failed" (number of failed disks) and "spare" +(number of spare disks). + +=over 4 + +=item B I + +Select md devices based on device name. The I is the basename of +the device, i.e. the name of the block device without the leading C. +See B for more details. + +=item B I|I + +Invert device selection: If set to true, all md devices B those listed +using B are collected. If false (the default), only those listed are +collected. If no configuration is given, the B plugin will collect data +from all md devices. + +=back + =head2 Plugin C The C connects to a memcached server, queries one or more diff --git a/src/md.c b/src/md.c new file mode 100644 index 00000000..fdac4e3d --- /dev/null +++ b/src/md.c @@ -0,0 +1,204 @@ +/** + * collectd - src/md.c + * Copyright (C) 2010,2011 Michael Hanselmann + * + * 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 + * + * Author: + * Michael Hanselmann + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" +#include "utils_ignorelist.h" + +#include + +#include +#include + +#define PROC_DISKSTATS "/proc/diskstats" +#define DEV_DIR "/dev" + +static const char *config_keys[] = +{ + "Device", + "IgnoreSelected" +}; +static int config_keys_num = STATIC_ARRAY_SIZE (config_keys); + +static ignorelist_t *ignorelist = NULL; + +static int md_config (const char *key, const char *value) +{ + if (ignorelist == NULL) + ignorelist = ignorelist_create (/* invert = */ 1); + if (ignorelist == NULL) + return (1); + + if (strcasecmp (key, "Device") == 0) + { + ignorelist_add (ignorelist, value); + } + else if (strcasecmp (key, "IgnoreSelected") == 0) + { + ignorelist_set_invert (ignorelist, IS_TRUE (value) ? 0 : 1); + } + else + { + return (-1); + } + + return (0); +} + +static void md_submit (const int minor, const char *type_instance, + gauge_t value) +{ + value_t values[1]; + value_list_t vl = VALUE_LIST_INIT; + + values[0].gauge = value; + + vl.values = values; + vl.values_len = 1; + sstrncpy (vl.host, hostname_g, sizeof (vl.host)); + sstrncpy (vl.plugin, "md", sizeof (vl.plugin)); + ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance), + "%i", minor); + sstrncpy (vl.type, "md_disks", sizeof (vl.type)); + sstrncpy (vl.type_instance, type_instance, + sizeof (vl.type_instance)); + + plugin_dispatch_values (&vl); +} /* void md_submit */ + +static void md_process (const int minor, const char *path) +{ + char errbuf[1024]; + int fd; + struct stat st; + mdu_array_info_t array; + + fd = open (path, O_RDONLY); + if (fd < 0) + { + WARNING ("md: open(%s): %s", path, + sstrerror (errno, errbuf, sizeof (errbuf))); + return; + } + + if (fstat (fd, &st) < 0) + { + WARNING ("md: Unable to fstat file descriptor for %s: %s", path, + sstrerror (errno, errbuf, sizeof (errbuf))); + close (fd); + return; + } + + if (! S_ISBLK (st.st_mode)) + { + WARNING ("md: %s is no block device", path); + close (fd); + return; + } + + if (st.st_rdev != makedev (MD_MAJOR, minor)) + { + WARNING ("md: Major/minor of %s are %i:%i, should be %i:%i", + path, (int)major(st.st_rdev), (int)minor(st.st_rdev), + (int)MD_MAJOR, minor); + close (fd); + return; + } + + /* Retrieve md information */ + if (ioctl (fd, GET_ARRAY_INFO, &array) < 0) { + WARNING ("md: Unable to retrieve array info from %s: %s", path, + sstrerror (errno, errbuf, sizeof (errbuf))); + close (fd); + return; + } + + close (fd); + + md_submit (minor, "number", (gauge_t) array.nr_disks); + md_submit (minor, "raid", (gauge_t) array.raid_disks); + md_submit (minor, "active", (gauge_t) array.active_disks); + md_submit (minor, "working", (gauge_t) array.working_disks); + md_submit (minor, "failed", (gauge_t) array.failed_disks); + md_submit (minor, "spare", (gauge_t) array.spare_disks); + + return; +} /* void md_process */ + +static int md_read (void) +{ + FILE *fh; + char buffer[1024]; + + fh = fopen (PROC_DISKSTATS, "r"); + if (fh == NULL) { + char errbuf[1024]; + WARNING ("md: Unable to open %s: %s", + PROC_DISKSTATS , + sstrerror (errno, errbuf, sizeof (errbuf))); + return (-1); + } + + /* Iterate md devices */ + while (fgets (buffer, sizeof (buffer), fh) != NULL) + { + char path[PATH_MAX]; + char *fields[4]; + char *name; + int major, minor; + + /* Extract interesting fields */ + if (strsplit (buffer, fields, STATIC_ARRAY_SIZE(fields)) < 3) + continue; + + major = atoi (fields[0]); + + if (major != MD_MAJOR) + continue; + + minor = atoi (fields[1]); + name = fields[2]; + + if (ignorelist_match (ignorelist, name)) + continue; + + /* FIXME: Don't hardcode path. Walk /dev collecting major, + * minor and name, then use lookup table to find device. + * Alternatively create a temporary device file with correct + * major/minor, but that again can be tricky if the filesystem + * with the device file is mounted using the "nodev" option. + */ + ssnprintf (path, sizeof (path), "%s/%s", DEV_DIR, name); + + md_process (minor, path); + } + + fclose (fh); + + return (0); +} /* int md_read */ + +void module_register (void) +{ + plugin_register_config ("md", md_config, config_keys, config_keys_num); + plugin_register_read ("md", md_read); +} /* void module_register */ diff --git a/src/types.db b/src/types.db index e6345ab6..8557fe6b 100644 --- a/src/types.db +++ b/src/types.db @@ -84,6 +84,7 @@ irq value:DERIVE:0:U latency value:GAUGE:0:65535 links value:GAUGE:0:U load shortterm:GAUGE:0:100, midterm:GAUGE:0:100, longterm:GAUGE:0:100 +md_disks value:GAUGE:0:U memcached_command value:DERIVE:0:U memcached_connections value:GAUGE:0:U memcached_items value:GAUGE:0:U -- 2.30.2