Initial revision

This commit is contained in:
Søren Sandmann Pedersen
2004-04-27 11:08:55 +00:00
commit 43dddf31ac
22 changed files with 4128 additions and 0 deletions

32
Makefile Normal file
View File

@ -0,0 +1,32 @@
CFLAGS = `pkg-config --cflags gtk+-2.0 libglade-2.0` -Wall -g
LIBS = `pkg-config --libs gtk+-2.0 libglade-2.0` -lbfd -liberty
C_FILES = sysprof.c binfile.c stackstash.c watch.c process.c profile.c treeviewutils.c
OBJS = $(addsuffix .o, $(basename $(C_FILES)))
BINARY = sysprof
MODULE := sysprof-module
INCLUDE := -isystem /lib/modules/`uname -r`/build/include
MODCFLAGS := -O2 -DMODULE -D__KERNEL__ -Wall ${INCLUDE}
all: $(BINARY) $(MODULE).o
$(BINARY): $(OBJS) depend
gcc $(OBJS) $(LIBS) -o$(BINARY)
clean:
rm -f $(OBJS) $(BINARY) $(MODULE).o *~ core* depend.mk
depend:
$(CC) -MM $(CFLAGS) $(C_FILES) > depend.mk
depend.mk:
touch depend.mk
$(MAKE) depend
include depend.mk
.PHONY: depend all
$(MODULE).o: $(MODULE).c
gcc $(MODCFLAGS) $(MODULE).c -c -o$(MODULE).o

14
README Normal file
View File

@ -0,0 +1,14 @@
This is a sampling profiler that uses a kernel module, sysprof-module,
to generate stacktraces which are then interpreted by the userspace
program "sysprof".
- The profiler uses a kernel module, so it is Linux specifc
- There is no auto* stuff. Just type "make" and hope for the best.
- It does not work on Linux 2.6. Feel free to port it and send me the
patch.
- You need gtk+ 2.4.0 or better, and you need libglade
S<EFBFBD>ren

81
TODO Normal file
View File

@ -0,0 +1,81 @@
- make presentation strings nicer
four different kinds of symbols:
a) I know exactly what this is
b) I know in what library this is
c) I know only the process that did this
d) I know the name, but there is another similarly named one
(a) is easy, (b) should be <in ...> (c) should just become "???"
(d) not sure
- grep FIXME
- make an "everything" object
maybe not necessary -- there is a libc_ctors_something()
- consider making ProfileObject more of an object.
- hide internal stuff in ProfileDescendant
- consider caching [filename->bin_file]
DONE:
- processes with a cmdline of "" should get a [pid = %d] instead.
- Kernel module should report the file the symbol was found in
- make an "n samples" label
Process stuff:
- make threads be reported together
(simply report pids with similar command lines together)
(note: it seems separating by pid is way too slow (uses too much memory),
so it has to be like this)
- stack stash should allow different pids to refer to the same root
(ie. there is no need to create a new tree for each pid)
The *leaves* should contain the pid, not the root. You could even imagine
a set of processes, each referring to a set of leaves.
- when we see a new pid, immediately capture its mappings
Road map:
- new object Process
- hashable by pointer
- contains list of maps
- process_from_pid (pid_t pid, gboolean join_threads)
- new processes are gets their maps immediately
- resulting pointer must be unref()ed, but it is possible it
just points to an existing process
- processes with identical cmdlines are taken together
- method lookup_symbol()
- method get_name()
- ref/unref
- StackStash stores map from process to leaves
- Profile is called with processes
It is possible that we simply need a better concept of Process:
If two pids have the same command line, consider them the same, period.
This should save considerable amounts of memory.
The assumptions:
"No pids are reused during a profiling run"
"Two processes with the same command line have the same mappings"
are somewhat dubious, but probably necessary.
(More complex kernel:
have the module report
- new pid arrived (along with mappings)
- mapping changed for pid
- stacktrace)
- make symbols in executable work
- the hashtables used in profile.c should not accept NULL as the key
- make callers work
- autoexpand descendant tree
- make double clicks work
- fix leaks

490
binfile.c Normal file
View File

@ -0,0 +1,490 @@
#include <glib.h>
#include "binfile.h"
#include <bfd.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
/* All interesting code in this file is lifted from bfdutils.c
* and process.c from Memprof,
*
* FIXME: add copyrights
*
*/
static void bfd_nonfatal (const char *string);
static void bfd_fatal (const char *string);
/* Binary File */
struct BinFile
{
char * filename;
int n_symbols;
Symbol *symbols;
Symbol undefined;
};
static bfd *
open_bfd (const char *file)
{
bfd *abfd = bfd_openr (file, NULL);
if (!abfd)
return NULL;
if (!bfd_check_format (abfd, bfd_object))
{
bfd_close (abfd);
return NULL;
}
return abfd;
}
static unsigned long
calc_crc32 (unsigned long crc, unsigned char *buf, size_t len)
{
static const unsigned long crc32_table[256] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,
0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,
0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,
0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,
0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,
0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,
0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,
0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,
0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,
0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,
0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,
0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,
0x2d02ef8d
};
unsigned char *end;
crc = ~crc & 0xffffffff;
for (end = buf + len; buf < end; ++buf)
crc = crc32_table[(crc ^ *buf) & 0xff] ^ (crc >> 8);
return ~crc & 0xffffffff;;
}
static char *
get_debug_link_info (bfd *abfd, unsigned long *crc32_out)
{
asection *sect;
bfd_size_type debuglink_size;
unsigned long crc32;
char *contents;
int crc_offset;
sect = bfd_get_section_by_name (abfd, ".gnu_debuglink");
if (sect == NULL)
return NULL;
debuglink_size = bfd_section_size (abfd, sect);
contents = g_malloc (debuglink_size);
bfd_get_section_contents (abfd, sect, contents,
(file_ptr)0, (bfd_size_type)debuglink_size);
/* Crc value is stored after the filename, aligned up to 4 bytes. */
crc_offset = strlen (contents) + 1;
crc_offset = (crc_offset + 3) & ~3;
crc32 = bfd_get_32 (abfd, (bfd_byte *) (contents + crc_offset));
*crc32_out = crc32;
return contents;
}
static gboolean
separate_debug_file_exists (const char *name, unsigned long crc)
{
unsigned long file_crc = 0;
int fd;
char buffer[8*1024];
int count;
fd = open (name, O_RDONLY);
if (fd < 0)
return 0;
while ((count = read (fd, buffer, sizeof (buffer))) > 0)
file_crc = calc_crc32 (file_crc, buffer, count);
close (fd);
return crc == file_crc;
}
/* FIXME: this should be detected by config.h */
static const char *debug_file_directory = "/usr/lib/debug";
static char *
find_separate_debug_file (bfd *abfd)
{
char *basename;
char *dir;
char *debugfile;
unsigned long crc32;
basename = get_debug_link_info (abfd, &crc32);
if (basename == NULL)
return NULL;
dir = g_path_get_dirname (bfd_get_filename (abfd));
/* First try in the same directory as the original file: */
debugfile = g_build_filename (dir, basename, NULL);
if (separate_debug_file_exists (debugfile, crc32))
{
g_free (basename);
g_free (dir);
return debugfile;
}
g_free (debugfile);
/* Then try in a subdirectory called .debug */
debugfile = g_build_filename (dir, ".debug", basename, NULL);
if (separate_debug_file_exists (debugfile, crc32))
{
g_free (basename);
g_free (dir);
return debugfile;
}
g_free (debugfile);
/* Then try in the global debugfile directory */
debugfile = g_build_filename (debug_file_directory, dir, basename, NULL);
if (separate_debug_file_exists (debugfile, crc32))
{
g_free (basename);
g_free (dir);
return debugfile;
}
g_free (debugfile);
g_free (basename);
g_free (dir);
return NULL;
}
static asymbol **
slurp_symtab (bfd *abfd, long *symcount)
{
asymbol **sy = (asymbol **) NULL;
long storage;
if (!(bfd_get_file_flags (abfd) & HAS_SYMS))
{
*symcount = 0;
return NULL;
}
storage = bfd_get_symtab_upper_bound (abfd);
if (storage < 0)
bfd_fatal (bfd_get_filename (abfd));
if (storage)
sy = (asymbol **) malloc (storage);
*symcount = bfd_canonicalize_symtab (abfd, sy);
if (*symcount < 0)
bfd_fatal (bfd_get_filename (abfd));
return sy;
}
extern char *cplus_demangle (const char *mangled, int options);
#define DMGL_PARAMS (1 << 0) /* Include function args */
#define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */
char *
demangle (bfd *bfd, const char *name)
{
char *demangled;
if (bfd_get_symbol_leading_char (bfd) == *name)
++name;
demangled = cplus_demangle (name, DMGL_ANSI | DMGL_PARAMS);
return demangled ? demangled : strdup (name);
}
static gint
compare_address (const void *a, const void *b)
{
const Symbol *symbol1 = a;
const Symbol *symbol2 = b;
if (symbol1->address < symbol2->address)
return -1;
else if (symbol1->address == symbol2->address)
return 0;
else
return 1;
}
static void
read_symbols (BinFile *bf)
{
asection *text_section;
const char *separate_debug_file;
asymbol **bfd_symbols;
long n_symbols;
int i;
bfd *bfd;
GArray *symbols;
bf->symbols = NULL;
bf->n_symbols = 0;
bfd = open_bfd (bf->filename);
if (!bfd)
return;
separate_debug_file = find_separate_debug_file (bfd);
if (separate_debug_file)
{
bfd_close (bfd);
bfd = open_bfd (separate_debug_file);
if (!bfd)
return;
}
bfd_symbols = slurp_symtab (bfd, &n_symbols);
if (!bfd_symbols)
return;
text_section = bfd_get_section_by_name (bfd, ".text");
if (!text_section)
return;
symbols = g_array_new (FALSE, FALSE, sizeof (Symbol));
for (i = 0; i < n_symbols; i++)
{
Symbol symbol;
if ((bfd_symbols[i]->flags & BSF_FUNCTION) &&
(bfd_symbols[i]->section == text_section))
{
char *name;
symbol.address = bfd_asymbol_value (bfd_symbols[i]);
name = demangle (bfd, bfd_asymbol_name (bfd_symbols[i]));
symbol.name = g_strdup (name);
free (name);
g_array_append_vals (symbols, &symbol, 1);
}
}
if (n_symbols)
free (bfd_symbols);
/* Sort the symbols by address */
qsort (symbols->data, symbols->len, sizeof(Symbol), compare_address);
bf->n_symbols = symbols->len;
bf->symbols = (Symbol *)g_array_free (symbols, FALSE);
}
BinFile *
bin_file_new (const char *filename)
{
BinFile *bf = g_new0 (BinFile, 1);
bf->filename = g_strdup (filename);
read_symbols (bf);
bf->undefined.name = g_strdup_printf ("In file %s", filename);
bf->undefined.address = 0x0;
return bf;
}
void
bin_file_free (BinFile *bf)
{
int i;
g_free (bf->filename);
for (i = 0; i < bf->n_symbols; ++i)
g_free (bf->symbols[i].name);
g_free (bf->symbols);
g_free (bf->undefined.name);
g_free (bf);
}
const Symbol *
bin_file_lookup_symbol (BinFile *bf,
gulong address)
{
int first = 0;
int last = bf->n_symbols - 1;
int middle = last;
Symbol *data;
Symbol *result;
if (!bf->symbols || (bf->n_symbols == 0))
return &(bf->undefined);
data = bf->symbols;
if (address < data[last].address)
{
/* Invariant: data[first].addr <= val < data[last].addr */
while (first < last - 1)
{
middle = (first + last) / 2;
if (address < data[middle].address)
last = middle;
else
first = middle;
}
/* Size is not included in generic bfd data, so we
* ignore it for now. (It is ELF specific)
*/
result = &data[first];
}
else
{
result = &data[last];
}
/* If the name is "call_gmon_start", the file probably doesn't
* have any other symbols
*/
if (strcmp (result->name, "call_gmon_start") == 0)
return &(bf->undefined);
return result;
}
/* Symbol */
Symbol *
symbol_copy (const Symbol *orig)
{
Symbol *copy;
copy = g_new (Symbol, 1);
copy->name = g_strdup (orig->name);
copy->address = orig->address;
return copy;
}
gboolean
symbol_equal (const void *sa,
const void *sb)
{
const Symbol *syma = sa;
const Symbol *symb = sb;
if (symb->address != syma->address)
return FALSE;
/* symbols compare equal if their names are both NULL */
if (!syma->name && !symb->name)
return TRUE;
if (!syma)
return FALSE;
if (!symb)
return FALSE;
return strcmp (syma->name, symb->name) == 0;
}
guint
symbol_hash (const void *s)
{
const Symbol *symbol = s;
if (!s)
return 0;
return symbol->name? g_str_hash (symbol->name) : 0 + symbol->address;
}
void
symbol_free (Symbol *symbol)
{
if (symbol->name)
g_free (symbol->name);
g_free (symbol);
}
static void
bfd_nonfatal (const char *string)
{
const char *errmsg = bfd_errmsg (bfd_get_error ());
if (string)
{
fprintf (stderr, "%s: %s: %s\n",
g_get_application_name(), string, errmsg);
}
else
{
fprintf (stderr, "%s: %s\n",
g_get_application_name(), errmsg);
}
}
static void
bfd_fatal (const char *string)
{
bfd_nonfatal (string);
exit (1);
}

29
binfile.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef BIN_FILE_H
#define BIN_FILE_H
#include <glib.h>
typedef struct BinFile BinFile;
typedef struct Symbol Symbol;
/* Binary File */
BinFile * bin_file_new (const char *filename);
void bin_file_free (BinFile *bin_file);
const Symbol *bin_file_lookup_symbol (BinFile *bin_file,
gulong address);
/* Symbol */
struct Symbol
{
char * name;
gulong address;
};
Symbol * symbol_copy (const Symbol *orig);
gboolean symbol_equal (const void *syma,
const void *symb);
guint symbol_hash (const void *sym);
void symbol_free (Symbol *symbol);
#endif

BIN
cdplayer-play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
cdplayer-stop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

216
pixbufs.c Normal file
View File

@ -0,0 +1,216 @@
/* GdkPixbuf RGBA C-Source image dump 1-byte-run-length-encoded */
#ifdef __SUNPRO_C
#pragma align 4 (stop_profiling)
#endif
#ifdef __GNUC__
static const guint8 stop_profiling[] __attribute__ ((__aligned__ (4))) =
#else
static const guint8 stop_profiling[] =
#endif
{ ""
/* Pixbuf magic (0x47646b50) */
"GdkP"
/* length: header (24) + pixel_data (1554) */
"\0\0\6*"
/* pixdata_type (0x2010002) */
"\2\1\0\2"
/* rowstride (192) */
"\0\0\0\300"
/* width (48) */
"\0\0\0""0"
/* height (48) */
"\0\0\0""0"
/* pixel_data: */
"\377\377\377\377\0\367\377\377\377\0\3\377\377\377[\377\377\377\315\377"
"\377\377\354\236\377\377\377\352\3\377\377\377\354\377\377\377\321\377"
"\377\377c\213\377\377\377\0\1\377\377\377^\244\377\377\377\377\1\377"
"\377\377h\212\377\377\377\0\1\377\377\377\317\244\377\377\377\377\1\377"
"\377\377\316\212\377\377\377\0\1\377\377\377\357\202\377\377\377\377"
"\2\247\247\247\377YYY\377\234ccc\377\2ZZZ\377\240\240\240\377\202\377"
"\377\377\377\1\377\377\377\352\212\377\377\377\0\1\377\377\377\360\202"
"\377\377\377\377\1mmm\377\236\0\0\0\377\1ccc\377\202\377\377\377\377"
"\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377"
"\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377"
"\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377"
"\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377"
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377"
"\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377"
"\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202"
"\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360"
"\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377"
"\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377"
"\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377"
"\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1oo"
"o\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212"
"\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0"
"\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377"
"\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc"
"\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377"
"\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377"
"\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202"
"\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377"
"\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377"
"\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377"
"\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377"
"\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377"
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377"
"\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377"
"\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202"
"\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360"
"\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377"
"\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377"
"\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377"
"\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1oo"
"o\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212"
"\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0"
"\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377"
"\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc"
"\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377"
"\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377"
"\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202"
"\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377"
"\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377"
"\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377"
"\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377"
"\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377"
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377"
"\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377"
"\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202"
"\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360"
"\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377"
"\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377"
"\377\377\1nnn\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377"
"\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1nn"
"n\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\354\212"
"\377\377\377\0\1\377\377\377\355\202\377\377\377\377\2\303\303\303\377"
"\217\217\217\377\234\226\226\226\377\2\220\220\220\377\277\277\277\377"
"\202\377\377\377\377\1\377\377\377\350\212\377\377\377\0\1\377\377\377"
"\276\244\377\377\377\377\1\377\377\377\300\212\377\377\377\0\2\377\377"
"\377@\377\377\377\363\242\377\377\377\377\2\377\377\377\370\377\377\377"
"J\213\377\377\377\0\3\377\377\3773\377\377\377\230\377\377\377\271\236"
"\377\377\377\267\3\377\377\377\271\377\377\377\234\377\377\3779\356\377"
"\377\377\0\2\252\252\252\0```\0\234iii\0\2```\0\243\243\243\0\220\377"
"\377\377\0\1mmm\0\236\0\0\0\0\1ccc\0\215\377\377\377\0\1\377\377\377"
"\10\202\377\377\377\11\1ooo\11\236\0\0\0\11\1ccc\11\202\377\377\377\11"
"\1\377\377\377\10\205\377\377\377\0"};
/* GdkPixbuf RGBA C-Source image dump 1-byte-run-length-encoded */
#ifdef __SUNPRO_C
#pragma align 4 (start_profiling)
#endif
#ifdef __GNUC__
static const guint8 start_profiling[] __attribute__ ((__aligned__ (4))) =
#else
static const guint8 start_profiling[] =
#endif
{ ""
/* Pixbuf magic (0x47646b50) */
"GdkP"
/* length: header (24) + pixel_data (1932) */
"\0\0\7\244"
/* pixdata_type (0x2010002) */
"\2\1\0\2"
/* rowstride (192) */
"\0\0\0\300"
/* width (48) */
"\0\0\0""0"
/* height (48) */
"\0\0\0""0"
/* pixel_data: */
"\377\377\377\377\0\317\377\377\377\0\2\377\377\377J\377\377\377\275\202"
"\377\377\377\337\3\377\377\377\345\377\377\377\311\377\377\377X\250\377"
"\377\377\0\2\377\377\377Q\377\377\377\373\206\377\377\377\377\1\377\377"
"\377x\247\377\377\377\0\1\377\377\377\314\210\377\377\377\377\1\377\377"
"\377\206\246\377\377\377\0\1\377\377\377\357\202\377\377\377\377\4\260"
"\260\260\377ddd\377\204\204\204\377\364\364\364\377\203\377\377\377\377"
"\1\377\377\377\206\245\377\377\377\0\1\377\377\377\360\202\377\377\377"
"\377\1qqq\377\202\0\0\0\377\2VVV\377\364\364\364\377\203\377\377\377"
"\377\2\377\377\377\212\377\377\377\1\243\377\377\377\0\1\377\377\377"
"\360\202\377\377\377\377\1qqq\377\203\0\0\0\377\2QQQ\377\365\365\365"
"\377\203\377\377\377\377\2\377\377\377\217\377\377\377\1\242\377\377"
"\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\204\0\0\0\377\2"
"KKK\377\360\360\360\377\203\377\377\377\377\2\377\377\377\231\377\377"
"\377\3\241\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377"
"\205\0\0\0\377\2AAA\377\354\354\354\377\203\377\377\377\377\2\377\377"
"\377\241\377\377\377\5\240\377\377\377\0\1\377\377\377\360\202\377\377"
"\377\377\1qqq\377\206\0\0\0\377\2AAA\377\354\354\354\377\203\377\377"
"\377\377\2\377\377\377\241\377\377\377\4\237\377\377\377\0\1\377\377"
"\377\360\202\377\377\377\377\1qqq\377\207\0\0\0\377\2@@@\377\347\347"
"\347\377\203\377\377\377\377\2\377\377\377\244\377\377\377\11\236\377"
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\210\0\0\0\377"
"\2""999\377\343\343\343\377\203\377\377\377\377\2\377\377\377\257\377"
"\377\377\15\235\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1"
"qqq\377\211\0\0\0\377\2""222\377\341\341\341\377\203\377\377\377\377"
"\2\377\377\377\261\377\377\377\15\234\377\377\377\0\1\377\377\377\360"
"\202\377\377\377\377\1qqq\377\212\0\0\0\377\2,,,\377\334\334\334\377"
"\203\377\377\377\377\2\377\377\377\272\377\377\377\21\233\377\377\377"
"\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\213\0\0\0\377\2,,,"
"\377\334\334\334\377\203\377\377\377\377\2\377\377\377\272\377\377\377"
"\21\232\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377"
"\214\0\0\0\377\2+++\377\326\326\326\377\203\377\377\377\377\2\377\377"
"\377\274\377\377\377\31\231\377\377\377\0\1\377\377\377\360\202\377\377"
"\377\377\1qqq\377\215\0\0\0\377\2\40\40\40\377\320\320\320\377\203\377"
"\377\377\377\2\377\377\377\311\377\377\377\33\230\377\377\377\0\1\377"
"\377\377\360\202\377\377\377\377\1qqq\377\216\0\0\0\377\2\37\37\37\377"
"\317\317\317\377\203\377\377\377\377\2\377\377\377\311\377\377\377\33"
"\227\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\217"
"\0\0\0\377\2\33\33\33\377\311\311\311\377\203\377\377\377\377\2\377\377"
"\377\317\377\377\377\33\226\377\377\377\0\1\377\377\377\360\202\377\377"
"\377\377\1qqq\377\220\0\0\0\377\2\33\33\33\377\311\311\311\377\203\377"
"\377\377\377\1\377\377\377\235\226\377\377\377\0\1\377\377\377\360\202"
"\377\377\377\377\1qqq\377\221\0\0\0\377\2\22\22\22\377\304\304\304\377"
"\202\377\377\377\377\1\377\377\377\332\226\377\377\377\0\1\377\377\377"
"\360\202\377\377\377\377\1qqq\377\221\0\0\0\377\2""000\377\333\333\333"
"\377\202\377\377\377\377\1\377\377\377\324\226\377\377\377\0\1\377\377"
"\377\360\202\377\377\377\377\1qqq\377\220\0\0\0\377\2:::\377\346\346"
"\346\377\203\377\377\377\377\1\377\377\377\207\226\377\377\377\0\1\377"
"\377\377\360\202\377\377\377\377\1qqq\377\217\0\0\0\377\2BBB\377\354"
"\354\354\377\203\377\377\377\377\2\377\377\377\250\377\377\377\7\226"
"\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\216\0\0"
"\0\377\2FFF\377\356\356\356\377\203\377\377\377\377\2\377\377\377\246"
"\377\377\377\6\227\377\377\377\0\1\377\377\377\360\202\377\377\377\377"
"\1qqq\377\215\0\0\0\377\2PPP\377\356\356\356\377\203\377\377\377\377"
"\2\377\377\377\235\377\377\377\7\230\377\377\377\0\1\377\377\377\360"
"\202\377\377\377\377\1qqq\377\214\0\0\0\377\2ZZZ\377\371\371\371\377"
"\203\377\377\377\377\2\377\377\377\220\377\377\377\1\231\377\377\377"
"\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\213\0\0\0\377\2]]]"
"\377\371\371\371\377\203\377\377\377\377\1\377\377\377\213\233\377\377"
"\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\212\0\0\0\377\2"
"iii\377\374\374\374\377\203\377\377\377\377\1\377\377\377\177\234\377"
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\211\0\0\0\377"
"\1ttt\377\204\377\377\377\377\1\377\377\377t\235\377\377\377\0\1\377"
"\377\377\360\202\377\377\377\377\1qqq\377\210\0\0\0\377\1\203\203\203"
"\377\203\377\377\377\377\2\377\377\377\370\377\377\377i\236\377\377\377"
"\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\207\0\0\0\377\1\213"
"\213\213\377\203\377\377\377\377\2\377\377\377\371\377\377\377^\237\377"
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\206\0\0\0\377"
"\1\216\216\216\377\203\377\377\377\377\2\377\377\377\367\377\377\377"
"Z\240\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\204"
"\0\0\0\377\2\2\2\2\377\230\230\230\377\203\377\377\377\377\2\377\377"
"\377\363\377\377\377Q\241\377\377\377\0\1\377\377\377\360\202\377\377"
"\377\377\1qqq\377\203\0\0\0\377\2\10\10\10\377\242\242\242\377\203\377"
"\377\377\377\2\377\377\377\361\377\377\377L\242\377\377\377\0\1\377\377"
"\377\360\202\377\377\377\377\1qqq\377\202\0\0\0\377\2\15\15\15\377\251"
"\251\251\377\203\377\377\377\377\2\377\377\377\350\377\377\377A\243\377"
"\377\377\0\1\377\377\377\360\202\377\377\377\377\4vvv\377\0\0\0\377\22"
"\22\22\377\261\261\261\377\203\377\377\377\377\2\377\377\377\347\377"
"\377\377:\244\377\377\377\0\1\377\377\377\350\202\377\377\377\377\3\352"
"\352\352\377\325\325\325\377\344\344\344\377\203\377\377\377\377\2\377"
"\377\377\344\377\377\3777\245\377\377\377\0\1\377\377\377\236\207\377"
"\377\377\377\2\377\377\377\336\377\377\377/\204\377\377\377\0\1\376\376"
"\376\0\241\377\377\377\0\2\377\377\377\27\377\377\377\302\205\377\377"
"\377\377\2\377\377\377\317\377\377\377&\203\377\377\377\0\3\364\364\364"
"\0\211\211\211\0\216\216\216\0\242\377\377\377\0\7\377\377\377\14\377"
"\377\377S\377\377\377s\377\377\377r\377\377\377v\377\377\377\\\377\377"
"\377\21\202\377\377\377\0\5\353\353\353\0\214\214\214\0%%%\0\0\0\0\0"
"eee\0\251\377\377\377\0\3\353\353\353\0\212\212\212\0\33\33\33\0\203"
"\0\0\0\0\1fff\0\244\377\377\377\0\6\327\327\327\0\263\263\263\0\267\267"
"\267\0\247\247\247\0ttt\0\31\31\31\0\205\0\0\0\0\1fff\0\244\377\377\377"
"\0\1qqq\0\212\0\0\0\0\1fff\0\241\377\377\377\0\1\377\377\377\10\202\377"
"\377\377\11\1qqq\11\212\0\0\0\11\1fff\11\203\377\377\377\11\2\377\377"
"\377\12\377\377\377\4\217\377\377\377\0"};

