diff --git a/src/libsysprof-profile/meson.build b/src/libsysprof-profile/meson.build index 2d3b3b6f..9969b9ee 100644 --- a/src/libsysprof-profile/meson.build +++ b/src/libsysprof-profile/meson.build @@ -2,6 +2,7 @@ libsysprof_profile_public_sources = [ 'sysprof-cpu-usage.c', 'sysprof-disk-usage.c', 'sysprof-instrument.c', + 'sysprof-memory-usage.c', 'sysprof-network-usage.c', 'sysprof-profiler.c', 'sysprof-recording.c', @@ -21,6 +22,7 @@ libsysprof_profile_public_headers = [ 'sysprof-cpu-usage.h', 'sysprof-disk-usage.h', 'sysprof-instrument.h', + 'sysprof-memory-usage.h', 'sysprof-network-usage.h', 'sysprof-profiler.h', 'sysprof-recording.h', diff --git a/src/libsysprof-profile/sysprof-memory-usage.c b/src/libsysprof-profile/sysprof-memory-usage.c new file mode 100644 index 00000000..ee543025 --- /dev/null +++ b/src/libsysprof-profile/sysprof-memory-usage.c @@ -0,0 +1,302 @@ +/* sysprof-memory-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-memory-usage.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" + +struct _SysprofMemoryUsage +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofMemoryUsageClass +{ + SysprofInstrumentClass parent_class; +}; + +typedef struct _MemStat +{ + int stat_fd; + + union { + struct { + SysprofCaptureCounterValue used; + gint64 total; + gint64 avail; + gint64 free; + } sys; + }; +} MemStat; + +G_DEFINE_FINAL_TYPE (SysprofMemoryUsage, sysprof_memory_usage, SYSPROF_TYPE_INSTRUMENT) + +static GHashTable *keys; + +static gboolean +mem_stat_open (MemStat *st, + GError **error) +{ + memset (st, 0, sizeof *st); + + st->stat_fd = open ("/proc/meminfo", O_RDONLY|O_CLOEXEC); + + if (st->stat_fd == -1) + { + int errsv = errno; + g_set_error_literal (error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + g_strerror (errsv)); + return FALSE; + } + + return TRUE; +} + +static gboolean +mem_stat_close (MemStat *st, + GError **error) +{ + g_assert (st != NULL); + + return g_clear_fd (&st->stat_fd, error); +} + +static void +mem_stat_parse (MemStat *st, + char *buf) +{ + char *bufptr = buf; + char *save = NULL; + + g_assert (st != NULL); + g_assert (buf != NULL); + + for (;;) + { + goffset off; + char *key; + char *value; + char *unit; + gint64 v64; + gint64 *v64ptr; + + /* Get the data key name */ + if (!(key = strtok_r (bufptr, " \n\t:", &save))) + break; + + bufptr = NULL; + + /* Offset from self to save value. Stop after getting to + * last value we care about. + */ + if (!(off = GPOINTER_TO_UINT (g_hash_table_lookup (keys, key)))) + break; + + /* Get the data value */ + if (!(value = strtok_r (bufptr, " \n\t:", &save))) + break; + + /* Parse the numeric value of this column */ + v64 = g_ascii_strtoll (value, NULL, 10); + if ((v64 == G_MININT64 || v64 == G_MAXINT64) && errno == ERANGE) + break; + + /* Get the data unit */ + unit = strtok_r (bufptr, " \n\t:", &save); + + if (g_strcmp0 (unit, "kB") == 0) + v64 *= 1024; + else if (g_strcmp0 (unit, "mB") == 0) + v64 *= 1024 * 1024; + + v64ptr = (gint64 *)(gpointer)(((gchar *)st) + off); + + *v64ptr = v64; + } + + /* Create pre-compiled value for used to simplify display */ + st->sys.used.vdbl = (gdouble)st->sys.total - (gdouble)st->sys.avail; +} + +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 DexFuture * +sysprof_memory_usage_record_fiber (gpointer user_data) +{ + Record *record = user_data; + SysprofCaptureWriter *writer; + g_autoptr(GError) error = NULL; + SysprofCaptureCounter counters[1]; + MemStat st; + char buf[4096]; + guint counter_id; + + 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 (!mem_stat_open (&st, &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + + counter_id = sysprof_capture_writer_request_counter (writer, 1); + + g_strlcpy (counters[0].category, "Memory", sizeof counters[0].category); + g_strlcpy (counters[0].name, "Used", sizeof counters[0].name); + g_strlcpy (counters[0].description, "Memory used by system", sizeof counters[0].description); + + counters[0].id = counter_id; + counters[0].type = SYSPROF_CAPTURE_COUNTER_DOUBLE; + counters[0].value.vdbl = 0; + + sysprof_capture_writer_define_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + counters, + 1); + + for (;;) + { + g_autoptr(DexFuture) read_future = dex_aio_read (NULL, st.stat_fd, buf, sizeof buf-1, 0); + gssize n_read; + + dex_await (dex_future_first (dex_ref (read_future), + dex_ref (record->cancellable), + NULL), + NULL); + + if (dex_future_get_status (read_future) != DEX_FUTURE_STATUS_RESOLVED) + break; + + n_read = dex_await_int64 (dex_ref (read_future), NULL); + if (n_read <= 0) + break; + + if (n_read > 0) + { + buf[n_read] = 0; + + mem_stat_parse (&st, buf); + + sysprof_capture_writer_set_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + &counter_id, + &st.sys.used, + 1); + } + + 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_REJECTED) + break; + } + + if (!mem_stat_close (&st, &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_memory_usage_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + Record *record; + + g_assert (SYSPROF_IS_MEMORY_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_memory_usage_record_fiber, + record, + record_free); +} + +static void +sysprof_memory_usage_class_init (SysprofMemoryUsageClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->record = sysprof_memory_usage_record; + + keys = g_hash_table_new (g_str_hash, g_str_equal); + +#define ADD_OFFSET(n,o) \ + g_hash_table_insert (keys, (gchar *)n, GUINT_TO_POINTER (o)) + ADD_OFFSET ("MemTotal", G_STRUCT_OFFSET (MemStat, sys.total)); + ADD_OFFSET ("MemFree", G_STRUCT_OFFSET (MemStat, sys.free)); + ADD_OFFSET ("MemAvailable", G_STRUCT_OFFSET (MemStat, sys.avail)); +#undef ADD_OFFSET +} + +static void +sysprof_memory_usage_init (SysprofMemoryUsage *self) +{ +} + +SysprofInstrument * +sysprof_memory_usage_new (void) +{ + return g_object_new (SYSPROF_TYPE_MEMORY_USAGE, NULL); +} diff --git a/src/libsysprof-profile/sysprof-memory-usage.h b/src/libsysprof-profile/sysprof-memory-usage.h new file mode 100644 index 00000000..4b63f868 --- /dev/null +++ b/src/libsysprof-profile/sysprof-memory-usage.h @@ -0,0 +1,42 @@ +/* sysprof-memory-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_MEMORY_USAGE (sysprof_memory_usage_get_type()) +#define SYSPROF_IS_MEMORY_USAGE(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_MEMORY_USAGE) +#define SYSPROF_MEMORY_USAGE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_MEMORY_USAGE, SysprofMemoryUsage) +#define SYSPROF_MEMORY_USAGE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_MEMORY_USAGE, SysprofMemoryUsageClass) + +typedef struct _SysprofMemoryUsage SysprofMemoryUsage; +typedef struct _SysprofMemoryUsageClass SysprofMemoryUsageClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_memory_usage_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_memory_usage_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofMemoryUsage, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof-profile/sysprof-profile.h b/src/libsysprof-profile/sysprof-profile.h index e8aace6d..7a560a98 100644 --- a/src/libsysprof-profile/sysprof-profile.h +++ b/src/libsysprof-profile/sysprof-profile.h @@ -28,6 +28,7 @@ G_BEGIN_DECLS # include "sysprof-cpu-usage.h" # include "sysprof-disk-usage.h" # include "sysprof-instrument.h" +# include "sysprof-memory-usage.h" # include "sysprof-network-usage.h" # include "sysprof-profiler.h" # include "sysprof-recording.h" diff --git a/src/libsysprof-profile/tests/test-profiler.c b/src/libsysprof-profile/tests/test-profiler.c index e451cc38..ba4b1364 100644 --- a/src/libsysprof-profile/tests/test-profiler.c +++ b/src/libsysprof-profile/tests/test-profiler.c @@ -118,6 +118,7 @@ main (int argc, sysprof_profiler_add_instrument (profiler, sysprof_cpu_usage_new ()); sysprof_profiler_add_instrument (profiler, sysprof_disk_usage_new ()); + sysprof_profiler_add_instrument (profiler, sysprof_memory_usage_new ()); sysprof_profiler_add_instrument (profiler, sysprof_network_usage_new ()); for (int i = 1; i < argc; i++)