139a3b3373
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
414 lines
12 KiB
Python
Executable File
414 lines
12 KiB
Python
Executable File
#!/usr/bin/python3
|
|
# 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
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify it
|
|
# under the terms of version 2 of the GNU General Public License as
|
|
# published by the Free Software Foundation.
|
|
|
|
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)
|