diff --git a/src/libsysprof-profile/meson.build b/src/libsysprof-profile/meson.build index b0bab6b8..957a3509 100644 --- a/src/libsysprof-profile/meson.build +++ b/src/libsysprof-profile/meson.build @@ -1,6 +1,7 @@ libsysprof_profile_public_sources = [ 'sysprof-cpu-usage.c', 'sysprof-instrument.c', + 'sysprof-network-usage.c', 'sysprof-profiler.c', 'sysprof-recording.c', 'sysprof-spawnable.c', @@ -18,6 +19,7 @@ libsysprof_profile_public_headers = [ 'sysprof-instrument.h', 'sysprof-cpu-usage.h', + 'sysprof-network-usage.h', 'sysprof-profiler.h', 'sysprof-recording.h', 'sysprof-spawnable.h', diff --git a/src/libsysprof-profile/sysprof-network-usage.c b/src/libsysprof-profile/sysprof-network-usage.c new file mode 100644 index 00000000..0db5a966 --- /dev/null +++ b/src/libsysprof-profile/sysprof-network-usage.c @@ -0,0 +1,368 @@ +/* sysprof-network-usage.c + * + * Copyright 2023 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 + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "line-reader-private.h" + +#include "sysprof-network-usage.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" + +struct _SysprofNetworkUsage +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofNetworkUsageClass +{ + SysprofInstrumentClass parent_class; +}; + +typedef struct _DeviceUsage +{ + /* Counter IDs */ + guint rx_bytes_id; + guint tx_bytes_id; + gchar iface[32]; + gint64 rx_bytes; + gint64 rx_packets; + gint64 rx_errors; + gint64 rx_dropped; + gint64 rx_fifo; + gint64 rx_frame; + gint64 rx_compressed; + gint64 rx_multicast; + gint64 tx_bytes; + gint64 tx_packets; + gint64 tx_errors; + gint64 tx_dropped; + gint64 tx_fifo; + gint64 tx_collisions; + gint64 tx_carrier; + gint64 tx_compressed; +} DeviceUsage; + +G_DEFINE_FINAL_TYPE (SysprofNetworkUsage, sysprof_network_usage, SYSPROF_TYPE_INSTRUMENT) + +typedef struct _Record +{ + SysprofRecording *recording; + DexFuture *cancellable; +} Record; + +static void +record_free (gpointer data) +{ + Record *record = data; + + g_clear_object (&record->recording); + dex_clear (&record->cancellable); + g_free (record); +} + +static DeviceUsage * +find_device_by_name (GArray *ar, + const char *name) +{ + for (guint i = 0; i < ar->len; i++) + { + DeviceUsage *dev = &g_array_index (ar, DeviceUsage, i); + + if (g_strcmp0 (name, dev->iface) == 0) + return dev; + } + + return NULL; +} + +static DexFuture * +sysprof_network_usage_record_fiber (gpointer user_data) +{ + char buf[4096*2]; + Record *record = user_data; + SysprofCaptureCounterValue *values; + g_autoptr(GArray) devices = NULL; + g_autoptr(GError) error = NULL; + SysprofCaptureWriter *writer; + SysprofCaptureCounter ctr[2] = {0}; + g_autofd int stat_fd = -1; + LineReader reader; + guint *ids; + guint combined_rx_id; + guint combined_tx_id; + gssize n_read; + gsize line_len; + guint lineno; + char *line; + + g_assert (record != NULL); + g_assert (SYSPROF_IS_RECORDING (record->recording)); + g_assert (DEX_IS_FUTURE (record->cancellable)); + + writer = _sysprof_recording_writer (record->recording); + + if (-1 == (stat_fd = open ("/proc/net/dev", O_RDONLY|O_CLOEXEC))) + return dex_future_new_reject (G_IO_ERROR, + g_io_error_from_errno (errno), + "%s", g_strerror (errno)); + + devices = g_array_new (FALSE, FALSE, sizeof (DeviceUsage)); + + combined_rx_id = sysprof_capture_writer_request_counter (writer, 1); + combined_tx_id = sysprof_capture_writer_request_counter (writer, 1); + + g_strlcpy (ctr[0].category, "Network", sizeof ctr[0].category); + g_strlcpy (ctr[0].name, "RX Bytes", sizeof ctr[0].name); + g_strlcpy (ctr[0].description, "Combined", sizeof ctr[0].description); + ctr[0].id = combined_rx_id; + ctr[0].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[0].value.v64 = 0; + + g_strlcpy (ctr[1].category, "Network", sizeof ctr[1].category); + g_strlcpy (ctr[1].name, "TX Bytes", sizeof ctr[1].name); + g_strlcpy (ctr[1].description, "Combined", sizeof ctr[1].description); + ctr[1].id = combined_tx_id; + ctr[1].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[1].value.v64 = 0; + + sysprof_capture_writer_define_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ctr, G_N_ELEMENTS (ctr)); + + n_read = dex_await_int64 (dex_aio_read (NULL, stat_fd, buf, sizeof buf, 0), &error); + if (n_read <= 0) + return dex_future_new_reject (G_IO_ERROR, + g_io_error_from_errno (errno), + "%s", g_strerror (errno)); + + lineno = 0; + line_reader_init (&reader, buf, n_read); + while ((line = line_reader_next (&reader, &line_len))) + { + DeviceUsage dev = {0}; + g_autofree char *rx = NULL; + g_autofree char *tx = NULL; + char *ptr = line; + char *name; + + line[line_len] = 0; + + if (lineno++ < 2) + continue; + + for (; *ptr && g_ascii_isspace (*ptr); ptr++) { /* Do Nothing */ } + name = ptr; + for (; *ptr && *ptr != ':'; ptr++) { /* Do Nothing */ } + *ptr = 0; + + rx = g_strdup_printf ("RX Bytes (%s)", name); + tx = g_strdup_printf ("TX Bytes (%s)", name); + + g_strlcpy (ctr[0].category, "Network", sizeof ctr[0].category); + g_strlcpy (ctr[0].name, rx, sizeof ctr[0].name); + g_strlcpy (ctr[0].description, name, sizeof ctr[0].description); + ctr[0].id = sysprof_capture_writer_request_counter (writer, 1); + ctr[0].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[0].value.v64 = 0; + + g_strlcpy (ctr[1].category, "Network", sizeof ctr[1].category); + g_strlcpy (ctr[1].name, tx, sizeof ctr[1].name); + g_strlcpy (ctr[1].description, name, sizeof ctr[1].description); + ctr[1].id = sysprof_capture_writer_request_counter (writer, 1); + ctr[1].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[1].value.v64 = 0; + + sysprof_capture_writer_define_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ctr, G_N_ELEMENTS (ctr)); + + dev.rx_bytes_id = ctr[0].id; + dev.tx_bytes_id = ctr[0].id; + g_strlcpy (dev.iface, name, sizeof dev.iface); + + g_array_append_val (devices, dev); + } + + values = g_alloca (sizeof (SysprofCaptureCounterValue) * (devices->len + 2)); + ids = g_alloca (sizeof (guint) * (devices->len + 2)); + ids[0] = combined_rx_id; + ids[1] = combined_tx_id; + for (guint i = 0; i < devices->len; i++) + { + const DeviceUsage *dev = &g_array_index (devices, DeviceUsage, i); + ids[(i+1)*2] = dev->rx_bytes_id; + ids[(i+1)*2+1] = dev->tx_bytes_id; + } + + for (;;) + { + DeviceUsage *first = &g_array_index (devices, DeviceUsage, 0); + gint64 combined_rx = 0; + gint64 combined_tx = 0; + + n_read = dex_await_int64 (dex_aio_read (NULL, stat_fd, buf, sizeof buf, 0), &error); + if (n_read <= 0) + break; + + lineno = 0; + line_reader_init (&reader, buf, n_read); + while ((line = line_reader_next (&reader, &line_len))) + { + DeviceUsage *dev; + guint index; + char *name; + char *ptr = line; + + line[line_len] = 0; + + if (lineno++ < 2) + continue; + + for (; *ptr && g_ascii_isspace (*ptr); ptr++) { /* Do Nothing */ } + name = ptr; + for (; *ptr && *ptr != ':'; ptr++) { /* Do Nothing */ } + *ptr = 0; + + if (!(dev = find_device_by_name (devices, name))) + continue; + + ptr++; + + sscanf (ptr, + "%"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT, + &dev->rx_bytes, + &dev->rx_packets, + &dev->rx_errors, + &dev->rx_dropped, + &dev->rx_fifo, + &dev->rx_frame, + &dev->rx_compressed, + &dev->rx_multicast, + &dev->tx_bytes, + &dev->tx_packets, + &dev->tx_errors, + &dev->tx_dropped, + &dev->tx_fifo, + &dev->tx_collisions, + &dev->tx_carrier, + &dev->tx_compressed); + + combined_rx += dev->rx_bytes; + combined_tx += dev->tx_bytes; + + index = dev - first; + + values[(index+1)*2].v64 = dev->rx_bytes; + values[(index+1)*2+1].v64 = dev->tx_bytes; + } + + values[0].v64 = combined_rx; + values[1].v64 = combined_tx; + + sysprof_capture_writer_set_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ids, + values, + (devices->len + 1) * 2); + + /* Wait for cancellation or ½ second */ + dex_await (dex_future_first (dex_ref (record->cancellable), + dex_timeout_new_usec (G_USEC_PER_SEC / 2), + NULL), + NULL); + if (dex_future_get_status (record->cancellable) != DEX_FUTURE_STATUS_PENDING) + break; + } + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_network_usage_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + Record *record; + + g_assert (SYSPROF_IS_NETWORK_USAGE (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + record = g_new0 (Record, 1); + record->recording = g_object_ref (recording); + record->cancellable = dex_cancellable_new_from_cancellable (cancellable); + + return dex_scheduler_spawn (NULL, 0, + sysprof_network_usage_record_fiber, + record, + record_free); +} + +static void +sysprof_network_usage_class_init (SysprofNetworkUsageClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->record = sysprof_network_usage_record; +} + +static void +sysprof_network_usage_init (SysprofNetworkUsage *self) +{ +} + +SysprofInstrument * +sysprof_network_usage_new (void) +{ + return g_object_new (SYSPROF_TYPE_NETWORK_USAGE, NULL); +} diff --git a/src/libsysprof-profile/sysprof-network-usage.h b/src/libsysprof-profile/sysprof-network-usage.h new file mode 100644 index 00000000..5cd0cb5d --- /dev/null +++ b/src/libsysprof-profile/sysprof-network-usage.h @@ -0,0 +1,42 @@ +/* sysprof-network-usage.h + * + * Copyright 2023 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-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_NETWORK_USAGE (sysprof_network_usage_get_type()) +#define SYSPROF_IS_NETWORK_USAGE(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_NETWORK_USAGE) +#define SYSPROF_NETWORK_USAGE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_NETWORK_USAGE, SysprofNetworkUsage) +#define SYSPROF_NETWORK_USAGE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_NETWORK_USAGE, SysprofNetworkUsageClass) + +typedef struct _SysprofNetworkUsage SysprofNetworkUsage; +typedef struct _SysprofNetworkUsageClass SysprofNetworkUsageClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_network_usage_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_network_usage_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofNetworkUsage, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof-profile/sysprof-profile.h b/src/libsysprof-profile/sysprof-profile.h index fd383d46..74f6e8ea 100644 --- a/src/libsysprof-profile/sysprof-profile.h +++ b/src/libsysprof-profile/sysprof-profile.h @@ -27,6 +27,7 @@ G_BEGIN_DECLS #define SYSPROF_PROFILE_INSIDE # include "sysprof-cpu-usage.h" # include "sysprof-instrument.h" +# include "sysprof-network-usage.h" # include "sysprof-profiler.h" # include "sysprof-recording.h" # include "sysprof-spawnable.h" diff --git a/src/libsysprof-profile/tests/test-profiler.c b/src/libsysprof-profile/tests/test-profiler.c index 5d9a7114..8e7780a8 100644 --- a/src/libsysprof-profile/tests/test-profiler.c +++ b/src/libsysprof-profile/tests/test-profiler.c @@ -114,6 +114,7 @@ main (int argc, profiler = sysprof_profiler_new (); sysprof_profiler_add_instrument (profiler, sysprof_cpu_usage_new ()); + sysprof_profiler_add_instrument (profiler, sysprof_network_usage_new ()); sysprof_profiler_record_async (profiler, writer, NULL, record_cb, NULL);