kefka 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -1,6 +1,14 @@
1
1
  source "http://rubygems.org"
2
2
 
3
+ gem "yajl-ruby"
4
+ gem "coderay"
5
+ gem "sinatra"
6
+ gem "method_source"
7
+ gem "rgl"
8
+
3
9
  group :development do
10
+ gem "pry"
11
+ gem "pry-doc"
4
12
  gem "rspec"
5
13
  gem "jeweler"
6
14
  end
data/README CHANGED
@@ -1,6 +1,6 @@
1
- == Kefka
1
+ == Kefka (Experimental)
2
2
 
3
- A tool for understanding unfamiliar codebases and 3rd party libraries. It shows you callgraphs for different execution paths of a program.
3
+ A tool for understanding unfamiliar codebases and 3rd party libraries. Basic Idea is to visualize the method callgraph of a program while showing local variable values for each line of execution.
4
4
 
5
5
  == Installation
6
6
 
@@ -8,9 +8,8 @@ A tool for understanding unfamiliar codebases and 3rd party libraries. It shows
8
8
 
9
9
  == Usage
10
10
 
11
- From the command line ,
12
-
13
- `kefka filename.rb`
11
+ 1. From the command line , `kefka`
12
+ 2. Go to browser and point to http://localhost:4567/
14
13
 
15
14
  Examples are provided under the examples directory of kefka gem
16
15
 
data/TODO CHANGED
@@ -1,5 +1,9 @@
1
1
  == TODO
2
2
 
3
+ http://wishlabs.blogspot.ca/2011/09/interactive-dot-generated-svg-graph.html
4
+ http://www.ibm.com/developerworks/library/l-graphvis/
5
+ http://bl.ocks.org/1667139
6
+
3
7
  1st Iteration
4
8
 
5
9
  method graph
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
data/app.rb ADDED
@@ -0,0 +1,36 @@
1
+ $LOAD_PATH.unshift("#{File.expand_path(File.dirname(__FILE__))}/lib")
2
+
3
+ require 'sinatra'
4
+ require 'kefka'
5
+ require 'yajl'
6
+ require 'yajl/json_gem'
7
+
8
+ get '/' do
9
+ erb :index
10
+ end
11
+
12
+ get '/callgraph' do
13
+ content_type :json
14
+
15
+ path = "examples/sample_a.rb"
16
+ file = File.open(path)
17
+
18
+ @tracer = Kefka::Tracer.new
19
+ @tracer.trace(file, :callgraph_handler)
20
+
21
+ graph = @tracer.method_graph
22
+ graph.vertices.each { |method| method.format = :html }
23
+ graph.to_json
24
+ end
25
+
26
+ get '/locals' do
27
+ content_type :json
28
+
29
+ path = "examples/sample_a.rb"
30
+ file = File.open(path)
31
+
32
+ tracer = Kefka::Tracer.new
33
+ tracer.trace(file, :local_values_handler)
34
+ results = tracer.local_values
35
+ results.to_json
36
+ end
data/bin/kefka CHANGED
@@ -3,16 +3,10 @@
3
3
  $LOAD_PATH.unshift("#{File.expand_path(File.dirname(__FILE__))}/../lib")
4
4
 
5
5
  require 'kefka'
6
- require 'pp'
7
6
 
8
- if ARGV.count < 1
9
- puts "Usage: kefka [path_to_file]"
10
- exit
11
- end
7
+ RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"]
12
8
 
13
- path = ARGV[0]
14
- file = File.open(path)
9
+ APP = "#{File.expand_path(File.dirname(__FILE__))}/../app.rb"
10
+ exec RUBY, APP
15
11
 
16
- Kefka.trace(file, :callgraph_handler)
17
- Kefka.display
18
12
 
