silhouette 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,161 +1,7 @@
1
1
  require 'pp'
2
2
 
3
3
  module Silhouette
4
-
5
- class InvalidFormat < Exception; end
6
-
7
- def self.emitters
8
- out = []
9
- constants.each do |name|
10
- con = const_get(name)
11
- if Class === con and con.superclass == Emitter
12
- out << con
13
- end
14
- end
15
- out
16
- end
17
-
18
- def self.find_emitter(io)
19
- if io.kind_of? String
20
- raise "Unknown file" unless File.exists?(io)
21
- io = File.open(io)
22
- end
23
-
24
- emitters.each do |em|
25
- begin
26
- return em.new(io)
27
- rescue InvalidFormat
28
- end
29
- end
30
-
31
- raise InvalidFormat, "Unable to find valid emitter"
32
- end
33
-
34
- class Emitter
35
- def initialize(processor=nil)
36
- @processor = processor
37
- end
38
4
 
39
- attr_accessor :processor
40
- end
41
-
42
- class BinaryEmitter < Emitter
43
- MAGIC = "<>"
44
-
45
- def initialize(file, processor=nil)
46
- if file.kind_of? String
47
- raise "Unknown file" unless File.exists?(file)
48
- @io = File.open(file)
49
- else
50
- @io = file
51
- end
52
-
53
- magic = @io.read(2)
54
- raise InvalidFormat unless magic == MAGIC
55
-
56
- @method_size = "S"
57
- @file_size = "S"
58
- @ret_call_fmt = "i#{@method_size}#{@file_size}ii"
59
- @ret_call_size = 16
60
- super(processor)
61
- end
62
-
63
- class DoneParsing < Exception; end
64
-
65
- def parse
66
- begin
67
- loop { emit }
68
- rescue DoneParsing
69
- end
70
- end
71
-
72
- FIXED_SIZE = {
73
- ?c => 20,
74
- ?r => 20,
75
- ?@ => 8
76
- }
77
-
78
- def next_cmd
79
- str = @io.read(1)
80
- raise DoneParsing unless str
81
-
82
- cmd = str[0]
83
- if [?!, ?*, ?&].include? cmd
84
- size = @io.read(4).unpack("i").first
85
- else
86
- size = FIXED_SIZE[cmd]
87
- end
88
-
89
- [cmd, size, @io.read(size)]
90
- end
91
-
92
- def parse
93
- begin
94
- # Use "while true" instead of "loop" because loop is
95
- # really a method call.
96
- while true
97
- data = @io.read(1)
98
- raise DoneParsing unless data
99
-
100
- cmd = data[0]
101
-
102
- # These are hardcoded in here for speed.
103
- if cmd == ?r or cmd == ?c
104
- size = @ret_call_size
105
- elsif cmd == ?@
106
- size = 8
107
- else
108
- size = @io.read(4).unpack("i").first
109
- end
110
-
111
- proc = @processor
112
- data = @io.read(size)
113
-
114
- case cmd
115
- when ?r
116
- parts = data.unpack(@ret_call_fmt)
117
- @processor.process_return(*parts)
118
- # return [:return, *parts]
119
- when ?c
120
- parts = data.unpack(@ret_call_fmt)
121
- @processor.process_call(*parts)
122
- # return [:call, *parts]
123
- when ?!
124
- parts = data.unpack("Z#{size - 8}ii")
125
- @processor.process_start(*parts)
126
- # return [:start, *parts]
127
- when ?@
128
- parts = data.unpack("ii")
129
- @processor.process_end(*parts)
130
- # return [:end, *parts]
131
- when ?&
132
- parts = data.unpack("ia#{size - 4}")
133
- parts += parts.pop.split("\0")
134
- @processor.process_method(*parts)
135
- # return [:method, *parts]
136
- when ?*
137
- parts = data.unpack("iZ#{size - 4}")
138
- @processor.process_file(*parts)
139
- # return [:file, *parts]
140
- when ?(
141
- @method_size = "I"
142
- @ret_call_size += 2
143
- @ret_call_fmt = "i#{@method_size}#{@file_size}ii"
144
- when ?)
145
- @file_size = "I"
146
- @ret_call_size += 2
147
- @ret_call_fmt = "i#{@method_size}#{@file_size}ii"
148
- else
149
- raise "Unknown type '#{cmd.chr}'"
150
- end
151
- end
152
-
153
- # This means we're done.
154
- rescue DoneParsing
155
- end
156
- end
157
- end
158
-
159
5
  class Processor