343
process.c Normal file
View File

@ -0,0 +1,343 @@
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include "process.h"
#include "binfile.h"
/* FIXME: All the interesting stuff in this file is from memprof
* and copyright Red Hat.
*/
/* FIXME: this should be done with getpagesize() */
#define PAGE_SIZE 4096
static GHashTable *processes_by_cmdline;
static GHashTable *processes_by_pid;
typedef struct Map Map;
struct Map
{
char * filename;
gulong start;
gulong end;
gboolean do_offset;
BinFile * bin_file;
};
struct Process
{
char *cmdline;
GList *maps;
GList *bad_pages;
};
static void
initialize (void)
{
if (!processes_by_cmdline)
{
processes_by_cmdline = g_hash_table_new (g_str_hash, g_str_equal);
processes_by_pid = g_hash_table_new (g_direct_hash, g_direct_equal);
}
}
static gboolean
should_offset (const char *filename, int pid)
{
gboolean result = TRUE;
struct stat stat1, stat2;
char *progname = g_strdup_printf ("/proc/%d/exe", pid);
if (stat (filename, &stat1) == 0)
{
if (stat (progname, &stat2) == 0)
result = !(stat1.st_ino == stat2.st_ino &&
stat1.st_dev == stat2.st_dev);
}
g_free (progname);
return result;
}
static gboolean
check_do_offset (const Map *map, int pid)
{
if (map->filename)
return should_offset (map->filename, pid);
else
return TRUE;
}
static GList *
read_maps (int pid)
{
char *name = g_strdup_printf ("/proc/%d/maps", pid);
char buffer[1024];
FILE *in;
GList *result = NULL;
in = fopen (name, "r");
if (!in)
{
g_free (name);
return NULL;
}
while (fgets (buffer, sizeof (buffer) - 1, in))
{
char file[256];
int count;
guint start;
guint end;
count = sscanf (
buffer, "%x-%x %*15s %*x %*u:%*u %*u %255s", &start, &end, file);
if (count == 3)
{
Map *map;
map = g_new (Map, 1);
map->filename = g_strdup (file);
map->start = start;
map->end = end;
map->do_offset = check_do_offset (map, pid);
map->bin_file = NULL;
result = g_list_prepend (result, map);
}
}
g_free (name);
fclose (in);
return result;
}
static Process *
create_process (const char *cmdline, int pid)
{
Process *p;
p = g_new (Process, 1);
p->cmdline = g_strdup_printf ("[%s]", cmdline);
p->bad_pages = NULL;
p->maps = NULL;
g_assert (!g_hash_table_lookup (processes_by_pid, GINT_TO_POINTER (pid)));
g_assert (!g_hash_table_lookup (processes_by_cmdline, cmdline));
g_hash_table_insert (processes_by_pid, GINT_TO_POINTER (pid), p);
g_hash_table_insert (processes_by_cmdline, g_strdup (cmdline), p);
return p;
}
static Map *
process_locate_map (Process *process, gulong addr)
{
GList *list;
for (list = process->maps; list != NULL; list = list->next)
{
Map *map = list->data;
if ((addr >= map->start) &&
(addr < map->end))
{
return map;
}
}
return NULL;
}
static void
process_free_maps (Process *process)
{
GList *list;
for (list = process->maps; list != NULL; list = list->next)
{
Map *map = list->data;
if (map->bin_file)
bin_file_free (map->bin_file);
g_free (map);
}
g_list_free (process->maps);
}
static gboolean
process_has_page (Process *process, gulong addr)
{
if (process_locate_map (process, addr))
return TRUE;
else
return FALSE;
}
void
process_ensure_map (Process *process, int pid, gulong addr)
{
/* Round don to closest page */
addr = (addr - addr % PAGE_SIZE);
if (process_has_page (process, addr))
return;
if (g_list_find (process->bad_pages, (gpointer)addr))
return;
/* a map containing addr was not found */
if (process->maps)
process_free_maps (process);
process->maps = read_maps (pid);
if (!process_has_page (process, addr))
g_list_prepend (process->bad_pages, (gpointer)addr);
}
Process *
process_get_from_pid (int pid)
{
Process *p;
gchar *filename = NULL;
gchar *cmdline = NULL;
initialize();
p = g_hash_table_lookup (processes_by_pid, GINT_TO_POINTER (pid));
if (p)
goto out;
filename = g_strdup_printf ("/proc/%d/cmdline", pid);
if (g_file_get_contents (filename, &cmdline, NULL, NULL))
{
p = g_hash_table_lookup (processes_by_cmdline, cmdline);
if (p)
goto out;
}
else
{
cmdline = g_strdup_printf ("[pid %d]", pid);
}
p = create_process (cmdline, pid);
out:
if (cmdline)
g_free (cmdline);
if (filename)
g_free (filename);
return p;
}
const Symbol *
process_lookup_symbol (Process *process, gulong address)
{
static Symbol undefined;
const Symbol *result;
Map *map = process_locate_map (process, address);
if (!map)
{
if (undefined.name)
g_free (undefined.name);
undefined.name = g_strdup_printf ("??? (%s)", process->cmdline);
undefined.address = 0xBABE0001;
return &undefined;
}
if (!map->bin_file)
map->bin_file = bin_file_new (map->filename);
if (map->do_offset)
address -= map->start;
result = bin_file_lookup_symbol (map->bin_file, address);
return result;
}
const Symbol *
process_lookup_symbol_with_filename (Process *process,
int pid,
gulong map_start,
const char *filename,
gulong address)
{
const Symbol *result;
BinFile *bin_file;
if (!filename)
return process_lookup_symbol (process, address);
bin_file = bin_file_new (filename);
if (should_offset (filename, pid))
address -= map_start;
result = bin_file_lookup_symbol (bin_file, address);
bin_file_free (bin_file);
return result;
}
const char *
process_get_cmdline (Process *process)
{
return process->cmdline;
}
static void
free_process (gpointer key, gpointer value, gpointer data)
{
char *cmdline = key;
Process *process = value;
#if 0
g_print ("freeing: %p\n", process);
memset (process, '\0', sizeof (Process));
#endif
g_free (process->cmdline);
#if 0
process->cmdline = "You are using free()'d memory";
#endif
process_free_maps (process);
g_list_free (process->bad_pages);
g_free (cmdline);
g_free (process);
}
void
process_flush_caches (void)
{
if (!processes_by_cmdline)
return;
g_hash_table_foreach (processes_by_cmdline, free_process, NULL);
g_hash_table_destroy (processes_by_cmdline);
g_hash_table_destroy (processes_by_pid);
processes_by_cmdline = NULL;
processes_by_pid = NULL;
}

38
process.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef PROCESS_H
#define PROCESS_H
#include "binfile.h"
typedef struct Process Process;
/* We are making the assumption that pid's are not recycled during
* a profiling run. That is wrong, but necessary to avoid reading
* from /proc/<pid>/maps on every sample (which would be a race
* condition anyway).
*
* If the address passed to new_from_pid() is somewhere that hasn't
* been checked before, the mappings are reread for the Process. This
* means that if some previously sampled pages have been unmapped,
* they will be lost and appear as "???" on the profile.
*
* To flush the pid cache, call process_flush_caches().
* This will invalidate all instances of Process.
*
*/
void process_flush_caches (void);
Process * process_get_from_pid (int pid);
void process_ensure_map (Process *process,
int pid,
gulong address);
const Symbol *process_lookup_symbol (Process *process,
gulong address);
const Symbol *process_lookup_symbol_with_filename (Process *process,
int pid,
gulong map_start,
const char *filename,
gulong address);
const char * process_get_cmdline (Process *process);
#endif

630
profile.c Normal file
View File

