memprof: add memory profiling using LD_PRELOAD

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,226 @@
/* sysprof-memprof-aid.c
*
* Copyright 2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "sysprof-memprof-aid"
#include "config.h"
#include <glib/gi18n.h>
#include "sysprof-memprof-aid.h"
#include "sysprof-memprof-page.h"
#include "sysprof-memprof-source.h"
#include "sysprof-memprof-visualizer.h"
struct _SysprofMemprofAid
{
SysprofAid parent_instance;
};
G_DEFINE_TYPE (SysprofMemprofAid, sysprof_memprof_aid, SYSPROF_TYPE_AID)
typedef struct
{
SysprofCaptureCursor *cursor;
SysprofDisplay *display;
guint has_allocs : 1;
} Present;
static void
present_free (gpointer data)
{
Present *p = data;
g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref);
g_clear_object (&p->display);
g_slice_free (Present, p);
}
static void
on_group_activated_cb (SysprofVisualizerGroup *group,
SysprofPage *page)
{
SysprofDisplay *display;
g_assert (SYSPROF_IS_VISUALIZER_GROUP (group));
g_assert (SYSPROF_IS_PAGE (page));
display = SYSPROF_DISPLAY (gtk_widget_get_ancestor (GTK_WIDGET (page), SYSPROF_TYPE_DISPLAY));
sysprof_display_set_visible_page (display, page);
}
SysprofAid *
sysprof_memprof_aid_new (void)
{
return g_object_new (SYSPROF_TYPE_MEMPROF_AID, NULL);
}
static void
sysprof_memprof_aid_prepare (SysprofAid *self,
SysprofProfiler *profiler)
{
#ifdef __linux__
g_autoptr(SysprofSource) source = NULL;
g_assert (SYSPROF_IS_MEMPROF_AID (self));
g_assert (SYSPROF_IS_PROFILER (profiler));
source = sysprof_memprof_source_new ();
sysprof_profiler_add_source (profiler, source);
#endif
}
static gboolean
discover_samples_cb (const SysprofCaptureFrame *frame,
gpointer user_data)
{
Present *p = user_data;
g_assert (frame != NULL);
g_assert (p != NULL);
if (frame->type == SYSPROF_CAPTURE_FRAME_ALLOCATION)
{
p->has_allocs = TRUE;
return FALSE;
}
return TRUE;
}
static void
sysprof_memprof_aid_present_worker (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
Present *p = task_data;
g_assert (G_IS_TASK (task));
g_assert (SYSPROF_IS_MEMPROF_AID (source_object));
g_assert (p != NULL);
g_assert (p->cursor != NULL);
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
sysprof_capture_cursor_foreach (p->cursor, discover_samples_cb, p);
g_task_return_boolean (task, TRUE);
}
static void
sysprof_memprof_aid_present_async (SysprofAid *aid,
SysprofCaptureReader *reader,
SysprofDisplay *display,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_ALLOCATION };
g_autoptr(SysprofCaptureCondition) condition = NULL;
g_autoptr(SysprofCaptureCursor) cursor = NULL;
g_autoptr(GTask) task = NULL;
Present present;
g_assert (SYSPROF_IS_MEMPROF_AID (aid));
g_assert (reader != NULL);
g_assert (SYSPROF_IS_DISPLAY (display));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
condition = sysprof_capture_condition_new_where_type_in (1, types);
cursor = sysprof_capture_cursor_new (reader);
sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition));
present.cursor = g_steal_pointer (&cursor);
present.display = g_object_ref (display);
task = g_task_new (aid, cancellable, callback, user_data);
g_task_set_source_tag (task, sysprof_memprof_aid_present_async);
g_task_set_task_data (task,
g_slice_dup (Present, &present),
present_free);
g_task_run_in_thread (task, sysprof_memprof_aid_present_worker);
}
static gboolean
sysprof_memprof_aid_present_finish (SysprofAid *aid,
GAsyncResult *result,
GError **error)
{
Present *p;
g_assert (SYSPROF_IS_MEMPROF_AID (aid));
g_assert (G_IS_TASK (result));
p = g_task_get_task_data (G_TASK (result));
if (p->has_allocs)
{
SysprofVisualizerGroup *group;
SysprofVisualizer *row;
SysprofPage *page;
group = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP,
"can-focus", TRUE,
"has-page", TRUE,
"priority", -300,
"title", _("Memory"),
"visible", TRUE,
NULL);
row = sysprof_memprof_visualizer_new (FALSE);
sysprof_visualizer_group_insert (group, row, 0, FALSE);
row = sysprof_memprof_visualizer_new (TRUE);
sysprof_visualizer_group_insert (group, row, 1, FALSE);
page = g_object_new (SYSPROF_TYPE_MEMPROF_PAGE,
"title", _("Memory Allocations"),
"vexpand", TRUE,
"visible", TRUE,
NULL);
sysprof_display_add_page (p->display, page);
g_signal_connect_object (group,
"group-activated",
G_CALLBACK (on_group_activated_cb),
page,
0);
sysprof_display_add_group (p->display, group);
}
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
sysprof_memprof_aid_class_init (SysprofMemprofAidClass *klass)
{
SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass);
aid_class->prepare = sysprof_memprof_aid_prepare;
aid_class->present_async = sysprof_memprof_aid_present_async;
aid_class->present_finish = sysprof_memprof_aid_present_finish;
}
static void
sysprof_memprof_aid_init (SysprofMemprofAid *self)
{
sysprof_aid_set_display_name (SYSPROF_AID (self), _("Track Allocations"));
sysprof_aid_set_icon_name (SYSPROF_AID (self), "org.gnome.Sysprof-symbolic");
}

View File

@ -0,0 +1,33 @@
/* sysprof-memprof-aid.h
*
* Copyright 2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "sysprof-aid.h"
G_BEGIN_DECLS
#define SYSPROF_TYPE_MEMPROF_AID (sysprof_memprof_aid_get_type())
G_DECLARE_FINAL_TYPE (SysprofMemprofAid, sysprof_memprof_aid, SYSPROF, MEMPROF_AID, SysprofAid)
SysprofAid *sysprof_memprof_aid_new (void);
G_END_DECLS

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,51 @@
/* sysprof-memprof-page.h
*
* Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <gtk/gtk.h>
#include <sysprof.h>
#include "sysprof-page.h"
G_BEGIN_DECLS
#define SYSPROF_TYPE_MEMPROF_PAGE (sysprof_memprof_page_get_type())
G_DECLARE_DERIVABLE_TYPE (SysprofMemprofPage, sysprof_memprof_page, SYSPROF, MEMPROF_PAGE, SysprofPage)
struct _SysprofMemprofPageClass
{
SysprofPageClass parent_class;
void (*go_previous) (SysprofMemprofPage *self);
/*< private >*/
gpointer _reserved[16];
};
GtkWidget *sysprof_memprof_page_new (void);
SysprofMemprofProfile *sysprof_memprof_page_get_profile (SysprofMemprofPage *self);
void sysprof_memprof_page_set_profile (SysprofMemprofPage *self,
SysprofMemprofProfile *profile);
gchar *sysprof_memprof_page_screenshot (SysprofMemprofPage *self);
guint sysprof_memprof_page_get_n_functions (SysprofMemprofPage *self);
G_END_DECLS

View File

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

View File

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

View File

@ -0,0 +1,33 @@
/* sysprof-memprof-visualizer.h
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "sysprof-visualizer.h"
G_BEGIN_DECLS
#define SYSPROF_TYPE_MEMPROF_VISUALIZER (sysprof_memprof_visualizer_get_type())
G_DECLARE_FINAL_TYPE (SysprofMemprofVisualizer, sysprof_memprof_visualizer, SYSPROF, MEMPROF_VISUALIZER, SysprofVisualizer)
SysprofVisualizer *sysprof_memprof_visualizer_new (gboolean total_allocs);
G_END_DECLS

View File

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

View File

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

View File

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