silhouette 1.0.0 → 2.0.0

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.
@@ -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
  }