mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
1174 lines
34 KiB
C
1174 lines
34 KiB
C
/* 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-autocleanups.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
|
|
{
|
|
gint pid;
|
|
gint tid;
|
|
gint64 time;
|
|
SysprofCaptureAddress addr;
|
|
gint64 size;
|
|
guint64 frame_num;
|
|
} Alloc;
|
|
|
|
typedef struct
|
|
{
|
|
volatile gint ref_count;
|
|
SysprofSelection *selection;
|
|
SysprofCaptureReader *reader;
|
|
GPtrArray *resolvers;
|
|
GStringChunk *symbols;
|
|
GHashTable *tags;
|
|
GHashTable *cmdlines;
|
|
StackStash *stash;
|
|
StackStash *building;
|
|
rax *rax;
|
|
GArray *resolved;
|
|
SysprofMemprofMode mode;
|
|
SysprofMemprofStats stats;
|
|
} Generate;
|
|
|
|
struct _SysprofMemprofProfile
|
|
{
|
|
GObject parent_instance;
|
|
SysprofSelection *selection;
|
|
SysprofCaptureReader *reader;
|
|
Generate *g;
|
|
SysprofMemprofMode mode;
|
|
};
|
|
|
|
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_finalize (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);
|
|
g_slice_free (Generate, g);
|
|
}
|
|
|
|
static Generate *
|
|
generate_ref (Generate *g)
|
|
{
|
|
g_return_val_if_fail (g != NULL, NULL);
|
|
g_return_val_if_fail (g->ref_count > 0, NULL);
|
|
|
|
g_atomic_int_inc (&g->ref_count);
|
|
|
|
return g;
|
|
}
|
|
|
|
static void
|
|
generate_unref (Generate *g)
|
|
{
|
|
g_return_if_fail (g != NULL);
|
|
g_return_if_fail (g->ref_count > 0);
|
|
|
|
if (g_atomic_int_dec_and_test (&g->ref_count))
|
|
generate_finalize (g);
|
|
}
|
|
|
|
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)
|
|
{
|
|
self->mode = SYSPROF_MEMPROF_MODE_ALL_ALLOCS;
|
|
}
|
|
|
|
SysprofMemprofMode
|
|
sysprof_memprof_profile_get_mode (SysprofMemprofProfile *self)
|
|
{
|
|
g_return_val_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self), 0);
|
|
|
|
return self->mode;
|
|
}
|
|
|
|
void
|
|
sysprof_memprof_profile_set_mode (SysprofMemprofProfile *self,
|
|
SysprofMemprofMode mode)
|
|
{
|
|
g_return_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self));
|
|
|
|
self->mode = mode;
|
|
}
|
|
|
|
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 bool
|
|
all_allocs_foreach_cb (const SysprofCaptureFrame *frame,
|
|
void *user_data)
|
|
{
|
|
Generate *g = user_data;
|
|
|
|
g_assert (frame != NULL);
|
|
g_assert (frame->type == SYSPROF_CAPTURE_FRAME_ALLOCATION ||
|
|
frame->type == SYSPROF_CAPTURE_FRAME_PROCESS);
|
|
|
|
if G_UNLIKELY (frame->type == SYSPROF_CAPTURE_FRAME_PROCESS)
|
|
{
|
|
const SysprofCaptureProcess *pr = (const SysprofCaptureProcess *)frame;
|
|
|
|
if (!g_hash_table_contains (g->cmdlines, GINT_TO_POINTER (frame->pid)))
|
|
{
|
|
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;
|
|
}
|
|
|
|
/* 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_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 gint
|
|
compare_frame_num_reverse (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
const Alloc *aptr = a;
|
|
const Alloc *bptr = b;
|
|
|
|
if (aptr->frame_num < bptr->frame_num)
|
|
return 1;
|
|
else if (aptr->frame_num > bptr->frame_num)
|
|
return -1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
static gint
|
|
compare_alloc (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
const Alloc *aptr = a;
|
|
const Alloc *bptr = b;
|
|
|
|
if (aptr->pid < bptr->pid)
|
|
return -1;
|
|
else if (aptr->pid > bptr->pid)
|
|
return 1;
|
|
|
|
if (aptr->tid < bptr->tid)
|
|
return -1;
|
|
else if (aptr->tid > bptr->tid)
|
|
return 1;
|
|
|
|
if (aptr->time < bptr->time)
|
|
return -1;
|
|
else if (aptr->time > bptr->time)
|
|
return 1;
|
|
|
|
if (aptr->addr < bptr->addr)
|
|
return -1;
|
|
else if (aptr->addr > bptr->addr)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static gint
|
|
compare_alloc_pid_addr_time (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
const Alloc *aptr = a;
|
|
const Alloc *bptr = b;
|
|
|
|
if (aptr->pid < bptr->pid)
|
|
return -1;
|
|
else if (aptr->pid > bptr->pid)
|
|
return 1;
|
|
|
|
if (aptr->addr < bptr->addr)
|
|
return -1;
|
|
else if (aptr->addr > bptr->addr)
|
|
return 1;
|
|
|
|
if (aptr->time < bptr->time)
|
|
return -1;
|
|
else if (aptr->time > bptr->time)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static guint
|
|
get_bucket (gint64 size)
|
|
{
|
|
if (size <= 32)
|
|
return 0;
|
|
if (size <= 64)
|
|
return 1;
|
|
if (size <= 128)
|
|
return 2;
|
|
if (size <= 256)
|
|
return 3;
|
|
if (size <= 512)
|
|
return 4;
|
|
if (size <= 1024)
|
|
return 5;
|
|
if (size <= 4096)
|
|
return 6;
|
|
if (size <= 4096*4)
|
|
return 7;
|
|
if (size <= 4096*8)
|
|
return 8;
|
|
if (size <= 4096*16)
|
|
return 9;
|
|
if (size <= 4096*32)
|
|
return 10;
|
|
if (size <= 4096*64)
|
|
return 11;
|
|
if (size <= 4096*256)
|
|
return 12;
|
|
return 13;
|
|
}
|
|
|
|
static void
|
|
summary_worker (Generate *g)
|
|
{
|
|
g_autoptr(GArray) allocs = NULL;
|
|
SysprofCaptureFrameType type;
|
|
SysprofCaptureAddress last_addr = 0;
|
|
guint last_bucket = 0;
|
|
|
|
g_assert (g != NULL);
|
|
g_assert (g->reader != NULL);
|
|
|
|
allocs = g_array_new (FALSE, FALSE, sizeof (Alloc));
|
|
|
|
sysprof_capture_reader_reset (g->reader);
|
|
|
|
g->stats.by_size[0].bucket = 32;
|
|
g->stats.by_size[1].bucket = 64;
|
|
g->stats.by_size[2].bucket = 128;
|
|
g->stats.by_size[3].bucket = 256;
|
|
g->stats.by_size[4].bucket = 512;
|
|
g->stats.by_size[5].bucket = 1024;
|
|
g->stats.by_size[6].bucket = 4096;
|
|
g->stats.by_size[7].bucket = 4096*4;
|
|
g->stats.by_size[8].bucket = 4096*8;
|
|
g->stats.by_size[9].bucket = 4096*16;
|
|
g->stats.by_size[10].bucket = 4096*32;
|
|
g->stats.by_size[11].bucket = 4096*64;
|
|
g->stats.by_size[12].bucket = 4096*256;
|
|
g->stats.by_size[13].bucket = 4096*256;
|
|
|
|
while (sysprof_capture_reader_peek_type (g->reader, &type))
|
|
{
|
|
if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
|
|
{
|
|
const SysprofCaptureAllocation *ev;
|
|
Alloc a;
|
|
|
|
if (!(ev = sysprof_capture_reader_read_allocation (g->reader)))
|
|
break;
|
|
|
|
a.pid = ev->frame.pid;
|
|
a.tid = ev->tid;
|
|
a.time = ev->frame.time;
|
|
a.addr = ev->alloc_addr;
|
|
a.size = ev->alloc_size;
|
|
a.frame_num = 0;
|
|
|
|
g_array_append_val (allocs, a);
|
|
|
|
if (a.size > 0)
|
|
g->stats.n_allocs++;
|
|
}
|
|
else
|
|
{
|
|
if (!sysprof_capture_reader_skip (g->reader))
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_array_sort (allocs, compare_alloc);
|
|
|
|
for (guint i = 0; i < allocs->len; i++)
|
|
{
|
|
const Alloc *a = &g_array_index (allocs, Alloc, i);
|
|
|
|
if (a->size <= 0)
|
|
{
|
|
if (last_addr == a->addr)
|
|
{
|
|
g->stats.temp_allocs++;
|
|
g->stats.by_size[last_bucket].temp_allocs++;
|
|
}
|
|
|
|
g->stats.leaked_allocs--;
|
|
|
|
last_addr = 0;
|
|
last_bucket = 0;
|
|
}
|
|
else
|
|
{
|
|
guint b = get_bucket (a->size);
|
|
|
|
g->stats.n_allocs++;
|
|
g->stats.leaked_allocs++;
|
|
g->stats.by_size[b].n_allocs++;
|
|
g->stats.by_size[b].allocated += a->size;
|
|
|
|
last_addr = a->addr;
|
|
last_bucket = b;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
temp_allocs_worker (Generate *g)
|
|
{
|
|
g_autoptr(GArray) temp_allocs = NULL;
|
|
g_autoptr(GArray) all_allocs = NULL;
|
|
StackNode *node;
|
|
SysprofCaptureFrameType type;
|
|
SysprofCaptureAddress last_addr = 0;
|
|
guint64 frame_num = 0;
|
|
|
|
g_assert (g != NULL);
|
|
g_assert (g->reader != NULL);
|
|
|
|
temp_allocs = g_array_new (FALSE, FALSE, sizeof (Alloc));
|
|
all_allocs = g_array_new (FALSE, FALSE, sizeof (Alloc));
|
|
|
|
sysprof_capture_reader_reset (g->reader);
|
|
|
|
while (sysprof_capture_reader_peek_type (g->reader, &type))
|
|
{
|
|
if G_UNLIKELY (type == SYSPROF_CAPTURE_FRAME_PROCESS)
|
|
{
|
|
const SysprofCaptureProcess *pr;
|
|
|
|
if (!(pr = sysprof_capture_reader_read_process (g->reader)))
|
|
break;
|
|
|
|
if (!g_hash_table_contains (g->cmdlines, GINT_TO_POINTER (pr->frame.pid)))
|
|
{
|
|
g_autofree gchar *cmdline = g_strdup_printf ("[%s]", pr->cmdline);
|
|
g_hash_table_insert (g->cmdlines,
|
|
GINT_TO_POINTER (pr->frame.pid),
|
|
(gchar *)g_string_chunk_insert_const (g->symbols, cmdline));
|
|
}
|
|
}
|
|
else if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
|
|
{
|
|
const SysprofCaptureAllocation *ev;
|
|
Alloc a;
|
|
|
|
if (!(ev = sysprof_capture_reader_read_allocation (g->reader)))
|
|
break;
|
|
|
|
frame_num++;
|
|
|
|
/* Short-circuit if we don't care about this frame */
|
|
if (!sysprof_selection_contains (g->selection, ev->frame.time))
|
|
continue;
|
|
|
|
a.pid = ev->frame.pid;
|
|
a.tid = ev->tid;
|
|
a.time = ev->frame.time;
|
|
a.addr = ev->alloc_addr;
|
|
a.size = ev->alloc_size;
|
|
a.frame_num = frame_num;
|
|
|
|
g_array_append_val (all_allocs, a);
|
|
}
|
|
else
|
|
{
|
|
if (!sysprof_capture_reader_skip (g->reader))
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Ensure items are in order because threads may be writing
|
|
* data into larger buffers, which are flushed in whole by
|
|
* the event marshalling from control fds.
|
|
*/
|
|
g_array_sort (all_allocs, compare_alloc);
|
|
|
|
for (guint i = 0; i < all_allocs->len; i++)
|
|
{
|
|
const Alloc *a = &g_array_index (all_allocs, Alloc, i);
|
|
|
|
if (a->size <= 0)
|
|
{
|
|
if (a->addr == last_addr && last_addr)
|
|
{
|
|
const Alloc *prev = a - 1;
|
|
g_array_append_vals (temp_allocs, prev, 1);
|
|
}
|
|
|
|
last_addr = 0;
|
|
}
|
|
else
|
|
{
|
|
last_addr = a->addr;
|
|
}
|
|
}
|
|
|
|
if (temp_allocs->len == 0)
|
|
return;
|
|
|
|
/* Now sort by frame number so we can walk the reader and get the stack
|
|
* for each allocation as we count frames. We can skip frames until we
|
|
* get to the matching frame_num for the next alloc.
|
|
*
|
|
* We sort in reverse so that we can just keep shortening the array as
|
|
* we match each frame to save having to keep a secondary position
|
|
* variable.
|
|
*/
|
|
g_array_sort (temp_allocs, compare_frame_num_reverse);
|
|
sysprof_capture_reader_reset (g->reader);
|
|
frame_num = 0;
|
|
while (sysprof_capture_reader_peek_type (g->reader, &type))
|
|
{
|
|
if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
|
|
{
|
|
const SysprofCaptureAllocation *ev;
|
|
const Alloc *tail;
|
|
|
|
if (!(ev = sysprof_capture_reader_read_allocation (g->reader)))
|
|
break;
|
|
|
|
frame_num++;
|
|
|
|
tail = &g_array_index (temp_allocs, Alloc, temp_allocs->len - 1);
|
|
|
|
if G_UNLIKELY (tail->frame_num == frame_num)
|
|
{
|
|
SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE;
|
|
const gchar *cmdline;
|
|
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,
|
|
ev->frame.time,
|
|
ev->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 (ev->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);
|
|
|
|
g_array_set_size (temp_allocs, temp_allocs->len - 1);
|
|
|
|
if (temp_allocs->len == 0)
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!sysprof_capture_reader_skip (g->reader))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
leaked_allocs_worker (Generate *g)
|
|
{
|
|
g_autoptr(GArray) leak_allocs = NULL;
|
|
g_autoptr(GArray) all_allocs = NULL;
|
|
StackNode *node;
|
|
SysprofCaptureFrameType type;
|
|
guint64 frame_num = 0;
|
|
|
|
g_assert (g != NULL);
|
|
g_assert (g->reader != NULL);
|
|
|
|
leak_allocs = g_array_new (FALSE, FALSE, sizeof (Alloc));
|
|
all_allocs = g_array_new (FALSE, FALSE, sizeof (Alloc));
|
|
|
|
sysprof_capture_reader_reset (g->reader);
|
|
|
|
while (sysprof_capture_reader_peek_type (g->reader, &type))
|
|
{
|
|
if G_UNLIKELY (type == SYSPROF_CAPTURE_FRAME_PROCESS)
|
|
{
|
|
const SysprofCaptureProcess *pr;
|
|
|
|
if (!(pr = sysprof_capture_reader_read_process (g->reader)))
|
|
break;
|
|
|
|
if (!g_hash_table_contains (g->cmdlines, GINT_TO_POINTER (pr->frame.pid)))
|
|
{
|
|
g_autofree gchar *cmdline = g_strdup_printf ("[%s]", pr->cmdline);
|
|
g_hash_table_insert (g->cmdlines,
|
|
GINT_TO_POINTER (pr->frame.pid),
|
|
(gchar *)g_string_chunk_insert_const (g->symbols, cmdline));
|
|
}
|
|
}
|
|
else if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
|
|
{
|
|
const SysprofCaptureAllocation *ev;
|
|
Alloc a;
|
|
|
|
if (!(ev = sysprof_capture_reader_read_allocation (g->reader)))
|
|
break;
|
|
|
|
frame_num++;
|
|
|
|
/* Short-circuit if we don't care about this frame */
|
|
if (!sysprof_selection_contains (g->selection, ev->frame.time))
|
|
continue;
|
|
|
|
a.pid = ev->frame.pid;
|
|
a.tid = ev->tid;
|
|
a.time = ev->frame.time;
|
|
a.addr = ev->alloc_addr;
|
|
a.size = ev->alloc_size;
|
|
a.frame_num = frame_num;
|
|
|
|
g_array_append_val (all_allocs, a);
|
|
}
|
|
else
|
|
{
|
|
if (!sysprof_capture_reader_skip (g->reader))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (all_allocs->len == 0)
|
|
return;
|
|
|
|
/* Order so we can find frees right after alloc */
|
|
g_array_sort (all_allocs, compare_alloc_pid_addr_time);
|
|
|
|
for (guint i = 0; i < all_allocs->len; i++)
|
|
{
|
|
const Alloc *a = &g_array_index (all_allocs, Alloc, i);
|
|
const Alloc *next;
|
|
|
|
/* free()s are <= 0 */
|
|
if (a->size <= 0)
|
|
continue;
|
|
|
|
if (i + 1 == all_allocs->len)
|
|
goto leaked;
|
|
|
|
next = &g_array_index (all_allocs, Alloc, i+1);
|
|
if (a->addr == next->addr && a->pid == next->pid && next->size <= 0)
|
|
continue;
|
|
|
|
leaked:
|
|
g_array_append_vals (leak_allocs, a, 1);
|
|
}
|
|
|
|
if (leak_allocs->len == 0)
|
|
return;
|
|
|
|
/* Now sort by frame number so we can walk the reader and get the stack
|
|
* for each allocation as we count frames. We can skip frames until we
|
|
* get to the matching frame_num for the next alloc.
|
|
*
|
|
* We sort in reverse so that we can just keep shortening the array as
|
|
* we match each frame to save having to keep a secondary position
|
|
* variable.
|
|
*/
|
|
g_array_sort (leak_allocs, compare_frame_num_reverse);
|
|
sysprof_capture_reader_reset (g->reader);
|
|
frame_num = 0;
|
|
while (sysprof_capture_reader_peek_type (g->reader, &type))
|
|
{
|
|
if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
|
|
{
|
|
const SysprofCaptureAllocation *ev;
|
|
const Alloc *tail;
|
|
|
|
if (!(ev = sysprof_capture_reader_read_allocation (g->reader)))
|
|
break;
|
|
|
|
frame_num++;
|
|
|
|
tail = &g_array_index (leak_allocs, Alloc, leak_allocs->len - 1);
|
|
|
|
if G_UNLIKELY (tail->frame_num == frame_num)
|
|
{
|
|
SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE;
|
|
const gchar *cmdline;
|
|
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,
|
|
ev->frame.time,
|
|
ev->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 (ev->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);
|
|
|
|
g_array_set_size (leak_allocs, leak_allocs->len - 1);
|
|
|
|
if (leak_allocs->len == 0)
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!sysprof_capture_reader_skip (g->reader))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
sysprof_memprof_profile_generate_worker (GTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
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);
|
|
}
|
|
|
|
if (g->mode == SYSPROF_MEMPROF_MODE_ALL_ALLOCS)
|
|
{
|
|
g_autoptr(SysprofCaptureCursor) cursor = NULL;
|
|
|
|
cursor = create_cursor (g->reader);
|
|
sysprof_capture_cursor_foreach (cursor, all_allocs_foreach_cb, g);
|
|
}
|
|
else if (g->mode == SYSPROF_MEMPROF_MODE_TEMP_ALLOCS)
|
|
{
|
|
temp_allocs_worker (g);
|
|
}
|
|
else if (g->mode == SYSPROF_MEMPROF_MODE_SUMMARY)
|
|
{
|
|
summary_worker (g);
|
|
}
|
|
else if (g->mode == SYSPROF_MEMPROF_MODE_LEAKED_ALLOCS)
|
|
{
|
|
leaked_allocs_worker (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_slice_new0 (Generate);
|
|
g->ref_count = 1;
|
|
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->mode = self->mode;
|
|
|
|
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);
|
|
}
|
|
|
|
void
|
|
sysprof_memprof_profile_get_stats (SysprofMemprofProfile *self,
|
|
SysprofMemprofStats *stats)
|
|
{
|
|
g_return_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self));
|
|
g_return_if_fail (stats != NULL);
|
|
|
|
if (self->g != NULL)
|
|
*stats = self->g->stats;
|
|
else
|
|
memset (stats, 0, sizeof *stats);
|
|
}
|