@@ -0,0 +1,61 @@
1
+ require 'forwardable'
2
+ require 'date'
3
+ require 'net/http'
4
+
5
+ class Book
6
+ extend Forwardable
7
+ def_delegators :@store, :first, :last
8
+
9
+ def initialize
10
+ @store = [1,2]
11
+ end
12
+
13
+ def []
14
+ "array accessor"
15
+ end
16
+ end
17
+
18
+ class Displayer
19
+ def initialize(out)
20
+ @out = out
21
+ end
22
+
23
+ def puts(val)
24
+ @out.puts val
25
+ end
26
+ end
27
+
28
+ def hello
29
+ num = init
30
+ x = 4
31
+ x = x * 2
32
+ b = Book.new
33
+
34
+ if b.first
35
+ puts "lol"
36
+ end
37
+
38
+ c = b[]
39
+ display = Displayer.new($stdout)
40
+ display.puts "Hello #{num}"
41
+ end
42
+
43
+ def init
44
+ a = 1 + rand(9)
45
+ (0..3).each do |x|
46
+ a = a + time_diff
47
+ end
48
+ a
49
+ end
50
+
51
+ # function called inside a loop
52
+ def time_diff
53
+ before = Time.now
54
+ after = Time.now
55
+ (after - before).to_i
56
+ end
57
+
58
+ hello
59
+
60
+ Net::HTTP.get_response(URI.parse("http://www.twitter.com")).body
61
+
data/kefka.gemspec CHANGED
@@ -5,18 +5,17 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "kefka"
8
- s.version = "0.0.1"
8
+ s.version = "0.0.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Reginald Tan"]
12
- s.date = "2012-07-14"
12
+ s.date = "2012-07-30"
13
13
  s.description = " It traces the execution path of a program and displays the source code of each method call in the callgraph "
14
14
  s.email = "redge.tan@gmail.com"
15
15
  s.executables = ["kefka"]
16
16
  s.extra_rdoc_files = [
17
17
  "LICENSE.txt",
18
18
  "README",
19
- "README.rdoc",
20
19
  "TODO"
21
20
  ]
22
21
  s.files = [
@@ -24,16 +23,20 @@ Gem::Specification.new do |s|
24
23
  "Gemfile",
25
24
  "LICENSE.txt",
26
25
  "README",
27
- "README.rdoc",
28
26
  "Rakefile",
29
27
  "VERSION",
28
+ "app.rb",
30
29
  "bin/kefka",
31
- "examples/sample1.rb",
30
+ "examples/sample_a.rb",
32
31
  "examples/trace_specific_lines.rb",
33
32
  "kefka.gemspec",
34
33
  "lib/kefka.rb",
34
+ "public/javascripts/app.js",
35
+ "public/javascripts/jquery-1.7.2.js",
36
+ "public/stylesheets/application.css",
35
37
  "test/helper.rb",
36
- "test/test_kefka.rb"
38
+ "test/test_kefka.rb",
39
+ "views/index.erb"
37
40
  ]
38
41
  s.homepage = "http://github.com/redgetan/kefka"
39
42
  s.licenses = ["MIT"]
@@ -45,13 +48,34 @@ Gem::Specification.new do |s|
45
48
  s.specification_version = 3
46
49
 
47
50
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51
+ s.add_runtime_dependency(%q<yajl-ruby>, [">= 0"])
52
+ s.add_runtime_dependency(%q<coderay>, [">= 0"])
53
+ s.add_runtime_dependency(%q<sinatra>, [">= 0"])
54
+ s.add_runtime_dependency(%q<method_source>, [">= 0"])
55
+ s.add_runtime_dependency(%q<rgl>, [">= 0"])
56
+ s.add_development_dependency(%q<pry>, [">= 0"])
57
+ s.add_development_dependency(%q<pry-doc>, [">= 0"])
48
58
  s.add_development_dependency(%q<rspec>, [">= 0"])
49
59
  s.add_development_dependency(%q<jeweler>, [">= 0"])
50
60
  else
61
+ s.add_dependency(%q<yajl-ruby>, [">= 0"])
62
+ s.add_dependency(%q<coderay>, [">= 0"])
63
+ s.add_dependency(%q<sinatra>, [">= 0"])
64
+ s.add_dependency(%q<method_source>, [">= 0"])
65
+ s.add_dependency(%q<rgl>, [">= 0"])
66
+ s.add_dependency(%q<pry>, [">= 0"])
67
+ s.add_dependency(%q<pry-doc>, [">= 0"])
51
68
  s.add_dependency(%q<rspec>, [">= 0"])
52
69
  s.add_dependency(%q<jeweler>, [">= 0"])
53
70
  end
54
71
  else
72
+ s.add_dependency(%q<yajl-ruby>, [">= 0"])
73
+ s.add_dependency(%q<coderay>, [">= 0"])
74
+ s.add_dependency(%q<sinatra>, [">= 0"])
75
+ s.add_dependency(%q<method_source>, [">= 0"])
76
+ s.add_dependency(%q<rgl>, [">= 0"])
77
+ s.add_dependency(%q<pry>, [">= 0"])
78
+ s.add_dependency(%q<pry-doc>, [">= 0"])
55
79
  s.add_dependency(%q<rspec>, [">= 0"])
