Last active
December 24, 2015 01:29
-
-
Save maliubiao/6724103 to your computer and use it in GitHub Desktop.
a dia plugin that draws the stack data generated by systemtap, you will find it useful when you want to read the source code of a large C project.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python | |
#-*-encoding=utf-8-*- | |
import pygtk | |
pygtk.require('2.0') | |
import gtk | |
import dia | |
import re | |
import os.path | |
import datetime | |
import time | |
import pdb | |
import cProfile | |
from struct import unpack | |
from cStringIO import StringIO | |
from collections import OrderedDict | |
#x, y increment | |
XINCR = 48 | |
YINCR = 3 | |
class DalvikTrace: | |
def __init__(self, data): | |
self.data = data | |
threads_offset = data.find("*threads") + len("*threads\n") | |
threads_end_offset = data.find("*methods") - 1 | |
threads_data = data[threads_offset:threads_end_offset] | |
methods_offset = data.find("*methods") + len("*methods\n") | |
methods_end_offset = data.find("*end") - 1 | |
methods_data = data[methods_offset:methods_end_offset] | |
self.threads = self.build_threads(threads_data) | |
self.methods = self.build_methods(methods_data) | |
stacks_offset = data.find("*end") + len("*end\n") | |
self.stacks = data[stacks_offset:] | |
def build_pathname(self, className, pathname): | |
index = className.rfind("/") | |
if index > 0 and index < len(className) - 1 and pathname.endswith(".java"): | |
pathname = className[:index+1] + pathname | |
return pathname | |
def build_threads(self, data): | |
threads = {} | |
for line in data.split("\n"): | |
thread = line.split("\t") | |
threads[int(thread[0])] = thread[1] | |
return threads | |
def build_methods(self, data): | |
methods = {} | |
for line in data.split("\n"): | |
tokens = line.split("\t") | |
methodid = int(tokens[0], 16) | |
methods[methodid] = {} | |
mdict =methods[methodid] | |
mdict['className'] = tokens[1] | |
if len(tokens) == 6: | |
mdict['methodName'] = tokens[2] | |
mdict['signature'] = tokens[3] | |
mdict['pathname'] = self.build_pathname(tokens[1], tokens[4]) | |
mdict['lineNumber'] = tokens[5] | |
elif len(tokens) > 2: | |
if tokens[3].startswith("("): | |
mdict['methodName'] = tokens[2] | |
mdict['signature'] = tokens[3] | |
else: | |
mdict['pathname'] = tokens[2] | |
mdict['lineNumber'] = token(3) | |
return methods | |
def build_stacks(self, data): | |
stacks = OrderedDict() | |
#ignore read header | |
buffer = StringIO() | |
buffer.write(data) | |
buffer.seek(0) | |
magic_number = buffer.read(4) | |
version = unpack("H", buffer.read(2))[0] | |
header_length = unpack("H", buffer.read(2))[0] | |
timestamp = unpack("Q", buffer.read(8))[0] | |
time = datetime.date.fromtimestamp(timestamp/1000000) | |
#ignore 16 chars | |
buffer.read(16) | |
while True: | |
try: | |
threadid = unpack("H", buffer.read(2))[0] | |
method = unpack("I", buffer.read(4))[0] | |
method_action = method & 0x03 | |
methodid = method & ~0x03 | |
time = unpack("I", buffer.read(4))[0] | |
if method_action == 0: | |
action = "ENTER" | |
elif method_action == 1: | |
action = "LEAVE" | |
elif method_action == 2: | |
action = "UNROLL" | |
elif method_action == 4: | |
action = "NATIVE ENTER" | |
elif method_action == 5: | |
action = "NATIVE LEAVE" | |
elif method_action == 6: | |
action = "NATIVE UNROLL" | |
method = self.methods[methodid] | |
if threadid not in stacks: | |
stacks[threadid] = [] | |
stacks[threadid].append((action, method['methodName'], method['className'], method['lineNumber'])) | |
#ignore 4 chars | |
buffer.read(4) | |
except: | |
return stacks | |
return stacks | |
def build_backtrace(self): | |
links = [] | |
newline = chr(0x0a) | |
threads = self.build_stacks(self.stacks) | |
for s in threads: | |
ENTER_list = [] | |
LEVAE_list = [] | |
for m in threads[s]: | |
action = m[0] | |
ENTER_list_length = len(ENTER_list) | |
if action == "ENTER": | |
this = " ".join((m[1], m[2], m[3])) | |
ENTER_list.append(this) | |
if ENTER_list_length + 1 > 0: | |
links.append(newline.join(ENTER_list)) | |
elif action == "LEAVE": | |
if ENTER_list_length > 0: | |
ENTER_list.pop() | |
return links | |
class StackData: | |
"""get stack data from a file *.stacks, and draw them in order | |
using dia api. | |
""" | |
def __init__(self): | |
"""get the diagram we are using now, and compile regexes | |
for three different type of stack data | |
""" | |
self.diagram = dia.diagrams()[0] | |
self.format_python = re.compile(r"\S+ \S+ [0-9]+") | |
self.format_kernel = re.compile(r"0x\S+ : \S+") | |
self.format_process = re.compile(r"^\S+ \[\S+\]") | |
def createbox(self, text): | |
"""Create a Flowchart - Box""" | |
if text in self.done: | |
return (self.done[text], True) | |
obj = dia.get_object_type("Flowchart - Box").create(0, 0)[0] | |
obj.properties['text'] = text | |
self.done[text] = obj | |
return (obj, False) | |
def draw_all(self, path): | |
"""main drawing function""" | |
splitf = None | |
text = open(path, "r").read() | |
firstline = text[:text.find("\n")] | |
if self.format_python.findall(firstline): | |
splitf = self.split_python_stack | |
elif self.format_kernel.findall(firstline): | |
splitf = self.split_kernel_stack | |
elif firstline == "*version": | |
splitf = self.split_dalvik_stack | |
else: | |
splitf = self.split_process_stack | |
levels = self.build_levels(splitf(text)) | |
self.draw_levels(levels) | |
self.diagram.update_extents() | |
self.diagram.flush() | |
def draw_levels(self, levels): | |
self.done = {} | |
singlepair = {} | |
self.column = 0 | |
for level in levels: | |
if any([x for x in level if x not in self.done]): | |
self.column += 1 | |
self.draw_boxes(level) | |
for level in levels: | |
for key in level: | |
for child in level[key]: | |
#don't connect two objects multiple times. | |
if key in singlepair: | |
if child == singlepair[key]: | |
continue | |
singlepair[key] = child | |
pa = self.done[key] | |
pb = self.done[child] | |
self.diagram.add_object(self.connect_boxes(pa, pb)) | |
self.diagram.update_extents() | |
self.diagram.flush() | |
def draw_boxes(self, stack): | |
"""Create boxes for all frames in a stack, and connect | |
them based on the invocation relationship between them | |
""" | |
boxes = [self.createbox(x) for x in stack] | |
for index, boxanddone in enumerate(boxes): | |
box, done = boxanddone | |
box.move(self.column * XINCR, index * YINCR) | |
self.diagram.add_object(box) | |
def connect_boxes(self, pointa, pointb): | |
"""Connect two boxes""" | |
jl, h1, h2 = dia.get_object_type("Standard - Line").create(0, 0) | |
boxa = pointa.bounding_box | |
boxb = pointb.bounding_box | |
#jl.properties['start_point'] = (boxa.right, boxa.bottom) | |
#jl.properties['end_point'] = (boxb.left, boxb.top) | |
self.diagram.connect_object(jl, h1, pointa.connections[15]) | |
self.diagram.connect_object(jl, h2, pointb.connections[0]) | |
return jl | |
def split_dalvik_stack(self, text): | |
global XINCR | |
XINCR = 48 | |
dm = DalvikTrace(text) | |
timea = time.time() | |
stacks = dm.build_backtrace() | |
print "build_backtrace:", time.time() - timea | |
timea = time.time() | |
out = [] | |
out_append = out.append | |
space = " " | |
try: | |
for stack in stacks: | |
inner = [] | |
inner_append = inner.append | |
for line in stack.split("\n"): | |
inner_append(space.join(line.split(space))) | |
out_append((inner, len(inner) -1)) | |
except Exception, err: | |
print str(err) | |
print "split_dalvik_stack:", time.time() - timea | |
return out | |
def split_python_stack(self, text): | |
global XINCR | |
XINCR = 48 | |
out = [] | |
out_append = out.append | |
space = " " | |
space_join = space.join | |
slash = "/" | |
slash_join = slash.join | |
try: | |
for stack in text.split("\n\n"): | |
inner = [] | |
inner_append = inner.append | |
for line in reversed(stack.split("\n")): | |
li = line.split(space) | |
inner_append(space_join((li[0], | |
slash_join(li[1].split("/")[-2:]) | |
))) | |
out_append((inner, len(inner) -1)) | |
except Exception, err: | |
print str(err) | |
return out | |
def split_kernel_stack(self, text): | |
out = [] | |
out_append = out.append | |
space = " " | |
plus = "+" | |
try: | |
for stack in text.split("\n\n"): | |
inner = [] | |
inner_append = inner.append | |
for line in reversed(stack.split("\n")): | |
inner_append(line.split(space)[3].split(plus)[0]) | |
out_append((inner, len(inner) - 1)) | |
except Exception, err: | |
print str(err) | |
return out | |
def split_process_stack(self, text): | |
out = [] | |
out_append = out.append | |
space = " " | |
plus = "+" | |
try: | |
for stack in text.split("\n\n"): | |
inner = [] | |
inner_append = inner.append | |
for line in reversed(stack.split("\n")): | |
inner_append(line.split(space)[0].split(plus)[0]) | |
out_append((inner, len(inner) -1)) | |
except Exception, err: | |
print str(err) | |
return out | |
def build_levels(self, data): | |
timea = time.time() | |
levels = [] | |
depth = max([x[1] for x in data]) + 1 | |
for number in range(depth): | |
level = OrderedDict() | |
for index, member in enumerate(data): | |
name, length = member | |
if not name: | |
continue | |
value = name[number] | |
if number == length: | |
data[index] = (None, None) | |
if value not in level: | |
level[value] = [] | |
else: | |
if value not in level: | |
level[value] = [] | |
child = name[number + 1] | |
container = level[value] | |
if child not in container: | |
container.append(child) | |
if level: | |
levels.append(level) | |
print "build_levels:", time.time() - timea | |
return levels | |
class MainWindow: | |
def __init__(self): | |
self.selected_object = None | |
self.diagram = dia.diagrams()[0] | |
self.name = None | |
self.setupui() | |
def setupui(self): | |
self.window = gtk.Window() | |
self.container = gtk.Fixed() | |
self.path_label = gtk.Label("Path: ") | |
self.path = gtk.Entry() | |
self.file = gtk.Button("Open Stacks") | |
self.file.connect("clicked", self.get_file) | |
self.execute = gtk.Button("Do it") | |
self.execute.connect("clicked", self.begin_draw) | |
self.selected_label = gtk.Label("selected") | |
self.get_selected = gtk.Button("Get Selected") | |
self.get_selected.connect("clicked", self.get_selected_object) | |
self.line_label = gtk.Label("Line Width: ") | |
self.line_width = gtk.Entry() | |
self.line_width.set_width_chars(3) | |
self.color = gtk.Button("Select Color") | |
self.color.connect("clicked", self.get_color) | |
self.color_preview = gtk.DrawingArea() | |
self.color_preview.set_size_request(100, 40) | |
self.paint = gtk.Button("Modify Line") | |
self.paint.connect("clicked", self.paintbox) | |
self.remove_connection = gtk.Button("Remove Connection") | |
self.remove_connection.connect("clicked", self.do_remove_conns) | |
self.window.set_size_request(500,300) | |
self.window.connect("delete-event", self.quit) | |
self.container.put(self.path_label, 10, 20) | |
self.container.put(self.path, 50, 20) | |
self.container.put(self.file, 250, 20) | |
self.container.put(self.execute, 350, 20) | |
self.container.put(self.selected_label, 10, 60) | |
self.container.put(self.get_selected, 350, 60) | |
self.container.put(self.line_label, 10, 100) | |
self.container.put(self.line_width, 100, 100) | |
self.container.put(self.color, 200, 100) | |
self.container.put(self.color_preview, 300, 100) | |
self.container.put(self.paint, 10, 130) | |
self.container.put(self.remove_connection, 10, 160) | |
self.window.add(self.container) | |
self.window.show_all() | |
def do_remove_conns(self, button): | |
diagram = dia.diagrams()[0] | |
if self.selected_object: | |
for connection in self.selected_object.connections: | |
for obj in connection.connected: | |
diagram.remove_object(obj) | |
#pdb.set_trace() | |
diagram.update_extents() | |
diagram.flush() | |
def get_selected_object(self, button): | |
self.selected_object = dia.diagrams()[0].get_sorted_selected()[0] | |
self.selected_label.set_text(str(self.selected_object)) | |
def get_color(self, button): | |
self.colorseldlg = gtk.ColorSelectionDialog("Select Line Color") | |
colorsel = self.colorseldlg.colorsel | |
colorsel.set_has_palette(True) | |
response = self.colorseldlg.run() | |
if response == gtk.RESPONSE_OK: | |
self.line_color = colorsel.get_current_color() | |
self.color_preview.modify_bg(gtk.STATE_NORMAL, self.line_color) | |
else: | |
self.line_color = None | |
self.colorseldlg.destroy() | |
def get_file(self, button): | |
self.name = self.get_path() | |
if not self.name: | |
self.path.set_text("") | |
self.path.set_text(self.name) | |
def begin_draw(self, widget): | |
if not self.name: | |
return | |
timea = time.time() | |
cProfile.runctx("StackData().draw_all(self.name)", globals(), locals(), "dia.trace") | |
print "time cost:", time.time() - timea | |
def quit(self, widget, value): | |
gtk.main_quit() | |
def get_path(self): | |
dialog = gtk.FileChooserDialog("Open Stack Sample", | |
None, | |
gtk.FILE_CHOOSER_ACTION_OPEN, | |
(gtk.STOCK_CANCEL, | |
gtk.RESPONSE_CANCEL, | |
gtk.STOCK_OPEN, gtk.RESPONSE_OK) | |
) | |
filefilter = gtk.FileFilter() | |
filefilter.set_name("program stack") | |
filefilter.add_pattern("*.stacks") | |
dialog.add_filter(filefilter) | |
response = dialog.run() | |
if response == gtk.RESPONSE_OK: | |
path = dialog.get_filename() | |
dialog.destroy() | |
return path | |
else: | |
dialog.destroy() | |
return None | |
def paintbox(self, button): | |
line_width = self.line_width.get_text() | |
if not all((line_width, self.line_color, self.selected_object)): | |
return | |
allconns = self.selected_object.connections | |
for line in allconns[15].connected + allconns[0].connected: | |
line.properties['line_colour'] = (self.line_color.red_float, | |
self.line_color.green_float, | |
self.line_color.blue_float) | |
line.properties['line_width'] = float(line_width) | |
dia.diagrams()[0].flush() | |
def open_window(data, flags): | |
MainWindow() | |
gtk.main() | |
dia.register_action("treegraph", "stack sample", | |
"/DisplayMenu/Dialogs/DialogsExtensionStart", | |
open_window) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment