May 27, 2008
(Posted at 5:18 am)Static call graphs for Ruby code
Ever since my bug list for ApnaBill.com started to go down, I was worried that what am I going to do in all the free time which will suddenly be created. So much so, I was even creating unnecessary tasks and them scratching them out upon realizing that they are just not needed - sure shot symptoms of working too lately!
While working on ApnaBill, I felt a huge urge to document my code - specially the libraries I wrote and some of the complex controllers. Surely enough the code is very well documented - infact, at times makes pun and even tells stories - but I was always missing a call graph generation tool, which when given my Rails project, can graph out all the calls each of the actions make. I am not talking about profile time graphs - but something pretty static, which just looks at your ruby code and draws a graph.
…And just why is it needed? Documentation - ofcourse! A colored and connected graph, which can take you directly to the source code is alot easier to digest than just the code - specially for those who have not written that piece of code. This can be real useful in many scenarios.
Ok, so I started my search two days ago, on Saturday night - and for all night, I was crawling web pages like a googlebot - trying to find the relevant pieces of information. I did came across some fantastic projects like RailRoad, RCov and ruby-prof - but there was nothing that could satisfy my need. I even tried Doxygen (which is still to support Ruby), rdoc and almost all the other doc tools mentioned at “Other tools” seciton on Doxygen’s website.
Continuing my {re}search, I came across ParseTree. Its a C extension which churns out Sexp representation of Ruby code. You can parse ruby code in classes or methods or strings format. The library (apart from other stuff) - basically gives you a callback like access to various parts of the Ruby language. So “process_defn” would be invoked when any “def my_method()” is encountered.
There isn’t much on the internet about how to use ParseTree but the code samples and RDoc included in the gem are a good start. And then there’s the code installed by the gem - which you can always read at leisure. Another document worth checking out is Ryan’s Dependency Analyzer - where he does a bit of explaining as well.
Okay, back to the problem in hand - How can I draw a call graph of my code without actually executing it. I don’t want to execute it because it cannot guarantee 100% path coverage. Further, my objective is to document the code rather than profile it.
After my initial attempts, I was finally able to create a hierarchical structure of the code (which is utterly basic as of now).
-
-
# Target code which is to be graphed
-
# Save as "mini.rb" in your current directory
-
module Junk
-
-
class Maku
-
-
def say_hello(str)
-
puts "hello #{str}"
-
a = 1+1
-
if a == 2
-
if a == 1
-
puts "Yahoo"
-
if a == 10
-
puts "Microsoft"
-
else
-
puts "Google"
-
end
-
end
-
pp a
-
else
-
puts "bye"
-
end
-
end
-
end
-
-
end
Introducing RAKA
-
-
#!/usr/bin/env ruby
-
# RAKA - A Ruby Kode Analyzer
-
# Save in same directory as mini.rb
-
-
require "pp"
-
require ‘rubygems’
-
require ‘parse_tree’
-
require ’sexp_processor’
-
-
class Raka < SexpProcessor
-
-
attr_accessor :tabber
-
-
def initialize
-
super
-
@tabber = 0
-
self.strict = false
-
self.auto_shift_type = true
-
end
-
-
# Just a wrapper for s method.
-
# Usefull in decrementing tabbing structure once processing is done.
-
def s(*args)
-
result = super(*args)
-
@tabber -= 1
-
return result
-
end
-
-
def process_fcall(exp)
-
@tabber += 1
-
name = exp.shift
-
display "CALL #{name}"
-
return s(:fcall, exp.shift)
-
end
-
-
def process_if(exp)
-
@tabber += 1
-
display "IF"
-
test = exp.shift
-
process(test)
-
left = exp.shift
-
process(left)
-
display "ELSE"
-
right = exp.shift
-
process(right)
-
display "END"
-
return s(:if, test, left, right)
-
end
-
-
def process_defn(exp)
-
@tabber += 1
-
name = exp.shift
-
display "DEF #{name}"
-
args = process exp.shift
-
body = process exp.shift
-
display "END"
-
return s(:defn, name, args, body)
-
end
-
-
private
-
-
def display(str)
-
@tabber.times{ print "\t" }
-
puts "#{str}"
-
end
-
-
end
-
-
require "mini"
-
puts "============Junk::Maku============"
-
Raka.new.process(*ParseTree.new.parse_tree(Junk::Maku))
The result
Woha! As you can see, I am able to relate the code structure with the calls and if statements. If I can output this into a format which Graphviz can understand, we’ll have exactly what we want!
Too bad, its almost 6am - and I have to catch up on some sleep
I’ll have to stop here - more on the project as things with RAKA proceed.
Comments and suggestions are more than welcome




nice..
Comment by SandyTheFire — May 27, 2008 @ 4:11 pm