56
80
  s.add_dependency(%q<jeweler>, [">= 0"])
57
81
  end
data/lib/kefka.rb CHANGED
@@ -1,14 +1,117 @@
1
- module Kefka
1
+ require 'coderay'
2
+ require 'rgl/adjacency'
3
+ require 'rgl/dot'
4
+ require 'method_source'
2
5
 
3
- @@values = {}
4
- @@method_source = {}
6
+ require 'forwardable'
7
+ require 'logger'
5
8
 
6
- class << self
9
+ class Kefka
7
10
 
8
- def get_values_of_locals_from_binding(binding)
9
- locals = binding.eval("local_variables")
11
+ class Method
12
+
13
+ attr_reader :classname, :id, :file, :line, :caller
14
+ attr_accessor :format
15
+
16
+ def initialize(options={})
17
+ @classname = options[:classname]
18
+ @id = options[:id]
19
+ @file = options[:file]
20
+ @line = options[:line]
21
+ @format = options[:format] || :plain
22
+ end
23
+
24
+ def source_location
25
+ return nil unless @file && @line
26
+ [@file,@line]
27
+ end
28
+
29
+ def end_line
30
+ return nil unless @line && source
31
+ @line + source.lines.count - 1
32
+ end
33
+
34
+ def key
35
+ source_location ? source_location.join(":") : nil
36
+ end
37
+
38
+ def contains?(file, line)
39
+ @file == file && @line < line && end_line > line
40
+ end
41
+
42
+ def source
43
+ @source ||= begin
44
+ MethodSource.source_helper(source_location)
45
+ rescue MethodSource::SourceNotFoundError => e
46
+ warn "Warning: #{e.class} #{e.message}"
47
+ nil
48
+ end
49
+ end
50
+
51
+ def formatted_source
52
+ if @format == :html
53
+ CodeRay.scan(source, :ruby)
54
+ .div(:line_numbers => :table, :line_number_start => @line)
55
+ else
56
+ source
57
+ end
58
+ end
59
+
60
+ def to_s
61
+ "#{classname} #{id}"
62
+ end
63
+
64
+ def to_json(*a)
65
+ {
66
+ :classname => @classname,
67
+ :id => @id,
68
+ :file => @file,
69
+ :line => @line,
70
+ :end_line => end_line,
71
+ :source => formatted_source
72
+ }.to_json(*a)
73
+ end
74
+ end
75
+
76
+ class MethodGraph
77
+ extend Forwardable
78
+ def_delegators :@graph, :vertices, :edges, :add_edge,
79
+ :write_to_graphic_file
80
+
81
+ def initialize
82
+ @graph = RGL::DirectedAdjacencyGraph.new
83
+ end
84
+
85
+ def to_json
86
+ {
87
+ :vertices => vertices,
88
+ :edges => edges.map { |edge|
89
+ {
90
+ :source => edge.source.key,
91
+ :target => edge.target.key
92
+ }
93
+ }
94
+ }.to_json
95
+ end
96
+ end
97
+
98
+ class Tracer
99
+
100
+ attr_reader :method_table, :local_values, :logger, :callstack, :method_graph
101
+
102
+ def initialize(log_level = Logger::INFO)
103
+ @method_graph = MethodGraph.new
104
+ @callstack = []
105
+ @local_values = {}
106
+ @event_disable = []
107
+ @logger = Logger.new($stderr)
108
+ @logger.level = log_level
109
+ end
110
+
111
+ def get_values_of_locals_from_binding(target)
112
+ locals = target.eval("local_variables")
10
113
  locals.inject({}) do |result,l|
11
- val = binding.eval(l.to_s)
114
+ val = target.eval(l.to_s)
12
115
  val = begin
13
116
  # deep copy
14
117
  Marshal.load(Marshal.dump(val)) if val
@@ -21,100 +124,85 @@ module Kefka
21
124
  end
22
125
  end
23
126
 
24
- # Things to IGNORE
25
- # 1. loading of rubygems/libraries
127
+ def disable_event_handlers?
128
+ !@event_disable.empty?
129
+ end
130
+
26
131
  def callgraph_handler(event, file, line, id, binding, classname)
27
- # do not trace current file (TODO: and anything in this library)
28
132
  return if file == __FILE__
133
+
134
+ @logger.debug "#{event} - #{file}:#{line} #{classname} #{id}"
135
+
136
+ if disable_event_handlers?
137
+ @event_disable.pop if event == "end"
138
+ return
139
+ end
140
+
29
141
  case event
