commit 43dddf31acbd8556ea08eb1eb714b7848c5988ac Author: Søren Sandmann Pedersen Date: Tue Apr 27 11:08:55 2004 +0000 Initial revision diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..8c960f14 --- /dev/null +++ b/Makefile @@ -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 + diff --git a/README b/README new file mode 100644 index 00000000..ae9e9d87 --- /dev/null +++ b/README @@ -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øren \ No newline at end of file diff --git a/TODO b/TODO new file mode 100644 index 00000000..74c8321e --- /dev/null +++ b/TODO @@ -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 (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 diff --git a/binfile.c b/binfile.c new file mode 100644 index 00000000..f898966c --- /dev/null +++ b/binfile.c @@ -0,0 +1,490 @@ +#include +#include "binfile.h" +#include +#include +#include +#include +#include +#include + +/* 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); +} diff --git a/binfile.h b/binfile.h new file mode 100644 index 00000000..782d6397 --- /dev/null +++ b/binfile.h @@ -0,0 +1,29 @@ +#ifndef BIN_FILE_H +#define BIN_FILE_H + +#include + +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 diff --git a/cdplayer-play.png b/cdplayer-play.png new file mode 100644 index 00000000..2090665c Binary files /dev/null and b/cdplayer-play.png differ diff --git a/cdplayer-stop.png b/cdplayer-stop.png new file mode 100644 index 00000000..1bac2223 Binary files /dev/null and b/cdplayer-stop.png differ diff --git a/pixbufs.c b/pixbufs.c new file mode 100644 index 00000000..ae70ae5e --- /dev/null +++ b/pixbufs.c @@ -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"}; + + diff --git a/process.c b/process.c new file mode 100644 index 00000000..76e3c5c1 --- /dev/null +++ b/process.c @@ -0,0 +1,343 @@ +#include +#include +#include +#include + +#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; +} diff --git a/process.h b/process.h new file mode 100644 index 00000000..d72f6aa5 --- /dev/null +++ b/process.h @@ -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//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 diff --git a/profile.c b/profile.c new file mode 100644 index 00000000..90dd8bc9 --- /dev/null +++ b/profile.c @@ -0,0 +1,630 @@ +#include +#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); +} diff --git a/profile.h b/profile.h new file mode 100644 index 00000000..49404509 --- /dev/null +++ b/profile.h @@ -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 +#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); diff --git a/stackstash.c b/stackstash.c new file mode 100644 index 00000000..5f88ea11 --- /dev/null +++ b/stackstash.c @@ -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); +} diff --git a/stackstash.h b/stackstash.h new file mode 100644 index 00000000..690f16d4 --- /dev/null +++ b/stackstash.h @@ -0,0 +1,26 @@ +#ifndef STACK_STASH_H +#define STACK_STASH_H + +#include +#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 diff --git a/sysprof-module.c b/sysprof-module.c new file mode 100644 index 00000000..ae9cb92d --- /dev/null +++ b/sysprof-module.c @@ -0,0 +1,286 @@ +#include +#ifdef CONFIG_SMP +# define __SMP__ +#endif +#include +#include /* Needed for KERN_ALERT */ +#include /* Needed by all modules */ +#include +#include +#include +#include +#include + +#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"); +} diff --git a/sysprof-module.h b/sysprof-module.h new file mode 100644 index 00000000..fbe10efe --- /dev/null +++ b/sysprof-module.h @@ -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 diff --git a/sysprof.c b/sysprof.c new file mode 100644 index 00000000..0b365a6e --- /dev/null +++ b/sysprof.c @@ -0,0 +1,712 @@ +#include +#include +#include +#include +#include +#include + +#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 (""); +} +#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 = ""; + + 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; +} diff --git a/sysprof.glade b/sysprof.glade new file mode 100644 index 00000000..a26968f2 --- /dev/null +++ b/sysprof.glade @@ -0,0 +1,318 @@ + + + + + + + System Profiler + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + + + + True + False + 0 + + + + True + + + + True + _File + True + + + + + + + True + gtk-new + True + + + + + + + True + gtk-open + True + + + + + + + True + gtk-save + True + + + + + + + True + gtk-save-as + True + + + + + + + True + + + + + + True + gtk-quit + True + + + + + + + + + + + True + _Edit + True + + + + + + + True + gtk-cut + True + + + + + + + True + gtk-copy + True + + + + + + + True + gtk-paste + True + + + + + + + True + gtk-delete + True + + + + + + + + + + + True + _View + True + + + + + + + + + + + True + _Help + True + + + + + + + True + _About + True + + + + + + + + + + 0 + False + False + + + + + + + + + + True + False + 0 + + + + 3 + True + True + + + + True + True + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + True + False + True + + + + + True + False + + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + True + False + True + + + + + True + True + + + + + True + False + + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + True + False + True + + + + + True + True + + + + + 0 + True + True + + + + + + True + True + + + 0 + False + False + + + + + 0 + True + True + + + + + + + diff --git a/treeviewutils.c b/treeviewutils.c new file mode 100644 index 00000000..0cc21834 --- /dev/null +++ b/treeviewutils.c @@ -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)); +} diff --git a/treeviewutils.h b/treeviewutils.h new file mode 100644 index 00000000..c722990a --- /dev/null +++ b/treeviewutils.h @@ -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 + +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); diff --git a/watch.c b/watch.c new file mode 100644 index 00000000..b1e4824f --- /dev/null +++ b/watch.c @@ -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 +#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; +} diff --git a/watch.h b/watch.h new file mode 100644 index 00000000..eb69c9e3 --- /dev/null +++ b/watch.h @@ -0,0 +1,22 @@ +#include + +/* + * 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); +