@ -0,0 +1,630 @@
#include <glib.h>
#include "binfile.h"
#include "process.h"
#include "stackstash.h"
#include "profile.h"
typedef struct RealProfile RealProfile;
typedef struct Node Node;
static void
update()
{
#if 0
while (g_main_pending())
g_main_iteration (FALSE);
#endif
}
static guint
direct_hash_no_null (gconstpointer v)
{
g_assert (v);
return GPOINTER_TO_UINT (v);
}
struct Node
{
ProfileObject *object;
Node *siblings; /* siblings in the call tree */
Node *children; /* children in the call tree */
Node *parent; /* parent in call tree */
Node *next; /* nodes that corresponding to same object */
guint total;
guint self;
gboolean toplevel;
};
struct RealProfile
{
Profile profile;
Node *call_tree;
GHashTable *profile_objects;
GHashTable *nodes_by_object;
};
static ProfileObject *
profile_object_new (void)
{
ProfileObject *obj = g_new (ProfileObject, 1);
obj->total = 0;
obj->self = 0;
return obj;
}
static void
profile_object_free (ProfileObject *obj)
{
g_free (obj->name);
g_free (obj);
}
static char *
generate_key (RealProfile *profile, Process *process, gulong address)
{
if (address)
{
const Symbol *symbol = process_lookup_symbol (process, address);
return g_strdup_printf ("%p%s", symbol->address, symbol->name);
}
else
{
return g_strdup_printf ("p:%p", process_get_cmdline (process));
}
}
static char *
generate_presentation_name (RealProfile *profile, Process *process, gulong address)
{
/* FIXME using 0 to indicate "process" is broken */
if (address)
{
const Symbol *symbol = process_lookup_symbol (process, address);
return g_strdup_printf ("%s", symbol->name);
}
else
{
return g_strdup_printf ("%s", process_get_cmdline (process));
}
#if 0
/* FIXME - don't return addresses and stuff */
return generate_key (profile, process, address);
#endif
}
static void
ensure_profile_node (RealProfile *profile, Process *process, gulong address)
{
char *key = generate_key (profile, process, address);
if (!g_hash_table_lookup (profile->profile_objects, key))
{
ProfileObject *object;
object = profile_object_new ();
object->name = generate_presentation_name (profile, process, address);
g_hash_table_insert (profile->profile_objects, key, object);
}
else
{
g_free (key);
}
}
static ProfileObject *
lookup_profile_object (RealProfile *profile, Process *process, gulong address)
{
ProfileObject *object;
char *key = generate_key (profile, process, address);
object = g_hash_table_lookup (profile->profile_objects, key);
g_free (key);
g_assert (object);
return object;
}
static void
generate_object_table (Process *process, GSList *trace, gint size, gpointer data)
{
RealProfile *profile = data;
GSList *list;
ensure_profile_node (profile, process, 0);
for (list = trace; list != NULL; list = list->next)
{
update ();
ensure_profile_node (profile, process, (gulong)list->data);
}
profile->profile.profile_size += size;
}
static Node *
node_new ()
{
Node *node = g_new (Node, 1);
node->siblings = NULL;
node->children = NULL;
node->parent = NULL;
node->next = NULL;
node->object = NULL;
node->self = 0;
node->total = 0;
return node;
}
static Node *
node_add_trace (RealProfile *profile, Node *node, Process *process,
GSList *trace, gint size,
GHashTable *seen_objects)
{
ProfileObject *object;
Node *match = NULL;
if (!trace)
return node;
object = lookup_profile_object (profile, process, (gulong)trace->data);
for (match = node; match != NULL; match = match->siblings)
{
if (match->object == object)
break;
}
if (!match)
{
match = node_new ();
match->object = object;
match->siblings = node;
node = match;
if (g_hash_table_lookup (seen_objects, object))
match->toplevel = FALSE;
else
match->toplevel = TRUE;
match->next = g_hash_table_lookup (profile->nodes_by_object, object);
g_hash_table_insert (profile->nodes_by_object, object, match);
}
g_hash_table_insert (seen_objects, object, GINT_TO_POINTER (1));
#if 0
g_print ("%s adds %d\n", match->object->name, size);
#endif
match->total += size;
if (!trace->next)
match->self += size;
match->children = node_add_trace (profile, match->children, process, trace->next, size,
seen_objects);
return node;
}
#if 0
static void
dump_trace (GSList *trace)
{
g_print ("TRACE: ");
while (trace)
{
g_print ("%x ", trace->data);
trace = trace->next;
}
g_print ("\n\n");
}
#endif
static void
generate_call_tree (Process *process, GSList *trace, gint size, gpointer data)
{
RealProfile *profile = data;
Node *match = NULL;
ProfileObject *proc = lookup_profile_object (profile, process, 0);
GHashTable *seen_objects;
for (match = profile->call_tree; match; match = match->siblings)
{
if (match->object == proc)
break;
}
if (!match)
{
match = node_new ();
match->object = proc;
match->siblings = profile->call_tree;
profile->call_tree = match;
match->toplevel = TRUE;
}
g_hash_table_insert (profile->nodes_by_object, proc, match);
match->total += size;
if (!trace)
match->self += size;
seen_objects = g_hash_table_new (direct_hash_no_null, g_direct_equal);
g_hash_table_insert (seen_objects, proc, GINT_TO_POINTER (1));
update ();
match->children = node_add_trace (profile, match->children, process,
trace, size, seen_objects);
g_hash_table_destroy (seen_objects);
}
static void
build_object_list (gpointer key, gpointer value, gpointer data)
{
Profile *profile = data;
Node *node = value;
ProfileObject *object = key;
while (node)
{
object->self += node->self;
if (node->toplevel)
object->total += node->total;
node = node->next;
}
profile->objects = g_list_prepend (profile->objects, object);
}
static void
link_parents (Node *node, Node *parent)
{
if (!node)
return;
node->parent = parent;
link_parents (node->siblings, parent);
link_parents (node->children, node);
}
Profile *
profile_new (StackStash *stash)
{
RealProfile *real_profile = g_new (RealProfile, 1);
real_profile->profile_objects =
g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify)profile_object_free);
real_profile->call_tree = NULL;
real_profile->nodes_by_object =
g_hash_table_new (direct_hash_no_null, g_direct_equal);
real_profile->profile.objects = NULL;
real_profile->profile.profile_size = 0;
stack_stash_foreach (stash, generate_object_table, real_profile);
stack_stash_foreach (stash, generate_call_tree, real_profile);
link_parents (real_profile->call_tree, NULL);
g_hash_table_foreach (real_profile->nodes_by_object, build_object_list, real_profile);
return (Profile *)real_profile;
}
static void
add_trace_to_tree (ProfileDescendant **tree, GList *trace, guint size)
{
GList *list;
GList *nodes_to_unmark = NULL;
GList *nodes_to_unmark_recursive = NULL;
ProfileDescendant *parent = NULL;
GHashTable *seen_objects =
g_hash_table_new (direct_hash_no_null, g_direct_equal);
for (list = trace; list != NULL; list = list->next)
{
Node *node = list->data;
ProfileDescendant *match = NULL;
update();
for (match = *tree; match != NULL; match = match->siblings)
{
if (match->object == node->object)
break;
}
if (!match)
{
ProfileDescendant *seen_tree_node;
seen_tree_node = g_hash_table_lookup (seen_objects, node->object);
if (seen_tree_node)
{
ProfileDescendant *node;
g_assert (parent);
for (node = parent; node != seen_tree_node->parent; node = node->parent)
{
node->non_recursion -= size;
--node->marked_non_recursive;
}
match = seen_tree_node;
g_hash_table_destroy (seen_objects);
seen_objects =
g_hash_table_new (direct_hash_no_null, g_direct_equal);
for (node = match; node != NULL; node = node->parent)
g_hash_table_insert (seen_objects, node->object, node);
}
}
if (!match)
{
match = g_new (ProfileDescendant, 1);
match->object = node->object;
match->non_recursion = 0;
match->total = 0;
match->self = 0;
match->children = NULL;
match->marked_non_recursive = 0;
match->marked_total = FALSE;
match->parent = parent;
match->siblings = *tree;
*tree = match;
}
if (!match->marked_non_recursive)
{
nodes_to_unmark = g_list_prepend (nodes_to_unmark, match);
match->non_recursion += size;
++match->marked_non_recursive;
}
if (!match->marked_total)
{
nodes_to_unmark_recursive = g_list_prepend (
nodes_to_unmark_recursive, match);
match->total += size;
match->marked_total = TRUE;
}
if (!list->next)
match->self += size;
g_hash_table_insert (seen_objects, node->object, match);
tree = &(match->children);
parent = match;
}
g_hash_table_destroy (seen_objects);
for (list = nodes_to_unmark; list != NULL; list = list->next)
{
ProfileDescendant *tree_node = list->data;
tree_node->marked_non_recursive = 0;
}
for (list = nodes_to_unmark_recursive; list != NULL; list = list->next)
{
ProfileDescendant *tree_node = list->data;
tree_node->marked_total = FALSE;
}
g_list_free (nodes_to_unmark);
}
static void
node_list_leaves (Node *node, GList **leaves)
{
Node *n;
if (node->self > 0)
*leaves = g_list_prepend (*leaves, node);
for (n = node->children; n != NULL; n = n->siblings)
node_list_leaves (n, leaves);
}
static void
add_leaf_to_tree (ProfileDescendant **tree, Node *leaf, Node *top)
{
GList *trace = NULL;
Node *node;
for (node = leaf; node != top->parent; node = node->parent)
trace = g_list_prepend (trace, node);
add_trace_to_tree (tree, trace, leaf->self);
g_list_free (trace);
}
ProfileDescendant *
profile_create_descendants (Profile *prf, ProfileObject *object)
{
ProfileDescendant *tree = NULL;
RealProfile *profile = (RealProfile *)prf;
Node *node;
node = g_hash_table_lookup (profile->nodes_by_object, object);
while (node)
{
update();
if (node->toplevel)
{
GList *leaves = NULL;
GList *list;
node_list_leaves (node, &leaves);
for (list = leaves; list != NULL; list = list->next)
add_leaf_to_tree (&tree, list->data, node);
g_list_free (leaves);
}
node = node->next;
}
return tree;
}
static ProfileCaller *
profile_caller_new (void)
{
ProfileCaller *caller = g_new (ProfileCaller, 1);
caller->next = NULL;
caller->self = 0;
caller->total = 0;
return caller;
}
ProfileCaller *
profile_list_callers (Profile *prf,
ProfileObject *callee)
{
RealProfile *profile = (RealProfile *)prf;
Node *callee_node;
Node *node;
GHashTable *callers_by_object;
GHashTable *seen_callers;
ProfileCaller *result = NULL;
callers_by_object =
g_hash_table_new (g_direct_hash, g_direct_equal);
seen_callers = g_hash_table_new (g_direct_hash, g_direct_equal);
callee_node = g_hash_table_lookup (profile->nodes_by_object, callee);
for (node = callee_node; node; node = node->next)
{
ProfileObject *object;
if (node->parent)
object = node->parent->object;
else
object = NULL;
if (!g_hash_table_lookup (callers_by_object, object))
{
ProfileCaller *caller = profile_caller_new ();
caller->object = object;
g_hash_table_insert (callers_by_object, object, caller);
caller->next = result;
result = caller;
}
}
for (node = callee_node; node != NULL; node = node->next)
{
Node *top_caller;
Node *top_callee;
Node *n;
ProfileCaller *caller;
ProfileObject *object;
if (node->parent)
object = node->parent->object;
else
object = NULL;
caller = g_hash_table_lookup (callers_by_object, object);
/* find topmost node/parent pair identical to this node/parent */
top_caller = node->parent;
top_callee = node;
for (n = node; n && n->parent; n = n->parent)
{
if (n->object == node->object &&
n->parent->object == node->parent->object)
{
top_caller = n->parent;
top_callee = n;
}
}
if (!g_hash_table_lookup (seen_callers, top_caller))
{
caller->total += top_callee->total;
g_hash_table_insert (seen_callers, top_caller, (void *)0x1);
}
if (node->self > 0)
caller->self += node->self;
}
g_hash_table_destroy (seen_callers);
g_hash_table_destroy (callers_by_object);
return result;
}
static void
node_free (Node *node)
{
if (!node)
return;
node_free (node->siblings);
node_free (node->children);
g_free (node);
}
void
profile_free (Profile *prf)
{
RealProfile *profile = (RealProfile *)prf;
g_list_free (prf->objects);
g_hash_table_destroy (profile->profile_objects);
node_free (profile->call_tree);
g_hash_table_destroy (profile->nodes_by_object);
g_free (prf);
}
void
profile_descendant_free (ProfileDescendant *descendant)
{
if (!descendant)
return;
profile_descendant_free (descendant->siblings);
profile_descendant_free (descendant->children);
g_free (descendant);
}
void
profile_caller_free (ProfileCaller *caller)
{
if (!caller)
return;
profile_caller_free (caller->next);
g_free (caller);
}

