Files
sysprof/src/libsysprof/sysprof-memory-usage.c
Christian Hergert dbb7833cbf libsysprof: join libsysprof-analyze and libsysprof-profile
This brings together the two libraries back into one now that the whole
design is pretty well sorted out. They depend on roughly the same libraries
anyway and it's way easier of the single library can both read and write
the capture files (along with bringing in libsysprof-capture symbols in
a single place).
2023-07-19 17:40:41 -07:00

306 lines
8.0 KiB
C

/* sysprof-memory-usage.c
*
* Copyright 2023 Christian Hergert <chergert@redhat.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glib-unix.h>
#include <glib/gstdio.h>
#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)
{
g_autoptr(GByteArray) buf = NULL;
Record *record = user_data;
SysprofCaptureWriter *writer;
g_autoptr(GError) error = NULL;
SysprofCaptureCounter counters[1];
MemStat st;
guint counter_id;
g_assert (record != NULL);
g_assert (SYSPROF_IS_RECORDING (record->recording));
g_assert (DEX_IS_FUTURE (record->cancellable));
buf = g_byte_array_new ();
g_byte_array_set_size (buf, 4096);
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->data, buf->len-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->data[n_read] = 0;
mem_stat_parse (&st, (char *)buf->data);
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);
}