diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | README.md | 41 | ||||
-rw-r--r-- | diskstats.c | 133 | ||||
-rw-r--r-- | diskstats.h | 8 | ||||
-rw-r--r-- | main.c | 2 |
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, |