74
profile.h Normal file
View File

@ -0,0 +1,74 @@
/* MemProf -- memory profiler and leak detector
* Copyright 2002, Soeren Sandmann (sandmann@daimi.au.dk)
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*====*/
#include <glib.h>
#include "binfile.h"
#include "process.h"
#include "stackstash.h"
typedef struct Profile Profile;
typedef struct ProfileObject ProfileObject;
typedef struct ProfileDescendant ProfileDescendant;
typedef struct ProfileCaller ProfileCaller;
struct ProfileObject
{
char * name; /* identifies this object uniquely */
guint total; /* sum of all toplevel totals */
guint self; /* sum of all selfs */
};
struct ProfileDescendant
{
ProfileObject * object;
guint self;
guint total;
guint non_recursion;
ProfileDescendant * parent;
ProfileDescendant * siblings;
ProfileDescendant * children;
int marked_non_recursive;
int marked_total;
};
struct Profile
{
gint profile_size;
GList * objects;
};
struct ProfileCaller
{
ProfileObject * object; /* can be NULL */
guint total;
guint self;
ProfileCaller * next;
};
Profile * profile_new (StackStash *stash);
void profile_free (Profile *profile);
ProfileDescendant *profile_create_descendants (Profile *prf,
ProfileObject *object);
ProfileCaller * profile_list_callers (Profile *profile,
ProfileObject *callee);
void profile_caller_free (ProfileCaller *caller);
void profile_descendant_free (ProfileDescendant *descendant);

195
stackstash.c Normal file
View File

@ -0,0 +1,195 @@
#include "stackstash.h"
typedef struct StackNode StackNode;
struct StackNode
{
StackNode * parent;
gpointer address;
StackNode * siblings;
StackNode * children;
StackNode * next; /* next leaf with the same pid */
int size;
};
struct StackStash
{
StackNode *root;
GHashTable *leaves_by_process;
};
static StackNode *
stack_node_new (void)
{
StackNode *node = g_new (StackNode, 1);
node->siblings = NULL;
node->children = NULL;
node->address = NULL;
node->parent = NULL;
node->next = NULL;
node->size = 0;
return node;
}
static void
stack_node_destroy (gpointer p)
{
StackNode *node = p;
if (node)
{
stack_node_destroy (node->siblings);
stack_node_destroy (node->children);
g_free (node);
}
}
/* Stach */
StackStash *
stack_stash_new (void)
{
StackStash *stash = g_new (StackStash, 1);
stash->leaves_by_process =
g_hash_table_new (g_direct_hash, g_direct_equal);
stash->root = NULL;
return stash;
}
static StackNode *
stack_node_add_trace (StackNode *node,
GList *bottom,
gint size,
StackNode **leaf)
{
StackNode *match;
StackNode *n;
if (!bottom)
{
*leaf = NULL;
return node;
}
if (!bottom->next)
{
/* A leaf must always be separate, so pids can
* point to them
*/
match = NULL;
}
else
{
for (match = node; match != NULL; match = match->siblings)
{
if (match->address == bottom->data)
break;
}
}
if (!match)
{
match = stack_node_new ();
match->address = bottom->data;
match->siblings = node;
node = match;
}
match->children =
stack_node_add_trace (match->children, bottom->next, size, leaf);
for (n = match->children; n; n = n->siblings)
n->parent = match;
if (!bottom->next)
{
match->size += size;
*leaf = match;
}
return node;
}
void
stack_stash_add_trace (StackStash *stash,
Process *process,
gulong *addrs,
int n_addrs,
int size)
{
GList *trace;
StackNode *leaf;
int i;
trace = NULL;
for (i = 0; i < n_addrs; ++i)
trace = g_list_prepend (trace, GINT_TO_POINTER (addrs[i]));
stash->root = stack_node_add_trace (stash->root, trace, size, &leaf);
leaf->next = g_hash_table_lookup (
stash->leaves_by_process, process);
g_hash_table_insert (
stash->leaves_by_process, process, leaf);
g_list_free (trace);
}
typedef struct CallbackInfo
{
StackFunction func;
gpointer data;
} CallbackInfo;
static void
do_callback (gpointer key, gpointer value, gpointer data)
{
CallbackInfo *info = data;
Process *process = key;
StackNode *n;
StackNode *leaf = value;
while (leaf)
{
GSList *trace;
trace = NULL;
for (n = leaf; n; n = n->parent)
trace = g_slist_prepend (trace, n->address);
info->func (process, trace, leaf->size, info->data);
g_slist_free (trace);
leaf = leaf->next;
}
}
void
stack_stash_foreach (StackStash *stash,
StackFunction stack_func,
gpointer data)
{
CallbackInfo info;
info.func = stack_func;
info.data = data;
g_hash_table_foreach (stash->leaves_by_process, do_callback, &info);
}
static void
stack_node_free (StackNode *node)
{
if (!node)
return;
stack_node_free (node->siblings);
stack_node_free (node->children);
g_free (node);
}
void
stack_stash_free (StackStash *stash)
{
stack_node_free (stash->root);
g_hash_table_destroy (stash->leaves_by_process);
}

26
stackstash.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef STACK_STASH_H
#define STACK_STASH_H
#include <glib.h>
#include "process.h"
typedef struct StackStash StackStash;
typedef void (* StackFunction) (Process *process,
GSList *trace,
gint size,
gpointer data);
/* Stach */
StackStash *stack_stash_new (void);
void stack_stash_add_trace (StackStash *stash,
Process *process,
gulong *addrs,
gint n_addrs,
int size);
void stack_stash_foreach (StackStash *stash,
StackFunction stack_func,
gpointer data);
void stack_stash_free (StackStash *stash);
#endif

286
sysprof-module.c Normal file
View File

@ -0,0 +1,286 @@
#include <linux/config.h>
#ifdef CONFIG_SMP
# define __SMP__
#endif
#include <asm/atomic.h>
#include <linux/kernel.h> /* Needed for KERN_ALERT */
#include <linux/module.h> /* Needed by all modules */
#include <linux/tqueue.h>
#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#include "sysprof-module.h"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Soeren Sandmann (sandmann@daimi.au.dk)");
#define SAMPLES_PER_SECOND 50 /* must divide HZ */
static const int cpu_profiler = 0; /* 0: page faults, 1: cpu */
static void on_timer_interrupt (void *);
static struct tq_struct timer_task =
{
{ NULL, NULL },
0,
on_timer_interrupt,
NULL
};
int exiting = 0;
DECLARE_WAIT_QUEUE_HEAD (wait_for_exit);
#define N_TRACES 256
static SysprofStackTrace stack_traces[N_TRACES];
static SysprofStackTrace * head = &stack_traces[0];
static SysprofStackTrace * tail = &stack_traces[0];
DECLARE_WAIT_QUEUE_HEAD (wait_for_trace);
static void
generate_stack_trace (struct task_struct *task,
SysprofStackTrace *trace)
{
#define START_OF_STACK 0xBFFFFFFF
typedef struct StackFrame StackFrame;
struct StackFrame {
StackFrame *next;
void *return_address;
};
struct pt_regs *regs = (struct pt_regs *)(
(long)current + THREAD_SIZE - sizeof (struct pt_regs));
StackFrame *frame;
int i;
memset (trace, 0, sizeof (SysprofStackTrace));
trace->pid = current->pid;
trace->truncated = 0;
trace->addresses[0] = (void *)regs->eip;
i = 1;
frame = (StackFrame *)regs->ebp;
while (frame && i < SYSPROF_MAX_ADDRESSES &&
(long)frame < (long)START_OF_STACK &&
(long)frame >= regs->esp)
{
void *next = NULL;
if (verify_area(VERIFY_READ, frame, sizeof(StackFrame)) == 0)
{
void *return_address;
__get_user (return_address, &(frame->return_address));
__get_user (next, &(frame->next));
trace->addresses[i++] = return_address;
if ((long)next <= (long)frame)
next = NULL;
}
frame = next;
}
trace->n_addresses = i;
if (i == SYSPROF_MAX_ADDRESSES)
trace->truncated = 1;
else
trace->truncated = 0;
}
struct page *
hijacked_nopage (struct vm_area_struct * area,
unsigned long address,
int unused)
{
if (current && current->pid != 0)
{
#if 0
generate_stack_trace (current, head);
if (head++ == &stack_traces[N_TRACES - 1])
head = &stack_traces[0];
wake_up (&wait_for_trace);
#endif
memset (head, 0, sizeof (SysprofStackTrace));
head->n_addresses = 1;
head->pid = current->pid;
head->addresses[0] = (void *)address;
head->truncated = 0;
if (area->vm_file)
{
char *line = d_path (area->vm_file->f_dentry,
area->vm_file->f_vfsmnt,
head->filename, PAGE_SIZE);
strncpy (head->filename, line, sizeof (head->filename) - 1);
#if 0
if (line == head->filename)
printk (KERN_ALERT "got name\n");
else if (line == NULL)
printk (KERN_ALERT "noasdf\n");
else
printk (KERN_ALERT "%s\n", line);
#endif
head->map_start = area->vm_start;
}
if (head++ == &stack_traces[N_TRACES - 1])
head = &stack_traces[0];
wake_up (&wait_for_trace);
}
return filemap_nopage (area, address, unused);
}
static void
hijack_nopages (struct task_struct *task)
{
struct mm_struct *mm = task->mm;
struct vm_area_struct *vma;
if (!mm)
return;
for (vma = mm->mmap; vma != NULL; vma = vma->vm_next)
{
if (vma->vm_ops && vma->vm_ops->nopage == filemap_nopage)
vma->vm_ops->nopage = hijacked_nopage;
}
}
static void
clean_hijacked_nopages (void)
{
struct task_struct *task;
for_each_process (task)
{
struct mm_struct *mm = task->mm;
if (mm)
{
struct vm_area_struct *vma;
for (vma = mm->mmap; vma != NULL; vma = vma->vm_next)
{
if (vma->vm_ops && vma->vm_ops->nopage == hijacked_nopage)
vma->vm_ops->nopage = filemap_nopage;
}
}
}
}
static void
on_timer_interrupt (void *data)
{
static int n_ticks = 0;
if (exiting)
{
clean_hijacked_nopages ();
wake_up (&wait_for_exit);
return;
}
++n_ticks;
if (current && current->pid != 0 &&
(n_ticks % (HZ / SAMPLES_PER_SECOND)) == 0)
{
if (cpu_profiler)
{
generate_stack_trace (current, head);
if (head++ == &stack_traces[N_TRACES - 1])
head = &stack_traces[0];
wake_up (&wait_for_trace);
}
else
{
hijack_nopages (current);
}
}
queue_task (&timer_task, &tq_timer);
}
static int
procfile_read (char *buffer,
char **buffer_location,
off_t offset,
int buffer_length,
int *eof,
void *data)
{
#if 0
if (offset > 0)
return 0;
#endif
wait_event_interruptible (wait_for_trace, head != tail);
*buffer_location = (char *)tail;
if (tail++ == &stack_traces[N_TRACES - 1])
tail = &stack_traces[0];
return sizeof (SysprofStackTrace);
}
struct proc_dir_entry *trace_proc_file;
static unsigned int
procfile_poll (struct file *filp, poll_table *poll_table)
{
if (head != tail)
{
return POLLIN;
}
poll_wait (filp, &wait_for_trace, poll_table);
if (head != tail)
{
return POLLIN;
}
return 0;
}
int
init_module (void)
{
trace_proc_file =
create_proc_entry ("sysprof-trace", S_IFREG | S_IRUGO, &proc_root);
if (!trace_proc_file)
return 1;
trace_proc_file->read_proc = procfile_read;
trace_proc_file->proc_fops->poll = procfile_poll;
trace_proc_file->size = sizeof (SysprofStackTrace);
queue_task(&timer_task, &tq_timer);
printk (KERN_ALERT "starting sysprof module\n");
return 0;
}
void
cleanup_module(void)
{
exiting = 1;
sleep_on (&wait_for_exit);
remove_proc_entry ("sysprof-trace", &proc_root);
printk (KERN_ALERT "stopping sysprof module\n");
}

