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:
@ -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"
|
||||
|
||||
Reference in New Issue
Block a user