From 52f411e1de44864932b320fceeb3f8ad5e7bd190 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 12 Oct 2018 17:11:17 -0700 Subject: [PATCH] memory: add memory source for basic mem stats --- lib/sources/meson.build | 4 +- lib/sources/sp-memory-source.c | 494 +++++++++++++++++++++++++++++++++ lib/sources/sp-memory-source.h | 33 +++ lib/sysprof.h | 1 + src/sp-window.c | 4 + tools/sysprof-cli.c | 9 + 6 files changed, 544 insertions(+), 1 deletion(-) create mode 100644 lib/sources/sp-memory-source.c create mode 100644 lib/sources/sp-memory-source.h diff --git a/lib/sources/meson.build b/lib/sources/meson.build index a5dd0fac..6a3ab64d 100644 --- a/lib/sources/meson.build +++ b/lib/sources/meson.build @@ -1,6 +1,7 @@ sources_headers = [ 'sp-gjs-source.h', 'sp-hostinfo-source.h', + 'sp-memory-source.h', 'sp-perf-source.h', 'sp-proc-source.h', 'sp-source.h', @@ -9,6 +10,7 @@ sources_headers = [ sources_sources = [ 'sp-gjs-source.c', 'sp-hostinfo-source.c', + 'sp-memory-source.c', 'sp-perf-counter.c', 'sp-perf-counter.h', 'sp-perf-source.c', @@ -20,4 +22,4 @@ libsysprof_headers += files(sources_headers) libsysprof_sources += files(sources_sources) install_headers(sources_headers, - subdir: join_paths(libsysprof_header_subdir, 'sources')) + subdir: join_paths(libsysprof_header_subdir, 'sources')) \ No newline at end of file diff --git a/lib/sources/sp-memory-source.c b/lib/sources/sp-memory-source.c new file mode 100644 index 00000000..2da7f0c2 --- /dev/null +++ b/lib/sources/sp-memory-source.c @@ -0,0 +1,494 @@ +/* sp-memory-source.c + * + * Copyright 2018 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" + +#define G_LOG_DOMAIN "sp-memory-source" + +#include +#include +#include +#include + +#include "sp-clock.h" + +#include "sources/sp-memory-source.h" + +#define BUF_SIZE 4096 + +struct _SpMemorySource +{ + GObject parent_instance; + + /* Capture writer to deliver samples */ + SpCaptureWriter *writer; + + /* 4k stat buffer for reading proc */ + gchar *stat_buf; + + /* Array of MemStat */ + GArray *mem_stats; + + /* Timeout to perform polling */ + guint timer_source; +}; + +typedef struct +{ + GPid pid; + int stat_fd; + + /* Keep in this order, so we can address from total */ + SpCaptureCounterValue mem_total; + SpCaptureCounterValue mem_avail; + SpCaptureCounterValue mem_free; + + /* Keep in this order, so we can address from size */ + SpCaptureCounterValue mem_size; + SpCaptureCounterValue mem_resident; + SpCaptureCounterValue mem_shared; + SpCaptureCounterValue mem_text; + SpCaptureCounterValue mem_data; + + guint counter_ids[5]; +} MemStat; + +static void source_iface_init (SpSourceInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (SpMemorySource, sp_memory_source, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (SP_TYPE_SOURCE, source_iface_init)) + +static GHashTable *keys; + +static void +mem_stat_open (MemStat *st) +{ + g_assert (st != NULL); + g_assert (st->stat_fd == -1); + + if (st->pid != -1) + { + g_autofree gchar *path = g_strdup_printf ("/proc/%d/statm", st->pid); + + if (-1 == (st->stat_fd = open (path, O_RDONLY))) + g_warning ("Failed to access statm for pid %d", st->pid); + } + else + { + st->stat_fd = open ("/proc/meminfo", O_RDONLY); + } +} + +static void +mem_stat_close (MemStat *st) +{ + g_assert (st != NULL); + + if (st->stat_fd != -1) + { + close (st->stat_fd); + st->stat_fd = -1; + } +} + +static void +mem_stat_parse_statm (MemStat *st, + gchar *buf) +{ + g_assert (st != NULL); + + sscanf (buf, + "%"G_GINT64_FORMAT" " + "%"G_GINT64_FORMAT" " + "%"G_GINT64_FORMAT" " + "%"G_GINT64_FORMAT" " + "%*1c " + "%"G_GINT64_FORMAT, + &st->mem_size.v64, + &st->mem_resident.v64, + &st->mem_shared.v64, + &st->mem_text.v64, + &st->mem_data.v64); +} + +static void +mem_stat_parse_meminfo (MemStat *st, + gchar *buf) +{ + gchar *bufptr = buf; + gchar *save = NULL; + + g_assert (st != NULL); + + for (;;) + { + goffset off; + gchar *key; + gchar *value; + gchar *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; + } +} + +static void +mem_stat_poll (MemStat *st, + gchar *stat_buf) +{ + gssize r; + + g_assert (st != NULL); + g_assert (st->stat_fd != -1); + + /* Seek to begin of FD to reset the proc read */ + if ((r = lseek (st->stat_fd, 0, SEEK_SET)) < 0) + return; + + /* Now read the updated proc buffer */ + if ((r = read (st->stat_fd, stat_buf, BUF_SIZE)) < 0) + return; + + if (r < BUF_SIZE) + stat_buf[r] = '\0'; + stat_buf[BUF_SIZE-1] = '\0'; + + if (st->pid == -1) + mem_stat_parse_meminfo (st, stat_buf); + else + mem_stat_parse_statm (st, stat_buf); +} + +static void +mem_stat_publish (MemStat *st, + SpCaptureWriter *writer, + gint64 current_time) +{ + g_assert (st != NULL); + g_assert (writer != NULL); + + sp_capture_writer_set_counters (writer, + current_time, + -1, + st->pid, + st->counter_ids, + st->pid == -1 ? &st->mem_total : &st->mem_size, + st->pid == -1 ? 3 : 5); +} + +/** + * sp_memory_source_new: + * + * Create a new #SpMemorySource. + * + * Use this source to record basic memory statistics about the system + * such as Available, Free, and Total Memory. + * + * Returns: (transfer full): a newly created #SpMemorySource + + * Since: 3.32 + */ +SpSource * +sp_memory_source_new (void) +{ + return g_object_new (SP_TYPE_MEMORY_SOURCE, NULL); +} + +static void +sp_memory_source_finalize (GObject *object) +{ + SpMemorySource *self = (SpMemorySource *)object; + + if (self->timer_source != 0) + { + g_source_remove (self->timer_source); + self->timer_source = 0; + } + + g_clear_pointer (&self->stat_buf, g_free); + g_clear_pointer (&self->writer, sp_capture_writer_unref); + g_clear_pointer (&self->mem_stats, g_array_unref); + + G_OBJECT_CLASS (sp_memory_source_parent_class)->finalize (object); +} + +static void +sp_memory_source_class_init (SpMemorySourceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sp_memory_source_finalize; + + 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, mem_total)); + ADD_OFFSET ("MemFree", G_STRUCT_OFFSET (MemStat, mem_free)); + ADD_OFFSET ("MemAvailable", G_STRUCT_OFFSET (MemStat, mem_avail)); +#undef ADD_OFFSET +} + +static void +sp_memory_source_init (SpMemorySource *self) +{ + self->stat_buf = g_malloc (BUF_SIZE); + self->mem_stats = g_array_new (FALSE, FALSE, sizeof (MemStat)); +} + +static void +sp_memory_source_set_writer (SpSource *source, + SpCaptureWriter *writer) +{ + SpMemorySource *self = (SpMemorySource *)source; + + g_assert (SP_IS_SOURCE (self)); + g_assert (writer != NULL); + g_assert (self->writer == NULL); + + self->writer = sp_capture_writer_ref (writer); +} + +static void +sp_memory_source_prepare (SpSource *source) +{ + SpMemorySource *self = (SpMemorySource *)source; + + g_assert (SP_IS_MEMORY_SOURCE (self)); + g_assert (self->writer != NULL); + + /* If no pids are registered, setup a system memory stat */ + if (self->mem_stats->len == 0) + { + MemStat st = {0}; + + st.pid = -1; + st.stat_fd = -1; + + g_array_append_val (self->mem_stats, st); + } + + /* Open FDs to all the necessary files in proc. We will re-use + * them to avoid re-opening files later. + */ + for (guint i = 0; i < self->mem_stats->len; i++) + { + MemStat *st = &g_array_index (self->mem_stats, MemStat, i); + SpCaptureCounter counters[5]; + guint base; + + mem_stat_open (st); + + for (guint j = 0; j < G_N_ELEMENTS (counters); j++) + g_strlcpy (counters[j].category, "Memory", sizeof counters[j].category); + + if (st->pid == -1) + { + base = sp_capture_writer_request_counter (self->writer, 3); + + g_strlcpy (counters[0].name, "Total", sizeof counters[0].name); + g_strlcpy (counters[1].name, "Available", sizeof counters[1].name); + g_strlcpy (counters[2].name, "Free", sizeof counters[2].name); + + g_strlcpy (counters[0].description, "", sizeof counters[0].description); + g_strlcpy (counters[1].description, "", sizeof counters[1].description); + g_strlcpy (counters[2].description, "", sizeof counters[2].description); + + counters[0].id = st->counter_ids[0] = base; + counters[1].id = st->counter_ids[1] = base + 1; + counters[2].id = st->counter_ids[2] = base + 2; + + counters[0].type = SP_CAPTURE_COUNTER_INT64; + counters[1].type = SP_CAPTURE_COUNTER_INT64; + counters[2].type = SP_CAPTURE_COUNTER_INT64; + + counters[0].value.v64 = 0; + counters[1].value.v64 = 0; + counters[2].value.v64 = 0; + + + sp_capture_writer_define_counters (self->writer, + SP_CAPTURE_CURRENT_TIME, + -1, + -1, + counters, + 3); + } + else + { + base = sp_capture_writer_request_counter (self->writer, 5); + + g_strlcpy (counters[0].name, "Size", sizeof counters[0].name); + g_strlcpy (counters[1].name, "Resident", sizeof counters[1].name); + g_strlcpy (counters[2].name, "Shared", sizeof counters[2].name); + g_strlcpy (counters[3].name, "Text", sizeof counters[3].name); + g_strlcpy (counters[4].name, "Data", sizeof counters[4].name); + + g_strlcpy (counters[0].description, "", sizeof counters[0].description); + g_strlcpy (counters[1].description, "", sizeof counters[1].description); + g_strlcpy (counters[2].description, "", sizeof counters[2].description); + g_strlcpy (counters[3].description, "", sizeof counters[3].description); + g_strlcpy (counters[4].description, "", sizeof counters[4].description); + + counters[0].id = st->counter_ids[0] = base; + counters[1].id = st->counter_ids[1] = base + 1; + counters[2].id = st->counter_ids[2] = base + 2; + counters[3].id = st->counter_ids[3] = base + 3; + counters[4].id = st->counter_ids[4] = base + 4; + + counters[0].type = SP_CAPTURE_COUNTER_INT64; + counters[1].type = SP_CAPTURE_COUNTER_INT64; + counters[2].type = SP_CAPTURE_COUNTER_INT64; + counters[3].type = SP_CAPTURE_COUNTER_INT64; + counters[4].type = SP_CAPTURE_COUNTER_INT64; + + counters[0].value.v64 = 0; + counters[1].value.v64 = 0; + counters[2].value.v64 = 0; + counters[3].value.v64 = 0; + counters[4].value.v64 = 0; + + sp_capture_writer_define_counters (self->writer, + SP_CAPTURE_CURRENT_TIME, + -1, + st->pid, + counters, + 5); + } + } + + sp_source_emit_ready (SP_SOURCE (self)); +} + +static gboolean +sp_memory_source_timer_cb (SpMemorySource *self) +{ + gint64 current_time; + + g_assert (SP_IS_MEMORY_SOURCE (self)); + g_assert (self->writer != NULL); + + current_time = sp_clock_get_current_time (); + + for (guint i = 0; i < self->mem_stats->len; i++) + { + MemStat *st = &g_array_index (self->mem_stats, MemStat, i); + + mem_stat_poll (st, self->stat_buf); + mem_stat_publish (st, self->writer, current_time); + } + + return G_SOURCE_CONTINUE; +} + +static void +sp_memory_source_start (SpSource *source) +{ + SpMemorySource *self = (SpMemorySource *)source; + + g_assert (SP_IS_MEMORY_SOURCE (self)); + + /* Poll 20x/sec for memory stats */ + self->timer_source = g_timeout_add_full (G_PRIORITY_HIGH, + 1000 / 20, + (GSourceFunc)sp_memory_source_timer_cb, + self, + NULL); +} + +static void +sp_memory_source_stop (SpSource *source) +{ + SpMemorySource *self = (SpMemorySource *)source; + + g_assert (SP_IS_MEMORY_SOURCE (self)); + + if (self->timer_source != 0) + { + g_source_remove (self->timer_source); + self->timer_source = 0; + } + + for (guint i = 0; i < self->mem_stats->len; i++) + { + MemStat *st = &g_array_index (self->mem_stats, MemStat, i); + + mem_stat_close (st); + } + + sp_source_emit_finished (source); +} + +static void +sp_memory_source_add_pid (SpSource *source, + GPid pid) +{ + SpMemorySource *self = (SpMemorySource *)source; + MemStat st = {0}; + + g_assert (SP_IS_MEMORY_SOURCE (self)); + + st.pid = pid; + st.stat_fd = -1; + + g_array_append_val (self->mem_stats, st); +} + +static void +source_iface_init (SpSourceInterface *iface) +{ + iface->set_writer = sp_memory_source_set_writer; + iface->prepare = sp_memory_source_prepare; + iface->start = sp_memory_source_start; + iface->stop = sp_memory_source_stop; + iface->add_pid = sp_memory_source_add_pid; +} diff --git a/lib/sources/sp-memory-source.h b/lib/sources/sp-memory-source.h new file mode 100644 index 00000000..a50636eb --- /dev/null +++ b/lib/sources/sp-memory-source.h @@ -0,0 +1,33 @@ +/* sp-memory-source.h + * + * Copyright 2018 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 "sources/sp-source.h" + +G_BEGIN_DECLS + +#define SP_TYPE_MEMORY_SOURCE (sp_memory_source_get_type()) + +G_DECLARE_FINAL_TYPE (SpMemorySource, sp_memory_source, SP, MEMORY_SOURCE, GObject) + +SpSource *sp_memory_source_new (void); + +G_END_DECLS diff --git a/lib/sysprof.h b/lib/sysprof.h index be650568..a3fd6338 100644 --- a/lib/sysprof.h +++ b/lib/sysprof.h @@ -44,6 +44,7 @@ G_BEGIN_DECLS # include "sources/sp-gjs-source.h" # include "sources/sp-hostinfo-source.h" +# include "sources/sp-memory-source.h" # include "sources/sp-perf-source.h" # include "sources/sp-proc-source.h" # include "sources/sp-source.h" diff --git a/src/sp-window.c b/src/sp-window.c index beab3b76..5d0485e8 100644 --- a/src/sp-window.c +++ b/src/sp-window.c @@ -403,6 +403,7 @@ sp_window_add_sources (SpWindow *window, g_autoptr(SpSource) host_source = NULL; g_autoptr(SpSource) proc_source = NULL; g_autoptr(SpSource) perf_source = NULL; + g_autoptr(SpSource) memory_source = NULL; g_assert (SP_IS_WINDOW (window)); g_assert (SP_IS_PROFILER (profiler)); @@ -415,6 +416,9 @@ sp_window_add_sources (SpWindow *window, host_source = sp_hostinfo_source_new (); sp_profiler_add_source (profiler, host_source); + + memory_source = sp_memory_source_new (); + sp_profiler_add_source (profiler, memory_source); } static void diff --git a/tools/sysprof-cli.c b/tools/sysprof-cli.c index e433dc9c..3be1b9e9 100644 --- a/tools/sysprof-cli.c +++ b/tools/sysprof-cli.c @@ -88,6 +88,7 @@ main (gint argc, GError *error = NULL; GSource *gsource; gchar *command = NULL; + gboolean memory = FALSE; gboolean version = FALSE; gboolean force = FALSE; int pid = -1; @@ -97,6 +98,7 @@ main (gint argc, { "pid", 'p', 0, G_OPTION_ARG_INT, &pid, N_("Make sysprof specific to a task"), N_("PID") }, { "command", 'c', 0, G_OPTION_ARG_STRING, &command, N_("Run a command and profile the process"), N_("COMMAND") }, { "force", 'f', 0, G_OPTION_ARG_NONE, &force, N_("Force overwrite the capture file") }, + { "memory", 'm', 0, G_OPTION_ARG_NONE, &memory, N_("Record basic memory statistics") }, { "version", 0, 0, G_OPTION_ARG_NONE, &version, N_("Print the sysprof-cli version and exit") }, { NULL } }; @@ -213,6 +215,13 @@ main (gint argc, sp_profiler_add_source (profiler, source); g_object_unref (source); + if (memory) + { + source = sp_memory_source_new (); + sp_profiler_add_source (profiler, source); + g_object_unref (source); + } + if (pid != -1) { sp_profiler_set_whole_system (profiler, FALSE);