21
sysprof-module.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef SYSPROF_MODULE_H
#define SYSPROF_MODULE_H
typedef struct SysprofStackTrace SysprofStackTrace;
#define SYSPROF_MAX_ADDRESSES 1024
struct SysprofStackTrace
{
int pid;
int truncated;
int n_addresses; /* note: this can be 1 if the process was compiled
* with -fomit-frame-pointer or is otherwise weird
*/
void *addresses[SYSPROF_MAX_ADDRESSES];
char filename[8192];
int map_start;
};
#endif

712
sysprof.c Normal file
View File

@ -0,0 +1,712 @@
#include <stdio.h>
#include <gtk/gtk.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <glade/glade.h>
#include "binfile.h"
#include "watch.h"
#include "sysprof-module.h"
#include "stackstash.h"
#include "profile.h"
#include "treeviewutils.h"
/* FIXME */
#define _(a) a
typedef struct Application Application;
struct Application
{
int input_fd;
StackStash * stash;
GList * page_faults;
GtkTreeView * object_view;
GtkTreeView * callers_view;
GtkTreeView * descendants_view;
GtkStatusbar * statusbar;
GtkToolItem * profile_button;
GtkToolItem * reset_button;
Profile * profile;
ProfileDescendant * descendants;
ProfileCaller * callers;
int n_samples;
gboolean profiling;
int timeout_id;
int generating_profile;
};
static void
disaster (const char *what)
{
fprintf (stderr, what);
exit (1);
}
static void
update_sensitivity (Application *app)
{
gboolean sensitive_profile_button = (app->n_samples != 0);
gtk_widget_set_sensitive (GTK_WIDGET (app->profile_button),
sensitive_profile_button);
#if 0
gtk_widget_set_sensitive (GTK_WIDGET (app->reset_button),
sensitive_profile_button);
#endif
}
#if 0
static gchar *
get_name (pid_t pid)
{
char *cmdline;
char *name = g_strdup_printf ("/proc/%d/cmdline", pid);
if (g_file_get_contents (name, &cmdline, NULL, NULL))
return cmdline;
else
return g_strdup ("<unknown>");
}
#endif
static gboolean
really_show_samples (gpointer data)
{
Application *app = data;
char *label;
label = g_strdup_printf ("Samples: %d", app->n_samples);
gtk_statusbar_pop (app->statusbar, 0);
gtk_statusbar_push (app->statusbar, 0, label);
g_free (label);
app->timeout_id = 0;
return FALSE;
}
static void
show_samples (Application *app)
{
if (!app->timeout_id)
app->timeout_id = g_timeout_add (300, really_show_samples, app);
}
static void
on_read (gpointer data)
{
Application *app = data;
SysprofStackTrace trace;
int rd;
rd = read (app->input_fd, &trace, sizeof (trace));
if (app->profiling && !app->generating_profile)
{
Process *process = process_get_from_pid (trace.pid);
int i;
char *filename = NULL;
if (*trace.filename)
filename = trace.filename;
for (i = 0; i < trace.n_addresses; ++i)
process_ensure_map (process, trace.pid, (gulong)trace.addresses[i]);
g_assert (!app->generating_profile);
stack_stash_add_trace (
app->stash, process,
(gulong *)trace.addresses, trace.n_addresses, 1);
app->n_samples++;
show_samples (app);
}
update_sensitivity (app);
}
static void
on_reset (GtkWidget *widget, gpointer data)
{
Application *app = data;
if (app->profile)
{
profile_free (app->profile);
app->profile = NULL;
gtk_tree_view_set_model (GTK_TREE_VIEW (app->object_view), NULL);
gtk_tree_view_set_model (GTK_TREE_VIEW (app->callers_view), NULL);
gtk_tree_view_set_model (GTK_TREE_VIEW (app->descendants_view), NULL);
}
if (app->stash)
stack_stash_free (app->stash);
app->stash = stack_stash_new ();
process_flush_caches ();
app->n_samples = 0;
show_samples (app);
update_sensitivity (app);
}
enum
{
OBJECT_NAME,
OBJECT_SELF,
OBJECT_TOTAL,
OBJECT_OBJECT
};
enum
{
CALLERS_NAME,
CALLERS_SELF,
CALLERS_TOTAL,
CALLERS_OBJECT
};
enum
{
DESCENDANTS_NAME,
DESCENDANTS_SELF,
DESCENDANTS_NON_RECURSE,
DESCENDANTS_TOTAL,
DESCENDANTS_OBJECT
};
static void
fill_main_list (Application *app)
{
GList *list;
GtkListStore *list_store;
Profile *profile = app->profile;
if (profile)
{
gpointer sort_state;
list_store = gtk_list_store_new (4,
G_TYPE_STRING,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE,
G_TYPE_POINTER);
for (list = profile->objects; list != NULL; list = list->next)
{
ProfileObject *object = list->data;
GtkTreeIter iter;
gtk_list_store_append (list_store, &iter);
gtk_list_store_set (list_store, &iter,
OBJECT_NAME, object->name,
OBJECT_SELF, 100.0 * object->self/(double)profile->profile_size,
OBJECT_TOTAL, 100.0 * object->total/(double)profile->profile_size,
OBJECT_OBJECT, object,
-1);
}
sort_state = save_sort_state (app->object_view);
gtk_tree_view_set_model (app->object_view, GTK_TREE_MODEL (list_store));
if (sort_state)
{
restore_sort_state (app->object_view, sort_state);
}
else
{
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_store),
OBJECT_TOTAL,
GTK_SORT_DESCENDING);
}
g_object_unref (G_OBJECT (list_store));
}
}
static void
on_profile (gpointer widget, gpointer data)
{
Application *app = data;
if (app->generating_profile)
return;
if (app->profile)
profile_free (app->profile);
/* take care of reentrancy */
app->generating_profile = TRUE;
app->profile = profile_new (app->stash);
app->generating_profile = FALSE;
fill_main_list (app);
}
static void
on_delete (GtkWidget *window)
{
gtk_main_quit ();
}
static void
on_start_toggled (GtkToggleToolButton *tool_button, gpointer data)
{
Application *app = data;
app->profiling = gtk_toggle_tool_button_get_active (tool_button);
update_sensitivity (app);
}
static void
add_node (GtkTreeStore *store,
int size,
const GtkTreeIter *parent,
ProfileDescendant *node)
{
GtkTreeIter iter;
if (!node)
return;
gtk_tree_store_insert (store, &iter, (GtkTreeIter *)parent, 0);
gtk_tree_store_set (store, &iter,
DESCENDANTS_NAME, node->object->name,
DESCENDANTS_SELF, 100 * (node->self)/(double)size,
DESCENDANTS_NON_RECURSE, 100 * (node->non_recursion)/(double)size,
DESCENDANTS_TOTAL, 100 * (node->total)/(double)size,
DESCENDANTS_OBJECT, node->object,
-1);
add_node (store, size, parent, node->siblings);
add_node (store, size, &iter, node->children);
}
static ProfileObject *
get_current_object (Application *app)
{
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter selected;
ProfileObject *object;
selection = gtk_tree_view_get_selection (app->object_view);
gtk_tree_selection_get_selected (selection, &model, &selected);
gtk_tree_model_get (model, &selected,
OBJECT_OBJECT, &object,
-1);
return object;
}
static void
fill_descendants_tree (Application *app)
{
GtkTreeStore *tree_store;
gpointer sort_state;
sort_state = save_sort_state (app->descendants_view);
if (app->descendants)
{
profile_descendant_free (app->descendants);
app->descendants = NULL;
}
tree_store =
gtk_tree_store_new (5,
G_TYPE_STRING,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE,
G_TYPE_POINTER);
if (app->profile)
{
ProfileObject *object = get_current_object (app);
if (object)
{
app->descendants =
profile_create_descendants (app->profile, object);
add_node (tree_store,
app->profile->profile_size, NULL, app->descendants);
}
}
gtk_tree_view_set_model (
app->descendants_view, GTK_TREE_MODEL (tree_store));
g_object_unref (G_OBJECT (tree_store));
if (!sort_state)
{
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (tree_store),
DESCENDANTS_NON_RECURSE,
GTK_SORT_DESCENDING);
}
else
{
restore_sort_state (app->descendants_view, sort_state);
}
}
static void
add_callers (GtkListStore *list_store,
Profile *profile,
ProfileCaller *callers)
{
while (callers)
{
gchar *name;
GtkTreeIter iter;
if (callers->object)
name = callers->object->name;
else
name = "<spontaneous>";
gtk_list_store_append (list_store, &iter);
gtk_list_store_set (
list_store, &iter,
CALLERS_NAME, name,
CALLERS_SELF, 100.0 * callers->self/(double)profile->profile_size,
CALLERS_TOTAL, 100.0 * callers->total/(double)profile->profile_size,
CALLERS_OBJECT, callers->object,
-1);
callers = callers->next;
}
}
static void
fill_callers_list (Application *app)
{
GtkListStore *list_store;
gpointer sort_state;
sort_state = save_sort_state (app->descendants_view);
if (app->callers)
{
profile_caller_free (app->callers);
app->callers = NULL;
}
list_store =
gtk_list_store_new (4,
G_TYPE_STRING,
G_TYPE_DOUBLE,
G_TYPE_DOUBLE,
G_TYPE_POINTER);
if (app->profile)
{
ProfileObject *object = get_current_object (app);
if (object)
{
app->callers = profile_list_callers (app->profile, object);
add_callers (list_store, app->profile, app->callers);
}
}
gtk_tree_view_set_model (
app->callers_view, GTK_TREE_MODEL (list_store));
g_object_unref (G_OBJECT (list_store));
if (!sort_state)
{
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_store),
CALLERS_TOTAL,
GTK_SORT_DESCENDING);
}
else
{
restore_sort_state (app->callers_view, sort_state);
}
}
static void
on_object_selection_changed (GtkTreeSelection *selection, gpointer data)
{
Application *app = data;
GtkTreePath *path;
fill_descendants_tree (app);
fill_callers_list (app);
/* Expand the toplevel of the descendant tree so we see the immediate
* descendants.
*/
path = gtk_tree_path_new_from_indices (0, -1);
gtk_tree_view_expand_row (
GTK_TREE_VIEW (app->descendants_view), path, FALSE);
gtk_tree_path_free (path);
}
static void
really_goto_object (Application *app, ProfileObject *object)
{
GtkTreeModel *profile_objects;
GtkTreeIter iter;
gboolean found = FALSE;
profile_objects = gtk_tree_view_get_model (app->object_view);
if (gtk_tree_model_get_iter_first (profile_objects, &iter))
{
do
{
ProfileObject *profile_object;
gtk_tree_model_get (profile_objects, &iter,
OBJECT_OBJECT, &profile_object,
-1);
if (profile_object == object)
{
found = TRUE;
break;
}
}
while (gtk_tree_model_iter_next (profile_objects, &iter));
}
if (found)
{
GtkTreePath *path =
gtk_tree_model_get_path (profile_objects, &iter);
gtk_tree_view_set_cursor (app->object_view, path, 0, FALSE);
}
gtk_widget_grab_focus (GTK_WIDGET (app->object_view));
}
static void
goto_object (Application *app,
GtkTreeView *tree_view,
GtkTreePath *path,
gint column)
{
GtkTreeIter iter;
GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
ProfileObject *object;
if (!gtk_tree_model_get_iter (model, &iter, path))
return;
gtk_tree_model_get (model, &iter, column, &object, -1);
if (!object)
return;
really_goto_object (app, object);
}
static void
on_descendants_row_activated (GtkTreeView *tree_view,
GtkTreePath *path,
GtkTreeViewColumn *column,
gpointer data)
{
goto_object (data, tree_view, path, DESCENDANTS_OBJECT);
}
static void
on_callers_row_activated (GtkTreeView *tree_view,
GtkTreePath *path,
GtkTreeViewColumn *column,
gpointer data)
{
goto_object (data, tree_view, path, CALLERS_OBJECT);
}
static void
add_stock (const char *stock_id,
const char *label, guint size, const guint8 *data)
{
GdkPixbuf *pixbuf;
GtkIconSet *icon_set;
GtkIconFactory *icon_factory;
GtkStockItem stock_item;
pixbuf = gdk_pixbuf_new_from_inline (size, data, FALSE, NULL);
icon_set = gtk_icon_set_new_from_pixbuf (pixbuf);
icon_factory = gtk_icon_factory_new ();
gtk_icon_factory_add (icon_factory, stock_id, icon_set);
gtk_icon_factory_add_default (icon_factory);
g_object_unref (G_OBJECT (icon_factory));
gtk_icon_set_unref (icon_set);
stock_item.stock_id = (char *)stock_id;
stock_item.label = (char *)label;
stock_item.modifier = 0;
stock_item.keyval = 0;
stock_item.translation_domain = NULL;
gtk_stock_add (&stock_item, 1);
}
static void
build_gui (Application *app)
{
GladeXML *xml;
GtkWidget *main_window;
GtkWidget *main_vbox;
GtkWidget *toolbar;
GtkToolItem *item;
GtkTreeSelection *selection;
xml = glade_xml_new ("./sysprof.glade", NULL, NULL);
/* Main Window */
main_window = glade_xml_get_widget (xml, "main_window");
g_signal_connect (G_OBJECT (main_window), "delete_event",
G_CALLBACK (on_delete), NULL);
gtk_window_set_default_size (GTK_WINDOW (main_window), 640, 400);
gtk_widget_show_all (main_window);
/* Toolbar */
main_vbox = glade_xml_get_widget (xml, "main_vbox");
toolbar = gtk_toolbar_new ();
/* Stock Items */
#include "pixbufs.c"
add_stock ("sysprof-start-profiling", "Star_t",
sizeof (start_profiling), start_profiling);
add_stock ("sysprof-stop-profiling", "Sto_p",
sizeof (stop_profiling), stop_profiling);
/* Stop */
item = gtk_radio_tool_button_new_from_stock (
NULL, "sysprof-stop-profiling");
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -11);
/* Start */
item = gtk_radio_tool_button_new_with_stock_from_widget (
GTK_RADIO_TOOL_BUTTON (item), "sysprof-start-profiling");
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 0);
g_signal_connect (G_OBJECT (item), "toggled",
G_CALLBACK (on_start_toggled), app);
/* Reset */
item = gtk_tool_button_new_from_stock (GTK_STOCK_CLEAR);
gtk_tool_button_set_label (GTK_TOOL_BUTTON (item), "_Reset");
gtk_tool_button_set_use_underline (GTK_TOOL_BUTTON (item), TRUE);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
g_signal_connect (G_OBJECT (item), "clicked",
G_CALLBACK (on_reset), app);
app->reset_button = item;
/* Separator */
item = gtk_separator_tool_item_new ();
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
/* Profile */
item = gtk_tool_button_new_from_stock (GTK_STOCK_JUSTIFY_LEFT);
gtk_tool_button_set_label (GTK_TOOL_BUTTON (item), "_Profile");
gtk_tool_button_set_use_underline (GTK_TOOL_BUTTON (item), TRUE);
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
g_signal_connect (G_OBJECT (item), "clicked",
G_CALLBACK (on_profile), app);
app->profile_button = item;
/* Show toolbar */
gtk_widget_show_all (GTK_WIDGET (toolbar));
/* Add toolbar to vbox */
gtk_container_add (GTK_CONTAINER (main_vbox), toolbar);
gtk_box_reorder_child (GTK_BOX (main_vbox), toolbar, 1);
gtk_box_set_child_packing (GTK_BOX (main_vbox), toolbar,
FALSE, TRUE, 0, GTK_PACK_START);
/* TreeViews */
/* object view */
app->object_view = (GtkTreeView *)glade_xml_get_widget (xml, "object_view");
add_plain_text_column (app->object_view, _("Name"), OBJECT_NAME);
add_double_format_column (app->object_view, _("Self"), OBJECT_SELF, "%.2f");
add_double_format_column (app->object_view, _("Total"), OBJECT_TOTAL, "%.2f");
selection = gtk_tree_view_get_selection (app->object_view);
g_signal_connect (selection, "changed", G_CALLBACK (on_object_selection_changed), app);
/* callers view */
app->callers_view = (GtkTreeView *)glade_xml_get_widget (xml, "callers_view");
add_plain_text_column (app->callers_view, _("Name"), CALLERS_NAME);
add_double_format_column (app->callers_view, _("Self"), CALLERS_SELF, "%.2f");
add_double_format_column (app->callers_view, _("Total"), CALLERS_TOTAL, "%.2f");
g_signal_connect (app->callers_view, "row-activated",
G_CALLBACK (on_callers_row_activated), app);
/* descendants view */
app->descendants_view = (GtkTreeView *)glade_xml_get_widget (xml, "descendants_view");
add_plain_text_column (app->descendants_view, _("Name"), DESCENDANTS_NAME);
add_double_format_column (app->descendants_view, _("Self"), DESCENDANTS_SELF, "%.2f");
add_double_format_column (app->descendants_view, _("Cummulative"), DESCENDANTS_NON_RECURSE, "%.2f");
g_signal_connect (app->descendants_view, "row-activated",
G_CALLBACK (on_descendants_row_activated), app);
/* Statusbar */
app->statusbar = (GtkStatusbar *)glade_xml_get_widget (xml, "statusbar");
show_samples (app);
}
static Application *
application_new (void)
{
Application *app = g_new0 (Application, 1);
app->stash = stack_stash_new ();
app->input_fd = -1;
return app;
}
int
main (int argc, char **argv)
{
Application *app;
gtk_init (&argc, &argv);
app = application_new ();
app->input_fd = open ("/proc/sysprof-trace", O_RDONLY);
if (app->input_fd < 0)
{
disaster ("Can't open /proc/sysprof-trace. You need to insert\n"
"the sysprof kernel module. Type\n"
"\n"
" insmod sysprof-module.o\n"
"\n"
"as root\n");
}
fd_add_watch (app->input_fd, app);
fd_set_read_callback (app->input_fd, on_read);
build_gui (app);
update_sensitivity (app);
gtk_main ();
return 0;
}

