[CTRACER]: Lots of improvements
1. We now use relayfs 2. ostra-cg is shipped and used in the Makefile 3. README.ctracer has all the details I followed README.ctracer and the result, in my workstation, was this one: http://oops.ghostprotocols.net:81/acme/dwarves/callgraphs/acme_eating_his_dog_food/6.html I've started on the 6th page as it is more interesting, having tcp and netlink callchains, don't be disappointed by some pages having just one level of nesting, that is just the lack of containers and aliases (to follow tcp_sock, skb->sk, etc) from the OSTRA days, but it's getting there! :-) Ah, it is collecting all the base types in the specified struct by doing a struct "view", i.e. trimming the struct to have just members that are "reducible" to basic types (int, long, char, signed and unsigned), then reorganizing it with the code introduced in pahole --reorganize to get it to the best layout possible, reducing the size of the entry probe points trace entries. Updated rpms are available at the usual place: http://oops.ghostprotocols.net:81/acme/dwarves/rpm/ Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
parent
d37f41df58
commit
ce9d7e11fe
@ -63,4 +63,8 @@ INSTALL(TARGETS codiff ctracer dtagnames pahole pdwtags
|
||||
pfunct pglobal prefcnt RUNTIME DESTINATION /usr/bin)
|
||||
INSTALL(TARGETS dwarves LIBRARY DESTINATION ${LIB_INSTALL_DIR})
|
||||
INSTALL(FILES dwarves.h DESTINATION /usr/include)
|
||||
INSTALL(FILES lib/Makefile lib/ctracer_jprobe.c DESTINATION ${LIB_INSTALL_DIR}/ctracer)
|
||||
INSTALL(FILES ostra/ostra-cg DESTINATION /usr/bin)
|
||||
INSTALL(FILES ostra/python/ostra.py DESTINATION ${LIB_INSTALL_DIR}/ctracer/python)
|
||||
INSTALL(FILES lib/Makefile lib/ctracer_jprobe.c
|
||||
lib/ctracer_relay.c lib/ctracer_relay.h
|
||||
DESTINATION ${LIB_INSTALL_DIR}/ctracer)
|
||||
|
4
MANIFEST
4
MANIFEST
@ -17,3 +17,7 @@ README.ctracer
|
||||
rpm/SPECS/dwarves.spec
|
||||
lib/Makefile
|
||||
lib/ctracer_jprobe.c
|
||||
lib/ctracer_relay.c
|
||||
lib/ctracer_relay.h
|
||||
ostra/ostra-cg
|
||||
ostra/python/ostra.py
|
||||
|
@ -19,8 +19,7 @@ Basic instructions to use ctracer:
|
||||
|
||||
mkdir foo
|
||||
cd foo
|
||||
ln -s /usr/lib/ctracer/ctracer_jprobe.c .
|
||||
ln -s /usr/lib/ctracer/Makefile .
|
||||
ln -s /usr/lib/ctracer/* .
|
||||
make CLASS=sock # to trace struct sock methods, this one is safe, try others
|
||||
# and tell me your horror (or success :-) ) story.
|
||||
|
||||
@ -30,25 +29,23 @@ make CLASS=sock # to trace struct sock methods, this one is safe, try others
|
||||
|
||||
insmod ctracer.ko
|
||||
|
||||
Then do a dmesg (this one was done in an Xorg session so tons of PF_LOCAL
|
||||
routines printk'ed):
|
||||
-> unix_peer_get: s=f6a8db80
|
||||
<- unix_peer_get
|
||||
<- unix_copy_addr
|
||||
-> unix_peer_get: s=f6a8db80
|
||||
<- unix_peer_get
|
||||
-> unix_peer_get: s=f6a8db80
|
||||
<- unix_peer_get
|
||||
-> unix_peer_get: s=f6a8db80
|
||||
<- unix_peer_get
|
||||
-> unix_peer_get: s=f6a8db80
|
||||
dmesg will show how many probes were successfully installed
|
||||
|
||||
Try keeping 'tail -f /var/log/messages' on an xterm and doing some more exciting
|
||||
network activity (or if tracing other struct, relevant activity for it).
|
||||
5. Do some related activity (ssh, in the above example should do)
|
||||
|
||||
When tired of the printk activity, just do:
|
||||
6. Make sure debugfs is mounted
|
||||
|
||||
rmmod ctracer
|
||||
[root@filo ~]# mount -t debugfs none_debugfs /sys/kernel/debug/
|
||||
|
||||
7. Get the log:
|
||||
|
||||
cat /sys/kernel/debug/ctracer0 > /tmp/ctracer.log
|
||||
|
||||
8. Generate the callgraph!
|
||||
|
||||
make callgraph
|
||||
|
||||
9. rmmod ctracer
|
||||
|
||||
Change the shipped Makefile accordingly to build a module for qemu or another test
|
||||
machine.
|
||||
|
359
ctracer.c
359
ctracer.c
@ -31,6 +31,22 @@ static struct cus *methods_cus;
|
||||
*/
|
||||
static struct cus *kprobes_cus;
|
||||
|
||||
/**
|
||||
* Mini class, the subset of the traced class that is collected at the probes
|
||||
*/
|
||||
|
||||
static struct class *mini_class;
|
||||
|
||||
/*
|
||||
* Directory where to generate source files
|
||||
*/
|
||||
static const char *src_dir = ".";
|
||||
|
||||
/*
|
||||
* Where to print the ctracer_methods.c file
|
||||
*/
|
||||
static FILE *fp_methods;
|
||||
|
||||
/*
|
||||
* List of definitions and forward declarations already emitted for
|
||||
* methods_cus and kprobes_cus, to avoid duplication.
|
||||
@ -129,19 +145,229 @@ static int cu_find_methods_iterator(struct cu *cu, void *cookie)
|
||||
return cu__for_each_tag(cu, find_methods_iterator, target, function__filter);
|
||||
}
|
||||
|
||||
static void class__remove_member(struct class *self, const struct cu *cu,
|
||||
struct class_member *member)
|
||||
{
|
||||
const size_t size = class_member__size(member, cu);
|
||||
/*
|
||||
* Is this the first member?
|
||||
*/
|
||||
if (member->tag.node.prev == &self->type.members) {
|
||||
self->type.size -= size;
|
||||
class__subtract_offsets_from(self, cu, member, size);
|
||||
} else {
|
||||
struct class_member *from_prev =
|
||||
list_entry(member->tag.node.prev,
|
||||
struct class_member, tag.node);
|
||||
if (member->hole + size >= cu->addr_size) {
|
||||
self->type.size -= size + member->hole;
|
||||
class__subtract_offsets_from(self, cu, member,
|
||||
size + member->hole);
|
||||
} else
|
||||
from_prev->hole += size + member->hole;
|
||||
}
|
||||
if (member->hole != 0)
|
||||
self->nr_holes--;
|
||||
list_del(&member->tag.node);
|
||||
class_member__delete(member);
|
||||
}
|
||||
|
||||
static size_t class__find_biggest_member_name(const struct class *self)
|
||||
{
|
||||
struct class_member *pos;
|
||||
size_t biggest_name_len = 0;
|
||||
|
||||
list_for_each_entry(pos, &self->type.members, tag.node) {
|
||||
const size_t len = strlen(pos->name);
|
||||
|
||||
if (len > biggest_name_len)
|
||||
biggest_name_len = len;
|
||||
}
|
||||
|
||||
return biggest_name_len;
|
||||
}
|
||||
|
||||
static void class__emit_class_state_collector(const struct class *self,
|
||||
const struct class *clone)
|
||||
{
|
||||
struct class_member *pos;
|
||||
size_t len = class__find_biggest_member_name(clone);
|
||||
|
||||
fprintf(fp_methods,
|
||||
"void ctracer__class_state(const void *from, void *to)\n"
|
||||
"{\n"
|
||||
"\tconst struct %s *obj = from;\n"
|
||||
"\tstruct %s *mini_obj = to;\n\n",
|
||||
class__name(self), class__name(clone));
|
||||
list_for_each_entry(pos, &clone->type.members, tag.node) {
|
||||
fprintf(fp_methods, "\tmini_obj->%-*s = obj->%s;\n",
|
||||
len, pos->name, pos->name);
|
||||
}
|
||||
fputs("}\n\n", fp_methods);
|
||||
}
|
||||
|
||||
static struct class *class__clone_base_types(const struct tag *tag_self,
|
||||
const struct cu *cu,
|
||||
const char *new_class_name)
|
||||
{
|
||||
struct class *self = tag__class(tag_self);
|
||||
struct class_member *pos, *next;
|
||||
struct class *clone = class__clone(self, new_class_name);
|
||||
|
||||
if (clone == NULL)
|
||||
return NULL;
|
||||
|
||||
class__find_holes(clone, cu);
|
||||
|
||||
list_for_each_entry_safe(pos, next, &clone->type.members, tag.node) {
|
||||
struct tag *member_type = cu__find_tag_by_id(cu, pos->tag.type);
|
||||
|
||||
if (member_type->tag != DW_TAG_base_type)
|
||||
class__remove_member(clone, cu, pos);
|
||||
}
|
||||
class__reorganize(clone, cu, 0, NULL);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converter to the legacy ostra tables, will be much improved in the future.
|
||||
*/
|
||||
static void emit_struct_member_table_entry(FILE *fp,
|
||||
int field, const char *name,
|
||||
int traced, const char *hooks)
|
||||
{
|
||||
fprintf(fp, "%u:%s:", field, name);
|
||||
if (traced)
|
||||
fprintf(fp, "yes:%%object->%s:u:%s:none\n", name, hooks);
|
||||
else
|
||||
fprintf(fp, "no:None:None:%s:dev_null\n", hooks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a converter to the ostra lebacy tables format, needef by
|
||||
* ostra-cg to preprocess the raw data collected from the debugfs/relay
|
||||
* channel.
|
||||
*/
|
||||
static int class__emit_ostra_converter(const struct tag *tag_self,
|
||||
const struct cu *cu)
|
||||
{
|
||||
const struct class *self = tag__class(tag_self);
|
||||
struct class_member *pos;
|
||||
struct type *type = &mini_class->type;
|
||||
int field = 0, first = 1;
|
||||
char filename[128];
|
||||
char parm_list[1024];
|
||||
char *p = parm_list;
|
||||
size_t n;
|
||||
size_t plen = sizeof(parm_list);
|
||||
FILE *fp_fields, *fp_converter;
|
||||
|
||||
snprintf(filename, sizeof(filename), "%s/%s.fields",
|
||||
src_dir, class__name(self));
|
||||
fp_fields = fopen(filename, "w");
|
||||
if (fp_fields == NULL) {
|
||||
fprintf(stderr, "ctracer: couldn't create %s\n", filename);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
snprintf(filename, sizeof(filename), "%s/ctracer2ostra.c", src_dir);
|
||||
|
||||
fp_converter = fopen(filename, "w");
|
||||
if (fp_converter == NULL) {
|
||||
fprintf(stderr, "ctracer: couldn't create %s\n", filename);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
fputs("#include <stdio.h>\n"
|
||||
"#include <string.h>\n"
|
||||
"#include \"ctracer_relay.h\"\n\n", fp_converter);
|
||||
class__print(class__tag(mini_class), cu, NULL, NULL, 0, fp_converter);
|
||||
|
||||
emit_struct_member_table_entry(fp_fields, field++, "action", 0,
|
||||
"entry,exit");
|
||||
emit_struct_member_table_entry(fp_fields, field++, "function_id", 0,
|
||||
"entry,exit");
|
||||
emit_struct_member_table_entry(fp_fields, field++, "object", 1,
|
||||
"entry,exit");
|
||||
|
||||
fputs("\n"
|
||||
"int main(void)\n"
|
||||
"{\n"
|
||||
"\twhile (1) {\n"
|
||||
"\t\tstruct trace_entry hdr;\n"
|
||||
"\t\tstruct ctracer__mini_sock obj;\n"
|
||||
"\n"
|
||||
"\t\tif (read(0, &hdr, sizeof(hdr)) != sizeof(hdr))\n"
|
||||
"\t\t\tbreak;\n"
|
||||
"\n"
|
||||
"\t\tfprintf(stdout, \"%u.%06u %c:%llu:%p\",\n"
|
||||
"\t\t\thdr.sec, hdr.usec,\n"
|
||||
"\t\t\thdr.probe_type ? 'o' : 'i',\n"
|
||||
"\t\t\thdr.function_id,\n"
|
||||
"\t\t\thdr.object);\n"
|
||||
"\n"
|
||||
"\t\tif (hdr.probe_type) {\n"
|
||||
"\t\t\tfputc('\\n', stdout);\n"
|
||||
"\t\t\tcontinue;\n"
|
||||
"\t\t}\n"
|
||||
"\n"
|
||||
"\t\tif (read(0, &obj, sizeof(obj)) != sizeof(obj))\n"
|
||||
"\t\t\tbreak;\n"
|
||||
"\t\tfprintf(stdout,\n"
|
||||
"\t\t\t\":",
|
||||
fp_converter);
|
||||
|
||||
list_for_each_entry(pos, &type->members, tag.node) {
|
||||
if (first)
|
||||
first = 0;
|
||||
else {
|
||||
fputc(':', fp_converter);
|
||||
n = snprintf(p, plen, ",\n\t\t\t ");
|
||||
plen -= n; p += n;
|
||||
}
|
||||
fprintf(fp_converter, "%%u");
|
||||
n = snprintf(p, plen, "obj.%s", pos->name);
|
||||
plen -= n; p += n;
|
||||
emit_struct_member_table_entry(fp_fields, field++,
|
||||
pos->name, 1, "entry");
|
||||
}
|
||||
fprintf(fp_converter,
|
||||
"\\n\",\n\t\t\t %s);\n"
|
||||
"\t}\n"
|
||||
"\treturn 0;\n"
|
||||
"}\n", parm_list);
|
||||
fclose(fp_fields);
|
||||
fclose(fp_converter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int class__emit_subset(const struct tag *tag_self, const struct cu *cu)
|
||||
{
|
||||
struct class *self = tag__class(tag_self);
|
||||
int err = -1;
|
||||
char mini_class_name[128];
|
||||
|
||||
snprintf(mini_class_name, sizeof(mini_class_name), "ctracer__mini_%s",
|
||||
class__name(self));
|
||||
|
||||
mini_class = class__clone_base_types(tag_self, cu, mini_class_name);
|
||||
if (mini_class == NULL)
|
||||
goto out;
|
||||
|
||||
class__print(class__tag(mini_class), cu, NULL, NULL, 0, fp_methods);
|
||||
fputc('\n', fp_methods);
|
||||
class__emit_class_state_collector(self, mini_class);
|
||||
err = 0;
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Emit the kprobes routine for one of the selected "methods", later we'll
|
||||
* put this into the 'kprobes' table, in cu_emit_kprobes_table_iterator.
|
||||
*
|
||||
* This marks the function entry, function__emit_kretprobes will emit the
|
||||
* probe for the function exit.
|
||||
*
|
||||
* For now it just printks the function name and the pointer, upcoming patches
|
||||
* will use relayfs, just like blktrace does, using the struct definition to
|
||||
* collect the specified subset of the struct members, just like OSTRA did,
|
||||
* see an example of post processing at:
|
||||
*
|
||||
* http://oops.ghostprotocols.net:81/dccp/ostra/delay_100ms_loss20percent_packet_size_256/
|
||||
*/
|
||||
static int function__emit_kprobes(struct function *self, const struct cu *cu,
|
||||
const struct tag *target)
|
||||
@ -153,8 +379,9 @@ static int function__emit_kprobes(struct function *self, const struct cu *cu,
|
||||
|
||||
snprintf(jprobe_name, sizeof(jprobe_name), "jprobe_entry__%s", name);
|
||||
ftype__snprintf(&self->proto, cu, bf, sizeof(bf), jprobe_name, 0, 0, 0);
|
||||
printf("static %s\n"
|
||||
"{\n", bf);
|
||||
fprintf(fp_methods,
|
||||
"static %s\n"
|
||||
"{\n", bf);
|
||||
|
||||
list_for_each_entry(pos, &self->proto.parms, tag.node) {
|
||||
struct tag *type = cu__find_tag_by_id(cu, pos->tag.type);
|
||||
@ -166,18 +393,21 @@ static int function__emit_kprobes(struct function *self, const struct cu *cu,
|
||||
if (type == NULL || type->id != target->id)
|
||||
continue;
|
||||
|
||||
printf("\tprintk(\"-> %s: %s=%%p\\n\", %s);\n",
|
||||
name, pos->name, pos->name);
|
||||
fprintf(fp_methods,
|
||||
"\tctracer__method_entry(%#llx, %s, %u);\n",
|
||||
self->proto.tag.id, pos->name,
|
||||
class__size(mini_class));
|
||||
break;
|
||||
}
|
||||
|
||||
printf("\n\tjprobe_return();\n"
|
||||
"\t/* NOTREACHED */%s\n}\n\n",
|
||||
self->proto.tag.type != 0 ? "\n\treturn 0;" : "");
|
||||
fprintf(fp_methods, "\tjprobe_return();\n"
|
||||
"\t/* NOTREACHED */%s\n}\n\n",
|
||||
self->proto.tag.type != 0 ? "\n\treturn 0;" : "");
|
||||
|
||||
printf("static struct jprobe jprobe__%s = {\n"
|
||||
"\t.kp = { .symbol_name = \"%s\", },\n"
|
||||
"\t.entry = (kprobe_opcode_t *)jprobe_entry__%s,\n"
|
||||
"};\n\n", name, name, name);
|
||||
fprintf(fp_methods, "static struct jprobe jprobe__%s = {\n"
|
||||
"\t.kp = { .symbol_name = \"%s\", },\n"
|
||||
"\t.entry = (kprobe_opcode_t *)jprobe_entry__%s,\n"
|
||||
"};\n\n", name, name, name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -195,7 +425,7 @@ static int cu_emit_kprobes_iterator(struct cu *cu, void *cookie)
|
||||
continue;
|
||||
pos->priv = (void *)1; /* Mark as visited, for the table iterator */
|
||||
cus__emit_ftype_definitions(methods_cus, cu,
|
||||
&pos->proto, stdout);
|
||||
&pos->proto, fp_methods);
|
||||
function__emit_kprobes(pos, cu, target);
|
||||
}
|
||||
|
||||
@ -213,8 +443,11 @@ static int cu_emit_kprobes_table_iterator(struct cu *cu, void *cookie __unused)
|
||||
struct function *pos;
|
||||
|
||||
list_for_each_entry(pos, &cu->tool_list, tool_node)
|
||||
if (pos->priv != NULL)
|
||||
printf("\t&jprobe__%s,\n", function__name(pos, cu));
|
||||
if (pos->priv != NULL) {
|
||||
const char *name = function__name(pos, cu);
|
||||
fprintf(fp_methods, "\t&jprobe__%s,\n", name);
|
||||
fprintf(cookie, "%llu:%s\n", pos->proto.tag.id, name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -237,16 +470,18 @@ static void function__emit_kretprobes(struct function *self,
|
||||
{
|
||||
const char *name = function__name(self, cu);
|
||||
|
||||
printf("static int kretprobe_handler__%s(struct kretprobe_instance *ri, "
|
||||
"struct pt_regs *regs)\n"
|
||||
"{\n"
|
||||
"\tprintk(\"<- %s\\n\");\n"
|
||||
"\treturn 0;\n"
|
||||
"}\n\n", name, name);
|
||||
printf("static struct kretprobe kretprobe__%s = {\n"
|
||||
"\t.kp = { .symbol_name = \"%s\", },\n"
|
||||
"\t.handler = (kretprobe_handler_t)kretprobe_handler__%s,\n"
|
||||
"};\n\n", name, name, name);
|
||||
fprintf(fp_methods,
|
||||
"static int kretprobe_handler__%s(struct kretprobe_instance *ri, "
|
||||
"struct pt_regs *regs)\n"
|
||||
"{\n"
|
||||
"\tctracer__method_exit(%#llx);\n"
|
||||
"\treturn 0;\n"
|
||||
"}\n\n", name, self->proto.tag.id);
|
||||
fprintf(fp_methods,
|
||||
"static struct kretprobe kretprobe__%s = {\n"
|
||||
"\t.kp = { .symbol_name = \"%s\", },\n"
|
||||
"\t.handler = (kretprobe_handler_t)kretprobe_handler__%s,\n"
|
||||
"};\n\n", name, name, name);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -281,7 +516,8 @@ static int cu_emit_kretprobes_table_iterator(struct cu *cu,
|
||||
|
||||
list_for_each_entry(pos, &cu->tool_list, tool_node)
|
||||
if (pos->priv != NULL)
|
||||
printf("\t&kretprobe__%s,\n", function__name(pos, cu));
|
||||
fprintf(fp_methods, "\t&kretprobe__%s,\n",
|
||||
function__name(pos, cu));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -301,9 +537,10 @@ static void emit_function_defs(const char *fn)
|
||||
|
||||
if (f != NULL) {
|
||||
cus__emit_ftype_definitions(kprobes_cus, cu,
|
||||
&tag__function(f)->proto, stdout);
|
||||
tag__print(f, cu, NULL, NULL, 0, stdout);
|
||||
puts(";\n");
|
||||
&tag__function(f)->proto,
|
||||
fp_methods);
|
||||
tag__print(f, cu, NULL, NULL, 0, fp_methods);
|
||||
fputs(";\n", fp_methods);
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,8 +553,8 @@ static void emit_struct_defs(const char *name)
|
||||
struct cu *cu;
|
||||
struct tag *c = cus__find_struct_by_name(kprobes_cus, &cu, name);
|
||||
if (c != NULL) {
|
||||
cus__emit_type_definitions(kprobes_cus, cu, c, stdout);
|
||||
type__emit(c, cu, NULL, NULL, stdout);
|
||||
cus__emit_type_definitions(kprobes_cus, cu, c, fp_methods);
|
||||
type__emit(c, cu, NULL, NULL, fp_methods);
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,7 +566,7 @@ static void emit_class_fwd_decl(const char *name)
|
||||
struct cu *cu;
|
||||
struct tag *c = cus__find_struct_by_name(kprobes_cus, &cu, name);
|
||||
if (c != NULL)
|
||||
cus__emit_fwd_decl(kprobes_cus, tag__type(c), stdout);
|
||||
cus__emit_fwd_decl(kprobes_cus, tag__type(c), fp_methods);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -339,6 +576,8 @@ static void emit_class_fwd_decl(const char *name)
|
||||
*/
|
||||
static void emit_module_preamble(void)
|
||||
{
|
||||
fputs("#include \"ctracer_relay.h\"\n", fp_methods);
|
||||
|
||||
emit_struct_defs("jprobe");
|
||||
emit_struct_defs("kretprobe");
|
||||
|
||||
@ -351,6 +590,7 @@ static void emit_module_preamble(void)
|
||||
|
||||
static struct option long_options[] = {
|
||||
{ "dir", required_argument, NULL, 'D' },
|
||||
{ "src_dir", required_argument, NULL, 'd' },
|
||||
{ "glob", required_argument, NULL, 'g' },
|
||||
{ "kprobes", required_argument, NULL, 'k' },
|
||||
{ "recursive", no_argument, NULL, 'r' },
|
||||
@ -363,6 +603,8 @@ static void usage(void)
|
||||
fprintf(stdout,
|
||||
"usage: ctracer [options] <filename> <class_name>\n"
|
||||
" where: \n"
|
||||
" -d, --src_dir generate source files in this "
|
||||
"directory\n"
|
||||
" -D, --dir load files in this directory\n"
|
||||
" -g, --glob file mask to load\n"
|
||||
" -k, --kprobes kprobes object file\n"
|
||||
@ -378,10 +620,14 @@ int main(int argc, char *argv[])
|
||||
char *class_name = NULL;
|
||||
struct tag *class;
|
||||
struct cu *cu;
|
||||
char functions_filename[PATH_MAX];
|
||||
char methods_filename[PATH_MAX];
|
||||
FILE *fp;
|
||||
|
||||
while ((option = getopt_long(argc, argv, "D:g:k:rh",
|
||||
while ((option = getopt_long(argc, argv, "d:D:g:k:rh",
|
||||
long_options, &option_index)) >= 0)
|
||||
switch (option) {
|
||||
case 'd': src_dir = optarg; break;
|
||||
case 'D': dirname = optarg; break;
|
||||
case 'g': glob = optarg; break;
|
||||
case 'k': kprobes_filename = optarg; break;
|
||||
@ -479,23 +725,48 @@ out_dwarf_err:
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
snprintf(functions_filename, sizeof(functions_filename),
|
||||
"%s/%s.functions", src_dir, class__name(tag__class(class)));
|
||||
fp = fopen(functions_filename, "w");
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "ctracer: couldn't create %s\n",
|
||||
functions_filename);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
snprintf(methods_filename, sizeof(methods_filename),
|
||||
"%s/ctracer_methods.c", src_dir);
|
||||
fp_methods = fopen(methods_filename, "w");
|
||||
if (fp_methods == NULL) {
|
||||
fprintf(stderr, "ctracer: couldn't create %s\n",
|
||||
methods_filename);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
emit_module_preamble();
|
||||
|
||||
cus__emit_type_definitions(methods_cus, cu, class, fp_methods);
|
||||
type__emit(class, cu, NULL, NULL, fp_methods);
|
||||
class__emit_subset(class, cu);
|
||||
class__emit_ostra_converter(class, cu);
|
||||
cus__for_each_cu(methods_cus, cu_find_methods_iterator,
|
||||
class_name, NULL);
|
||||
cus__for_each_cu(methods_cus, cu_emit_kprobes_iterator,
|
||||
class_name, NULL);
|
||||
cus__for_each_cu(methods_cus, cu_emit_kretprobes_iterator,
|
||||
NULL, NULL);
|
||||
puts("struct jprobe *ctracer__jprobes[] = {");
|
||||
|
||||
fputs("struct jprobe *ctracer__jprobes[] = {", fp_methods);
|
||||
cus__for_each_cu(methods_cus, cu_emit_kprobes_table_iterator,
|
||||
NULL, NULL);
|
||||
fp, NULL);
|
||||
/* Emit the sentinel */
|
||||
puts("\t(void *)0,\n};\n");
|
||||
puts("struct kretprobe *ctracer__kretprobes[] = {");
|
||||
fputs("\t(void *)0,\n};\n", fp_methods);
|
||||
fclose(fp);
|
||||
fputs("struct kretprobe *ctracer__kretprobes[] = {", fp_methods);
|
||||
cus__for_each_cu(methods_cus, cu_emit_kretprobes_table_iterator,
|
||||
NULL, NULL);
|
||||
/* Emit the sentinel */
|
||||
puts("\t(void *)0,\n};\n");
|
||||
fputs("\t(void *)0,\n};\n", fp_methods);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
34
lib/Makefile
Normal file
34
lib/Makefile
Normal file
@ -0,0 +1,34 @@
|
||||
obj-m := ctracer.o
|
||||
|
||||
ctracer-y := ctracer_methods.o ctracer_jprobe.o ctracer_relay.o
|
||||
|
||||
# Files generated that shall be removed upon make clean
|
||||
clean-files := ctracer_methods.c
|
||||
|
||||
CLASS=sock
|
||||
#KDIR := /home/acme/git/OUTPUT/qemu/linux-2.6/
|
||||
KDIR := /lib/modules/$(shell uname -r)/build
|
||||
PWD := $(shell pwd)
|
||||
|
||||
default:
|
||||
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
|
||||
|
||||
clean:
|
||||
rm -rf .*.mod.c .*o.cmd *.mod.c *.ko *.o \
|
||||
ctracer_methods.c Module.symvers .tmp_versions/ \
|
||||
$(CLASS).{fields,functions} ctracer2ostra*
|
||||
|
||||
$(src)/ctracer2ostra: ctracer_methods.c
|
||||
$(CC) $@.c -o $@
|
||||
|
||||
LOG=/tmp/ctracer.log
|
||||
callgraph: ctracer2ostra
|
||||
./ctracer2ostra < $(LOG) > $(LOG).ostra ; \
|
||||
rm -rf $(CLASS).callgraph ; \
|
||||
PYTHONPATH=python/ ostra-cg $(CLASS) $(LOG).ostra
|
||||
|
||||
$(obj)/ctracer_methods.o: ctracer_methods.c
|
||||
|
||||
# ctracer /home/acme/git/OUTPUT/qemu/linux-2.6/vmlinux --src_dir $(src) $(CLASS)
|
||||
$(src)/ctracer_methods.c:
|
||||
ctracer /usr/lib/debug/lib/modules/$(shell uname -r)/vmlinux --src_dir $(src) $(CLASS)
|
@ -4,6 +4,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/stddef.h>
|
||||
#include "ctracer_relay.h"
|
||||
|
||||
extern struct jprobe *ctracer__jprobes[];
|
||||
extern struct kretprobe *ctracer__kretprobes[];
|
||||
@ -12,6 +13,9 @@ static int __init ctracer__jprobe_init(void)
|
||||
{
|
||||
int i = 0, nj = 0, nr = 0;
|
||||
|
||||
if (ctracer__relay_init() != 0)
|
||||
return -1;
|
||||
|
||||
while (ctracer__jprobes[i] != NULL) {
|
||||
int err = register_jprobe(ctracer__jprobes[i]);
|
||||
if (err != 0)
|
||||
@ -44,6 +48,8 @@ static void __exit ctracer__jprobe_exit(void)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
ctracer__relay_exit();
|
||||
|
||||
while (ctracer__jprobes[i] != NULL) {
|
||||
if (ctracer__jprobes[i]->kp.nmissed != 0)
|
||||
pr_info("ctracer: entry: missed %lu %s\n",
|
||||
|
124
lib/ctracer_relay.c
Normal file
124
lib/ctracer_relay.c
Normal file
@ -0,0 +1,124 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/percpu.h>
|
||||
#include <linux/relay.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
static struct rchan *ctracer__rchan;
|
||||
|
||||
static int ctracer__subbuf_start_callback(struct rchan_buf *buf, void *subbuf,
|
||||
void *prev_subbuf,
|
||||
size_t prev_padding)
|
||||
{
|
||||
static int warned;
|
||||
if (!relay_buf_full(buf))
|
||||
return 1;
|
||||
if (!warned) {
|
||||
warned = 1;
|
||||
printk("relay_buf_full!\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dentry *ctracer__create_buf_file_callback(const char *filename,
|
||||
struct dentry *parent,
|
||||
int mode,
|
||||
struct rchan_buf *buf,
|
||||
int *is_global)
|
||||
{
|
||||
return debugfs_create_file(filename, mode, parent, buf,
|
||||
&relay_file_operations);
|
||||
}
|
||||
|
||||
static int ctracer__remove_buf_file_callback(struct dentry *dentry)
|
||||
{
|
||||
debugfs_remove(dentry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct rchan_callbacks ctracer__relay_callbacks = {
|
||||
.subbuf_start = ctracer__subbuf_start_callback,
|
||||
.create_buf_file = ctracer__create_buf_file_callback,
|
||||
.remove_buf_file = ctracer__remove_buf_file_callback,
|
||||
};
|
||||
|
||||
struct trace_entry {
|
||||
unsigned int sec;
|
||||
unsigned int usec:31;
|
||||
unsigned int probe_type:1; /* Entry or exit */
|
||||
const void *object;
|
||||
unsigned long long function_id;
|
||||
};
|
||||
|
||||
extern void ctracer__class_state(const void *from, void *to);
|
||||
|
||||
void ctracer__method_entry(const unsigned long long function_id,
|
||||
const void *object, const int state_len)
|
||||
{
|
||||
struct timeval now;
|
||||
|
||||
do_gettimeofday(&now);
|
||||
{
|
||||
unsigned long flags;
|
||||
void *t;
|
||||
|
||||
local_irq_save(flags);
|
||||
t = relay_reserve(ctracer__rchan,
|
||||
sizeof(struct trace_entry) + state_len);
|
||||
|
||||
if (t != NULL) {
|
||||
struct trace_entry *entry = t;
|
||||
|
||||
entry->sec = now.tv_sec;
|
||||
entry->usec = now.tv_usec;
|
||||
entry->probe_type = 0;
|
||||
entry->object = object;
|
||||
entry->function_id = function_id;
|
||||
ctracer__class_state(object, t + sizeof(*entry));
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
}
|
||||
|
||||
void ctracer__method_exit(unsigned long long function_id)
|
||||
{
|
||||
struct timeval now;
|
||||
|
||||
do_gettimeofday(&now);
|
||||
{
|
||||
unsigned long flags;
|
||||
void *t;
|
||||
|
||||
local_irq_save(flags);
|
||||
t = relay_reserve(ctracer__rchan, sizeof(struct trace_entry));
|
||||
|
||||
if (t != NULL) {
|
||||
struct trace_entry *entry = t;
|
||||
|
||||
entry->sec = now.tv_sec;
|
||||
entry->usec = now.tv_usec;
|
||||
entry->probe_type = 1;
|
||||
entry->object = NULL; /* need to find a way to get this */
|
||||
entry->function_id = function_id;
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
}
|
||||
|
||||
int ctracer__relay_init(void)
|
||||
{
|
||||
ctracer__rchan = relay_open("ctracer", NULL, 256 * 1024, 64,
|
||||
&ctracer__relay_callbacks);
|
||||
if (ctracer__rchan == NULL) {
|
||||
pr_info("ctracer: couldn't create the relay\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ctracer__relay_exit(void)
|
||||
{
|
||||
relay_close(ctracer__rchan);
|
||||
}
|
19
lib/ctracer_relay.h
Normal file
19
lib/ctracer_relay.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef _CTRACER_RELAY_H_
|
||||
#define _CTRACER_RELAY_H_ 1
|
||||
|
||||
struct trace_entry {
|
||||
unsigned int sec;
|
||||
unsigned int usec:31;
|
||||
unsigned int probe_type:1; /* Entry or exit */
|
||||
const void *object;
|
||||
unsigned long long function_id;
|
||||
};
|
||||
|
||||
void ctracer__method_entry(const unsigned long long function,
|
||||
const void *object, const int state_len);
|
||||
void ctracer__method_exit(unsigned long long function);
|
||||
|
||||
int ctracer__relay_init(void);
|
||||
void ctracer__relay_exit(void);
|
||||
|
||||
#endif
|
410
ostra/ostra-cg
Executable file
410
ostra/ostra-cg
Executable file
@ -0,0 +1,410 @@
|
||||
#!/usr/bin/python
|
||||
# ostra-cg - generate callgraphs from encoded trace
|
||||
#
|
||||
# Arnaldo Carvalho de Melo <acme@redhat.com>
|
||||
# <acme@ghostprotocols.net>
|
||||
#
|
||||
# Copyright (C) 2005, 2006, 2007 Arnaldo Carvalho de Melo
|
||||
# Licensed under the Open Software License version 1.1
|
||||
|
||||
import sys, datetime, os, ostra
|
||||
|
||||
class_def = None
|
||||
|
||||
ident = 0
|
||||
|
||||
verbose = False
|
||||
valid_html = False
|
||||
print_exits = True
|
||||
print_exit_details = False
|
||||
print_function_times = True
|
||||
gen_html = True
|
||||
html_file_seq = 0
|
||||
nr_lines_per_page = 256
|
||||
output_file = None
|
||||
callgraph = None
|
||||
print_nr_exit_points = False
|
||||
first_table_row = True
|
||||
plot_min_samples = 4
|
||||
plot = False
|
||||
tab_space = 10
|
||||
my_object = None
|
||||
|
||||
# @import url(file:///home/acme/git/ostra/ostra.css);
|
||||
html_style_import='''
|
||||
<style type="text/css">
|
||||
@import url(http://vger.kernel.org/~acme/ostra.css);
|
||||
</style>
|
||||
'''
|
||||
|
||||
def emit_html_page_sequence_links(page):
|
||||
global output_file
|
||||
|
||||
output_file.write("<div class=\"page_links\">")
|
||||
if page != 1:
|
||||
if page == 2:
|
||||
prev = "index"
|
||||
else:
|
||||
prev = str(page - 2)
|
||||
output_file.write("<a href=\"index.html\">Index</a> | ")
|
||||
output_file.write("<a href=\"%s.html\">Previous</a> | " % prev)
|
||||
output_file.write("<a href=\"%d.html\">Next</a> | " % page)
|
||||
output_file.write("<a href=\"changes.html\">Where fields changed</a> | ")
|
||||
output_file.write("<a href=\"methods/index.html\">Methods statistics</a> | ")
|
||||
output_file.write("<a href=\"last.html\">Last</a>\n")
|
||||
output_file.write("</div>")
|
||||
|
||||
def close_callgraph_file():
|
||||
if gen_html:
|
||||
output_file.write("</td></tr></table>\n")
|
||||
emit_html_page_sequence_links(html_file_seq)
|
||||
if valid_html:
|
||||
output_file.write('''
|
||||
<p>
|
||||
<a href="http://validator.w3.org/check?uri=referer"><img border="0"
|
||||
src="http://www.w3.org/Icons/valid-html401"
|
||||
alt="Valid HTML 4.01!" height="31" width="88"></a>
|
||||
</p>
|
||||
''')
|
||||
output_file.write("</body>\n</html>\n")
|
||||
|
||||
output_file.close()
|
||||
|
||||
def new_callgraph_file(traced_class):
|
||||
global html_file_seq, output_file, first_table_row
|
||||
|
||||
if not gen_html:
|
||||
if output_file == None:
|
||||
output_file = file("%s.txt" % callgraph, "w")
|
||||
return
|
||||
|
||||
first_table_row = True
|
||||
|
||||
if html_file_seq == 0:
|
||||
os.mkdir(callgraph)
|
||||
if output_file != None:
|
||||
output_file.close()
|
||||
filename = "index"
|
||||
help = '''
|
||||
<h3>Tracing struct %s methods (functions with a struct %s * argument)</h3>
|
||||
<h3>Click on the timestamps to see the object state</h3>
|
||||
<h3>Click on the functions to go to its definition in LXR (http://lxr.linux.no/)</h3>
|
||||
<h3 style=\"color:red;\">Red timestamps means the state changed</h3>
|
||||
''' % (traced_class, traced_class)
|
||||
else:
|
||||
close_callgraph_file()
|
||||
filename = str(html_file_seq)
|
||||
help = " "
|
||||
|
||||
output_file = file("%s/%s.html" % (callgraph, filename), "w")
|
||||
output_file.write('''
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>OSTRA Callgraph: %s, file %d</title>
|
||||
%s
|
||||
</head>
|
||||
<body>
|
||||
''' % (callgraph, html_file_seq, html_style_import))
|
||||
|
||||
html_file_seq += 1
|
||||
emit_html_page_sequence_links(html_file_seq)
|
||||
|
||||
output_file.write("\n%s\n<table class=\"listing\" cellspacing=\"0\" border=\"0\">\n" % help)
|
||||
|
||||
def trim_tstamp(tstamp):
|
||||
return str(tstamp).strip().lstrip('0').lstrip(':').lstrip('0').lstrip(':').lstrip('0').lstrip('.').lstrip('0')
|
||||
|
||||
def object_state():
|
||||
output = "<table class=\"state\" cellspacing=\"0\">"
|
||||
state_changed = False
|
||||
for field in class_def.fields.values():
|
||||
if not field.cg:
|
||||
continue
|
||||
value_changed_or_not_zero = False
|
||||
value = field.value
|
||||
if field.changed():
|
||||
state_changed = True
|
||||
last_value = field.last_value
|
||||
if field.table and last_value and field.table.has_key(int(last_value)):
|
||||
last_value = field.table[int(last_value)]
|
||||
transition = "%s -> " % last_value
|
||||
color = " class=\"odd\""
|
||||
value_changed_or_not_zero = True
|
||||
else:
|
||||
field_changed = False
|
||||
transition = ""
|
||||
color = ""
|
||||
if value != "0" and value != None:
|
||||
value_changed_or_not_zero = True
|
||||
|
||||
if value_changed_or_not_zero:
|
||||
if field.table and value and field.table.has_key(int(value)):
|
||||
value = field.table[int(value)]
|
||||
output = output.strip() + "<tr%s><td>%s</td><td class=\"right\">%s%s</td></tr>" % \
|
||||
(color, field, transition, value)
|
||||
output += "</table>"
|
||||
return (output, state_changed)
|
||||
|
||||
total_lines = 0
|
||||
|
||||
def tstamp_str():
|
||||
global total_lines, first_table_row
|
||||
|
||||
total_lines += 1
|
||||
|
||||
if gen_html:
|
||||
state, changed = object_state()
|
||||
if changed:
|
||||
anchor = "%d.%d" % (class_def.tstamp.seconds, class_def.tstamp.microseconds)
|
||||
anchor_color = " class=\"red\""
|
||||
else:
|
||||
anchor = ""
|
||||
anchor_color = ""
|
||||
if total_lines % 2 == 1:
|
||||
row_color = "odd"
|
||||
else:
|
||||
row_color = "evn"
|
||||
if first_table_row:
|
||||
close_last_tr = ""
|
||||
first_table_row = False
|
||||
else:
|
||||
close_last_tr = "</td></tr>\n"
|
||||
|
||||
return "%s<tr class=\"%s\"><td class=\"state\"><a name=\"%s\"%s>%04d.%06d<span>%s</span></a></td>" % \
|
||||
(close_last_tr, row_color, anchor, anchor_color,
|
||||
class_def.tstamp.seconds, class_def.tstamp.microseconds, state)
|
||||
else:
|
||||
return "%06d.%06d" % (class_def.tstamp.seconds, class_def.tstamp.microseconds)
|
||||
|
||||
def indent_str(indent, text):
|
||||
if gen_html:
|
||||
method = class_def.current_method()
|
||||
time_so_far = method.total_time.seconds * 10000 + method.total_time.microseconds
|
||||
tooltip = "%s: calls=%d, total time=%dus" % (method.name, method.calls, time_so_far)
|
||||
if class_def.fields["action"].value[0] == 'o':
|
||||
if class_def.fields.has_key("exit_point"):
|
||||
tooltip += ", exit point=%d" % (int(class_def.fields["exit_point"].value) + 1)
|
||||
else:
|
||||
text = "<a class=\"lxr\" href=\"http://lxr.linux.no/ident?i=%s\">%s</a>" % (method.name, text)
|
||||
|
||||
return "<td title=\"%s\">%s%s" % (tooltip, " " * tab_space * indent, text)
|
||||
else:
|
||||
return "%s%s" % ("\t" * ident, text)
|
||||
|
||||
def function_time_str(time):
|
||||
if gen_html:
|
||||
if class_def.current_method().print_return_value:
|
||||
ret_value = "%s" % class_def.fields["return_value"].value
|
||||
else:
|
||||
ret_value = "0"
|
||||
if ret_value == "0":
|
||||
ret_value = ""
|
||||
else:
|
||||
ret_value=" title=\"returned %s\"" % ret_value
|
||||
return "</td><td%s class=\"time\">%sus" % (ret_value, time)
|
||||
else:
|
||||
return " %sus\n" % time
|
||||
|
||||
previous_was_entry = False
|
||||
nr_lines = 0
|
||||
|
||||
def process_record():
|
||||
global ident, previous_was_entry, nr_lines
|
||||
|
||||
if gen_html:
|
||||
nr_lines += 1
|
||||
if nr_lines > nr_lines_per_page:
|
||||
if ident == 0 or nr_lines > nr_lines_per_page * 5:
|
||||
new_callgraph_file(traced_class)
|
||||
nr_lines = 0
|
||||
|
||||
method = class_def.current_method()
|
||||
|
||||
if class_def.fields["action"].value[0] == 'i':
|
||||
output = "%s()" % method.name
|
||||
|
||||
if print_exits and previous_was_entry:
|
||||
if gen_html:
|
||||
last_open = " {</td><td> "
|
||||
else:
|
||||
last_open = " {\n"
|
||||
else:
|
||||
last_open = ""
|
||||
output_file.write("%s%s %s" % (last_open, tstamp_str(), indent_str(ident, output.strip())))
|
||||
if not print_exits:
|
||||
output_file.write("\n")
|
||||
|
||||
ident += 1
|
||||
|
||||
method.calls += 1
|
||||
method.last_tstamp = class_def.tstamp
|
||||
previous_was_entry = True
|
||||
else:
|
||||
if not method.last_tstamp:
|
||||
method.last_tstamp = class_def.tstamp
|
||||
tstamp_delta = class_def.tstamp - method.last_tstamp
|
||||
if tstamp_delta < datetime.timedelta():
|
||||
tstamp_delta = datetime.timedelta()
|
||||
method.total_time += tstamp_delta
|
||||
|
||||
|
||||
if ident > 0:
|
||||
ident -= 1
|
||||
if print_exits:
|
||||
if print_exit_details:
|
||||
exit_point = int(class_def.fields["exit_point"].value) + 1
|
||||
if class_def.last_method.name != method.name:
|
||||
output_file.write("%s %s" % (tstamp_str(), indent_str(ident, "}")))
|
||||
if print_exit_details:
|
||||
output_file.write(" EXIT #%d (%s)" % (exit_point, method.name))
|
||||
else:
|
||||
if print_exit_details:
|
||||
output_file.write("EXIT #%d" % exit_point)
|
||||
|
||||
function_time = trim_tstamp(tstamp_delta)
|
||||
if len(function_time) == 0:
|
||||
function_time = "0"
|
||||
if print_exits:
|
||||
if print_function_times:
|
||||
output_file.write(function_time_str(function_time))
|
||||
else:
|
||||
output_file.write("\n")
|
||||
|
||||
if print_nr_exit_points:
|
||||
if method.exits.has_key(exit_point):
|
||||
method.exits[exit_point] += 1
|
||||
else:
|
||||
method.exits[exit_point] = 1
|
||||
|
||||
previous_was_entry = False
|
||||
|
||||
return html_file_seq - 1
|
||||
|
||||
def print_where_fields_changed():
|
||||
f = file("%s/changes.html" % callgraph, "w")
|
||||
f.write('''
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>OSTRA Callgraph: %s, Where the Fields Changed</title>
|
||||
%s
|
||||
</head>
|
||||
<body>
|
||||
<h3>Click on the values to go to where it was changed</h3>
|
||||
<h3>Click on the field names to see a plotting of its value over time</h3>
|
||||
''' % (callgraph, html_style_import))
|
||||
output_file.write("<div class=\"page_links\">")
|
||||
f.write("<a href=\"index.html\">Index</a>\n")
|
||||
f.write("<a href=\"%d.html\">Last</a>\n" % (html_file_seq - 1))
|
||||
f.write("<table border=\"1\">")
|
||||
|
||||
max_samples = 50
|
||||
|
||||
for key in class_def.fields.keys():
|
||||
fields = class_def.fields[key]
|
||||
changes = fields.changes
|
||||
|
||||
changes_str=""
|
||||
link_pre=""
|
||||
link_pos=""
|
||||
if len(changes) == 0:
|
||||
changes_str="Unchanged</td></tr>\n"
|
||||
elif plot and len(changes) >= plot_min_samples and fields.plot_fmt != "dev_null":
|
||||
link_pre="<a href=\"%s.png\">" % key
|
||||
link_pos="</a>"
|
||||
|
||||
f.write("<tr><td valign=\"top\">%s%s%s</td><td>%s" % (link_pre, key, link_pos, changes_str))
|
||||
if len(changes) == 0:
|
||||
continue
|
||||
|
||||
f.write("<table border=\"0\">\n")
|
||||
nr_samples = 0
|
||||
for change in changes:
|
||||
nr_samples += 1
|
||||
if nr_samples <= max_samples:
|
||||
if change.seq == 0:
|
||||
filename="index"
|
||||
else:
|
||||
filename = str(change.seq)
|
||||
f.write("<tr><td><a href=%s.html#%d.%d>%s</td></tr>" % \
|
||||
(filename, change.tstamp.seconds, change.tstamp.microseconds, change.value))
|
||||
|
||||
if nr_samples > max_samples:
|
||||
f.write("<tr><td>Only %d samples out of %d were printed</td></tr>" % (max_samples, nr_samples))
|
||||
|
||||
f.write("</table>\n</td></tr>\n")
|
||||
|
||||
f.write("</table>")
|
||||
output_file.write("</div>")
|
||||
f.write("</body>\n</html>\n")
|
||||
f.close()
|
||||
os.symlink("changes.html", "%s/%d.html" % (callgraph, html_file_seq))
|
||||
os.symlink("%d.html" % (html_file_seq - 1), "%s/last.html" % callgraph)
|
||||
|
||||
|
||||
def method_stats(class_def, callgraph):
|
||||
os.mkdir("%s/methods" % callgraph)
|
||||
f = file("%s/methods/index.html" % callgraph, "w")
|
||||
f.write('''
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>OSTRA Callgraph: %s, Methods Statistics</title>
|
||||
%s
|
||||
</head>
|
||||
<body>
|
||||
<h3>Click on the methods names to see a plotting of the times for each call</h3>
|
||||
''' % (callgraph, html_style_import))
|
||||
|
||||
if plot:
|
||||
class_def.plot_methods(callgraph)
|
||||
f.write("<table border=\"1\">")
|
||||
for method in class_def.methods.values():
|
||||
changes_str=""
|
||||
link_pre=""
|
||||
link_pos=""
|
||||
if len(method.times) < 4:
|
||||
changes_str="Less than 4 calls</td></tr>\n"
|
||||
else:
|
||||
if plot:
|
||||
link_pre="<a href=\"%s.png\">" % method.name
|
||||
link_pos="</a>"
|
||||
changes_str="%d calls</td></tr>\n" % len(method.times)
|
||||
|
||||
f.write("<tr><td valign=\"top\">%s%s%s</td><td>%s" % \
|
||||
(link_pre, method.name, link_pos, changes_str))
|
||||
f.write("</table>")
|
||||
f.write("</body>\n</html>\n")
|
||||
f.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) not in [ 3, 4 ]:
|
||||
print "usage: ostra-cg <traced_class> <encoded_trace> [object]"
|
||||
sys.exit(1)
|
||||
|
||||
gen_html = True
|
||||
traced_class = sys.argv[1]
|
||||
callgraph = "%s.callgraph" % traced_class
|
||||
encoded_trace = sys.argv[2]
|
||||
if len(sys.argv) == 4:
|
||||
my_object = sys.argv[3]
|
||||
if my_object == "none":
|
||||
my_object = None
|
||||
plot = True
|
||||
|
||||
class_def = ostra.class_definition(class_def_file = "%s.fields" % traced_class,
|
||||
class_methods_file = "%s.functions" % traced_class)
|
||||
new_callgraph_file(traced_class)
|
||||
class_def.parse_file(encoded_trace, verbose = verbose,
|
||||
process_record = process_record,
|
||||
my_object = my_object)
|
||||
if gen_html:
|
||||
print_where_fields_changed()
|
||||
close_callgraph_file()
|
||||
method_stats(class_def, callgraph)
|
||||
if plot:
|
||||
ostra.plot(class_def, callgraph)
|
392
ostra/python/ostra.py
Executable file
392
ostra/python/ostra.py
Executable file
@ -0,0 +1,392 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
class trace_points:
|
||||
def __init__(self, hooks):
|
||||
self.entry = "entry" in hooks
|
||||
self.exit = "exit" in hooks
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.__dict__.values())
|
||||
__str__ = __repr__
|
||||
|
||||
class change_point:
|
||||
def __init__(self, tstamp, value, seq):
|
||||
self.tstamp = tstamp
|
||||
self.value = value
|
||||
self.seq = seq
|
||||
|
||||
class class_field:
|
||||
def __init__(self, line, class_def_file):
|
||||
field, self.name, cgtraced, self.grab_expr, \
|
||||
self.collector_fmt, hooks, self.plot_fmt = line.strip().split(':')
|
||||
|
||||
self.field = int(field)
|
||||
self.cg = cgtraced == "yes"
|
||||
self.hooks = trace_points(hooks.split(','))
|
||||
self.value = None
|
||||
self.last_value = None
|
||||
self.changes = []
|
||||
self._load_text_table(class_def_file)
|
||||
|
||||
def _text_table_tokenizer(self, line):
|
||||
tokens = line.split(":")
|
||||
return int(tokens[0]), tokens[1][:-1]
|
||||
|
||||
def _load_text_table(self, class_def_file):
|
||||
try:
|
||||
f = file("%s.%s.table" % (class_def_file, self.name))
|
||||
except:
|
||||
self.table = {}
|
||||
return
|
||||
self.table = dict([self._text_table_tokenizer(line) for line in f.readlines()])
|
||||
f.close()
|
||||
|
||||
def set_last_value(self, tstamp, seq):
|
||||
if self.value != None:
|
||||
if self.cg and self.changed():
|
||||
self.changes.append(change_point(tstamp, self.value, seq))
|
||||
self.last_value = self.value
|
||||
|
||||
def changed(self):
|
||||
return self.value != None and self.value != self.last_value
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
__str__ = __repr__
|
||||
|
||||
class class_method:
|
||||
def __init__(self, line):
|
||||
fields = line.strip().split(':')
|
||||
self.function_id = fields[0]
|
||||
self.name = fields[1]
|
||||
self.print_return_value = fields[-1]
|
||||
self.function_id = int(self.function_id)
|
||||
self.print_return_value = self.print_return_value == "yes"
|
||||
self.calls = 0
|
||||
self.total_time = timedelta()
|
||||
self.last_tstamp = None
|
||||
self.times = []
|
||||
self.exits = {}
|
||||
|
||||
def begin(self, tstamp):
|
||||
self.calls += 1
|
||||
self.last_tstamp = tstamp
|
||||
|
||||
def end(self, tstamp):
|
||||
tstamp_delta = tstamp - self.last_tstamp
|
||||
if tstamp_delta < timedelta():
|
||||
tstamp_delta = timedelta()
|
||||
|
||||
self.total_time += tstamp_delta
|
||||
self.times.append(tstamp_delta.seconds * 1000000 + tstamp_delta.microseconds)
|
||||
|
||||
def plot(self, directory, entries, samples, nr_samples, verbose = False):
|
||||
from matplotlib import use as muse
|
||||
muse('Agg')
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.ticker import FuncFormatter, FixedFormatter, LinearLocator
|
||||
from matplotlib.mlab import std as std_deviation
|
||||
from matplotlib.mlab import mean
|
||||
from time import asctime
|
||||
|
||||
yfont = { 'fontname' : 'Bitstream Vera Sans',
|
||||
'color' : 'r',
|
||||
'fontsize' : 8 }
|
||||
|
||||
xfont = { 'fontname' : 'Bitstream Vera Sans',
|
||||
'color' : 'b',
|
||||
'fontsize' : 8 }
|
||||
|
||||
titlefont = { 'fontname' : 'Bitstream Vera Sans',
|
||||
'color' : 'g',
|
||||
'fontweight' : 'bold',
|
||||
'fontsize' : 10 }
|
||||
|
||||
inches = 0.00666667
|
||||
width = 950 * inches
|
||||
height = 680 * inches
|
||||
|
||||
fig = Figure(figsize = (width, height))
|
||||
canvas = FigureCanvas(fig)
|
||||
ax = fig.add_subplot(111)
|
||||
ax.grid(False)
|
||||
xtickfontsize = 5
|
||||
ytickfontsize = 5
|
||||
|
||||
plot_type = 'b-'
|
||||
field_mean = mean(samples)
|
||||
yaxis_plot_fmt = FuncFormatter(pylab_formatter_ms)
|
||||
|
||||
ax.plot(entries, samples, "b-")
|
||||
|
||||
ax.set_xlabel("samples", xfont)
|
||||
ax.set_ylabel("time", yfont)
|
||||
|
||||
for label in ax.get_xticklabels():
|
||||
label.set(fontsize = xtickfontsize)
|
||||
for label in ax.get_yticklabels():
|
||||
label.set(fontsize = ytickfontsize)
|
||||
|
||||
ax.yaxis.set_major_formatter(yaxis_plot_fmt)
|
||||
ax.set_title("%d %s samples (%s)" % (nr_samples, self.name, asctime()), titlefont)
|
||||
canvas.print_figure("%s/methods/%s.png" % (directory, self.name))
|
||||
del fig, canvas, ax
|
||||
|
||||
class class_definition:
|
||||
def __init__(self, class_def_file = None, class_methods_file = None):
|
||||
self.fields = {}
|
||||
self.methods = {}
|
||||
self.tstamp = None
|
||||
self.last_tstamp = None
|
||||
self.last_method = None
|
||||
self.epoch = None
|
||||
|
||||
if class_def_file:
|
||||
f = file(class_def_file)
|
||||
for line in f.readlines():
|
||||
field = class_field(line, class_def_file)
|
||||
self.fields[field.name] = field
|
||||
f.close()
|
||||
|
||||
if class_methods_file:
|
||||
f = file(class_methods_file)
|
||||
self.methods = dict([self._method_tokenizer(line) for line in f.readlines()])
|
||||
f.close()
|
||||
|
||||
def _method_tokenizer(self, line):
|
||||
method = class_method(line)
|
||||
return method.function_id, method
|
||||
|
||||
def set_last_values(self, seq = 0):
|
||||
self.last_method = self.current_method()
|
||||
for field in self.fields.values():
|
||||
field.set_last_value(self.tstamp, seq)
|
||||
self.last_tstamp = self.tstamp
|
||||
|
||||
def parse_record(self, line):
|
||||
tstamp, record = line[:-1].split(' ', 1)
|
||||
line_fields = record.split(':')
|
||||
|
||||
sec, usec = tstamp.split('.', 1)
|
||||
self.tstamp = timedelta(seconds = int(sec), microseconds = int(usec))
|
||||
if self.epoch == None:
|
||||
self.epoch = self.tstamp
|
||||
self.tstamp -= self.epoch
|
||||
|
||||
action = line_fields[0][0]
|
||||
nr_fields = len(line_fields)
|
||||
for field in self.fields.values():
|
||||
if field.field >= nr_fields or \
|
||||
(action == 'i' and not field.hooks.entry) or \
|
||||
(action == 'o' and not field.hooks.exit):
|
||||
field.value = None
|
||||
continue
|
||||
field.value = line_fields[field.field]
|
||||
|
||||
def parse_file(self, filename, process_record = None, verbose = False,
|
||||
my_object = None):
|
||||
f = file(filename)
|
||||
current_object = None
|
||||
object_stack = []
|
||||
|
||||
if verbose:
|
||||
nr_lines = 0
|
||||
|
||||
while True:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
if verbose:
|
||||
nr_lines += 1
|
||||
print "\r%d" % nr_lines,
|
||||
|
||||
self.parse_record(line)
|
||||
|
||||
method = self.current_method()
|
||||
# print method.name
|
||||
if my_object:
|
||||
if self.fields["action"].value[0] == 'i':
|
||||
current_object = self.fields["object"].value
|
||||
object_stack.append(current_object)
|
||||
else:
|
||||
current_object = object_stack.pop()
|
||||
|
||||
if current_object != my_object:
|
||||
continue
|
||||
|
||||
if self.fields["action"].value[0] == 'i':
|
||||
method.begin(self.tstamp)
|
||||
else:
|
||||
method.end(self.tstamp)
|
||||
seq = 0
|
||||
if process_record:
|
||||
seq = process_record()
|
||||
self.set_last_values(seq)
|
||||
|
||||
f.close()
|
||||
if verbose:
|
||||
print
|
||||
|
||||
def current_method(self):
|
||||
return self.methods[int(self.fields["function_id"].value)]
|
||||
|
||||
def plot_methods(self, callgraph, verbose = False):
|
||||
for current_method in self.methods.values():
|
||||
nr_samples = len(current_method.times)
|
||||
if nr_samples < 4:
|
||||
continue
|
||||
|
||||
if verbose:
|
||||
print "plot_methods: plotting %s method (%d samples)" % \
|
||||
(current_method.name, nr_samples)
|
||||
|
||||
entries = [float("%d.0" % entry) for entry in range(nr_samples)]
|
||||
samples = current_method.times
|
||||
current_method.plot(callgraph, entries, samples,
|
||||
nr_samples, verbose)
|
||||
|
||||
def pylab_formatter_kbps(x):
|
||||
mb = 1024 * 1024
|
||||
if x > mb:
|
||||
return "%d,%d Mbps" % (x / mb, x % mb)
|
||||
else:
|
||||
return "%d,%d Kbps" % (x / 1024, x % 1024)
|
||||
|
||||
def pylab_formatter_ms(x, pos = 0):
|
||||
ms = x / 1000
|
||||
us = x % 1000
|
||||
s = "%d" % ms
|
||||
if us > 0:
|
||||
s += ".%03d" % us
|
||||
s = s.rstrip('0')
|
||||
s += "ms"
|
||||
|
||||
return s
|
||||
|
||||
def pylab_formatter(x, pos = 0):
|
||||
if current_plot_fmt == "kbps":
|
||||
return pylab_formatter_kbps(x)
|
||||
elif current_plot_fmt == "ms":
|
||||
return pylab_formatter_ms(x)
|
||||
else:
|
||||
return "%s" % str(int(x))
|
||||
|
||||
def plot_field(name, directory, tstamps, samples, nr_samples, plot_fmt = None,
|
||||
table = None, verbose = False):
|
||||
global current_plot_fmt
|
||||
|
||||
from matplotlib import use as muse
|
||||
muse('Agg')
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.ticker import FuncFormatter, FixedFormatter, LinearLocator
|
||||
from matplotlib.mlab import std as std_deviation
|
||||
from matplotlib.mlab import mean
|
||||
from time import asctime
|
||||
|
||||
yfont = { 'fontname' : 'Bitstream Vera Sans',
|
||||
'color' : 'r',
|
||||
'fontsize' : 8 }
|
||||
|
||||
xfont = { 'fontname' : 'Bitstream Vera Sans',
|
||||
'color' : 'b',
|
||||
'fontsize' : 8 }
|
||||
|
||||
titlefont = { 'fontname' : 'Bitstream Vera Sans',
|
||||
'color' : 'g',
|
||||
'fontweight' : 'bold',
|
||||
'fontsize' : 10 }
|
||||
|
||||
inches = 0.00666667
|
||||
width = 950 * inches
|
||||
height = 680 * inches
|
||||
|
||||
fig = Figure(figsize = (width, height))
|
||||
canvas = FigureCanvas(fig)
|
||||
ax = fig.add_subplot(111)
|
||||
ax.grid(False)
|
||||
xtickfontsize = 5
|
||||
ytickfontsize = 5
|
||||
|
||||
current_plot_fmt = plot_fmt
|
||||
field_mean = None
|
||||
|
||||
plot_type = 'b-'
|
||||
if current_plot_fmt == "filter_dev":
|
||||
std = std_deviation(samples) * 2
|
||||
if verbose:
|
||||
print "filter_dev(%s) std=%d" % (name, std)
|
||||
for i in range(nr_samples):
|
||||
if samples[i] > std:
|
||||
if verbose:
|
||||
print "%s: filtering out %d" % (name, samples[i])
|
||||
samples[i] = 0
|
||||
field_mean = mean(samples)
|
||||
yaxis_plot_fmt = FuncFormatter(pylab_formatter)
|
||||
elif current_plot_fmt == "table":
|
||||
ax.grid(True)
|
||||
plot_type = 'bo-'
|
||||
max_value = max(samples)
|
||||
without_zero = 1
|
||||
if table.has_key(0):
|
||||
without_zero = 0
|
||||
max_value += 1
|
||||
ax.yaxis.set_major_locator(LinearLocator(max_value))
|
||||
tstamps = range(nr_samples)
|
||||
seq = [ " " ] * max_value
|
||||
for key in table.keys():
|
||||
if key in samples:
|
||||
seq[key - without_zero] = "%s(%d)" % (table[key], key)
|
||||
ytickfontsize = 4
|
||||
yaxis_plot_fmt = FixedFormatter(seq)
|
||||
else:
|
||||
field_mean = mean(samples)
|
||||
yaxis_plot_fmt = FuncFormatter(pylab_formatter)
|
||||
|
||||
ax.plot(tstamps, samples, plot_type)
|
||||
|
||||
ax.set_xlabel("time", xfont)
|
||||
yname = name
|
||||
if field_mean:
|
||||
yname += " (mean=%s)" % pylab_formatter(field_mean)
|
||||
ax.set_ylabel(yname, yfont)
|
||||
|
||||
for label in ax.get_xticklabels():
|
||||
label.set(fontsize = xtickfontsize)
|
||||
for label in ax.get_yticklabels():
|
||||
label.set(fontsize = ytickfontsize)
|
||||
|
||||
ax.yaxis.set_major_formatter(yaxis_plot_fmt)
|
||||
ax.set_title("%d %s samples (%s)" % (nr_samples, name, asctime()), titlefont)
|
||||
canvas.print_figure("%s/%s.png" % (directory, name))
|
||||
del fig, canvas, ax
|
||||
|
||||
def plot(class_def, callgraph, verbose = False):
|
||||
for current_field in class_def.fields.values():
|
||||
nr_samples = len(current_field.changes)
|
||||
if nr_samples < 4:
|
||||
continue
|
||||
|
||||
if verbose:
|
||||
print "ostra-plot: plotting %s field (%d samples)" % (current_field.name, nr_samples)
|
||||
|
||||
tstamps = [float("%d.%06d" % (entry.tstamp.seconds, entry.tstamp.microseconds)) \
|
||||
for entry in current_field.changes]
|
||||
try:
|
||||
samples = [int(entry.value) for entry in current_field.changes]
|
||||
except:
|
||||
continue
|
||||
plot_field(current_field.name, callgraph, tstamps, samples,
|
||||
nr_samples, current_field.plot_fmt,
|
||||
current_field.table, verbose)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
c = class_definition(sys.argv[1], sys.argv[2])
|
||||
for field in c.fields.values():
|
||||
print "%s: %s" % (field, field.table)
|
||||
for method in c.methods.values():
|
||||
print "%d: %s" % (method.function_id, method.name)
|
@ -3,7 +3,7 @@
|
||||
|
||||
Name: dwarves
|
||||
Version: 0
|
||||
Release: 13
|
||||
Release: 14
|
||||
License: GPL
|
||||
Summary: Dwarf Tools
|
||||
Group: Base
|
||||
@ -72,9 +72,14 @@ rm -rf %{buildroot}
|
||||
%{_bindir}/pfunct
|
||||
%{_bindir}/pglobal
|
||||
%{_bindir}/prefcnt
|
||||
%{_bindir}/ostra-cg
|
||||
%dir %{_libdir}/ctracer
|
||||
%{_libdir}/ctracer/Makefile
|
||||
%{_libdir}/ctracer/ctracer_jprobe.c
|
||||
%{_libdir}/ctracer/ctracer_relay.c
|
||||
%{_libdir}/ctracer/ctracer_relay.h
|
||||
%dir %{_libdir}/ctracer/python
|
||||
%{_libdir}/ctracer/python/ostra.py*
|
||||
|
||||
%files -n %{libname}%{libver}
|
||||
%defattr(0644,root,root,0755)
|
||||
@ -87,7 +92,15 @@ rm -rf %{buildroot}
|
||||
|
||||
%changelog
|
||||
* Fri Feb 2 2007 Arnaldo Carvalho de Melo <acme@redhat.com>
|
||||
- 4ab3403e3b72a18fbd5fe15630e76605264ba18c
|
||||
- d37f41df58c375412badf827e24dfc346cea2ff2
|
||||
- ostra-cg
|
||||
- relay/debugfs
|
||||
- mini-structs
|
||||
- ctracer2ostra
|
||||
- All this in the Makefile
|
||||
|
||||
* Fri Feb 2 2007 Arnaldo Carvalho de Melo <acme@redhat.com>
|
||||
- b7cad1782d683571ffb2601b429ab151bddad5d7
|
||||
- pglobal, by Davi Arnaut
|
||||
- pahole --show_reorg_steps
|
||||
- Reorganize bitfields in pahole --reorganize
|
||||
|
Loading…
Reference in New Issue
Block a user