kefka 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.0.5
data/app.rb CHANGED
@@ -18,25 +18,29 @@ get '/callgraph' do
18
18
  @tracer.trace(file_path)
19
19
 
20
20
  # input code
21
- input = CodeRay.scan(@tracer.code, :ruby).div(:line_numbers => :table)
21
+ #input = CodeRay.scan(@tracer.code, :ruby).div(:line_numbers => :table)
22
22
 
23
- graph = @tracer.method_graph
23
+ method_graph = @tracer.method_graph
24
24
 
25
25
  # output call graph using dot if graphviz is installed
26
- if is_graphviz_installed = system("which dot")
27
- graph.write_to_graphic_file("png", "#{File.expand_path(File.dirname(__FILE__))}/public/graph")
28
- end
26
+ #if is_graphviz_installed = system("which dot")
27
+ # method_graph.write_to_graphic_file("png", "#{File.expand_path(File.dirname(__FILE__))}/public/graph")
28
+ #end
29
29
 
30
30
  # html code graph
31
- graph.vertices.each { |method| method.format = :html }
31
+ method_graph.vertices.each { |method| method.format = :html }
32
+
33
+ # line graph
34
+ line_graph = @tracer.line_graph
32
35
 
33
36
  # locals
34
37
  locals = @tracer.local_values
35
38
 
36
39
  {
37
- :input => input,
38
- :is_graphviz_installed => is_graphviz_installed,
39
- :graph => graph,
40
+ #:input => input,
41
+ #:is_graphviz_installed => is_graphviz_installed,
42
+ :vertices => method_graph.vertices,
43
+ :edges => line_graph.edges,
40
44
  :locals => locals
41
45
  }.to_json
42
46
  end
data/kefka.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "kefka"
8
- s.version = "0.0.4"
8
+ s.version = "0.0.5"
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-08-18"
12
+ s.date = "2012-08-27"
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"]
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
33
33
  "lib/kefka.rb",
34
34
  "public/javascripts/app.js",
35
35
  "public/javascripts/jquery-1.7.2.js",
36
+ "public/javascripts/underscore.js",
36
37
  "public/stylesheets/application.css",
37
38
  "spec/fixture/sample_a.rb",
38
39
  "spec/kekfa_spec.rb",
data/lib/kefka.rb CHANGED
@@ -9,10 +9,42 @@ require 'logger'
9
9
 
10
10
  class Kefka
11
11
 
12
+ class Call
13
+ attr_accessor :method, :line
14
+
15
+ def initialize(options={})
16
+ @method = options[:method]
17
+ @line = options[:line]
18
+ end
19
+
20
+ def file
21
+ @method.file
22
+ end
23
+
24
+ def to_s
25
+ "#{@method.file}:#{@line} in #{@method}"
26
+ end
27
+
28
+ def key
29
+ [@method.id,@method.file,@line].join(":")
30
+ end
31
+
32
+ def eql?(other)
33
+ self.key == other.key
34
+ end
35
+
36
+ alias :== :eql?
37
+
38
+ def hash
39
+ [@method.file,@line].hash
40
+ end
41
+
42
+ end
43
+
12
44
  class Method
13
45
 
14
46
  attr_reader :classname, :id, :file, :line
15
- attr_accessor :source, :format
47
+ attr_accessor :source, :format, :depth
16
48
 
17
49
  def initialize(options={})
18
50
  raise ArgumentError, "missing file + line" unless options[:file] && options[:line]
@@ -32,7 +64,7 @@ class Kefka
32
64
  end
33
65
 
34
66
  def key
35
- source_location ? source_location.join(":") : nil
67
+ source_location ? [id,source_location].flatten.join(":") : nil
36
68
  end
37
69
 
38
70
  def contains?(file, line)
@@ -80,44 +112,95 @@ class Kefka
80
112
  def to_json(*a)
81
113
  {
82
114
  :classname => @classname,
83
- :id => @id,
115
+ :id => @id.to_s,
84
116
  :file => @file,
85
117
  :line => @start_line,
86
118
  :end_line => end_line,
119
+ :depth => depth,
87
120
  :source => formatted_source
88
121
  }.to_json(*a)
89
122
  end
90
123
  end
91
124
 
125
+ # shows which method called which method
92
126
  class MethodGraph
93
127
  extend Forwardable
94
- def_delegators :@graph, :vertices, :edges, :add_edge,
95
- :write_to_graphic_file
128
+ def_delegators :@graph, :vertices, :edges, :write_to_graphic_file
96
129
 
97
130
  def initialize
98
131
  @graph = RGL::DirectedAdjacencyGraph.new
99
132
  end
100
133
 
