diff --git a/CMakeLists.txt b/CMakeLists.txt index fae3422..d763b24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/MANIFEST b/MANIFEST index e41eb01..67044b5 100644 --- a/MANIFEST +++ b/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 diff --git a/README.ctracer b/README.ctracer index ff4ba00..f008e0f 100644 --- a/README.ctracer +++ b/README.ctracer @@ -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. diff --git a/ctracer.c b/ctracer.c index 7cfcffe..27358b7 100644 --- a/ctracer.c +++ b/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 \n" + "#include \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] \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; } diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..852561e --- /dev/null +++ b/lib/Makefile @@ -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) diff --git a/lib/ctracer_jprobe.c b/lib/ctracer_jprobe.c index e835c9b..d980f8b 100644 --- a/lib/ctracer_jprobe.c +++ b/lib/ctracer_jprobe.c @@ -4,6 +4,7 @@ #include #include #include +#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", diff --git a/lib/ctracer_relay.c b/lib/ctracer_relay.c new file mode 100644 index 0000000..533ff85 --- /dev/null +++ b/lib/ctracer_relay.c @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/lib/ctracer_relay.h b/lib/ctracer_relay.h new file mode 100644 index 0000000..74d2bfa --- /dev/null +++ b/lib/ctracer_relay.h @@ -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 diff --git a/ostra/ostra-cg b/ostra/ostra-cg new file mode 100755 index 0000000..ddffb89 --- /dev/null +++ b/ostra/ostra-cg @@ -0,0 +1,410 @@ +#!/usr/bin/python +# ostra-cg - generate callgraphs from encoded trace +# +# Arnaldo Carvalho de Melo +# +# +# 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=''' + +''' + +def emit_html_page_sequence_links(page): + global output_file + + output_file.write("
") + if page != 1: + if page == 2: + prev = "index" + else: + prev = str(page - 2) + output_file.write("Index | ") + output_file.write("Previous | " % prev) + output_file.write("Next | " % page) + output_file.write("Where fields changed | ") + output_file.write("Methods statistics | ") + output_file.write("Last\n") + output_file.write("
") + +def close_callgraph_file(): + if gen_html: + output_file.write("\n") + emit_html_page_sequence_links(html_file_seq) + if valid_html: + output_file.write(''' +

+Valid HTML 4.01! +

+''') + output_file.write("\n\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 = ''' +

Tracing struct %s methods (functions with a struct %s * argument)

+

Click on the timestamps to see the object state

+

Click on the functions to go to its definition in LXR (http://lxr.linux.no/)

+

Red timestamps means the state changed

+''' % (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(''' + + + +OSTRA Callgraph: %s, file %d +%s + + +''' % (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\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 = "
" + 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() + "" % \ + (color, field, transition, value) + output += "
%s%s%s
" + 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 = "\n" + + return "%s%04d.%06d%s" % \ + (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 = "%s" % (method.name, text) + + return "%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 "%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 = " { " + 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(''' + + + +OSTRA Callgraph: %s, Where the Fields Changed +%s + + +

Click on the values to go to where it was changed

+

Click on the field names to see a plotting of its value over time

+''' % (callgraph, html_style_import)) + output_file.write("
") + f.write("Index\n") + f.write("Last\n" % (html_file_seq - 1)) + f.write("") + + 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\n" + elif plot and len(changes) >= plot_min_samples and fields.plot_fmt != "dev_null": + link_pre="" % key + link_pos="" + + f.write("\n") + + f.write("
%s%s%s%s" % (link_pre, key, link_pos, changes_str)) + if len(changes) == 0: + continue + + f.write("\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("" % \ + (filename, change.tstamp.seconds, change.tstamp.microseconds, change.value)) + + if nr_samples > max_samples: + f.write("" % (max_samples, nr_samples)) + + f.write("
%s
Only %d samples out of %d were printed
\n
") + output_file.write("
") + f.write("\n\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(''' + + + +OSTRA Callgraph: %s, Methods Statistics +%s + + +

Click on the methods names to see a plotting of the times for each call

+''' % (callgraph, html_style_import)) + + if plot: + class_def.plot_methods(callgraph) + f.write("") + for method in class_def.methods.values(): + changes_str="" + link_pre="" + link_pos="" + if len(method.times) < 4: + changes_str="Less than 4 calls\n" + else: + if plot: + link_pre="" % method.name + link_pos="" + changes_str="%d calls\n" % len(method.times) + + f.write("
%s%s%s%s" % \ + (link_pre, method.name, link_pos, changes_str)) + f.write("
") + f.write("\n\n") + f.close() + +if __name__ == '__main__': + if len(sys.argv) not in [ 3, 4 ]: + print "usage: ostra-cg [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) diff --git a/ostra/python/ostra.py b/ostra/python/ostra.py new file mode 100755 index 0000000..2fe3f13 --- /dev/null +++ b/ostra/python/ostra.py @@ -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) diff --git a/rpm/SPECS/dwarves.spec b/rpm/SPECS/dwarves.spec index 1f63fa4..6f07d79 100644 --- a/rpm/SPECS/dwarves.spec +++ b/rpm/SPECS/dwarves.spec @@ -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 -- 4ab3403e3b72a18fbd5fe15630e76605264ba18c +- d37f41df58c375412badf827e24dfc346cea2ff2 +- ostra-cg +- relay/debugfs +- mini-structs +- ctracer2ostra +- All this in the Makefile + +* Fri Feb 2 2007 Arnaldo Carvalho de Melo +- b7cad1782d683571ffb2601b429ab151bddad5d7 - pglobal, by Davi Arnaut - pahole --show_reorg_steps - Reorganize bitfields in pahole --reorganize