]> git.ipfire.org Git - thirdparty/collectd.git/commitdiff
mmc plugin: initial checkin (#3887)
authorFlorian Eckert <fe@dev.tdt.de>
Mon, 13 Sep 2021 13:00:46 +0000 (15:00 +0200)
committerGitHub <noreply@github.com>
Mon, 13 Sep 2021 13:00:46 +0000 (15:00 +0200)
Signed-off-by: Florian Eckert <fe@dev.tdt.de>
AUTHORS
Makefile.am
README
configure.ac
src/collectd.conf.in
src/mmc.c [new file with mode: 0644]
src/types.db

diff --git a/AUTHORS b/AUTHORS
index 61a15f7a89227562c9327daecf1c625b174c9baa..488aacb4703a13dc64b5ff8fd72b80a17f957273 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -183,6 +183,9 @@ Flavio Stanchina <flavio at stanchina.net>
 Franck Lombardi
  - UNIX socket code for the memcached plugin.
 
+Florian Eckert
+ - MMC plugin
+
 Gergely Nagy <algernon at madhouse-project.org>
  - Write-Riemann plugin.
 
index 00947da0783bf789c595f99c476435b52942d362..00b780c9fcfa295f0bc2cba4ebb41205b963e03c 100644 (file)
@@ -1472,6 +1472,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 e42e9c24a8d68e2ad594074147ca831b6d3831b7..8b5255636fbe0437a66433f92c87b9e5024a7edd 100644 (file)
--- 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.
index feee27d59f3e1faa20a1666741835f07aca89283..69ad27b9321580c87908c1375d999416b6ee1242 100644 (file)
@@ -6640,6 +6640,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"
@@ -7125,6 +7126,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])
index 579a4f0a1921cce31f891afcf34cb314f3b2f3e4..f5b4fdcdceb1645bf192b31d67fd88cb1f54851e 100644 (file)
 #      ValuesPercentage false
 #</Plugin>
 
+#<Plugin mmc>
+#      Device "mmc0"
+#      IgnoreSelected false
+#</Plugin>
+
 #<Plugin modbus>
 #      <Data "data_name">
 #              RegisterBase 1234
diff --git a/src/mmc.c b/src/mmc.c
new file mode 100644 (file)
index 0000000..49a0439
--- /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 <fe@dev.tdt.de>
+ *
+ */
+
+#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 */
index 98fece166d94608831a66c9c400b9fd4e54f8675..0de90bac4581ebf7ed25c4ff9a083fa779da1ebd 100644 (file)
@@ -166,6 +166,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