Skip to content

Instantly share code, notes, and snippets.

@maliubiao
Last active December 24, 2015 01:29
Show Gist options
  • Save maliubiao/6724103 to your computer and use it in GitHub Desktop.
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.
#! /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