mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
Initial revision
This commit is contained in:
32
Makefile
Normal file
32
Makefile
Normal file
@ -0,0 +1,32 @@
|
||||
CFLAGS = `pkg-config --cflags gtk+-2.0 libglade-2.0` -Wall -g
|
||||
LIBS = `pkg-config --libs gtk+-2.0 libglade-2.0` -lbfd -liberty
|
||||
C_FILES = sysprof.c binfile.c stackstash.c watch.c process.c profile.c treeviewutils.c
|
||||
OBJS = $(addsuffix .o, $(basename $(C_FILES)))
|
||||
BINARY = sysprof
|
||||
MODULE := sysprof-module
|
||||
INCLUDE := -isystem /lib/modules/`uname -r`/build/include
|
||||
MODCFLAGS := -O2 -DMODULE -D__KERNEL__ -Wall ${INCLUDE}
|
||||
|
||||
|
||||
all: $(BINARY) $(MODULE).o
|
||||
|
||||
$(BINARY): $(OBJS) depend
|
||||
gcc $(OBJS) $(LIBS) -o$(BINARY)
|
||||
clean:
|
||||
rm -f $(OBJS) $(BINARY) $(MODULE).o *~ core* depend.mk
|
||||
|
||||
depend:
|
||||
$(CC) -MM $(CFLAGS) $(C_FILES) > depend.mk
|
||||
|
||||
depend.mk:
|
||||
touch depend.mk
|
||||
$(MAKE) depend
|
||||
|
||||
include depend.mk
|
||||
|
||||
.PHONY: depend all
|
||||
|
||||
|
||||
$(MODULE).o: $(MODULE).c
|
||||
gcc $(MODCFLAGS) $(MODULE).c -c -o$(MODULE).o
|
||||
|
||||
14
README
Normal file
14
README
Normal file
@ -0,0 +1,14 @@
|
||||
This is a sampling profiler that uses a kernel module, sysprof-module,
|
||||
to generate stacktraces which are then interpreted by the userspace
|
||||
program "sysprof".
|
||||
|
||||
- The profiler uses a kernel module, so it is Linux specifc
|
||||
|
||||
- There is no auto* stuff. Just type "make" and hope for the best.
|
||||
|
||||
- It does not work on Linux 2.6. Feel free to port it and send me the
|
||||
patch.
|
||||
|
||||
- You need gtk+ 2.4.0 or better, and you need libglade
|
||||
|
||||
S<EFBFBD>ren
|
||||
81
TODO
Normal file
81
TODO
Normal file
@ -0,0 +1,81 @@
|
||||
- make presentation strings nicer
|
||||
|
||||
four different kinds of symbols:
|
||||
|
||||
a) I know exactly what this is
|
||||
b) I know in what library this is
|
||||
c) I know only the process that did this
|
||||
d) I know the name, but there is another similarly named one
|
||||
|
||||
(a) is easy, (b) should be <in ...> (c) should just become "???"
|
||||
(d) not sure
|
||||
|
||||
- grep FIXME
|
||||
- make an "everything" object
|
||||
maybe not necessary -- there is a libc_ctors_something()
|
||||
- consider making ProfileObject more of an object.
|
||||
- hide internal stuff in ProfileDescendant
|
||||
- consider caching [filename->bin_file]
|
||||
|
||||
DONE:
|
||||
|
||||
- processes with a cmdline of "" should get a [pid = %d] instead.
|
||||
|
||||
- Kernel module should report the file the symbol was found in
|
||||
|
||||
- make an "n samples" label
|
||||
Process stuff:
|
||||
|
||||
- make threads be reported together
|
||||
(simply report pids with similar command lines together)
|
||||
(note: it seems separating by pid is way too slow (uses too much memory),
|
||||
so it has to be like this)
|
||||
|
||||
- stack stash should allow different pids to refer to the same root
|
||||
(ie. there is no need to create a new tree for each pid)
|
||||
The *leaves* should contain the pid, not the root. You could even imagine
|
||||
a set of processes, each referring to a set of leaves.
|
||||
|
||||
- when we see a new pid, immediately capture its mappings
|
||||
|
||||
Road map:
|
||||
- new object Process
|
||||
- hashable by pointer
|
||||
- contains list of maps
|
||||
- process_from_pid (pid_t pid, gboolean join_threads)
|
||||
- new processes are gets their maps immediately
|
||||
- resulting pointer must be unref()ed, but it is possible it
|
||||
just points to an existing process
|
||||
- processes with identical cmdlines are taken together
|
||||
- method lookup_symbol()
|
||||
- method get_name()
|
||||
- ref/unref
|
||||
- StackStash stores map from process to leaves
|
||||
- Profile is called with processes
|
||||
|
||||
It is possible that we simply need a better concept of Process:
|
||||
|
||||
If two pids have the same command line, consider them the same, period.
|
||||
This should save considerable amounts of memory.
|
||||
|
||||
The assumptions:
|
||||
|
||||
"No pids are reused during a profiling run"
|
||||
"Two processes with the same command line have the same mappings"
|
||||
|
||||
are somewhat dubious, but probably necessary.
|
||||
|
||||
(More complex kernel:
|
||||
|
||||
have the module report
|
||||
|
||||
- new pid arrived (along with mappings)
|
||||
- mapping changed for pid
|
||||
- stacktrace)
|
||||
|
||||
- make symbols in executable work
|
||||
- the hashtables used in profile.c should not accept NULL as the key
|
||||
- make callers work
|
||||
- autoexpand descendant tree
|
||||
- make double clicks work
|
||||
- fix leaks
|
||||
490
binfile.c
Normal file
490
binfile.c
Normal file
@ -0,0 +1,490 @@
|
||||
#include <glib.h>
|
||||
#include "binfile.h"
|
||||
#include <bfd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* All interesting code in this file is lifted from bfdutils.c
|
||||
* and process.c from Memprof,
|
||||
*
|
||||
* FIXME: add copyrights
|
||||
*
|
||||
*/
|
||||
|
||||
static void bfd_nonfatal (const char *string);
|
||||
static void bfd_fatal (const char *string);
|
||||
|
||||
/* Binary File */
|
||||
struct BinFile
|
||||
{
|
||||
char * filename;
|
||||
int n_symbols;
|
||||
Symbol *symbols;
|
||||
Symbol undefined;
|
||||
};
|
||||
|
||||
static bfd *
|
||||
open_bfd (const char *file)
|
||||
{
|
||||
bfd *abfd = bfd_openr (file, NULL);
|
||||
|
||||
if (!abfd)
|
||||
return NULL;
|
||||
|
||||
if (!bfd_check_format (abfd, bfd_object))
|
||||
{
|
||||
bfd_close (abfd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return abfd;
|
||||
}
|
||||
|
||||
static unsigned long
|
||||
calc_crc32 (unsigned long crc, unsigned char *buf, size_t len)
|
||||
{
|
||||
static const unsigned long crc32_table[256] = {
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
|
||||
0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
|
||||
0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,
|
||||
0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
|
||||
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
|
||||
0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
||||
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
|
||||
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
|
||||
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
|
||||
0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,
|
||||
0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
|
||||
0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
|
||||
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,
|
||||
0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,
|
||||
0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,
|
||||
0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
|
||||
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
|
||||
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
|
||||
0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
|
||||
0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,
|
||||
0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
|
||||
0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
|
||||
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
|
||||
0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
||||
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,
|
||||
0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,
|
||||
0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
|
||||
0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
|
||||
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
|
||||
0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
|
||||
0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,
|
||||
0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
|
||||
0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,
|
||||
0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
|
||||
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
|
||||
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
|
||||
0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,
|
||||
0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
|
||||
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
|
||||
0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,
|
||||
0x2d02ef8d
|
||||
};
|
||||
unsigned char *end;
|
||||
|
||||
crc = ~crc & 0xffffffff;
|
||||
for (end = buf + len; buf < end; ++buf)
|
||||
crc = crc32_table[(crc ^ *buf) & 0xff] ^ (crc >> 8);
|
||||
return ~crc & 0xffffffff;;
|
||||
}
|
||||
|
||||
static char *
|
||||
get_debug_link_info (bfd *abfd, unsigned long *crc32_out)
|
||||
{
|
||||
asection *sect;
|
||||
bfd_size_type debuglink_size;
|
||||
unsigned long crc32;
|
||||
char *contents;
|
||||
int crc_offset;
|
||||
|
||||
sect = bfd_get_section_by_name (abfd, ".gnu_debuglink");
|
||||
|
||||
if (sect == NULL)
|
||||
return NULL;
|
||||
|
||||
debuglink_size = bfd_section_size (abfd, sect);
|
||||
|
||||
contents = g_malloc (debuglink_size);
|
||||
bfd_get_section_contents (abfd, sect, contents,
|
||||
(file_ptr)0, (bfd_size_type)debuglink_size);
|
||||
|
||||
/* Crc value is stored after the filename, aligned up to 4 bytes. */
|
||||
crc_offset = strlen (contents) + 1;
|
||||
crc_offset = (crc_offset + 3) & ~3;
|
||||
|
||||
crc32 = bfd_get_32 (abfd, (bfd_byte *) (contents + crc_offset));
|
||||
|
||||
*crc32_out = crc32;
|
||||
return contents;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
separate_debug_file_exists (const char *name, unsigned long crc)
|
||||
{
|
||||
unsigned long file_crc = 0;
|
||||
int fd;
|
||||
char buffer[8*1024];
|
||||
int count;
|
||||
|
||||
fd = open (name, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return 0;
|
||||
|
||||
while ((count = read (fd, buffer, sizeof (buffer))) > 0)
|
||||
file_crc = calc_crc32 (file_crc, buffer, count);
|
||||
|
||||
close (fd);
|
||||
|
||||
return crc == file_crc;
|
||||
}
|
||||
|
||||
/* FIXME: this should be detected by config.h */
|
||||
static const char *debug_file_directory = "/usr/lib/debug";
|
||||
|
||||
static char *
|
||||
find_separate_debug_file (bfd *abfd)
|
||||
{
|
||||
char *basename;
|
||||
char *dir;
|
||||
char *debugfile;
|
||||
unsigned long crc32;
|
||||
|
||||
basename = get_debug_link_info (abfd, &crc32);
|
||||
if (basename == NULL)
|
||||
return NULL;
|
||||
|
||||
dir = g_path_get_dirname (bfd_get_filename (abfd));
|
||||
|
||||
/* First try in the same directory as the original file: */
|
||||
debugfile = g_build_filename (dir, basename, NULL);
|
||||
if (separate_debug_file_exists (debugfile, crc32))
|
||||
{
|
||||
g_free (basename);
|
||||
g_free (dir);
|
||||
return debugfile;
|
||||
}
|
||||
g_free (debugfile);
|
||||
|
||||
/* Then try in a subdirectory called .debug */
|
||||
debugfile = g_build_filename (dir, ".debug", basename, NULL);
|
||||
if (separate_debug_file_exists (debugfile, crc32))
|
||||
{
|
||||
g_free (basename);
|
||||
g_free (dir);
|
||||
return debugfile;
|
||||
}
|
||||
g_free (debugfile);
|
||||
|
||||
/* Then try in the global debugfile directory */
|
||||
debugfile = g_build_filename (debug_file_directory, dir, basename, NULL);
|
||||
if (separate_debug_file_exists (debugfile, crc32))
|
||||
{
|
||||
g_free (basename);
|
||||
g_free (dir);
|
||||
return debugfile;
|
||||
}
|
||||
g_free (debugfile);
|
||||
|
||||
g_free (basename);
|
||||
g_free (dir);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static asymbol **
|
||||
slurp_symtab (bfd *abfd, long *symcount)
|
||||
{
|
||||
asymbol **sy = (asymbol **) NULL;
|
||||
long storage;
|
||||
|
||||
if (!(bfd_get_file_flags (abfd) & HAS_SYMS))
|
||||
{
|
||||
*symcount = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
storage = bfd_get_symtab_upper_bound (abfd);
|
||||
if (storage < 0)
|
||||
bfd_fatal (bfd_get_filename (abfd));
|
||||
|
||||
if (storage)
|
||||
sy = (asymbol **) malloc (storage);
|
||||
|
||||
*symcount = bfd_canonicalize_symtab (abfd, sy);
|
||||
if (*symcount < 0)
|
||||
bfd_fatal (bfd_get_filename (abfd));
|
||||
|
||||
return sy;
|
||||
}
|
||||
|
||||
extern char *cplus_demangle (const char *mangled, int options);
|
||||
|
||||
#define DMGL_PARAMS (1 << 0) /* Include function args */
|
||||
#define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */
|
||||
|
||||
char *
|
||||
demangle (bfd *bfd, const char *name)
|
||||
{
|
||||
char *demangled;
|
||||
|
||||
if (bfd_get_symbol_leading_char (bfd) == *name)
|
||||
++name;
|
||||
|
||||
demangled = cplus_demangle (name, DMGL_ANSI | DMGL_PARAMS);
|
||||
return demangled ? demangled : strdup (name);
|
||||
}
|
||||
|
||||
static gint
|
||||
compare_address (const void *a, const void *b)
|
||||
{
|
||||
const Symbol *symbol1 = a;
|
||||
const Symbol *symbol2 = b;
|
||||
|
||||
if (symbol1->address < symbol2->address)
|
||||
return -1;
|
||||
else if (symbol1->address == symbol2->address)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
read_symbols (BinFile *bf)
|
||||
{
|
||||
asection *text_section;
|
||||
const char *separate_debug_file;
|
||||
asymbol **bfd_symbols;
|
||||
long n_symbols;
|
||||
int i;
|
||||
bfd *bfd;
|
||||
GArray *symbols;
|
||||
|
||||
bf->symbols = NULL;
|
||||
bf->n_symbols = 0;
|
||||
|
||||
bfd = open_bfd (bf->filename);
|
||||
if (!bfd)
|
||||
return;
|
||||
|
||||
separate_debug_file = find_separate_debug_file (bfd);
|
||||
if (separate_debug_file)
|
||||
{
|
||||
bfd_close (bfd);
|
||||
bfd = open_bfd (separate_debug_file);
|
||||
if (!bfd)
|
||||
return;
|
||||
}
|
||||
|
||||
bfd_symbols = slurp_symtab (bfd, &n_symbols);
|
||||
|
||||
if (!bfd_symbols)
|
||||
return;
|
||||
|
||||
text_section = bfd_get_section_by_name (bfd, ".text");
|
||||
if (!text_section)
|
||||
return;
|
||||
|
||||
symbols = g_array_new (FALSE, FALSE, sizeof (Symbol));
|
||||
|
||||
for (i = 0; i < n_symbols; i++)
|
||||
{
|
||||
Symbol symbol;
|
||||
|
||||
if ((bfd_symbols[i]->flags & BSF_FUNCTION) &&
|
||||
(bfd_symbols[i]->section == text_section))
|
||||
{
|
||||
char *name;
|
||||
|
||||
symbol.address = bfd_asymbol_value (bfd_symbols[i]);
|
||||
name = demangle (bfd, bfd_asymbol_name (bfd_symbols[i]));
|
||||
symbol.name = g_strdup (name);
|
||||
free (name);
|
||||
|
||||
g_array_append_vals (symbols, &symbol, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (n_symbols)
|
||||
free (bfd_symbols);
|
||||
|
||||
/* Sort the symbols by address */
|
||||
qsort (symbols->data, symbols->len, sizeof(Symbol), compare_address);
|
||||
|
||||
bf->n_symbols = symbols->len;
|
||||
bf->symbols = (Symbol *)g_array_free (symbols, FALSE);
|
||||
}
|
||||
|
||||
BinFile *
|
||||
bin_file_new (const char *filename)
|
||||
{
|
||||
BinFile *bf = g_new0 (BinFile, 1);
|
||||
|
||||
bf->filename = g_strdup (filename);
|
||||
|
||||
read_symbols (bf);
|
||||
|
||||
bf->undefined.name = g_strdup_printf ("In file %s", filename);
|
||||
bf->undefined.address = 0x0;
|
||||
|
||||
return bf;
|
||||
}
|
||||
|
||||
void
|
||||
bin_file_free (BinFile *bf)
|
||||
{
|
||||
int i;
|
||||
|
||||
g_free (bf->filename);
|
||||
|
||||
for (i = 0; i < bf->n_symbols; ++i)
|
||||
g_free (bf->symbols[i].name);
|
||||
g_free (bf->symbols);
|
||||
g_free (bf->undefined.name);
|
||||
g_free (bf);
|
||||
}
|
||||
|
||||
const Symbol *
|
||||
bin_file_lookup_symbol (BinFile *bf,
|
||||
gulong address)
|
||||
{
|
||||
int first = 0;
|
||||
int last = bf->n_symbols - 1;
|
||||
int middle = last;
|
||||
Symbol *data;
|
||||
Symbol *result;
|
||||
|
||||
if (!bf->symbols || (bf->n_symbols == 0))
|
||||
return &(bf->undefined);
|
||||
|
||||
data = bf->symbols;
|
||||
|
||||
if (address < data[last].address)
|
||||
{
|
||||
/* Invariant: data[first].addr <= val < data[last].addr */
|
||||
|
||||
while (first < last - 1)
|
||||
{
|
||||
middle = (first + last) / 2;
|
||||
if (address < data[middle].address)
|
||||
last = middle;
|
||||
else
|
||||
first = middle;
|
||||
}
|
||||
/* Size is not included in generic bfd data, so we
|
||||
* ignore it for now. (It is ELF specific)
|
||||
*/
|
||||
result = &data[first];
|
||||
}
|
||||
else
|
||||
{
|
||||
result = &data[last];
|
||||
}
|
||||
|
||||
/* If the name is "call_gmon_start", the file probably doesn't
|
||||
* have any other symbols
|
||||
*/
|
||||
if (strcmp (result->name, "call_gmon_start") == 0)
|
||||
return &(bf->undefined);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* Symbol */
|
||||
Symbol *
|
||||
symbol_copy (const Symbol *orig)
|
||||
{
|
||||
Symbol *copy;
|
||||
|
||||
copy = g_new (Symbol, 1);
|
||||
copy->name = g_strdup (orig->name);
|
||||
copy->address = orig->address;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
gboolean
|
||||
symbol_equal (const void *sa,
|
||||
const void *sb)
|
||||
{
|
||||
const Symbol *syma = sa;
|
||||
const Symbol *symb = sb;
|
||||
|
||||
if (symb->address != syma->address)
|
||||
return FALSE;
|
||||
|
||||
/* symbols compare equal if their names are both NULL */
|
||||
|
||||
if (!syma->name && !symb->name)
|
||||
return TRUE;
|
||||
|
||||
if (!syma)
|
||||
return FALSE;
|
||||
|
||||
if (!symb)
|
||||
return FALSE;
|
||||
|
||||
return strcmp (syma->name, symb->name) == 0;
|
||||
}
|
||||
|
||||
guint
|
||||
symbol_hash (const void *s)
|
||||
{
|
||||
const Symbol *symbol = s;
|
||||
|
||||
if (!s)
|
||||
return 0;
|
||||
|
||||
return symbol->name? g_str_hash (symbol->name) : 0 + symbol->address;
|
||||
}
|
||||
|
||||
void
|
||||
symbol_free (Symbol *symbol)
|
||||
{
|
||||
if (symbol->name)
|
||||
g_free (symbol->name);
|
||||
g_free (symbol);
|
||||
}
|
||||
|
||||
static void
|
||||
bfd_nonfatal (const char *string)
|
||||
{
|
||||
const char *errmsg = bfd_errmsg (bfd_get_error ());
|
||||
|
||||
if (string)
|
||||
{
|
||||
fprintf (stderr, "%s: %s: %s\n",
|
||||
g_get_application_name(), string, errmsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf (stderr, "%s: %s\n",
|
||||
g_get_application_name(), errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
bfd_fatal (const char *string)
|
||||
{
|
||||
bfd_nonfatal (string);
|
||||
exit (1);
|
||||
}
|
||||
29
binfile.h
Normal file
29
binfile.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef BIN_FILE_H
|
||||
#define BIN_FILE_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
typedef struct BinFile BinFile;
|
||||
typedef struct Symbol Symbol;
|
||||
|
||||
/* Binary File */
|
||||
|
||||
BinFile * bin_file_new (const char *filename);
|
||||
void bin_file_free (BinFile *bin_file);
|
||||
const Symbol *bin_file_lookup_symbol (BinFile *bin_file,
|
||||
gulong address);
|
||||
|
||||
/* Symbol */
|
||||
struct Symbol
|
||||
{
|
||||
char * name;
|
||||
gulong address;
|
||||
};
|
||||
|
||||
Symbol * symbol_copy (const Symbol *orig);
|
||||
gboolean symbol_equal (const void *syma,
|
||||
const void *symb);
|
||||
guint symbol_hash (const void *sym);
|
||||
void symbol_free (Symbol *symbol);
|
||||
|
||||
#endif
|
||||
BIN
cdplayer-play.png
Normal file
BIN
cdplayer-play.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
cdplayer-stop.png
Normal file
BIN
cdplayer-stop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 625 B |
216
pixbufs.c
Normal file
216
pixbufs.c
Normal file
@ -0,0 +1,216 @@
|
||||
/* GdkPixbuf RGBA C-Source image dump 1-byte-run-length-encoded */
|
||||
|
||||
#ifdef __SUNPRO_C
|
||||
#pragma align 4 (stop_profiling)
|
||||
#endif
|
||||
#ifdef __GNUC__
|
||||
static const guint8 stop_profiling[] __attribute__ ((__aligned__ (4))) =
|
||||
#else
|
||||
static const guint8 stop_profiling[] =
|
||||
#endif
|
||||
{ ""
|
||||
/* Pixbuf magic (0x47646b50) */
|
||||
"GdkP"
|
||||
/* length: header (24) + pixel_data (1554) */
|
||||
"\0\0\6*"
|
||||
/* pixdata_type (0x2010002) */
|
||||
"\2\1\0\2"
|
||||
/* rowstride (192) */
|
||||
"\0\0\0\300"
|
||||
/* width (48) */
|
||||
"\0\0\0""0"
|
||||
/* height (48) */
|
||||
"\0\0\0""0"
|
||||
/* pixel_data: */
|
||||
"\377\377\377\377\0\367\377\377\377\0\3\377\377\377[\377\377\377\315\377"
|
||||
"\377\377\354\236\377\377\377\352\3\377\377\377\354\377\377\377\321\377"
|
||||
"\377\377c\213\377\377\377\0\1\377\377\377^\244\377\377\377\377\1\377"
|
||||
"\377\377h\212\377\377\377\0\1\377\377\377\317\244\377\377\377\377\1\377"
|
||||
"\377\377\316\212\377\377\377\0\1\377\377\377\357\202\377\377\377\377"
|
||||
"\2\247\247\247\377YYY\377\234ccc\377\2ZZZ\377\240\240\240\377\202\377"
|
||||
"\377\377\377\1\377\377\377\352\212\377\377\377\0\1\377\377\377\360\202"
|
||||
"\377\377\377\377\1mmm\377\236\0\0\0\377\1ccc\377\202\377\377\377\377"
|
||||
"\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377"
|
||||
"\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377"
|
||||
"\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377"
|
||||
"\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377"
|
||||
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377"
|
||||
"\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377"
|
||||
"\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202"
|
||||
"\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360"
|
||||
"\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377"
|
||||
"\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377"
|
||||
"\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377"
|
||||
"\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1oo"
|
||||
"o\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212"
|
||||
"\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0"
|
||||
"\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377"
|
||||
"\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc"
|
||||
"\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377"
|
||||
"\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377"
|
||||
"\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202"
|
||||
"\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377"
|
||||
"\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377"
|
||||
"\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377"
|
||||
"\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377"
|
||||
"\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377"
|
||||
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377"
|
||||
"\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377"
|
||||
"\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202"
|
||||
"\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360"
|
||||
"\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377"
|
||||
"\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377"
|
||||
"\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377"
|
||||
"\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1oo"
|
||||
"o\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212"
|
||||
"\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0"
|
||||
"\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377"
|
||||
"\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc"
|
||||
"\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377"
|
||||
"\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377"
|
||||
"\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202"
|
||||
"\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377"
|
||||
"\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377"
|
||||
"\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377"
|
||||
"\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377"
|
||||
"\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377"
|
||||
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377"
|
||||
"\1ccc\377\202\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377"
|
||||
"\377\377\360\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202"
|
||||
"\377\377\377\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360"
|
||||
"\202\377\377\377\377\1ooo\377\236\0\0\0\377\1ccc\377\202\377\377\377"
|
||||
"\377\1\377\377\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377"
|
||||
"\377\377\1nnn\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377"
|
||||
"\377\353\212\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1nn"
|
||||
"n\377\236\0\0\0\377\1ccc\377\202\377\377\377\377\1\377\377\377\354\212"
|
||||
"\377\377\377\0\1\377\377\377\355\202\377\377\377\377\2\303\303\303\377"
|
||||
"\217\217\217\377\234\226\226\226\377\2\220\220\220\377\277\277\277\377"
|
||||
"\202\377\377\377\377\1\377\377\377\350\212\377\377\377\0\1\377\377\377"
|
||||
"\276\244\377\377\377\377\1\377\377\377\300\212\377\377\377\0\2\377\377"
|
||||
"\377@\377\377\377\363\242\377\377\377\377\2\377\377\377\370\377\377\377"
|
||||
"J\213\377\377\377\0\3\377\377\3773\377\377\377\230\377\377\377\271\236"
|
||||
"\377\377\377\267\3\377\377\377\271\377\377\377\234\377\377\3779\356\377"
|
||||
"\377\377\0\2\252\252\252\0```\0\234iii\0\2```\0\243\243\243\0\220\377"
|
||||
"\377\377\0\1mmm\0\236\0\0\0\0\1ccc\0\215\377\377\377\0\1\377\377\377"
|
||||
"\10\202\377\377\377\11\1ooo\11\236\0\0\0\11\1ccc\11\202\377\377\377\11"
|
||||
"\1\377\377\377\10\205\377\377\377\0"};
|
||||
|
||||
|
||||
/* GdkPixbuf RGBA C-Source image dump 1-byte-run-length-encoded */
|
||||
|
||||
#ifdef __SUNPRO_C
|
||||
#pragma align 4 (start_profiling)
|
||||
#endif
|
||||
#ifdef __GNUC__
|
||||
static const guint8 start_profiling[] __attribute__ ((__aligned__ (4))) =
|
||||
#else
|
||||
static const guint8 start_profiling[] =
|
||||
#endif
|
||||
{ ""
|
||||
/* Pixbuf magic (0x47646b50) */
|
||||
"GdkP"
|
||||
/* length: header (24) + pixel_data (1932) */
|
||||
"\0\0\7\244"
|
||||
/* pixdata_type (0x2010002) */
|
||||
"\2\1\0\2"
|
||||
/* rowstride (192) */
|
||||
"\0\0\0\300"
|
||||
/* width (48) */
|
||||
"\0\0\0""0"
|
||||
/* height (48) */
|
||||
"\0\0\0""0"
|
||||
/* pixel_data: */
|
||||
"\377\377\377\377\0\317\377\377\377\0\2\377\377\377J\377\377\377\275\202"
|
||||
"\377\377\377\337\3\377\377\377\345\377\377\377\311\377\377\377X\250\377"
|
||||
"\377\377\0\2\377\377\377Q\377\377\377\373\206\377\377\377\377\1\377\377"
|
||||
"\377x\247\377\377\377\0\1\377\377\377\314\210\377\377\377\377\1\377\377"
|
||||
"\377\206\246\377\377\377\0\1\377\377\377\357\202\377\377\377\377\4\260"
|
||||
"\260\260\377ddd\377\204\204\204\377\364\364\364\377\203\377\377\377\377"
|
||||
"\1\377\377\377\206\245\377\377\377\0\1\377\377\377\360\202\377\377\377"
|
||||
"\377\1qqq\377\202\0\0\0\377\2VVV\377\364\364\364\377\203\377\377\377"
|
||||
"\377\2\377\377\377\212\377\377\377\1\243\377\377\377\0\1\377\377\377"
|
||||
"\360\202\377\377\377\377\1qqq\377\203\0\0\0\377\2QQQ\377\365\365\365"
|
||||
"\377\203\377\377\377\377\2\377\377\377\217\377\377\377\1\242\377\377"
|
||||
"\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\204\0\0\0\377\2"
|
||||
"KKK\377\360\360\360\377\203\377\377\377\377\2\377\377\377\231\377\377"
|
||||
"\377\3\241\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377"
|
||||
"\205\0\0\0\377\2AAA\377\354\354\354\377\203\377\377\377\377\2\377\377"
|
||||
"\377\241\377\377\377\5\240\377\377\377\0\1\377\377\377\360\202\377\377"
|
||||
"\377\377\1qqq\377\206\0\0\0\377\2AAA\377\354\354\354\377\203\377\377"
|
||||
"\377\377\2\377\377\377\241\377\377\377\4\237\377\377\377\0\1\377\377"
|
||||
"\377\360\202\377\377\377\377\1qqq\377\207\0\0\0\377\2@@@\377\347\347"
|
||||
"\347\377\203\377\377\377\377\2\377\377\377\244\377\377\377\11\236\377"
|
||||
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\210\0\0\0\377"
|
||||
"\2""999\377\343\343\343\377\203\377\377\377\377\2\377\377\377\257\377"
|
||||
"\377\377\15\235\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1"
|
||||
"qqq\377\211\0\0\0\377\2""222\377\341\341\341\377\203\377\377\377\377"
|
||||
"\2\377\377\377\261\377\377\377\15\234\377\377\377\0\1\377\377\377\360"
|
||||
"\202\377\377\377\377\1qqq\377\212\0\0\0\377\2,,,\377\334\334\334\377"
|
||||
"\203\377\377\377\377\2\377\377\377\272\377\377\377\21\233\377\377\377"
|
||||
"\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\213\0\0\0\377\2,,,"
|
||||
"\377\334\334\334\377\203\377\377\377\377\2\377\377\377\272\377\377\377"
|
||||
"\21\232\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377"
|
||||
"\214\0\0\0\377\2+++\377\326\326\326\377\203\377\377\377\377\2\377\377"
|
||||
"\377\274\377\377\377\31\231\377\377\377\0\1\377\377\377\360\202\377\377"
|
||||
"\377\377\1qqq\377\215\0\0\0\377\2\40\40\40\377\320\320\320\377\203\377"
|
||||
"\377\377\377\2\377\377\377\311\377\377\377\33\230\377\377\377\0\1\377"
|
||||
"\377\377\360\202\377\377\377\377\1qqq\377\216\0\0\0\377\2\37\37\37\377"
|
||||
"\317\317\317\377\203\377\377\377\377\2\377\377\377\311\377\377\377\33"
|
||||
"\227\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\217"
|
||||
"\0\0\0\377\2\33\33\33\377\311\311\311\377\203\377\377\377\377\2\377\377"
|
||||
"\377\317\377\377\377\33\226\377\377\377\0\1\377\377\377\360\202\377\377"
|
||||
"\377\377\1qqq\377\220\0\0\0\377\2\33\33\33\377\311\311\311\377\203\377"
|
||||
"\377\377\377\1\377\377\377\235\226\377\377\377\0\1\377\377\377\360\202"
|
||||
"\377\377\377\377\1qqq\377\221\0\0\0\377\2\22\22\22\377\304\304\304\377"
|
||||
"\202\377\377\377\377\1\377\377\377\332\226\377\377\377\0\1\377\377\377"
|
||||
"\360\202\377\377\377\377\1qqq\377\221\0\0\0\377\2""000\377\333\333\333"
|
||||
"\377\202\377\377\377\377\1\377\377\377\324\226\377\377\377\0\1\377\377"
|
||||
"\377\360\202\377\377\377\377\1qqq\377\220\0\0\0\377\2:::\377\346\346"
|
||||
"\346\377\203\377\377\377\377\1\377\377\377\207\226\377\377\377\0\1\377"
|
||||
"\377\377\360\202\377\377\377\377\1qqq\377\217\0\0\0\377\2BBB\377\354"
|
||||
"\354\354\377\203\377\377\377\377\2\377\377\377\250\377\377\377\7\226"
|
||||
"\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\216\0\0"
|
||||
"\0\377\2FFF\377\356\356\356\377\203\377\377\377\377\2\377\377\377\246"
|
||||
"\377\377\377\6\227\377\377\377\0\1\377\377\377\360\202\377\377\377\377"
|
||||
"\1qqq\377\215\0\0\0\377\2PPP\377\356\356\356\377\203\377\377\377\377"
|
||||
"\2\377\377\377\235\377\377\377\7\230\377\377\377\0\1\377\377\377\360"
|
||||
"\202\377\377\377\377\1qqq\377\214\0\0\0\377\2ZZZ\377\371\371\371\377"
|
||||
"\203\377\377\377\377\2\377\377\377\220\377\377\377\1\231\377\377\377"
|
||||
"\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\213\0\0\0\377\2]]]"
|
||||
"\377\371\371\371\377\203\377\377\377\377\1\377\377\377\213\233\377\377"
|
||||
"\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\212\0\0\0\377\2"
|
||||
"iii\377\374\374\374\377\203\377\377\377\377\1\377\377\377\177\234\377"
|
||||
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\211\0\0\0\377"
|
||||
"\1ttt\377\204\377\377\377\377\1\377\377\377t\235\377\377\377\0\1\377"
|
||||
"\377\377\360\202\377\377\377\377\1qqq\377\210\0\0\0\377\1\203\203\203"
|
||||
"\377\203\377\377\377\377\2\377\377\377\370\377\377\377i\236\377\377\377"
|
||||
"\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\207\0\0\0\377\1\213"
|
||||
"\213\213\377\203\377\377\377\377\2\377\377\377\371\377\377\377^\237\377"
|
||||
"\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\206\0\0\0\377"
|
||||
"\1\216\216\216\377\203\377\377\377\377\2\377\377\377\367\377\377\377"
|
||||
"Z\240\377\377\377\0\1\377\377\377\360\202\377\377\377\377\1qqq\377\204"
|
||||
"\0\0\0\377\2\2\2\2\377\230\230\230\377\203\377\377\377\377\2\377\377"
|
||||
"\377\363\377\377\377Q\241\377\377\377\0\1\377\377\377\360\202\377\377"
|
||||
"\377\377\1qqq\377\203\0\0\0\377\2\10\10\10\377\242\242\242\377\203\377"
|
||||
"\377\377\377\2\377\377\377\361\377\377\377L\242\377\377\377\0\1\377\377"
|
||||
"\377\360\202\377\377\377\377\1qqq\377\202\0\0\0\377\2\15\15\15\377\251"
|
||||
"\251\251\377\203\377\377\377\377\2\377\377\377\350\377\377\377A\243\377"
|
||||
"\377\377\0\1\377\377\377\360\202\377\377\377\377\4vvv\377\0\0\0\377\22"
|
||||
"\22\22\377\261\261\261\377\203\377\377\377\377\2\377\377\377\347\377"
|
||||
"\377\377:\244\377\377\377\0\1\377\377\377\350\202\377\377\377\377\3\352"
|
||||
"\352\352\377\325\325\325\377\344\344\344\377\203\377\377\377\377\2\377"
|
||||
"\377\377\344\377\377\3777\245\377\377\377\0\1\377\377\377\236\207\377"
|
||||
"\377\377\377\2\377\377\377\336\377\377\377/\204\377\377\377\0\1\376\376"
|
||||
"\376\0\241\377\377\377\0\2\377\377\377\27\377\377\377\302\205\377\377"
|
||||
"\377\377\2\377\377\377\317\377\377\377&\203\377\377\377\0\3\364\364\364"
|
||||
"\0\211\211\211\0\216\216\216\0\242\377\377\377\0\7\377\377\377\14\377"
|
||||
"\377\377S\377\377\377s\377\377\377r\377\377\377v\377\377\377\\\377\377"
|
||||
"\377\21\202\377\377\377\0\5\353\353\353\0\214\214\214\0%%%\0\0\0\0\0"
|
||||
"eee\0\251\377\377\377\0\3\353\353\353\0\212\212\212\0\33\33\33\0\203"
|
||||
"\0\0\0\0\1fff\0\244\377\377\377\0\6\327\327\327\0\263\263\263\0\267\267"
|
||||
"\267\0\247\247\247\0ttt\0\31\31\31\0\205\0\0\0\0\1fff\0\244\377\377\377"
|
||||
"\0\1qqq\0\212\0\0\0\0\1fff\0\241\377\377\377\0\1\377\377\377\10\202\377"
|
||||
"\377\377\11\1qqq\11\212\0\0\0\11\1fff\11\203\377\377\377\11\2\377\377"
|
||||
"\377\12\377\377\377\4\217\377\377\377\0"};
|
||||
|
||||
|
||||
343
process.c
Normal file
343
process.c
Normal file
@ -0,0 +1,343 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "process.h"
|
||||
#include "binfile.h"
|
||||
|
||||
/* FIXME: All the interesting stuff in this file is from memprof
|
||||
* and copyright Red Hat.
|
||||
*/
|
||||
|
||||
/* FIXME: this should be done with getpagesize() */
|
||||
#define PAGE_SIZE 4096
|
||||
|
||||
static GHashTable *processes_by_cmdline;
|
||||
static GHashTable *processes_by_pid;
|
||||
|
||||
typedef struct Map Map;
|
||||
struct Map
|
||||
{
|
||||
char * filename;
|
||||
gulong start;
|
||||
gulong end;
|
||||
gboolean do_offset;
|
||||
|
||||
BinFile * bin_file;
|
||||
};
|
||||
|
||||
struct Process
|
||||
{
|
||||
char *cmdline;
|
||||
|
||||
GList *maps;
|
||||
GList *bad_pages;
|
||||
};
|
||||
|
||||
static void
|
||||
initialize (void)
|
||||
{
|
||||
if (!processes_by_cmdline)
|
||||
{
|
||||
processes_by_cmdline = g_hash_table_new (g_str_hash, g_str_equal);
|
||||
processes_by_pid = g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
should_offset (const char *filename, int pid)
|
||||
{
|
||||
gboolean result = TRUE;
|
||||
|
||||
struct stat stat1, stat2;
|
||||
char *progname = g_strdup_printf ("/proc/%d/exe", pid);
|
||||
|
||||
if (stat (filename, &stat1) == 0)
|
||||
{
|
||||
if (stat (progname, &stat2) == 0)
|
||||
result = !(stat1.st_ino == stat2.st_ino &&
|
||||
stat1.st_dev == stat2.st_dev);
|
||||
}
|
||||
|
||||
g_free (progname);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
check_do_offset (const Map *map, int pid)
|
||||
{
|
||||
if (map->filename)
|
||||
return should_offset (map->filename, pid);
|
||||
else
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GList *
|
||||
read_maps (int pid)
|
||||
{
|
||||
char *name = g_strdup_printf ("/proc/%d/maps", pid);
|
||||
char buffer[1024];
|
||||
FILE *in;
|
||||
GList *result = NULL;
|
||||
|
||||
in = fopen (name, "r");
|
||||
if (!in)
|
||||
{
|
||||
g_free (name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (fgets (buffer, sizeof (buffer) - 1, in))
|
||||
{
|
||||
char file[256];
|
||||
int count;
|
||||
guint start;
|
||||
guint end;
|
||||
|
||||
count = sscanf (
|
||||
buffer, "%x-%x %*15s %*x %*u:%*u %*u %255s", &start, &end, file);
|
||||
if (count == 3)
|
||||
{
|
||||
Map *map;
|
||||
|
||||
map = g_new (Map, 1);
|
||||
|
||||
map->filename = g_strdup (file);
|
||||
map->start = start;
|
||||
map->end = end;
|
||||
|
||||
map->do_offset = check_do_offset (map, pid);
|
||||
|
||||
map->bin_file = NULL;
|
||||
|
||||
result = g_list_prepend (result, map);
|
||||
}
|
||||
}
|
||||
|
||||
g_free (name);
|
||||
fclose (in);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static Process *
|
||||
create_process (const char *cmdline, int pid)
|
||||
{
|
||||
Process *p;
|
||||
|
||||
p = g_new (Process, 1);
|
||||
|
||||
p->cmdline = g_strdup_printf ("[%s]", cmdline);
|
||||
p->bad_pages = NULL;
|
||||
p->maps = NULL;
|
||||
|
||||
g_assert (!g_hash_table_lookup (processes_by_pid, GINT_TO_POINTER (pid)));
|
||||
g_assert (!g_hash_table_lookup (processes_by_cmdline, cmdline));
|
||||
|
||||
g_hash_table_insert (processes_by_pid, GINT_TO_POINTER (pid), p);
|
||||
g_hash_table_insert (processes_by_cmdline, g_strdup (cmdline), p);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static Map *
|
||||
process_locate_map (Process *process, gulong addr)
|
||||
{
|
||||
GList *list;
|
||||
|
||||
for (list = process->maps; list != NULL; list = list->next)
|
||||
{
|
||||
Map *map = list->data;
|
||||
|
||||
if ((addr >= map->start) &&
|
||||
(addr < map->end))
|
||||
{
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
process_free_maps (Process *process)
|
||||
{
|
||||
GList *list;
|
||||
|
||||
for (list = process->maps; list != NULL; list = list->next)
|
||||
{
|
||||
Map *map = list->data;
|
||||
|
||||
if (map->bin_file)
|
||||
bin_file_free (map->bin_file);
|
||||
|
||||
g_free (map);
|
||||
}
|
||||
|
||||
g_list_free (process->maps);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
process_has_page (Process *process, gulong addr)
|
||||
{
|
||||
if (process_locate_map (process, addr))
|
||||
return TRUE;
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
process_ensure_map (Process *process, int pid, gulong addr)
|
||||
{
|
||||
/* Round don to closest page */
|
||||
addr = (addr - addr % PAGE_SIZE);
|
||||
|
||||
if (process_has_page (process, addr))
|
||||
return;
|
||||
|
||||
if (g_list_find (process->bad_pages, (gpointer)addr))
|
||||
return;
|
||||
|
||||
/* a map containing addr was not found */
|
||||
if (process->maps)
|
||||
process_free_maps (process);
|
||||
|
||||
process->maps = read_maps (pid);
|
||||
|
||||
if (!process_has_page (process, addr))
|
||||
g_list_prepend (process->bad_pages, (gpointer)addr);
|
||||
}
|
||||
|
||||
Process *
|
||||
process_get_from_pid (int pid)
|
||||
{
|
||||
Process *p;
|
||||
gchar *filename = NULL;
|
||||
gchar *cmdline = NULL;
|
||||
|
||||
initialize();
|
||||
|
||||
p = g_hash_table_lookup (processes_by_pid, GINT_TO_POINTER (pid));
|
||||
if (p)
|
||||
goto out;
|
||||
|
||||
filename = g_strdup_printf ("/proc/%d/cmdline", pid);
|
||||
|
||||
if (g_file_get_contents (filename, &cmdline, NULL, NULL))
|
||||
{
|
||||
p = g_hash_table_lookup (processes_by_cmdline, cmdline);
|
||||
if (p)
|
||||
goto out;
|
||||
}
|
||||
else
|
||||
{
|
||||
cmdline = g_strdup_printf ("[pid %d]", pid);
|
||||
}
|
||||
|
||||
p = create_process (cmdline, pid);
|
||||
|
||||
out:
|
||||
if (cmdline)
|
||||
g_free (cmdline);
|
||||
|
||||
if (filename)
|
||||
g_free (filename);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
const Symbol *
|
||||
process_lookup_symbol (Process *process, gulong address)
|
||||
{
|
||||
static Symbol undefined;
|
||||
const Symbol *result;
|
||||
Map *map = process_locate_map (process, address);
|
||||
|
||||
if (!map)
|
||||
{
|
||||
if (undefined.name)
|
||||
g_free (undefined.name);
|
||||
undefined.name = g_strdup_printf ("??? (%s)", process->cmdline);
|
||||
undefined.address = 0xBABE0001;
|
||||
|
||||
return &undefined;
|
||||
}
|
||||
|
||||
if (!map->bin_file)
|
||||
map->bin_file = bin_file_new (map->filename);
|
||||
|
||||
if (map->do_offset)
|
||||
address -= map->start;
|
||||
|
||||
result = bin_file_lookup_symbol (map->bin_file, address);
|
||||
return result;
|
||||
}
|
||||
|
||||
const Symbol *
|
||||
process_lookup_symbol_with_filename (Process *process,
|
||||
int pid,
|
||||
gulong map_start,
|
||||
const char *filename,
|
||||
gulong address)
|
||||
{
|
||||
const Symbol *result;
|
||||
BinFile *bin_file;
|
||||
|
||||
if (!filename)
|
||||
return process_lookup_symbol (process, address);
|
||||
|
||||
bin_file = bin_file_new (filename);
|
||||
|
||||
if (should_offset (filename, pid))
|
||||
address -= map_start;
|
||||
|
||||
result = bin_file_lookup_symbol (bin_file, address);
|
||||
|
||||
bin_file_free (bin_file);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *
|
||||
process_get_cmdline (Process *process)
|
||||
{
|
||||
return process->cmdline;
|
||||
}
|
||||
|
||||
static void
|
||||
free_process (gpointer key, gpointer value, gpointer data)
|
||||
{
|
||||
char *cmdline = key;
|
||||
Process *process = value;
|
||||
|
||||
#if 0
|
||||
g_print ("freeing: %p\n", process);
|
||||
memset (process, '\0', sizeof (Process));
|
||||
#endif
|
||||
g_free (process->cmdline);
|
||||
#if 0
|
||||
process->cmdline = "You are using free()'d memory";
|
||||
#endif
|
||||
process_free_maps (process);
|
||||
g_list_free (process->bad_pages);
|
||||
g_free (cmdline);
|
||||
|
||||
g_free (process);
|
||||
}
|
||||
|
||||
void
|
||||
process_flush_caches (void)
|
||||
{
|
||||
if (!processes_by_cmdline)
|
||||
return;
|
||||
g_hash_table_foreach (processes_by_cmdline, free_process, NULL);
|
||||
|
||||
g_hash_table_destroy (processes_by_cmdline);
|
||||
g_hash_table_destroy (processes_by_pid);
|
||||
|
||||
processes_by_cmdline = NULL;
|
||||
processes_by_pid = NULL;
|
||||
}
|
||||
38
process.h
Normal file
38
process.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef PROCESS_H
|
||||
#define PROCESS_H
|
||||
|
||||
#include "binfile.h"
|
||||
|
||||
typedef struct Process Process;
|
||||
|
||||
/* We are making the assumption that pid's are not recycled during
|
||||
* a profiling run. That is wrong, but necessary to avoid reading
|
||||
* from /proc/<pid>/maps on every sample (which would be a race
|
||||
* condition anyway).
|
||||
*
|
||||
* If the address passed to new_from_pid() is somewhere that hasn't
|
||||
* been checked before, the mappings are reread for the Process. This
|
||||
* means that if some previously sampled pages have been unmapped,
|
||||
* they will be lost and appear as "???" on the profile.
|
||||
*
|
||||
* To flush the pid cache, call process_flush_caches().
|
||||
* This will invalidate all instances of Process.
|
||||
*
|
||||
*/
|
||||
|
||||
void process_flush_caches (void);
|
||||
Process * process_get_from_pid (int pid);
|
||||
void process_ensure_map (Process *process,
|
||||
int pid,
|
||||
gulong address);
|
||||
const Symbol *process_lookup_symbol (Process *process,
|
||||
gulong address);
|
||||
const Symbol *process_lookup_symbol_with_filename (Process *process,
|
||||
int pid,
|
||||
gulong map_start,
|
||||
const char *filename,
|
||||
gulong address);
|
||||
const char * process_get_cmdline (Process *process);
|
||||
|
||||
|
||||
#endif
|
||||
630
profile.c
Normal file
630
profile.c
Normal file
@ -0,0 +1,630 @@
|
||||
#include <glib.h>
|
||||
#include "binfile.h"
|
||||
#include "process.h"
|
||||
#include "stackstash.h"
|
||||
#include "profile.h"
|
||||
|
||||
typedef struct RealProfile RealProfile;
|
||||
typedef struct Node Node;
|
||||
|
||||
static void
|
||||
update()
|
||||
{
|
||||
#if 0
|
||||
while (g_main_pending())
|
||||
g_main_iteration (FALSE);
|
||||
#endif
|
||||
}
|
||||
|
||||
static guint
|
||||
direct_hash_no_null (gconstpointer v)
|
||||
{
|
||||
g_assert (v);
|
||||
return GPOINTER_TO_UINT (v);
|
||||
}
|
||||
|
||||
struct Node
|
||||
{
|
||||
ProfileObject *object;
|
||||
|
||||
Node *siblings; /* siblings in the call tree */
|
||||
Node *children; /* children in the call tree */
|
||||
Node *parent; /* parent in call tree */
|
||||
Node *next; /* nodes that corresponding to same object */
|
||||
|
||||
guint total;
|
||||
guint self;
|
||||
|
||||
gboolean toplevel;
|
||||
};
|
||||
|
||||
struct RealProfile
|
||||
{
|
||||
Profile profile;
|
||||
|
||||
Node *call_tree;
|
||||
GHashTable *profile_objects;
|
||||
GHashTable *nodes_by_object;
|
||||
};
|
||||
|
||||
static ProfileObject *
|
||||
profile_object_new (void)
|
||||
{
|
||||
ProfileObject *obj = g_new (ProfileObject, 1);
|
||||
obj->total = 0;
|
||||
obj->self = 0;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static void
|
||||
profile_object_free (ProfileObject *obj)
|
||||
{
|
||||
g_free (obj->name);
|
||||
g_free (obj);
|
||||
}
|
||||
|
||||
static char *
|
||||
generate_key (RealProfile *profile, Process *process, gulong address)
|
||||
{
|
||||
if (address)
|
||||
{
|
||||
const Symbol *symbol = process_lookup_symbol (process, address);
|
||||
|
||||
return g_strdup_printf ("%p%s", symbol->address, symbol->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return g_strdup_printf ("p:%p", process_get_cmdline (process));
|
||||
}
|
||||
}
|
||||
|
||||
static char *
|
||||
generate_presentation_name (RealProfile *profile, Process *process, gulong address)
|
||||
{
|
||||
/* FIXME using 0 to indicate "process" is broken */
|
||||
if (address)
|
||||
{
|
||||
const Symbol *symbol = process_lookup_symbol (process, address);
|
||||
|
||||
return g_strdup_printf ("%s", symbol->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
return g_strdup_printf ("%s", process_get_cmdline (process));
|
||||
}
|
||||
#if 0
|
||||
/* FIXME - don't return addresses and stuff */
|
||||
return generate_key (profile, process, address);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
ensure_profile_node (RealProfile *profile, Process *process, gulong address)
|
||||
{
|
||||
char *key = generate_key (profile, process, address);
|
||||
|
||||
if (!g_hash_table_lookup (profile->profile_objects, key))
|
||||
{
|
||||
ProfileObject *object;
|
||||
|
||||
object = profile_object_new ();
|
||||
object->name = generate_presentation_name (profile, process, address);
|
||||
|
||||
g_hash_table_insert (profile->profile_objects, key, object);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_free (key);
|
||||
}
|
||||
}
|
||||
|
||||
static ProfileObject *
|
||||
lookup_profile_object (RealProfile *profile, Process *process, gulong address)
|
||||
{
|
||||
ProfileObject *object;
|
||||
char *key = generate_key (profile, process, address);
|
||||
object = g_hash_table_lookup (profile->profile_objects, key);
|
||||
g_free (key);
|
||||
g_assert (object);
|
||||
return object;
|
||||
}
|
||||
|
||||
static void
|
||||
generate_object_table (Process *process, GSList *trace, gint size, gpointer data)
|
||||
{
|
||||
RealProfile *profile = data;
|
||||
GSList *list;
|
||||
|
||||
ensure_profile_node (profile, process, 0);
|
||||
|
||||
for (list = trace; list != NULL; list = list->next)
|
||||
{
|
||||
update ();
|
||||
ensure_profile_node (profile, process, (gulong)list->data);
|
||||
}
|
||||
|
||||
profile->profile.profile_size += size;
|
||||
}
|
||||
|
||||
static Node *
|
||||
node_new ()
|
||||
{
|
||||
Node *node = g_new (Node, 1);
|
||||
node->siblings = NULL;
|
||||
node->children = NULL;
|
||||
node->parent = NULL;
|
||||
node->next = NULL;
|
||||
node->object = NULL;
|
||||
node->self = 0;
|
||||
node->total = 0;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static Node *
|
||||
node_add_trace (RealProfile *profile, Node *node, Process *process,
|
||||
GSList *trace, gint size,
|
||||
GHashTable *seen_objects)
|
||||
{
|
||||
ProfileObject *object;
|
||||
Node *match = NULL;
|
||||
|
||||
if (!trace)
|
||||
return node;
|
||||
|
||||
object = lookup_profile_object (profile, process, (gulong)trace->data);
|
||||
for (match = node; match != NULL; match = match->siblings)
|
||||
{
|
||||
if (match->object == object)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!match)
|
||||
{
|
||||
match = node_new ();
|
||||
match->object = object;
|
||||
match->siblings = node;
|
||||
node = match;
|
||||
|
||||
if (g_hash_table_lookup (seen_objects, object))
|
||||
match->toplevel = FALSE;
|
||||
else
|
||||
match->toplevel = TRUE;
|
||||
|
||||
match->next = g_hash_table_lookup (profile->nodes_by_object, object);
|
||||
g_hash_table_insert (profile->nodes_by_object, object, match);
|
||||
}
|
||||
|
||||
g_hash_table_insert (seen_objects, object, GINT_TO_POINTER (1));
|
||||
|
||||
#if 0
|
||||
g_print ("%s adds %d\n", match->object->name, size);
|
||||
#endif
|
||||
match->total += size;
|
||||
if (!trace->next)
|
||||
match->self += size;
|
||||
|
||||
match->children = node_add_trace (profile, match->children, process, trace->next, size,
|
||||
seen_objects);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void
|
||||
dump_trace (GSList *trace)
|
||||
{
|
||||
g_print ("TRACE: ");
|
||||
while (trace)
|
||||
{
|
||||
g_print ("%x ", trace->data);
|
||||
trace = trace->next;
|
||||
}
|
||||
g_print ("\n\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
generate_call_tree (Process *process, GSList *trace, gint size, gpointer data)
|
||||
{
|
||||
RealProfile *profile = data;
|
||||
Node *match = NULL;
|
||||
ProfileObject *proc = lookup_profile_object (profile, process, 0);
|
||||
GHashTable *seen_objects;
|
||||
|
||||
for (match = profile->call_tree; match; match = match->siblings)
|
||||
{
|
||||
if (match->object == proc)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!match)
|
||||
{
|
||||
match = node_new ();
|
||||
match->object = proc;
|
||||
match->siblings = profile->call_tree;
|
||||
profile->call_tree = match;
|
||||
match->toplevel = TRUE;
|
||||
}
|
||||
|
||||
g_hash_table_insert (profile->nodes_by_object, proc, match);
|
||||
|
||||
match->total += size;
|
||||
if (!trace)
|
||||
match->self += size;
|
||||
|
||||
seen_objects = g_hash_table_new (direct_hash_no_null, g_direct_equal);
|
||||
|
||||
g_hash_table_insert (seen_objects, proc, GINT_TO_POINTER (1));
|
||||
|
||||
update ();
|
||||
match->children = node_add_trace (profile, match->children, process,
|
||||
trace, size, seen_objects);
|
||||
|
||||
g_hash_table_destroy (seen_objects);
|
||||
}
|
||||
|
||||
static void
|
||||
build_object_list (gpointer key, gpointer value, gpointer data)
|
||||
{
|
||||
Profile *profile = data;
|
||||
Node *node = value;
|
||||
ProfileObject *object = key;
|
||||
|
||||
while (node)
|
||||
{
|
||||
object->self += node->self;
|
||||
if (node->toplevel)
|
||||
object->total += node->total;
|
||||
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
profile->objects = g_list_prepend (profile->objects, object);
|
||||
}
|
||||
|
||||
static void
|
||||
link_parents (Node *node, Node *parent)
|
||||
{
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
node->parent = parent;
|
||||
|
||||
link_parents (node->siblings, parent);
|
||||
link_parents (node->children, node);
|
||||
}
|
||||
|
||||
Profile *
|
||||
profile_new (StackStash *stash)
|
||||
{
|
||||
RealProfile *real_profile = g_new (RealProfile, 1);
|
||||
|
||||
real_profile->profile_objects =
|
||||
g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
g_free, (GDestroyNotify)profile_object_free);
|
||||
|
||||
real_profile->call_tree = NULL;
|
||||
|
||||
real_profile->nodes_by_object =
|
||||
g_hash_table_new (direct_hash_no_null, g_direct_equal);
|
||||
real_profile->profile.objects = NULL;
|
||||
|
||||
real_profile->profile.profile_size = 0;
|
||||
|
||||
stack_stash_foreach (stash, generate_object_table, real_profile);
|
||||
stack_stash_foreach (stash, generate_call_tree, real_profile);
|
||||
link_parents (real_profile->call_tree, NULL);
|
||||
|
||||
g_hash_table_foreach (real_profile->nodes_by_object, build_object_list, real_profile);
|
||||
|
||||
return (Profile *)real_profile;
|
||||
}
|
||||
|
||||
static void
|
||||
add_trace_to_tree (ProfileDescendant **tree, GList *trace, guint size)
|
||||
{
|
||||
GList *list;
|
||||
GList *nodes_to_unmark = NULL;
|
||||
GList *nodes_to_unmark_recursive = NULL;
|
||||
ProfileDescendant *parent = NULL;
|
||||
|
||||
GHashTable *seen_objects =
|
||||
g_hash_table_new (direct_hash_no_null, g_direct_equal);
|
||||
|
||||
for (list = trace; list != NULL; list = list->next)
|
||||
{
|
||||
Node *node = list->data;
|
||||
ProfileDescendant *match = NULL;
|
||||
|
||||
update();
|
||||
|
||||
for (match = *tree; match != NULL; match = match->siblings)
|
||||
{
|
||||
if (match->object == node->object)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!match)
|
||||
{
|
||||
ProfileDescendant *seen_tree_node;
|
||||
|
||||
seen_tree_node = g_hash_table_lookup (seen_objects, node->object);
|
||||
|
||||
if (seen_tree_node)
|
||||
{
|
||||
ProfileDescendant *node;
|
||||
|
||||
g_assert (parent);
|
||||
|
||||
for (node = parent; node != seen_tree_node->parent; node = node->parent)
|
||||
{
|
||||
node->non_recursion -= size;
|
||||
--node->marked_non_recursive;
|
||||
}
|
||||
|
||||
match = seen_tree_node;
|
||||
|
||||
g_hash_table_destroy (seen_objects);
|
||||
seen_objects =
|
||||
g_hash_table_new (direct_hash_no_null, g_direct_equal);
|
||||
|
||||
for (node = match; node != NULL; node = node->parent)
|
||||
g_hash_table_insert (seen_objects, node->object, node);
|
||||
}
|
||||
}
|
||||
|
||||
if (!match)
|
||||
{
|
||||
match = g_new (ProfileDescendant, 1);
|
||||
|
||||
match->object = node->object;
|
||||
match->non_recursion = 0;
|
||||
match->total = 0;
|
||||
match->self = 0;
|
||||
match->children = NULL;
|
||||
match->marked_non_recursive = 0;
|
||||
match->marked_total = FALSE;
|
||||
match->parent = parent;
|
||||
|
||||
match->siblings = *tree;
|
||||
*tree = match;
|
||||
}
|
||||
|
||||
if (!match->marked_non_recursive)
|
||||
{
|
||||
nodes_to_unmark = g_list_prepend (nodes_to_unmark, match);
|
||||
match->non_recursion += size;
|
||||
++match->marked_non_recursive;
|
||||
}
|
||||
|
||||
if (!match->marked_total)
|
||||
{
|
||||
nodes_to_unmark_recursive = g_list_prepend (
|
||||
nodes_to_unmark_recursive, match);
|
||||
|
||||
match->total += size;
|
||||
match->marked_total = TRUE;
|
||||
}
|
||||
|
||||
if (!list->next)
|
||||
match->self += size;
|
||||
|
||||
g_hash_table_insert (seen_objects, node->object, match);
|
||||
|
||||
tree = &(match->children);
|
||||
parent = match;
|
||||
}
|
||||
|
||||
g_hash_table_destroy (seen_objects);
|
||||
|
||||
for (list = nodes_to_unmark; list != NULL; list = list->next)
|
||||
{
|
||||
ProfileDescendant *tree_node = list->data;
|
||||
|
||||
tree_node->marked_non_recursive = 0;
|
||||
}
|
||||
|
||||
for (list = nodes_to_unmark_recursive; list != NULL; list = list->next)
|
||||
{
|
||||
ProfileDescendant *tree_node = list->data;
|
||||
|
||||
tree_node->marked_total = FALSE;
|
||||
}
|
||||
|
||||
g_list_free (nodes_to_unmark);
|
||||
}
|
||||
|
||||
static void
|
||||
node_list_leaves (Node *node, GList **leaves)
|
||||
{
|
||||
Node *n;
|
||||
|
||||
if (node->self > 0)
|
||||
*leaves = g_list_prepend (*leaves, node);
|
||||
|
||||
for (n = node->children; n != NULL; n = n->siblings)
|
||||
node_list_leaves (n, leaves);
|
||||
}
|
||||
|
||||
static void
|
||||
add_leaf_to_tree (ProfileDescendant **tree, Node *leaf, Node *top)
|
||||
{
|
||||
GList *trace = NULL;
|
||||
Node *node;
|
||||
|
||||
for (node = leaf; node != top->parent; node = node->parent)
|
||||
trace = g_list_prepend (trace, node);
|
||||
|
||||
add_trace_to_tree (tree, trace, leaf->self);
|
||||
|
||||
g_list_free (trace);
|
||||
}
|
||||
|
||||
ProfileDescendant *
|
||||
profile_create_descendants (Profile *prf, ProfileObject *object)
|
||||
{
|
||||
ProfileDescendant *tree = NULL;
|
||||
RealProfile *profile = (RealProfile *)prf;
|
||||
Node *node;
|
||||
|
||||
node = g_hash_table_lookup (profile->nodes_by_object, object);
|
||||
|
||||
while (node)
|
||||
{
|
||||
update();
|
||||
if (node->toplevel)
|
||||
{
|
||||
GList *leaves = NULL;
|
||||
GList *list;
|
||||
|
||||
node_list_leaves (node, &leaves);
|
||||
|
||||
for (list = leaves; list != NULL; list = list->next)
|
||||
add_leaf_to_tree (&tree, list->data, node);
|
||||
|
||||
g_list_free (leaves);
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
static ProfileCaller *
|
||||
profile_caller_new (void)
|
||||
{
|
||||
ProfileCaller *caller = g_new (ProfileCaller, 1);
|
||||
caller->next = NULL;
|
||||
caller->self = 0;
|
||||
caller->total = 0;
|
||||
return caller;
|
||||
}
|
||||
|
||||
ProfileCaller *
|
||||
profile_list_callers (Profile *prf,
|
||||
ProfileObject *callee)
|
||||
{
|
||||
RealProfile *profile = (RealProfile *)prf;
|
||||
Node *callee_node;
|
||||
Node *node;
|
||||
GHashTable *callers_by_object;
|
||||
GHashTable *seen_callers;
|
||||
ProfileCaller *result = NULL;
|
||||
|
||||
callers_by_object =
|
||||
g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
seen_callers = g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
|
||||
callee_node = g_hash_table_lookup (profile->nodes_by_object, callee);
|
||||
|
||||
for (node = callee_node; node; node = node->next)
|
||||
{
|
||||
ProfileObject *object;
|
||||
if (node->parent)
|
||||
object = node->parent->object;
|
||||
else
|
||||
object = NULL;
|
||||
|
||||
if (!g_hash_table_lookup (callers_by_object, object))
|
||||
{
|
||||
ProfileCaller *caller = profile_caller_new ();
|
||||
caller->object = object;
|
||||
g_hash_table_insert (callers_by_object, object, caller);
|
||||
|
||||
caller->next = result;
|
||||
result = caller;
|
||||
}
|
||||
}
|
||||
|
||||
for (node = callee_node; node != NULL; node = node->next)
|
||||
{
|
||||
Node *top_caller;
|
||||
Node *top_callee;
|
||||
Node *n;
|
||||
ProfileCaller *caller;
|
||||
ProfileObject *object;
|
||||
|
||||
if (node->parent)
|
||||
object = node->parent->object;
|
||||
else
|
||||
object = NULL;
|
||||
|
||||
caller = g_hash_table_lookup (callers_by_object, object);
|
||||
|
||||
/* find topmost node/parent pair identical to this node/parent */
|
||||
top_caller = node->parent;
|
||||
top_callee = node;
|
||||
for (n = node; n && n->parent; n = n->parent)
|
||||
{
|
||||
if (n->object == node->object &&
|
||||
n->parent->object == node->parent->object)
|
||||
{
|
||||
top_caller = n->parent;
|
||||
top_callee = n;
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_hash_table_lookup (seen_callers, top_caller))
|
||||
{
|
||||
caller->total += top_callee->total;
|
||||
|
||||
g_hash_table_insert (seen_callers, top_caller, (void *)0x1);
|
||||
}
|
||||
|
||||
if (node->self > 0)
|
||||
caller->self += node->self;
|
||||
}
|
||||
|
||||
g_hash_table_destroy (seen_callers);
|
||||
g_hash_table_destroy (callers_by_object);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
node_free (Node *node)
|
||||
{
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
node_free (node->siblings);
|
||||
node_free (node->children);
|
||||
g_free (node);
|
||||
}
|
||||
|
||||
void
|
||||
profile_free (Profile *prf)
|
||||
{
|
||||
RealProfile *profile = (RealProfile *)prf;
|
||||
|
||||
g_list_free (prf->objects);
|
||||
g_hash_table_destroy (profile->profile_objects);
|
||||
node_free (profile->call_tree);
|
||||
g_hash_table_destroy (profile->nodes_by_object);
|
||||
g_free (prf);
|
||||
}
|
||||
|
||||
void
|
||||
profile_descendant_free (ProfileDescendant *descendant)
|
||||
{
|
||||
if (!descendant)
|
||||
return;
|
||||
|
||||
profile_descendant_free (descendant->siblings);
|
||||
profile_descendant_free (descendant->children);
|
||||
|
||||
g_free (descendant);
|
||||
}
|
||||
|
||||
void
|
||||
profile_caller_free (ProfileCaller *caller)
|
||||
{
|
||||
if (!caller)
|
||||
return;
|
||||
|
||||
profile_caller_free (caller->next);
|
||||
g_free (caller);
|
||||
}
|
||||
74
profile.h
Normal file
74
profile.h
Normal file
@ -0,0 +1,74 @@
|
||||
/* MemProf -- memory profiler and leak detector
|
||||
* Copyright 2002, Soeren Sandmann (sandmann@daimi.au.dk)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
/*====*/
|
||||
|
||||
#include <glib.h>
|
||||
#include "binfile.h"
|
||||
#include "process.h"
|
||||
#include "stackstash.h"
|
||||
|
||||
typedef struct Profile Profile;
|
||||
typedef struct ProfileObject ProfileObject;
|
||||
typedef struct ProfileDescendant ProfileDescendant;
|
||||
typedef struct ProfileCaller ProfileCaller;
|
||||
|
||||
struct ProfileObject
|
||||
{
|
||||
char * name; /* identifies this object uniquely */
|
||||
|
||||
guint total; /* sum of all toplevel totals */
|
||||
guint self; /* sum of all selfs */
|
||||
};
|
||||
|
||||
struct ProfileDescendant
|
||||
{
|
||||
ProfileObject * object;
|
||||
guint self;
|
||||
guint total;
|
||||
guint non_recursion;
|
||||
ProfileDescendant * parent;
|
||||
ProfileDescendant * siblings;
|
||||
ProfileDescendant * children;
|
||||
|
||||
int marked_non_recursive;
|
||||
int marked_total;
|
||||
};
|
||||
|
||||
struct Profile
|
||||
{
|
||||
gint profile_size;
|
||||
GList * objects;
|
||||
};
|
||||
|
||||
struct ProfileCaller
|
||||
{
|
||||
ProfileObject * object; /* can be NULL */
|
||||
guint total;
|
||||
guint self;
|
||||
|
||||
ProfileCaller * next;
|
||||
};
|
||||
|
||||
Profile * profile_new (StackStash *stash);
|
||||
void profile_free (Profile *profile);
|
||||
ProfileDescendant *profile_create_descendants (Profile *prf,
|
||||
ProfileObject *object);
|
||||
ProfileCaller * profile_list_callers (Profile *profile,
|
||||
ProfileObject *callee);
|
||||
void profile_caller_free (ProfileCaller *caller);
|
||||
void profile_descendant_free (ProfileDescendant *descendant);
|
||||
195
stackstash.c
Normal file
195
stackstash.c
Normal file
@ -0,0 +1,195 @@
|
||||
#include "stackstash.h"
|
||||
|
||||
typedef struct StackNode StackNode;
|
||||
|
||||
struct StackNode
|
||||
{
|
||||
StackNode * parent;
|
||||
gpointer address;
|
||||
StackNode * siblings;
|
||||
StackNode * children;
|
||||
StackNode * next; /* next leaf with the same pid */
|
||||
int size;
|
||||
};
|
||||
|
||||
struct StackStash
|
||||
{
|
||||
StackNode *root;
|
||||
GHashTable *leaves_by_process;
|
||||
};
|
||||
|
||||
static StackNode *
|
||||
stack_node_new (void)
|
||||
{
|
||||
StackNode *node = g_new (StackNode, 1);
|
||||
node->siblings = NULL;
|
||||
node->children = NULL;
|
||||
node->address = NULL;
|
||||
node->parent = NULL;
|
||||
node->next = NULL;
|
||||
node->size = 0;
|
||||
return node;
|
||||
}
|
||||
|
||||
static void
|
||||
stack_node_destroy (gpointer p)
|
||||
{
|
||||
StackNode *node = p;
|
||||
if (node)
|
||||
{
|
||||
stack_node_destroy (node->siblings);
|
||||
stack_node_destroy (node->children);
|
||||
g_free (node);
|
||||
}
|
||||
}
|
||||
|
||||
/* Stach */
|
||||
StackStash *
|
||||
stack_stash_new (void)
|
||||
{
|
||||
StackStash *stash = g_new (StackStash, 1);
|
||||
|
||||
stash->leaves_by_process =
|
||||
g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
stash->root = NULL;
|
||||
return stash;
|
||||
}
|
||||
|
||||
static StackNode *
|
||||
stack_node_add_trace (StackNode *node,
|
||||
GList *bottom,
|
||||
gint size,
|
||||
StackNode **leaf)
|
||||
{
|
||||
StackNode *match;
|
||||
StackNode *n;
|
||||
|
||||
if (!bottom)
|
||||
{
|
||||
*leaf = NULL;
|
||||
return node;
|
||||
}
|
||||
|
||||
if (!bottom->next)
|
||||
{
|
||||
/* A leaf must always be separate, so pids can
|
||||
* point to them
|
||||
*/
|
||||
match = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (match = node; match != NULL; match = match->siblings)
|
||||
{
|
||||
if (match->address == bottom->data)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match)
|
||||
{
|
||||
match = stack_node_new ();
|
||||
match->address = bottom->data;
|
||||
match->siblings = node;
|
||||
node = match;
|
||||
}
|
||||
|
||||
match->children =
|
||||
stack_node_add_trace (match->children, bottom->next, size, leaf);
|
||||
|
||||
for (n = match->children; n; n = n->siblings)
|
||||
n->parent = match;
|
||||
|
||||
if (!bottom->next)
|
||||
{
|
||||
match->size += size;
|
||||
*leaf = match;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void
|
||||
stack_stash_add_trace (StackStash *stash,
|
||||
Process *process,
|
||||
gulong *addrs,
|
||||
int n_addrs,
|
||||
int size)
|
||||
{
|
||||
GList *trace;
|
||||
StackNode *leaf;
|
||||
int i;
|
||||
|
||||
trace = NULL;
|
||||
for (i = 0; i < n_addrs; ++i)
|
||||
trace = g_list_prepend (trace, GINT_TO_POINTER (addrs[i]));
|
||||
|
||||
stash->root = stack_node_add_trace (stash->root, trace, size, &leaf);
|
||||
|
||||
leaf->next = g_hash_table_lookup (
|
||||
stash->leaves_by_process, process);
|
||||
g_hash_table_insert (
|
||||
stash->leaves_by_process, process, leaf);
|
||||
|
||||
g_list_free (trace);
|
||||
}
|
||||
|
||||
typedef struct CallbackInfo
|
||||
{
|
||||
StackFunction func;
|
||||
gpointer data;
|
||||
} CallbackInfo;
|
||||
|
||||
static void
|
||||
do_callback (gpointer key, gpointer value, gpointer data)
|
||||
{
|
||||
CallbackInfo *info = data;
|
||||
Process *process = key;
|
||||
StackNode *n;
|
||||
StackNode *leaf = value;
|
||||
while (leaf)
|
||||
{
|
||||
GSList *trace;
|
||||
|
||||
trace = NULL;
|
||||
for (n = leaf; n; n = n->parent)
|
||||
trace = g_slist_prepend (trace, n->address);
|
||||
|
||||
info->func (process, trace, leaf->size, info->data);
|
||||
|
||||
g_slist_free (trace);
|
||||
|
||||
leaf = leaf->next;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
stack_stash_foreach (StackStash *stash,
|
||||
StackFunction stack_func,
|
||||
gpointer data)
|
||||
{
|
||||
CallbackInfo info;
|
||||
info.func = stack_func;
|
||||
info.data = data;
|
||||
|
||||
g_hash_table_foreach (stash->leaves_by_process, do_callback, &info);
|
||||
}
|
||||
|
||||
static void
|
||||
stack_node_free (StackNode *node)
|
||||
{
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
stack_node_free (node->siblings);
|
||||
stack_node_free (node->children);
|
||||
|
||||
g_free (node);
|
||||
}
|
||||
|
||||
void
|
||||
stack_stash_free (StackStash *stash)
|
||||
{
|
||||
stack_node_free (stash->root);
|
||||
g_hash_table_destroy (stash->leaves_by_process);
|
||||
}
|
||||
26
stackstash.h
Normal file
26
stackstash.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef STACK_STASH_H
|
||||
#define STACK_STASH_H
|
||||
|
||||
#include <glib.h>
|
||||
#include "process.h"
|
||||
|
||||
typedef struct StackStash StackStash;
|
||||
|
||||
typedef void (* StackFunction) (Process *process,
|
||||
GSList *trace,
|
||||
gint size,
|
||||
gpointer data);
|
||||
|
||||
/* Stach */
|
||||
StackStash *stack_stash_new (void);
|
||||
void stack_stash_add_trace (StackStash *stash,
|
||||
Process *process,
|
||||
gulong *addrs,
|
||||
gint n_addrs,
|
||||
int size);
|
||||
void stack_stash_foreach (StackStash *stash,
|
||||
StackFunction stack_func,
|
||||
gpointer data);
|
||||
void stack_stash_free (StackStash *stash);
|
||||
|
||||
#endif
|
||||
286
sysprof-module.c
Normal file
286
sysprof-module.c
Normal file
@ -0,0 +1,286 @@
|
||||
#include <linux/config.h>
|
||||
#ifdef CONFIG_SMP
|
||||
# define __SMP__
|
||||
#endif
|
||||
#include <asm/atomic.h>
|
||||
#include <linux/kernel.h> /* Needed for KERN_ALERT */
|
||||
#include <linux/module.h> /* Needed by all modules */
|
||||
#include <linux/tqueue.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <asm/uaccess.h>
|
||||
#include <linux/poll.h>
|
||||
|
||||
#include "sysprof-module.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Soeren Sandmann (sandmann@daimi.au.dk)");
|
||||
|
||||
#define SAMPLES_PER_SECOND 50 /* must divide HZ */
|
||||
|
||||
static const int cpu_profiler = 0; /* 0: page faults, 1: cpu */
|
||||
|
||||
static void on_timer_interrupt (void *);
|
||||
|
||||
static struct tq_struct timer_task =
|
||||
{
|
||||
{ NULL, NULL },
|
||||
0,
|
||||
on_timer_interrupt,
|
||||
NULL
|
||||
};
|
||||
|
||||
int exiting = 0;
|
||||
DECLARE_WAIT_QUEUE_HEAD (wait_for_exit);
|
||||
|
||||
#define N_TRACES 256
|
||||
|
||||
static SysprofStackTrace stack_traces[N_TRACES];
|
||||
static SysprofStackTrace * head = &stack_traces[0];
|
||||
static SysprofStackTrace * tail = &stack_traces[0];
|
||||
DECLARE_WAIT_QUEUE_HEAD (wait_for_trace);
|
||||
|
||||
static void
|
||||
generate_stack_trace (struct task_struct *task,
|
||||
SysprofStackTrace *trace)
|
||||
{
|
||||
#define START_OF_STACK 0xBFFFFFFF
|
||||
|
||||
typedef struct StackFrame StackFrame;
|
||||
struct StackFrame {
|
||||
StackFrame *next;
|
||||
void *return_address;
|
||||
};
|
||||
|
||||
struct pt_regs *regs = (struct pt_regs *)(
|
||||
(long)current + THREAD_SIZE - sizeof (struct pt_regs));
|
||||
|
||||
StackFrame *frame;
|
||||
int i;
|
||||
|
||||
memset (trace, 0, sizeof (SysprofStackTrace));
|
||||
|
||||
trace->pid = current->pid;
|
||||
trace->truncated = 0;
|
||||
|
||||
trace->addresses[0] = (void *)regs->eip;
|
||||
i = 1;
|
||||
|
||||
frame = (StackFrame *)regs->ebp;
|
||||
|
||||
while (frame && i < SYSPROF_MAX_ADDRESSES &&
|
||||
(long)frame < (long)START_OF_STACK &&
|
||||
(long)frame >= regs->esp)
|
||||
{
|
||||
void *next = NULL;
|
||||
|
||||
if (verify_area(VERIFY_READ, frame, sizeof(StackFrame)) == 0)
|
||||
{
|
||||
void *return_address;
|
||||
|
||||
__get_user (return_address, &(frame->return_address));
|
||||
__get_user (next, &(frame->next));
|
||||
trace->addresses[i++] = return_address;
|
||||
|
||||
if ((long)next <= (long)frame)
|
||||
next = NULL;
|
||||
}
|
||||
|
||||
frame = next;
|
||||
}
|
||||
|
||||
trace->n_addresses = i;
|
||||
if (i == SYSPROF_MAX_ADDRESSES)
|
||||
trace->truncated = 1;
|
||||
else
|
||||
trace->truncated = 0;
|
||||
}
|
||||
|
||||
struct page *
|
||||
hijacked_nopage (struct vm_area_struct * area,
|
||||
unsigned long address,
|
||||
int unused)
|
||||
{
|
||||
if (current && current->pid != 0)
|
||||
{
|
||||
#if 0
|
||||
generate_stack_trace (current, head);
|
||||
if (head++ == &stack_traces[N_TRACES - 1])
|
||||
head = &stack_traces[0];
|
||||
|
||||
wake_up (&wait_for_trace);
|
||||
#endif
|
||||
memset (head, 0, sizeof (SysprofStackTrace));
|
||||
|
||||
head->n_addresses = 1;
|
||||
head->pid = current->pid;
|
||||
head->addresses[0] = (void *)address;
|
||||
head->truncated = 0;
|
||||
|
||||
if (area->vm_file)
|
||||
{
|
||||
char *line = d_path (area->vm_file->f_dentry,
|
||||
area->vm_file->f_vfsmnt,
|
||||
head->filename, PAGE_SIZE);
|
||||
|
||||
strncpy (head->filename, line, sizeof (head->filename) - 1);
|
||||
#if 0
|
||||
|
||||
if (line == head->filename)
|
||||
printk (KERN_ALERT "got name\n");
|
||||
else if (line == NULL)
|
||||
printk (KERN_ALERT "noasdf\n");
|
||||
else
|
||||
printk (KERN_ALERT "%s\n", line);
|
||||
#endif
|
||||
head->map_start = area->vm_start;
|
||||
}
|
||||
|
||||
if (head++ == &stack_traces[N_TRACES - 1])
|
||||
head = &stack_traces[0];
|
||||
|
||||
wake_up (&wait_for_trace);
|
||||
}
|
||||
|
||||
return filemap_nopage (area, address, unused);
|
||||
}
|
||||
|
||||
static void
|
||||
hijack_nopages (struct task_struct *task)
|
||||
{
|
||||
struct mm_struct *mm = task->mm;
|
||||
struct vm_area_struct *vma;
|
||||
|
||||
if (!mm)
|
||||
return;
|
||||
|
||||
for (vma = mm->mmap; vma != NULL; vma = vma->vm_next)
|
||||
{
|
||||
if (vma->vm_ops && vma->vm_ops->nopage == filemap_nopage)
|
||||
vma->vm_ops->nopage = hijacked_nopage;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clean_hijacked_nopages (void)
|
||||
{
|
||||
struct task_struct *task;
|
||||
|
||||
for_each_process (task)
|
||||
{
|
||||
struct mm_struct *mm = task->mm;
|
||||
|
||||
if (mm)
|
||||
{
|
||||
struct vm_area_struct *vma;
|
||||
|
||||
for (vma = mm->mmap; vma != NULL; vma = vma->vm_next)
|
||||
{
|
||||
if (vma->vm_ops && vma->vm_ops->nopage == hijacked_nopage)
|
||||
vma->vm_ops->nopage = filemap_nopage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_timer_interrupt (void *data)
|
||||
{
|
||||
static int n_ticks = 0;
|
||||
|
||||
if (exiting)
|
||||
{
|
||||
clean_hijacked_nopages ();
|
||||
wake_up (&wait_for_exit);
|
||||
return;
|
||||
}
|
||||
|
||||
++n_ticks;
|
||||
|
||||
if (current && current->pid != 0 &&
|
||||
(n_ticks % (HZ / SAMPLES_PER_SECOND)) == 0)
|
||||
{
|
||||
if (cpu_profiler)
|
||||
{
|
||||
generate_stack_trace (current, head);
|
||||
|
||||
if (head++ == &stack_traces[N_TRACES - 1])
|
||||
head = &stack_traces[0];
|
||||
|
||||
wake_up (&wait_for_trace);
|
||||
}
|
||||
else
|
||||
{
|
||||
hijack_nopages (current);
|
||||
}
|
||||
}
|
||||
|
||||
queue_task (&timer_task, &tq_timer);
|
||||
}
|
||||
|
||||
static int
|
||||
procfile_read (char *buffer,
|
||||
char **buffer_location,
|
||||
off_t offset,
|
||||
int buffer_length,
|
||||
int *eof,
|
||||
void *data)
|
||||
{
|
||||
#if 0
|
||||
if (offset > 0)
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
wait_event_interruptible (wait_for_trace, head != tail);
|
||||
*buffer_location = (char *)tail;
|
||||
if (tail++ == &stack_traces[N_TRACES - 1])
|
||||
tail = &stack_traces[0];
|
||||
return sizeof (SysprofStackTrace);
|
||||
}
|
||||
|
||||
struct proc_dir_entry *trace_proc_file;
|
||||
|
||||
static unsigned int
|
||||
procfile_poll (struct file *filp, poll_table *poll_table)
|
||||
{
|
||||
if (head != tail)
|
||||
{
|
||||
return POLLIN;
|
||||
}
|
||||
poll_wait (filp, &wait_for_trace, poll_table);
|
||||
if (head != tail)
|
||||
{
|
||||
return POLLIN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
init_module (void)
|
||||
{
|
||||
trace_proc_file =
|
||||
create_proc_entry ("sysprof-trace", S_IFREG | S_IRUGO, &proc_root);
|
||||
|
||||
if (!trace_proc_file)
|
||||
return 1;
|
||||
|
||||
trace_proc_file->read_proc = procfile_read;
|
||||
trace_proc_file->proc_fops->poll = procfile_poll;
|
||||
trace_proc_file->size = sizeof (SysprofStackTrace);
|
||||
|
||||
queue_task(&timer_task, &tq_timer);
|
||||
|
||||
printk (KERN_ALERT "starting sysprof module\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
cleanup_module(void)
|
||||
{
|
||||
exiting = 1;
|
||||
sleep_on (&wait_for_exit);
|
||||
remove_proc_entry ("sysprof-trace", &proc_root);
|
||||
|
||||
printk (KERN_ALERT "stopping sysprof module\n");
|
||||
}
|
||||
21
sysprof-module.h
Normal file
21
sysprof-module.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef SYSPROF_MODULE_H
|
||||
#define SYSPROF_MODULE_H
|
||||
|
||||
typedef struct SysprofStackTrace SysprofStackTrace;
|
||||
|
||||
#define SYSPROF_MAX_ADDRESSES 1024
|
||||
|
||||
struct SysprofStackTrace
|
||||
{
|
||||
int pid;
|
||||
int truncated;
|
||||
int n_addresses; /* note: this can be 1 if the process was compiled
|
||||
* with -fomit-frame-pointer or is otherwise weird
|
||||
*/
|
||||
void *addresses[SYSPROF_MAX_ADDRESSES];
|
||||
|
||||
char filename[8192];
|
||||
int map_start;
|
||||
};
|
||||
|
||||
#endif
|
||||
712
sysprof.c
Normal file
712
sysprof.c
Normal file
@ -0,0 +1,712 @@
|
||||
#include <stdio.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <glade/glade.h>
|
||||
|
||||
#include "binfile.h"
|
||||
#include "watch.h"
|
||||
#include "sysprof-module.h"
|
||||
#include "stackstash.h"
|
||||
#include "profile.h"
|
||||
#include "treeviewutils.h"
|
||||
|
||||
/* FIXME */
|
||||
#define _(a) a
|
||||
|
||||
typedef struct Application Application;
|
||||
|
||||
struct Application
|
||||
{
|
||||
int input_fd;
|
||||
StackStash * stash;
|
||||
GList * page_faults;
|
||||
GtkTreeView * object_view;
|
||||
GtkTreeView * callers_view;
|
||||
GtkTreeView * descendants_view;
|
||||
GtkStatusbar * statusbar;
|
||||
|
||||
GtkToolItem * profile_button;
|
||||
GtkToolItem * reset_button;
|
||||
|
||||
Profile * profile;
|
||||
ProfileDescendant * descendants;
|
||||
ProfileCaller * callers;
|
||||
|
||||
int n_samples;
|
||||
gboolean profiling;
|
||||
|
||||
int timeout_id;
|
||||
|
||||
int generating_profile;
|
||||
};
|
||||
|
||||
static void
|
||||
disaster (const char *what)
|
||||
{
|
||||
fprintf (stderr, what);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
static void
|
||||
update_sensitivity (Application *app)
|
||||
{
|
||||
gboolean sensitive_profile_button = (app->n_samples != 0);
|
||||
|
||||
gtk_widget_set_sensitive (GTK_WIDGET (app->profile_button),
|
||||
sensitive_profile_button);
|
||||
#if 0
|
||||
gtk_widget_set_sensitive (GTK_WIDGET (app->reset_button),
|
||||
sensitive_profile_button);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if 0
|
||||
static gchar *
|
||||
get_name (pid_t pid)
|
||||
{
|
||||
char *cmdline;
|
||||
char *name = g_strdup_printf ("/proc/%d/cmdline", pid);
|
||||
|
||||
if (g_file_get_contents (name, &cmdline, NULL, NULL))
|
||||
return cmdline;
|
||||
else
|
||||
return g_strdup ("<unknown>");
|
||||
}
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
really_show_samples (gpointer data)
|
||||
{
|
||||
Application *app = data;
|
||||
char *label;
|
||||
|
||||
label = g_strdup_printf ("Samples: %d", app->n_samples);
|
||||
|
||||
gtk_statusbar_pop (app->statusbar, 0);
|
||||
gtk_statusbar_push (app->statusbar, 0, label);
|
||||
|
||||
g_free (label);
|
||||
|
||||
app->timeout_id = 0;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
show_samples (Application *app)
|
||||
{
|
||||
if (!app->timeout_id)
|
||||
app->timeout_id = g_timeout_add (300, really_show_samples, app);
|
||||
}
|
||||
|
||||
static void
|
||||
on_read (gpointer data)
|
||||
{
|
||||
Application *app = data;
|
||||
SysprofStackTrace trace;
|
||||
int rd;
|
||||
|
||||
rd = read (app->input_fd, &trace, sizeof (trace));
|
||||
|
||||
if (app->profiling && !app->generating_profile)
|
||||
{
|
||||
Process *process = process_get_from_pid (trace.pid);
|
||||
int i;
|
||||
char *filename = NULL;
|
||||
|
||||
if (*trace.filename)
|
||||
filename = trace.filename;
|
||||
|
||||
for (i = 0; i < trace.n_addresses; ++i)
|
||||
process_ensure_map (process, trace.pid, (gulong)trace.addresses[i]);
|
||||
g_assert (!app->generating_profile);
|
||||
|
||||
stack_stash_add_trace (
|
||||
app->stash, process,
|
||||
(gulong *)trace.addresses, trace.n_addresses, 1);
|
||||
|
||||
app->n_samples++;
|
||||
show_samples (app);
|
||||
}
|
||||
|
||||
update_sensitivity (app);
|
||||
}
|
||||
|
||||
static void
|
||||
on_reset (GtkWidget *widget, gpointer data)
|
||||
{
|
||||
Application *app = data;
|
||||
|
||||
if (app->profile)
|
||||
{
|
||||
profile_free (app->profile);
|
||||
app->profile = NULL;
|
||||
|
||||
gtk_tree_view_set_model (GTK_TREE_VIEW (app->object_view), NULL);
|
||||
gtk_tree_view_set_model (GTK_TREE_VIEW (app->callers_view), NULL);
|
||||
gtk_tree_view_set_model (GTK_TREE_VIEW (app->descendants_view), NULL);
|
||||
}
|
||||
|
||||
if (app->stash)
|
||||
stack_stash_free (app->stash);
|
||||
app->stash = stack_stash_new ();
|
||||
process_flush_caches ();
|
||||
app->n_samples = 0;
|
||||
show_samples (app);
|
||||
update_sensitivity (app);
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
OBJECT_NAME,
|
||||
OBJECT_SELF,
|
||||
OBJECT_TOTAL,
|
||||
OBJECT_OBJECT
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
CALLERS_NAME,
|
||||
CALLERS_SELF,
|
||||
CALLERS_TOTAL,
|
||||
CALLERS_OBJECT
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
DESCENDANTS_NAME,
|
||||
DESCENDANTS_SELF,
|
||||
DESCENDANTS_NON_RECURSE,
|
||||
DESCENDANTS_TOTAL,
|
||||
DESCENDANTS_OBJECT
|
||||
};
|
||||
|
||||
static void
|
||||
fill_main_list (Application *app)
|
||||
{
|
||||
GList *list;
|
||||
GtkListStore *list_store;
|
||||
Profile *profile = app->profile;
|
||||
|
||||
if (profile)
|
||||
{
|
||||
gpointer sort_state;
|
||||
|
||||
list_store = gtk_list_store_new (4,
|
||||
G_TYPE_STRING,
|
||||
G_TYPE_DOUBLE,
|
||||
G_TYPE_DOUBLE,
|
||||
G_TYPE_POINTER);
|
||||
|
||||
for (list = profile->objects; list != NULL; list = list->next)
|
||||
{
|
||||
ProfileObject *object = list->data;
|
||||
GtkTreeIter iter;
|
||||
|
||||
gtk_list_store_append (list_store, &iter);
|
||||
|
||||
gtk_list_store_set (list_store, &iter,
|
||||
OBJECT_NAME, object->name,
|
||||
OBJECT_SELF, 100.0 * object->self/(double)profile->profile_size,
|
||||
OBJECT_TOTAL, 100.0 * object->total/(double)profile->profile_size,
|
||||
OBJECT_OBJECT, object,
|
||||
-1);
|
||||
}
|
||||
|
||||
sort_state = save_sort_state (app->object_view);
|
||||
|
||||
gtk_tree_view_set_model (app->object_view, GTK_TREE_MODEL (list_store));
|
||||
|
||||
if (sort_state)
|
||||
{
|
||||
restore_sort_state (app->object_view, sort_state);
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_store),
|
||||
OBJECT_TOTAL,
|
||||
GTK_SORT_DESCENDING);
|
||||
}
|
||||
|
||||
g_object_unref (G_OBJECT (list_store));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_profile (gpointer widget, gpointer data)
|
||||
{
|
||||
Application *app = data;
|
||||
|
||||
if (app->generating_profile)
|
||||
return;
|
||||
|
||||
if (app->profile)
|
||||
profile_free (app->profile);
|
||||
|
||||
/* take care of reentrancy */
|
||||
app->generating_profile = TRUE;
|
||||
|
||||
app->profile = profile_new (app->stash);
|
||||
|
||||
app->generating_profile = FALSE;
|
||||
|
||||
fill_main_list (app);
|
||||
}
|
||||
|
||||
static void
|
||||
on_delete (GtkWidget *window)
|
||||
{
|
||||
gtk_main_quit ();
|
||||
}
|
||||
|
||||
static void
|
||||
on_start_toggled (GtkToggleToolButton *tool_button, gpointer data)
|
||||
{
|
||||
Application *app = data;
|
||||
|
||||
app->profiling = gtk_toggle_tool_button_get_active (tool_button);
|
||||
|
||||
update_sensitivity (app);
|
||||
}
|
||||
|
||||
static void
|
||||
add_node (GtkTreeStore *store,
|
||||
int size,
|
||||
const GtkTreeIter *parent,
|
||||
ProfileDescendant *node)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
gtk_tree_store_insert (store, &iter, (GtkTreeIter *)parent, 0);
|
||||
|
||||
gtk_tree_store_set (store, &iter,
|
||||
DESCENDANTS_NAME, node->object->name,
|
||||
DESCENDANTS_SELF, 100 * (node->self)/(double)size,
|
||||
DESCENDANTS_NON_RECURSE, 100 * (node->non_recursion)/(double)size,
|
||||
DESCENDANTS_TOTAL, 100 * (node->total)/(double)size,
|
||||
DESCENDANTS_OBJECT, node->object,
|
||||
-1);
|
||||
|
||||
add_node (store, size, parent, node->siblings);
|
||||
add_node (store, size, &iter, node->children);
|
||||
}
|
||||
|
||||
static ProfileObject *
|
||||
get_current_object (Application *app)
|
||||
{
|
||||
GtkTreeSelection *selection;
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter selected;
|
||||
ProfileObject *object;
|
||||
|
||||
selection = gtk_tree_view_get_selection (app->object_view);
|
||||
|
||||
gtk_tree_selection_get_selected (selection, &model, &selected);
|
||||
|
||||
gtk_tree_model_get (model, &selected,
|
||||
OBJECT_OBJECT, &object,
|
||||
-1);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
static void
|
||||
fill_descendants_tree (Application *app)
|
||||
{
|
||||
GtkTreeStore *tree_store;
|
||||
gpointer sort_state;
|
||||
|
||||
sort_state = save_sort_state (app->descendants_view);
|
||||
|
||||
if (app->descendants)
|
||||
{
|
||||
profile_descendant_free (app->descendants);
|
||||
app->descendants = NULL;
|
||||
}
|
||||
|
||||
tree_store =
|
||||
gtk_tree_store_new (5,
|
||||
G_TYPE_STRING,
|
||||
G_TYPE_DOUBLE,
|
||||
G_TYPE_DOUBLE,
|
||||
G_TYPE_DOUBLE,
|
||||
G_TYPE_POINTER);
|
||||
|
||||
if (app->profile)
|
||||
{
|
||||
ProfileObject *object = get_current_object (app);
|
||||
if (object)
|
||||
{
|
||||
app->descendants =
|
||||
profile_create_descendants (app->profile, object);
|
||||
add_node (tree_store,
|
||||
app->profile->profile_size, NULL, app->descendants);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_tree_view_set_model (
|
||||
app->descendants_view, GTK_TREE_MODEL (tree_store));
|
||||
|
||||
g_object_unref (G_OBJECT (tree_store));
|
||||
|
||||
if (!sort_state)
|
||||
{
|
||||
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (tree_store),
|
||||
DESCENDANTS_NON_RECURSE,
|
||||
GTK_SORT_DESCENDING);
|
||||
}
|
||||
else
|
||||
{
|
||||
restore_sort_state (app->descendants_view, sort_state);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_callers (GtkListStore *list_store,
|
||||
Profile *profile,
|
||||
ProfileCaller *callers)
|
||||
{
|
||||
while (callers)
|
||||
{
|
||||
gchar *name;
|
||||
GtkTreeIter iter;
|
||||
|
||||
if (callers->object)
|
||||
name = callers->object->name;
|
||||
else
|
||||
name = "<spontaneous>";
|
||||
|
||||
gtk_list_store_append (list_store, &iter);
|
||||
gtk_list_store_set (
|
||||
list_store, &iter,
|
||||
CALLERS_NAME, name,
|
||||
CALLERS_SELF, 100.0 * callers->self/(double)profile->profile_size,
|
||||
CALLERS_TOTAL, 100.0 * callers->total/(double)profile->profile_size,
|
||||
CALLERS_OBJECT, callers->object,
|
||||
-1);
|
||||
|
||||
callers = callers->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
fill_callers_list (Application *app)
|
||||
{
|
||||
GtkListStore *list_store;
|
||||
gpointer sort_state;
|
||||
|
||||
sort_state = save_sort_state (app->descendants_view);
|
||||
|
||||
if (app->callers)
|
||||
{
|
||||
profile_caller_free (app->callers);
|
||||
app->callers = NULL;
|
||||
}
|
||||
|
||||
list_store =
|
||||
gtk_list_store_new (4,
|
||||
G_TYPE_STRING,
|
||||
G_TYPE_DOUBLE,
|
||||
G_TYPE_DOUBLE,
|
||||
G_TYPE_POINTER);
|
||||
|
||||
if (app->profile)
|
||||
{
|
||||
ProfileObject *object = get_current_object (app);
|
||||
if (object)
|
||||
{
|
||||
app->callers = profile_list_callers (app->profile, object);
|
||||
add_callers (list_store, app->profile, app->callers);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_tree_view_set_model (
|
||||
app->callers_view, GTK_TREE_MODEL (list_store));
|
||||
|
||||
g_object_unref (G_OBJECT (list_store));
|
||||
|
||||
if (!sort_state)
|
||||
{
|
||||
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_store),
|
||||
CALLERS_TOTAL,
|
||||
GTK_SORT_DESCENDING);
|
||||
}
|
||||
else
|
||||
{
|
||||
restore_sort_state (app->callers_view, sort_state);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_object_selection_changed (GtkTreeSelection *selection, gpointer data)
|
||||
{
|
||||
Application *app = data;
|
||||
GtkTreePath *path;
|
||||
|
||||
fill_descendants_tree (app);
|
||||
fill_callers_list (app);
|
||||
|
||||
/* Expand the toplevel of the descendant tree so we see the immediate
|
||||
* descendants.
|
||||
*/
|
||||
path = gtk_tree_path_new_from_indices (0, -1);
|
||||
gtk_tree_view_expand_row (
|
||||
GTK_TREE_VIEW (app->descendants_view), path, FALSE);
|
||||
gtk_tree_path_free (path);
|
||||
}
|
||||
|
||||
static void
|
||||
really_goto_object (Application *app, ProfileObject *object)
|
||||
{
|
||||
GtkTreeModel *profile_objects;
|
||||
GtkTreeIter iter;
|
||||
gboolean found = FALSE;
|
||||
|
||||
profile_objects = gtk_tree_view_get_model (app->object_view);
|
||||
|
||||
if (gtk_tree_model_get_iter_first (profile_objects, &iter))
|
||||
{
|
||||
do
|
||||
{
|
||||
ProfileObject *profile_object;
|
||||
|
||||
gtk_tree_model_get (profile_objects, &iter,
|
||||
OBJECT_OBJECT, &profile_object,
|
||||
-1);
|
||||
|
||||
if (profile_object == object)
|
||||
{
|
||||
found = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (gtk_tree_model_iter_next (profile_objects, &iter));
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
GtkTreePath *path =
|
||||
gtk_tree_model_get_path (profile_objects, &iter);
|
||||
|
||||
gtk_tree_view_set_cursor (app->object_view, path, 0, FALSE);
|
||||
}
|
||||
|
||||
gtk_widget_grab_focus (GTK_WIDGET (app->object_view));
|
||||
}
|
||||
|
||||
static void
|
||||
goto_object (Application *app,
|
||||
GtkTreeView *tree_view,
|
||||
GtkTreePath *path,
|
||||
gint column)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
|
||||
ProfileObject *object;
|
||||
|
||||
if (!gtk_tree_model_get_iter (model, &iter, path))
|
||||
return;
|
||||
|
||||
gtk_tree_model_get (model, &iter, column, &object, -1);
|
||||
|
||||
if (!object)
|
||||
return;
|
||||
|
||||
really_goto_object (app, object);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
on_descendants_row_activated (GtkTreeView *tree_view,
|
||||
GtkTreePath *path,
|
||||
GtkTreeViewColumn *column,
|
||||
gpointer data)
|
||||
{
|
||||
goto_object (data, tree_view, path, DESCENDANTS_OBJECT);
|
||||
}
|
||||
|
||||
static void
|
||||
on_callers_row_activated (GtkTreeView *tree_view,
|
||||
GtkTreePath *path,
|
||||
GtkTreeViewColumn *column,
|
||||
gpointer data)
|
||||
{
|
||||
goto_object (data, tree_view, path, CALLERS_OBJECT);
|
||||
}
|
||||
|
||||
static void
|
||||
add_stock (const char *stock_id,
|
||||
const char *label, guint size, const guint8 *data)
|
||||
{
|
||||
GdkPixbuf *pixbuf;
|
||||
GtkIconSet *icon_set;
|
||||
GtkIconFactory *icon_factory;
|
||||
GtkStockItem stock_item;
|
||||
|
||||
pixbuf = gdk_pixbuf_new_from_inline (size, data, FALSE, NULL);
|
||||
icon_set = gtk_icon_set_new_from_pixbuf (pixbuf);
|
||||
icon_factory = gtk_icon_factory_new ();
|
||||
gtk_icon_factory_add (icon_factory, stock_id, icon_set);
|
||||
gtk_icon_factory_add_default (icon_factory);
|
||||
g_object_unref (G_OBJECT (icon_factory));
|
||||
gtk_icon_set_unref (icon_set);
|
||||
stock_item.stock_id = (char *)stock_id;
|
||||
stock_item.label = (char *)label;
|
||||
stock_item.modifier = 0;
|
||||
stock_item.keyval = 0;
|
||||
stock_item.translation_domain = NULL;
|
||||
gtk_stock_add (&stock_item, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
build_gui (Application *app)
|
||||
{
|
||||
GladeXML *xml;
|
||||
GtkWidget *main_window;
|
||||
GtkWidget *main_vbox;
|
||||
GtkWidget *toolbar;
|
||||
GtkToolItem *item;
|
||||
GtkTreeSelection *selection;
|
||||
|
||||
xml = glade_xml_new ("./sysprof.glade", NULL, NULL);
|
||||
|
||||
/* Main Window */
|
||||
main_window = glade_xml_get_widget (xml, "main_window");
|
||||
|
||||
g_signal_connect (G_OBJECT (main_window), "delete_event",
|
||||
G_CALLBACK (on_delete), NULL);
|
||||
gtk_window_set_default_size (GTK_WINDOW (main_window), 640, 400);
|
||||
gtk_widget_show_all (main_window);
|
||||
|
||||
/* Toolbar */
|
||||
main_vbox = glade_xml_get_widget (xml, "main_vbox");
|
||||
toolbar = gtk_toolbar_new ();
|
||||
|
||||
/* Stock Items */
|
||||
#include "pixbufs.c"
|
||||
add_stock ("sysprof-start-profiling", "Star_t",
|
||||
sizeof (start_profiling), start_profiling);
|
||||
add_stock ("sysprof-stop-profiling", "Sto_p",
|
||||
sizeof (stop_profiling), stop_profiling);
|
||||
|
||||
/* Stop */
|
||||
item = gtk_radio_tool_button_new_from_stock (
|
||||
NULL, "sysprof-stop-profiling");
|
||||
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -11);
|
||||
|
||||
/* Start */
|
||||
item = gtk_radio_tool_button_new_with_stock_from_widget (
|
||||
GTK_RADIO_TOOL_BUTTON (item), "sysprof-start-profiling");
|
||||
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 0);
|
||||
g_signal_connect (G_OBJECT (item), "toggled",
|
||||
G_CALLBACK (on_start_toggled), app);
|
||||
|
||||
/* Reset */
|
||||
item = gtk_tool_button_new_from_stock (GTK_STOCK_CLEAR);
|
||||
gtk_tool_button_set_label (GTK_TOOL_BUTTON (item), "_Reset");
|
||||
gtk_tool_button_set_use_underline (GTK_TOOL_BUTTON (item), TRUE);
|
||||
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
|
||||
g_signal_connect (G_OBJECT (item), "clicked",
|
||||
G_CALLBACK (on_reset), app);
|
||||
|
||||
app->reset_button = item;
|
||||
|
||||
/* Separator */
|
||||
item = gtk_separator_tool_item_new ();
|
||||
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
|
||||
|
||||
/* Profile */
|
||||
item = gtk_tool_button_new_from_stock (GTK_STOCK_JUSTIFY_LEFT);
|
||||
gtk_tool_button_set_label (GTK_TOOL_BUTTON (item), "_Profile");
|
||||
gtk_tool_button_set_use_underline (GTK_TOOL_BUTTON (item), TRUE);
|
||||
gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1);
|
||||
g_signal_connect (G_OBJECT (item), "clicked",
|
||||
G_CALLBACK (on_profile), app);
|
||||
|
||||
app->profile_button = item;
|
||||
|
||||
/* Show toolbar */
|
||||
gtk_widget_show_all (GTK_WIDGET (toolbar));
|
||||
|
||||
/* Add toolbar to vbox */
|
||||
gtk_container_add (GTK_CONTAINER (main_vbox), toolbar);
|
||||
gtk_box_reorder_child (GTK_BOX (main_vbox), toolbar, 1);
|
||||
gtk_box_set_child_packing (GTK_BOX (main_vbox), toolbar,
|
||||
FALSE, TRUE, 0, GTK_PACK_START);
|
||||
|
||||
/* TreeViews */
|
||||
/* object view */
|
||||
app->object_view = (GtkTreeView *)glade_xml_get_widget (xml, "object_view");
|
||||
add_plain_text_column (app->object_view, _("Name"), OBJECT_NAME);
|
||||
add_double_format_column (app->object_view, _("Self"), OBJECT_SELF, "%.2f");
|
||||
add_double_format_column (app->object_view, _("Total"), OBJECT_TOTAL, "%.2f");
|
||||
selection = gtk_tree_view_get_selection (app->object_view);
|
||||
g_signal_connect (selection, "changed", G_CALLBACK (on_object_selection_changed), app);
|
||||
|
||||
/* callers view */
|
||||
app->callers_view = (GtkTreeView *)glade_xml_get_widget (xml, "callers_view");
|
||||
add_plain_text_column (app->callers_view, _("Name"), CALLERS_NAME);
|
||||
add_double_format_column (app->callers_view, _("Self"), CALLERS_SELF, "%.2f");
|
||||
add_double_format_column (app->callers_view, _("Total"), CALLERS_TOTAL, "%.2f");
|
||||
g_signal_connect (app->callers_view, "row-activated",
|
||||
G_CALLBACK (on_callers_row_activated), app);
|
||||
|
||||
/* descendants view */
|
||||
app->descendants_view = (GtkTreeView *)glade_xml_get_widget (xml, "descendants_view");
|
||||
add_plain_text_column (app->descendants_view, _("Name"), DESCENDANTS_NAME);
|
||||
add_double_format_column (app->descendants_view, _("Self"), DESCENDANTS_SELF, "%.2f");
|
||||
add_double_format_column (app->descendants_view, _("Cummulative"), DESCENDANTS_NON_RECURSE, "%.2f");
|
||||
g_signal_connect (app->descendants_view, "row-activated",
|
||||
G_CALLBACK (on_descendants_row_activated), app);
|
||||
|
||||
/* Statusbar */
|
||||
app->statusbar = (GtkStatusbar *)glade_xml_get_widget (xml, "statusbar");
|
||||
show_samples (app);
|
||||
}
|
||||
|
||||
static Application *
|
||||
application_new (void)
|
||||
{
|
||||
Application *app = g_new0 (Application, 1);
|
||||
|
||||
app->stash = stack_stash_new ();
|
||||
app->input_fd = -1;
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
Application *app;
|
||||
|
||||
gtk_init (&argc, &argv);
|
||||
|
||||
app = application_new ();
|
||||
|
||||
app->input_fd = open ("/proc/sysprof-trace", O_RDONLY);
|
||||
if (app->input_fd < 0)
|
||||
{
|
||||
disaster ("Can't open /proc/sysprof-trace. You need to insert\n"
|
||||
"the sysprof kernel module. Type\n"
|
||||
"\n"
|
||||
" insmod sysprof-module.o\n"
|
||||
"\n"
|
||||
"as root\n");
|
||||
}
|
||||
|
||||
fd_add_watch (app->input_fd, app);
|
||||
fd_set_read_callback (app->input_fd, on_read);
|
||||
|
||||
build_gui (app);
|
||||
|
||||
update_sensitivity (app);
|
||||
|
||||
gtk_main ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
318
sysprof.glade
Normal file
318
sysprof.glade
Normal file
@ -0,0 +1,318 @@
|
||||
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
|
||||
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
|
||||
|
||||
<glade-interface>
|
||||
|
||||
<widget class="GtkWindow" id="main_window">
|
||||
<property name="title" translatable="yes">System Profiler</property>
|
||||
<property name="type">GTK_WINDOW_TOPLEVEL</property>
|
||||
<property name="window_position">GTK_WIN_POS_NONE</property>
|
||||
<property name="modal">False</property>
|
||||
<property name="resizable">True</property>
|
||||
<property name="destroy_with_parent">False</property>
|
||||
<property name="decorated">True</property>
|
||||
<property name="skip_taskbar_hint">False</property>
|
||||
<property name="skip_pager_hint">False</property>
|
||||
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
|
||||
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkVBox" id="main_vbox">
|
||||
<property name="visible">True</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="spacing">0</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenuBar" id="menubar1">
|
||||
<property name="visible">True</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="menuitem1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_File</property>
|
||||
<property name="use_underline">True</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenu" id="menu1">
|
||||
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="new1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-new</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_new1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="open1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-open</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_open1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="save1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-save</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_save1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="save_as1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-save-as</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_save_as1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkSeparatorMenuItem" id="separatormenuitem1">
|
||||
<property name="visible">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="quit1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-quit</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_quit1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="menuitem2">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Edit</property>
|
||||
<property name="use_underline">True</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenu" id="menu2">
|
||||
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="cut1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-cut</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_cut1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="copy1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-copy</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_copy1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="paste1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-paste</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_paste1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="delete1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label">gtk-delete</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="activate" handler="on_delete1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="menuitem3">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_View</property>
|
||||
<property name="use_underline">True</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenu" id="menu3">
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="menuitem4">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_Help</property>
|
||||
<property name="use_underline">True</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenu" id="menu4">
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="about1">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">_About</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="on_about1_activate" last_modification_time="Wed, 31 Dec 2003 20:44:40 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkVBox" id="vbox2">
|
||||
<property name="visible">True</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="spacing">0</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkHPaned" id="hpaned1">
|
||||
<property name="border_width">3</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkVPaned" id="vpaned1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="scrolledwindow1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="object_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="headers_visible">True</property>
|
||||
<property name="rules_hint">True</property>
|
||||
<property name="reorderable">False</property>
|
||||
<property name="enable_search">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="shrink">True</property>
|
||||
<property name="resize">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="scrolledwindow3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="callers_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="headers_visible">True</property>
|
||||
<property name="rules_hint">True</property>
|
||||
<property name="reorderable">False</property>
|
||||
<property name="enable_search">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="shrink">True</property>
|
||||
<property name="resize">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="shrink">True</property>
|
||||
<property name="resize">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkScrolledWindow" id="scrolledwindow2">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
|
||||
|
||||
<child>
|
||||
<widget class="GtkTreeView" id="descendants_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="headers_visible">True</property>
|
||||
<property name="rules_hint">True</property>
|
||||
<property name="reorderable">False</property>
|
||||
<property name="enable_search">True</property>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="shrink">True</property>
|
||||
<property name="resize">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkStatusbar" id="statusbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="has_resize_grip">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
</child>
|
||||
</widget>
|
||||
|
||||
</glade-interface>
|
||||
228
treeviewutils.c
Normal file
228
treeviewutils.c
Normal file
@ -0,0 +1,228 @@
|
||||
/* -*- mode: C; c-file-style: "linux" -*- */
|
||||
|
||||
/* MemProf -- memory profiler and leak detector
|
||||
* Copyright 2002, Soeren Sandmann (sandmann@daimi.au.dk)
|
||||
* Copyright 2003, Red Hat, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
/*====*/
|
||||
|
||||
#include "treeviewutils.h"
|
||||
|
||||
void
|
||||
column_set_sort_id (GtkTreeViewColumn *column,
|
||||
int column_id)
|
||||
{
|
||||
g_object_set_data (G_OBJECT (column),
|
||||
"mi-saved-sort-column", GINT_TO_POINTER (column_id));
|
||||
gtk_tree_view_column_set_sort_column_id (column, column_id);
|
||||
}
|
||||
|
||||
void
|
||||
tree_view_unset_sort_ids (GtkTreeView *tree_view)
|
||||
{
|
||||
GList *columns = gtk_tree_view_get_columns (tree_view);
|
||||
GList *l;
|
||||
|
||||
for (l = columns; l; l = l->next) {
|
||||
gtk_tree_view_column_set_sort_column_id (l->data, -1);
|
||||
}
|
||||
|
||||
g_list_free (columns);
|
||||
}
|
||||
|
||||
void
|
||||
tree_view_set_sort_ids (GtkTreeView *tree_view)
|
||||
{
|
||||
GList *columns = gtk_tree_view_get_columns (tree_view);
|
||||
GList *l;
|
||||
|
||||
for (l = columns; l; l = l->next) {
|
||||
int column_id = GPOINTER_TO_INT (g_object_get_data (l->data, "mi-saved-sort-column"));
|
||||
gtk_tree_view_column_set_sort_column_id (l->data, column_id);
|
||||
}
|
||||
|
||||
g_list_free (columns);
|
||||
}
|
||||
|
||||
int
|
||||
list_iter_get_index (GtkTreeModel *model,
|
||||
GtkTreeIter *iter)
|
||||
{
|
||||
GtkTreePath *path = gtk_tree_model_get_path (model,iter);
|
||||
int result;
|
||||
|
||||
g_assert (path);
|
||||
g_assert (gtk_tree_path_get_depth (path) == 1);
|
||||
result = gtk_tree_path_get_indices (path)[0];
|
||||
gtk_tree_path_free (path);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
GtkTreeViewColumn *
|
||||
add_plain_text_column (GtkTreeView *view, const gchar *title, gint model_column)
|
||||
{
|
||||
GtkCellRenderer *renderer;
|
||||
GtkTreeViewColumn *column;
|
||||
|
||||
renderer = gtk_cell_renderer_text_new ();
|
||||
column = gtk_tree_view_column_new_with_attributes (title, renderer,
|
||||
"text", model_column,
|
||||
NULL);
|
||||
gtk_tree_view_column_set_resizable (column, TRUE);
|
||||
gtk_tree_view_append_column (view, column);
|
||||
column_set_sort_id (column, model_column);
|
||||
|
||||
return column;
|
||||
}
|
||||
|
||||
static void
|
||||
pointer_to_text (GtkTreeViewColumn *tree_column,
|
||||
GtkCellRenderer *cell, GtkTreeModel *tree_model,
|
||||
GtkTreeIter *iter, gpointer data)
|
||||
{
|
||||
gpointer p;
|
||||
gchar *text;
|
||||
int column = GPOINTER_TO_INT (data);
|
||||
|
||||
gtk_tree_model_get (tree_model, iter, column, &p, -1);
|
||||
text = g_strdup_printf ("%p", p);
|
||||
g_object_set (cell, "text", text, NULL);
|
||||
g_free (text);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int column;
|
||||
char *format;
|
||||
} ColumnInfo;
|
||||
|
||||
static void
|
||||
double_to_text (GtkTreeViewColumn *tree_column,
|
||||
GtkCellRenderer *cell, GtkTreeModel *tree_model,
|
||||
GtkTreeIter *iter, gpointer data)
|
||||
{
|
||||
gdouble d;
|
||||
gchar *text;
|
||||
ColumnInfo *info = data;
|
||||
|
||||
gtk_tree_model_get (tree_model, iter, info->column, &d, -1);
|
||||
|
||||
text = g_strdup_printf (info->format, d);
|
||||
|
||||
g_object_set (cell, "text", text, NULL);
|
||||
g_free (text);
|
||||
}
|
||||
|
||||
static void
|
||||
free_column_info (void *data)
|
||||
{
|
||||
ColumnInfo *info = data;
|
||||
g_free (info->format);
|
||||
g_free (info);
|
||||
}
|
||||
|
||||
GtkTreeViewColumn *
|
||||
add_double_format_column (GtkTreeView *view, const gchar *title, gint model_column, const char *format)
|
||||
{
|
||||
GtkCellRenderer *renderer;
|
||||
GtkTreeViewColumn *column;
|
||||
ColumnInfo *column_info = g_new (ColumnInfo, 1);
|
||||
|
||||
renderer = gtk_cell_renderer_text_new ();
|
||||
|
||||
column = gtk_tree_view_column_new ();
|
||||
gtk_tree_view_column_set_title (column, title);
|
||||
gtk_tree_view_column_pack_start (column, renderer, TRUE);
|
||||
gtk_tree_view_column_set_resizable (column, TRUE);
|
||||
|
||||
column_info->column = model_column;
|
||||
column_info->format = g_strdup (format);
|
||||
|
||||
gtk_tree_view_column_set_cell_data_func (column, renderer,
|
||||
double_to_text, column_info, free_column_info);
|
||||
|
||||
gtk_tree_view_append_column (view, column);
|
||||
column_set_sort_id (column, model_column);
|
||||
|
||||
return column;
|
||||
}
|
||||
|
||||
GtkTreeViewColumn *
|
||||
add_pointer_column (GtkTreeView *view, const gchar *title, gint model_column)
|
||||
{
|
||||
GtkCellRenderer *renderer;
|
||||
GtkTreeViewColumn *column;
|
||||
|
||||
renderer = gtk_cell_renderer_text_new ();
|
||||
|
||||
column = gtk_tree_view_column_new ();
|
||||
if (title)
|
||||
gtk_tree_view_column_set_title (column, title);
|
||||
gtk_tree_view_column_pack_start (column, renderer, TRUE);
|
||||
gtk_tree_view_column_set_resizable (column, TRUE);
|
||||
gtk_tree_view_column_set_cell_data_func (column, renderer,
|
||||
pointer_to_text, GINT_TO_POINTER (model_column), NULL);
|
||||
|
||||
gtk_tree_view_append_column (view, column);
|
||||
column_set_sort_id (column, model_column);
|
||||
|
||||
return column;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gboolean is_sorted;
|
||||
int sort_column;
|
||||
GtkSortType sort_type;
|
||||
} SortState;
|
||||
|
||||
|
||||
gpointer
|
||||
save_sort_state (GtkTreeView *view)
|
||||
{
|
||||
SortState *state = NULL;
|
||||
GtkTreeModel *model = gtk_tree_view_get_model (view);
|
||||
|
||||
if (model && GTK_IS_TREE_SORTABLE (model)) {
|
||||
state = g_new (SortState, 1);
|
||||
state->is_sorted = gtk_tree_sortable_get_sort_column_id (
|
||||
GTK_TREE_SORTABLE (model),
|
||||
&(state->sort_column),
|
||||
&(state->sort_type));
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
void
|
||||
restore_sort_state (GtkTreeView *view, gpointer st)
|
||||
{
|
||||
SortState *state = st;
|
||||
GtkTreeModel *model;
|
||||
|
||||
if (state) {
|
||||
model = gtk_tree_view_get_model (view);
|
||||
if (state->is_sorted && model && GTK_IS_TREE_SORTABLE (model)) {
|
||||
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
|
||||
state->sort_column,
|
||||
state->sort_type);
|
||||
}
|
||||
g_free (state);
|
||||
}
|
||||
|
||||
gtk_tree_sortable_sort_column_changed (GTK_TREE_SORTABLE (model));
|
||||
}
|
||||
45
treeviewutils.h
Normal file
45
treeviewutils.h
Normal file
@ -0,0 +1,45 @@
|
||||
/* -*- mode: C; c-file-style: "linux" -*- */
|
||||
|
||||
/* MemProf -- memory profiler and leak detector
|
||||
* Copyright 2002, Soeren Sandmann (sandmann@daimi.au.dk)
|
||||
* Copyright 2003, Red Hat, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
/*====*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
void column_set_sort_id (GtkTreeViewColumn *column,
|
||||
int column_id);
|
||||
void tree_view_unset_sort_ids (GtkTreeView *tree_view);
|
||||
void tree_view_set_sort_ids (GtkTreeView *tree_view);
|
||||
|
||||
int list_iter_get_index (GtkTreeModel *model,
|
||||
GtkTreeIter *iter);
|
||||
|
||||
GtkTreeViewColumn *add_plain_text_column (GtkTreeView *view,
|
||||
const char *title,
|
||||
gint model_column);
|
||||
GtkTreeViewColumn *add_double_format_column (GtkTreeView *view,
|
||||
const char *title,
|
||||
int model_column,
|
||||
const char *format);
|
||||
GtkTreeViewColumn *add_pointer_column (GtkTreeView *view,
|
||||
const char *title,
|
||||
int model_column);
|
||||
gpointer save_sort_state (GtkTreeView *view);
|
||||
void restore_sort_state (GtkTreeView *view,
|
||||
gpointer state);
|
||||
328
watch.c
Normal file
328
watch.c
Normal file
@ -0,0 +1,328 @@
|
||||
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- */
|
||||
|
||||
/* - Library for asynchronous communication
|
||||
* Copyright (C) 2002 S<>ren Sandmann (sandmann@daimi.au.dk)
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include "watch.h"
|
||||
|
||||
typedef struct Watch Watch;
|
||||
|
||||
struct Watch {
|
||||
GSource source;
|
||||
GPollFD poll_fd;
|
||||
gboolean removed;
|
||||
|
||||
WatchCallback read_callback;
|
||||
WatchCallback write_callback;
|
||||
WatchCallback hangup_callback;
|
||||
WatchCallback error_callback;
|
||||
WatchCallback priority_callback;
|
||||
|
||||
gpointer data;
|
||||
};
|
||||
|
||||
static GHashTable *watched_fds;
|
||||
|
||||
static void
|
||||
init (void)
|
||||
{
|
||||
if (!watched_fds)
|
||||
watched_fds = g_hash_table_new (g_int_hash, g_int_equal);
|
||||
}
|
||||
|
||||
static Watch *
|
||||
lookup_watch (gint fd)
|
||||
{
|
||||
init ();
|
||||
|
||||
return g_hash_table_lookup (watched_fds, &fd);
|
||||
}
|
||||
|
||||
static void
|
||||
internal_add_watch (Watch *watch)
|
||||
{
|
||||
gpointer fd = &(watch->poll_fd.fd);
|
||||
|
||||
init ();
|
||||
|
||||
g_hash_table_insert (watched_fds, fd, watch);
|
||||
g_source_add_poll ((GSource *)watch, &(watch->poll_fd));
|
||||
}
|
||||
|
||||
static void
|
||||
internal_remove_watch (Watch *watch)
|
||||
{
|
||||
gpointer fd = &(watch->poll_fd.fd);
|
||||
|
||||
init ();
|
||||
|
||||
watch->removed = TRUE;
|
||||
g_source_remove_poll ((GSource *)watch, &(watch->poll_fd));
|
||||
g_hash_table_remove (watched_fds, fd);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
watch_prepare (GSource *source,
|
||||
gint *timeout)
|
||||
{
|
||||
*timeout = -1;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
watch_check (GSource *source)
|
||||
{
|
||||
Watch *watch = (Watch *)source;
|
||||
gint revents = watch->poll_fd.revents;
|
||||
|
||||
if (revents & (G_IO_NVAL))
|
||||
{
|
||||
/* This can happen if the user closes the file descriptor
|
||||
* without first removing the watch. We silently ignore it
|
||||
*/
|
||||
internal_remove_watch (watch);
|
||||
g_source_unref (source);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ((revents & G_IO_HUP) && watch->hangup_callback)
|
||||
return TRUE;
|
||||
|
||||
if ((revents & G_IO_IN) && watch->read_callback)
|
||||
return TRUE;
|
||||
|
||||
if ((revents & G_IO_PRI) && watch->priority_callback)
|
||||
return TRUE;
|
||||
|
||||
if ((revents & G_IO_ERR) && watch->error_callback)
|
||||
return TRUE;
|
||||
|
||||
if ((revents & G_IO_OUT) && watch->write_callback)
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
watch_dispatch (GSource *source,
|
||||
GSourceFunc callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
Watch *watch = (Watch *)source;
|
||||
gint revents = watch->poll_fd.revents;
|
||||
gboolean removed;
|
||||
|
||||
g_source_ref (source);
|
||||
|
||||
if (!watch->removed && (revents & G_IO_IN) && watch->read_callback)
|
||||
watch->read_callback (watch->data);
|
||||
|
||||
if (!watch->removed && (revents & G_IO_PRI) && watch->priority_callback)
|
||||
watch->priority_callback (watch->data);
|
||||
|
||||
if (!watch->removed && (revents & G_IO_OUT) && watch->write_callback)
|
||||
watch->write_callback (watch->data);
|
||||
|
||||
if (!watch->removed && (revents & G_IO_ERR) && watch->error_callback)
|
||||
watch->error_callback (watch->data);
|
||||
|
||||
if (!watch->removed && (revents & G_IO_HUP) && watch->hangup_callback)
|
||||
watch->hangup_callback (watch->data);
|
||||
|
||||
removed = watch->removed;
|
||||
|
||||
g_source_unref (source);
|
||||
|
||||
if (removed)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
watch_finalize (GSource *source)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
update_poll_mask (Watch *watch)
|
||||
{
|
||||
gint events = 0;
|
||||
|
||||
g_source_remove_poll ((GSource *)watch, &(watch->poll_fd));
|
||||
|
||||
if (watch->read_callback)
|
||||
events |= G_IO_IN;
|
||||
|
||||
if (watch->write_callback)
|
||||
events |= G_IO_OUT;
|
||||
|
||||
if (watch->priority_callback)
|
||||
events |= G_IO_PRI;
|
||||
|
||||
if (watch->hangup_callback)
|
||||
events |= G_IO_HUP;
|
||||
|
||||
if (watch->error_callback)
|
||||
events |= G_IO_ERR;
|
||||
|
||||
watch->poll_fd.events = events;
|
||||
|
||||
g_source_add_poll ((GSource *)watch, &(watch->poll_fd));
|
||||
}
|
||||
|
||||
void
|
||||
fd_add_watch (gint fd,
|
||||
gpointer data)
|
||||
{
|
||||
static GSourceFuncs watch_funcs = {
|
||||
watch_prepare,
|
||||
watch_check,
|
||||
watch_dispatch,
|
||||
watch_finalize,
|
||||
};
|
||||
Watch *watch = lookup_watch (fd);
|
||||
|
||||
g_return_if_fail (fd > 0);
|
||||
g_return_if_fail (watch == NULL);
|
||||
|
||||
watch = (Watch *)g_source_new (&watch_funcs, sizeof (Watch));
|
||||
g_source_set_can_recurse ((GSource *)watch, TRUE);
|
||||
g_source_attach ((GSource *)watch, NULL);
|
||||
|
||||
watch->poll_fd.fd = fd;
|
||||
watch->poll_fd.events = 0;
|
||||
watch->removed = FALSE;
|
||||
|
||||
watch->read_callback = NULL;
|
||||
watch->write_callback = NULL;
|
||||
watch->hangup_callback = NULL;
|
||||
watch->error_callback = NULL;
|
||||
watch->priority_callback = NULL;
|
||||
|
||||
watch->data = data;
|
||||
|
||||
internal_add_watch (watch);
|
||||
}
|
||||
|
||||
void
|
||||
fd_set_read_callback (gint fd,
|
||||
WatchCallback read_cb)
|
||||
{
|
||||
Watch *watch = lookup_watch (fd);
|
||||
|
||||
g_return_if_fail (fd > 0);
|
||||
g_return_if_fail (watch != NULL);
|
||||
|
||||
if (watch->read_callback != read_cb)
|
||||
{
|
||||
watch->read_callback = read_cb;
|
||||
update_poll_mask (watch);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
fd_set_write_callback (gint fd,
|
||||
WatchCallback write_cb)
|
||||
{
|
||||
Watch *watch = lookup_watch (fd);
|
||||
|
||||
g_return_if_fail (fd > 0);
|
||||
g_return_if_fail (watch != NULL);
|
||||
|
||||
if (watch->write_callback != write_cb)
|
||||
{
|
||||
watch->write_callback = write_cb;
|
||||
update_poll_mask (watch);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
fd_set_hangup_callback (gint fd,
|
||||
WatchCallback hangup_cb)
|
||||
{
|
||||
Watch *watch = lookup_watch (fd);
|
||||
|
||||
g_return_if_fail (fd > 0);
|
||||
g_return_if_fail (watch != NULL);
|
||||
|
||||
if (watch->hangup_callback != hangup_cb)
|
||||
{
|
||||
watch->hangup_callback = hangup_cb;
|
||||
update_poll_mask (watch);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
fd_set_error_callback (gint fd,
|
||||
WatchCallback error_cb)
|
||||
{
|
||||
Watch *watch = lookup_watch (fd);
|
||||
|
||||
g_return_if_fail (fd > 0);
|
||||
g_return_if_fail (watch != NULL);
|
||||
|
||||
if (watch->error_callback != error_cb)
|
||||
{
|
||||
watch->error_callback = error_cb;
|
||||
update_poll_mask (watch);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
fd_set_priority_callback (gint fd,
|
||||
WatchCallback priority_cb)
|
||||
{
|
||||
Watch *watch = lookup_watch (fd);
|
||||
|
||||
g_return_if_fail (fd > 0);
|
||||
g_return_if_fail (watch != NULL);
|
||||
|
||||
if (watch->priority_callback != priority_cb)
|
||||
{
|
||||
watch->priority_callback = priority_cb;
|
||||
update_poll_mask (watch);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
fd_remove_watch (gint fd)
|
||||
{
|
||||
Watch *watch = lookup_watch (fd);
|
||||
|
||||
g_return_if_fail (fd > 0);
|
||||
|
||||
if (!watch)
|
||||
return;
|
||||
|
||||
internal_remove_watch (watch);
|
||||
g_source_unref ((GSource *)watch);
|
||||
}
|
||||
|
||||
gboolean
|
||||
fd_is_watched (gint fd)
|
||||
{
|
||||
g_return_val_if_fail (fd > 0, FALSE);
|
||||
|
||||
if (lookup_watch (fd))
|
||||
return TRUE;
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
22
watch.h
Normal file
22
watch.h
Normal file
@ -0,0 +1,22 @@
|
||||
#include <glib.h>
|
||||
|
||||
/*
|
||||
* Watching file descriptors
|
||||
*/
|
||||
typedef void (* WatchCallback) (gpointer data);
|
||||
|
||||
void fd_add_watch (gint fd,
|
||||
gpointer data);
|
||||
void fd_set_read_callback (gint fd,
|
||||
WatchCallback read_cb);
|
||||
void fd_set_write_callback (gint fd,
|
||||
WatchCallback write_cb);
|
||||
void fd_set_hangup_callback (gint fd,
|
||||
WatchCallback hangup_cb);
|
||||
void fd_set_error_callback (gint fd,
|
||||
WatchCallback error_cb);
|
||||
void fd_set_priority_callback (gint fd,
|
||||
WatchCallback priority_cb);
|
||||
void fd_remove_watch (gint fd);
|
||||
gboolean fd_is_watched (gint fd);
|
||||
|
||||
Reference in New Issue
Block a user