kefka 0.0.4 → 0.0.5

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/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({