318
sysprof.glade Normal file
View File

@ -0,0 +1,318 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkWindow" id="main_window">
<property name="title" translatable="yes">System Profiler</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<child>
<widget class="GtkVBox" id="main_vbox">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkMenuBar" id="menubar1">
<property name="visible">True</property>
<child>
<widget class="GtkMenuItem" id="menuitem1">
<property name="visible">True</property>
<property name="label" translatable="yes">_File</property>
<property name="use_underline">True</property>
<child>
<widget class="GtkMenu" id="menu1">
<child>
<widget class="GtkImageMenuItem" id="new1">
<property name="visible">True</property>
<property name="label">gtk-new</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_new1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="open1">
<property name="visible">True</property>
<property name="label">gtk-open</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_open1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="save1">
<property name="visible">True</property>
<property name="label">gtk-save</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_save1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="save_as1">
<property name="visible">True</property>
<property name="label">gtk-save-as</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_save_as1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
</widget>
</child>
<child>
<widget class="GtkSeparatorMenuItem" id="separatormenuitem1">
<property name="visible">True</property>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="quit1">
<property name="visible">True</property>
<property name="label">gtk-quit</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_quit1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
</widget>
</child>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="menuitem2">
<property name="visible">True</property>
<property name="label" translatable="yes">_Edit</property>
<property name="use_underline">True</property>
<child>
<widget class="GtkMenu" id="menu2">
<child>
<widget class="GtkImageMenuItem" id="cut1">
<property name="visible">True</property>
<property name="label">gtk-cut</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_cut1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="copy1">
<property name="visible">True</property>
<property name="label">gtk-copy</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_copy1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="paste1">
<property name="visible">True</property>
<property name="label">gtk-paste</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_paste1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="delete1">
<property name="visible">True</property>
<property name="label">gtk-delete</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_delete1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
</widget>
</child>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="menuitem3">
<property name="visible">True</property>
<property name="label" translatable="yes">_View</property>
<property name="use_underline">True</property>
<child>
<widget class="GtkMenu" id="menu3">
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="menuitem4">
<property name="visible">True</property>
<property name="label" translatable="yes">_Help</property>
<property name="use_underline">True</property>
<child>
<widget class="GtkMenu" id="menu4">
<child>
<widget class="GtkMenuItem" id="about1">
<property name="visible">True</property>
<property name="label" translatable="yes">_About</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_about1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
</widget>
</child>
</widget>
</child>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkHPaned" id="hpaned1">
<property name="border_width">3</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<widget class="GtkVPaned" id="vpaned1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkTreeView" id="object_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">True</property>
<property name="rules_hint">True</property>
<property name="reorderable">False</property>
<property name="enable_search">True</property>
</widget>
</child>
</widget>
<packing>
<property name="shrink">True</property>
<property name="resize">False</property>
</packing>
</child>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow3">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkTreeView" id="callers_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">True</property>
<property name="rules_hint">True</property>
<property name="reorderable">False</property>
<property name="enable_search">True</property>
</widget>
</child>
</widget>
<packing>
<property name="shrink">True</property>
<property name="resize">True</property>
</packing>
</child>
</widget>
<packing>
<property name="shrink">True</property>
<property name="resize">False</property>
</packing>
</child>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkTreeView" id="descendants_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">True</property>
<property name="rules_hint">True</property>
<property name="reorderable">False</property>
<property name="enable_search">True</property>
</widget>
</child>
</widget>
<packing>
<property name="shrink">True</property>
<property name="resize">True</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkStatusbar" id="statusbar">
<property name="visible">True</property>
<property name="has_resize_grip">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

228
treeviewutils.c Normal file
View File