160
6
  def initialize(emitter)
161
7
  @emitter = emitter
@@ -172,97 +18,26 @@ module Silhouette
172
18
  end
173
19
  end
174
20
  end
175
- end
176
-
177
- # NOTE: This uses IO#write instead of IO#puts
178
- # because IO#write is faster as it does a quick test
179
- # to see if the argument is already a string and just
180
- # writes it if it is. IO#puts calls respond_to? on
181
- # all arguments to see if they are strings, which is
182
- # a lot slower if you do this 20,000 times.
183
- class ASCIIConverter < Processor
184
- def initialize(file)
185
- @io = File.open(file, "w")
186
- end
187
21
 
188
- def process_start(*args)
189
- @io.write "! #{args.join(' ')}\n"
22
+ def process_start(*a)
190
23
  end
191
24
 
192
- def process_end(*args)
193
- @io.write "@ #{args.join(' ')}\n"
25
+ def process_end(*a)
194
26
  end
195
27
 
196
- def process_method(*args)
197
- @io.write "& #{args.join(' ')}\n"
28
+ def process_method(*a)
198
29
  end
199
30
 
200
- def process_file(*args)
201
- @io.write "* #{args.join(' ')}\n"
31
+ def process_file(*a)
202
32
  end
203
33
 
204
- def process_call(*args)
205
- @io.write "c #{args.join(' ')}\n"
34
+ def process_line(*a)
206
35
  end
207
36
 
208
- def process_return(*args)
209
- @io.write "r #{args.join(' ')}\n"
37
+ def process_call(*a)
210
38
  end
211
39
 
212
- def close
213
- @io.close
214
- end
215
- end
216
-
217
- class ASCIIConverterLong < ASCIIConverter
218
-
219
- def initialize(file)
220
- @methods = Hash.new
221
- @files = Hash.new
222
- @last_method = nil
223
- @last_series = nil
224
- @skip_return = false
225
- super(file)
226
- end
227
- def process_method(idx, klass, kind, meth)
228
- @methods[idx] = [klass, kind, meth].to_s
229
- end
230
-
231
- def process_file(idx, file)
232
- @files[idx] = file
233
- end
234
-
235
- def process_call(thread, meth, file, line, clock)
236
- @io.puts "c #{thread} #{@methods[meth]} #{@files[file]} #{line} #{clock}"
237
- end
238
-
239
- def process_return(thread, meth, file, line, clock)
240
- @io.puts "r #{thread} #{@methods[meth]} #{clock}"
241
- end
242
-
243
- def process_call_rep(thread, meth, file, line, clock)
244
- if @last_method == [thread, meth, file, line] and @last_series
245
- @last_series += 1
246
- @skip_return = true
247
- else
248
- @io.puts "cal #{thread} #{@methods[meth]} #{meth} #{@files[file]} #{line} #{clock}"
249
- end
250
-
251
- @last_method = [thread, meth, file, line]
252
- end
253
-
254
- def process_return_rep(thread, meth, file, line, clock)
255
- if @last_method == [thread, meth, file, line]
256
- @last_series = 1 unless @last_series
257
- elsif @last_series
258
- p [thread, meth, @methods[meth]]
259
- p @last_method
260
- @io.puts "rep #{@last_series}"
261
- @last_series = nil
262
- @skip_return = false
263
- end
264
- return if @skip_return
265
- @io.puts "ret #{thread} #{@methods[meth]} #{clock}"
40
+ def process_return(*a)
266
41
  end
267
42
  end
268
43
 
