From 43dddf31acbd8556ea08eb1eb714b7848c5988ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Sandmann=20Pedersen?= Date: Tue, 27 Apr 2004 11:08:55 +0000 Subject: [PATCH] Initial revision --- Makefile | 32 +++ README | 14 + TODO | 81 ++++++ binfile.c | 490 +++++++++++++++++++++++++++++++ binfile.h | 29 ++ cdplayer-play.png | Bin 0 -> 1246 bytes cdplayer-stop.png | Bin 0 -> 625 bytes pixbufs.c | 216 ++++++++++++++ process.c | 343 ++++++++++++++++++++++ process.h | 38 +++ profile.c | 630 ++++++++++++++++++++++++++++++++++++++++ profile.h | 74 +++++ stackstash.c | 195 +++++++++++++ stackstash.h | 26 ++ sysprof-module.c | 286 +++++++++++++++++++ sysprof-module.h | 21 ++ sysprof.c | 712 ++++++++++++++++++++++++++++++++++++++++++++++ sysprof.glade | 318 +++++++++++++++++++++ treeviewutils.c | 228 +++++++++++++++ treeviewutils.h | 45 +++ watch.c | 328 +++++++++++++++++++++ watch.h | 22 ++ 22 files changed, 4128 insertions(+) create mode 100644 Makefile create mode 100644 README create mode 100644 TODO create mode 100644 binfile.c create mode 100644 binfile.h create mode 100644 cdplayer-play.png create mode 100644 cdplayer-stop.png create mode 100644 pixbufs.c create mode 100644 process.c create mode 100644 process.h create mode 100644 profile.c create mode 100644 profile.h create mode 100644 stackstash.c create mode 100644 stackstash.h create mode 100644 sysprof-module.c create mode 100644 sysprof-module.h create mode 100644 sysprof.c create mode 100644 sysprof.glade create mode 100644 treeviewutils.c create mode 100644 treeviewutils.h create mode 100644 watch.c create mode 100644 watch.h 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 0000000000000000000000000000000000000000..2090665cfd7b263fe41c3943f2d07d8369811ae6 GIT binary patch literal 1246 zcmV<41R?v0P)JoF(WM^03d*y_}pJgSfajvd}ML^Z^7A%V2Q4m6a94nl)<}BqdQw(apr@1qh%4j{zqq zCx%U%HlaHnRIxP>qaPrE2Gj#?Zf@X^7Zw&q7G4f*vHm2+41fR{Fb{y56f0J&U=S2U zX+eNGJc-1ne-Ho&paJnfR8$nh%9Sh89X}C>Q-}?D7yt+$x_bcB>7PG;J_8#Y8?tZ@ zwBJnfP&q&V(bWT)nVHxe-vAxCAPWEl5ZydbQc}V&ZQ3*jW@cveA(uu<%moM_x_F?u zxtXE6yBk>ul+LrDNrxf;5I{7q$UqJUg+5vh3mRfsL=E!+0*Iy_=t?@bq{cGa4^i8H49u^B7slPp_DDu zwhthHsOkY;US5VdbLN1@Mv=fvAdUp${j`W}fB>S{1FWo&p-v$oA#fS*=jVqk4H}XQ z2I6D1%4Yxp#7O2mG00)y#{28nufdp~pPxZm8l@Nb6zcesv@9q90*Ks+g?R*~m+k?v zKDxet|Nc>Zf&(Cc$kiwg#D{P={yY$?A*=uLP##7cOL&Iddk%?c28*Qd3i@o`L`Zh>}bUD%aOylK?p! zl%Xe1oCxl)!!sNs^=Fv?0*K;L=*Ep340rC_VPIilVfgUj1H;*~XBn0*UCQwM`E$B7 z5&;5;Vh?~St^WT00qd3m1Q59%;9#K6tRo||arcK@0{{XH0FJNb!mIMw-T(jq07*qo IM6N<$f?JLHB>(^b literal 0 HcmV?d00001 diff --git a/cdplayer-stop.png b/cdplayer-stop.png new file mode 100644 index 0000000000000000000000000000000000000000..1bac22231b15cc6816da1d61d26637721c4f47f2 GIT binary patch literal 625 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmUKs7M+Sy#H?H_c7y}1{rUgj{%`;r1LKh+ z21ghj7c0ChnM-zme{dl$BS3D-rwJE zykqB1!z0I!A8tw6u#y?1>;<#Vy`th`$3PPW3rkB$OU}K=Kt?J>a2Xo%zH8!AXlSc3 z`^3gR(H5(fG8@ay`-o(mTO*j!y**~-ewcJ2Ch zeSJL7uV24TIMqY^DJ=85;YQ(vqX!%n${Zj8w1Tr^KabM!|16L6a)f}f(4jtAin&lr zK--AtjmrIGwi7J(3&EjXxI*8Qfn$MUJTQI)xD{Dfhp$h&u`O4cSzTQ{R+eqnlj5?n zw2scsgRA)z8VY +#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); +