the heap profiler. yay.
svn path=/trunk/heap-prof/; revision=38579
This commit is contained in:
Коммит
3c812ee5e5
|
@ -0,0 +1 @@
|
|||
Ben Maurer <bmaurer@ximian.com>
|
|
@ -0,0 +1,4 @@
|
|||
2005-01-09 Ben Maurer <bmaurer@ximian.com>
|
||||
|
||||
* *: Initial Import.
|
||||
|
|
@ -0,0 +1 @@
|
|||
/usr/share/automake-1.8/INSTALL
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2005 Novell
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1 @@
|
|||
SUBDIRS = src
|
|
@ -0,0 +1,12 @@
|
|||
This is the Mono Heap Profiler.
|
||||
|
||||
To install, you need to do:
|
||||
|
||||
patch < ../heap-prof/mono.patch
|
||||
|
||||
in your mono dir.
|
||||
|
||||
Then do an autogen/make/make install in here.
|
||||
|
||||
Run mono --profile=heap:outfile, then run mono-heap-prof-view
|
||||
to view it.
|
|
@ -0,0 +1,143 @@
|
|||
#!/bin/sh
|
||||
# Run this to generate all the initial makefiles, etc.
|
||||
# Ripped off from GNOME macros version
|
||||
|
||||
DIE=0
|
||||
|
||||
srcdir=`dirname $0`
|
||||
test -z "$srcdir" && srcdir=.
|
||||
|
||||
if [ -n "$MONO_PATH" ]; then
|
||||
# from -> /mono/lib:/another/mono/lib
|
||||
# to -> /mono /another/mono
|
||||
for i in `echo ${MONO_PATH} | tr ":" " "`; do
|
||||
i=`dirname ${i}`
|
||||
if [ -n "{i}" -a -d "${i}/share/aclocal" ]; then
|
||||
ACLOCAL_FLAGS="-I ${i}/share/aclocal $ACLOCAL_FLAGS"
|
||||
fi
|
||||
if [ -n "{i}" -a -d "${i}/bin" ]; then
|
||||
PATH="${i}/bin:$PATH"
|
||||
fi
|
||||
done
|
||||
export PATH
|
||||
fi
|
||||
|
||||
(autoconf --version) < /dev/null > /dev/null 2>&1 || {
|
||||
echo
|
||||
echo "**Error**: You must have \`autoconf' installed to compile Mono."
|
||||
echo "Download the appropriate package for your distribution,"
|
||||
echo "or get the source tarball at ftp://ftp.gnu.org/pub/gnu/"
|
||||
DIE=1
|
||||
}
|
||||
|
||||
if [ -z "$LIBTOOL" ]; then
|
||||
LIBTOOL=`which glibtool 2>/dev/null`
|
||||
if [ ! -x "$LIBTOOL" ]; then
|
||||
LIBTOOL=`which libtool`
|
||||
fi
|
||||
fi
|
||||
|
||||
(grep "^AM_PROG_LIBTOOL" $srcdir/configure.in >/dev/null) && {
|
||||
($LIBTOOL --version) < /dev/null > /dev/null 2>&1 || {
|
||||
echo
|
||||
echo "**Error**: You must have \`libtool' installed to compile Mono."
|
||||
echo "Get ftp://ftp.gnu.org/pub/gnu/libtool-1.2d.tar.gz"
|
||||
echo "(or a newer version if it is available)"
|
||||
DIE=1
|
||||
}
|
||||
}
|
||||
|
||||
grep "^AM_GNU_GETTEXT" $srcdir/configure.in >/dev/null && {
|
||||
grep "sed.*POTFILES" $srcdir/configure.in >/dev/null || \
|
||||
(gettext --version) < /dev/null > /dev/null 2>&1 || {
|
||||
echo
|
||||
echo "**Error**: You must have \`gettext' installed to compile Mono."
|
||||
echo "Get ftp://alpha.gnu.org/gnu/gettext-0.10.35.tar.gz"
|
||||
echo "(or a newer version if it is available)"
|
||||
DIE=1
|
||||
}
|
||||
}
|
||||
|
||||
(automake --version) < /dev/null > /dev/null 2>&1 || {
|
||||
echo
|
||||
echo "**Error**: You must have \`automake' installed to compile Mono."
|
||||
echo "Get ftp://ftp.gnu.org/pub/gnu/automake-1.3.tar.gz"
|
||||
echo "(or a newer version if it is available)"
|
||||
DIE=1
|
||||
NO_AUTOMAKE=yes
|
||||
}
|
||||
|
||||
|
||||
# if no automake, don't bother testing for aclocal
|
||||
test -n "$NO_AUTOMAKE" || (aclocal --version) < /dev/null > /dev/null 2>&1 || {
|
||||
echo
|
||||
echo "**Error**: Missing \`aclocal'. The version of \`automake'"
|
||||
echo "installed doesn't appear recent enough."
|
||||
echo "Get ftp://ftp.gnu.org/pub/gnu/automake-1.3.tar.gz"
|
||||
echo "(or a newer version if it is available)"
|
||||
DIE=1
|
||||
}
|
||||
|
||||
if test "$DIE" -eq 1; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if test -z "$*"; then
|
||||
echo "**Warning**: I am going to run \`configure' with no arguments."
|
||||
echo "If you wish to pass any to it, please specify them on the"
|
||||
echo \`$0\'" command line."
|
||||
echo
|
||||
fi
|
||||
|
||||
case $CC in
|
||||
xlc )
|
||||
am_opt=--include-deps;;
|
||||
esac
|
||||
|
||||
|
||||
if grep "^AM_PROG_LIBTOOL" configure.in >/dev/null; then
|
||||
if test -z "$NO_LIBTOOLIZE" ; then
|
||||
echo "Running libtoolize..."
|
||||
${LIBTOOL}ize --force --copy
|
||||
#${LIBTOOL}ize --copy
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Running aclocal $ACLOCAL_FLAGS ..."
|
||||
aclocal $ACLOCAL_FLAGS || {
|
||||
echo
|
||||
echo "**Error**: aclocal failed. This may mean that you have not"
|
||||
echo "installed all of the packages you need, or you may need to"
|
||||
echo "set ACLOCAL_FLAGS to include \"-I \$prefix/share/aclocal\""
|
||||
echo "for the prefix where you installed the packages whose"
|
||||
echo "macros were not found"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if grep "^AM_CONFIG_HEADER" configure.in >/dev/null; then
|
||||
echo "Running autoheader..."
|
||||
autoheader || { echo "**Error**: autoheader failed."; exit 1; }
|
||||
fi
|
||||
|
||||
echo "Running automake --gnu $am_opt ..."
|
||||
automake --add-missing --gnu $am_opt ||
|
||||
{ echo "**Error**: automake failed."; exit 1; }
|
||||
echo "Running autoconf ..."
|
||||
autoconf || { echo "**Error**: autoconf failed."; exit 1; }
|
||||
|
||||
if test -d $srcdir/libgc; then
|
||||
echo Running libgc/autogen.sh ...
|
||||
(cd $srcdir/libgc ; NOCONFIGURE=1 ./autogen.sh "$@")
|
||||
echo Done running libgc/autogen.sh ...
|
||||
fi
|
||||
|
||||
|
||||
conf_flags="--enable-maintainer-mode --enable-compile-warnings" #--enable-iso-c
|
||||
|
||||
if test x$NOCONFIGURE = x; then
|
||||
echo Running $srcdir/configure $conf_flags "$@" ...
|
||||
$srcdir/configure $conf_flags "$@" \
|
||||
&& echo Now type \`make\' to compile $PKG_NAME || exit 1
|
||||
else
|
||||
echo Skipping configure process.
|
||||
fi
|
|
@ -0,0 +1,18 @@
|
|||
AC_INIT(README)
|
||||
AM_INIT_AUTOMAKE(heap-prof, 0.01)
|
||||
AC_PROG_CC
|
||||
AC_PROG_LIBTOOL
|
||||
|
||||
PKG_CHECK_MODULES(CCOMPILE, mono glib-2.0)
|
||||
PKG_CHECK_MODULES(GTKSHARP, gtk-sharp-2.0)
|
||||
|
||||
AC_PATH_PROG(MCS, mcs)
|
||||
AC_PATH_PROG(MONO, mono)
|
||||
|
||||
AC_OUTPUT([
|
||||
Makefile
|
||||
src/Makefile
|
||||
src/runtime-profiler/Makefile
|
||||
src/viewer/Makefile
|
||||
src/viewer/mono-heap-prof-view
|
||||
])
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1 @@
|
|||
/usr/share/automake-1.8/missing
|
|
@ -0,0 +1,149 @@
|
|||
Index: libgc/include/gc.h
|
||||
===================================================================
|
||||
--- libgc/include/gc.h (revision 38557)
|
||||
+++ libgc/include/gc.h (working copy)
|
||||
@@ -91,7 +91,19 @@
|
||||
/* If it returns, it must return 0 or a valid */
|
||||
/* pointer to a previously allocated heap */
|
||||
/* object. */
|
||||
+
|
||||
+GC_API GC_PTR (*GC_profile_marks_set) GC_PROTO((int col_num));
|
||||
+ /* Invoked on every collection. At this time mark
|
||||
+ * bits are set. A profiler would use this to do
|
||||
+ * a heap profile: so it can see what objects are
|
||||
+ * alive at a given time.
|
||||
+ */
|
||||
|
||||
+/* Slow/general mark bit manipulation: */
|
||||
+GC_API int GC_is_marked GC_PROTO((char * p));
|
||||
+GC_API void GC_clear_mark_bit GC_PROTO((char * p));
|
||||
+GC_API void GC_set_mark_bit GC_PROTO((char * p));
|
||||
+
|
||||
GC_API int GC_find_leak;
|
||||
/* Do not actually garbage collect, but simply */
|
||||
/* report inaccessible memory that was not */
|
||||
Index: libgc/include/private/gc_priv.h
|
||||
===================================================================
|
||||
--- libgc/include/private/gc_priv.h (revision 38557)
|
||||
+++ libgc/include/private/gc_priv.h (working copy)
|
||||
@@ -1781,10 +1781,7 @@
|
||||
|
||||
void GC_dirty_init GC_PROTO((void));
|
||||
|
||||
-/* Slow/general mark bit manipulation: */
|
||||
-GC_API GC_bool GC_is_marked GC_PROTO((ptr_t p));
|
||||
-void GC_clear_mark_bit GC_PROTO((ptr_t p));
|
||||
-void GC_set_mark_bit GC_PROTO((ptr_t p));
|
||||
+
|
||||
|
||||
/* Stubborn objects: */
|
||||
void GC_read_changed GC_PROTO((void)); /* Analogous to GC_read_dirty */
|
||||
Index: libgc/alloc.c
|
||||
===================================================================
|
||||
--- libgc/alloc.c (revision 38557)
|
||||
+++ libgc/alloc.c (working copy)
|
||||
@@ -617,6 +617,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
+GC_PTR (*GC_profile_marks_set) GC_PROTO((int col_num));
|
||||
+
|
||||
/* Finish up a collection. Assumes lock is held, signals are disabled, */
|
||||
/* but the world is otherwise running. */
|
||||
void GC_finish_collection()
|
||||
@@ -638,6 +640,11 @@
|
||||
GC_print_address_map();
|
||||
}
|
||||
# endif
|
||||
+
|
||||
+
|
||||
+ if (GC_profile_marks_set)
|
||||
+ GC_profile_marks_set (GC_gc_no);
|
||||
+
|
||||
COND_DUMP;
|
||||
if (GC_find_leak) {
|
||||
/* Mark all objects on the free list. All objects should be */
|
||||
Index: mono/metadata/profiler.c
|
||||
===================================================================
|
||||
--- mono/metadata/profiler.c (revision 38558)
|
||||
+++ mono/metadata/profiler.c (working copy)
|
||||
@@ -12,6 +12,7 @@
|
||||
#ifdef HAVE_BACKTRACE_SYMBOLS
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
+#include <mono/os/gc_wrapper.h>
|
||||
|
||||
static MonoProfiler * current_profiler = NULL;
|
||||
|
||||
@@ -46,6 +47,8 @@
|
||||
static MonoProfileThreadFunc thread_start;
|
||||
static MonoProfileThreadFunc thread_end;
|
||||
|
||||
+static MonoProfileGCFunc on_gc;
|
||||
+
|
||||
static MonoProfileCoverageFilterFunc coverage_filter_cb;
|
||||
|
||||
static MonoProfileFunc shutdown_callback;
|
||||
@@ -163,7 +166,21 @@
|
||||
class_end_unload = end_unload;
|
||||
}
|
||||
|
||||
+static void
|
||||
+mono_profiler_gc (int gc_num)
|
||||
+{
|
||||
+ if ((mono_profiler_events & MONO_PROFILE_GC) && on_gc)
|
||||
+ on_gc (current_profiler, gc_num);
|
||||
+}
|
||||
+
|
||||
void
|
||||
+mono_profiler_install_gc (MonoProfileGCFunc f)
|
||||
+{
|
||||
+ GC_profile_marks_set = mono_profiler_gc;
|
||||
+ on_gc = f;
|
||||
+}
|
||||
+
|
||||
+void
|
||||
mono_profiler_method_enter (MonoMethod *method)
|
||||
{
|
||||
if ((mono_profiler_events & MONO_PROFILE_ENTER_LEAVE) && method_enter)
|
||||
@@ -226,6 +243,7 @@
|
||||
thread_end (current_profiler, tid);
|
||||
}
|
||||
|
||||
+
|
||||
void
|
||||
mono_profiler_assembly_event (MonoAssembly *assembly, int code)
|
||||
{
|
||||
@@ -357,6 +375,12 @@
|
||||
shutdown_callback (current_profiler);
|
||||
}
|
||||
|
||||
+gboolean
|
||||
+mono_profiler_mark_set (MonoObject* o)
|
||||
+{
|
||||
+ return GC_is_marked (o);
|
||||
+}
|
||||
+
|
||||
static GHashTable *coverage_hash = NULL;
|
||||
|
||||
MonoProfileCoverageInfo*
|
||||
Index: mono/metadata/profiler.h
|
||||
===================================================================
|
||||
--- mono/metadata/profiler.h (revision 38557)
|
||||
+++ mono/metadata/profiler.h (working copy)
|
||||
@@ -64,6 +64,7 @@
|
||||
typedef void (*MonoProfileThreadFunc) (MonoProfiler *prof, guint32 tid);
|
||||
typedef void (*MonoProfileAllocFunc) (MonoProfiler *prof, MonoObject *obj, MonoClass *klass);
|
||||
typedef void (*MonoProfileStatFunc) (MonoProfiler *prof, guchar *ip, void *context);
|
||||
+typedef void (*MonoProfileGCFunc) (MonoProfiler *prof, int gc_num);
|
||||
|
||||
typedef gboolean (*MonoProfileCoverageFilterFunc) (MonoProfiler *prof, MonoMethod *method);
|
||||
|
||||
@@ -94,6 +95,7 @@
|
||||
void mono_profiler_install_statistical (MonoProfileStatFunc callback);
|
||||
void mono_profiler_install_coverage_filter (MonoProfileCoverageFilterFunc callback);
|
||||
void mono_profiler_coverage_get (MonoProfiler *prof, MonoMethod *method, MonoProfileCoverageFunc func);
|
||||
+void mono_profiler_install_gc (MonoProfileGCFunc callback);
|
||||
|
||||
void mono_profiler_load (const char *desc);
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
2005-01-09 Ben Maurer <bmaurer@ximian.com>
|
||||
|
||||
* *: Initial Import.
|
||||
|
|
@ -0,0 +1 @@
|
|||
SUBDIRS= viewer runtime-profiler
|
|
@ -0,0 +1,4 @@
|
|||
2005-01-09 Ben Maurer <bmaurer@ximian.com>
|
||||
|
||||
* *: Initial Import.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
lib_LTLIBRARIES = libmono-profiler-heap.la
|
||||
|
||||
libmono_profiler_heap_la_SOURCES = gc-profiler.c
|
||||
|
||||
libmono_profiler_heap_la_LIBADD = @CCOMPILE_LIBS@
|
||||
|
||||
INCLUDES = @CCOMPILE_CFLAGS@ -Wall
|
|
@ -0,0 +1,489 @@
|
|||
/*
|
||||
* gc-profiler.c - A heap profiler
|
||||
*
|
||||
* Author:
|
||||
* Ben Maurer <bmaurer@ximian.com>
|
||||
*
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
#include <mono/io-layer/io-layer.h>
|
||||
#include <mono/metadata/class.h>
|
||||
#include <mono/metadata/debug-helpers.h>
|
||||
#include <mono/metadata/object.h>
|
||||
#include <mono/metadata/profiler.h>
|
||||
|
||||
#define leu32 GUINT32_TO_LE
|
||||
#define leu64 GINT64_TO_LE
|
||||
|
||||
|
||||
typedef struct AllocRec AllocRec;
|
||||
|
||||
typedef struct {
|
||||
guint64 alloc_pos;
|
||||
guint32 time;
|
||||
guint32 alloc_ctx;
|
||||
} HeapProfGcFreedRec;
|
||||
|
||||
struct AllocRec {
|
||||
AllocRec* next;
|
||||
MonoObject* obj;
|
||||
HeapProfGcFreedRec rec;
|
||||
};
|
||||
|
||||
struct _MonoProfiler {
|
||||
FILE* out;
|
||||
char* file;
|
||||
|
||||
GPtrArray* klass_table;
|
||||
GHashTable* klass_to_table_idx;
|
||||
|
||||
GPtrArray* method_table;
|
||||
GHashTable* method_to_table_idx;
|
||||
|
||||
GPtrArray* bt_table;
|
||||
GHashTable* bt_to_table_idx;
|
||||
|
||||
GPtrArray* ctx_table;
|
||||
GHashTable* ctx_to_table_idx;
|
||||
|
||||
|
||||
AllocRec* live_allocs;
|
||||
guint32 t_zero;
|
||||
guint64 foffset;
|
||||
};
|
||||
|
||||
|
||||
|
||||
static CRITICAL_SECTION hp_lock;
|
||||
#define hp_lock_enter() EnterCriticalSection (&hp_lock)
|
||||
#define hp_lock_leave() LeaveCriticalSection (&hp_lock)
|
||||
|
||||
/* binary file format */
|
||||
static const guint8 heap_prof_dump_sig [] = {
|
||||
0x68, 0x30, 0xa4, 0x57, 0x18, 0xec, 0xd6, 0xa1,
|
||||
0x61, 0x9c, 0x1d, 0x43, 0xe1, 0x47, 0x27, 0xb6
|
||||
};
|
||||
|
||||
static const guint8 heap_prof_md_sig [] = {
|
||||
0xe4, 0x37, 0x29, 0x60, 0x3e, 0x31, 0x89, 0x12,
|
||||
0xaa, 0x93, 0xc8, 0x76, 0xf4, 0x6a, 0x95, 0x11
|
||||
};
|
||||
|
||||
static const guint32 heap_prof_version = 2;
|
||||
|
||||
#define BT_SIZE 5
|
||||
|
||||
typedef struct {
|
||||
guint8 signature [16];
|
||||
guint32 version;
|
||||
} HeapProfHeader;
|
||||
|
||||
typedef struct {
|
||||
guint32 time;
|
||||
guint32 alloc_ctx;
|
||||
} HeapProfAllocationRec;
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
guint32 time;
|
||||
guint32 gc_num;
|
||||
HeapProfGcFreedRec freed [MONO_ZERO_LEN_ARRAY];
|
||||
} HeapProfGCRec;
|
||||
|
||||
|
||||
|
||||
static guint32
|
||||
get_delta_t (MonoProfiler *p)
|
||||
{
|
||||
return GetTickCount () - p->t_zero;
|
||||
}
|
||||
|
||||
static guint64
|
||||
write (MonoProfiler* p, gconstpointer data, guint32 size)
|
||||
{
|
||||
guint32 offset = p->foffset;
|
||||
p->foffset += size;
|
||||
fwrite (data, size, 1, p->out);
|
||||
return offset;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
MonoMethod* methods [BT_SIZE];
|
||||
} Backtrace;
|
||||
|
||||
typedef struct {
|
||||
guint32 len;
|
||||
guint32 methods [BT_SIZE];
|
||||
} IdxBacktrace;
|
||||
|
||||
typedef struct {
|
||||
MonoClass* klass;
|
||||
guint32 size;
|
||||
Backtrace bt;
|
||||
} AllocationCtx;
|
||||
|
||||
typedef struct {
|
||||
guint32 klass;
|
||||
guint32 size;
|
||||
guint32 bt;
|
||||
} IdxAllocationCtx;
|
||||
|
||||
|
||||
static guint32
|
||||
get_method_idx (MonoProfiler *p, MonoMethod* m)
|
||||
{
|
||||
guint32 idx_plus_one;
|
||||
|
||||
if (!(idx_plus_one = GPOINTER_TO_UINT (g_hash_table_lookup (p->method_to_table_idx, m)))) {
|
||||
char* name = mono_method_full_name (m, TRUE);
|
||||
g_ptr_array_add (p->method_table, name);
|
||||
idx_plus_one = p->method_table->len;
|
||||
|
||||
g_hash_table_insert (p->method_to_table_idx, m, idx_plus_one);
|
||||
}
|
||||
|
||||
return idx_plus_one - 1;
|
||||
}
|
||||
|
||||
static guint32
|
||||
get_type_idx (MonoProfiler *p, MonoClass* klass)
|
||||
{
|
||||
guint32 idx_plus_one;
|
||||
|
||||
if (!(idx_plus_one = GPOINTER_TO_UINT (g_hash_table_lookup (p->klass_to_table_idx, klass)))) {
|
||||
char* name = mono_type_get_full_name (mono_class_get_type (klass));
|
||||
g_ptr_array_add (p->klass_table, name);
|
||||
idx_plus_one = p->klass_table->len;
|
||||
|
||||
g_hash_table_insert (p->klass_to_table_idx, klass, idx_plus_one);
|
||||
}
|
||||
|
||||
return idx_plus_one - 1;
|
||||
}
|
||||
|
||||
|
||||
static guint32
|
||||
get_bt_idx (MonoProfiler *p, Backtrace* bt)
|
||||
{
|
||||
guint32 idx_plus_one;
|
||||
|
||||
if (!(idx_plus_one = GPOINTER_TO_UINT (g_hash_table_lookup (p->bt_to_table_idx, bt)))) {
|
||||
|
||||
IdxBacktrace* ibt = g_new0 (IdxBacktrace, 1);
|
||||
for (ibt->len = 0; ibt->len < BT_SIZE; ibt->len ++) {
|
||||
if (! bt->methods [ibt->len])
|
||||
break;
|
||||
ibt->methods [ibt->len] = get_method_idx (p, bt->methods [ibt->len]);
|
||||
}
|
||||
|
||||
g_ptr_array_add (p->bt_table, ibt);
|
||||
idx_plus_one = p->bt_table->len;
|
||||
|
||||
g_hash_table_insert (p->bt_to_table_idx, g_memdup (bt, sizeof (*bt)), idx_plus_one);
|
||||
}
|
||||
|
||||
return idx_plus_one - 1;
|
||||
}
|
||||
|
||||
|
||||
static guint32
|
||||
get_ctx_idx (MonoProfiler *p, AllocationCtx* ctx)
|
||||
{
|
||||
guint32 idx_plus_one;
|
||||
|
||||
if (!(idx_plus_one = GPOINTER_TO_UINT (g_hash_table_lookup (p->ctx_to_table_idx, ctx)))) {
|
||||
|
||||
IdxAllocationCtx* ictx = g_new0 (IdxAllocationCtx, 1);
|
||||
|
||||
ictx->klass = get_type_idx (p, ctx->klass);
|
||||
ictx->size = ctx->size;
|
||||
ictx->bt = get_bt_idx (p, &ctx->bt);
|
||||
|
||||
g_ptr_array_add (p->ctx_table, ictx);
|
||||
idx_plus_one = p->ctx_table->len;
|
||||
|
||||
g_hash_table_insert (p->ctx_to_table_idx, g_memdup (ctx, sizeof (*ctx)), idx_plus_one);
|
||||
}
|
||||
|
||||
return idx_plus_one - 1;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
AllocationCtx* c;
|
||||
int pos;
|
||||
} AllocBTData;
|
||||
|
||||
static gboolean
|
||||
get_bt (MonoMethod *m, gint no, gint ilo, gboolean managed, AllocBTData* data)
|
||||
{
|
||||
if (!managed)
|
||||
return FALSE;
|
||||
|
||||
data->c->bt.methods [data->pos++] = m;
|
||||
|
||||
return data->pos == BT_SIZE;
|
||||
}
|
||||
|
||||
static void
|
||||
write_allocation (MonoProfiler *p, MonoObject *obj, MonoClass *klass)
|
||||
{
|
||||
AllocBTData btd = {0};
|
||||
AllocationCtx c = {0};
|
||||
guint32 offset;
|
||||
HeapProfAllocationRec rec;
|
||||
AllocRec* arec = g_new0 (AllocRec, 1);
|
||||
|
||||
btd.c = &c;
|
||||
|
||||
mono_stack_walk_no_il (get_bt, &btd);
|
||||
|
||||
c.klass = klass;
|
||||
c.size = mono_object_get_size (obj);
|
||||
|
||||
hp_lock_enter ();
|
||||
|
||||
rec.time = leu32 (get_delta_t (p));
|
||||
rec.alloc_ctx = leu32 (get_ctx_idx (p, &c));
|
||||
|
||||
offset = write (p, &rec, sizeof (rec));
|
||||
|
||||
arec->rec.time = rec.time;
|
||||
arec->rec.alloc_ctx = rec.alloc_ctx;
|
||||
arec->rec.alloc_pos = leu64 (offset);
|
||||
arec->obj = obj;
|
||||
arec->next = p->live_allocs;
|
||||
p->live_allocs = arec;
|
||||
|
||||
hp_lock_leave ();
|
||||
}
|
||||
|
||||
static void
|
||||
prof_marks_set (MonoProfiler *p, int gc_num)
|
||||
{
|
||||
HeapProfGCRec rec;
|
||||
|
||||
hp_lock_enter ();
|
||||
|
||||
rec.time = leu32 (get_delta_t (p) | (1 << 31));
|
||||
rec.gc_num = leu32 (gc_num);
|
||||
|
||||
write (p, &rec, sizeof (rec));
|
||||
|
||||
AllocRec *l, *next = NULL, *prev = NULL;
|
||||
for (l = p->live_allocs; l; l = next) {
|
||||
next = l->next;
|
||||
|
||||
if (! mono_profiler_mark_set (l->obj)) {
|
||||
write (p, &l->rec, sizeof (l->rec));
|
||||
|
||||
if (prev)
|
||||
prev->next = next;
|
||||
else
|
||||
p->live_allocs = next;
|
||||
|
||||
g_free (l);
|
||||
} else
|
||||
prev = l;
|
||||
}
|
||||
|
||||
{
|
||||
HeapProfGcFreedRec null = {0};
|
||||
write (p, &null, sizeof (null));
|
||||
}
|
||||
|
||||
hp_lock_leave ();
|
||||
}
|
||||
|
||||
static void
|
||||
write_enc_int (MonoProfiler*p, int v)
|
||||
{
|
||||
do {
|
||||
int high = (v >> 7) & 0x01ffffff;
|
||||
guint8 b = (guint8) (v & 0x7f);
|
||||
|
||||
if (high != 0) {
|
||||
b = (guint8) (b | 0x80);
|
||||
}
|
||||
|
||||
write (p, &b, sizeof (b));
|
||||
v = high;
|
||||
} while (v);
|
||||
}
|
||||
|
||||
static void
|
||||
write_string_table (MonoProfiler* p, GPtrArray* arr)
|
||||
{
|
||||
int i;
|
||||
guint32 size = leu32 (arr->len);
|
||||
write (p, &size, sizeof (size));
|
||||
|
||||
for (i = 0; i < arr->len; i ++) {
|
||||
char* s = g_ptr_array_index (arr, i);
|
||||
int l = strlen (s);
|
||||
|
||||
write_enc_int (p, l);
|
||||
write (p, s, l);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
write_bt_table (MonoProfiler* p)
|
||||
{
|
||||
GPtrArray* arr = p->bt_table;
|
||||
int i;
|
||||
guint32 size = leu32 (arr->len);
|
||||
write (p, &size, sizeof (size));
|
||||
|
||||
for (i = 0; i < arr->len; i ++) {
|
||||
IdxBacktrace* b = g_ptr_array_index (arr, i);
|
||||
write (p, b, sizeof (*b));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
write_ctx_table (MonoProfiler* p)
|
||||
{
|
||||
GPtrArray* arr = p->ctx_table;
|
||||
int i;
|
||||
guint32 size = leu32 (arr->len);
|
||||
|
||||
write (p, &size, sizeof (size));
|
||||
|
||||
for (i = 0; i < arr->len; i ++) {
|
||||
IdxAllocationCtx* c = g_ptr_array_index (arr, i);
|
||||
|
||||
|
||||
write (p, c, sizeof (*c));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
write_meta_header (MonoProfiler* p)
|
||||
{
|
||||
HeapProfHeader h;
|
||||
|
||||
memcpy (h.signature, heap_prof_md_sig, sizeof (heap_prof_md_sig));
|
||||
h.version = leu32 (heap_prof_version);
|
||||
write (p, &h, sizeof (h));
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
write_metadata_file (MonoProfiler* p)
|
||||
{
|
||||
write_meta_header (p);
|
||||
|
||||
write_string_table (p, p->klass_table);
|
||||
write_string_table (p, p->method_table);
|
||||
write_bt_table (p);
|
||||
write_ctx_table (p);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
mono_heap_prof_shutdown (MonoProfiler *p)
|
||||
{
|
||||
guint32 eofevent = -1;
|
||||
guint64 meta_offset;
|
||||
|
||||
meta_offset = write (p, &eofevent, sizeof (eofevent)) + sizeof (eofevent);
|
||||
|
||||
write_metadata_file (p);
|
||||
|
||||
meta_offset = leu64 (meta_offset);
|
||||
|
||||
write (p, &meta_offset, sizeof (meta_offset));
|
||||
|
||||
fclose (p->out);
|
||||
}
|
||||
|
||||
static void
|
||||
write_header (MonoProfiler* p)
|
||||
{
|
||||
HeapProfHeader h;
|
||||
|
||||
memcpy (h.signature, heap_prof_dump_sig, sizeof (heap_prof_dump_sig));
|
||||
h.version = leu32 (heap_prof_version);
|
||||
write (p, &h, sizeof (h));
|
||||
}
|
||||
|
||||
static guint
|
||||
ctx_hash (const AllocationCtx* c)
|
||||
{
|
||||
const guint* x = c;
|
||||
int i, h = 0;
|
||||
|
||||
for (i = 0; i < sizeof (*c) / sizeof (*x); i ++) {
|
||||
h *= 31;
|
||||
h += x [i];
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
ctx_eq (const AllocationCtx* a, const AllocationCtx* b)
|
||||
{
|
||||
return !memcmp (a, b, sizeof (*a));
|
||||
}
|
||||
|
||||
static guint
|
||||
bt_hash (const Backtrace* c)
|
||||
{
|
||||
const guint* x = c;
|
||||
int i, h = 0;
|
||||
|
||||
for (i = 0; i < sizeof (*c) / sizeof (*x); i ++) {
|
||||
h *= 31;
|
||||
h += x [i];
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
bt_eq (const Backtrace* a, const Backtrace* b)
|
||||
{
|
||||
return !memcmp (a, b, sizeof (*a));
|
||||
}
|
||||
|
||||
void
|
||||
mono_profiler_startup (const char *desc)
|
||||
{
|
||||
const char* file;
|
||||
char* dump_file;
|
||||
MonoProfiler* p = g_new0 (MonoProfiler, 1);
|
||||
|
||||
InitializeCriticalSection (&hp_lock);
|
||||
|
||||
g_assert (! strncmp (desc, "heap", 4));
|
||||
|
||||
if (strncmp (desc, "heap:", 5))
|
||||
g_error ("You need to specify an output file for the heap profiler with --profile=heap:outfile");
|
||||
|
||||
p->file = strdup (desc + 5);
|
||||
|
||||
p->klass_to_table_idx = g_hash_table_new (NULL, NULL);
|
||||
p->method_to_table_idx = g_hash_table_new (NULL, NULL);
|
||||
p->bt_to_table_idx = g_hash_table_new (bt_hash, bt_eq);
|
||||
p->ctx_to_table_idx = g_hash_table_new (ctx_hash, ctx_eq);
|
||||
|
||||
p->klass_table = g_ptr_array_new ();
|
||||
p->method_table = g_ptr_array_new ();
|
||||
p->bt_table = g_ptr_array_new ();
|
||||
p->ctx_table = g_ptr_array_new ();
|
||||
|
||||
|
||||
p->out = fopen (p->file, "w+");
|
||||
p->t_zero = GetTickCount ();
|
||||
|
||||
write_header (p);
|
||||
|
||||
mono_profiler_install_allocation (write_allocation);
|
||||
mono_profiler_install_gc (prof_marks_set);
|
||||
mono_profiler_set_events (MONO_PROFILE_ALLOCATIONS | MONO_PROFILE_GC);
|
||||
|
||||
mono_profiler_install (p, mono_heap_prof_shutdown);
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
2005-01-09 Ben Maurer <bmaurer@ximian.com>
|
||||
|
||||
* *: Initial Import.
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
CLEANFILES = mono-heap-prof-view.exe mono-heap-prof-view
|
||||
|
||||
mono_heap_prof_view_SCRIPTS = mono-heap-prof-view.exe
|
||||
mono_heap_prof_view_DATA = mono-heap-prof-view.exe.config
|
||||
mono_heap_prof_viewdir = $(prefix)/lib/mono-heap-prof
|
||||
|
||||
common_sources = \
|
||||
$(srcdir)/common/TypeTabulator.cs \
|
||||
$(srcdir)/common/ProfileReader.cs \
|
||||
$(srcdir)/common/BacktraceTabulator.cs \
|
||||
$(srcdir)/common/TypeGraphPlotter.cs
|
||||
|
||||
gtk_sources = \
|
||||
$(srcdir)/gui-gtk/TypeGraphViewer.cs \
|
||||
$(srcdir)/gui-gtk/BacktraceViewer.cs
|
||||
|
||||
|
||||
|
||||
mono_heap_prof_view_sources = $(common_sources) $(gtk_sources)
|
||||
|
||||
mono-heap-prof-view.exe : $(mono_heap_prof_view_sources)
|
||||
$(MCS) /out:$@ $(mono_heap_prof_view_sources) /r:System.Drawing.dll @GTKSHARP_LIBS@
|
||||
|
||||
scriptdir = $(bindir)
|
||||
|
||||
script_SCRIPTS = mono-heap-prof-view
|
|
@ -0,0 +1,183 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
|
||||
class AllocNode : IComparable {
|
||||
public int n_allocs;
|
||||
public int n_bytes;
|
||||
|
||||
public ArrayList Children;
|
||||
public AllocNode Parent;
|
||||
|
||||
public int type;
|
||||
public int [] bt;
|
||||
public int bt_len;
|
||||
|
||||
public BacktraceTabulator tab;
|
||||
|
||||
public AllocNode () {}
|
||||
|
||||
public AllocNode (int t, int [] bt, int bt_len, BacktraceTabulator tab)
|
||||
{
|
||||
this.type = t;
|
||||
this.bt = bt;
|
||||
this.bt_len = bt_len;
|
||||
this.tab = tab;
|
||||
|
||||
tab.nodes.Add (this, this);
|
||||
|
||||
if (bt_len != 0) {
|
||||
Parent = tab.LookupNode (t, bt, bt_len - 1);
|
||||
if (Parent.Children == null)
|
||||
Parent.Children = new ArrayList ();
|
||||
|
||||
Parent.Children.Add (this);
|
||||
} else {
|
||||
tab.type_nodes.Add (this);
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordAlloc (int c, int b)
|
||||
{
|
||||
for (AllocNode n = this; n != null; n = n.Parent) {
|
||||
n.n_allocs += c;
|
||||
n.n_bytes += b;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals (object o)
|
||||
{
|
||||
AllocNode a = o as AllocNode;
|
||||
if (a == null)
|
||||
return false;
|
||||
|
||||
if (a.type != type || a.bt_len != bt_len)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < bt_len; i ++)
|
||||
if (a.bt [i] != bt [i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode ()
|
||||
{
|
||||
int h = type ^ bt_len;
|
||||
|
||||
for (int i = 0; i < bt_len; i ++) {
|
||||
h ^= bt [i];
|
||||
h *= 31;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
public int CompareTo (object o)
|
||||
{
|
||||
int nb = ((AllocNode) o).n_bytes;
|
||||
|
||||
return nb - n_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
class BacktraceTabulator {
|
||||
public Hashtable nodes;
|
||||
public TypeTabulator t;
|
||||
|
||||
public ArrayList type_nodes;
|
||||
|
||||
public int total_size;
|
||||
|
||||
public BacktraceTabulator (TypeTabulator t, int [] context_data)
|
||||
{
|
||||
this.t = t;
|
||||
nodes = new Hashtable ();
|
||||
type_nodes = new ArrayList ();
|
||||
|
||||
for (int i = 0; i < context_data.Length; i ++) {
|
||||
|
||||
if (context_data [i] == 0)
|
||||
continue;
|
||||
|
||||
Context c = t.GetContext (i);
|
||||
int [] bt = t.GetBacktrace (c.Backtrace);
|
||||
LookupNode (c.Type, bt, bt.Length).RecordAlloc (context_data [i], context_data [i] * c.Size);
|
||||
|
||||
total_size += total_size;
|
||||
}
|
||||
|
||||
SortRecursive (type_nodes);
|
||||
}
|
||||
|
||||
static void SortRecursive (ArrayList ar)
|
||||
{
|
||||
if (ar == null)
|
||||
return;
|
||||
|
||||
ar.Sort ();
|
||||
|
||||
foreach (AllocNode an in ar)
|
||||
SortRecursive (an.Children);
|
||||
}
|
||||
|
||||
AllocNode temp_node = new AllocNode ();
|
||||
public AllocNode LookupNode (int t, int [] bt, int bt_len)
|
||||
{
|
||||
temp_node.type = t;
|
||||
temp_node.bt = bt;
|
||||
temp_node.bt_len = bt_len;
|
||||
|
||||
AllocNode ret = nodes [temp_node] as AllocNode;
|
||||
|
||||
if (ret != null)
|
||||
return ret;
|
||||
|
||||
return new AllocNode (t, bt, bt_len, this);
|
||||
}
|
||||
|
||||
public void Dump ()
|
||||
{
|
||||
foreach (AllocNode an in type_nodes) {
|
||||
|
||||
if (an.n_bytes < total_size * .15)
|
||||
continue;
|
||||
|
||||
Console.WriteLine ("{0} -- {1} bytes, {2} objects", t.GetTypeName (an.type), an.n_bytes, an.n_allocs);
|
||||
|
||||
WriteAllocSitesRecursive (an.Children, "\t");
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteAllocSitesRecursive (ArrayList ar, string pre)
|
||||
{
|
||||
if (ar == null)
|
||||
return;
|
||||
|
||||
foreach (AllocNode an in ar) {
|
||||
|
||||
|
||||
Console.WriteLine (pre + "{0} -- {1} bytes, {2} objects", t.GetMethodName (an.bt [an.bt_len - 1]), an.n_bytes, an.n_allocs);
|
||||
WriteAllocSitesRecursive (an.Children, pre + "\t");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
static void Main (string [] args)
|
||||
{
|
||||
TypeTabulator t = new TypeTabulator (args [0]);
|
||||
t.Read ();
|
||||
t.Process ();
|
||||
|
||||
|
||||
foreach (TimeData d in t.Data) {
|
||||
|
||||
if (d.TotalSize == 0)
|
||||
continue;
|
||||
|
||||
Console.WriteLine ("Heap at {0} ms", d.Time);
|
||||
Console.WriteLine ("Total heap size {0}", d.TotalSize);
|
||||
|
||||
new BacktraceTabulator (t, d.ContextData).Dump ();
|
||||
}
|
||||
}*/
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
2005-01-09 Ben Maurer <bmaurer@ximian.com>
|
||||
|
||||
* *: Initial Import.
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
|
||||
|
||||
public abstract class ProfileReader {
|
||||
|
||||
Metadata mr;
|
||||
BinaryReader br;
|
||||
string name;
|
||||
|
||||
public ProfileReader (string name)
|
||||
{
|
||||
this.name = name;
|
||||
mr = new Metadata (name);
|
||||
//mr.Dump ();
|
||||
}
|
||||
|
||||
public void Read ()
|
||||
{
|
||||
using (br = new BinaryReader (File.OpenRead (name))) {
|
||||
ProfilerSignature.ReadHeader (br, true);
|
||||
|
||||
while (true) {
|
||||
int time = br.ReadInt32 ();
|
||||
|
||||
// end of file
|
||||
if (time == -1)
|
||||
return;
|
||||
|
||||
if ((time & (int)(1 << 31)) == 0) {
|
||||
// allocation
|
||||
int ctx = br.ReadInt32 ();
|
||||
|
||||
AllocationSeen (time, GetContext (ctx), br.BaseStream.Position);
|
||||
|
||||
} else {
|
||||
time &= int.MaxValue;
|
||||
|
||||
int gc_num = br.ReadInt32 ();
|
||||
|
||||
GcSeen (time, gc_num);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ReadGcFreed ()
|
||||
{
|
||||
while (true) {
|
||||
long pos = br.ReadInt64 ();
|
||||
int alloc_time = br.ReadInt32 ();
|
||||
int alloc_ctx = br.ReadInt32 ();
|
||||
|
||||
if (pos == 0 && alloc_time == 0 && alloc_ctx == 0)
|
||||
return;
|
||||
|
||||
GcFreedSeen (alloc_time, GetContext (alloc_ctx), pos);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void AllocationSeen (int time, Context ctx, long pos);
|
||||
protected abstract void GcSeen (int time, int gc_num);
|
||||
protected abstract void GcFreedSeen (int time, Context ctx, long pos);
|
||||
|
||||
public string GetTypeName (int idx)
|
||||
{
|
||||
return mr.GetTypeName (idx);
|
||||
}
|
||||
|
||||
public string GetMethodName (int idx)
|
||||
{
|
||||
return mr.GetMethodName (idx);
|
||||
}
|
||||
|
||||
public int [] GetBacktrace (int idx)
|
||||
{
|
||||
return mr.GetBacktrace (idx);
|
||||
}
|
||||
|
||||
public Context GetContext (int idx)
|
||||
{
|
||||
return mr.GetContext (idx);
|
||||
}
|
||||
|
||||
|
||||
public int TypeTableSize { get { return mr.TypeTableSize; } }
|
||||
public int ContextTableSize { get { return mr.ContextTableSize; } }
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class Metadata {
|
||||
|
||||
const int BacktraceSize = 5;
|
||||
|
||||
|
||||
string [] typeTable;
|
||||
string [] methodTable;
|
||||
int [][] backtraceTable;
|
||||
Context [] contextTable;
|
||||
|
||||
public int TypeTableSize { get { return typeTable.Length; } }
|
||||
public int ContextTableSize { get { return contextTable.Length; } }
|
||||
|
||||
public string GetTypeName (int idx)
|
||||
{
|
||||
return typeTable [idx];
|
||||
}
|
||||
|
||||
public string GetMethodName (int idx)
|
||||
{
|
||||
return methodTable [idx];
|
||||
}
|
||||
|
||||
public int [] GetBacktrace (int idx)
|
||||
{
|
||||
return backtraceTable [idx];
|
||||
}
|
||||
|
||||
public Context GetContext (int idx)
|
||||
{
|
||||
return contextTable [idx];
|
||||
}
|
||||
|
||||
|
||||
public Metadata (string name)
|
||||
{
|
||||
using (BinaryReader br = new BinaryReader (File.OpenRead (name))) {
|
||||
|
||||
br.BaseStream.Seek (-8, SeekOrigin.End);
|
||||
br.BaseStream.Seek (br.ReadInt64 (), SeekOrigin.Begin);
|
||||
|
||||
ProfilerSignature.ReadHeader (br, false);
|
||||
|
||||
typeTable = ReadStringTable (br);
|
||||
methodTable = ReadStringTable (br);
|
||||
backtraceTable = ReadBacktraceTable (br);
|
||||
contextTable = ReadContextTable (br);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dump ()
|
||||
{
|
||||
foreach (Context c in contextTable) {
|
||||
Console.WriteLine ("size {0}, type {1}", c.Size, typeTable [c.Type]);
|
||||
foreach (int i in backtraceTable [c.Backtrace])
|
||||
Console.WriteLine (" {0} {1}", i, methodTable [i]);
|
||||
Console.WriteLine ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string [] ReadStringTable (BinaryReader br)
|
||||
{
|
||||
int sz = br.ReadInt32 ();
|
||||
|
||||
string [] ret = new string [sz];
|
||||
|
||||
for (int i = 0; i < sz; i ++)
|
||||
ret [i] = br.ReadString ();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int [] [] ReadBacktraceTable (BinaryReader br)
|
||||
{
|
||||
int sz = br.ReadInt32 ();
|
||||
|
||||
int [][] t = new int [sz] [];
|
||||
|
||||
for (int i = 0; i < sz; i ++) {
|
||||
int szz = br.ReadInt32 ();
|
||||
|
||||
t [i] = new int [szz];
|
||||
for (int j = 0; j < BacktraceSize; j ++) {
|
||||
int n = br.ReadInt32 ();
|
||||
if (j < szz)
|
||||
t [i] [j] = n;
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
Context [] ReadContextTable (BinaryReader br)
|
||||
{
|
||||
|
||||
int sz = br.ReadInt32 ();
|
||||
Context [] d = new Context [sz];
|
||||
|
||||
for (int i = 0; i < sz; i ++) {
|
||||
d [i].Id = i;
|
||||
d [i].Type = br.ReadInt32 ();
|
||||
d [i].Size = br.ReadInt32 ();
|
||||
d [i].Backtrace = br.ReadInt32 ();
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
class ProfilerSignature {
|
||||
static readonly byte [] DumpSignature = {
|
||||
0x68, 0x30, 0xa4, 0x57, 0x18, 0xec, 0xd6, 0xa1,
|
||||
0x61, 0x9c, 0x1d, 0x43, 0xe1, 0x47, 0x27, 0xb6
|
||||
};
|
||||
|
||||
static readonly byte [] MetaSignature = {
|
||||
0xe4, 0x37, 0x29, 0x60, 0x3e, 0x31, 0x89, 0x12,
|
||||
0xaa, 0x93, 0xc8, 0x76, 0xf4, 0x6a, 0x95, 0x11
|
||||
};
|
||||
|
||||
const int Version = 2;
|
||||
|
||||
public static void ReadHeader (BinaryReader br, bool is_dump)
|
||||
{
|
||||
byte [] s = is_dump ? DumpSignature : MetaSignature;
|
||||
byte [] sig = br.ReadBytes (s.Length);
|
||||
|
||||
for (int i = 0; i < s.Length; i ++) {
|
||||
if (sig [i] != s [i])
|
||||
throw new Exception ("Invalid file format");
|
||||
}
|
||||
|
||||
int ver = br.ReadInt32 ();
|
||||
if (ver != Version)
|
||||
throw new Exception (String.Format ("Wrong version: expected {0}, got {1}", Version, ver));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct Context {
|
||||
public int Id;
|
||||
public int Type;
|
||||
public int Size;
|
||||
public int Backtrace;
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
|
||||
class TimePoint {
|
||||
public int Time;
|
||||
public int X;
|
||||
public int OtherSize;
|
||||
public int [] TypeData;
|
||||
public TimeData Data;
|
||||
}
|
||||
|
||||
class Plotter {
|
||||
int xsize, ysize;
|
||||
TypeTabulator d;
|
||||
TypeList tl;
|
||||
|
||||
public Plotter (int xsize, int ysize, TypeTabulator d, TypeList tl)
|
||||
{
|
||||
this.xsize = xsize;
|
||||
this.ysize = ysize;
|
||||
this.d = d;
|
||||
this.tl = tl;
|
||||
|
||||
FixupData ();
|
||||
}
|
||||
|
||||
public ArrayList data;
|
||||
|
||||
int end_t;
|
||||
|
||||
void FixupData ()
|
||||
{
|
||||
end_t = ((TimeData) d.Data [d.Data.Count - 1]).Time;
|
||||
|
||||
data = new ArrayList ();
|
||||
int size_threshold = d.MaxSize / ysize;
|
||||
|
||||
foreach (TimeData td in d.Data) {
|
||||
if (td.TotalSize < size_threshold)
|
||||
continue;
|
||||
|
||||
TimePoint p = new TimePoint ();
|
||||
|
||||
data.Add (p);
|
||||
|
||||
p.Data = td;
|
||||
p.Time = td.Time;
|
||||
p.X = td.Time * xsize / end_t;
|
||||
p.OtherSize = td.OtherSize;
|
||||
p.TypeData = new int [tl.TypeIndexes.Length];
|
||||
|
||||
for (int i = 0; i < tl.TypeIndexes.Length; i ++) {
|
||||
int ty = tl.TypeIndexes [i];
|
||||
|
||||
if (td.TypeData [ty] < size_threshold) {
|
||||
p.OtherSize += td.TypeData [ty];
|
||||
continue;
|
||||
}
|
||||
|
||||
p.TypeData [i] = td.TypeData [ty];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw (Graphics g)
|
||||
{
|
||||
|
||||
int [] offsets = new int [data.Count];
|
||||
Point [] prev = new Point [data.Count];
|
||||
|
||||
for (int i = 0; i < prev.Length; i ++)
|
||||
prev [i].Y = ysize;
|
||||
|
||||
prev [0].X = xsize;
|
||||
|
||||
for (int i = -1; i < tl.TypeIndexes.Length; i ++) {
|
||||
Point [] line = new Point [data.Count];
|
||||
|
||||
int j = 0;
|
||||
foreach (TimePoint tp in data) {
|
||||
|
||||
|
||||
int psize;
|
||||
|
||||
if (i == -1)
|
||||
psize = tp.OtherSize;
|
||||
else
|
||||
psize = tp.TypeData [i];
|
||||
|
||||
line [j].X = tp.X;
|
||||
line [j].Y = ysize - checked (offsets [j] + (int)((long)psize * (long) ysize / (long)d.MaxSize));
|
||||
offsets [j] = ysize - line [j].Y;
|
||||
j ++;
|
||||
}
|
||||
|
||||
GraphicsPath path = new GraphicsPath ();
|
||||
path.AddLines (line);
|
||||
path.AddLine (line [line.Length - 1], prev [0]);
|
||||
path.AddLines (prev);
|
||||
//path.CloseFigure ();
|
||||
|
||||
Brush b;
|
||||
if (i == -1)
|
||||
b = Brushes.DarkGray;
|
||||
else
|
||||
b = tl.TypeBrushes [i];
|
||||
|
||||
g.FillPath (b, path);
|
||||
|
||||
prev = line;
|
||||
Array.Reverse (prev, 0, prev.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RandomBrush {
|
||||
int i;
|
||||
|
||||
static Brush [] brushes = {
|
||||
Brushes.IndianRed,
|
||||
Brushes.BurlyWood,
|
||||
Brushes.Chocolate,
|
||||
Brushes.DarkGoldenrod,
|
||||
Brushes.PaleGoldenrod,
|
||||
Brushes.DarkOrange,
|
||||
Brushes.DarkSalmon,
|
||||
|
||||
};
|
||||
|
||||
public Brush Next ()
|
||||
{
|
||||
return brushes [i ++ % brushes.Length];
|
||||
}
|
||||
}
|
||||
|
||||
class TypeList {
|
||||
public long [] Sizes;
|
||||
public int [] TypeIndexes;
|
||||
public Brush [] TypeBrushes;
|
||||
public string [] Names;
|
||||
|
||||
public TypeList (TypeTabulator d)
|
||||
{
|
||||
int num = 0;
|
||||
|
||||
foreach (bool b in d.IsSizeLongEnough)
|
||||
if (b)
|
||||
num ++;
|
||||
|
||||
Sizes = new long [num];
|
||||
TypeIndexes = new int [num];
|
||||
Names = new string [num];
|
||||
TypeBrushes = new Brush [num];
|
||||
|
||||
num = 0;
|
||||
for (int i = 0; i < d.TotalTypeSizes.Length; i ++) {
|
||||
if (d.IsSizeLongEnough [i]) {
|
||||
Sizes [num] = d.TotalTypeSizes [i];
|
||||
TypeIndexes [num] = i;
|
||||
num ++;
|
||||
}
|
||||
}
|
||||
|
||||
Array.Sort (Sizes, TypeIndexes);
|
||||
|
||||
RandomBrush rb = new RandomBrush ();
|
||||
|
||||
for (int i = 0; i < Sizes.Length; i ++) {
|
||||
TypeBrushes [i] = rb.Next ();
|
||||
Names [i] = d.GetTypeName (TypeIndexes [i]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
|
||||
|
||||
class TimeData {
|
||||
public int Time;
|
||||
public int [] TypeData;
|
||||
public int [] ContextData;
|
||||
public int OtherSize;
|
||||
public int TotalSize;
|
||||
}
|
||||
|
||||
class TypeTabulator : ProfileReader {
|
||||
|
||||
const int DeltaT = 50;
|
||||
const double Threshold = .005;
|
||||
|
||||
|
||||
public ArrayList Data;
|
||||
public int MaxSize;
|
||||
public long [] TotalTypeSizes;
|
||||
public bool [] IsSizeLongEnough;
|
||||
|
||||
int [] current_type_data;
|
||||
int [] current_context_data;
|
||||
int last_time;
|
||||
|
||||
public TypeTabulator (string basename) : base (basename) {
|
||||
Data = new ArrayList ();
|
||||
current_type_data = new int [TypeTableSize];
|
||||
current_context_data = new int [ContextTableSize];
|
||||
}
|
||||
|
||||
void Split (int time)
|
||||
{
|
||||
TimeData td = new TimeData ();
|
||||
td.Time = time - 1;
|
||||
td.TypeData = (int []) current_type_data.Clone ();
|
||||
td.ContextData = (int []) current_context_data.Clone ();
|
||||
|
||||
foreach (int i in td.TypeData)
|
||||
td.TotalSize += i;
|
||||
|
||||
MaxSize = Math.Max (td.TotalSize, MaxSize);
|
||||
|
||||
Data.Add (td);
|
||||
}
|
||||
|
||||
void SplitIfNeeded (int time)
|
||||
{
|
||||
if (time < last_time + DeltaT)
|
||||
return;
|
||||
|
||||
Split (time - 1);
|
||||
|
||||
last_time = time;
|
||||
}
|
||||
|
||||
protected override void AllocationSeen (int time, Context ctx, long pos)
|
||||
{
|
||||
SplitIfNeeded (time);
|
||||
|
||||
current_type_data [ctx.Type] += ctx.Size;
|
||||
current_context_data [ctx.Id] ++;
|
||||
}
|
||||
|
||||
protected override void GcSeen (int time, int gc_num)
|
||||
{
|
||||
// Splitting twice here gives nice graphs, since you get a strait line
|
||||
Split (time);
|
||||
ReadGcFreed ();
|
||||
Split (time);
|
||||
|
||||
last_time = time;
|
||||
}
|
||||
|
||||
protected override void GcFreedSeen (int time, Context ctx, long pos)
|
||||
{
|
||||
current_type_data [ctx.Type] -= ctx.Size;
|
||||
current_context_data [ctx.Id] --;
|
||||
}
|
||||
|
||||
public void Dump ()
|
||||
{
|
||||
long [] sizes = (long []) TotalTypeSizes.Clone ();
|
||||
int [] indexes = new int [sizes.Length];
|
||||
|
||||
for (int i = 0; i < indexes.Length; i ++)
|
||||
indexes [i] = i;
|
||||
|
||||
Array.Sort (sizes, indexes);
|
||||
|
||||
Array.Reverse (sizes, 0, sizes.Length);
|
||||
Array.Reverse (indexes, 0, indexes.Length);
|
||||
|
||||
|
||||
foreach (TimeData d in Data) {
|
||||
|
||||
if (d.TotalSize == 0)
|
||||
continue;
|
||||
|
||||
Console.WriteLine ("Heap at {0} ms", d.Time);
|
||||
Console.WriteLine ("Total heap size {0}", d.TotalSize);
|
||||
|
||||
foreach (int ty in indexes) {
|
||||
if (!IsSizeLongEnough [ty])
|
||||
continue;
|
||||
|
||||
Console.WriteLine ("{0} ({2:p}) -- {1}", d.TypeData [ty], GetTypeName (ty), (double) d.TypeData [ty] / (double) d.TotalSize);
|
||||
}
|
||||
|
||||
Console.WriteLine ();
|
||||
}
|
||||
}
|
||||
|
||||
public void Process ()
|
||||
{
|
||||
int cutoff = (int) (MaxSize * Threshold);
|
||||
|
||||
TotalTypeSizes = new long [TypeTableSize];
|
||||
IsSizeLongEnough = new bool [TypeTableSize];
|
||||
|
||||
foreach (TimeData d in Data) {
|
||||
for (int i = 0; i < d.TypeData.Length; i ++) {
|
||||
TotalTypeSizes [i] += d.TypeData [i];
|
||||
|
||||
if (d.TypeData [i] > cutoff)
|
||||
IsSizeLongEnough [i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (TimeData d in Data) {
|
||||
for (int i = 0; i < d.TypeData.Length; i ++) {
|
||||
if (! IsSizeLongEnough [i]) {
|
||||
d.OtherSize += d.TypeData [i];
|
||||
d.TypeData [i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using Gtk;
|
||||
|
||||
class BacktraceViewerWindow : Window {
|
||||
TimeData data;
|
||||
TypeTabulator t;
|
||||
BacktraceTabulator bt;
|
||||
|
||||
BacktraceNodeStore ns;
|
||||
NodeView nv;
|
||||
|
||||
VBox box;
|
||||
|
||||
|
||||
|
||||
public BacktraceViewerWindow (TimeData data, TypeTabulator t) : base ("")
|
||||
{
|
||||
this.data = data;
|
||||
this.t = t;
|
||||
this.bt = new BacktraceTabulator (t, data.ContextData);
|
||||
|
||||
box = new VBox ();
|
||||
box.Spacing = 12;
|
||||
|
||||
this.Add (box);
|
||||
|
||||
box.PackStart (CreateHeader (), false, false, 0);
|
||||
|
||||
ns = new BacktraceNodeStore (data, t, bt);
|
||||
|
||||
ScrolledWindow sw = new ScrolledWindow ();
|
||||
sw.Add (ns.GetNodeView ());
|
||||
box.PackStart (sw, true, true, 0);
|
||||
|
||||
Title = string.Format ("Heap at {0} ms", data.Time);
|
||||
}
|
||||
|
||||
Widget CreateHeader ()
|
||||
{
|
||||
VBox vb = new VBox ();
|
||||
vb.Spacing = 12;
|
||||
vb.BorderWidth = 12;
|
||||
|
||||
Label l = new Label (string.Format ("<b>Heap at {0} ms</b>", data.Time));
|
||||
l.Xalign = 0;
|
||||
|
||||
l.UseMarkup = true;
|
||||
|
||||
vb.PackStart (l, false, false, 0);
|
||||
|
||||
HBox hb = new HBox ();
|
||||
hb.Spacing = 12;
|
||||
l = new Label ("Heap Size:");
|
||||
l.Xalign = 0;
|
||||
l.Xpad = 12;
|
||||
|
||||
hb.PackStart (l, false, false, 0);
|
||||
|
||||
l = new Label (FormatHelper.BytesToString (data.TotalSize));
|
||||
l.Xalign = 0;
|
||||
hb.PackStart (l, false, false, 0);
|
||||
|
||||
vb.PackStart (hb, false, false, 0);
|
||||
|
||||
return vb;
|
||||
}
|
||||
}
|
||||
|
||||
class BacktraceNodeStore : NodeStore {
|
||||
TimeData data;
|
||||
TypeTabulator t;
|
||||
BacktraceTabulator bt;
|
||||
|
||||
public BacktraceNodeStore (TimeData data, TypeTabulator t, BacktraceTabulator bt) : base (typeof (BacktraceNode))
|
||||
{
|
||||
this.data = data;
|
||||
this.t = t;
|
||||
this.bt = bt;
|
||||
|
||||
foreach (AllocNode an in bt.type_nodes) {
|
||||
BacktraceNode n = new BacktraceNode (data, t, an);
|
||||
ProcessNode (n);
|
||||
AddNode (n);
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessNode (BacktraceNode n)
|
||||
{
|
||||
AllocNode p = n.an;
|
||||
|
||||
if (p.Children == null)
|
||||
return;
|
||||
|
||||
foreach (AllocNode an in p.Children) {
|
||||
BacktraceNode nn = new BacktraceNode (data, t, an);
|
||||
ProcessNode (nn);
|
||||
n.AddChild (nn);
|
||||
}
|
||||
}
|
||||
|
||||
public NodeView GetNodeView ()
|
||||
{
|
||||
NodeView nv = new NodeView (this);
|
||||
nv.HeadersVisible = false;
|
||||
nv.AppendColumn ("Size", new CellRendererText (), new NodeCellDataFunc (GetNumBytes));
|
||||
nv.AppendColumn ("Num objects", new CellRendererText (), new NodeCellDataFunc (GetNumObjects));
|
||||
nv.AppendColumn ("Percent", new CellRendererText (), new NodeCellDataFunc (GetPercent));
|
||||
|
||||
nv.AppendColumn ("Source", new CellRendererText (), "text", 0);
|
||||
|
||||
|
||||
return nv;
|
||||
}
|
||||
|
||||
private void GetNumBytes (TreeViewColumn col, CellRenderer cell, ITreeNode node)
|
||||
{
|
||||
CellRendererText c = (CellRendererText) cell;
|
||||
BacktraceNode n = (BacktraceNode) node;
|
||||
|
||||
|
||||
|
||||
c.Text = FormatHelper.BytesToString (n.an.n_bytes);
|
||||
}
|
||||
|
||||
private void GetPercent (TreeViewColumn col, CellRenderer cell, ITreeNode node)
|
||||
{
|
||||
CellRendererText c = (CellRendererText) cell;
|
||||
BacktraceNode n = (BacktraceNode) node;
|
||||
|
||||
c.Text = String.Format ("{0:p}", (double) n.an.n_bytes / (double) n.data.TotalSize);
|
||||
}
|
||||
|
||||
private void GetNumObjects (TreeViewColumn col, CellRenderer cell, ITreeNode node)
|
||||
{
|
||||
CellRendererText c = (CellRendererText) cell;
|
||||
BacktraceNode n = (BacktraceNode) node;
|
||||
c.Text = n.an.n_allocs.ToString ();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
[TreeNode (ColumnCount = 1)]
|
||||
class BacktraceNode : TreeNode {
|
||||
public TimeData data;
|
||||
TypeTabulator t;
|
||||
public AllocNode an;
|
||||
|
||||
public BacktraceNode (TimeData data, TypeTabulator t, AllocNode an)
|
||||
{
|
||||
this.data = data;
|
||||
this.t = t;
|
||||
this.an = an;
|
||||
}
|
||||
|
||||
[TreeNodeValue (Column = 0)]
|
||||
public string Name {
|
||||
get {
|
||||
if (an.bt_len == 0)
|
||||
return t.GetTypeName (an.type);
|
||||
else
|
||||
return t.GetMethodName (an.bt [an.bt_len - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FormatHelper {
|
||||
public static string BytesToString (int cb)
|
||||
{
|
||||
const int K = 1024;
|
||||
const int M = 1024 * K;
|
||||
|
||||
if (cb > M)
|
||||
return String.Format ("{0:0.0} MB", (double) cb / (double) M);
|
||||
else if (cb > K)
|
||||
return String.Format ("{0:0.0} KB", (double) cb / (double) K);
|
||||
else
|
||||
return String.Format ("{0} bytes", cb);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
2005-01-09 Ben Maurer <bmaurer@ximian.com>
|
||||
|
||||
* *: Initial Import.
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using Gtk;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
class X {
|
||||
static DrawingArea d;
|
||||
static TypeTabulator t;
|
||||
static TypeList tl;
|
||||
static HPaned paned;
|
||||
|
||||
static void Main (string [] args)
|
||||
{
|
||||
int b = Environment.TickCount;
|
||||
t = new TypeTabulator (args [0]);
|
||||
t.Read ();
|
||||
t.Process ();
|
||||
//t.Dump ();
|
||||
tl = new TypeList (t);
|
||||
|
||||
Console.WriteLine (Environment.TickCount - b);
|
||||
|
||||
RunGtk ();
|
||||
}
|
||||
|
||||
static void RunGtk ()
|
||||
{
|
||||
Application.Init ();
|
||||
Gtk.Window w = new Gtk.Window ("Ben's little profiler");
|
||||
|
||||
w.DeleteEvent += Window_Delete;
|
||||
|
||||
paned = new HPaned ();
|
||||
|
||||
d = new PrettyGraphic (t, tl);
|
||||
|
||||
|
||||
|
||||
ScrolledWindow sw = new ScrolledWindow ();
|
||||
sw.Add (new TypeListNodeStore (tl).GetNodeView ());
|
||||
|
||||
paned.Add1 (d);
|
||||
paned.Add2 (sw);
|
||||
|
||||
|
||||
w.Add (paned);
|
||||
|
||||
w.ShowAll ();
|
||||
Application.Run ();
|
||||
}
|
||||
|
||||
static void Window_Delete (object obj, DeleteEventArgs args)
|
||||
{
|
||||
Application.Quit ();
|
||||
args.RetVal = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TypeListNodeStore : NodeStore {
|
||||
TypeList tl;
|
||||
static ColorCellRenderer r;
|
||||
|
||||
public TypeListNodeStore (TypeList tl) : base (typeof (TypeListTreeNode))
|
||||
{
|
||||
this.tl = tl;
|
||||
|
||||
for (int i = tl.Sizes.Length - 1; i >= 0; i --)
|
||||
AddNode (new TypeListTreeNode (tl, i));
|
||||
}
|
||||
|
||||
public NodeView GetNodeView ()
|
||||
{
|
||||
r = new ColorCellRenderer ();
|
||||
NodeView nv = new NodeView (this);
|
||||
nv.HeadersVisible = false;
|
||||
nv.AppendColumn ("Color",r, new NodeCellDataFunc (GetColorData));
|
||||
nv.AppendColumn ("Type", new CellRendererText (), "text", 1);
|
||||
|
||||
return nv;
|
||||
}
|
||||
|
||||
private void GetColorData (TreeViewColumn col, CellRenderer cell, ITreeNode node)
|
||||
{
|
||||
ColorCellRenderer c = (ColorCellRenderer) cell;
|
||||
c.Idx = ((TypeListTreeNode) node).idx;
|
||||
c.List = tl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ColorCellRenderer : CellRenderer {
|
||||
|
||||
public int Idx;
|
||||
public TypeList List;
|
||||
|
||||
Gdk.Color ColorFromBrush (Brush b)
|
||||
{
|
||||
Color c = ((SolidBrush) b).Color;
|
||||
return new Gdk.Color (c.R, c.G, c.B);
|
||||
}
|
||||
|
||||
public override void GetSize (Widget widget, ref Gdk.Rectangle cell_area, out int x_offset, out int y_offset, out int width, out int height)
|
||||
{
|
||||
int calc_width = (int) this.Xpad * 2 + 10;
|
||||
int calc_height = (int) this.Ypad * 2 + 10;
|
||||
|
||||
width = calc_width;
|
||||
height = calc_height;
|
||||
|
||||
x_offset = 0;
|
||||
y_offset = 0;
|
||||
if (!cell_area.Equals (Gdk.Rectangle.Zero)) {
|
||||
x_offset = (int) (this.Xalign * (cell_area.Width - calc_width));
|
||||
x_offset = Math.Max (x_offset, 0);
|
||||
|
||||
y_offset = (int) (this.Yalign * (cell_area.Height - calc_height));
|
||||
y_offset = Math.Max (y_offset, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Render (Gdk.Drawable window, Widget widget, Gdk.Rectangle background_area,
|
||||
Gdk.Rectangle cell_area, Gdk.Rectangle expose_area, CellRendererState flags)
|
||||
{
|
||||
int width = 0, height = 0, x_offset = 0, y_offset = 0;
|
||||
GetSize (widget, ref cell_area, out x_offset, out y_offset, out width, out height);
|
||||
|
||||
width -= (int) this.Xpad * 2;
|
||||
height -= (int) this.Ypad * 2;
|
||||
|
||||
Gdk.Rectangle clipping_area = new Gdk.Rectangle ((int) (cell_area.X + x_offset + this.Xpad),
|
||||
(int) (cell_area.Y + y_offset + this.Ypad), width - 1, height - 1);
|
||||
|
||||
|
||||
using (Gdk.GC gc = new Gdk.GC (window)) {
|
||||
gc.RgbFgColor = ColorFromBrush (List.TypeBrushes [Idx]);
|
||||
window.DrawRectangle (gc,
|
||||
true,
|
||||
clipping_area);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TreeNode (ColumnCount = 2)]
|
||||
class TypeListTreeNode : TreeNode {
|
||||
|
||||
TypeList tl;
|
||||
public int idx;
|
||||
|
||||
public TypeListTreeNode (TypeList tl, int idx)
|
||||
{
|
||||
this.tl = tl;
|
||||
this.idx = idx;
|
||||
}
|
||||
|
||||
[TreeNodeValue (Column = 0)]
|
||||
public Brush Color {
|
||||
get { return tl.TypeBrushes [idx]; }
|
||||
}
|
||||
|
||||
[TreeNodeValue (Column = 1)]
|
||||
public string Name {
|
||||
get { return tl.Names [idx]; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// A sample using inheritance to draw
|
||||
//
|
||||
class PrettyGraphic : DrawingArea {
|
||||
|
||||
TypeTabulator t;
|
||||
TypeList tl;
|
||||
|
||||
Gdk.Pixmap bitmap_cache;
|
||||
//System.Drawing.Bitmap bitmap_cache;
|
||||
Gdk.Rectangle current_allocation; // The current allocation.
|
||||
bool allocated = false;
|
||||
Plotter plot;
|
||||
|
||||
public PrettyGraphic (TypeTabulator t, TypeList tl)
|
||||
{
|
||||
Events |= Gdk.EventMask.ButtonPressMask;
|
||||
|
||||
this.t = t;
|
||||
this.tl = tl;
|
||||
SetSizeRequest (500, 500);
|
||||
}
|
||||
|
||||
protected override bool OnExposeEvent (Gdk.EventExpose args)
|
||||
{
|
||||
|
||||
if (bitmap_cache == null) {
|
||||
bitmap_cache = new Gdk.Pixmap (GdkWindow, current_allocation.Width, current_allocation.Height, -1);
|
||||
bitmap_cache.DrawRectangle (Style.WhiteGC, true, 0, 0,
|
||||
current_allocation.Width, current_allocation.Height);
|
||||
|
||||
using (Graphics g = Gdk.Graphics.FromDrawable (bitmap_cache)) {
|
||||
plot = new Plotter (current_allocation.Width, current_allocation.Height, t, tl);
|
||||
plot.Draw (g);
|
||||
}
|
||||
}
|
||||
|
||||
Gdk.Rectangle area = args.Area;
|
||||
GdkWindow.DrawDrawable (Style.BlackGC,
|
||||
bitmap_cache,
|
||||
area.X, area.Y,
|
||||
area.X, area.Y,
|
||||
area.Width, area.Height);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnSizeAllocated (Gdk.Rectangle allocation)
|
||||
{
|
||||
allocated = true;
|
||||
current_allocation = allocation;
|
||||
UpdateCache ();
|
||||
base.OnSizeAllocated (allocation);
|
||||
}
|
||||
|
||||
void UpdateCache ()
|
||||
{
|
||||
if (bitmap_cache != null)
|
||||
bitmap_cache.Dispose ();
|
||||
|
||||
bitmap_cache = null;
|
||||
}
|
||||
|
||||
protected override bool OnButtonPressEvent (Gdk.EventButton e)
|
||||
{
|
||||
if (e.Button != 3)
|
||||
return false;
|
||||
|
||||
Console.WriteLine ("Button press at ({0}, {1})", e.X, e.Y);
|
||||
|
||||
foreach (TimePoint tp in plot.data) {
|
||||
if (tp.X >= e.X) {
|
||||
Console.WriteLine ("Found {0}", tp.Time);
|
||||
|
||||
new BacktraceViewerWindow (tp.Data, t).ShowAll ();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
namespace Gdk {
|
||||
public class Graphics {
|
||||
|
||||
[DllImport("libgdk-win32-2.0-0.dll")]
|
||||
internal static extern IntPtr gdk_x11_drawable_get_xdisplay (IntPtr raw);
|
||||
|
||||
[DllImport("libgdk-win32-2.0-0.dll")]
|
||||
internal static extern IntPtr gdk_x11_drawable_get_xid (IntPtr raw);
|
||||
|
||||
public static System.Drawing.Graphics FromDrawable (Gdk.Drawable drawable)
|
||||
{
|
||||
IntPtr x_drawable;
|
||||
int x_off = 0, y_off = 0;
|
||||
|
||||
|
||||
if (drawable is Gdk.Window){
|
||||
((Gdk.Window) drawable).GetInternalPaintInfo(out drawable, out x_off, out y_off);
|
||||
}
|
||||
x_drawable = drawable.Handle;
|
||||
IntPtr display = gdk_x11_drawable_get_xdisplay (x_drawable);
|
||||
|
||||
Type graphics = typeof (System.Drawing.Graphics);
|
||||
MethodInfo mi = graphics.GetMethod ("FromXDrawable", BindingFlags.Static | BindingFlags.NonPublic);
|
||||
if (mi == null)
|
||||
throw new NotImplementedException ("In this implementation I can not get a graphics from a drawable");
|
||||
object [] args = new object [2] { (IntPtr) gdk_x11_drawable_get_xid (drawable.Handle), (IntPtr) display };
|
||||
object r = mi.Invoke (null, args);
|
||||
System.Drawing.Graphics g = (System.Drawing.Graphics) r;
|
||||
|
||||
|
||||
g.TranslateTransform (-x_off, -y_off);
|
||||
|
||||
return g;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<configuration>
|
||||
<dllmap dll="libglib-2.0-0.dll" target="libglib-2.0.so.0"/>
|
||||
<dllmap dll="libgobject-2.0-0.dll" target="libgobject-2.0.so.0"/>
|
||||
<dllmap dll="libgdk-win32-2.0-0.dll" target="libgdk-x11-2.0.so.0"/>
|
||||
<dllmap dll="libgdk_pixbuf-2.0-0.dll" target="libgdk_pixbuf-2.0.so.0"/>
|
||||
</configuration>
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
exec @MONO@ @prefix@/lib/mono-heap-prof/mono-heap-prof-view.exe "$@"
|
Загрузка…
Ссылка в новой задаче