@@ -343,53 +118,7 @@ module Silhouette
343
118
  def data=(d)
344
119
  @map = d
345
120
  end
346
-
347
- def run
348
- @emitter.parse do |kind, *args|
349
- case kind
350
- when :start
351
- @directory, @clock_per_sec, @start_clock = *args
352
- when :end
353
- @final_clock, @profiler_cost = *args
354
- when :method
355
- idx = args.shift
356
- @methods[idx] = args
357
- when :file
358
- idx = args.shift
359
- @files[idx] = args
360
- when :call
361
- thread, method, file, line, clock = *args
362
- stack(thread).push [clock, 0.0, method]
363
- when :return
364
- thread, method, file, line, clock = *args
365
- if tick = stack(thread).pop
366
- if tick.last != method
367
- STDERR.puts "Unmatched return for #{method} (#{tick.last})"
368
- return
369
- end
370
-
371
- if @per_callsite
372
- key = [method, loc]
373
- else
374
- key = method
375
- end
376
-
377
- data = (@map[key] ||= [0, 0.0, 0.0, key])
378
- data[0] += 1
379
- cost = (clock.to_f - tick[0]) / @clock_per_sec
380
- data[2] += cost
381
- data[1] += cost - tick[1]
382
- @total += cost
383
-
384
- # Add to the callers callee cost.
385
- if last = stack(thread).last
386
- last[1] += cost
387
- end
388
- end
389
- end
390
- end
391
- end
392
-
121
+
393
122
  def save(file)
394
123
  File.open(file, "w") do |f|
395
124
  f << Marshal.dump(self)
@@ -0,0 +1,102 @@
1
+ require "silhouette_ext"
2
+
3
+ class Silhouette::Options
4
+
5
+ def initialize
6
+ @compress = true
7
+ @follow_lines = false
8
+ @file = "silhouette.out"
9
+ @coverage = false
10
+ @all = false
11
+ @describe = false
12
+ @location = false
13
+ end
14
+
15
+ attr_accessor :compress, :follow_lines, :file, :coverage
16
+ attr_accessor :all, :describe, :location
17
+
18
+ def detect_compressor(file)
19
+ `which bzip2`
20
+ if $?.exitstatus == 0
21
+ file.replace "#{file}.bz2"
22
+ return "bzip2 -c > #{file}"
23
+ end
24
+
25
+ `which gzip`
26
+ if $?.exitstatus == 0
27
+ file.replace "#{file}.gz"
28
+ return "gzip -c > #{file}"
29
+ end
30
+ return nil
31
+ end
32
+
33
+ def import_env
34
+ if ENV["SILHOUETTE_FILE"]
35
+ @file = ENV["SILHOUETTE_FILE"]
36
+ end
37
+
38
+ if ENV["NO_COMPRESS"]
39
+ @compress = false
40
+ end
41
+
42
+ if ENV["FOLLOW_LINES"]
43
+ @follow_lines = true
44
+ end
45
+
46
+ if ENV["COVERAGE"]
47
+ @coverage = true
48
+ end
49
+
50
+ if ENV["ALL"]
51
+ @all = true
52
+ end
53
+ end
54
+
55
+ def setup_args
56
+ args = []
57
+
58
+ gzip = nil
59
+
60
+ output = @file
61
+
62
+ unless IO === @file
63
+ if @compress
64
+ compress = detect_compressor(file)
65
+ if compress
66
+ gzip = IO.popen(compress, "w")
67
+ output = gzip
68
+ else
69
+ # Couldn't auto detect a compressor.
70
+ end
71
+ end
72
+ end
73
+
74
+ args << output
75
+
76
+ if @follow_lines
77
+ args << true
78
+ end
79
+
80
+ if @coverage
81
+ if @describe
82
+ STDERR.puts "Generating coverage information only."
83
+ end
84
+ args << Silhouette::COVERAGE
85
+ end
86
+
87
+ if @all
88
+ args << Silhouette::ALL
89
+ end
90
+
91
+ unless @location
92
+ @location = @file
93
+ end
94
+
95
+ at_exit {
96
+ Silhouette.stop_profile
97
+ STDERR.puts "Flushed profile information to #{@location}"
98
+ }
99
+
100
+ return [args, @location]
101
+ end
102
+ end
@@ -21,12 +21,16 @@ static char *method_magic = "&";
21
21
  static char *file_magic = "*";
