From 7daf99fa17b1f172ea9de605620310026f921834 Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Mon, 13 Sep 2021 15:00:46 +0200 Subject: [PATCH] mmc plugin: initial checkin (#3887) Signed-off-by: Florian Eckert --- AUTHORS | 3 + Makefile.am | 7 ++ README | 5 + configure.ac | 2 + src/collectd.conf.in | 5 + src/mmc.c | 281 +++++++++++++++++++++++++++++++++++++++++++ src/types.db | 3 + 7 files changed, 306 insertions(+) create mode 100644 src/mmc.c diff --git a/AUTHORS b/AUTHORS index 61a15f7a8..488aacb47 100644 --- a/AUTHORS +++ b/AUTHORS @@ -183,6 +183,9 @@ Flavio Stanchina Franck Lombardi - UNIX socket code for the memcached plugin. +Florian Eckert + - MMC plugin + Gergely Nagy - Write-Riemann plugin. diff --git a/Makefile.am b/Makefile.am index 7da618886..f8003cb89 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1508,6 +1508,13 @@ mqtt_la_LDFLAGS = $(PLUGIN_LDFLAGS) $(BUILD_WITH_LIBMOSQUITTO_LDFLAGS) mqtt_la_LIBADD = $(BUILD_WITH_LIBMOSQUITTO_LIBS) endif +if BUILD_PLUGIN_MMC +pkglib_LTLIBRARIES += mmc.la +mmc_la_SOURCES = src/mmc.c +mmc_la_LDFLAGS = $(PLUGIN_LDFLAGS) +mmc_la_LIBADD = libignorelist.la +endif + if BUILD_PLUGIN_MULTIMETER pkglib_LTLIBRARIES += multimeter.la multimeter_la_SOURCES = src/multimeter.c diff --git a/README b/README index 6d1c078a3..bf766efcc 100644 --- a/README +++ b/README @@ -265,6 +265,11 @@ Features Collects CPU usage, memory usage, temperatures and power consumption from Intel Many Integrated Core (MIC) CPUs. + - mmc + Reads the percentage of bad blocks on mmc device. The additional values + for block erases and power cycles are also read. This Plugin in does only + work for the swissbit mmc Cards. MANFID=0x5D OEMID=0x5342 + - modbus Reads values from Modbus/TCP enabled devices. Supports reading values from multiple "slaves" so gateway devices can be used. diff --git a/configure.ac b/configure.ac index a29ef348b..8cfc812e8 100644 --- a/configure.ac +++ b/configure.ac @@ -6611,6 +6611,7 @@ plugin_log_logstash="no" plugin_mcelog="no" plugin_mdevents="no" plugin_memory="no" +plugin_mmc="no" plugin_multimeter="no" plugin_netstat_udp="no" plugin_nfs="no" @@ -7092,6 +7093,7 @@ AC_PLUGIN([memory], [$plugin_memory], [Memory usage]) AC_PLUGIN([mic], [$with_mic], [Intel Many Integrated Core stats]) AC_PLUGIN([modbus], [$with_libmodbus], [Modbus plugin]) AC_PLUGIN([mqtt], [$with_libmosquitto], [MQTT output plugin]) +AC_PLUGIN([mmc], [$plugin_mmc], [MMC statistics]) AC_PLUGIN([multimeter], [$plugin_multimeter], [Read multimeter values]) AC_PLUGIN([mysql], [$with_libmysql], [MySQL statistics]) AC_PLUGIN([netapp], [$with_libnetapp], [NetApp plugin]) diff --git a/src/collectd.conf.in b/src/collectd.conf.in index 13e4a798d..2f7468cc6 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -1056,6 +1056,11 @@ # ValuesPercentage false # +# +# Device "mmc0" +# IgnoreSelected false +# + # # # RegisterBase 1234 diff --git a/src/mmc.c b/src/mmc.c new file mode 100644 index 000000000..49a0439b1 --- /dev/null +++ b/src/mmc.c @@ -0,0 +1,281 @@ +/* + * collectd - src/mmc.c + * + * 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: + * Florian Eckert + * + */ + +#include "collectd.h" + +#include "plugin.h" +#include "utils/common/common.h" +#include "utils/ignorelist/ignorelist.h" + +#if !KERNEL_LINUX +#error "No applicable input method." +#endif + +#define PLUGIN_NAME "mmc" +#define SYS_PATH "/sys/bus/mmc/devices/" + +#define DEVICE_KEY "Device" +#define IGNORE_KEY "IgnoreSelected" + +static const char *config_keys[] = { + DEVICE_KEY, + IGNORE_KEY, +}; +static int config_keys_num = STATIC_ARRAY_SIZE(config_keys); + +#define MMC_MANUFACTOR "manfid" +#define MMC_OEM_ID "oemid" +#define MMC_SSR "ssr" + +static ignorelist_t *ignorelist = NULL; + +static int mmc_config(const char *key, const char *value) { + if (ignorelist == NULL) + ignorelist = ignorelist_create(1); + + if (ignorelist == NULL) { + ERROR(PLUGIN_NAME ": Ignorelist_create failed"); + return -ENOMEM; + } + + if (strcasecmp(key, DEVICE_KEY) == 0) { + if (ignorelist_add(ignorelist, value)) { + ERROR(PLUGIN_NAME ": Cannot add value to ignorelist"); + return -1; + } + } else if (strcasecmp(key, IGNORE_KEY) == 0) + ignorelist_set_invert(ignorelist, IS_TRUE(value) ? 0 : 1); + else { + ERROR(PLUGIN_NAME ": Invalid option %s", key); + return -1; + } + + return 0; +} + +static void mmc_submit(const char *dev_name, const char *type, gauge_t value) { + value_list_t vl = VALUE_LIST_INIT; + + vl.values = &(value_t){.gauge = value}; + vl.values_len = 1; + sstrncpy(vl.plugin, PLUGIN_NAME, sizeof(vl.plugin)); + sstrncpy(vl.plugin_instance, dev_name, sizeof(vl.plugin_instance)); + sstrncpy(vl.type, type, sizeof(vl.type)); + + plugin_dispatch_values(&vl); +} + +static int mmc_read_dev_attr(const char *dev_name, const char *file_name, + char *buffer, int size) { + FILE *fh; + char str[sizeof(SYS_PATH) + strlen(dev_name) + sizeof("/") + + strlen(file_name) + 1]; + int length; + + snprintf(str, sizeof(str), SYS_PATH "%s/%s", dev_name, file_name); + fh = fopen(str, "r"); + + if (fh == NULL) { + ERROR(PLUGIN_NAME "(%s): Cannot open file [%s]", dev_name, str); + return EXIT_FAILURE; + } + + DEBUG(PLUGIN_NAME "(%s): try to read [%s]", dev_name, str); + if (fgets(buffer, size, fh) == NULL) { + ERROR(PLUGIN_NAME "(%s): Unable to read file [%s] (%s)", dev_name, str, + STRERRNO); + fclose(fh); + return EXIT_FAILURE; + } + fclose(fh); + + /* Remove trailing whitespace for sysfs attr read */ + length = strlen(buffer); + DEBUG(PLUGIN_NAME "(%s): Read %d characters [%s]", dev_name, length, str); + if (buffer > 0) + buffer[length - 1] = '\0'; + + return 0; +} + +static int mmc_read_manfid(const char *dev_name, int *value) { + char buffer[4096]; + + if (mmc_read_dev_attr(dev_name, MMC_MANUFACTOR, buffer, sizeof(buffer)) == + 0) { + *value = (int)strtol(buffer, NULL, 0); + DEBUG(PLUGIN_NAME "(%s): [%s]=%s (%d)", dev_name, MMC_MANUFACTOR, buffer, + *value); + return 0; + } + + WARNING(PLUGIN_NAME "(%s): Unable to read manufacturer identifier (manfid)", + dev_name); + return EXIT_FAILURE; +} + +static int mmc_read_oemid(const char *dev_name, int *value) { + char buffer[4096]; + + if (mmc_read_dev_attr(dev_name, MMC_OEM_ID, buffer, sizeof(buffer)) == 0) { + *value = (int)strtol(buffer, NULL, 0); + DEBUG(PLUGIN_NAME "(%s): [%s]=%s (%d)", dev_name, MMC_OEM_ID, buffer, + *value); + return 0; + } + + WARNING( + PLUGIN_NAME + "(%s): Unable to read original equipment manufacturer identifier (oemid)", + dev_name); + return EXIT_FAILURE; +} + +enum mmc_manfid { + MANUFACTUR_SWISSBIT = 93, // 0x5d +}; + +enum mmc_oemid_swissbit { + OEMID_SWISSBIT_1 = 21314, // 0x5342 +}; + +#define MMC_POWER_CYCLES "mmc_power_cycles" +#define MMC_BLOCK_ERASES "mmc_block_erases" +#define MMC_BAD_BLOCKS "mmc_bad_blocks" + +// Size of string buffer with '\0' +#define SWISSBIT_LENGTH_SPARE_BLOCKS 3 +#define SWISSBIT_LENGTH_BLOCK_ERASES 13 +#define SWISSBIT_LENGTH_POWER_ON 9 + +#define SWISSBIT_SSR_START_SPARE_BLOCKS 66 +#define SWISSBIT_SSR_START_BLOCK_ERASES 92 +#define SWISSBIT_SSR_START_POWER_ON 112 + +static int mmc_read_ssr_swissbit(const char *dev_name) { + char buffer[4096]; + int oemid; + int value; + int length; + char bad_blocks[SWISSBIT_LENGTH_SPARE_BLOCKS]; + char block_erases[SWISSBIT_LENGTH_BLOCK_ERASES]; + char power_on[SWISSBIT_LENGTH_POWER_ON]; + + if (mmc_read_oemid(dev_name, &oemid) != 0) { + return EXIT_FAILURE; + } + + if (oemid != OEMID_SWISSBIT_1) { + INFO(PLUGIN_NAME + "(%s): The mmc device is not suppored by this plugin (oemid: 0x%x)", + dev_name, oemid); + return EXIT_FAILURE; + } + + if (mmc_read_dev_attr(dev_name, MMC_SSR, buffer, sizeof(buffer)) != 0) { + return EXIT_FAILURE; + } + + /* + * Since the register is read out as a byte stream, it is 128 bytes long. + * One char represents a half byte (nibble). + * + */ + length = strlen(buffer); + DEBUG(PLUGIN_NAME ": %d byte read from SSR register", length); + if (length < 128) { + INFO(PLUGIN_NAME "(%s): The SSR register is not 128 byte long", dev_name); + return EXIT_FAILURE; + } + DEBUG(PLUGIN_NAME "(%s): [%s]=%s", dev_name, MMC_SSR, buffer); + + /* write MMC_BAD_BLOCKS */ + sstrncpy(bad_blocks, &buffer[SWISSBIT_SSR_START_SPARE_BLOCKS], + sizeof(bad_blocks) - 1); + bad_blocks[sizeof(bad_blocks) - 1] = '\0'; + value = (int)strtol(bad_blocks, NULL, 16); + /* convert to more common bad blocks information */ + value = abs(value - 100); + DEBUG(PLUGIN_NAME "(%s): [bad_blocks] str=%s int=%d", dev_name, bad_blocks, + value); + mmc_submit(dev_name, MMC_BAD_BLOCKS, (gauge_t)value); + + /* write MMC_BLOCK_ERASES */ + sstrncpy(block_erases, &buffer[SWISSBIT_SSR_START_BLOCK_ERASES], + sizeof(block_erases) - 1); + block_erases[sizeof(block_erases) - 1] = '\0'; + value = (int)strtol(block_erases, NULL, 16); + DEBUG(PLUGIN_NAME "(%s): [block_erases] str=%s int=%d", dev_name, + block_erases, value); + mmc_submit(dev_name, MMC_BLOCK_ERASES, (gauge_t)value); + + /* write MMC_POWER_CYCLES */ + sstrncpy(power_on, &buffer[SWISSBIT_SSR_START_POWER_ON], + sizeof(power_on) - 1); + power_on[sizeof(power_on) - 1] = '\0'; + value = (int)strtol(power_on, NULL, 16); + DEBUG(PLUGIN_NAME "(%s): [power_on] str=%s int=%d", dev_name, power_on, + value); + mmc_submit(dev_name, MMC_POWER_CYCLES, (gauge_t)value); + + return 0; +} + +static int mmc_read(void) { + DIR *dir; + struct dirent *dirent; + int manfid; + + if ((dir = opendir(SYS_PATH)) == NULL) { + ERROR(PLUGIN_NAME ": Cannot open directory [%s]", SYS_PATH); + return -1; + } + + while ((dirent = readdir(dir)) != NULL) { + if (ignorelist_match(ignorelist, dirent->d_name)) + continue; + + if (mmc_read_manfid(dirent->d_name, &manfid) != 0) + continue; + + DEBUG(PLUGIN_NAME "(%s): manfid=%d", dirent->d_name, manfid); + + switch (manfid) { + case MANUFACTUR_SWISSBIT: + mmc_read_ssr_swissbit(dirent->d_name); + break; + default: + INFO(PLUGIN_NAME + "(%s): The manufactur id %d is not suppored by this plugin", + dirent->d_name, manfid); + break; + } + } + + closedir(dir); + + return 0; +} + +void module_register(void) { + plugin_register_config(PLUGIN_NAME, mmc_config, config_keys, config_keys_num); + plugin_register_read(PLUGIN_NAME, mmc_read); +} /* void module_register */ diff --git a/src/types.db b/src/types.db index dc86a7bc8..14057e770 100644 --- a/src/types.db +++ b/src/types.db @@ -165,6 +165,9 @@ memcached_ops value:DERIVE:0:U memory value:GAUGE:0:281474976710656 memory_lua value:GAUGE:0:281474976710656 memory_throttle_count value:DERIVE:0:U +mmc_power_cycles value:GAUGE:0:U +mmc_block_erases value:GAUGE:0:U +mmc_bad_blocks value:GAUGE:0:255 multimeter value:GAUGE:U:U mutex_operations value:DERIVE:0:U mysql_bpool_bytes value:GAUGE:0:U -- 2.47.2