From 757e152f88f961a2aa6625487972f797fd5b588b Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 1 Jul 2019 12:23:50 -0700 Subject: [PATCH] diskstat: add simple /proc/diskstats data source --- src/libsysprof/meson.build | 2 + src/libsysprof/sysprof-diskstat-source.c | 472 +++++++++++++++++++++++ src/libsysprof/sysprof-diskstat-source.h | 35 ++ src/libsysprof/sysprof.h | 1 + 4 files changed, 510 insertions(+) create mode 100644 src/libsysprof/sysprof-diskstat-source.c create mode 100644 src/libsysprof/sysprof-diskstat-source.h diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build index 4d45b20b..14f99057 100644 --- a/src/libsysprof/meson.build +++ b/src/libsysprof/meson.build @@ -7,6 +7,7 @@ libsysprof_public_sources = [ 'sysprof-callgraph-profile.c', 'sysprof-capture-gobject.c', 'sysprof-capture-symbol-resolver.c', + 'sysprof-diskstat-source.c', 'sysprof-elf-symbol-resolver.c', 'sysprof-gjs-source.c', 'sysprof-governor-source.c', @@ -35,6 +36,7 @@ libsysprof_public_headers = [ 'sysprof-callgraph-profile.h', 'sysprof-capture-gobject.h', 'sysprof-capture-symbol-resolver.h', + 'sysprof-diskstat-source.h', 'sysprof-elf-symbol-resolver.h', 'sysprof-gjs-source.h', 'sysprof-governor-source.h', diff --git a/src/libsysprof/sysprof-diskstat-source.c b/src/libsysprof/sysprof-diskstat-source.c new file mode 100644 index 00000000..33feb1a7 --- /dev/null +++ b/src/libsysprof/sysprof-diskstat-source.c @@ -0,0 +1,472 @@ +/* sysprof-diskstat-source.c + * + * Copyright 2019 Christian Hergert + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "sysprof-diskstat-source" + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "sysprof-backport-autocleanups.h" +#include "sysprof-line-reader.h" +#include "sysprof-diskstat-source.h" +#include "sysprof-helpers.h" + +#define ADD_FROM_CHAR(v, c) (((v)*10L)+((c)-'0')) + +struct _SysprofDiskstatSource +{ + GObject parent_instance; + + SysprofCaptureWriter *writer; + GArray *diskstats; + + /* FD for /proc/net/dev contents */ + gint diskstat_fd; + + /* GSource ID for polling */ + guint poll_source; + + guint ignore_next_poll : 1; +}; + +typedef struct +{ + /* Counter IDs */ + guint reads_total_id; + guint writes_total_id; + + gchar device[32]; + gint64 reads_total; + gint64 reads_merged; + gint64 reads_sectors; + gint64 reads_msec; + gint64 writes_total; + gint64 writes_merged; + gint64 writes_sectors; + gint64 writes_msec; + gint64 iops_active; + gint64 iops_msec; + gint64 iops_msec_weighted; +} Diskstat; + +enum { + /* -2 */ COLUMN_MAJOR, + /* -1 */ COLUMN_MINOR, + /* 0 */ COLUMN_NAME, + /* 1 */ COLUMN_READS_TOTAL, + /* 2 */ COLUMN_READS_MERGED, + /* 3 */ COLUMN_READS_SECTORS, + /* 4 */ COLUMN_READS_MSEC, + /* 5 */ COLUMN_WRITES_TOTAL, + /* 6 */ COLUMN_WRITES_MERGED, + /* 7 */ COLUMN_WRITES_SECTORS, + /* 8 */ COLUMN_WRITES_MSEC, + /* 9 */ COLUMN_IOPS_ACTIVE, + /* 10 */ COLUMN_IOPS_MSEC, + /* 11 */ COLUMN_IOPS_MSEC_WEIGHTED, +}; + +static void source_iface_init (SysprofSourceInterface *); + +G_DEFINE_TYPE_WITH_CODE (SysprofDiskstatSource, sysprof_diskstat_source, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) + +static Diskstat * +find_device_by_name (SysprofDiskstatSource *self, + const gchar *name) +{ + g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); + g_assert (self->writer != NULL); + g_assert (name != NULL); + + for (guint i = 0; i < self->diskstats->len; i++) + { + Diskstat *diskstat = &g_array_index (self->diskstats, Diskstat, i); + + if (strcmp (name, diskstat->device) == 0) + return diskstat; + } + + return NULL; +} + +static Diskstat * +register_counters_by_name (SysprofDiskstatSource *self, + const gchar *name) +{ + SysprofCaptureCounter ctr[2] = {0}; + Diskstat ds = {0}; + + g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); + g_assert (name != NULL); + g_assert (self->writer != NULL); + + ds.reads_total_id = sysprof_capture_writer_request_counter (self->writer, 1); + ds.writes_total_id = sysprof_capture_writer_request_counter (self->writer, 1); + + g_strlcpy (ds.device, name, sizeof ds.device); + + g_strlcpy (ctr[0].category, "Disk", sizeof ctr[0].category); + g_snprintf (ctr[0].name, sizeof ctr[0].name, "Reads (%s)", name); + g_strlcpy (ctr[0].description, name, sizeof ctr[0].description); + ctr[0].id = ds.reads_total_id; + ctr[0].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[0].value.v64 = 0; + + g_strlcpy (ctr[1].category, "Disk", sizeof ctr[1].category); + g_snprintf (ctr[1].name, sizeof ctr[1].name, "Writes (%s)", name); + g_strlcpy (ctr[1].description, name, sizeof ctr[1].description); + ctr[1].id = ds.writes_total_id; + ctr[1].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[1].value.v64 = 1; + + sysprof_capture_writer_define_counters (self->writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ctr, G_N_ELEMENTS (ctr)); + + g_array_append_val (self->diskstats, ds); + return &g_array_index (self->diskstats, Diskstat, self->diskstats->len - 1); +} + +static gboolean +sysprof_diskstat_source_get_is_ready (SysprofSource *source) +{ + return TRUE; +} + +static gboolean +sysprof_diskstat_source_poll_cb (gpointer data) +{ + g_autoptr(SysprofLineReader) reader = NULL; + SysprofDiskstatSource *self = data; + g_autoptr(GArray) counters = NULL; + g_autoptr(GArray) values = NULL; + gchar buf[4096*4]; + Diskstat *found; + gint64 combined_reads_total = 0; + gint64 combined_writes_total = 0; + gssize len; + gsize line_len; + gchar *line; + + g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); + + if (self->diskstat_fd == -1) + { + self->poll_source = 0; + return G_SOURCE_REMOVE; + } + + /* Seek to 0 forces reload of data */ + lseek (self->diskstat_fd, 0, SEEK_SET); + + len = read (self->diskstat_fd, buf, sizeof buf - 1); + + /* Bail for now unless we read enough data */ + if (len > 0) + buf[len] = 0; + else + return G_SOURCE_CONTINUE; + + counters = g_array_new (FALSE, FALSE, sizeof (guint)); + values = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounterValue)); + reader = sysprof_line_reader_new (buf, len); + +#if 0 +Entries looks like this... +------------------------------------------------------------------------------------------------------------------------------ + 259 0 nvme0n1 455442 37 15274065 82579 714090 524038 44971827 1078532 0 244176 957543 0 0 0 0 + 259 1 nvme0n1p1 403 0 10411 102 61 39 33171 72 0 61 53 0 0 0 0 + 259 2 nvme0n1p2 138 0 9594 29 7 1 64 20 0 49 24 0 0 0 0 + 259 3 nvme0n1p3 454820 37 15249700 82432 688488 523998 44938592 1038914 0 237085 924545 0 0 0 0 + 253 0 dm-0 454789 0 15246924 107870 1238001 0 44938592 7873687 0 248354 7981557 0 0 0 0 + 253 1 dm-1 92473 0 4824170 27656 179001 0 5876760 1378161 0 45291 1405817 0 0 0 0 + 253 2 dm-2 206 0 5552 100 240 0 1920 297 0 219 397 0 0 0 0 + 253 3 dm-3 362064 0 10414850 80861 1058760 0 39245920 6496983 0 206426 6577844 0 0 0 0 +------------------------------------------------------------------------------------------------------------------------------ +#endif + + while ((line = (gchar *)sysprof_line_reader_next (reader, &line_len))) + { + Diskstat ds = {0}; + gint64 dummy = 0; + gint column = COLUMN_MAJOR; + + line[line_len] = 0; + + /* Skip past initial space */ + while (g_ascii_isspace (*line)) + line++; + + for (const gchar *ptr = line; *ptr; ptr++) + { + gchar ch; + + /* Skip past space and advance to next column */ + if (g_ascii_isspace (*ptr)) + { + while (g_ascii_isspace (*ptr)) + ptr++; + column++; + } + + ch = *ptr; + + switch (column) + { + case COLUMN_MAJOR: + case COLUMN_MINOR: + default: + dummy = ADD_FROM_CHAR (dummy, ch); + break; + + case COLUMN_NAME: + { + guint j; + + for (j = 0; j < sizeof ds.device && ds.device[j] != 0; j++) { /* Do Nothing */ } + if (j < sizeof ds.device) + ds.device[j] = ch; + ds.device[sizeof ds.device - 1] = 0; + + break; + } + + case COLUMN_READS_TOTAL: + ds.reads_total = ADD_FROM_CHAR (ds.reads_total, ch); + break; + + case COLUMN_READS_MERGED: + ds.reads_merged = ADD_FROM_CHAR (ds.reads_merged, ch); + break; + + case COLUMN_READS_SECTORS: + ds.reads_sectors = ADD_FROM_CHAR (ds.reads_sectors, ch); + break; + + case COLUMN_READS_MSEC: + ds.reads_msec = ADD_FROM_CHAR (ds.reads_msec, ch); + break; + + case COLUMN_WRITES_TOTAL: + ds.writes_total = ADD_FROM_CHAR (ds.writes_total, ch); + break; + + case COLUMN_WRITES_MERGED: + ds.writes_merged = ADD_FROM_CHAR (ds.writes_merged, ch); + break; + + case COLUMN_WRITES_SECTORS: + ds.writes_sectors = ADD_FROM_CHAR (ds.writes_sectors, ch); + break; + + case COLUMN_WRITES_MSEC: + ds.writes_msec = ADD_FROM_CHAR (ds.writes_msec, ch); + break; + + case COLUMN_IOPS_ACTIVE: + ds.iops_active = ADD_FROM_CHAR (ds.iops_active, ch); + break; + + case COLUMN_IOPS_MSEC: + ds.iops_msec = ADD_FROM_CHAR (ds.iops_msec, ch); + break; + + case COLUMN_IOPS_MSEC_WEIGHTED: + ds.iops_msec_weighted = ADD_FROM_CHAR (ds.iops_msec_weighted, ch); + break; + } + } + + g_strstrip (ds.device); + + if (ds.device[0]) + { + gint64 reads_total; + gint64 writes_total; + + if (!(found = find_device_by_name (self, ds.device))) + found = register_counters_by_name (self, ds.device); + + /* Stash new value, based on diff from previous */ + reads_total = ds.reads_total - found->reads_total; + writes_total = ds.writes_total - found->writes_total; + + g_array_append_val (counters, found->reads_total_id); + g_array_append_val (values, reads_total); + + g_array_append_val (counters, found->writes_total_id); + g_array_append_val (values, writes_total); + + /* Update combined values */ + combined_reads_total += reads_total; + combined_writes_total += writes_total; + + /* Save current value for diff on next poll */ + found->reads_total = ds.reads_total; + found->writes_total = ds.writes_total; + } + } + + if (!(found = find_device_by_name (self, "Combined"))) + found = register_counters_by_name (self, "Combined"); + + g_array_append_val (counters, found->reads_total_id); + g_array_append_val (values, combined_reads_total); + + g_array_append_val (counters, found->writes_total_id); + g_array_append_val (values, combined_writes_total); + + if (self->ignore_next_poll) + self->ignore_next_poll = FALSE; + else + sysprof_capture_writer_set_counters (self->writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + (const guint *)(gpointer)counters->data, + (const SysprofCaptureCounterValue *)(gpointer)values->data, + counters->len); + + return G_SOURCE_CONTINUE; +} + +static void +sysprof_diskstat_source_prepare (SysprofSource *source) +{ + SysprofDiskstatSource *self = (SysprofDiskstatSource *)source; + g_autoptr(GError) error = NULL; + + g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); + + self->diskstat_fd = g_open ("/proc/diskstats", O_RDONLY, 0); + + if (self->diskstat_fd == -1) + { + int errsv = errno; + error = g_error_new (G_FILE_ERROR, + g_file_error_from_errno (errsv), + "%s", + g_strerror (errsv)); + sysprof_source_emit_failed (source, error); + return; + } + + self->ignore_next_poll = TRUE; + sysprof_diskstat_source_poll_cb (self); + + sysprof_source_emit_ready (source); +} + +static void +sysprof_diskstat_source_start (SysprofSource *source) +{ + SysprofDiskstatSource *self = (SysprofDiskstatSource *)source; + + g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); + + self->poll_source = g_timeout_add (200, sysprof_diskstat_source_poll_cb, self); + + /* Poll immediately */ + sysprof_diskstat_source_poll_cb (self); +} + +static void +sysprof_diskstat_source_stop (SysprofSource *source) +{ + SysprofDiskstatSource *self = (SysprofDiskstatSource *)source; + + g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); + + /* Poll one last time */ + sysprof_diskstat_source_poll_cb (self); + + g_clear_handle_id (&self->poll_source, g_source_remove); + + sysprof_source_emit_finished (source); +} + +static void +sysprof_diskstat_source_set_writer (SysprofSource *source, + SysprofCaptureWriter *writer) +{ + SysprofDiskstatSource *self = (SysprofDiskstatSource *)source; + + g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); + g_assert (writer != NULL); + + g_clear_pointer (&self->writer, sysprof_capture_writer_unref); + self->writer = sysprof_capture_writer_ref (writer); +} + +static void +source_iface_init (SysprofSourceInterface *iface) +{ + iface->get_is_ready = sysprof_diskstat_source_get_is_ready; + iface->prepare = sysprof_diskstat_source_prepare; + iface->set_writer = sysprof_diskstat_source_set_writer; + iface->start = sysprof_diskstat_source_start; + iface->stop = sysprof_diskstat_source_stop; +} + +static void +sysprof_diskstat_source_finalize (GObject *object) +{ + SysprofDiskstatSource *self = (SysprofDiskstatSource *)object; + + g_clear_pointer (&self->diskstats, g_array_unref); + g_clear_pointer (&self->writer, sysprof_capture_writer_unref); + + if (self->diskstat_fd != -1) + { + close (self->diskstat_fd); + self->diskstat_fd = -1; + } + + G_OBJECT_CLASS (sysprof_diskstat_source_parent_class)->finalize (object); +} + +static void +sysprof_diskstat_source_class_init (SysprofDiskstatSourceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_diskstat_source_finalize; +} + +static void +sysprof_diskstat_source_init (SysprofDiskstatSource *self) +{ + self->diskstats = g_array_new (FALSE, FALSE, sizeof (Diskstat)); +} + +SysprofSource * +sysprof_diskstat_source_new (void) +{ + return g_object_new (SYSPROF_TYPE_DISKSTAT_SOURCE, NULL); +} diff --git a/src/libsysprof/sysprof-diskstat-source.h b/src/libsysprof/sysprof-diskstat-source.h new file mode 100644 index 00000000..58386e7a --- /dev/null +++ b/src/libsysprof/sysprof-diskstat-source.h @@ -0,0 +1,35 @@ +/* sysprof-diskstat-source.h + * + * Copyright 2019 Christian Hergert + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-source.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DISKSTAT_SOURCE (sysprof_diskstat_source_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofDiskstatSource, sysprof_diskstat_source, SYSPROF, DISKSTAT_SOURCE, GObject) + +SYSPROF_AVAILABLE_IN_ALL +SysprofSource *sysprof_diskstat_source_new (void); + +G_END_DECLS diff --git a/src/libsysprof/sysprof.h b/src/libsysprof/sysprof.h index f5f35070..c8c6fc04 100644 --- a/src/libsysprof/sysprof.h +++ b/src/libsysprof/sysprof.h @@ -28,6 +28,7 @@ G_BEGIN_DECLS # include "sysprof-callgraph-profile.h" # include "sysprof-capture-gobject.h" # include "sysprof-capture-symbol-resolver.h" +# include "sysprof-diskstat-source.h" # include "sysprof-elf-symbol-resolver.h" # include "sysprof-gjs-source.h" # include "sysprof-governor-source.h"