101
- def to_json
102
- {
103
- :vertices => vertices,
104
- :edges => edges.map { |edge|
105
- {
106
- :source => edge.source.key,
107
- :target => edge.target.key
108
- }
134
+ def assign_depth(u,v)
135
+ u.depth = 0 unless u.depth
136
+ v.depth = u.depth + 1
137
+ end
138
+
139
+ def add_edge(u,v)
140
+ raise ArgumentError unless Method === u && Method === v
141
+ assign_depth(u,v)
142
+ @graph.add_edge(u,v)
143
+ end
144
+ end
145
+
146
+ # shows which line in a method called which method
147
+ class LineGraph
148
+ extend Forwardable
149
+ def_delegators :@graph, :vertices, :write_to_graphic_file
150
+
151
+ def initialize
152
+ @graph = RGL::DirectedAdjacencyGraph.new
153
+ end
154
+
155
+ def add_edge(u,v)
156
+ raise ArgumentError unless Call === u && Method === v
157
+ @graph.add_edge(u,v)
158
+ end
159
+
160
+ def edges
161
+ @graph.edges.map { |edge|
162
+ {
163
+ :source => edge.source.key,
164
+ :target => edge.target.key
109
165
  }
110
- }.to_json
166
+ }
167
+ end
168
+ end
169
+
170
+ module LocalsHelper
171
+ # @target - binding
172
+ def self.get_locals(target, line_source)
173
+ scope_locals = target.eval("local_variables")
174
+ scope_locals.map! { |local| local.to_s }
175
+
176
+ tokens = Ripper.lex(line_source)
177
+
178
+ lvar, ivar, cvar = [], [], []
179
+
180
+ tokens.each { |token|
181
+ type = token[1]
182
+
183
+ case type
184
+ when :on_ident; lvar << token[2] if scope_locals.include? token[2]
185
+ when :on_ivar; ivar << token[2]
186
+ when :on_cvar; cvar << token[2]
187
+ else # do nothing
188
+ end
189
+ }
190
+
191
+ [lvar,ivar,cvar].flatten
111
192
  end
112
193
  end
113
194
 
114
195
  class Tracer
115
196
 
116
- attr_reader :local_values, :logger, :callstack, :method_graph,
197
+ attr_reader :local_values, :logger, :callstack,
198
+ :method_graph, :line_graph,
117
199
  :code
118
200
 
119
201
  def initialize(log_level = Logger::INFO)
120
202
  @method_graph = MethodGraph.new
203
+ @line_graph = LineGraph.new
121
204
  @callstack = []
122
205
  @local_values = {}
123
206
  @event_disable = []
@@ -130,7 +213,8 @@ class Kefka
130
213
 
131
214
  thread = Thread.new {
132
215
  @code = file.read
133
- #@callstack << create_top_level_method(file_path)
216
+ toplevel_method = create_toplevel_method(file_path)
217
+ @callstack << Call.new(:method => toplevel_method, :line => 1)
134
218
  eval(@code, TOPLEVEL_BINDING, file.path, 1)
135
219
  }
136
220
 
@@ -157,7 +241,8 @@ class Kefka
157
241
  when "class"
158
242
  @event_disable << true
159
243
  when "call"
160
- method_caller = @callstack.last
244
+ called_from = @callstack.last
245
+ caller_method = called_from.method
161
246
 
162
247
  method = Method.new(
163
248
  :classname => classname,
@@ -166,11 +251,10 @@ class Kefka
166
251
  :line => line
167
252
  )
168
253
 
169
- if method_caller
170
- @method_graph.add_edge(method_caller,method)
171
- end
254
+ @line_graph.add_edge(called_from.dup,method)
255
+ @method_graph.add_edge(caller_method,method)
172
256
 
173
- @callstack << method
257
+ @callstack << Call.new(:method => method, :line => line)
174
258
  when "line"
175
259
  # skip variables that should not be tracked
176
260
  # 1. anything in current lib (i.e __FILE__)
@@ -180,6 +264,10 @@ class Kefka
180
264
  # vars in that program
181
265
  #current_method = @callstack.last
182
266
 
267
+ # update callstack last method's line
268
+ if call = @callstack.last
269
+ call.line = line
270
+ end
183
271
  # given current file & line, determine what method I am in
184
272
 
185
273
  method = @method_graph.vertices
@@ -190,7 +278,7 @@ class Kefka
190
278
  if method
191
279
  line_source = method.source_at_line(line)
192
280
 
193
- iseq_key = [file, line].join(":")
281
+ iseq_key = [id,file,line].join(":")
194
282
  @local_values[iseq_key] = get_values_of_locals_from_binding(binding, line_source)
195
283
  end
196
284
  when "return"
@@ -204,31 +292,15 @@ class Kefka
204
292
  end
205
293
 
206
294
  def get_values_of_locals_from_binding(target, line_source)
207
- locals = get_locals(target,line_source)
295
+ locals = LocalsHelper.get_locals(target,line_source)
208
296
  locals.inject({}) do |result,l|
209
297
  val = target.eval(l.to_s)
210
298
  val = deep_copy(val)
211
- result.merge!({ l => val })
299
+ result.merge!({ l => val.to_s })
212
300
  result
213
301
  end
214
302
  end
215
303
 
216
- def get_locals(target, line_source)
217
- scope_locals = target.eval("local_variables")
218
- scope_locals.map! { |local| local.to_s }
219
-
220
- tokens = Ripper.lex(line_source)
221
-
222
- tokens.select! { |token|
223
- type = token[1]
224
- type == :on_ident || type == :on_ivar || type == :on_cvar
225
- }
226
-
227
- possible_line_variables = tokens.map { |token| token[2] }
228
- line_variables = scope_locals & possible_line_variables
229
- line_variables
230
- end
231
-
232
304
  def deep_copy(val)
233
305
  Marshal.load(Marshal.dump(val))
234
306
  rescue TypeError
@@ -236,14 +308,14 @@ class Kefka
236
308
  end
237
309
 
238
310
 
239
- def create_top_level_method(file)
311
+ def create_toplevel_method(file_path)
240
312
  method = Method.new(
241
- :classname => "Object",
242
- :id => "<main>",
243
- :file => file,
313
+ :classname => "Toplevel",
314
+ :id => nil,
315
+ :file => file_path,
244
316
  :line => 1
245
317
  )
246
- method.source = File.readlines(file).join
318
+ method.source = File.readlines(file_path).join
247
319
  return method
248
320
  end
249
321
 
@@ -2,13 +2,6 @@ var jqSelectorEscape = function(text) {
2
2
  return text.replace(/([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, "\\$&");
3
3
  };
4
4
 
5
- var outputTrace = function(data) {
6
- displayInput(data.input);
7
- displayGraphiz(data.is_graphviz_installed);
8
- createCodeBubbles(data.graph);
9
- displayLocals(data.locals);
10
- }
11
-
12
5
  var displayInput = function(input) {
13
6
  $("div#input").last().append(input);
14
7
  };
@@ -21,58 +14,137 @@ var displayGraphiz = function(is_graphviz_installed) {
21
14
  }
22
15
  };
23
16
 
24
- var createCodeBubbles = function(graph) {
25
- console.log(graph);
17
+ var setupBubble = function(bubble, method) {
18
+ var $bubble, xPos, key, header;
26
19
 
27
- var xPos = 0;
28
- var bubbleDiv, $bubble, $code,
29
- key, header,
30
- lineCount, column;
20
+ $bubble = bubble;
31
21
 
32
- var methods = graph.vertices;
22
+ $bubble.append(method.source);
33
23
 
34
- for (var i = 0; i < methods.length; i++ )
35
- {
36
- bubbleDiv = "<div class='bubble'></div>";
37
- $(bubbleDiv).appendTo("#codeGraph");
24
+ // set id for bubble table
25
+ key = method.id + ":" + method.file + ":" + method.line;
26
+ $bubble.attr("id",key);
38
27
 
39
- $bubble = $("#codeGraph .bubble").last();
40
- $bubble.append(methods[i].source);
28
+ // set header of bubble div
29
+ header = method.file + ":" + method.line;
30
+ $bubble.prepend("<pre><span class='methodHeader'>" + header + "</span></pre>");
41
31
 
42
- // set id for bubble table
43
- key = methods[i].file + ":" + methods[i].line;
44
- $bubble.attr("id",key);
32
+ // set column position
33
+ xPos = method.depth * 200;
45
34
 
46
- // set header of bubble div
47
- header = methods[i].file + ":" + methods[i].line;
48
- $bubble.prepend("<pre><span class='methodHeader'>" + header + "</span></pre>");
35
+ // position bubble table
36
+ $bubble.css("position", "relative")
37
+ .css("left", xPos);
49
38
 
50
- //callerKey = methodTable[key].caller.method.key;
51
- //$callerBubble = $("div.bubble#" + jqSelectorEscape(callerKey));
52
- //xPos = $callerBubble.position().left + 200;
39
+ // hide bubbles in deeper levels
53
40
 
54
- // position bubble table
55
- $bubble.css("position", "relative")
56
- .css("left", xPos);
41
+ if (method.depth != 0) {
42
+ $bubble.hide();
43
+ }
44
+ };
57
45
 
58
- // add column for displaying local values
59
- lineCount = $bubble.find("td.line-numbers a").length;
46
+ var addExpandColumn = function(bubble,method) {
47
+ var column, $code, $bubble;
60
48
 
61
- column = "<td class='locals'>";
62
- column += "<pre>";
49
+ $bubble = bubble;
63
50
 
64
- for (var j = methods[i].line; j < methods[i].line + lineCount; j++)
65
- {
66
- column += "<span id='line" + j + "'></span>\n";
67
- }
51
+ column = "<td class='expand'>";
52
+ column += "<pre>";
68
53
 
69
- column += "</pre>";
70
- column += "</td>";
54
+ for (var j = method.line; j <= method.end_line ; j++)
55
+ {
56
+ column += "<span id='line" + j + "'></span>\n";
57
+ }
58
+
59
+ column += "</pre>";
60
+ column += "</td>";
61
+
62
+ $code = $bubble.find("td.code");
63
+ $code.after(column);
64
+ };
65
+
66
+ var addLocalsColumn = function(bubble,method) {
67
+ var column, $expand, $bubble;
68
+
69
+ $bubble = bubble;
71
70
 
72
- $code = $bubble.find("td.code");
73
- $code.after(column);
71
+ column = "<td class='locals'>";
72
+ column += "<pre>";
74
73
 
74
+ for (var j = method.line; j <= method.end_line; j++)
75
+ {
76
+ column += "<span id='line" + j + "'></span>\n";
75
77
  }
78
+
79
+ column += "</pre>";
80
+ column += "</td>";
81
+
82
+ $expand = $bubble.find("td.expand");
83
+ $expand.after(column);
84
+ };
85
+
86
+ var setupCall = function(call, edges) {
87
+ var keyTokens, methodName, file, line,
88
+ $bubbles, $line;
89
+
90
+ keyTokens = call.split(":");
91
+ methodName = keyTokens[0];
92
+ file = keyTokens[1];
93
+ line = keyTokens[2];
94
+
95
+ $bubbles = $("div.bubble").filter(function(){
96
+ return this.id.match(methodName) && this.id.match(file);
97
+ });
98
+
99
+ $line = $bubbles.find("td.expand span#line" + line).first();
100
+ $line.data("key",call);
101
+
102
+ $line.html("<div class='expand'>+</div>");
103
+ $line.find("div.expand").first().click(function() {
104
+ return function(that){
105
+ _.chain(edges)
106
+ .filter( function(edge){ return edge.source == $(that).parent().data("key"); })
107
+ .map( function(edge){ console.log(edge);return edge.target })
108
+ .each( function(target){
109
+ $("#codeGraph .bubble#" + jqSelectorEscape(target))
110
+ .first()
111
+ .toggle();
112
+ });
113
+ }(this);
114
+ });
115
+
116
+ };
117
+
118
+ var createCodeBubbles = function(vertices,edges) {
119
+ console.log(vertices);
120
+ console.log(edges);
121
+
122
+ var xPos = 0;
123
+ var bubbleDiv, $bubble;
124
+
125
+ var methods = vertices;
126
+
127
+ _.each(methods, function(method){
128
+ bubbleDiv = "<div class='bubble'></div>";
129
+ $(bubbleDiv).appendTo("#codeGraph");
130
+
131
+ $bubble = $("#codeGraph .bubble").last();
132
+
133
+ setupBubble($bubble, method);
134
+ addExpandColumn($bubble,method);
135
+ addLocalsColumn($bubble,method);
136
+ });
137
+
138
+ var calls = _.map(edges, function(edge) { return edge.source });
139
+
140
+ // for each call, get the source (key -> method:line)
141
+ // add an 'expand' button on the source
142
+ // on button, add click handler that will
143
+
144
+ _.each(calls, function(call){
145
+ setupCall(call, edges);
146
+ });
147
+
76
148
  };
77
149
 
78
150
  var displayLocals = function(locals) {
@@ -93,11 +165,12 @@ var displayLocals = function(locals) {
93
165
  }
94
166
 
95
167
  keys = key.split(":");
96
- file = keys[0];
97
- line = keys[1];
168
+ methodName = keys[0];
169
+ file = keys[1];
170
+ line = keys[2];
98
171
 
99
172
  $bubbles = $("div.bubble").filter(function(){
100
- return this.id.match(file);
173
+ return this.id.match(methodName) && this.id.match(file);
101
174
  });
102
175
 
103
176
  $line = $bubbles.find("td.locals span#line" + line).first();
@@ -105,6 +178,13 @@ var displayLocals = function(locals) {
105
178
  }
106
179
  };
107
180
 
181
+ var outputTrace = function(data) {
182
+ //displayInput(data.input);
183
+ //displayGraphiz(data.is_graphviz_installed);
184
+ createCodeBubbles(data.vertices,data.edges);
185
+ displayLocals(data.locals);
186
+ }
187
+
108
188
  $(document).ready(function(){
109
189
 
110
190
  $.ajax({