From fd1fb68a985a2077dd54313550d6c1251694fb1c Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Tue, 30 May 2023 21:29:49 -0700 Subject: [PATCH] libsysprof-profile: add various CPU parsing This still needs some work because the read operations are blocked currently. --- src/libsysprof-profile/meson.build | 1 + src/libsysprof-profile/sysprof-cpu-usage.c | 286 +++++++++++++++++---- 2 files changed, 235 insertions(+), 52 deletions(-) diff --git a/src/libsysprof-profile/meson.build b/src/libsysprof-profile/meson.build index 2d29d3e5..b0bab6b8 100644 --- a/src/libsysprof-profile/meson.build +++ b/src/libsysprof-profile/meson.build @@ -38,6 +38,7 @@ libsysprof_profile_deps = [ dependency('libdex-1', version: dex_req_version), dependency('polkit-gobject-1', version: polkit_req_version), + liblinereader_static_dep, libsysprof_capture_dep, ] diff --git a/src/libsysprof-profile/sysprof-cpu-usage.c b/src/libsysprof-profile/sysprof-cpu-usage.c index 18e20a57..5b4769be 100644 --- a/src/libsysprof-profile/sysprof-cpu-usage.c +++ b/src/libsysprof-profile/sysprof-cpu-usage.c @@ -28,12 +28,17 @@ #include #include +#include #include +#include "line-reader-private.h" + #include "sysprof-cpu-usage.h" #include "sysprof-instrument-private.h" #include "sysprof-recording-private.h" +#define PROC_STAT_BUF_SIZE (4096*4) + struct _SysprofCpuUsage { SysprofInstrument parent_instance; @@ -44,14 +49,30 @@ struct _SysprofCpuUsageClass SysprofInstrumentClass parent_class; }; -enum { - PROP_0, - N_PROPS -}; - G_DEFINE_FINAL_TYPE (SysprofCpuUsage, sysprof_cpu_usage, SYSPROF_TYPE_INSTRUMENT) -static GParamSpec *properties [N_PROPS]; +typedef struct _CpuInfo +{ + int counter_base; + double total; + glong last_user; + glong last_idle; + glong last_system; + glong last_nice; + glong last_iowait; + glong last_irq; + glong last_softirq; + glong last_steal; + glong last_guest; + glong last_guest_nice; +} CpuInfo; + +typedef struct _CpuFreq +{ + gint64 max; + int stat_fd; + char buf[116]; +} CpuFreq; typedef struct _Record { @@ -69,14 +90,48 @@ record_free (gpointer data) g_free (record); } +static void +freq_info_clear (gpointer data) +{ + CpuFreq *cpu_freq = data; + g_clear_fd (&cpu_freq->stat_fd, NULL); +} + +static double +get_cpu_freq (int stat_fd, + guint cpu, + double max, + char *buf, + gssize len) +{ + gint64 val; + + if (stat_fd == -1) + return .0; + + if (len <= 0) + return .0; + + buf[len] = 0; + g_strchug (buf); + val = g_ascii_strtoll (buf, NULL, 10); + + return (double)val / max * 100.; +} + static DexFuture * sysprof_cpu_usage_record_fiber (gpointer user_data) { Record *record = user_data; + g_autoptr(GArray) cpu_info = NULL; + g_autoptr(GArray) freq_info = NULL; + g_autofd int stat_fd = -1; + g_autofree char *read_buffer = NULL; + SysprofCaptureCounterValue *values; SysprofCaptureCounter *counters; SysprofCaptureCounter *counter; SysprofCaptureWriter *writer; - g_autofd int stat_fd = -1; + guint *ids; guint n_cpu; g_assert (record != NULL); @@ -86,7 +141,16 @@ sysprof_cpu_usage_record_fiber (gpointer user_data) writer = _sysprof_recording_writer (record->recording); n_cpu = g_get_num_processors (); stat_fd = open ("/proc/stat", O_RDONLY|O_CLOEXEC); + g_unix_set_fd_nonblocking (stat_fd, TRUE, NULL); + read_buffer = g_malloc (PROC_STAT_BUF_SIZE); + counters = g_alloca (sizeof *counters * ((n_cpu * 2) + 1)); + ids = g_alloca (sizeof *ids * ((n_cpu * 2) + 1)); + values = g_alloca (sizeof *values * ((n_cpu * 2) + 1)); + + cpu_info = g_array_new (FALSE, TRUE, sizeof (CpuInfo)); + freq_info = g_array_new (FALSE, TRUE, sizeof (CpuFreq)); + g_array_set_clear_func (freq_info, freq_info_clear); /* Create counter information for all of our counters that we will need * to submit in upcoming parses. @@ -94,6 +158,13 @@ sysprof_cpu_usage_record_fiber (gpointer user_data) for (guint i = 0; i < n_cpu; i++) { guint counter_base = sysprof_capture_writer_request_counter (writer, 2); + g_autofree char *max_path = g_strdup_printf ("/sys/devices/system/cpu/cpu%u/cpufreq/scaling_max_freq", i); + g_autofree char *cur_path = g_strdup_printf ("/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq", i); + g_autofree char *max_value = NULL; + CpuFreq cf; + + ids[n_cpu*2] = counter_base; + ids[n_cpu*2+1] = counter_base + 1; counter = &counters[i*2]; counter->id = counter_base; @@ -112,11 +183,21 @@ sysprof_cpu_usage_record_fiber (gpointer user_data) g_snprintf (counter->name, sizeof counter->name, "CPU %d", i); g_snprintf (counter->description, sizeof counter->description, "Frequency of CPU %d", i); + + cf.stat_fd = open (cur_path, O_RDONLY|O_CLOEXEC); + g_unix_set_fd_nonblocking (cf.stat_fd, TRUE, NULL); + cf.buf[0] = 0; + if (g_file_get_contents (max_path, &max_value, NULL, NULL)) + cf.max = g_ascii_strtoll (max_value, NULL, 10); + else + cf.max = 0; + g_array_append_val (freq_info, cf); } /* Now create our combined counter */ + ids[n_cpu*2] = sysprof_capture_writer_request_counter (writer, 1); counter = &counters[n_cpu*2]; - counter->id = sysprof_capture_writer_request_counter (writer, 1); + counter->id = ids[n_cpu*2]; counter->type = SYSPROF_CAPTURE_COUNTER_DOUBLE; counter->value.vdbl = 0; g_strlcpy (counter->category, "CPU Percent", sizeof counter->category); @@ -131,7 +212,151 @@ sysprof_cpu_usage_record_fiber (gpointer user_data) counters, n_cpu * 2 + 1); - g_print ("Registering %d counters\n", n_cpu * 2 + 1); + for (;;) + { + g_autoptr(GPtrArray) futures = g_ptr_array_new_with_free_func (dex_unref); + g_autoptr(DexFuture) cpu_future = NULL; + LineReader reader; + glong total_usage = 0; + gsize line_len; + char *line; + + g_print ("parsing\n"); + + /* First collect all our reads and then wait for them to finish before + * parsing in a pass. With io_uring, this lets us coalesce all the lseek + * and reads into a single set of iops. + */ + for (guint i = 0; i < n_cpu; i++) + { + CpuFreq *cf = &g_array_index (freq_info, CpuFreq, i); + + g_ptr_array_add (futures, dex_aio_read (NULL, cf->stat_fd, cf->buf, sizeof cf->buf - 1, 0)); + } + cpu_future = dex_aio_read (NULL, stat_fd, read_buffer, PROC_STAT_BUF_SIZE, 0); + g_ptr_array_add (futures, dex_ref (cpu_future)); + g_ptr_array_add (futures, dex_ref (record->cancellable)); + if (!dex_await (dex_future_allv ((DexFuture **)futures->pdata, futures->len), NULL)) + break; + + g_print ("Waiting for completions\n"); + + /* Now parse all the contents of the stat files which should be + * populated in the various files. + */ + line_reader_init (&reader, read_buffer, dex_await_int64 (cpu_future, NULL)); + while ((line = line_reader_next (&reader, &line_len))) + { + CpuInfo *ci; + char cpu[64]; + glong user; + glong sys; + glong nice; + glong idle; + int id; + int ret; + glong iowait; + glong irq; + glong softirq; + glong steal; + glong guest; + glong guest_nice; + glong user_calc; + glong system_calc; + glong nice_calc; + glong idle_calc; + glong iowait_calc; + glong irq_calc; + glong softirq_calc; + glong steal_calc; + glong guest_calc; + glong guest_nice_calc; + glong total; + + line[line_len] = 0; + + /* CPU lines come first, short-circuit after */ + if (!g_str_has_prefix (line, "cpu")) + break; + + /* First line is "cpu ..." */ + if (!g_ascii_isdigit (line[3])) + continue; + + /* Parse the various counters in order */ + user = nice = sys = idle = id = 0; + ret = sscanf (line, "%s %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld", + cpu, &user, &nice, &sys, &idle, + &iowait, &irq, &softirq, &steal, &guest, &guest_nice); + if (ret != 11) + continue; + + /* Get the CPU identifier */ + ret = sscanf (cpu, "cpu%d", &id); + if (ret != 1 || id < 0 || id >= n_cpu) + continue; + + ci = &g_array_index (cpu_info, CpuInfo, id); + + user_calc = user - ci->last_user; + nice_calc = nice - ci->last_nice; + system_calc = sys - ci->last_system; + idle_calc = idle - ci->last_idle; + iowait_calc = iowait - ci->last_iowait; + irq_calc = irq - ci->last_irq; + softirq_calc = softirq - ci->last_softirq; + steal_calc = steal - ci->last_steal; + guest_calc = guest - ci->last_guest; + guest_nice_calc = guest_nice - ci->last_guest_nice; + + total = user_calc + nice_calc + system_calc + idle_calc + iowait_calc + irq_calc + softirq_calc + steal_calc + guest_calc + guest_nice_calc; + ci->total = ((total - idle_calc) / (double)total) * 100.; + + ci->last_user = user; + ci->last_nice = nice; + ci->last_idle = idle; + ci->last_system = sys; + ci->last_iowait = iowait; + ci->last_irq = irq; + ci->last_softirq = softirq; + ci->last_steal = steal; + ci->last_guest = guest; + ci->last_guest_nice = guest_nice; + } + + /* Publish counters to the capture file */ + for (guint i = 0; i < n_cpu; i++) + { + const CpuInfo *ci = &g_array_index (cpu_info, CpuInfo, i); + CpuFreq *cf = &g_array_index (freq_info, CpuFreq, i); + DexFuture *freq_future = g_ptr_array_index (futures, i); + gssize len = dex_await_int64 (dex_ref (freq_future), NULL); + + values[n_cpu*i].vdbl = ci->total; + values[n_cpu*i+1].vdbl = get_cpu_freq (cf->stat_fd, i, cf->max, cf->buf, len); + + total_usage += ci->total; + } + + values[n_cpu*2].vdbl = total_usage / (double)n_cpu; + sysprof_capture_writer_set_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ids, + values, + n_cpu * 2 + 1); + + g_print ("adding counters\n"); + + /* Wait for cancellation or ½ second */ + dex_await (dex_future_any (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); } @@ -157,54 +382,11 @@ sysprof_cpu_usage_record (SysprofInstrument *instrument, record_free); } -static void -sysprof_cpu_usage_finalize (GObject *object) -{ - SysprofCpuUsage *self = (SysprofCpuUsage *)object; - - G_OBJECT_CLASS (sysprof_cpu_usage_parent_class)->finalize (object); -} - -static void -sysprof_cpu_usage_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofCpuUsage *self = SYSPROF_CPU_USAGE (object); - - switch (prop_id) - { - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_cpu_usage_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofCpuUsage *self = SYSPROF_CPU_USAGE (object); - - switch (prop_id) - { - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - static void sysprof_cpu_usage_class_init (SysprofCpuUsageClass *klass) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); - object_class->finalize = sysprof_cpu_usage_finalize; - object_class->get_property = sysprof_cpu_usage_get_property; - object_class->set_property = sysprof_cpu_usage_set_property; - instrument_class->record = sysprof_cpu_usage_record; }