silhouette 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +10 -1
- data/Rakefile +4 -2
- data/bin/silhouette +2 -29
- data/bin/silhouette_coverage +70 -0
- data/bin/silrun +83 -0
- data/lib/silhouette.rb +5 -13
- data/lib/silhouette/converter.rb +102 -0
- data/lib/silhouette/coverage.rb +604 -0
- data/lib/silhouette/default.css +68 -0
- data/lib/silhouette/emitters.rb +176 -0
- data/lib/silhouette/finder.rb +138 -0
- data/lib/silhouette/light.css +63 -0
- data/lib/silhouette/process.rb +3 -0
- data/lib/silhouette/processor.rb +8 -279
- data/lib/silhouette/setup.rb +102 -0
- data/silhouette_ext.c +66 -23
- data/test/test.rb +8 -1
- metadata +54 -22
- data/test/silhouette.out +0 -0
data/lib/silhouette/processor.rb
CHANGED
@@ -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(*
|
189
|
-
@io.write "! #{args.join(' ')}\n"
|
22
|
+
def process_start(*a)
|
190
23
|
end
|
191
24
|
|
192
|
-
def process_end(*
|
193
|
-
@io.write "@ #{args.join(' ')}\n"
|
25
|
+
def process_end(*a)
|
194
26
|
end
|
195
27
|
|
196
|
-
def process_method(*
|
197
|
-
@io.write "& #{args.join(' ')}\n"
|
28
|
+
def process_method(*a)
|
198
29
|
end
|
199
30
|
|
200
|
-
def process_file(*
|
201
|
-
@io.write "* #{args.join(' ')}\n"
|
31
|
+
def process_file(*a)
|
202
32
|
end
|
203
33
|
|
204
|
-
def
|
205
|
-
@io.write "c #{args.join(' ')}\n"
|
34
|
+
def process_line(*a)
|
206
35
|
end
|
207
36
|
|
208
|
-
def
|
209
|
-
@io.write "r #{args.join(' ')}\n"
|
37
|
+
def process_call(*a)
|
210
38
|
end
|
211
39
|
|
212
|
-
def
|
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
|
data/silhouette_ext.c
CHANGED
@@ -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
|
-
//
|
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
|
-
|
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(¤t_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
|
-
|
192
|
-
|
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
|
-
|
221
|
-
|
222
|
-
|
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
|
}
|