From f1826bfc86a7b94d7c6bbd6c839656d6cbabe0da Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Tue, 6 Jun 2023 12:29:01 -0700 Subject: [PATCH] libsysprof-profile: implement battery charge counters This parses the battery charge values on a regular interval and exits when the recording is stopped. --- .../sysprof-battery-charge.c | 187 +++++++++++++++++- 1 file changed, 186 insertions(+), 1 deletion(-) diff --git a/src/libsysprof-profile/sysprof-battery-charge.c b/src/libsysprof-profile/sysprof-battery-charge.c index 18e858b4..4ad534f3 100644 --- a/src/libsysprof-profile/sysprof-battery-charge.c +++ b/src/libsysprof-profile/sysprof-battery-charge.c @@ -37,6 +37,8 @@ #include "sysprof-instrument-private.h" #include "sysprof-recording-private.h" +#define SYS_CLASS_POWER_SUPPLY "/sys/class/power_supply/" + struct _SysprofBatteryCharge { SysprofInstrument parent_instance; @@ -65,17 +67,200 @@ record_free (gpointer data) g_free (record); } +static void +clear_fd (gpointer ptr) +{ + int *fdptr = ptr; + g_clear_fd (fdptr, NULL); +} + +static char ** +collect_battery_names (void) +{ + GPtrArray *ar = g_ptr_array_new (); + g_autoptr(GDir) dir = NULL; + + if ((dir = g_dir_open (SYS_CLASS_POWER_SUPPLY, 0, NULL))) + { + const char *name; + + while ((name = g_dir_read_name (dir))) + { + if (strcmp (name, "AC") == 0) + continue; + + g_ptr_array_add (ar, g_strdup (name)); + } + } + + g_ptr_array_add (ar, NULL); + + return (char **)g_ptr_array_free (ar, FALSE); +} + +typedef struct _ReadBuffer +{ + char buf[32]; +} ReadBuffer; + static DexFuture * sysprof_battery_charge_record_fiber (gpointer user_data) { - G_GNUC_UNUSED SysprofCaptureWriter *writer; + const int invalid_fd = -1; + SysprofCaptureCounterValue *values; + SysprofCaptureCounter *counters; + SysprofCaptureWriter *writer; Record *record = user_data; + g_autoptr(GArray) charge_fds = NULL; + g_auto(GStrv) names = NULL; + ReadBuffer *bufs; + guint *ids; + guint n_names; + guint n_counters = 1; g_assert (record != NULL); g_assert (SYSPROF_IS_RECORDING (record->recording)); g_assert (DEX_IS_FUTURE (record->cancellable)); writer = _sysprof_recording_writer (record->recording); + names = collect_battery_names (); + n_names = g_strv_length (names); + + /* Use some stack space for our counters and values. */ + ids = g_alloca0 (sizeof *ids * (n_names + 1)); + counters = g_alloca0 (sizeof *counters * (n_names + 1)); + values = g_alloca0 (sizeof *values * (n_names + 1)); + bufs = g_alloca0 (sizeof (ReadBuffer) * (n_names + 1)); + + /* Setup the combined counter which is the total charge of all of + * the batteries we discover on the system. + */ + counters[0].id = ids[0] = sysprof_capture_writer_request_counter (writer, 1); + g_strlcpy (counters[0].category, "Battery Charge", sizeof counters[0].category); + g_strlcpy (counters[0].name, "Combined", sizeof counters[0].name); + g_snprintf (counters[0].description, sizeof counters[0].description, "Combined Battery Charge (µAh)"); + counters[0].type = SYSPROF_CAPTURE_COUNTER_INT64; + counters[0].value.v64 = 0; + + /* Create array to store open FDs to the charge file. We'll do a + * positioned read at 0 on these so we get new data on each subsequent + * read via AIO. Set the first FD to invalid since that will map to the + * position of the combined-counter. + */ + charge_fds = g_array_new (FALSE, FALSE, sizeof (int)); + g_array_set_clear_func (charge_fds, clear_fd); + g_array_append_val (charge_fds, invalid_fd); + + for (guint i = 0; names[i]; i++) + { + SysprofCaptureCounter *counter = &counters[n_counters]; + const char *name = names[i]; + g_autofree char *charge_path = g_build_filename (SYS_CLASS_POWER_SUPPLY, name, "charge_now", NULL); + g_autofree char *model_name_path = g_build_filename (SYS_CLASS_POWER_SUPPLY, name, "model_name", NULL); + g_autofree char *type_path = g_build_filename (SYS_CLASS_POWER_SUPPLY, name, "type", NULL); + g_autofree char *model_name_data = NULL; + g_autofree char *type_data = NULL; + g_autofd int charge_fd = -1; + + if (!g_file_get_contents (type_path, &type_data, NULL, NULL) || + !g_str_has_prefix (type_data, "Battery") || + -1 == (charge_fd = open (charge_path, O_RDONLY|O_CLOEXEC))) + continue; + + counter->id = ids[n_counters] = sysprof_capture_writer_request_counter (writer, 1); + counter->type = SYSPROF_CAPTURE_COUNTER_INT64; + g_strlcpy (counter->category, "Battery Charge", sizeof counter->description); + if (g_file_get_contents (model_name_path, &model_name_data, NULL, NULL)) + g_strlcpy (counter->name, g_strstrip (model_name_data), sizeof counter->name); + else + g_strlcpy (counter->name, name, sizeof counter->name); + g_snprintf (counter->description, sizeof counter->description, "%s (µAh)", counter->name); + counter->value.v64 = 0; + + g_array_append_val (charge_fds, charge_fd); + charge_fd = -1; + + n_counters++; + } + + /* If we only have the combined counter, then just short-circuit and + * don't record any counters. Otherwise the battery charge row might + * show up in UI which we would want to omit. + */ + if (n_counters == 1) + return dex_future_new_for_boolean (TRUE); + + sysprof_capture_writer_define_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + counters, + n_counters); + + /* Main recording loop */ + for (;;) + { + g_autoptr(GPtrArray) futures = g_ptr_array_new_with_free_func (dex_unref); + + /* Read the contents of all the charge buffers in a single + * submission to the AIO layer. + */ + g_ptr_array_add (futures, dex_future_new_for_boolean (TRUE)); + for (guint i = 1; i < charge_fds->len; i++) + { + int charge_fd = g_array_index (charge_fds, int, i); + + g_ptr_array_add (futures, + dex_aio_read (NULL, + charge_fd, + &bufs[i].buf, + sizeof bufs[i].buf-1, + 0)); + } + + /* Now wait until all the reads complete */ + if (futures->len > 0) + dex_await (dex_future_anyv ((DexFuture **)futures->pdata, futures->len), NULL); + + /* Parse the current charge values */ + values[0].v64 = 0; + for (guint i = 1; i < charge_fds->len; i++) + { + gssize len = dex_await_int64 (dex_ref (g_ptr_array_index (futures, i)), NULL); + guint64 v64; + + if (len <= 0) + continue; + + errno = 0; + bufs[i].buf[len] = 0; + v64 = g_ascii_strtoull (bufs[i].buf, NULL, 10); + if (v64 == G_MAXUINT64 || errno != 0) + continue; + + values[i].v64 = v64; + values[0].v64 += v64; + } + + /* Deliver new values to capture */ + sysprof_capture_writer_set_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ids, + values, + n_counters); + + /* 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 cancellable is rejected, then we're done recording */ + if (dex_future_get_status (record->cancellable) != DEX_FUTURE_STATUS_PENDING) + break; + } return dex_future_new_for_boolean (TRUE); }