# # # add_file "contrib/dtrace2calltree.py" # content [06897cc97583f0be6db2124b52f9def71148c1b1] # # add_file "contrib/op2calltree.py" # content [810bb19499bd99e81fa1d4520e8986ff34e6d2ff] # # set "contrib/dtrace2calltree.py" # attr "mtn:execute" # value "true" # # set "contrib/op2calltree.py" # attr "mtn:execute" # value "true" # ============================================================ --- contrib/dtrace2calltree.py 06897cc97583f0be6db2124b52f9def71148c1b1 +++ contrib/dtrace2calltree.py 06897cc97583f0be6db2124b52f9def71148c1b1 @@ -0,0 +1,167 @@ +#!/usr/bin/python + +# to get calltree on stdout, feed to this file's stdin, the output of: +# +#---------------- +# +# #!/usr/sbin/dtrace -qs +# syscall:::entry +# /pid == $target/ +# { +# self->start = timestamp; +# } +# syscall:::return +# /self->start/ +# { +# now=timestamp; +# printf("%d\t%s\n", now - self->start, probefunc); +# ustack(); +# self->start = 0; +# } +# +#---------------- +# +# It looks like: +# +#---------------- +# t.d +# t.result.txt +# +# 47029 munmap +# ld.so.1`munmap+0x7 +# ld.so.1`leave+0x83 +# ld.so.1`call_init+0x41 +# ld.so.1`setup+0xe2c +# ld.so.1`_setup+0x28c +# ld.so.1`_rt_boot+0x56 +# 0x80473dc +# +# 20017 mmap +# libc.so.1`mmap+0x15 +# libc.so.1`lmalloc+0x6c +# libc.so.1`atexit+0x1c +# libc.so.1`libc_init+0x40 +# ld.so.1`call_init+0x46 +# ld.so.1`setup+0xe2c +# ld.so.1`_setup+0x28c +# ld.so.1`_rt_boot+0x56 +# 0x80473dc +# +# 4092 fstat64 +# libc.so.1`fstat64+0x15 +# libc.so.1`opendir+0x3e +# ls`0x8052605 +# ls`0x8051b2b +# ls`main+0x637 +# ls`0x8051106 +#---------------- + +# See also: +# http://kcachegrind.sourceforge.net/cgi-bin/show.cgi/KcacheGrindCalltreeFormat + +import sys +import os.path + +def main(): + data = read(sys.stdin) + data.writeto(sys.stdout) + +# We need to eventually get, for each function: +# -- time spent directly in it +# -- list of functions it calls +# -- call count (which we don't have) +# -- the file/line number/name of the function called +# -- inclusive cost of those calls +# To do this, we have to accumulate everything in memory, then write it out. + +class FnEntry: + def __init__(self, obj_symbol): + (self.obj, self.symbol) = obj_symbol + self.cost = 0 + self.children = {} + + def charge(self, cost): + self.cost += cost + + def charge_child(self, obj_symbol, cost): + self.children.setdefault(obj_symbol, 0) + self.children[obj_symbol] += cost + + def writeto(self, stream): + stream.write("ob=%s\n" % self.obj) + stream.write("fn=%s\n" % self.symbol) + # + stream.write("%s %s\n" % (1, self.cost)) + for ((obj, symbol), cost) in self.children.iteritems(): + stream.write("cob=%s\n" % obj) + stream.write("cfn=%s\n" % symbol) + # + stream.write("calls=1 1\n") + # + stream.write("%s %s\n" % (1, cost)) + stream.write("\n") + +class Data: + def __init__(self): + self.fns = {} + + def get_entry(self, obj_symbol): + if obj_symbol not in self.fns: + self.fns[obj_symbol] = FnEntry(obj_symbol) + return self.fns[obj_symbol] + + # stack is like [("libc.so.1", "fstat64"), ("ls", "main")], with outermost + # function last + def charge(self, cost, stack): + assert stack + prev = None + for obj_symbol in stack: + entry = self.get_entry(obj_symbol) + if prev is None: + entry.charge(cost) + else: + entry.charge_child(prev, cost) + prev = obj_symbol + + def writeto(self, stream): + stream.write("events: syscall_wallclock\n") + stream.write("\n") + for entry in self.fns.itervalues(): + entry.writeto(stream) + +def read_stack(stream): + stack = [] + for line in stream: + line = line.strip() + if not line: + break + if "`" in line: + obj, rest = line.split("`", 1) + # libc.so.1`fstat64+0x15 + if "+" in rest: + symbol, rest = rest.split("+", 1) + # ls`0x8052605 + else: + symbol = rest + stack.append((obj, symbol)) + else: + # 0x80473dc + stack.append(("?", line)) + return stack + +def read(stream): + data = Data() + for line in stream: + if not line or line[0] not in "0123456789": + # skip over nonsense + continue + else: + # we have the beginning of a sample + cost_str, syscall = line.strip().split() + cost = int(cost_str) + stack = read_stack(stream) + data.charge(cost, stack) + return data + +if __name__ == "__main__": + main() ============================================================ --- contrib/op2calltree.py 810bb19499bd99e81fa1d4520e8986ff34e6d2ff +++ contrib/op2calltree.py 810bb19499bd99e81fa1d4520e8986ff34e6d2ff @@ -0,0 +1,90 @@ +#!/usr/bin/python + +# feed this file opreport -gcf on stdin, and it will spit out +# calltree on stdout + +# http://kcachegrind.sourceforge.net/cgi-bin/show.cgi/KcacheGrindCalltreeFormat + +import sys +import os.path + +def main(): + convert(sys.stdin, sys.stdout) + +# need to store: +# bunch of function data +# for each function, its file/line location, plus name +# plus total samples directly in it +# plus list of functions called +# -- call count (which we don't have) +# -- the file/line number/name of the function called +# -- inclusive cost of those calls +def convert(op, ct): + for line in op: + if line.startswith("Counted "): + ct.write("events: %s" % line.split(" ", 3)[1]) + continue + if line.startswith("-" * 70): + # found our first stanza + break + stanza = [] + for line in op: + if line.startswith("-" * 70): + process_stanza(stanza, ct) + stanza = [] + else: + stanza.append(line) + +# some sample lines: +# 601802 9.9042 botan/sha160.cpp:54 /home/njs/src/monotone/vlogs-test/mtn-client _ZN5Botan7SHA_1604hashEPKh +# 1585865 26.0995 (no location information) /usr/lib/libstdc++.so.6.0.7 (no symbols) +# we return the tuple: (count, filename, line, object, symbol) +def parse_line(line): + rest = line.strip() + count, percent, rest = rest.split(None, 2) + if rest.startswith("(no location information)"): + filename = "(unknown)" + line = "0" + blah, blah, blah, rest = rest.split(None, 3) + else: + filename_line, rest = rest.split(None, 1) + assert ":" in filename_line + filename, line = filename_line.split(":") + object, rest = rest.split(None, 1) + symbol = rest.strip() + return (count, filename, line, object, symbol) + +def process_stanza(stanza, ct): + ct.write("\n") + it = iter(stanza) + # skip over the parent call info + for line in it: + # skip past the caller lines + if line[0] not in "0123456789": + continue + # process the direct cost line + count, filename, line, object, symbol = parse_line(line) + ct.write("fl=%s\n" % filename) + ct.write("ob=%s\n" % object) + ct.write("fn=%s\n" % symbol) + ct.write("%s %s\n" % (line, count)) + # save for later + caller_line = line + # start processing next set of lines + break + # process the cumulative child cost lines + for line in it: + if "[self]" in line: + continue + count, filename, line, object, symbol = parse_line(line) + ct.write("cfi=%s\n" % filename) + ct.write("cob=%s\n" % object) + ct.write("cfn=%s\n" % symbol) + # we don't know how many calls were made, so just hard-code to "1" + # and we don't know the line number the calls were made from, so just + # pretend everything came from the same line our function started on + ct.write("calls=%s %s\n" % (1, line)) + ct.write("%s %s\n" % (caller_line, count)) + +if __name__ == "__main__": + main()