mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
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:
21
meson.build
21
meson.build
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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 ());
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
226
src/libsysprof-ui/sysprof-memprof-aid.c
Normal file
226
src/libsysprof-ui/sysprof-memprof-aid.c
Normal 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");
|
||||
}
|
||||
33
src/libsysprof-ui/sysprof-memprof-aid.h
Normal file
33
src/libsysprof-ui/sysprof-memprof-aid.h
Normal 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
|
||||
1319
src/libsysprof-ui/sysprof-memprof-page.c
Normal file
1319
src/libsysprof-ui/sysprof-memprof-page.c
Normal file
File diff suppressed because it is too large
Load Diff
51
src/libsysprof-ui/sysprof-memprof-page.h
Normal file
51
src/libsysprof-ui/sysprof-memprof-page.h
Normal 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
|
||||
232
src/libsysprof-ui/sysprof-memprof-page.ui
Normal file
232
src/libsysprof-ui/sysprof-memprof-page.ui
Normal 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>
|
||||
614
src/libsysprof-ui/sysprof-memprof-visualizer.c
Normal file
614
src/libsysprof-ui/sysprof-memprof-visualizer.c
Normal 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)
|
||||
{
|
||||
}
|
||||
33
src/libsysprof-ui/sysprof-memprof-visualizer.h
Normal file
33
src/libsysprof-ui/sysprof-memprof-visualizer.h
Normal 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
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
16
src/libsysprof/preload/meson.build
Normal file
16
src/libsysprof/preload/meson.build
Normal 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'),
|
||||
)
|
||||
295
src/libsysprof/preload/sysprof-memory-collector.c
Normal file
295
src/libsysprof/preload/sysprof-memory-collector.c
Normal 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
1947
src/libsysprof/rax.c
Normal file
File diff suppressed because it is too large
Load Diff
216
src/libsysprof/rax.h
Normal file
216
src/libsysprof/rax.h
Normal 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
|
||||
43
src/libsysprof/rax_malloc.h
Normal file
43
src/libsysprof/rax_malloc.h
Normal 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
|
||||
489
src/libsysprof/sysprof-memprof-profile.c
Normal file
489
src/libsysprof/sysprof-memprof-profile.c
Normal 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);
|
||||
}
|
||||
53
src/libsysprof/sysprof-memprof-profile.h
Normal file
53
src/libsysprof/sysprof-memprof-profile.h
Normal 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
|
||||
77
src/libsysprof/sysprof-memprof-source.c
Normal file
77
src/libsysprof/sysprof-memprof-source.c
Normal 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);
|
||||
}
|
||||
35
src/libsysprof/sysprof-memprof-source.h
Normal file
35
src/libsysprof/sysprof-memprof-source.h
Normal 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
|
||||
@ -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
147
src/tests/allocs-by-size.c
Normal 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;
|
||||
}
|
||||
137
src/tests/cross-thread-frees.c
Normal file
137
src/tests/cross-thread-frees.c
Normal 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;
|
||||
}
|
||||
90
src/tests/memory-stack-stash.c
Normal file
90
src/tests/memory-stack-stash.c
Normal 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;
|
||||
}
|
||||
@ -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
196
src/tests/show-page-usage.c
Normal 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;
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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, >k, 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);
|
||||
|
||||
@ -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))
|
||||
|
||||
Reference in New Issue
Block a user