@ -0,0 +1,228 @@
/* -*- mode: C; c-file-style: "linux" -*- */
/* MemProf -- memory profiler and leak detector
* Copyright 2002, Soeren Sandmann (sandmann@daimi.au.dk)
* Copyright 2003, Red Hat, Inc.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*====*/
#include "treeviewutils.h"
void
column_set_sort_id (GtkTreeViewColumn *column,
int column_id)
{
g_object_set_data (G_OBJECT (column),
"mi-saved-sort-column", GINT_TO_POINTER (column_id));
gtk_tree_view_column_set_sort_column_id (column, column_id);
}
void
tree_view_unset_sort_ids (GtkTreeView *tree_view)
{
GList *columns = gtk_tree_view_get_columns (tree_view);
GList *l;
for (l = columns; l; l = l->next) {
gtk_tree_view_column_set_sort_column_id (l->data, -1);
}
g_list_free (columns);
}
void
tree_view_set_sort_ids (GtkTreeView *tree_view)
{
GList *columns = gtk_tree_view_get_columns (tree_view);
GList *l;
for (l = columns; l; l = l->next) {
int column_id = GPOINTER_TO_INT (g_object_get_data (l->data, "mi-saved-sort-column"));
gtk_tree_view_column_set_sort_column_id (l->data, column_id);
}
g_list_free (columns);
}
int
list_iter_get_index (GtkTreeModel *model,
GtkTreeIter *iter)
{
GtkTreePath *path = gtk_tree_model_get_path (model,iter);
int result;
g_assert (path);
g_assert (gtk_tree_path_get_depth (path) == 1);
result = gtk_tree_path_get_indices (path)[0];
gtk_tree_path_free (path);
return result;
}
GtkTreeViewColumn *
add_plain_text_column (GtkTreeView *view, const gchar *title, gint model_column)
{
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new_with_attributes (title, renderer,
"text", model_column,
NULL);
gtk_tree_view_column_set_resizable (column, TRUE);
gtk_tree_view_append_column (view, column);
column_set_sort_id (column, model_column);
return column;
}
static void
pointer_to_text (GtkTreeViewColumn *tree_column,
GtkCellRenderer *cell, GtkTreeModel *tree_model,
GtkTreeIter *iter, gpointer data)
{
gpointer p;
gchar *text;
int column = GPOINTER_TO_INT (data);
gtk_tree_model_get (tree_model, iter, column, &p, -1);
text = g_strdup_printf ("%p", p);
g_object_set (cell, "text", text, NULL);
g_free (text);
}
typedef struct {
int column;
char *format;
} ColumnInfo;
static void
double_to_text (GtkTreeViewColumn *tree_column,
GtkCellRenderer *cell, GtkTreeModel *tree_model,
GtkTreeIter *iter, gpointer data)
{
gdouble d;
gchar *text;
ColumnInfo *info = data;
gtk_tree_model_get (tree_model, iter, info->column, &d, -1);
text = g_strdup_printf (info->format, d);
g_object_set (cell, "text", text, NULL);
g_free (text);
}
static void
free_column_info (void *data)
{
ColumnInfo *info = data;
g_free (info->format);
g_free (info);
}
GtkTreeViewColumn *
add_double_format_column (GtkTreeView *view, const gchar *title, gint model_column, const char *format)
{
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
ColumnInfo *column_info = g_new (ColumnInfo, 1);
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new ();
gtk_tree_view_column_set_title (column, title);
gtk_tree_view_column_pack_start (column, renderer, TRUE);
gtk_tree_view_column_set_resizable (column, TRUE);
column_info->column = model_column;
column_info->format = g_strdup (format);
gtk_tree_view_column_set_cell_data_func (column, renderer,
double_to_text, column_info, free_column_info);
gtk_tree_view_append_column (view, column);
column_set_sort_id (column, model_column);
return column;
}
GtkTreeViewColumn *
add_pointer_column (GtkTreeView *view, const gchar *title, gint model_column)
{
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;
renderer = gtk_cell_renderer_text_new ();
column = gtk_tree_view_column_new ();
if (title)
gtk_tree_view_column_set_title (column, title);
gtk_tree_view_column_pack_start (column, renderer, TRUE);
gtk_tree_view_column_set_resizable (column, TRUE);
gtk_tree_view_column_set_cell_data_func (column, renderer,
pointer_to_text, GINT_TO_POINTER (model_column), NULL);
gtk_tree_view_append_column (view, column);
column_set_sort_id (column, model_column);
return column;
}
typedef struct
{
gboolean is_sorted;
int sort_column;
GtkSortType sort_type;
} SortState;
gpointer
save_sort_state (GtkTreeView *view)
{
SortState *state = NULL;
GtkTreeModel *model = gtk_tree_view_get_model (view);
if (model && GTK_IS_TREE_SORTABLE (model)) {
state = g_new (SortState, 1);
state->is_sorted = gtk_tree_sortable_get_sort_column_id (
GTK_TREE_SORTABLE (model),
&(state->sort_column),
&(state->sort_type));
}
return state;
}
void
restore_sort_state (GtkTreeView *view, gpointer st)
{
SortState *state = st;
GtkTreeModel *model;
if (state) {
model = gtk_tree_view_get_model (view);
if (state->is_sorted && model && GTK_IS_TREE_SORTABLE (model)) {
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
state->sort_column,
state->sort_type);
}
g_free (state);
}
gtk_tree_sortable_sort_column_changed (GTK_TREE_SORTABLE (model));
}

45
treeviewutils.h Normal file
View File

@ -0,0 +1,45 @@
/* -*- mode: C; c-file-style: "linux" -*- */
/* MemProf -- memory profiler and leak detector
* Copyright 2002, Soeren Sandmann (sandmann@daimi.au.dk)
* Copyright 2003, Red Hat, Inc.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*====*/
#include <gtk/gtk.h>
void column_set_sort_id (GtkTreeViewColumn *column,
int column_id);
void tree_view_unset_sort_ids (GtkTreeView *tree_view);
void tree_view_set_sort_ids (GtkTreeView *tree_view);
int list_iter_get_index (GtkTreeModel *model,
GtkTreeIter *iter);
GtkTreeViewColumn *add_plain_text_column (GtkTreeView *view,
const char *title,
gint model_column);
GtkTreeViewColumn *add_double_format_column (GtkTreeView *view,
const char *title,
int model_column,
const char *format);
GtkTreeViewColumn *add_pointer_column (GtkTreeView *view,
const char *title,
int model_column);
gpointer save_sort_state (GtkTreeView *view);
void restore_sort_state (GtkTreeView *view,
gpointer state);

328
watch.c Normal file
View File

@ -0,0 +1,328 @@
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
/* - Library for asynchronous communication
* Copyright (C) 2002 S<>ren Sandmann (sandmann@daimi.au.dk)
*
* This library 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 2 of the License, or
* (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <glib.h>
#include "watch.h"
typedef struct Watch Watch;
struct Watch {
GSource source;
GPollFD poll_fd;
gboolean removed;
WatchCallback read_callback;
WatchCallback write_callback;
WatchCallback hangup_callback;
WatchCallback error_callback;
WatchCallback priority_callback;
gpointer data;
};
static GHashTable *watched_fds;
static void
init (void)
{
if (!watched_fds)
watched_fds = g_hash_table_new (g_int_hash, g_int_equal);
}
static Watch *
lookup_watch (gint fd)
{
init ();
return g_hash_table_lookup (watched_fds, &fd);
}
static void
internal_add_watch (Watch *watch)
{
gpointer fd = &(watch->poll_fd.fd);
init ();
g_hash_table_insert (watched_fds, fd, watch);
g_source_add_poll ((GSource *)watch, &(watch->poll_fd));
}
static void
internal_remove_watch (Watch *watch)
{
gpointer fd = &(watch->poll_fd.fd);
init ();
watch->removed = TRUE;
g_source_remove_poll ((GSource *)watch, &(watch->poll_fd));
g_hash_table_remove (watched_fds, fd);
}
static gboolean
watch_prepare (GSource *source,
gint *timeout)
{
*timeout = -1;
return FALSE;
}
static gboolean
watch_check (GSource *source)
{
Watch *watch = (Watch *)source;
gint revents = watch->poll_fd.revents;
if (revents & (G_IO_NVAL))
{
/* This can happen if the user closes the file descriptor
* without first removing the watch. We silently ignore it
*/
internal_remove_watch (watch);
g_source_unref (source);
return FALSE;
}
if ((revents & G_IO_HUP) && watch->hangup_callback)
return TRUE;
if ((revents & G_IO_IN) && watch->read_callback)
return TRUE;
if ((revents & G_IO_PRI) && watch->priority_callback)
return TRUE;
if ((revents & G_IO_ERR) && watch->error_callback)
return TRUE;
if ((revents & G_IO_OUT) && watch->write_callback)
return TRUE;
return FALSE;
}
static gboolean
watch_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
Watch *watch = (Watch *)source;
gint revents = watch->poll_fd.revents;
gboolean removed;
g_source_ref (source);
if (!watch->removed && (revents & G_IO_IN) && watch->read_callback)
watch->read_callback (watch->data);
if (!watch->removed && (revents & G_IO_PRI) && watch->priority_callback)
watch->priority_callback (watch->data);
if (!watch->removed && (revents & G_IO_OUT) && watch->write_callback)
watch->write_callback (watch->data);
if (!watch->removed && (revents & G_IO_ERR) && watch->error_callback)
watch->error_callback (watch->data);
if (!watch->removed && (revents & G_IO_HUP) && watch->hangup_callback)
watch->hangup_callback (watch->data);
removed = watch->removed;
g_source_unref (source);
if (removed)
return FALSE;
return TRUE;
}
static void
watch_finalize (GSource *source)
{
}
static void
update_poll_mask (Watch *watch)
{
gint events = 0;
g_source_remove_poll ((GSource *)watch, &(watch->poll_fd));
if (watch->read_callback)
events |= G_IO_IN;
if (watch->write_callback)
events |= G_IO_OUT;
if (watch->priority_callback)
events |= G_IO_PRI;
if (watch->hangup_callback)
events |= G_IO_HUP;
if (watch->error_callback)
events |= G_IO_ERR;
watch->poll_fd.events = events;
g_source_add_poll ((GSource *)watch, &(watch->poll_fd));
}
void
fd_add_watch (gint fd,
gpointer data)
{
static GSourceFuncs watch_funcs = {
watch_prepare,
watch_check,
watch_dispatch,
watch_finalize,
};
Watch *watch = lookup_watch (fd);
g_return_if_fail (fd > 0);
g_return_if_fail (watch == NULL);
watch = (Watch *)g_source_new (&watch_funcs, sizeof (Watch));
g_source_set_can_recurse ((GSource *)watch, TRUE);
g_source_attach ((GSource *)watch, NULL);
watch->poll_fd.fd = fd;
watch->poll_fd.events = 0;
watch->removed = FALSE;
watch->read_callback = NULL;
watch->write_callback = NULL;
watch->hangup_callback = NULL;
watch->error_callback = NULL;
watch->priority_callback = NULL;
watch->data = data;
internal_add_watch (watch);
}
void
fd_set_read_callback (gint fd,
WatchCallback read_cb)
{
Watch *watch = lookup_watch (fd);
g_return_if_fail (fd > 0);
g_return_if_fail (watch != NULL);
if (watch->read_callback != read_cb)
{
watch->read_callback = read_cb;
update_poll_mask (watch);
}
}
void
fd_set_write_callback (gint fd,
WatchCallback write_cb)
{
Watch *watch = lookup_watch (fd);
g_return_if_fail (fd > 0);
g_return_if_fail (watch != NULL);
if (watch->write_callback != write_cb)
{
watch->write_callback = write_cb;
update_poll_mask (watch);
}
}
void
fd_set_hangup_callback (gint fd,
WatchCallback hangup_cb)
{
Watch *watch = lookup_watch (fd);
g_return_if_fail (fd > 0);
g_return_if_fail (watch != NULL);
if (watch->hangup_callback != hangup_cb)
{
watch->hangup_callback = hangup_cb;
update_poll_mask (watch);
}
}
void
fd_set_error_callback (gint fd,
WatchCallback error_cb)
{
Watch *watch = lookup_watch (fd);
g_return_if_fail (fd > 0);
g_return_if_fail (watch != NULL);
if (watch->error_callback != error_cb)
{
watch->error_callback = error_cb;
update_poll_mask (watch);
}
}
void
fd_set_priority_callback (gint fd,
WatchCallback priority_cb)
{
Watch *watch = lookup_watch (fd);
g_return_if_fail (fd > 0);
g_return_if_fail (watch != NULL);
if (watch->priority_callback != priority_cb)
{
watch->priority_callback = priority_cb;
update_poll_mask (watch);
}
}
void
fd_remove_watch (gint fd)
{
Watch *watch = lookup_watch (fd);
g_return_if_fail (fd > 0);
if (!watch)
return;
internal_remove_watch (watch);
g_source_unref ((GSource *)watch);
}
gboolean
fd_is_watched (gint fd)
{
g_return_val_if_fail (fd > 0, FALSE);
if (lookup_watch (fd))
return TRUE;
else
return FALSE;
}

22
watch.h Normal file
View File

@ -0,0 +1,22 @@
#include <glib.h>
/*
* Watching file descriptors
*/
typedef void (* WatchCallback) (gpointer data);
void fd_add_watch (gint fd,
gpointer data);
void fd_set_read_callback (gint fd,
WatchCallback read_cb);
void fd_set_write_callback (gint fd,
WatchCallback write_cb);
void fd_set_hangup_callback (gint fd,
WatchCallback hangup_cb);
void fd_set_error_callback (gint fd,
WatchCallback error_cb);
void fd_set_priority_callback (gint fd,
WatchCallback priority_cb);
void fd_remove_watch (gint fd);
gboolean fd_is_watched (gint fd);