142
+ when "class"
143
+ @event_disable << true
30
144
  when "call"
31
- # mark the start of method call
32
-
33
- # key must be uniquely identifiable -
34
- # Class methodname is not enough
35
- # perhaps include:
36
- # 1. line
37
- # 2. file
38
- key = "#{classname}_#{id}"
39
- @@method_source[key] = [file, caller[1], line]
40
- when "line"
145
+ method_caller = @callstack.last
146
+
147
+ method = Method.new(
148
+ :classname => classname,
149
+ :id => id,
150
+ :file => file,
151
+ :line => line
152
+ )
153
+
154
+ if method_caller
155
+ @method_graph.add_edge(method_caller,method)
156
+ end
157
+
158
+ @callstack << method
41
159
  when "return"
42
- key = "#{classname}_#{id}"
43
- @@method_source[key] << line if @@method_source[key]
160
+ @callstack.pop
44
161
  else
45
162
  # do nothing
46
163
  end
47
164
  rescue Exception => e
48
- puts "#{e.message} from -- #{e.backtrace.join("\n")}"
165
+ puts "\n#{e.class}: #{e.message} \n from #{e.backtrace.join("\n")}"
166
+ Process.kill("KILL", $$)
49
167
  end
50
168
 
51
- def locals_values_handler(event, file, line, id, binding, classname)
52
-
169
+ def local_values_handler(event, file, line, id, binding, classname)
170
+ # skip variables that should not be tracked
171
+ # 1. anything in current lib (i.e __FILE__)
172
+ # 2. all local variables that are in TOP LEVEL BINDING before tracing
173
+ # - but these variables may be overwritten by the traced program,
174
+ # excluding them would mean not displaying certain relevant
175
+ # vars in that program
53
176
  return if file == __FILE__
54
177
 
178
+ @logger.debug "#{event} - #{file}:#{line} #{classname} #{id}" if $DEBUG
179
+
180
+ if disable_event_handlers?
181
+ @event_disable.pop if event == "end"
182
+ return
183
+ end
184
+
55
185
  case event
56
- when "call"
57
- #puts "Entering method #{classname} - #{id}"
186
+ when "class"
187
+ @event_disable << true
58
188
  when "line"
59
- # variables that should not be tracked
60
- # 1. anything in current lib
61
- # 2. all local variables that are in TOP LEVEL BINDING before tracing
62
- # - but these variables may be overwritten by the traced program,
63
- # excluding them would mean not displaying certain relevant
64
- # vars in that program
65
- key = "#{file}_#{line}".to_sym
66
- @@values[key] = get_values_of_locals_from_binding(binding)
189
+ key = [file, line].join(":")
190
+ @local_values[key] = get_values_of_locals_from_binding(binding)
67
191
  else
68
192
  # do nothing
69
193
  end
70
194
  end
71
195
 
72
- def start(handler)
73
- set_trace_func method(handler).to_proc
74
- end
75
-
76
- def stop
77
- set_trace_func nil
78
- end
79
-
80
- def trace(file, handler = :callgraph_handler)
81
- puts "\nTracing Execution using #{handler}...\n\n"
82
- start(handler)
83
- file.rewind if file.eof?
84
- code = file.read
85
- eval(code, TOPLEVEL_BINDING, file.path, 1)
86
- stop
87
- end
88
-
89
- def method_graph
90
- @@method_source
91
- end
92
-
93
- def display
94
- puts "\n==== Generating Method Callgraph...\n\n"
95
- @@method_source.each do |meth,props|
96
- puts
97
- puts meth
98
- puts
99
-
100
- file, parent_caller, start_line, finish_line = props
101
-
102
- File.open(file) { |f|
103
- (start_line - 1).times { f.readline }
104
-
105
- code = ""
106
- (finish_line - start_line + 1).times {
107
- code << f.readline
108
- }
109
- puts code
110
- }
196
+ def trace(file_path, handler = :callgraph_handler)
197
+ file = File.open(file_path)
111
198
 
112
- puts
113
- end
114
- end
199
+ thread = Thread.new {
200
+ code = file.read
201
+ eval(code, TOPLEVEL_BINDING, file.path, 1)
202
+ }
115
203
 
116
- def values
117
- @@values
204
+ thread.set_trace_func method(handler).to_proc
205
+ thread.join
118
206
  end
119
207
 
120
208
  end