memprof: add memory profiling using LD_PRELOAD

This brings over some of the techniques from the old memprof design.
Sysprof and memprof shared a lot of code, so it is pretty natural to
bring back the same callgraph view based on memory allocations.

This reuses the StackStash just like it did in memprof. While it
would be nice to reuse some existing tools out there, the fit of
memprof with sysprof is so naturally aligned, it's not really a
big deal to bring back the LD_PRELOAD. The value really comes
from seeing all this stuff together instead of multiple apps.

There are plenty of things we can implement on top of this that
we are not doing yet such as temporary allocations, cross-thread
frees, graphing the heap, and graphing differences between the
heap at to points in time. I'd like all of these things, given
enough time to make them useful.

This is still a bit slow though due to the global lock we take
to access the writer. To improve the speed here we need to get
rid of that lock and head towards a design that allows a thread
to request a new writer from Sysprof and save it in TLS (to be
destroyed when the thread exits).
This commit is contained in:
Christian Hergert
2020-01-30 18:24:04 -08:00
parent cae70498da
commit 33c81a3a9c
41 changed files with 6817 additions and 193 deletions

View File

@ -27,10 +27,12 @@ cc = meson.get_compiler('c')
cxx = meson.get_compiler('cpp')
config_h = configuration_data()
config_h.set_quoted('API_VERSION_S', '@0@'.format(libsysprof_api_version))
config_h.set_quoted('PACKAGE_NAME', 'sysprof')
config_h.set_quoted('PACKAGE_VERSION', meson.project_version())
config_h.set_quoted('PACKAGE_STRING', 'sysprof-' + meson.project_version())
config_h.set_quoted('PACKAGE_BUGREPORT', 'https://bugzilla.gnome.org/enter_bug.cgi?product=sysprof')
config_h.set_quoted('PACKAGE_LIBEXECDIR', join_paths(get_option('prefix'), get_option('libexecdir')))
config_h.set('PACKAGE_TARNAME', 'PACKAGE_STRING')
config_h.set('PACKAGE', 'PACKAGE_NAME')
config_h.set('VERSION', 'PACKAGE_VERSION')
@ -83,6 +85,15 @@ config_h.set10('ENABLE_NLS', true)
config_h.set_quoted('PACKAGE_LOCALE_DIR', join_paths(get_option('prefix'), get_option('datadir'), 'locale'))
config_h.set('LOCALEDIR', 'PACKAGE_LOCALE_DIR')
if cc.has_header('execinfo.h')
config_h.set10('HAVE_EXECINFO_H', true)
endif
libunwind_dep = dependency('libunwind-generic', required: false)
if libunwind_dep.found()
config_h.set10('ENABLE_LIBUNWIND', libunwind_dep.found())
endif
# Development build setup
config_h.set('DEVELOPMENT_BUILD', version_split[1].to_int().is_odd())
@ -179,17 +190,17 @@ int main(void) {
error('Sysprof requires a C compiler with stdatomic support such as GCC 4.9 or newer')
endif
configure_file(
output: 'config.h',
configuration: config_h
)
subdir('src')
subdir('data')
subdir('examples')
subdir('help')
subdir('po')
configure_file(
output: 'config.h',
configuration: config_h
)
if get_option('enable_gtk')
meson.add_install_script('build-aux/meson/post_install.sh')
endif

View File

@ -207,6 +207,10 @@ sysprof_capture_cursor_foreach (SysprofCaptureCursor *self,
delegate = READ_DELEGATE (sysprof_capture_reader_read_file);
break;
case SYSPROF_CAPTURE_FRAME_ALLOCATION:
delegate = READ_DELEGATE (sysprof_capture_reader_read_allocation);
break;
default:
if (!sysprof_capture_reader_skip (self->reader))
return;

View File

@ -1352,3 +1352,52 @@ sysprof_capture_reader_find_file (SysprofCaptureReader *self,
return NULL;
}
const SysprofCaptureAllocation *
sysprof_capture_reader_read_allocation (SysprofCaptureReader *self)
{
SysprofCaptureAllocation *ma;
g_assert (self != NULL);
g_assert ((self->pos % SYSPROF_CAPTURE_ALIGN) == 0);
g_assert (self->pos <= self->bufsz);
if (!sysprof_capture_reader_ensure_space_for (self, sizeof *ma))
return NULL;
ma = (SysprofCaptureAllocation *)(gpointer)&self->buf[self->pos];
sysprof_capture_reader_bswap_frame (self, &ma->frame);
if (ma->frame.type != SYSPROF_CAPTURE_FRAME_ALLOCATION)
return NULL;
if (ma->frame.len < sizeof *ma)
return NULL;
if (self->endian != G_BYTE_ORDER)
{
ma->n_addrs = GUINT16_SWAP_LE_BE (ma->n_addrs);
ma->alloc_size = GUINT64_SWAP_LE_BE (ma->alloc_size);
ma->alloc_addr = GUINT64_SWAP_LE_BE (ma->alloc_addr);
ma->tid = GUINT32_SWAP_LE_BE (ma->tid);
}
if (ma->frame.len < (sizeof *ma + (sizeof(SysprofCaptureAddress) * ma->n_addrs)))
return NULL;
if (!sysprof_capture_reader_ensure_space_for (self, ma->frame.len))
return NULL;
ma = (SysprofCaptureAllocation *)(gpointer)&self->buf[self->pos];
if (G_UNLIKELY (self->endian != G_BYTE_ORDER))
{
for (guint i = 0; i < ma->n_addrs; i++)
ma->addrs[i] = GUINT64_SWAP_LE_BE (ma->addrs[i]);
}
self->pos += ma->frame.len;
return ma;
}

View File

@ -64,86 +64,88 @@ G_BEGIN_DECLS
typedef struct _SysprofCaptureReader SysprofCaptureReader;
SYSPROF_AVAILABLE_IN_ALL
SysprofCaptureReader *sysprof_capture_reader_new (const gchar *filename,
GError **error);
SysprofCaptureReader *sysprof_capture_reader_new (const gchar *filename,
GError **error);
SYSPROF_AVAILABLE_IN_ALL
SysprofCaptureReader *sysprof_capture_reader_new_from_fd (int fd,
GError **error);
SysprofCaptureReader *sysprof_capture_reader_new_from_fd (int fd,
GError **error);
SYSPROF_AVAILABLE_IN_ALL
SysprofCaptureReader *sysprof_capture_reader_copy (SysprofCaptureReader *self);
SysprofCaptureReader *sysprof_capture_reader_copy (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
SysprofCaptureReader *sysprof_capture_reader_ref (SysprofCaptureReader *self);
SysprofCaptureReader *sysprof_capture_reader_ref (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
void sysprof_capture_reader_unref (SysprofCaptureReader *self);
void sysprof_capture_reader_unref (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
gint sysprof_capture_reader_get_byte_order (SysprofCaptureReader *self);
gint sysprof_capture_reader_get_byte_order (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const gchar *sysprof_capture_reader_get_filename (SysprofCaptureReader *self);
const gchar *sysprof_capture_reader_get_filename (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const gchar *sysprof_capture_reader_get_time (SysprofCaptureReader *self);
const gchar *sysprof_capture_reader_get_time (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
gint64 sysprof_capture_reader_get_start_time (SysprofCaptureReader *self);
gint64 sysprof_capture_reader_get_start_time (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
gint64 sysprof_capture_reader_get_end_time (SysprofCaptureReader *self);
gint64 sysprof_capture_reader_get_end_time (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_reader_skip (SysprofCaptureReader *self);
gboolean sysprof_capture_reader_skip (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_reader_peek_type (SysprofCaptureReader *self,
SysprofCaptureFrameType *type);
gboolean sysprof_capture_reader_peek_type (SysprofCaptureReader *self,
SysprofCaptureFrameType *type);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_reader_peek_frame (SysprofCaptureReader *self,
SysprofCaptureFrame *frame);
gboolean sysprof_capture_reader_peek_frame (SysprofCaptureReader *self,
SysprofCaptureFrame *frame);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureLog *sysprof_capture_reader_read_log (SysprofCaptureReader *self);
const SysprofCaptureLog *sysprof_capture_reader_read_log (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureMap *sysprof_capture_reader_read_map (SysprofCaptureReader *self);
const SysprofCaptureMap *sysprof_capture_reader_read_map (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureMark *sysprof_capture_reader_read_mark (SysprofCaptureReader *self);
const SysprofCaptureMark *sysprof_capture_reader_read_mark (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureMetadata *sysprof_capture_reader_read_metadata (SysprofCaptureReader *self);
const SysprofCaptureMetadata *sysprof_capture_reader_read_metadata (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureExit *sysprof_capture_reader_read_exit (SysprofCaptureReader *self);
const SysprofCaptureExit *sysprof_capture_reader_read_exit (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureFork *sysprof_capture_reader_read_fork (SysprofCaptureReader *self);
const SysprofCaptureFork *sysprof_capture_reader_read_fork (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureTimestamp *sysprof_capture_reader_read_timestamp (SysprofCaptureReader *self);
const SysprofCaptureTimestamp *sysprof_capture_reader_read_timestamp (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureProcess *sysprof_capture_reader_read_process (SysprofCaptureReader *self);
const SysprofCaptureProcess *sysprof_capture_reader_read_process (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureSample *sysprof_capture_reader_read_sample (SysprofCaptureReader *self);
const SysprofCaptureSample *sysprof_capture_reader_read_sample (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
GHashTable *sysprof_capture_reader_read_jitmap (SysprofCaptureReader *self);
GHashTable *sysprof_capture_reader_read_jitmap (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureCounterDefine *sysprof_capture_reader_read_counter_define (SysprofCaptureReader *self);
const SysprofCaptureCounterDefine *sysprof_capture_reader_read_counter_define (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureCounterSet *sysprof_capture_reader_read_counter_set (SysprofCaptureReader *self);
const SysprofCaptureCounterSet *sysprof_capture_reader_read_counter_set (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureFileChunk *sysprof_capture_reader_read_file (SysprofCaptureReader *self);
const SysprofCaptureFileChunk *sysprof_capture_reader_read_file (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_3_36
const SysprofCaptureAllocation *sysprof_capture_reader_read_allocation (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_reader_reset (SysprofCaptureReader *self);
gboolean sysprof_capture_reader_reset (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_reader_splice (SysprofCaptureReader *self,
SysprofCaptureWriter *dest,
GError **error);
gboolean sysprof_capture_reader_splice (SysprofCaptureReader *self,
SysprofCaptureWriter *dest,
GError **error);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_reader_save_as (SysprofCaptureReader *self,
const gchar *filename,
GError **error);
gboolean sysprof_capture_reader_save_as (SysprofCaptureReader *self,
const gchar *filename,
GError **error);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_reader_get_stat (SysprofCaptureReader *self,
SysprofCaptureStat *st_buf);
gboolean sysprof_capture_reader_get_stat (SysprofCaptureReader *self,
SysprofCaptureStat *st_buf);
SYSPROF_AVAILABLE_IN_ALL
void sysprof_capture_reader_set_stat (SysprofCaptureReader *self,
const SysprofCaptureStat *st_buf);
void sysprof_capture_reader_set_stat (SysprofCaptureReader *self,
const SysprofCaptureStat *st_buf);
SYSPROF_AVAILABLE_IN_ALL
const SysprofCaptureFileChunk *sysprof_capture_reader_find_file (SysprofCaptureReader *self,
const gchar *path);
const SysprofCaptureFileChunk *sysprof_capture_reader_find_file (SysprofCaptureReader *self,
const gchar *path);
SYSPROF_AVAILABLE_IN_ALL
gchar **sysprof_capture_reader_list_files (SysprofCaptureReader *self);
gchar **sysprof_capture_reader_list_files (SysprofCaptureReader *self);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_reader_read_file_fd (SysprofCaptureReader *self,
const gchar *path,
gint fd);
gboolean sysprof_capture_reader_read_file_fd (SysprofCaptureReader *self,
const gchar *path,
gint fd);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureReader, sysprof_capture_reader_unref)

View File

@ -115,19 +115,20 @@ typedef union
typedef enum
{
SYSPROF_CAPTURE_FRAME_TIMESTAMP = 1,
SYSPROF_CAPTURE_FRAME_SAMPLE = 2,
SYSPROF_CAPTURE_FRAME_MAP = 3,
SYSPROF_CAPTURE_FRAME_PROCESS = 4,
SYSPROF_CAPTURE_FRAME_FORK = 5,
SYSPROF_CAPTURE_FRAME_EXIT = 6,
SYSPROF_CAPTURE_FRAME_JITMAP = 7,
SYSPROF_CAPTURE_FRAME_CTRDEF = 8,
SYSPROF_CAPTURE_FRAME_CTRSET = 9,
SYSPROF_CAPTURE_FRAME_MARK = 10,
SYSPROF_CAPTURE_FRAME_METADATA = 11,
SYSPROF_CAPTURE_FRAME_LOG = 12,
SYSPROF_CAPTURE_FRAME_FILE_CHUNK = 13,
SYSPROF_CAPTURE_FRAME_TIMESTAMP = 1,
SYSPROF_CAPTURE_FRAME_SAMPLE = 2,
SYSPROF_CAPTURE_FRAME_MAP = 3,
SYSPROF_CAPTURE_FRAME_PROCESS = 4,
SYSPROF_CAPTURE_FRAME_FORK = 5,
SYSPROF_CAPTURE_FRAME_EXIT = 6,
SYSPROF_CAPTURE_FRAME_JITMAP = 7,
SYSPROF_CAPTURE_FRAME_CTRDEF = 8,
SYSPROF_CAPTURE_FRAME_CTRSET = 9,
SYSPROF_CAPTURE_FRAME_MARK = 10,
SYSPROF_CAPTURE_FRAME_METADATA = 11,
SYSPROF_CAPTURE_FRAME_LOG = 12,
SYSPROF_CAPTURE_FRAME_FILE_CHUNK = 13,
SYSPROF_CAPTURE_FRAME_ALLOCATION = 14,
} SysprofCaptureFrameType;
SYSPROF_ALIGNED_BEGIN(1)
@ -311,6 +312,19 @@ typedef struct
} SysprofCaptureFileChunk
SYSPROF_ALIGNED_END(1);
SYSPROF_ALIGNED_BEGIN(1)
typedef struct
{
SysprofCaptureFrame frame;
SysprofCaptureAddress alloc_addr;
gint64 alloc_size;
gint32 tid;
guint32 n_addrs : 16;
guint32 padding1 : 16;
SysprofCaptureAddress addrs[0];
} SysprofCaptureAllocation
SYSPROF_ALIGNED_END(1);
G_STATIC_ASSERT (sizeof (SysprofCaptureFileHeader) == 256);
G_STATIC_ASSERT (sizeof (SysprofCaptureFrame) == 24);
G_STATIC_ASSERT (sizeof (SysprofCaptureMap) == 56);
@ -328,6 +342,8 @@ G_STATIC_ASSERT (sizeof (SysprofCaptureMark) == 96);
G_STATIC_ASSERT (sizeof (SysprofCaptureMetadata) == 64);
G_STATIC_ASSERT (sizeof (SysprofCaptureLog) == 64);
G_STATIC_ASSERT (sizeof (SysprofCaptureFileChunk) == 284);
G_STATIC_ASSERT (sizeof (SysprofCaptureAllocation) == 48);
G_STATIC_ASSERT ((G_STRUCT_OFFSET (SysprofCaptureAllocation, addrs) % 8) == 0);
static inline gint
sysprof_capture_address_compare (SysprofCaptureAddress a,

View File

@ -477,6 +477,24 @@ sysprof_capture_writer_cat (SysprofCaptureWriter *self,
goto panic;
break;
case SYSPROF_CAPTURE_FRAME_ALLOCATION: {
const SysprofCaptureAllocation *frame;
if (!(frame = sysprof_capture_reader_read_allocation (reader)))
goto panic;
sysprof_capture_writer_add_allocation_copy (self,
frame->frame.time,
frame->frame.cpu,
frame->frame.pid,
frame->tid,
frame->alloc_addr,
frame->alloc_size,
frame->addrs,
frame->n_addrs);
break;
}
default:
/* Silently drop, which is better than looping. We could potentially
* copy this over using the raw bytes at some point.

View File

@ -78,6 +78,7 @@
#define DEFAULT_BUFFER_SIZE (_sysprof_getpagesize() * 64L)
#define INVALID_ADDRESS (G_GUINT64_CONSTANT(0))
#define MAX_COUNTERS ((1 << 24) - 1)
#define MAX_UNWIND_DEPTH 128
typedef struct
{
@ -1510,3 +1511,101 @@ sysprof_capture_writer_set_flush_delay (SysprofCaptureWriter *self,
g_source_attach (self->periodic_flush, main_context);
}
gboolean
sysprof_capture_writer_add_allocation (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
gint32 tid,
SysprofCaptureAddress alloc_addr,
gint64 alloc_size,
SysprofBacktraceFunc backtrace_func,
gpointer backtrace_data)
{
SysprofCaptureAllocation *ev;
gsize len;
guint n_addrs;
g_assert (self != NULL);
g_assert (backtrace_func != NULL);
len = sizeof *ev + (MAX_UNWIND_DEPTH * sizeof (SysprofCaptureAddress));
ev = (SysprofCaptureAllocation *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_ALLOCATION);
ev->alloc_size = alloc_size;
ev->alloc_addr = alloc_addr;
ev->padding1 = 0;
ev->tid = tid;
ev->n_addrs = 0;
n_addrs = backtrace_func (ev->addrs, MAX_UNWIND_DEPTH, backtrace_data);
if (n_addrs <= MAX_UNWIND_DEPTH)
ev->n_addrs = n_addrs;
if (ev->n_addrs < MAX_UNWIND_DEPTH)
{
gsize diff = (sizeof (SysprofCaptureAddress) * (MAX_UNWIND_DEPTH - ev->n_addrs));
ev->frame.len -= diff;
self->pos -= diff;
}
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_ALLOCATION]++;
return TRUE;
}
gboolean
sysprof_capture_writer_add_allocation_copy (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
gint32 tid,
SysprofCaptureAddress alloc_addr,
gint64 alloc_size,
const SysprofCaptureAddress *addrs,
guint n_addrs)
{
SysprofCaptureAllocation *ev;
gsize len;
g_assert (self != NULL);
if (n_addrs > 0xFFF)
n_addrs = 0xFFF;
len = sizeof *ev + (n_addrs * sizeof (SysprofCaptureAddress));
ev = (SysprofCaptureAllocation *)sysprof_capture_writer_allocate (self, &len);
if (!ev)
return FALSE;
sysprof_capture_writer_frame_init (&ev->frame,
len,
cpu,
pid,
time,
SYSPROF_CAPTURE_FRAME_ALLOCATION);
ev->alloc_size = alloc_size;
ev->alloc_addr = alloc_addr;
ev->padding1 = 0;
ev->tid = tid;
ev->n_addrs = n_addrs;
memcpy (ev->addrs, addrs, sizeof (SysprofCaptureAddress) * n_addrs);
self->stat.frame_count[SYSPROF_CAPTURE_FRAME_ALLOCATION]++;
return TRUE;
}

View File

@ -63,154 +63,188 @@ G_BEGIN_DECLS
typedef struct _SysprofCaptureWriter SysprofCaptureWriter;
/**
* SysprofBacktraceFunc:
* @addrs: (inout) (array length=n_addrs): an array to place addresses
* into the capture frame
* @n_addrs: the length of @addrs
* @user_data: (scope call): closure data for the callback
*
* Returns: the number of addresses filled in @addrs
*/
typedef guint (*SysprofBacktraceFunc) (SysprofCaptureAddress *addrs,
guint n_addrs,
gpointer user_data);
SYSPROF_AVAILABLE_IN_ALL
SysprofCaptureWriter *sysprof_capture_writer_new_from_env (gsize buffer_size);
SysprofCaptureWriter *sysprof_capture_writer_new_from_env (gsize buffer_size);
SYSPROF_AVAILABLE_IN_ALL
SysprofCaptureWriter *sysprof_capture_writer_new (const gchar *filename,
gsize buffer_size);
SysprofCaptureWriter *sysprof_capture_writer_new (const gchar *filename,
gsize buffer_size);
SYSPROF_AVAILABLE_IN_ALL
SysprofCaptureWriter *sysprof_capture_writer_new_from_fd (int fd,
gsize buffer_size);
SysprofCaptureWriter *sysprof_capture_writer_new_from_fd (int fd,
gsize buffer_size);
SYSPROF_AVAILABLE_IN_ALL
gsize sysprof_capture_writer_get_buffer_size (SysprofCaptureWriter *self);
gsize sysprof_capture_writer_get_buffer_size (SysprofCaptureWriter *self);
SYSPROF_AVAILABLE_IN_ALL
SysprofCaptureWriter *sysprof_capture_writer_ref (SysprofCaptureWriter *self);
SysprofCaptureWriter *sysprof_capture_writer_ref (SysprofCaptureWriter *self);
SYSPROF_AVAILABLE_IN_ALL
void sysprof_capture_writer_unref (SysprofCaptureWriter *self);
void sysprof_capture_writer_unref (SysprofCaptureWriter *self);
SYSPROF_AVAILABLE_IN_ALL
void sysprof_capture_writer_stat (SysprofCaptureWriter *self,
SysprofCaptureStat *stat);
void sysprof_capture_writer_stat (SysprofCaptureWriter *self,
SysprofCaptureStat *stat);
SYSPROF_AVAILABLE_IN_ALL
void sysprof_capture_writer_set_flush_delay (SysprofCaptureWriter *self,
GMainContext *main_context,
guint timeout_seconds);
void sysprof_capture_writer_set_flush_delay (SysprofCaptureWriter *self,
GMainContext *main_context,
guint timeout_seconds);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_add_file (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *path,
gboolean is_last,
const guint8 *data,
gsize data_len);
gboolean sysprof_capture_writer_add_file (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *path,
gboolean is_last,
const guint8 *data,
gsize data_len);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_add_file_fd (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *path,
gint fd);
gboolean sysprof_capture_writer_add_file_fd (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *path,
gint fd);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_add_map (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
guint64 start,
guint64 end,
guint64 offset,
guint64 inode,
const gchar *filename);
gboolean sysprof_capture_writer_add_map (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
guint64 start,
guint64 end,
guint64 offset,
guint64 inode,
const gchar *filename);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_add_mark (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
guint64 duration,
const gchar *group,
const gchar *name,
const gchar *message);
gboolean sysprof_capture_writer_add_mark (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
guint64 duration,
const gchar *group,
const gchar *name,
const gchar *message);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_add_metadata (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *id,
const gchar *metadata,
gssize metadata_len);
gboolean sysprof_capture_writer_add_metadata (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *id,
const gchar *metadata,
gssize metadata_len);
SYSPROF_AVAILABLE_IN_ALL
guint64 sysprof_capture_writer_add_jitmap (SysprofCaptureWriter *self,
const gchar *name);
guint64 sysprof_capture_writer_add_jitmap (SysprofCaptureWriter *self,
const gchar *name);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_add_process (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *cmdline);
gboolean sysprof_capture_writer_add_process (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const gchar *cmdline);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_add_sample (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
gint32 tid,
const SysprofCaptureAddress *addrs,
guint n_addrs);
gboolean sysprof_capture_writer_add_sample (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
gint32 tid,
const SysprofCaptureAddress *addrs,
guint n_addrs);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_add_fork (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
gint32 child_pid);
gboolean sysprof_capture_writer_add_fork (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
gint32 child_pid);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_add_exit (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid);
gboolean sysprof_capture_writer_add_exit (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_add_timestamp (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid);
gboolean sysprof_capture_writer_add_timestamp (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_define_counters (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const SysprofCaptureCounter *counters,
guint n_counters);
gboolean sysprof_capture_writer_define_counters (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const SysprofCaptureCounter *counters,
guint n_counters);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_set_counters (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const guint *counters_ids,
const SysprofCaptureCounterValue *values,
guint n_counters);
gboolean sysprof_capture_writer_set_counters (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
const guint *counters_ids,
const SysprofCaptureCounterValue *values,
guint n_counters);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_add_log (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
GLogLevelFlags severity,
const gchar *domain,
const gchar *message);
gboolean sysprof_capture_writer_add_log (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
GLogLevelFlags severity,
const gchar *domain,
const gchar *message);
SYSPROF_AVAILABLE_IN_3_36
gboolean sysprof_capture_writer_add_allocation (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
gint32 tid,
SysprofCaptureAddress alloc_addr,
gint64 alloc_size,
SysprofBacktraceFunc backtrace_func,
gpointer backtrace_data);
SYSPROF_AVAILABLE_IN_3_36
gboolean sysprof_capture_writer_add_allocation_copy (SysprofCaptureWriter *self,
gint64 time,
gint cpu,
gint32 pid,
gint32 tid,
SysprofCaptureAddress alloc_addr,
gint64 alloc_size,
const SysprofCaptureAddress *addrs,
guint n_addrs);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_flush (SysprofCaptureWriter *self);
gboolean sysprof_capture_writer_flush (SysprofCaptureWriter *self);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_save_as (SysprofCaptureWriter *self,
const gchar *filename,
GError **error);
gboolean sysprof_capture_writer_save_as (SysprofCaptureWriter *self,
const gchar *filename,
GError **error);
SYSPROF_AVAILABLE_IN_ALL
guint sysprof_capture_writer_request_counter (SysprofCaptureWriter *self,
guint n_counters);
guint sysprof_capture_writer_request_counter (SysprofCaptureWriter *self,
guint n_counters);
SYSPROF_AVAILABLE_IN_ALL
SysprofCaptureReader *sysprof_capture_writer_create_reader (SysprofCaptureWriter *self,
GError **error);
SysprofCaptureReader *sysprof_capture_writer_create_reader (SysprofCaptureWriter *self,
GError **error);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_splice (SysprofCaptureWriter *self,
SysprofCaptureWriter *dest,
GError **error);
gboolean sysprof_capture_writer_splice (SysprofCaptureWriter *self,
SysprofCaptureWriter *dest,
GError **error);
SYSPROF_AVAILABLE_IN_ALL
gboolean sysprof_capture_writer_cat (SysprofCaptureWriter *self,
SysprofCaptureReader *reader,
GError **error);
gboolean sysprof_capture_writer_cat (SysprofCaptureWriter *self,
SysprofCaptureReader *reader,
GError **error);
G_GNUC_INTERNAL
gboolean _sysprof_capture_writer_splice_from_fd (SysprofCaptureWriter *self,
int fd,
GError **error) G_GNUC_INTERNAL;
gboolean _sysprof_capture_writer_splice_from_fd (SysprofCaptureWriter *self,
int fd,
GError **error) G_GNUC_INTERNAL;
G_GNUC_INTERNAL
gboolean _sysprof_capture_writer_set_time_range (SysprofCaptureWriter *self,
gint64 start_time,
gint64 end_time) G_GNUC_INTERNAL;
gboolean _sysprof_capture_writer_set_time_range (SysprofCaptureWriter *self,
gint64 start_time,
gint64 end_time) G_GNUC_INTERNAL;
G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureWriter, sysprof_capture_writer_unref)

View File

@ -19,6 +19,7 @@
<file preprocess="xml-stripblanks">sysprof-failed-state-view.ui</file>
<file preprocess="xml-stripblanks">sysprof-logs-page.ui</file>
<file preprocess="xml-stripblanks">sysprof-marks-page.ui</file>
<file preprocess="xml-stripblanks">sysprof-memprof-page.ui</file>
<file preprocess="xml-stripblanks">sysprof-process-model-row.ui</file>
<file preprocess="xml-stripblanks">sysprof-profiler-assistant.ui</file>
<file preprocess="xml-stripblanks">sysprof-recording-state-view.ui</file>

View File

@ -45,6 +45,9 @@ libsysprof_ui_private_sources = [
'sysprof-marks-page.c',
'sysprof-mark-visualizer.c',
'sysprof-memory-aid.c',
'sysprof-memprof-aid.c',
'sysprof-memprof-page.c',
'sysprof-memprof-visualizer.c',
'sysprof-netdev-aid.c',
'sysprof-procs-visualizer.c',
'sysprof-profiler-assistant.c',

View File

@ -42,6 +42,8 @@
#include "sysprof-diskstat-aid.h"
#include "sysprof-logs-aid.h"
#include "sysprof-marks-aid.h"
#include "sysprof-memory-aid.h"
#include "sysprof-memprof-aid.h"
#include "sysprof-netdev-aid.h"
#include "sysprof-rapl-aid.h"
@ -654,6 +656,8 @@ sysprof_display_present_async (SysprofDisplay *self,
g_ptr_array_add (aids, sysprof_diskstat_aid_new ());
g_ptr_array_add (aids, sysprof_logs_aid_new ());
g_ptr_array_add (aids, sysprof_marks_aid_new ());
g_ptr_array_add (aids, sysprof_memory_aid_new ());
g_ptr_array_add (aids, sysprof_memprof_aid_new ());
g_ptr_array_add (aids, sysprof_netdev_aid_new ());
g_ptr_array_add (aids, sysprof_rapl_aid_new ());

View File

@ -33,15 +33,6 @@ struct _SysprofMemoryAid
G_DEFINE_TYPE (SysprofMemoryAid, sysprof_memory_aid, SYSPROF_TYPE_AID)
/**
* sysprof_memory_aid_new:
*
* Create a new #SysprofMemoryAid.
*
* Returns: (transfer full): a newly created #SysprofMemoryAid
*
* Since: 3.34
*/
SysprofAid *
sysprof_memory_aid_new (void)
{
@ -50,7 +41,7 @@ sysprof_memory_aid_new (void)
static void
sysprof_memory_aid_prepare (SysprofAid *self,
SysprofProfiler *profiler)
SysprofProfiler *profiler)
{
#ifdef __linux__
g_autoptr(SysprofSource) source = NULL;

View File

@ -0,0 +1,226 @@
/* sysprof-memprof-aid.c
*
* Copyright 2019 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
*/
#define G_LOG_DOMAIN "sysprof-memprof-aid"
#include "config.h"
#include <glib/gi18n.h>
#include "sysprof-memprof-aid.h"
#include "sysprof-memprof-page.h"
#include "sysprof-memprof-source.h"
#include "sysprof-memprof-visualizer.h"
struct _SysprofMemprofAid
{
SysprofAid parent_instance;
};
G_DEFINE_TYPE (SysprofMemprofAid, sysprof_memprof_aid, SYSPROF_TYPE_AID)
typedef struct
{
SysprofCaptureCursor *cursor;
SysprofDisplay *display;
guint has_allocs : 1;
} Present;
static void
present_free (gpointer data)
{
Present *p = data;
g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref);
g_clear_object (&p->display);
g_slice_free (Present, p);
}
static void
on_group_activated_cb (SysprofVisualizerGroup *group,
SysprofPage *page)
{
SysprofDisplay *display;
g_assert (SYSPROF_IS_VISUALIZER_GROUP (group));
g_assert (SYSPROF_IS_PAGE (page));
display = SYSPROF_DISPLAY (gtk_widget_get_ancestor (GTK_WIDGET (page), SYSPROF_TYPE_DISPLAY));
sysprof_display_set_visible_page (display, page);
}
SysprofAid *
sysprof_memprof_aid_new (void)
{
return g_object_new (SYSPROF_TYPE_MEMPROF_AID, NULL);
}
static void
sysprof_memprof_aid_prepare (SysprofAid *self,
SysprofProfiler *profiler)
{
#ifdef __linux__
g_autoptr(SysprofSource) source = NULL;
g_assert (SYSPROF_IS_MEMPROF_AID (self));
g_assert (SYSPROF_IS_PROFILER (profiler));
source = sysprof_memprof_source_new ();
sysprof_profiler_add_source (profiler, source);
#endif
}
static gboolean
discover_samples_cb (const SysprofCaptureFrame *frame,
gpointer user_data)
{
Present *p = user_data;
g_assert (frame != NULL);
g_assert (p != NULL);
if (frame->type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
{
p->has_allocs = TRUE;
return FALSE;
}
return TRUE;
}
static void
sysprof_memprof_aid_present_worker (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
Present *p = task_data;
g_assert (G_IS_TASK (task));
g_assert (SYSPROF_IS_MEMPROF_AID (source_object));
g_assert (p != NULL);
g_assert (p->cursor != NULL);
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
sysprof_capture_cursor_foreach (p->cursor, discover_samples_cb, p);
g_task_return_boolean (task, TRUE);
}
static void
sysprof_memprof_aid_present_async (SysprofAid *aid,
SysprofCaptureReader *reader,
SysprofDisplay *display,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_ALLOCATION };
g_autoptr(SysprofCaptureCondition) condition = NULL;
g_autoptr(SysprofCaptureCursor) cursor = NULL;
g_autoptr(GTask) task = NULL;
Present present;
g_assert (SYSPROF_IS_MEMPROF_AID (aid));
g_assert (reader != NULL);
g_assert (SYSPROF_IS_DISPLAY (display));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
condition = sysprof_capture_condition_new_where_type_in (1, types);
cursor = sysprof_capture_cursor_new (reader);
sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition));
present.cursor = g_steal_pointer (&cursor);
present.display = g_object_ref (display);
task = g_task_new (aid, cancellable, callback, user_data);
g_task_set_source_tag (task, sysprof_memprof_aid_present_async);
g_task_set_task_data (task,
g_slice_dup (Present, &present),
present_free);
g_task_run_in_thread (task, sysprof_memprof_aid_present_worker);
}
static gboolean
sysprof_memprof_aid_present_finish (SysprofAid *aid,
GAsyncResult *result,
GError **error)
{
Present *p;
g_assert (SYSPROF_IS_MEMPROF_AID (aid));
g_assert (G_IS_TASK (result));
p = g_task_get_task_data (G_TASK (result));
if (p->has_allocs)
{
SysprofVisualizerGroup *group;
SysprofVisualizer *row;
SysprofPage *page;
group = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP,
"can-focus", TRUE,
"has-page", TRUE,
"priority", -300,
"title", _("Memory"),
"visible", TRUE,
NULL);
row = sysprof_memprof_visualizer_new (FALSE);
sysprof_visualizer_group_insert (group, row, 0, FALSE);
row = sysprof_memprof_visualizer_new (TRUE);
sysprof_visualizer_group_insert (group, row, 1, FALSE);
page = g_object_new (SYSPROF_TYPE_MEMPROF_PAGE,
"title", _("Memory Allocations"),
"vexpand", TRUE,
"visible", TRUE,
NULL);
sysprof_display_add_page (p->display, page);
g_signal_connect_object (group,
"group-activated",
G_CALLBACK (on_group_activated_cb),
page,
0);
sysprof_display_add_group (p->display, group);
}
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
sysprof_memprof_aid_class_init (SysprofMemprofAidClass *klass)
{
SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass);
aid_class->prepare = sysprof_memprof_aid_prepare;
aid_class->present_async = sysprof_memprof_aid_present_async;
aid_class->present_finish = sysprof_memprof_aid_present_finish;
}
static void
sysprof_memprof_aid_init (SysprofMemprofAid *self)
{
sysprof_aid_set_display_name (SYSPROF_AID (self), _("Track Allocations"));
sysprof_aid_set_icon_name (SYSPROF_AID (self), "org.gnome.Sysprof-symbolic");
}

View File

@ -0,0 +1,33 @@
/* sysprof-memprof-aid.h
*
* Copyright 2019 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
*/
#pragma once
#include "sysprof-aid.h"
G_BEGIN_DECLS
#define SYSPROF_TYPE_MEMPROF_AID (sysprof_memprof_aid_get_type())
G_DECLARE_FINAL_TYPE (SysprofMemprofAid, sysprof_memprof_aid, SYSPROF, MEMPROF_AID, SysprofAid)
SysprofAid *sysprof_memprof_aid_new (void);
G_END_DECLS

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
/* sysprof-memprof-page.h
*
* Copyright 2016-2019 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
*/
#pragma once
#include <gtk/gtk.h>
#include <sysprof.h>
#include "sysprof-page.h"
G_BEGIN_DECLS
#define SYSPROF_TYPE_MEMPROF_PAGE (sysprof_memprof_page_get_type())
G_DECLARE_DERIVABLE_TYPE (SysprofMemprofPage, sysprof_memprof_page, SYSPROF, MEMPROF_PAGE, SysprofPage)
struct _SysprofMemprofPageClass
{
SysprofPageClass parent_class;
void (*go_previous) (SysprofMemprofPage *self);
/*< private >*/
gpointer _reserved[16];
};
GtkWidget *sysprof_memprof_page_new (void);
SysprofMemprofProfile *sysprof_memprof_page_get_profile (SysprofMemprofPage *self);
void sysprof_memprof_page_set_profile (SysprofMemprofPage *self,
SysprofMemprofProfile *profile);
gchar *sysprof_memprof_page_screenshot (SysprofMemprofPage *self);
guint sysprof_memprof_page_get_n_functions (SysprofMemprofPage *self);
G_END_DECLS

View File

@ -0,0 +1,232 @@
<interface>
<template class="SysprofMemprofPage" parent="SysprofPage">
<child>
<object class="GtkStack" id="stack">
<property name="visible">true</property>
<child>
<object class="GtkPaned">
<property name="orientation">horizontal</property>
<property name="position">450</property>
<property name="visible">true</property>
<child>
<object class="GtkPaned">
<property name="orientation">vertical</property>
<property name="visible">true</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">true</property>
<child>
<object class="GtkTreeView" id="functions_view">
<property name="fixed-height-mode">true</property>
<property name="visible">true</property>
<child>
<object class="GtkTreeViewColumn" id="function_name_column">
<property name="expand">true</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">0</property>
<property name="title" translatable="yes">Functions</property>
<child>
<object class="GtkCellRendererText">
<property name="ellipsize">middle</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="function_self_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">1</property>
<property name="title" translatable="yes">Self</property>
<child>
<object class="SysprofCellRendererPercent">
<property name="width">65</property>
</object>
<attributes>
<attribute name="percent">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="function_total_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">2</property>
<property name="title" translatable="yes">Total</property>
<child>
<object class="SysprofCellRendererPercent">
<property name="width">65</property>
</object>
<attributes>
<attribute name="percent">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="resize">true</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">true</property>
<child>
<object class="GtkTreeView" id="callers_view">
<property name="visible">true</property>
<child>
<object class="GtkTreeViewColumn" id="callers_name_column">
<property name="expand">true</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">0</property>
<property name="title" translatable="yes">Callers</property>
<child>
<object class="GtkCellRendererText">
<property name="ellipsize">middle</property>
</object>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="callers_self_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">1</property>
<property name="title" translatable="yes">Self</property>
<child>
<object class="SysprofCellRendererPercent">
<property name="width">65</property>
</object>
<attributes>
<attribute name="percent">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="callers_total_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">2</property>
<property name="title" translatable="yes">Total</property>
<child>
<object class="SysprofCellRendererPercent">
<property name="width">65</property>
</object>
<attributes>
<attribute name="percent">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="resize">true</property>
</packing>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">true</property>
<child>
<object class="GtkTreeView" id="descendants_view">
<property name="visible">true</property>
<child>
<object class="GtkTreeViewColumn" id="descendants_name_column">
<property name="expand">true</property>
<property name="sizing">autosize</property>
<property name="sort-column-id">0</property>
<property name="title" translatable="yes">Descendants</property>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="descendants_self_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">1</property>
<property name="title" translatable="yes">Self</property>
<child>
<object class="SysprofCellRendererPercent">
<property name="width">65</property>
</object>
<attributes>
<attribute name="percent">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="descendants_total_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="sort-column-id">2</property>
<property name="title" translatable="yes">Total</property>
<child>
<object class="SysprofCellRendererPercent">
<property name="width">65</property>
</object>
<attributes>
<attribute name="percent">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="function_size_column">
<property name="expand">false</property>
<property name="sizing">fixed</property>
<property name="title" translatable="yes">Size</property>
<child>
<object class="GtkCellRendererText" id="function_size_cell">
<property name="xalign">1.0</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="name">callgraph</property>
</packing>
</child>
<child>
<object class="DzlEmptyState">
<property name="icon-name">content-loading-symbolic</property>
<property name="title" translatable="yes">Generating Callgraph</property>
<property name="subtitle" translatable="yes">Sysprof is busy creating the selected callgraph.</property>
<property name="visible">true</property>
</object>
<packing>
<property name="name">loading</property>
</packing>
</child>
<child>
<object class="DzlEmptyState">
<property name="icon-name">computer-fail-symbolic</property>
<property name="title" translatable="yes">Not Enough Samples</property>
<property name="subtitle" translatable="yes">More samples are necessary to display a callgraph.</property>
<property name="visible">false</property>
</object>
<packing>
<property name="name">empty-state</property>
</packing>
</child>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,614 @@
/* sysprof-memprof-visualizer.c
*
* Copyright 2020 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"
#define G_LOG_DOMAIN "sysprof-memprof-visualizer"
#include <glib/gi18n.h>
#include <math.h>
#include <stddef.h>
#include "rax.h"
#include "sysprof-memprof-visualizer.h"
typedef struct
{
cairo_surface_t *surface;
SysprofCaptureReader *reader;
rax *rax;
GtkAllocation alloc;
gint64 begin_time;
gint64 duration;
gint64 total_alloc;
gint64 max_alloc;
GdkRGBA fg;
GdkRGBA fg2;
guint scale;
} DrawContext;
struct _SysprofMemprofVisualizer
{
SysprofVisualizer parent_instance;
SysprofCaptureReader *reader;
GCancellable *cancellable;
cairo_surface_t *surface;
gint surface_w;
gint surface_h;
guint queued_draw;
gint64 begin_time;
gint64 duration;
gint64 cached_total_alloc;
gint64 cached_max_alloc;
guint mode : 1;
};
enum {
MODE_ALLOCS,
MODE_TOTAL,
};
G_DEFINE_TYPE (SysprofMemprofVisualizer, sysprof_memprof_visualizer, SYSPROF_TYPE_VISUALIZER)
static void
draw_context_free (DrawContext *draw)
{
g_clear_pointer (&draw->reader, sysprof_capture_reader_unref);
g_clear_pointer (&draw->surface, cairo_surface_destroy);
g_clear_pointer (&draw->rax, raxFree);
g_slice_free (DrawContext, draw);
}
static void
sysprof_memprof_visualizer_set_reader (SysprofVisualizer *visualizer,
SysprofCaptureReader *reader)
{
SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)visualizer;
g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self));
if (reader == self->reader)
return;
g_clear_pointer (&self->reader, sysprof_capture_reader_unref);
self->reader = sysprof_capture_reader_ref (reader);
self->begin_time = sysprof_capture_reader_get_start_time (reader);
self->duration = sysprof_capture_reader_get_end_time (reader)
- sysprof_capture_reader_get_start_time (reader);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
SysprofVisualizer *
sysprof_memprof_visualizer_new (gboolean total_allocs)
{
SysprofMemprofVisualizer *self;
self = g_object_new (SYSPROF_TYPE_MEMPROF_VISUALIZER,
"title", total_allocs ? _("Memory Used") : _("Memory Allocations"),
"height-request", 35,
"visible", TRUE,
NULL);
if (total_allocs)
self->mode = MODE_TOTAL;
else
self->mode = MODE_ALLOCS;
return SYSPROF_VISUALIZER (self);
}
static guint64
get_max_alloc (SysprofCaptureReader *reader)
{
SysprofCaptureFrameType type;
gint64 ret = 0;
while (sysprof_capture_reader_peek_type (reader, &type))
{
const SysprofCaptureAllocation *ev;
if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
{
if (!(ev = sysprof_capture_reader_read_allocation (reader)))
break;
if (ev->alloc_size > ret)
ret = ev->alloc_size;
}
else
{
if (!sysprof_capture_reader_skip (reader))
break;
continue;
}
}
sysprof_capture_reader_reset (reader);
return ret;
}
static guint64
get_total_alloc (SysprofCaptureReader *reader)
{
SysprofCaptureFrameType type;
guint64 total = 0;
guint64 max = 0;
rax *r;
r = raxNew ();
while (sysprof_capture_reader_peek_type (reader, &type))
{
const SysprofCaptureAllocation *ev;
if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
{
if (!(ev = sysprof_capture_reader_read_allocation (reader)))
break;
if (ev->alloc_size > 0)
{
raxInsert (r,
(guint8 *)&ev->alloc_addr,
sizeof ev->alloc_addr,
GSIZE_TO_POINTER (ev->alloc_size),
NULL);
total += ev->alloc_size;
if (total > max)
max = total;
}
else
{
gpointer res = raxFind (r, (guint8 *)&ev->alloc_addr, sizeof ev->alloc_addr);
if (res != raxNotFound)
{
total -= GPOINTER_TO_SIZE (res);
raxRemove (r,
(guint8 *)&ev->alloc_addr,
sizeof ev->alloc_addr,
NULL);
}
}
}
else
{
if (!sysprof_capture_reader_skip (reader))
break;
continue;
}
}
sysprof_capture_reader_reset (reader);
raxFree (r);
return max;
}
static void
draw_total_worker (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
SysprofCaptureFrameType type;
DrawContext *draw = task_data;
gint64 total = 0;
cairo_t *cr;
rax *r;
gint x = 0;
g_assert (G_IS_TASK (task));
g_assert (draw != NULL);
g_assert (draw->surface != NULL);
g_assert (draw->reader != NULL);
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
if (draw->total_alloc == 0)
draw->total_alloc = get_total_alloc (draw->reader);
r = raxNew ();
/* To avoid sorting, this code assums that all allocation information
* is sorted and in order. Generally this is the case, but a crafted
* syscap file could break it on purpose if they tried.
*/
cr = cairo_create (draw->surface);
cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
cairo_set_source_rgb (cr, 0, 0, 0);
while (sysprof_capture_reader_peek_type (draw->reader, &type))
{
const SysprofCaptureAllocation *ev;
gint y;
if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
{
if (!(ev = sysprof_capture_reader_read_allocation (draw->reader)))
break;
if (ev->alloc_size > 0)
{
raxInsert (r,
(guint8 *)&ev->alloc_addr,
sizeof ev->alloc_addr,
GSIZE_TO_POINTER (ev->alloc_size),
NULL);
total += ev->alloc_size;
}
else
{
gpointer res = raxFind (r, (guint8 *)&ev->alloc_addr, sizeof ev->alloc_addr);
if (res != raxNotFound)
{
total -= GPOINTER_TO_SIZE (res);
raxRemove (r,
(guint8 *)&ev->alloc_addr,
sizeof ev->alloc_addr,
NULL);
}
}
}
else
{
if (!sysprof_capture_reader_skip (draw->reader))
break;
continue;
}
x = (ev->frame.time - draw->begin_time) / (gdouble)draw->duration * draw->alloc.width;
y = draw->alloc.height - ((gdouble)total / (gdouble)draw->total_alloc * (gdouble)draw->alloc.height);
cairo_rectangle (cr, x, y, 1, 1);
cairo_fill (cr);
}
cairo_destroy (cr);
g_task_return_boolean (task, TRUE);
raxFree (r);
}
static void
draw_alloc_worker (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
static const gdouble dashes[] = { 1.0, 2.0 };
DrawContext *draw = task_data;
SysprofCaptureFrameType type;
GdkRGBA *last;
GdkRGBA mid;
cairo_t *cr;
guint counter = 0;
gint midpt;
gdouble log_max;
g_assert (G_IS_TASK (task));
g_assert (draw != NULL);
g_assert (draw->surface != NULL);
g_assert (draw->reader != NULL);
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
if (draw->max_alloc == 0)
draw->max_alloc = get_max_alloc (draw->reader);
log_max = log10 (draw->max_alloc);
midpt = draw->alloc.height / 2;
cr = cairo_create (draw->surface);
/* Draw mid-point line */
mid = draw->fg;
mid.alpha *= 0.4;
cairo_set_line_width (cr, 1.0);
gdk_cairo_set_source_rgba (cr, &mid);
cairo_move_to (cr, 0, midpt);
cairo_line_to (cr, draw->alloc.width, midpt);
cairo_set_dash (cr, dashes, G_N_ELEMENTS (dashes), 0);
cairo_stroke (cr);
cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
gdk_cairo_set_source_rgba (cr, &draw->fg);
last = &draw->fg;
/* Now draw data points */
while (sysprof_capture_reader_peek_type (draw->reader, &type))
{
const SysprofCaptureAllocation *ev;
gint64 size;
gdouble l;
gint x;
gint y;
/* Cancellation check every 1000 frames */
if G_UNLIKELY (++counter == 1000)
{
if (g_task_return_error_if_cancelled (task))
{
cairo_destroy (cr);
return;
}
counter = 0;
}
/* We only care about memory frames here */
if (type != SYSPROF_CAPTURE_FRAME_ALLOCATION)
{
if (!sysprof_capture_reader_skip (draw->reader))
break;
continue;
}
if (!(ev = sysprof_capture_reader_read_allocation (draw->reader)))
break;
if (ev->alloc_size > 0)
{
size = ev->alloc_size;
raxInsert (draw->rax, (guint8 *)&ev->alloc_addr, sizeof ev->alloc_addr, GSIZE_TO_POINTER (size), NULL);
if (last != &draw->fg)
{
gdk_cairo_set_source_rgba (cr, &draw->fg);
last = &draw->fg;
}
}
else
{
size = GPOINTER_TO_SIZE (raxFind (draw->rax, (guint8 *)&ev->alloc_addr, sizeof ev->alloc_addr));
if (size)
raxRemove (draw->rax, (guint8 *)&ev->alloc_addr, sizeof ev->alloc_addr, NULL);
if (last != &draw->fg2)
{
gdk_cairo_set_source_rgba (cr, &draw->fg2);
last = &draw->fg2;
}
}
l = log10 (size);
x = (ev->frame.time - draw->begin_time) / (gdouble)draw->duration * draw->alloc.width;
if (ev->alloc_size > 0)
y = midpt - ((l / log_max) * midpt);
else
y = midpt + ((l / log_max) * midpt);
/* Fill immediately instead of batching draws so that
* we don't take a lot of memory to hold on to the
* path while drawing.
*/
cairo_rectangle (cr, x, y, 1, 1);
cairo_fill (cr);
}
cairo_destroy (cr);
g_task_return_boolean (task, TRUE);
}
static void
draw_finished (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(SysprofMemprofVisualizer) self = user_data;
g_autoptr(GError) error = NULL;
g_assert (object == NULL);
g_assert (G_IS_TASK (result));
g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self));
if (g_task_propagate_boolean (G_TASK (result), &error))
{
DrawContext *draw = g_task_get_task_data (G_TASK (result));
g_clear_pointer (&self->surface, cairo_surface_destroy);
self->surface = g_steal_pointer (&draw->surface);
self->surface_w = draw->alloc.width;
self->surface_h = draw->alloc.height;
self->cached_max_alloc = draw->max_alloc;
self->cached_total_alloc = draw->total_alloc;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
static gboolean
sysprof_memprof_visualizer_begin_draw (SysprofMemprofVisualizer *self)
{
g_autoptr(GTask) task = NULL;
GtkAllocation alloc;
DrawContext *draw;
g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self));
self->queued_draw = 0;
/* Make sure we even need to draw */
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
if (self->reader == NULL ||
!gtk_widget_get_visible (GTK_WIDGET (self)) ||
!gtk_widget_get_mapped (GTK_WIDGET (self)) ||
alloc.width == 0 || alloc.height == 0)
return G_SOURCE_REMOVE;
/* Some GPUs (Intel) cannot deal with graphics textures larger than
* 8000x8000. So here we are going to cheat a bit and just use that as our
* max, and scale when drawing. The biggest issue here is that long term we
* need a tiling solution that lets us render lots of tiles and then draw
* them as necessary.
*/
if (alloc.width > 8000)
alloc.width = 8000;
draw = g_slice_new0 (DrawContext);
draw->rax = raxNew ();
draw->alloc.width = alloc.width;
draw->alloc.height = alloc.height;
draw->reader = sysprof_capture_reader_copy (self->reader);
draw->begin_time = self->begin_time;
draw->duration = self->duration;
draw->scale = gtk_widget_get_scale_factor (GTK_WIDGET (self));
draw->max_alloc = self->cached_max_alloc;
draw->total_alloc = self->cached_total_alloc;
gdk_rgba_parse (&draw->fg, "rgba(246,97,81,1)");
gdk_rgba_parse (&draw->fg2, "rgba(245,194,17,1)");
draw->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
alloc.width * draw->scale,
alloc.height * draw->scale);
cairo_surface_set_device_scale (draw->surface, draw->scale, draw->scale);
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->cancellable);
self->cancellable = g_cancellable_new ();
task = g_task_new (NULL, self->cancellable, draw_finished, g_object_ref (self));
g_task_set_source_tag (task, sysprof_memprof_visualizer_begin_draw);
g_task_set_task_data (task, g_steal_pointer (&draw), (GDestroyNotify)draw_context_free);
if (self->mode == MODE_ALLOCS)
g_task_run_in_thread (task, draw_alloc_worker);
else
g_task_run_in_thread (task, draw_total_worker);
return G_SOURCE_REMOVE;
}
static void
sysprof_memprof_visualizer_queue_redraw (SysprofMemprofVisualizer *self)
{
g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self));
if (self->queued_draw == 0)
self->queued_draw = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
(GSourceFunc) sysprof_memprof_visualizer_begin_draw,
g_object_ref (self),
g_object_unref);
}
static void
sysprof_memprof_visualizer_size_allocate (GtkWidget *widget,
GtkAllocation *alloc)
{
SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)widget;
g_assert (GTK_IS_WIDGET (widget));
g_assert (alloc != NULL);
GTK_WIDGET_CLASS (sysprof_memprof_visualizer_parent_class)->size_allocate (widget, alloc);
sysprof_memprof_visualizer_queue_redraw (self);
}
static void
sysprof_memprof_visualizer_destroy (GtkWidget *widget)
{
SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)widget;
g_clear_pointer (&self->reader, sysprof_capture_reader_unref);
g_clear_pointer (&self->surface, cairo_surface_destroy);
g_clear_handle_id (&self->queued_draw, g_source_remove);
GTK_WIDGET_CLASS (sysprof_memprof_visualizer_parent_class)->destroy (widget);
}
static gboolean
sysprof_memprof_visualizer_draw (GtkWidget *widget,
cairo_t *cr)
{
SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)widget;
gboolean ret;
g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self));
g_assert (cr != NULL);
ret = GTK_WIDGET_CLASS (sysprof_memprof_visualizer_parent_class)->draw (widget, cr);
if (self->surface != NULL)
{
GtkAllocation alloc;
gtk_widget_get_allocation (widget, &alloc);
cairo_save (cr);
cairo_rectangle (cr, 0, 0, alloc.width, alloc.height);
/* We might be drawing an updated image in the background, and this
* will take our current surface (which is the wrong size) and draw
* it stretched to fit the allocation. That gives us *something* that
* represents the end result even if it is a bit blurry in the mean
* time. Allocators take a while to render anyway.
*/
if (self->surface_w != alloc.width || self->surface_h != alloc.height)
{
cairo_scale (cr,
(gdouble)alloc.width / (gdouble)self->surface_w,
(gdouble)alloc.height / (gdouble)self->surface_h);
}
cairo_set_source_surface (cr, self->surface, 0, 0);
cairo_paint (cr);
cairo_restore (cr);
}
return ret;
}
static void
sysprof_memprof_visualizer_class_init (SysprofMemprofVisualizerClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
SysprofVisualizerClass *visualizer_class = SYSPROF_VISUALIZER_CLASS (klass);
widget_class->destroy = sysprof_memprof_visualizer_destroy;
widget_class->draw = sysprof_memprof_visualizer_draw;
widget_class->size_allocate = sysprof_memprof_visualizer_size_allocate;
visualizer_class->set_reader = sysprof_memprof_visualizer_set_reader;
}
static void
sysprof_memprof_visualizer_init (SysprofMemprofVisualizer *self)
{
}

View File

@ -0,0 +1,33 @@
/* sysprof-memprof-visualizer.h
*
* Copyright 2020 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
*/
#pragma once
#include "sysprof-visualizer.h"
G_BEGIN_DECLS
#define SYSPROF_TYPE_MEMPROF_VISUALIZER (sysprof_memprof_visualizer_get_type())
G_DECLARE_FINAL_TYPE (SysprofMemprofVisualizer, sysprof_memprof_visualizer, SYSPROF, MEMPROF_VISUALIZER, SysprofVisualizer)
SysprofVisualizer *sysprof_memprof_visualizer_new (gboolean total_allocs);
G_END_DECLS

View File

@ -41,6 +41,7 @@
#include "sysprof-callgraph-aid.h"
#include "sysprof-cpu-aid.h"
#include "sysprof-memory-aid.h"
#include "sysprof-memprof-aid.h"
#include "sysprof-netdev-aid.h"
#include "sysprof-proxy-aid.h"
#include "sysprof-rapl-aid.h"
@ -397,6 +398,7 @@ sysprof_profiler_assistant_class_init (SysprofProfilerAssistantClass *klass)
g_type_ensure (SYSPROF_TYPE_DISKSTAT_SOURCE);
g_type_ensure (SYSPROF_TYPE_ENVIRON_EDITOR);
g_type_ensure (SYSPROF_TYPE_MEMORY_AID);
g_type_ensure (SYSPROF_TYPE_MEMPROF_AID);
g_type_ensure (SYSPROF_TYPE_NETDEV_AID);
g_type_ensure (SYSPROF_TYPE_PROXY_AID);
g_type_ensure (SYSPROF_TYPE_RAPL_AID);

View File

@ -3,6 +3,7 @@
<requires lib="gtk+" version="3.22"/>
<object class="SysprofCpuAid" id="cpu_aid"/>
<object class="SysprofMemoryAid" id="memory_aid"/>
<object class="SysprofMemprofAid" id="memprof_aid"/>
<object class="SysprofCallgraphAid" id="callgraph_aid"/>
<object class="SysprofNetdevAid" id="network_aid"/>
<object class="SysprofRaplAid" id="rapl_aid"/>
@ -178,6 +179,14 @@
<property name="visible">true</property>
</object>
</child>
<child>
<object class="SysprofAidIcon">
<property name="tooltip-text" translatable="yes">Track application memory allocations (Sysprof must launch target application)</property>
<property name="aid">memprof_aid</property>
<property name="selected">false</property>
<property name="visible">true</property>
</object>
</child>
</object>
</child>
</object>

View File

@ -29,6 +29,9 @@ G_BEGIN_DECLS
void _sysprof_callgraph_page_set_failed (SysprofCallgraphPage *self);
void _sysprof_callgraph_page_set_loading (SysprofCallgraphPage *self,
gboolean loading);
void _sysprof_memory_page_set_failed (SysprofCallgraphPage *self);
void _sysprof_memory_page_set_loading (SysprofCallgraphPage *self,
gboolean loading);
void _sysprof_display_focus_record (SysprofDisplay *self);
void _sysprof_profiler_assistant_focus_record (SysprofProfilerAssistant *self);
gchar *_sysprof_format_duration (gint64 duration);

View File

@ -16,6 +16,8 @@ libsysprof_public_sources = [
'sysprof-kernel-symbol.c',
'sysprof-kernel-symbol-resolver.c',
'sysprof-local-profiler.c',
'sysprof-memprof-profile.c',
'sysprof-memprof-source.c',
'sysprof-netdev-source.c',
'sysprof-process-model.c',
'sysprof-process-model-item.c',
@ -45,6 +47,8 @@ libsysprof_public_headers = [
'sysprof-kernel-symbol.h',
'sysprof-kernel-symbol-resolver.h',
'sysprof-local-profiler.h',
'sysprof-memprof-profile.h',
'sysprof-memprof-source.h',
'sysprof-process-model.h',
'sysprof-process-model-item.h',
'sysprof-profile.h',
@ -79,10 +83,23 @@ libsysprof_private_sources = [
libsysprof_public_sources += libsysprof_capture_sources
librax = static_library('rax', ['rax.c'],
c_args: [ '-Wno-declaration-after-statement',
'-Wno-format-nonliteral',
'-Wno-shadow' ],
)
librax_dep = declare_dependency(
link_whole: librax,
include_directories: include_directories('.'),
)
libsysprof_deps = [
libsysprof_capture_deps,
gio_dep,
gio_unix_dep,
polkit_dep,
librax_dep,
]
if host_machine.system() == 'linux'
@ -146,4 +163,6 @@ pkgconfig.generate(
install_headers(libsysprof_public_headers, subdir: sysprof_header_subdir)
subdir('preload')
endif

View File

@ -0,0 +1,16 @@
libsysprof_memory_preload_deps = [
cc.find_library('dl', required: false),
libsysprof_capture_dep,
libunwind_dep,
]
libsysprof_memory_preload_sources = [
'sysprof-memory-collector.c',
]
libsysprof_memory_preload = shared_library('sysprof-memory-@0@'.format(libsysprof_api_version),
libsysprof_memory_preload_sources,
dependencies: libsysprof_memory_preload_deps,
install: true,
install_dir: get_option('libexecdir'),
)

View File

@ -0,0 +1,295 @@
#define _GNU_SOURCE
#include "config.h"
#include <dlfcn.h>
#ifdef HAVE_EXECINFO_H
# include <execinfo.h>
#endif
#ifdef ENABLE_LIBUNWIND
# include <libunwind.h>
#endif
#include <sched.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sysprof-capture.h>
#include <unistd.h>
typedef void *(* RealMalloc) (size_t);
typedef void (* RealFree) (void *);
typedef void *(* RealCalloc) (size_t, size_t);
typedef void *(* RealRealloc) (void *, size_t);
typedef void *(* RealAlignedAlloc) (size_t, size_t);
typedef int (* RealPosixMemalign) (void **, size_t, size_t);
typedef void *(* RealMemalign) (size_t, size_t);
typedef struct
{
char buf[4092];
int off;
} ScratchAlloc;
static void hook_memtable (void);
static void *scratch_malloc (size_t);
static void *scratch_realloc (void *, size_t);
static void *scratch_calloc (size_t, size_t);
static void scratch_free (void *);
static G_LOCK_DEFINE (writer);
static SysprofCaptureWriter *writer;
static int hooked;
static int pid;
static ScratchAlloc scratch;
static RealCalloc real_calloc = scratch_calloc;
static RealFree real_free = scratch_free;
static RealMalloc real_malloc = scratch_malloc;
static RealRealloc real_realloc = scratch_realloc;
static RealAlignedAlloc real_aligned_alloc;
static RealPosixMemalign real_posix_memalign;
static RealMemalign real_memalign;
static guint
backtrace_func (SysprofCaptureAddress *addrs,
guint n_addrs,
gpointer user_data)
{
#if defined(ENABLE_LIBUNWIND)
unw_context_t uc;
unw_cursor_t cursor;
unw_word_t ip;
unw_getcontext (&uc);
unw_init_local (&cursor, &uc);
/* Skip past caller frames */
if (unw_step (&cursor) > 0 && unw_step (&cursor) > 0)
{
guint n = 0;
/* Now walk the stack frames back */
while (n < n_addrs && unw_step (&cursor) > 0)
{
unw_get_reg (&cursor, UNW_REG_IP, &ip);
addrs[n++] = ip;
}
return n;
}
return 0;
#elif defined(HAVE_EXECINFO_H)
# if GLIB_SIZEOF_VOID_P == 8
return backtrace ((void **)addrs, n_addrs);
# else /* GLIB_SIZEOF_VOID_P != 8 */
void **stack = alloca (n_addrs * sizeof (gpointer));
guint n = backtrace (stack, n_addrs);
for (guint i = 0; i < n; i++)
addrs[i] = GPOINTER_TO_SIZE (stack[i]);
return n;
# endif /* GLIB_SIZEOF_VOID_P */
#else
return 0;
#endif
}
static void *
scratch_malloc (size_t size)
{
hook_memtable ();
return real_malloc (size);
}
static void *
scratch_realloc (void *ptr,
size_t size)
{
hook_memtable ();
return real_realloc (ptr, size);
}
static void *
scratch_calloc (size_t nmemb,
size_t size)
{
void *ret;
/* re-entrant, but forces early hook in case calloc is
* called before any of our other hooks.
*/
if (!hooked)
hook_memtable ();
size *= nmemb;
ret = &scratch.buf[scratch.off];
scratch.off += size;
return ret;
}
static void
scratch_free (void *ptr)
{
if ((char *)ptr >= scratch.buf && (char *)ptr < scratch.buf + scratch.off)
return;
}
static void
flush_writer (void)
{
G_LOCK (writer);
sysprof_capture_writer_flush (writer);
G_UNLOCK (writer);
}
static void
hook_memtable (void)
{
const gchar *env;
if (hooked)
return;
hooked = 1;
real_calloc = dlsym (RTLD_NEXT, "calloc");
real_free = dlsym (RTLD_NEXT, "free");
real_malloc = dlsym (RTLD_NEXT, "malloc");
real_realloc = dlsym (RTLD_NEXT, "realloc");
real_aligned_alloc = dlsym (RTLD_NEXT, "aligned_alloc");
real_posix_memalign = dlsym (RTLD_NEXT, "posix_memalign");
real_memalign = dlsym (RTLD_NEXT, "memalign");
unsetenv ("LD_PRELOAD");
pid = getpid ();
/* TODO: We want an API that let's us create a new writer
* per-thread instead of something like this (or using an
* environment variable). That will require a control channel
* to sysprof to request new writer/muxed APIs.
*/
env = getenv ("MEMPROF_TRACE_FD");
if (env != NULL)
{
int fd = atoi (env);
if (fd > 0)
writer = sysprof_capture_writer_new_from_fd (fd, 0);
}
if (writer == NULL)
writer = sysprof_capture_writer_new ("memory.syscap", 0);
atexit (flush_writer);
}
#define gettid() syscall(__NR_gettid, 0)
static inline void
track_malloc (void *ptr,
size_t size)
{
if G_UNLIKELY (!writer)
return;
G_LOCK (writer);
sysprof_capture_writer_add_allocation (writer,
SYSPROF_CAPTURE_CURRENT_TIME,
sched_getcpu (),
pid,
gettid(),
GPOINTER_TO_SIZE (ptr),
size,
backtrace_func,
NULL);
G_UNLOCK (writer);
}
static inline void
track_free (void *ptr)
{
if G_UNLIKELY (!writer)
return;
G_LOCK (writer);
sysprof_capture_writer_add_allocation (writer,
SYSPROF_CAPTURE_CURRENT_TIME,
sched_getcpu (),
pid,
gettid(),
GPOINTER_TO_SIZE (ptr),
0,
backtrace_func,
0);
G_UNLOCK (writer);
}
void *
malloc (size_t size)
{
void *ret = real_malloc (size);
track_malloc (ret, size);
return ret;
}
void *
calloc (size_t nmemb,
size_t size)
{
void *ret = real_calloc (nmemb, size);
track_malloc (ret, size);
return ret;
}
void *
realloc (void *ptr,
size_t size)
{
void *ret = real_realloc (ptr, size);
if (ret != ptr)
{
track_free (ptr);
track_malloc (ret, size);
}
return ret;
}
void
free (void *ptr)
{
real_free (ptr);
track_free (ptr);
}
void *
aligned_alloc (size_t alignment,
size_t size)
{
void *ret = real_aligned_alloc (alignment, size);
track_malloc (ret, size);
return ret;
}
int
posix_memalign (void **memptr,
size_t alignment,
size_t size)
{
int ret = real_posix_memalign (memptr, alignment, size);
track_malloc (*memptr, size);
return ret;
}
void *
memalign (size_t alignment,
size_t size)
{
void *ret = real_memalign (alignment, size);
track_malloc (ret, size);
return ret;
}

1947
src/libsysprof/rax.c Normal file

File diff suppressed because it is too large Load Diff

216
src/libsysprof/rax.h Normal file
View File

@ -0,0 +1,216 @@
/* Rax -- A radix tree implementation.
*
* Copyright (c) 2017-2018, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef RAX_H
#define RAX_H
#include <stdint.h>
/* Representation of a radix tree as implemented in this file, that contains
* the strings "foo", "foobar" and "footer" after the insertion of each
* word. When the node represents a key inside the radix tree, we write it
* between [], otherwise it is written between ().
*
* This is the vanilla representation:
*
* (f) ""
* \
* (o) "f"
* \
* (o) "fo"
* \
* [t b] "foo"
* / \
* "foot" (e) (a) "foob"
* / \
* "foote" (r) (r) "fooba"
* / \
* "footer" [] [] "foobar"
*
* However, this implementation implements a very common optimization where
* successive nodes having a single child are "compressed" into the node
* itself as a string of characters, each representing a next-level child,
* and only the link to the node representing the last character node is
* provided inside the representation. So the above representation is turend
* into:
*
* ["foo"] ""
* |
* [t b] "foo"
* / \
* "foot" ("er") ("ar") "foob"
* / \
* "footer" [] [] "foobar"
*
* However this optimization makes the implementation a bit more complex.
* For instance if a key "first" is added in the above radix tree, a
* "node splitting" operation is needed, since the "foo" prefix is no longer
* composed of nodes having a single child one after the other. This is the
* above tree and the resulting node splitting after this event happens:
*
*
* (f) ""
* /
* (i o) "f"
* / \
* "firs" ("rst") (o) "fo"
* / \
* "first" [] [t b] "foo"
* / \
* "foot" ("er") ("ar") "foob"
* / \
* "footer" [] [] "foobar"
*
* Similarly after deletion, if a new chain of nodes having a single child
* is created (the chain must also not include nodes that represent keys),
* it must be compressed back into a single node.
*
*/
#define RAX_NODE_MAX_SIZE ((1<<29)-1)
typedef struct raxNode {
uint32_t iskey:1; /* Does this node contain a key? */
uint32_t isnull:1; /* Associated value is NULL (don't store it). */
uint32_t iscompr:1; /* Node is compressed. */
uint32_t size:29; /* Number of children, or compressed string len. */
/* Data layout is as follows:
*
* If node is not compressed we have 'size' bytes, one for each children
* character, and 'size' raxNode pointers, point to each child node.
* Note how the character is not stored in the children but in the
* edge of the parents:
*
* [header iscompr=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)
*
* if node is compressed (iscompr bit is 1) the node has 1 children.
* In that case the 'size' bytes of the string stored immediately at
* the start of the data section, represent a sequence of successive
* nodes linked one after the other, for which only the last one in
* the sequence is actually represented as a node, and pointed to by
* the current compressed node.
*
* [header iscompr=1][xyz][z-ptr](value-ptr?)
*
* Both compressed and not compressed nodes can represent a key
* with associated data in the radix tree at any level (not just terminal
* nodes).
*
* If the node has an associated key (iskey=1) and is not NULL
* (isnull=0), then after the raxNode pointers poiting to the
* children, an additional value pointer is present (as you can see
* in the representation above as "value-ptr" field).
*/
unsigned char data[];
} raxNode;
typedef struct rax {
raxNode *head;
uint64_t numele;
uint64_t numnodes;
} rax;
/* Stack data structure used by raxLowWalk() in order to, optionally, return
* a list of parent nodes to the caller. The nodes do not have a "parent"
* field for space concerns, so we use the auxiliary stack when needed. */
#define RAX_STACK_STATIC_ITEMS 32
typedef struct raxStack {
void **stack; /* Points to static_items or an heap allocated array. */
size_t items, maxitems; /* Number of items contained and total space. */
/* Up to RAXSTACK_STACK_ITEMS items we avoid to allocate on the heap
* and use this static array of pointers instead. */
void *static_items[RAX_STACK_STATIC_ITEMS];
int oom; /* True if pushing into this stack failed for OOM at some point. */
} raxStack;
/* Optional callback used for iterators and be notified on each rax node,
* including nodes not representing keys. If the callback returns true
* the callback changed the node pointer in the iterator structure, and the
* iterator implementation will have to replace the pointer in the radix tree
* internals. This allows the callback to reallocate the node to perform
* very special operations, normally not needed by normal applications.
*
* This callback is used to perform very low level analysis of the radix tree
* structure, scanning each possible node (but the root node), or in order to
* reallocate the nodes to reduce the allocation fragmentation (this is the
* Redis application for this callback).
*
* This is currently only supported in forward iterations (raxNext) */
typedef int (*raxNodeCallback)(raxNode **noderef);
/* Radix tree iterator state is encapsulated into this data structure. */
#define RAX_ITER_STATIC_LEN 128
#define RAX_ITER_JUST_SEEKED (1<<0) /* Iterator was just seeked. Return current
element for the first iteration and
clear the flag. */
#define RAX_ITER_EOF (1<<1) /* End of iteration reached. */
#define RAX_ITER_SAFE (1<<2) /* Safe iterator, allows operations while
iterating. But it is slower. */
typedef struct raxIterator {
int flags;
rax *rt; /* Radix tree we are iterating. */
unsigned char *key; /* The current string. */
void *data; /* Data associated to this key. */
size_t key_len; /* Current key length. */
size_t key_max; /* Max key len the current key buffer can hold. */
unsigned char key_static_string[RAX_ITER_STATIC_LEN];
raxNode *node; /* Current node. Only for unsafe iteration. */
raxStack stack; /* Stack used for unsafe iteration. */
raxNodeCallback node_cb; /* Optional node callback. Normally set to NULL. */
} raxIterator;
/* A special pointer returned for not found items. */
extern void *raxNotFound;
/* Exported API. */
rax *raxNew(void);
int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
int raxRemove(rax *rax, unsigned char *s, size_t len, void **old);
void *raxFind(rax *rax, unsigned char *s, size_t len);
void raxFree(rax *rax);
void raxFreeWithCallback(rax *rax, void (*free_callback)(void*));
void raxStart(raxIterator *it, rax *rt);
int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len);
int raxNext(raxIterator *it);
int raxPrev(raxIterator *it);
int raxRandomWalk(raxIterator *it, size_t steps);
int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key_len);
void raxStop(raxIterator *it);
int raxEOF(raxIterator *it);
void raxShow(rax *rax);
uint64_t raxSize(rax *rax);
unsigned long raxTouch(raxNode *n);
void raxSetDebugMsg(int onoff);
/* Internal API. May be used by the node callback in order to access rax nodes
* in a low level way, so this function is exported as well. */
void raxSetData(raxNode *n, void *data);
#endif

View File

@ -0,0 +1,43 @@
/* Rax -- A radix tree implementation.
*
* Copyright (c) 2017, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/* Allocator selection.
*
* This file is used in order to change the Rax allocator at compile time.
* Just define the following defines to what you want to use. Also add
* the include of your alternate allocator if needed (not needed in order
* to use the default libc allocator). */
#ifndef RAX_ALLOC_H
#define RAX_ALLOC_H
#define rax_malloc malloc
#define rax_realloc realloc
#define rax_free free
#endif

View File

@ -0,0 +1,489 @@
/* sysprof-memprof-profile.c
*
* Copyright 2020 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
*/
#define G_LOG_DOMAIN "sysprof-memprof-profile"
#include "config.h"
#include <sysprof-capture.h>
#include "sysprof-capture-symbol-resolver.h"
#include "sysprof-elf-symbol-resolver.h"
#include "sysprof-kernel-symbol-resolver.h"
#include "sysprof-memprof-profile.h"
#include "sysprof-symbol-resolver.h"
#include "rax.h"
#include "../stackstash.h"
typedef struct
{
SysprofSelection *selection;
SysprofCaptureReader *reader;
GPtrArray *resolvers;
GStringChunk *symbols;
GHashTable *tags;
GHashTable *cmdlines;
StackStash *stash;
StackStash *building;
rax *rax;
GArray *resolved;
} Generate;
struct _SysprofMemprofProfile
{
GObject parent_instance;
SysprofSelection *selection;
SysprofCaptureReader *reader;
Generate *g;
};
static void profile_iface_init (SysprofProfileInterface *iface);
G_DEFINE_TYPE_WITH_CODE (SysprofMemprofProfile, sysprof_memprof_profile, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_PROFILE, profile_iface_init))
enum {
PROP_0,
PROP_SELECTION,
N_PROPS
};
static GParamSpec *properties[N_PROPS];
static void
generate_free (Generate *g)
{
g_clear_pointer (&g->reader, sysprof_capture_reader_unref);
g_clear_pointer (&g->rax, raxFree);
g_clear_pointer (&g->stash, stack_stash_unref);
g_clear_pointer (&g->building, stack_stash_unref);
g_clear_pointer (&g->resolvers, g_ptr_array_unref);
g_clear_pointer (&g->symbols, g_string_chunk_free);
g_clear_pointer (&g->tags, g_hash_table_unref);
g_clear_pointer (&g->resolved, g_array_unref);
g_clear_pointer (&g->cmdlines, g_hash_table_unref);
g_clear_object (&g->selection);
}
static Generate *
generate_ref (Generate *g)
{
return g_atomic_rc_box_acquire (g);
}
static void
generate_unref (Generate *g)
{
g_atomic_rc_box_release_full (g, (GDestroyNotify)generate_free);
}
static void
sysprof_memprof_profile_finalize (GObject *object)
{
SysprofMemprofProfile *self = (SysprofMemprofProfile *)object;
g_clear_pointer (&self->g, generate_unref);
g_clear_pointer (&self->reader, sysprof_capture_reader_unref);
g_clear_object (&self->selection);
G_OBJECT_CLASS (sysprof_memprof_profile_parent_class)->finalize (object);
}
static void
sysprof_memprof_profile_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SysprofMemprofProfile *self = SYSPROF_MEMPROF_PROFILE (object);
switch (prop_id)
{
case PROP_SELECTION:
g_value_set_object (value, self->selection);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sysprof_memprof_profile_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SysprofMemprofProfile *self = SYSPROF_MEMPROF_PROFILE (object);
switch (prop_id)
{
case PROP_SELECTION:
self->selection = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
sysprof_memprof_profile_class_init (SysprofMemprofProfileClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = sysprof_memprof_profile_finalize;
object_class->get_property = sysprof_memprof_profile_get_property;
object_class->set_property = sysprof_memprof_profile_set_property;
properties [PROP_SELECTION] =
g_param_spec_object ("selection",
"Selection",
"The selection for filtering the callgraph",
SYSPROF_TYPE_SELECTION,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
sysprof_memprof_profile_init (SysprofMemprofProfile *self)
{
}
SysprofProfile *
sysprof_memprof_profile_new (void)
{
return g_object_new (SYSPROF_TYPE_MEMPROF_PROFILE, NULL);
}
static void
sysprof_memprof_profile_set_reader (SysprofProfile *profile,
SysprofCaptureReader *reader)
{
SysprofMemprofProfile *self = (SysprofMemprofProfile *)profile;
g_assert (SYSPROF_IS_MEMPROF_PROFILE (self));
g_assert (reader != NULL);
if (reader != self->reader)
{
g_clear_pointer (&self->reader, sysprof_capture_reader_unref);
self->reader = sysprof_capture_reader_ref (reader);
}
}
static SysprofCaptureCursor *
create_cursor (SysprofCaptureReader *reader)
{
static SysprofCaptureFrameType types[] = {
SYSPROF_CAPTURE_FRAME_ALLOCATION,
SYSPROF_CAPTURE_FRAME_PROCESS,
};
SysprofCaptureCursor *cursor;
SysprofCaptureCondition *cond;
cond = sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types);
cursor = sysprof_capture_cursor_new (reader);
sysprof_capture_cursor_add_condition (cursor, cond);
return cursor;
}
static gboolean
cursor_foreach_cb (const SysprofCaptureFrame *frame,
gpointer user_data)
{
Generate *g = user_data;
g_assert (frame != NULL);
g_assert (frame->type == SYSPROF_CAPTURE_FRAME_ALLOCATION ||
frame->type == SYSPROF_CAPTURE_FRAME_PROCESS);
/* Short-circuit if we don't care about this frame */
if (!sysprof_selection_contains (g->selection, frame->time))
return TRUE;
if (frame->type == SYSPROF_CAPTURE_FRAME_PROCESS)
{
const SysprofCaptureProcess *pr = (const SysprofCaptureProcess *)frame;
g_autofree gchar *cmdline = g_strdup_printf ("[%s]", pr->cmdline);
g_hash_table_insert (g->cmdlines,
GINT_TO_POINTER (frame->pid),
(gchar *)g_string_chunk_insert_const (g->symbols, cmdline));
return TRUE;
}
if (frame->type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
{
const SysprofCaptureAllocation *ev = (const SysprofCaptureAllocation *)frame;
/* Handle memory allocations */
if (ev->alloc_size > 0)
{
SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE;
const gchar *cmdline;
StackNode *node;
guint len = 5;
node = stack_stash_add_trace (g->building, ev->addrs, ev->n_addrs, ev->alloc_size);
for (const StackNode *iter = node; iter != NULL; iter = iter->parent)
len++;
if (G_UNLIKELY (g->resolved->len < len))
g_array_set_size (g->resolved, len);
len = 0;
for (const StackNode *iter = node; iter != NULL; iter = iter->parent)
{
SysprofAddressContext context = SYSPROF_ADDRESS_CONTEXT_NONE;
SysprofAddress address = iter->data;
const gchar *symbol = NULL;
if (sysprof_address_is_context_switch (address, &context))
{
if (last_context)
symbol = sysprof_address_context_to_string (last_context);
else
symbol = NULL;
last_context = context;
}
else
{
for (guint i = 0; i < g->resolvers->len; i++)
{
SysprofSymbolResolver *resolver = g_ptr_array_index (g->resolvers, i);
GQuark tag = 0;
gchar *str;
str = sysprof_symbol_resolver_resolve_with_context (resolver,
frame->time,
frame->pid,
last_context,
address,
&tag);
if (str != NULL)
{
symbol = g_string_chunk_insert_const (g->symbols, str);
g_free (str);
if (tag != 0 && !g_hash_table_contains (g->tags, symbol))
g_hash_table_insert (g->tags, (gchar *)symbol, GSIZE_TO_POINTER (tag));
break;
}
}
}
if (symbol != NULL)
g_array_index (g->resolved, SysprofAddress, len++) = POINTER_TO_U64 (symbol);
}
if ((cmdline = g_hash_table_lookup (g->cmdlines, GINT_TO_POINTER (frame->pid))))
g_array_index (g->resolved, guint64, len++) = POINTER_TO_U64 (cmdline);
g_array_index (g->resolved, guint64, len++) = POINTER_TO_U64 ("[Everything]");
stack_stash_add_trace (g->stash,
(gpointer)g->resolved->data,
len,
ev->alloc_size);
}
}
return TRUE;
}
static void
sysprof_memprof_profile_generate_worker (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
SysprofCaptureCursor *cursor;
Generate *g = task_data;
g_assert (G_IS_TASK (task));
g_assert (g != NULL);
g_assert (g->reader != NULL);
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
/* Make sure the capture is at the beginning */
sysprof_capture_reader_reset (g->reader);
/* Load all our symbol resolvers */
for (guint i = 0; i < g->resolvers->len; i++)
{
SysprofSymbolResolver *resolver = g_ptr_array_index (g->resolvers, i);
sysprof_symbol_resolver_load (resolver, g->reader);
sysprof_capture_reader_reset (g->reader);
}
cursor = create_cursor (g->reader);
sysprof_capture_cursor_foreach (cursor, cursor_foreach_cb, g);
/* Release some data we don't need anymore */
g_clear_pointer (&g->resolved, g_array_unref);
g_clear_pointer (&g->resolvers, g_ptr_array_unref);
g_clear_pointer (&g->reader, sysprof_capture_reader_unref);
g_clear_pointer (&g->building, stack_stash_unref);
g_clear_pointer (&g->cmdlines, g_hash_table_unref);
g_clear_object (&g->selection);
g_task_return_boolean (task, TRUE);
}
static void
sysprof_memprof_profile_generate (SysprofProfile *profile,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
SysprofMemprofProfile *self = (SysprofMemprofProfile *)profile;
g_autoptr(GTask) task = NULL;
Generate *g;
g_assert (SYSPROF_IS_MEMPROF_PROFILE (self));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, sysprof_memprof_profile_generate);
if (self->reader == NULL)
{
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_NOT_INITIALIZED,
"No capture reader has been set");
return;
}
g = g_atomic_rc_box_new0 (Generate);
g->reader = sysprof_capture_reader_copy (self->reader);
g->selection = sysprof_selection_copy (self->selection);
g->cmdlines = g_hash_table_new (NULL, NULL);
g->rax = raxNew ();
g->stash = stack_stash_new (NULL);
g->building = stack_stash_new (NULL);
g->resolvers = g_ptr_array_new_with_free_func (g_object_unref);
g->symbols = g_string_chunk_new (4096*4);
g->tags = g_hash_table_new (g_str_hash, g_str_equal);
g->resolved = g_array_new (FALSE, TRUE, sizeof (guint64));
g_ptr_array_add (g->resolvers, sysprof_capture_symbol_resolver_new ());
g_ptr_array_add (g->resolvers, sysprof_kernel_symbol_resolver_new ());
g_ptr_array_add (g->resolvers, sysprof_elf_symbol_resolver_new ());
g_task_set_task_data (task, g, (GDestroyNotify) generate_unref);
g_task_run_in_thread (task, sysprof_memprof_profile_generate_worker);
}
static gboolean
sysprof_memprof_profile_generate_finish (SysprofProfile *profile,
GAsyncResult *result,
GError **error)
{
SysprofMemprofProfile *self = (SysprofMemprofProfile *)profile;
g_assert (SYSPROF_IS_MEMPROF_PROFILE (self));
g_assert (G_IS_TASK (result));
g_clear_pointer (&self->g, generate_unref);
if (g_task_propagate_boolean (G_TASK (result), error))
{
Generate *g = g_task_get_task_data (G_TASK (result));
self->g = generate_ref (g);
return TRUE;
}
return FALSE;
}
static void
profile_iface_init (SysprofProfileInterface *iface)
{
iface->set_reader = sysprof_memprof_profile_set_reader;
iface->generate = sysprof_memprof_profile_generate;
iface->generate_finish = sysprof_memprof_profile_generate_finish;
}
gpointer
sysprof_memprof_profile_get_native (SysprofMemprofProfile *self)
{
g_return_val_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self), NULL);
if (self->g != NULL)
return self->g->rax;
return NULL;
}
gpointer
sysprof_memprof_profile_get_stash (SysprofMemprofProfile *self)
{
g_return_val_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self), NULL);
if (self->g != NULL)
return self->g->stash;
return NULL;
}
gboolean
sysprof_memprof_profile_is_empty (SysprofMemprofProfile *self)
{
StackNode *root;
g_return_val_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self), FALSE);
return (self->g == NULL ||
self->g->stash == NULL ||
!(root = stack_stash_get_root (self->g->stash)) ||
!root->total);
}
GQuark
sysprof_memprof_profile_get_tag (SysprofMemprofProfile *self,
const gchar *symbol)
{
g_return_val_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self), 0);
if (self->g != NULL)
return GPOINTER_TO_SIZE (g_hash_table_lookup (self->g->tags, symbol));
return 0;
}
SysprofProfile *
sysprof_memprof_profile_new_with_selection (SysprofSelection *selection)
{
return g_object_new (SYSPROF_TYPE_MEMPROF_PROFILE,
"selection", selection,
NULL);
}

View File

@ -0,0 +1,53 @@
/* sysprof-memprof-profile.h
*
* Copyright 2020 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
*/
#pragma once
#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION)
# error "Only <sysprof.h> can be included directly."
#endif
#include "sysprof-version-macros.h"
#include "sysprof-profile.h"
#include "sysprof-selection.h"
G_BEGIN_DECLS
#define SYSPROF_TYPE_MEMPROF_PROFILE (sysprof_memprof_profile_get_type())
SYSPROF_AVAILABLE_IN_ALL
G_DECLARE_FINAL_TYPE (SysprofMemprofProfile, sysprof_memprof_profile, SYSPROF, MEMPROF_PROFILE, GObject)
SYSPROF_AVAILABLE_IN_3_36
SysprofProfile *sysprof_memprof_profile_new (void);
SYSPROF_AVAILABLE_IN_3_36
SysprofProfile *sysprof_memprof_profile_new_with_selection (SysprofSelection *selection);
SYSPROF_AVAILABLE_IN_3_36
gpointer sysprof_memprof_profile_get_native (SysprofMemprofProfile *self);
SYSPROF_AVAILABLE_IN_3_36
gpointer sysprof_memprof_profile_get_stash (SysprofMemprofProfile *self);
SYSPROF_AVAILABLE_IN_3_36
gboolean sysprof_memprof_profile_is_empty (SysprofMemprofProfile *self);
SYSPROF_AVAILABLE_IN_3_36
GQuark sysprof_memprof_profile_get_tag (SysprofMemprofProfile *self,
const gchar *symbol);
G_END_DECLS

View File

@ -0,0 +1,77 @@
/* sysprof-memprof-source.c
*
* Copyright 2020 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
*/
#define G_LOG_DOMAIN "sysprof-memprof-source"
#include "config.h"
#include "sysprof-memprof-source.h"
struct _SysprofMemprofSource
{
SysprofTracefdSource parent_instance;
};
static SysprofSourceInterface *parent_iface;
static void
sysprof_memprof_source_modify_spawn (SysprofSource *source,
SysprofSpawnable *spawnable)
{
g_assert (SYSPROF_IS_SOURCE (source));
g_assert (SYSPROF_IS_SPAWNABLE (spawnable));
parent_iface->modify_spawn (source, spawnable);
#ifdef __linux__
sysprof_spawnable_setenv (spawnable, "G_SLICE", "always-malloc");
sysprof_spawnable_setenv (spawnable,
"LD_PRELOAD",
PACKAGE_LIBEXECDIR"/libsysprof-memory-"API_VERSION_S".so");
#endif
}
static void
source_iface_init (SysprofSourceInterface *iface)
{
parent_iface = g_type_interface_peek_parent (iface);
iface->modify_spawn = sysprof_memprof_source_modify_spawn;
}
G_DEFINE_TYPE_WITH_CODE (SysprofMemprofSource, sysprof_memprof_source, SYSPROF_TYPE_TRACEFD_SOURCE,
G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init))
static void
sysprof_memprof_source_class_init (SysprofMemprofSourceClass *klass)
{
}
static void
sysprof_memprof_source_init (SysprofMemprofSource *self)
{
sysprof_tracefd_source_set_envvar (SYSPROF_TRACEFD_SOURCE (self), "MEMPROF_TRACE_FD");
}
SysprofSource *
sysprof_memprof_source_new (void)
{
return g_object_new (SYSPROF_TYPE_MEMPROF_SOURCE, NULL);
}

View File

@ -0,0 +1,35 @@
/* sysprof-memprof-source.h
*
* Copyright 2020 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
*/
#pragma once
#include "sysprof-tracefd-source.h"
G_BEGIN_DECLS
#define SYSPROF_TYPE_MEMPROF_SOURCE (sysprof_memprof_source_get_type())
SYSPROF_AVAILABLE_IN_3_36
G_DECLARE_FINAL_TYPE (SysprofMemprofSource, sysprof_memprof_source, SYSPROF, MEMPROF_SOURCE, SysprofTracefdSource)
SYSPROF_AVAILABLE_IN_3_36
SysprofSource *sysprof_memprof_source_new (void);
G_END_DECLS

View File

@ -36,6 +36,8 @@ G_BEGIN_DECLS
# include "sysprof-kernel-symbol-resolver.h"
# include "sysprof-kernel-symbol.h"
# include "sysprof-local-profiler.h"
# include "sysprof-memprof-profile.h"
# include "sysprof-memprof-source.h"
# include "sysprof-netdev-source.h"
# include "sysprof-process-model-item.h"
# include "sysprof-process-model.h"

147
src/tests/allocs-by-size.c Normal file
View File

@ -0,0 +1,147 @@
/* allocs-by-size.c
*
* Copyright 2020 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 <glib/gi18n.h>
#include <stddef.h>
#include <rax.h>
#include <sysprof.h>
typedef struct
{
gsize size;
gsize count;
gsize cmp;
} Item;
static gint
item_compare (gconstpointer a,
gconstpointer b)
{
const Item *item_a = a;
const Item *item_b = b;
if (item_a->cmp < item_b->cmp)
return -1;
else if (item_a->cmp > item_b->cmp)
return 1;
else
return 0;
}
static void
allocs_by_size (SysprofCaptureReader *reader)
{
SysprofCaptureFrameType type;
g_autoptr(GHashTable) allocs = NULL;
g_autoptr(GArray) ar = NULL;
GHashTableIter iter;
gpointer k,v;
gsize *count;
allocs = g_hash_table_new_full (NULL, NULL, NULL, g_free);
ar = g_array_new (FALSE, FALSE, sizeof (Item));
while (sysprof_capture_reader_peek_type (reader, &type))
{
if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
{
const SysprofCaptureAllocation *ev = sysprof_capture_reader_read_allocation (reader);
if (ev == NULL)
break;
/* Ignore frees */
if (ev->alloc_size <= 0)
continue;
if (!(count = g_hash_table_lookup (allocs, GSIZE_TO_POINTER (ev->alloc_size))))
{
count = g_new0 (gsize, 1);
g_hash_table_insert (allocs, GSIZE_TO_POINTER (ev->alloc_size), count);
}
(*count)++;
}
else
{
if (!sysprof_capture_reader_skip (reader))
break;
}
}
g_hash_table_iter_init (&iter, allocs);
while (g_hash_table_iter_next (&iter, &k, &v))
{
const Item item = {
.size = GPOINTER_TO_SIZE (k),
.count = *(gsize *)v,
.cmp = *(gsize *)v * GPOINTER_TO_SIZE (k),
};
g_array_append_val (ar, item);
}
g_array_sort (ar, item_compare);
g_print ("alloc_size,total_alloc,n_allocs\n");
for (guint i = 0; i < ar->len; i++)
{
const Item *item = &g_array_index (ar, Item, i);
g_print ("%"G_GUINT64_FORMAT",%"G_GUINT64_FORMAT",%"G_GUINT64_FORMAT"\n",
item->size, item->cmp, item->count);
}
}
gint
main (gint argc,
gchar *argv[])
{
SysprofCaptureReader *reader;
const gchar *filename = argv[1];
g_autoptr(GError) error = NULL;
if (argc < 2)
{
g_printerr ("usage: %s FILENAME\n", argv[0]);
return EXIT_FAILURE;
}
/* Set up gettext translations */
setlocale (LC_ALL, "");
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
if (!(reader = sysprof_capture_reader_new (filename, &error)))
{
g_printerr ("%s\n", error->message);
return EXIT_FAILURE;
}
allocs_by_size (reader);
sysprof_capture_reader_unref (reader);
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,137 @@
/* cross-thread-frees.c
*
* Copyright 2020 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 <stddef.h>
#include <rax.h>
#include <sysprof.h>
typedef struct
{
gint tid;
guint n_addrs;
gint64 size;
SysprofCaptureAddress addrs[0];
} Stack;
static void
stack_free (gpointer ptr)
{
Stack *stack = ptr;
gsize size = sizeof *stack + (stack->n_addrs * sizeof (SysprofCaptureAddress));
g_slice_free1 (size, stack);
}
static Stack *
stack_new (gint tid,
gint64 size,
guint n_addrs,
const SysprofCaptureAddress *addrs)
{
Stack *stack;
stack = g_slice_alloc (sizeof *stack + (n_addrs * sizeof (SysprofCaptureAddress)));
stack->tid = tid;
stack->size = size;
stack->n_addrs = n_addrs;
for (guint i = 0; i < n_addrs; i++)
stack->addrs[i] = addrs[i];
return stack;
}
static void
cross_thread_frees (SysprofCaptureReader *reader)
{
SysprofCaptureFrameType type;
g_autoptr(GHashTable) stacks = NULL;
stacks = g_hash_table_new_full (NULL, NULL, NULL, stack_free);
while (sysprof_capture_reader_peek_type (reader, &type))
{
if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
{
const SysprofCaptureAllocation *ev = sysprof_capture_reader_read_allocation (reader);
gpointer key;
if (ev == NULL)
break;
key = GINT_TO_POINTER (ev->alloc_addr);
if (ev->alloc_size > 0)
{
g_hash_table_insert (stacks,
key,
stack_new (ev->tid, ev->alloc_size, ev->n_addrs, ev->addrs));
}
else
{
Stack *stack;
stack = g_hash_table_lookup (stacks, key);
if (stack == NULL)
continue;
if (ev->tid != stack->tid)
{
g_print ("Alloc-Thread=%d Free-Thread=%d Size=%"G_GUINT64_FORMAT"\n",
stack->tid, ev->tid, stack->size);
}
g_hash_table_remove (stacks, key);
}
}
else
{
if (!sysprof_capture_reader_skip (reader))
break;
}
}
}
gint
main (gint argc,
gchar *argv[])
{
SysprofCaptureReader *reader;
const gchar *filename = argv[1];
g_autoptr(GError) error = NULL;
if (argc < 2)
{
g_printerr ("usage: %s FILENAME\n", argv[0]);
return EXIT_FAILURE;
}
if (!(reader = sysprof_capture_reader_new (filename, &error)))
{
g_printerr ("%s\n", error->message);
return EXIT_FAILURE;
}
cross_thread_frees (reader);
sysprof_capture_reader_unref (reader);
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,90 @@
/* memory-stack-stash.c
*
* Copyright 2020 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 <glib/gi18n.h>
#include <stddef.h>
#include <rax.h>
#include <sysprof.h>
#include "../stackstash.h"
#include "../stackstash.c"
static void
memory_stack_stash (SysprofCaptureReader *reader)
{
SysprofCaptureFrameType type;
StackStash *stash = stack_stash_new (NULL);
while (sysprof_capture_reader_peek_type (reader, &type))
{
if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
{
const SysprofCaptureAllocation *ev = sysprof_capture_reader_read_allocation (reader);
if (ev == NULL)
break;
if (ev->alloc_size > 0)
stack_stash_add_trace (stash, ev->addrs, ev->n_addrs, ev->alloc_size);
}
else
{
if (!sysprof_capture_reader_skip (reader))
break;
}
}
stack_stash_unref (stash);
}
gint
main (gint argc,
gchar *argv[])
{
SysprofCaptureReader *reader;
const gchar *filename = argv[1];
g_autoptr(GError) error = NULL;
if (argc < 2)
{
g_printerr ("usage: %s FILENAME\n", argv[0]);
return EXIT_FAILURE;
}
/* Set up gettext translations */
setlocale (LC_ALL, "");
bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
if (!(reader = sysprof_capture_reader_new (filename, &error)))
{
g_printerr ("%s\n", error->message);
return EXIT_FAILURE;
}
memory_stack_stash (reader);
sysprof_capture_reader_unref (reader);
return EXIT_SUCCESS;
}

View File

@ -64,6 +64,30 @@ test_resolvers = executable('test-resolvers',
dependencies: test_deps,
)
allocs_by_size = executable('allocs-by-size',
['allocs-by-size.c'],
c_args: test_cflags,
dependencies: test_deps,
)
cross_thread_frees = executable('cross-thread-frees',
['cross-thread-frees.c'],
c_args: test_cflags,
dependencies: test_deps,
)
memory_stack_stash = executable('memory-stack-stash',
['memory-stack-stash.c'],
c_args: test_cflags,
dependencies: test_deps,
)
show_page_usage = executable('show-page-usage',
[ 'show-page-usage.c' ],
c_args: test_cflags,
dependencies: test_deps + [ librax_dep,
dependency('cairo') ],
)
if get_option('enable_gtk')

196
src/tests/show-page-usage.c Normal file
View File

@ -0,0 +1,196 @@
/* show-page-usage.c
*
* Copyright 2020 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 <cairo.h>
#include <stddef.h>
#include <rax.h>
#include <sysprof.h>
static GMainLoop *main_loop;
static gint
u64_compare (gconstpointer a,
gconstpointer b)
{
const guint64 *aptr = a;
const guint64 *bptr = b;
if (*aptr < *bptr)
return -1;
else if (*aptr > *bptr)
return 1;
else
return 0;
}
static void
generate_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
SysprofProfile *profile = (SysprofProfile *)object;
g_autoptr(GError) error = NULL;
GHashTable *seen;
GHashTableIter iter;
cairo_t *cr;
cairo_surface_t *surface;
GArray *ar;
raxIterator it;
rax *r;
gpointer k,v;
g_assert (SYSPROF_IS_MEMPROF_PROFILE (profile));
g_assert (G_IS_ASYNC_RESULT (result));
if (!sysprof_profile_generate_finish (profile, result, &error))
{
g_printerr ("%s\n", error->message);
exit (EXIT_FAILURE);
}
r = sysprof_memprof_profile_get_native (SYSPROF_MEMPROF_PROFILE (profile));
seen = g_hash_table_new (NULL, NULL);
raxStart (&it, r);
raxSeek (&it, "^", NULL, 0);
while (raxNext (&it))
{
guint64 page;
guint64 addr;
memcpy (&addr, it.key, sizeof addr);
page = addr / 4096;
if (g_hash_table_contains (seen, GSIZE_TO_POINTER (page)))
continue;
g_hash_table_insert (seen, GSIZE_TO_POINTER (page), NULL);
}
raxStop (&it);
ar = g_array_sized_new (FALSE, FALSE, sizeof (guint64), g_hash_table_size (seen));
g_hash_table_iter_init (&iter, seen);
while (g_hash_table_iter_next (&iter, &k, &v))
{
guint64 key = GPOINTER_TO_SIZE (k);
g_array_append_val (ar, key);
}
g_array_sort (ar, u64_compare);
for (guint i = 0; i < ar->len; i++)
{
guint64 key = g_array_index (ar, guint64, i);
g_hash_table_insert (seen, GSIZE_TO_POINTER (key), GSIZE_TO_POINTER (i));
}
g_printerr ("We have %u pages to graph\n", ar->len);
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, ar->len, (4096/16));
cr = cairo_create (surface);
cairo_set_line_width (cr, 1.0);
cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
cairo_set_source_rgb (cr, 1, 1, 1);
cairo_rectangle (cr, 0, 0, ar->len, (4096/16));
cairo_fill (cr);
cairo_set_source_rgb (cr, 0, 0, 0);
cairo_scale (cr, 1.0, 1.0/16.0);
cairo_translate (cr, .5, .5);
raxStart (&it, r);
raxSeek (&it, "^", NULL, 0);
while (raxNext (&it))
{
guint64 page;
guint64 addr;
guint64 size;
guint x;
guint y;
memcpy (&addr, it.key, sizeof addr);
page = addr / 4096;
size = GPOINTER_TO_SIZE (it.data);
x = GPOINTER_TO_UINT (g_hash_table_lookup (seen, GSIZE_TO_POINTER (page)));
y = addr % 4096;
/* TODO: Need size */
cairo_move_to (cr, x, y);
cairo_line_to (cr, x, y+size);
}
raxStop (&it);
cairo_stroke (cr);
cairo_surface_write_to_png (surface, "memory.png");
cairo_destroy (cr);
cairo_surface_destroy (surface);
g_array_unref (ar);
g_hash_table_unref (seen);
g_main_loop_quit (main_loop);
}
gint
main (gint argc,
gchar *argv[])
{
SysprofCaptureReader *reader;
const gchar *filename = argv[1];
g_autoptr(SysprofProfile) memprof = NULL;
g_autoptr(GError) error = NULL;
if (argc < 2)
{
g_printerr ("usage: %s FILENAME\n", argv[0]);
return EXIT_FAILURE;
}
main_loop = g_main_loop_new (NULL, FALSE);
if (!(reader = sysprof_capture_reader_new (filename, &error)))
{
g_printerr ("%s\n", error->message);
return EXIT_FAILURE;
}
memprof = sysprof_memprof_profile_new ();
sysprof_profile_set_reader (memprof, reader);
sysprof_profile_generate (memprof, NULL, generate_cb, NULL);
g_main_loop_run (main_loop);
g_main_loop_unref (main_loop);
sysprof_capture_reader_unref (reader);
return EXIT_SUCCESS;
}

View File

@ -900,6 +900,67 @@ test_reader_writer_cat_jitmap (void)
g_unlink ("jitmap-joined.syscap");
}
static void
test_writer_memory_alloc_free (void)
{
SysprofCaptureWriter *writer;
SysprofCaptureReader *reader;
GError *error = NULL;
SysprofCaptureAddress addrs[20] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19,
};
gboolean r;
writer = sysprof_capture_writer_new ("memory.syscap", 0);
for (guint i = 0; i < 20; i++)
{
r = sysprof_capture_writer_add_allocation_copy (writer,
SYSPROF_CAPTURE_CURRENT_TIME,
i % 4,
i % 3,
i % 7,
i,
i * 2,
addrs,
i);
g_assert_true (r);
}
sysprof_capture_writer_flush (writer);
reader = sysprof_capture_writer_create_reader (writer, &error);
g_assert_no_error (error);
g_assert_nonnull (reader);
for (guint i = 0; i < 20; i++)
{
const SysprofCaptureAllocation *ev;
ev = sysprof_capture_reader_read_allocation (reader);
g_assert_nonnull (ev);
g_assert_cmpint (ev->frame.type, ==, SYSPROF_CAPTURE_FRAME_ALLOCATION);
g_assert_cmpint (ev->frame.cpu, ==, i % 4);
g_assert_cmpint (ev->frame.pid, ==, i % 3);
g_assert_cmpint (ev->tid, ==, i % 7);
g_assert_cmpint (ev->alloc_addr, ==, i);
g_assert_cmpint (ev->alloc_size, ==, i * 2);
g_assert_cmpint (ev->n_addrs, ==, i);
for (guint j = 0; j < i; j++)
{
g_assert_cmpint (ev->addrs[j], ==, j);
}
}
sysprof_capture_writer_unref (writer);
sysprof_capture_reader_unref (reader);
g_unlink ("memory.syscap");
}
int
main (int argc,
char *argv[])
@ -907,6 +968,7 @@ main (int argc,
sysprof_clock_init ();
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/SysprofCapture/ReaderWriter", test_reader_basic);
g_test_add_func ("/SysprofCapture/ReaderWriter/alloc_free", test_writer_memory_alloc_free);
g_test_add_func ("/SysprofCapture/Writer/splice", test_writer_splice);
g_test_add_func ("/SysprofCapture/Reader/splice", test_reader_splice);
g_test_add_func ("/SysprofCapture/ReaderWriter/log", test_reader_writer_log);

View File

@ -189,6 +189,7 @@ main (gint argc,
gboolean use_trace_fd = FALSE;
gboolean gnome_shell = FALSE;
gboolean rapl = FALSE;
gboolean memprof = FALSE;
gboolean merge = FALSE;
int pid = -1;
int fd;
@ -208,6 +209,7 @@ main (gint argc,
{ "gjs", 0, 0, G_OPTION_ARG_NONE, &gjs, N_("Set GJS_TRACE_FD environment to trace GJS processes") },
{ "gtk", 0, 0, G_OPTION_ARG_NONE, &gtk, N_("Set GTK_TRACE_FD environment to trace a GTK application") },
{ "rapl", 0, 0, G_OPTION_ARG_NONE, &rapl, N_("Include RAPL energy statistics") },
{ "memprof", 0, 0, G_OPTION_ARG_NONE, &memprof, N_("Profile memory allocations and frees") },
{ "gnome-shell", 0, 0, G_OPTION_ARG_NONE, &gnome_shell, N_("Connect to org.gnome.Shell for profiler statistics") },
{ "merge", 0, 0, G_OPTION_ARG_NONE, &merge, N_("Merge all provided *.syscap files and write to stdout") },
{ "version", 0, 0, G_OPTION_ARG_NONE, &version, N_("Print the sysprof-cli version and exit") },
@ -471,6 +473,13 @@ Examples:\n\
g_object_unref (source);
}
if (memprof)
{
source = sysprof_memprof_source_new ();
sysprof_profiler_add_source (profiler, source);
g_object_unref (source);
}
if (pid != -1)
{
sysprof_profiler_set_whole_system (profiler, FALSE);

View File

@ -299,6 +299,19 @@ main (gint argc,
}
break;
case SYSPROF_CAPTURE_FRAME_ALLOCATION:
{
const SysprofCaptureAllocation *ev = sysprof_capture_reader_read_allocation (reader);
gdouble ptime = (ev->frame.time - begin_time) / (gdouble)SYSPROF_NSEC_PER_SEC;
g_print ("%s: pid=%d tid=%d addr=0x%"G_GINT64_MODIFIER"x size=%"G_GINT64_FORMAT" time=%"G_GINT64_FORMAT" (%lf)\n",
ev->alloc_size > 0 ? "ALLOC" : "FREE",
ev->frame.pid, ev->tid,
ev->alloc_addr, ev->alloc_size,
ev->frame.time, ptime);
}
break;
default:
g_print ("Skipping unknown frame type: (%d): ", type);
if (!sysprof_capture_reader_skip (reader))