about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--README.md41
-rw-r--r--diskstats.c133
-rw-r--r--diskstats.h8
-rw-r--r--main.c2
5 files changed, 185 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index 906031a..18f278e 100644
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,7 @@
 
 COLLECTORS =
 COLLECTORS += cpu
+COLLECTORS += diskstats
 COLLECTORS += filesystem
 COLLECTORS += hwmon
 COLLECTORS += meminfo
diff --git a/README.md b/README.md
index 1626b83..52f863d 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@ including generated metrics, labels and configuration options.
 | Name | Description |
 | ---- | ----------- |
 | [`cpu`](#cpu) | CPU usage from `/proc/stat` and CPU frequency scaling data from sysfs. |
+| [`diskstats`](#diskstats) | Disk I/O statistics from `/proc/diskstats`. |
 | [`filesystem`](#filesystem) | Statistics of mounted filesystems from `statvfs(2)`. |
 | [`hwmon`](#hwmon) | Temperature, fan and voltage sensors from `/sys/class/hwmon`. |
 | [`meminfo`](#meminfo) | Memory usage statistics from `/proc/meminfo`. |
@@ -83,6 +84,46 @@ Metrics and labels:
   `cpufreq/scaling_cur_freq` value under the CPU-specific sysfs
   directory.
 
+### `diskstats`
+
+The metrics correspond to the columns of `/proc/diskstats`:
+
+* `node_disk_reads_completed_total`: Total number of successfully
+  completed disk reads.
+* `node_disk_reads_merged_total` Total number of adjacent reads merged
+  together.
+* `node_disk_read_bytes_total`: Total number of bytes read from the
+  device.
+* `node_disk_read_time_seconds_total`: Total time spent in read
+  requests.
+* `node_disk_writes_completed_total`: Total number of successfully
+  completed disk writes.
+* `node_disk_writes_merged_total`: Total number of adjacent writes
+  merged together.
+* `node_disk_written_bytes_total`: Total number of bytes written to
+  the device.
+* `node_disk_write_time_seconds_total`: Total time spent in write
+  requests.
+* `node_disk_io_now`: Number of I/O operations currently in progress.
+* `node_disk_io_time_seconds_total`: Total time spent in disk I/O.
+* `node_disk_io_time_weighted_seconds_total`: Time spent in disk I/O
+  weighted by the number of pending operations.
+
+See the kernel's
+[Documentation/iostats.txt](https://www.kernel.org/doc/Documentation/iostats.txt)
+for more details. The collector assumes the read/write totals are
+reported using a sector size of 512 bytes.
+
+All metrics have one label, `device`, containing the device name from
+`/proc/diskstats`.
+
+The `--diskstats-include=` and `--diskstats-exclude=` command line
+arguments can be used to select which devices to report on. The format
+for both is a comma-separated list of device names (e.g.,
+`--diskstats-include=sda,sdb`). If an include list is provided, only
+those devices explicitly listed are included. Otherwise, all devices
+not mentioned on the exclude list are included.
+
 ### `filesystem`
 
 Metrics:
diff --git a/diskstats.c b/diskstats.c
new file mode 100644
index 0000000..6fdcf01
--- /dev/null
+++ b/diskstats.c
@@ -0,0 +1,133 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "diskstats.h"
+#include "util.h"
+
+// size of input buffer for paths and lines
+#define BUF_SIZE 256
+
+static void *diskstats_init(int argc, char *argv[]);
+static void diskstats_collect(scrape_req *req, void *ctx);
+
+const struct collector diskstats_collector = {
+  .name = "diskstats",
+  .collect = diskstats_collect,
+  .init = diskstats_init,
+  .has_args = true,
+};
+
+struct diskstats_context {
+  struct slist *include;
+  struct slist *exclude;
+};
+
+static void *diskstats_init(int argc, char *argv[]) {
+  struct diskstats_context *ctx = must_malloc(sizeof *ctx);
+
+  ctx->include = 0;
+  ctx->exclude = 0;
+
+  for (int arg = 0; arg < argc; arg++) {
+    if (strncmp(argv[arg], "include=", 8) == 0) {
+      ctx->include = slist_split(&argv[arg][8], ",");
+    } else if (strncmp(argv[arg], "exclude=", 8) == 0) {
+      ctx->exclude = slist_split(&argv[arg][8], ",");
+    } else {
+      fprintf(stderr, "unknown argument for diskstats collector: %s", argv[arg]);
+      return 0;
+    }
+  }
+
+  return ctx;
+}
+
+static const struct {
+  const char *metric;
+  double factor;
+} columns[] = {
+  { .metric = "node_disk_reads_completed_total", .factor = 1.0 },
+  { .metric = "node_disk_reads_merged_total", .factor = 1.0 },
+  { .metric = "node_disk_read_bytes_total", .factor = 512.0 },  // TODO: always 512-byte sectors?
+  { .metric = "node_disk_read_time_seconds_total", .factor = 0.001 },
+  { .metric = "node_disk_writes_completed_total", .factor = 1.0 },
+  { .metric = "node_disk_writes_merged_total", .factor = 1.0 },
+  { .metric = "node_disk_written_bytes_total", .factor = 512.0 },  // TODO: constant?
+  { .metric = "node_disk_write_time_seconds_total", .factor = 0.001 },  // TODO: constant?
+  { .metric = "node_disk_io_now", .factor = 1.0 },
+  { .metric = "node_disk_io_time_seconds_total", .factor = 0.001 },
+  { .metric = "node_disk_io_time_weighted_seconds_total", .factor = 0.001 },
+  // TODO: metrics for 4.18+ discard fields
+  // - # of discards completed
+  // - # of discards merged
+  // - # of sectors discarded
+  // - # of milliseconds spent discarding
+};
+#define NCOLUMNS (sizeof columns / sizeof *columns)
+
+static void diskstats_collect(scrape_req *req, void *ctx_ptr) {
+  struct diskstats_context *ctx = ctx_ptr;
+
+  // buffers
+
+  const char *labels[][2] = {
+    { "device", 0 },  // filled by code
+    { 0, 0 },
+  };
+
+  char buf[BUF_SIZE];
+
+  FILE *f;
+
+  // emit metrics for all known columns of /proc/diskstats for included devices
+
+  f = fopen("/proc/diskstats", "r");
+  if (!f)
+    return;
+
+  while (fgets_line(buf, sizeof buf, f)) {
+    char *p;
+
+    // skip first two columns (device node numbers)
+
+    strtok_r(buf, " ", &p);
+    strtok_r(0, " ", &p);
+
+    // extract device name
+
+    char *dev = strtok_r(0, " ", &p);
+    if (!dev || *dev == '\0')
+      continue;
+    labels[0][1] = dev;
+
+    // filter
+
+    if (ctx->include) {
+      if (!slist_contains(ctx->include, dev))
+        continue;
+    } else if (ctx->exclude) {
+      if (slist_contains(ctx->exclude, dev))
+        continue;
+    }
+
+    // emit metrics while known columns last
+
+    for (size_t c = 0; c < NCOLUMNS; c++) {
+      char *v = strtok_r(0, " ", &p);
+      if (!v || *v == '\0')
+        break;
+
+      char *end;
+      double d = strtod(v, &end);
+      if (*end != '\0')
+        continue;
+
+      scrape_write(req, columns[c].metric, labels, d * columns[c].factor);
+    }
+  }
+
+  fclose(f);
+}
diff --git a/diskstats.h b/diskstats.h
new file mode 100644
index 0000000..15a867a
--- /dev/null
+++ b/diskstats.h
@@ -0,0 +1,8 @@
+#ifndef PNANOE_DISKSTATS_H_
+#define PNANOE_DISKSTATS_H_ 1
+
+#include "collector.h"
+
+extern const struct collector diskstats_collector;
+
+#endif // PNANOE_DISKSTATS_H_
diff --git a/main.c b/main.c
index 77d4e49..036a06f 100644
--- a/main.c
+++ b/main.c
@@ -6,6 +6,7 @@
 #include <string.h>
 
 #include "cpu.h"
+#include "diskstats.h"
 #include "filesystem.h"
 #include "hwmon.h"
 #include "meminfo.h"
@@ -17,6 +18,7 @@
 
 static const struct collector *collectors[] = {
   &cpu_collector,
+  &diskstats_collector,
   &filesystem_collector,
   &hwmon_collector,
   &meminfo_collector,