22
22
  static char *call_magic = "c";
23
23
  static char *return_magic = "r";
24
+ static char *line_magic = "l";
24
25
  static char *null_ptr = "\0";
25
26
  static int profiler_cost = 0;
26
27
  static char *method_size_magic = "(";
27
28
  static char *file_size_magic = ")";
28
29
  static int method_idx_size = 0;
29
30
  static int file_idx_size = 0;
31
+ static int emit_lines = 0;
32
+ static int last_file = -1;
33
+ static int last_line = -1;
30
34
 
31
35
  #define PER_TIME 10
32
36
  #define HASH_SIZE 128
@@ -51,13 +55,6 @@ extprof_event_hook(rb_event_t event, NODE *node,
51
55
  unsigned int i, j, m_idx, f_idx, size;
52
56
  unsigned short s_m_idx, s_f_idx;
53
57
 
54
- if(!mid) return;
55
-
56
- method = rb_id2name(mid);
57
- if(!method) {
58
- method = "<undefined>";
59
- }
60
-
61
58
  /* If we've forked, dont profile anymore. */
62
59
  if(getpid() != profiling_pid) {
63
60
  return;
@@ -67,6 +64,15 @@ extprof_event_hook(rb_event_t event, NODE *node,
67
64
  if (profiling) return;
68
65
  profiling++;
69
66
 
67
+ if(!mid) {
68
+ method = "(main)";
69
+ } else {
70
+ method = rb_id2name(mid);
71
+ if(!method) {
72
+ method = "<undefined>";
73
+ }
74
+ }
75
+
70
76
  if(rb_obj_is_kind_of(self, rb_cModule)) {
71
77
  kind = ".";
72
78
  klass = rb_class_name(self);
@@ -76,6 +82,9 @@ extprof_event_hook(rb_event_t event, NODE *node,
76
82
  *(method_ent + i) = '.';
77
83
  } else {
78
84
  kind = "#";
85
+ if(!in_klass) {
86
+ in_klass = rb_cObject;
87
+ }
79
88
  klass = rb_class_name(in_klass);
80
89
  i = RSTRING(klass)->len;
81
90
  s_klass = RSTRING(klass)->ptr;
@@ -112,7 +121,7 @@ extprof_event_hook(rb_event_t event, NODE *node,
112
121
  if(node) {
113
122
  file = node->nd_file;
114
123
  line = nd_line(node);
115
- // sprintf(file_ent, "%s:%d", node->nd_file, nd_line(node));
124
+ // printf("%s:%d\n", file, line);
116
125
  if(!st_lookup(file_tbl, (st_data_t)(node->nd_file), (st_data_t*)&f_idx)) {
117
126
  f_idx = ++file_idx;
118
127
  if(file_idx > USHRT_MAX) {
@@ -139,8 +148,16 @@ extprof_event_hook(rb_event_t event, NODE *node,
139
148
  STR(rb_class_name(klass)), kind, method, CLOCK)
140
149
  #define PL_EXT(type) fprintf(pro_file, #type " %x %s %x %s %s %s %d %f\n", (int)rb_thread_current(), \
141
150
  STR(rb_class_name(klass)), (int)self, kind, method, file, line, CLOCK)
142
-
151
+
143
152
  switch(event) {
153
+ case RUBY_EVENT_LINE:
154
+ /* Suppress double hits on the same file and line. */
155
+ if(last_file == f_idx && last_line == line)
156
+ goto done;
157
+ last_file = f_idx;
158
+ last_line = line;
159
+ fwrite(line_magic, 1, 1, pro_file);
160
+ goto output;
144
161
  case RUBY_EVENT_RETURN:
145
162
  case RUBY_EVENT_C_RETURN:
146
163
  fwrite(return_magic, 1, 1, pro_file);
@@ -166,16 +183,11 @@ output:
166
183
  fwrite(&s_f_idx, sizeof(s_f_idx), 1, pro_file);
167
184
  }
168
185
 
169
- if(node) {
170
- j = nd_line(node);
171
- } else {
172
- j = 0;
173
- }
174
- fwrite(&j, 4, 1, pro_file);
186
+ fwrite(&line, 4, 1, pro_file);
175
187
  fwrite(&current_clock, 4, 1, pro_file);
176
188
  break;
177
189
  }
178
-
190
+ done:
179
191
  profiler_cost = profiler_cost + (CLOCK - current_clock);
180
192
  profiling--;
181
193
  }
@@ -183,13 +195,29 @@ output:
183
195
  static VALUE extprof_start(int argc, VALUE *argv, VALUE self) {
184
196
  struct timeval tv;
185
197
  char path[1024];
186
- int size, i;
198
+ int size, i, mask;
199
+
200
+ mask = -1;
201
+
202
+ emit_lines = 0;
187
203
 
188
204
  if(argc == 0) {
189
205
  pro_file = fopen("silhouette.out","w");
190
206
  } else {
191
- StringValue(argv[0]);
192
- pro_file = fopen(STR(argv[0]), "w");
207
+ if(rb_obj_is_kind_of(argv[0], rb_cIO)) {
208
+ pro_file = fdopen(NUM2INT(
209
+ rb_funcall(argv[0], rb_intern("fileno"), 0)), "w");
210
+ } else {
211
+ StringValue(argv[0]);
212
+ pro_file = fopen(STR(argv[0]), "w");
213
+ }
214
+ if(argc == 2) {
215
+ if(argv[1] == Qfalse || argv[1] == Qtrue) {
216
+ emit_lines = RTEST(argv[1]);
217
+ } else {
218
+ mask = NUM2INT(argv[1]);
219
+ }
220
+ }
193
221
  }
194
222
 
195
223
  profiling_pid = getpid();
@@ -216,10 +244,17 @@ static VALUE extprof_start(int argc, VALUE *argv, VALUE self) {
216
244
  gettimeofday(&tv, NULL);
217
245
  fprintf(pro_file, "@ %d %d %d\n", (int)tv.tv_sec,
218
246
  (int)tv.tv_usec, CLOCK);
219
- */
220
- rb_add_event_hook(extprof_event_hook,
221
- RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
222
- RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN);
247
+ */
248
+ if(mask == -1) {
249
+ mask = RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
250
+ RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN;
251
+
252
+ if(emit_lines) {
253
+ mask = mask | RUBY_EVENT_LINE;
254
+ }
255
+ }
256
+
257
+ rb_add_event_hook(extprof_event_hook, mask);
223
258
 
224
259
  return Qtrue;
225
260
  }
@@ -247,6 +282,14 @@ static VALUE extprof_end(VALUE self) {
247
282
  void Init_silhouette_ext() {
248
283
  VALUE extprof;
249
284
  extprof = rb_define_module("Silhouette");
285
+ rb_define_const(extprof, "CALL", INT2NUM(RUBY_EVENT_CALL | RUBY_EVENT_RETURN));
286
+ rb_define_const(extprof, "C_CALL", INT2NUM(RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN));
287
+ rb_define_const(extprof, "LINE", INT2NUM(RUBY_EVENT_LINE));
288
+ rb_define_const(extprof, "COVERAGE", INT2NUM(RUBY_EVENT_LINE | RUBY_EVENT_END));
289
+ rb_define_const(extprof, "CALLS", INT2NUM(RUBY_EVENT_CALL | RUBY_EVENT_RETURN |
290
+ RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN));
291
+ rb_define_const(extprof, "ALL", INT2NUM(RUBY_EVENT_ALL));
292
+
250
293
  rb_define_singleton_method(extprof, "start_profile", extprof_start, -1);
251
294
  rb_define_singleton_method(extprof, "stop_profile", extprof_end, 0);
252
295
  }