Snippets

Mário Almeida DMTRACEDUMP - From method nodes to class nodes + weights

Created by Mário Almeida last modified
## THIS SCRIPT PROVIDES METHODS TO PARSE DMTRACEDUMP DOT FILES AND ITS OUTPUT
## ADDITIONALLY IT IS ABLE TO CONVERT NODES TO CLASSES INSTEAD OF METHODS
## TO EXECUTE THIS SCRIPT RUN "ruby <script_name>.rb <dot_file_name>.dot <dmtracedump_output>.txt"
## CHANGE THE INVOKED METHODS IN THE END OF THE FILE DEPENDING ON WHAT YOU WANT
## NO PATIENCE FOR A NICE INTERFACE :)

require 'set'

def map_2_classes(g, weights)

	map_class_ids = {} #Provides an ID per class
	map_mth_2_cls_ids = {}   #Provides the translation between method ID to class ID
	current_id = 0

	nodes = g[0]
	edges = g[1]

	new_egdes = Set.new

	nodes.each do |node|
		cls = node[1] #Should have done a struct/class but bored

		new_node = map_class_ids[cls]

		#First occurrence of class
		if new_node.nil?
			#Create class entry
			new_node = [current_id, cls, nil, node[3], node[4], node[5]] #nil is there to keep the indexs similar. lazy :)
			current_id += 1
		else
			#Class already existed. Aggregate times and occurrences
			new_node = [new_node[0], cls, nil, new_node[3] + node[3], new_node[4] + node[4], new_node[5] + node[5]]
		end

		map_class_ids[cls] = new_node

		#Map method ids to class ids
		map_mth_2_cls_ids[node[0].to_i] = new_node[0]

	end

	edges.each do |edge|
		id1 = edge[0].to_i
		id2 = edge[1].to_i

		if weights[id1].nil?
			puts "ERROR"
			puts "Couldnt find weight for node#{id1} to node#{id2}"
		end

		weight = weights[id1][id2] #Still need to get it here

		new_egdes << [map_mth_2_cls_ids[id1], map_mth_2_cls_ids[id2], weight]

	end

	[map_class_ids.values, new_egdes]
end

def print_class_based(g)

	nodes = g[0]
	edges = g[1]

	puts "digraph g {"
	puts "node [shape = record,height=.1];"

	nodes.each do |node|
		#Difference is it ommits node[2], i.e., the method name
		puts "node#{node[0]}[label = \"[#{node[0]}] #{node[1]} (#{node[3]}, #{node[4]}, #{node[5]})\"]"
	end

	edges.each do |edge|
		#Difference is we are adding the edge weight here
		puts "node#{edge[0]} -> node#{edge[1]} [weight=#{edge[2]}]"
	end

	print "}"
end

# Tested with whatsapp by diff original.dot new.dot :)
def print_original(g)

	nodes = g[0]
	edges = g[1]

	puts "digraph g {"
	puts "node [shape = record,height=.1];"

	nodes.each do |node|
		puts "node#{node[0]}[label = \"[#{node[0]}] #{node[1]}.#{node[2]} (#{node[3]}, #{node[4]}, #{node[5]})\"]"
	end

	edges.each do |edge|
		puts "node#{edge[0]} -> node#{edge[1]}"
	end

	print "}"
end

def read_edges_trace(f)
	separator = /^====/
	separator2 = /^----/
	edge_1 = /^\[(\d+)\]/ #= [id]
	edge_2 = /^\s+\d+\.\d+%\s+\[(\d+)\]\s+(\d+)\/(\d+)/ #= [id, #calls, #total_calls]

	inclusive_index = 2
	separator_index = 0

	e1 = nil
	e2 = nil

	read_child = false

	edges = {}

	File.open(f, 'r').each_line do |line|

		if separator.match(line) 
			separator_index += 1
			next
		end

		if separator2.match(line) 
			read_child = false
			next
		end

		break if separator_index > 2 # We are done :)

		if separator_index == 2 # We will check only the inclusive elapsed times table
			
			if m = edge_1.match(line) # Found parent node 

				e1 = m[1].to_i
				read_child = true

			elsif m = edge_2.match(line) # Found child node

				#puts line if !read_child
				next if !read_child

				e2 = m[1].to_i
				calls = m[2].to_i

				edges[e1] = {} if edges[e1].nil?

				edges[e1][e2] = calls # Store parent to child calls
			end

		end
	end

	edges
end

#We could do all at once here but i rather keep it clean. We only read file once at least
#Tested with ~30 most popular apps.
def read_dot(f)

	nodes = []	   #Holds the nodes as they come from file [ID, Class, Method, Inc. time, Exc. time, Occurrences]
	edges = []	   #Holds the edges as they come from file [ID1, ID2]

	r = /node([\d]+)\[label = "\[\d+\] ([^\s]+) \((\d+), (\d+), (\d+)\)"\]/ #Node description
	rr = /node([\d]+) -> node([\d]+)/ #Edge match
	rrr = /(([\w\$\d]+\/)*[^\.]+)\.([\w\d\$<>]+)/ #Class/Method match

	File.open(f,'r').each_line do |line|

		#puts line
		if node = r.match(line)

			id = node[1]

			if node[2] == "(toplevel).(null)"
				cls = "(toplevel)"
				mth = "(null)"
			else
				m = rrr.match(node[2])
				cls = m[1]
				mth = m[3]
			end

			tinc = node[3].to_i
			texc = node[4].to_i
			occur = node[5].to_i

			new_line = "node#{id}[label = \"[#{id}] #{cls}.#{mth} (#{tinc}, #{texc}, #{occur})\"]"

			nodes << [id,cls,mth,tinc,texc,occur]

			#puts new_line	

			#For validation purposes
			if new_line != line.strip
				puts "Error"
				puts line
				exit
			end
		elsif edge = rr.match(line)

			id = edge[1]
			id2 = edge[2]

			new_line = "node#{id} -> node#{id2}"

			edges << [id, id2]
		
			#puts new_line

			if new_line != line.strip
				puts "Error"
	            puts line
				exit
			end

		else	#Unmatched (basically the digraph, options and brackets
			#puts line
		end
	end

	[nodes, edges]
end

#Reads the dot file
g = read_dot(ARGV[0])

#Prints the original file
#print_original(g)

#Gets the weights for each edge from the dmtracedump output
edges = read_edges_trace(ARGV[1])

#Generates a new graph with classes as nodes
g2 = map_2_classes(g, edges)

#Prints new graph to output
print_class_based(g2)

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.