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