silhouette 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +10 -0
- data/README +57 -0
- data/Rakefile +34 -0
- data/bin/silhouette +128 -0
- data/extconf.rb +18 -0
- data/lib/silhouette.rb +16 -0
- data/lib/silhouette/processor.rb +653 -0
- data/silhouette_ext.c +254 -0
- data/test/silhouette.out +0 -0
- data/test/test.rb +33 -0
- metadata +50 -0
data/Manifest.txt
ADDED
data/README
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
Here is an example of a generated profile report:
|
3
|
+
|
4
|
+
$ silhouette silhouette.out
|
5
|
+
Number of threads: 1
|
6
|
+
Profiling based on method call.
|
7
|
+
Cost of profiler: 0.45 seconds.
|
8
|
+
|
9
|
+
Flat profile (0.76 total seconds):
|
10
|
+
% total self self total
|
11
|
+
time seconds seconds calls ms/call ms/call name
|
12
|
+
19.74 0.15 0.15 1228 0.12 0.28 Silhouette::DefaultProfiler#process_return
|
13
|
+
10.53 0.23 0.08 1 80.00 640.00 Silhouette::BinaryEmitter#parse
|
14
|
+
6.58 0.28 0.05 6151 0.01 0.01 Array#[]
|
15
|
+
5.26 0.32 0.04 1229 0.03 0.07 Silhouette::DefaultProfiler#process_call
|
16
|
+
5.26 0.36 0.04 4966 0.01 0.01 IO#read
|
17
|
+
5.26 0.40 0.04 3842 0.01 0.01 Object#===
|
18
|
+
3.95 0.43 0.03 4474 0.01 0.01 Hash#[]
|
19
|
+
...
|
20
|
+
|
21
|
+
Call Tree Profile:
|
22
|
+
index calls ms/ self total
|
23
|
+
call sec sec
|
24
|
+
1228/1 - - - Silhouette::BinaryEmitter#parse [243]
|
25
|
+
[256] 1228 0.12 0.15 0.34 Silhouette::DefaultProfiler#process_return
|
26
|
+
2 0.01 0.00 0.02 Array#[] [4]
|
27
|
+
2 0.01 0.00 0.02 Hash#[] [162]
|
28
|
+
1 0.05 0.00 0.06 Silhouette::ProfileNode#add_cost [265]
|
29
|
+
1 0.02 0.00 0.02 Silhouette::ProfileNode#inc_call! [263]
|
30
|
+
2 0.01 0.00 0.02 Array#last [260]
|
31
|
+
1 0.01 0.00 0.02 Hash#[]= [37]
|
32
|
+
----------------------------------------------------------------------
|
33
|
+
...
|
34
|
+
|
35
|
+
parent called/called child - - - Parent method name [parent_id]
|
36
|
+
...
|
37
|
+
[method_id] calls 0.03 0.04 0.08 Method name
|
38
|
+
called child 0.02 0.00 0.02 Child method name [child_id]
|
39
|
+
...
|
40
|
+
|
41
|
+
|
42
|
+
Explanation of Call Tree Profile:
|
43
|
+
|
44
|
+
For each method profiled, there is an entry that describes the method, it's parents
|
45
|
+
and it's direct children. Above we've shown a real world output from the profiler and
|
46
|
+
and explanation of each field.
|
47
|
+
|
48
|
+
First are listed all the parent methods that called the current method. The only stat
|
49
|
+
available for the parent is the number of times the parent was called and the number
|
50
|
+
of times the parent called the current method.
|
51
|
+
|
52
|
+
Next listed is the current method.
|
53
|
+
|
54
|
+
Finally, all the methods directly called by the current method are listed. The calls
|
55
|
+
column is only the number of times the current method called the child, not the
|
56
|
+
total number of calls the child received.
|
57
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
|
5
|
+
$VERBOSE = nil
|
6
|
+
|
7
|
+
spec = Gem::Specification.new do |s|
|
8
|
+
s.name = 'silhouette'
|
9
|
+
s.version = '1.0.0'
|
10
|
+
s.summary = 'A 2 stage profiler'
|
11
|
+
s.author = 'Evan Webb'
|
12
|
+
s.email = 'evan@fallingsnow.net'
|
13
|
+
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.files = File.read('Manifest.txt').split($/)
|
16
|
+
s.require_path = 'lib'
|
17
|
+
s.executables = ['silhouette']
|
18
|
+
s.default_executable = 'silhouette'
|
19
|
+
s.extensions = ['extconf.rb']
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'Build Gem'
|
23
|
+
Rake::GemPackageTask.new spec do |pkg|
|
24
|
+
pkg.need_tar = true
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Clean up'
|
28
|
+
task :clean => [ :clobber_package ]
|
29
|
+
|
30
|
+
desc 'Clean up'
|
31
|
+
task :clobber => [ :clean ]
|
32
|
+
|
33
|
+
# vim: syntax=Ruby
|
34
|
+
|
data/bin/silhouette
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require 'silhouette/processor'
|
4
|
+
|
5
|
+
output = nil
|
6
|
+
max = nil
|
7
|
+
entry = nil
|
8
|
+
depth = nil
|
9
|
+
ascii = nil
|
10
|
+
long = false
|
11
|
+
callsite = false
|
12
|
+
gzip = false
|
13
|
+
processed = nil
|
14
|
+
load = false
|
15
|
+
|
16
|
+
STDOUT.sync = true
|
17
|
+
|
18
|
+
opt = OptionParser.new do |opt|
|
19
|
+
=begin
|
20
|
+
opt.on("-o FILE", "Where to output data") { |output| }
|
21
|
+
opt.on("-c", "--combine", "Combine multiple data files.") do
|
22
|
+
data = Hash.new
|
23
|
+
ARGV.each do |file|
|
24
|
+
print "#{file}: "
|
25
|
+
rp = Silhouette.new(file)
|
26
|
+
rp.data = data
|
27
|
+
rp.parse
|
28
|
+
puts "done."
|
29
|
+
end
|
30
|
+
|
31
|
+
File.open(output, "w") do |f|
|
32
|
+
f << Marshal.dump(data)
|
33
|
+
end
|
34
|
+
puts "Data saved to #{output}. #{data.keys.size} data points."
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
=end
|
38
|
+
opt.on("-m", "--max MAX", "Only show the top N call sites") do |m|
|
39
|
+
max = m.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
opt.on("-e SIG","Only profile calls made from +SIG+") do |e|
|
43
|
+
entry = e
|
44
|
+
end
|
45
|
+
|
46
|
+
opt.on("-d DEPTH", "Only process calls +DEPTH+ levels down") do |o|
|
47
|
+
depth = o.to_i
|
48
|
+
end
|
49
|
+
|
50
|
+
opt.on("-s", "--site", "Profile based on method call and call site") do |o|
|
51
|
+
callsite = true
|
52
|
+
end
|
53
|
+
|
54
|
+
opt.on("-a OUT", "Convert a binary profile file to an ASCII one") do |a|
|
55
|
+
ascii = a
|
56
|
+
end
|
57
|
+
|
58
|
+
opt.on("-l", "Use the long format for the ASCII profile") do |l|
|
59
|
+
long = true
|
60
|
+
end
|
61
|
+
|
62
|
+
opt.on("-z", "Use GZIP when reading data") do |z|
|
63
|
+
gzip = z
|
64
|
+
end
|
65
|
+
|
66
|
+
opt.on("-p FILE", "Process the profile data and save the processed data") do |o|
|
67
|
+
processed = o
|
68
|
+
STDERR.puts "Saving processed data to #{processed}"
|
69
|
+
end
|
70
|
+
|
71
|
+
opt.on("-r FILE", "Load data and process directly") do |o|
|
72
|
+
load = o
|
73
|
+
end
|
74
|
+
|
75
|
+
opt.on("-h", "--help") do
|
76
|
+
puts opt
|
77
|
+
exit 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
opt.parse!
|
82
|
+
|
83
|
+
if load
|
84
|
+
rp = Marshal.load(File.open(load))
|
85
|
+
rp.print(STDOUT, max)
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
unless file = ARGV.shift
|
91
|
+
STDERR.puts "Please specify a file to process."
|
92
|
+
end
|
93
|
+
|
94
|
+
io = File.open(file)
|
95
|
+
|
96
|
+
if gzip
|
97
|
+
require 'zlib'
|
98
|
+
io = Zlib::GzipReader.new(io)
|
99
|
+
end
|
100
|
+
|
101
|
+
emit = Silhouette.find_emitter(io)
|
102
|
+
|
103
|
+
if ascii
|
104
|
+
if long
|
105
|
+
ap = Silhouette::ASCIIConverterLong.new(ascii)
|
106
|
+
else
|
107
|
+
ap = Silhouette::ASCIIConverter.new(ascii)
|
108
|
+
end
|
109
|
+
puts "Saving ASCII profile to #{ascii} using #{ap.class}"
|
110
|
+
emit.processor = ap
|
111
|
+
emit.parse
|
112
|
+
ap.close
|
113
|
+
puts "Saved."
|
114
|
+
exit
|
115
|
+
end
|
116
|
+
|
117
|
+
if entry
|
118
|
+
rp = Silhouette::EntryPointProfiler.new(file, entry, depth)
|
119
|
+
else
|
120
|
+
rp = Silhouette::DefaultProfiler.new(callsite)
|
121
|
+
end
|
122
|
+
emit.processor = rp
|
123
|
+
emit.parse
|
124
|
+
if processed
|
125
|
+
rp.save(processed)
|
126
|
+
STDERR.puts "Saved data to #{processed}."
|
127
|
+
end
|
128
|
+
rp.print(STDOUT,max)
|
data/extconf.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "mkmf"
|
2
|
+
|
3
|
+
if RUBY_VERSION >= "1.9"
|
4
|
+
if RUBY_RELEASE_DATE < "2005-03-17"
|
5
|
+
STDERR.print("Ruby version is too old\n")
|
6
|
+
exit(1)
|
7
|
+
end
|
8
|
+
elsif RUBY_VERSION >= "1.8"
|
9
|
+
if RUBY_RELEASE_DATE < "2005-03-22"
|
10
|
+
STDERR.print("Ruby version is too old #{RUBY_RELEASE_DATE}\n")
|
11
|
+
exit(1)
|
12
|
+
end
|
13
|
+
else
|
14
|
+
STDERR.print("Ruby version is too old\n")
|
15
|
+
exit(1)
|
16
|
+
end
|
17
|
+
|
18
|
+
create_makefile("silhouette_ext")
|
data/lib/silhouette.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
require "silhouette_ext"
|
3
|
+
|
4
|
+
at_exit {
|
5
|
+
STDERR.puts "Flushing profile information..."
|
6
|
+
Silhouette.stop_profile
|
7
|
+
}
|
8
|
+
|
9
|
+
if ENV["SILHOUETTE_FILE"]
|
10
|
+
file = ENV["SILHOUETTE_FILE"]
|
11
|
+
else
|
12
|
+
file = "silhouette.out"
|
13
|
+
end
|
14
|
+
|
15
|
+
STDERR.puts "Logging profile information to #{file}"
|
16
|
+
Silhouette.start_profile file
|
@@ -0,0 +1,653 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
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
|
+
|
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
|
+
class Processor
|
160
|
+
def initialize(emitter)
|
161
|
+
@emitter = emitter
|
162
|
+
@processors = Hash.new
|
163
|
+
methods.grep(/^process_(.*)/) do |meth|
|
164
|
+
@processors[$1.to_sym] = meth.to_sym
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def run
|
169
|
+
@emitter.parse do |kind, *args|
|
170
|
+
if meth = @processors[kind]
|
171
|
+
__send__(meth, *args)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
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
|
+
|
188
|
+
def process_start(*args)
|
189
|
+
@io.write "! #{args.join(' ')}\n"
|
190
|
+
end
|
191
|
+
|
192
|
+
def process_end(*args)
|
193
|
+
@io.write "@ #{args.join(' ')}\n"
|
194
|
+
end
|
195
|
+
|
196
|
+
def process_method(*args)
|
197
|
+
@io.write "& #{args.join(' ')}\n"
|
198
|
+
end
|
199
|
+
|
200
|
+
def process_file(*args)
|
201
|
+
@io.write "* #{args.join(' ')}\n"
|
202
|
+
end
|
203
|
+
|
204
|
+
def process_call(*args)
|
205
|
+
@io.write "c #{args.join(' ')}\n"
|
206
|
+
end
|
207
|
+
|
208
|
+
def process_return(*args)
|
209
|
+
@io.write "r #{args.join(' ')}\n"
|
210
|
+
end
|
211
|
+
|
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}"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
class CallTree
|
270
|
+
def initialize
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
class CallNode
|
275
|
+
def initialize
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
class ProfileNode
|
280
|
+
attr_accessor :total_sec, :self_sec, :calls
|
281
|
+
attr_accessor :callers, :children
|
282
|
+
|
283
|
+
attr_reader :key
|
284
|
+
def initialize(key, children=[])
|
285
|
+
@key = key
|
286
|
+
@total_sec = 0.0
|
287
|
+
@self_sec = 0.0
|
288
|
+
@calls = 0
|
289
|
+
@callers = []
|
290
|
+
@children = children
|
291
|
+
end
|
292
|
+
|
293
|
+
def inc_call!
|
294
|
+
@calls += 1
|
295
|
+
end
|
296
|
+
|
297
|
+
def add_cost(cost, last)
|
298
|
+
@total_sec += cost
|
299
|
+
@self_sec += (cost - last)
|
300
|
+
end
|
301
|
+
|
302
|
+
def percentage(total)
|
303
|
+
@self_sec / total * 100.0
|
304
|
+
end
|
305
|
+
|
306
|
+
def self_ms_per_call
|
307
|
+
(@self_sec * 1000.0 / @calls)
|
308
|
+
end
|
309
|
+
|
310
|
+
def total_ms_per_call
|
311
|
+
(@total_sec * 1000.0 / @calls)
|
312
|
+
end
|
313
|
+
|
314
|
+
alias method key
|
315
|
+
end
|
316
|
+
|
317
|
+
class DefaultProfiler < Processor
|
318
|
+
|
319
|
+
def initialize(per_callsite=false)
|
320
|
+
@threads = Hash.new
|
321
|
+
@map = Hash.new
|
322
|
+
@cs_map = Hash.new
|
323
|
+
@timestamps = []
|
324
|
+
@clocks = []
|
325
|
+
@pid = nil
|
326
|
+
@total = 0.0
|
327
|
+
@filters = []
|
328
|
+
@methods = Hash.new
|
329
|
+
@files = Hash.new
|
330
|
+
@per_callsite = per_callsite
|
331
|
+
end
|
332
|
+
|
333
|
+
def stack(thread)
|
334
|
+
@threads[thread] ||= []
|
335
|
+
end
|
336
|
+
|
337
|
+
attr_reader :directory, :timestamps
|
338
|
+
|
339
|
+
def data
|
340
|
+
@map
|
341
|
+
end
|
342
|
+
|
343
|
+
def data=(d)
|
344
|
+
@map = d
|
345
|
+
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
|
+
|
393
|
+
def save(file)
|
394
|
+
File.open(file, "w") do |f|
|
395
|
+
f << Marshal.dump(self)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
def process_start(*args)
|
400
|
+
@directory, @clock_per_sec, @start_clock = *args
|
401
|
+
end
|
402
|
+
|
403
|
+
def process_end(*args)
|
404
|
+
@final_clock, @profiler_cost = *args
|
405
|
+
end
|
406
|
+
|
407
|
+
def process_method(*args)
|
408
|
+
idx = args.shift
|
409
|
+
@methods[idx] = args
|
410
|
+
end
|
411
|
+
|
412
|
+
def process_file(*args)
|
413
|
+
idx = args.shift
|
414
|
+
@files[idx] = args
|
415
|
+
end
|
416
|
+
|
417
|
+
def process_call(thread, method, file, line, clock)
|
418
|
+
st = (@threads[thread] ||= [])
|
419
|
+
st.push [clock, 0.0, [method, file, line], []]
|
420
|
+
end
|
421
|
+
|
422
|
+
def process_return(thread, method, file, line, clock)
|
423
|
+
st = (@threads[thread] ||= [])
|
424
|
+
if tick = st.pop
|
425
|
+
=begin
|
426
|
+
if tick.last != method
|
427
|
+
STDERR.puts "Unmatched return for #{method} (#{tick.last})"
|
428
|
+
return
|
429
|
+
end
|
430
|
+
if @per_callsite
|
431
|
+
key = [method, loc]
|
432
|
+
else
|
433
|
+
key = method
|
434
|
+
end
|
435
|
+
=end
|
436
|
+
cost = (clock.to_f - tick[0]) / @clock_per_sec
|
437
|
+
|
438
|
+
# Add to the callers callee cost and child list.
|
439
|
+
if last = st.last
|
440
|
+
last[1] += cost
|
441
|
+
last[3] << method
|
442
|
+
caller = last[2]
|
443
|
+
else
|
444
|
+
caller = nil
|
445
|
+
end
|
446
|
+
|
447
|
+
# Record the data for the method.
|
448
|
+
key = method
|
449
|
+
node = (@map[key] ||= ProfileNode.new(key, tick.last))
|
450
|
+
node.inc_call!
|
451
|
+
node.add_cost cost, tick[1]
|
452
|
+
node.callers << caller.first if caller
|
453
|
+
|
454
|
+
# data = (@map[key] ||= [0, 0.0, 0.0, key, tick.last, []])
|
455
|
+
# data[0] += 1
|
456
|
+
# data[2] += cost
|
457
|
+
# data[1] += cost - tick[1]
|
458
|
+
# data[5] << caller.first if caller # Just the method index
|
459
|
+
|
460
|
+
# Record the data for the method at callsite
|
461
|
+
# key = [method, file, line]
|
462
|
+
# data = (@cs_map[key] ||= [0, 0.0, 0.0, key, tick.last, caller])
|
463
|
+
# data[0] += 1
|
464
|
+
# data[2] += cost
|
465
|
+
# data[1] += cost - tick[1]
|
466
|
+
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
def print(f=STDERR, max=nil)
|
471
|
+
print_flat_profile(f, max)
|
472
|
+
print_tree_profile(f, max)
|
473
|
+
end
|
474
|
+
|
475
|
+
def total_seconds
|
476
|
+
(@final_clock - @start_clock ).to_f / @clock_per_sec
|
477
|
+
end
|
478
|
+
|
479
|
+
def print_flat_profile(f=STDERR, max=nil)
|
480
|
+
f.puts "Number of threads: #{@threads.size}"
|
481
|
+
if @per_callsite
|
482
|
+
f.puts "Profiling based on method call and call site."
|
483
|
+
else
|
484
|
+
f.puts "Profiling based on method call."
|
485
|
+
end
|
486
|
+
f.puts "Cost of profiler: #{@profiler_cost.to_f / @clock_per_sec} seconds."
|
487
|
+
|
488
|
+
total = total_seconds
|
489
|
+
total = 0.01 if total == 0
|
490
|
+
f.puts "\nFlat profile (#{total} total seconds):"
|
491
|
+
data = @map.values
|
492
|
+
data.sort! { |a,b| b.self_sec <=> a.self_sec }
|
493
|
+
# data.sort! { |a,b| b[1] <=> a[1] }
|
494
|
+
sum = 0
|
495
|
+
f.puts " % total self self total"
|
496
|
+
f.puts " time seconds seconds calls ms/call ms/call name"
|
497
|
+
count = 0
|
498
|
+
data.each do |node|
|
499
|
+
#data.each do |calls, self_ms, total_ms, sig|
|
500
|
+
sum += node.self_sec
|
501
|
+
|
502
|
+
prec = node.percentage(total)
|
503
|
+
next if prec < 0.01
|
504
|
+
|
505
|
+
f.printf "%6.2f ", prec
|
506
|
+
f.printf "%8.2f ", sum
|
507
|
+
f.printf "%8.2f ", node.self_sec
|
508
|
+
f.printf "%8d ", node.calls
|
509
|
+
f.printf "%8.2f ", node.self_ms_per_call
|
510
|
+
f.printf "%8.2f ", node.total_ms_per_call
|
511
|
+
f.puts get_name(node.method)
|
512
|
+
|
513
|
+
count += 1
|
514
|
+
return if max and count > max
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
def collapse_children(cl, map)
|
519
|
+
out = Hash.new { |h,k| h[k] = 0 }
|
520
|
+
|
521
|
+
# p cl
|
522
|
+
cl.each do |c|
|
523
|
+
out[c] += 1
|
524
|
+
end
|
525
|
+
|
526
|
+
data = []
|
527
|
+
out.each do |meth,times|
|
528
|
+
# ch_ms = (map[meth][1] * 1000) / map[meth][0]
|
529
|
+
data << [meth, times, map[meth]]
|
530
|
+
end
|
531
|
+
|
532
|
+
data.sort! { |a,b| b[2].self_sec <=> a[2].self_sec }
|
533
|
+
data
|
534
|
+
end
|
535
|
+
|
536
|
+
def show_callers(callers, map)
|
537
|
+
end
|
538
|
+
|
539
|
+
def print_tree_profile(f=STDERR, max=nil)
|
540
|
+
width = 40
|
541
|
+
f.puts
|
542
|
+
f.puts "Call Tree Profile: "
|
543
|
+
f.puts "index calls ms/ self total"
|
544
|
+
f.puts " call sec sec"
|
545
|
+
map = @map
|
546
|
+
|
547
|
+
data = map.values
|
548
|
+
data.sort! { |a,b| b.self_sec <=> a.self_sec }
|
549
|
+
data.each do |pn|
|
550
|
+
next if pn.total_ms_per_call < 0.01
|
551
|
+
data = collapse_children(pn.callers, map)
|
552
|
+
|
553
|
+
data.each do |meth, called_times, cn|
|
554
|
+
times = map[meth].children.find_all { |i| i == pn.method }.size
|
555
|
+
if times == 0
|
556
|
+
times = "?"
|
557
|
+
else
|
558
|
+
called_times = called_times / times
|
559
|
+
end
|
560
|
+
vars = ["", "#{times}/#{called_times}", "-", "-",
|
561
|
+
"-", get_name(cn.method)]
|
562
|
+
f.puts "%-2s %14s %8s %8s %8s %s [#{meth}]" % vars
|
563
|
+
end
|
564
|
+
|
565
|
+
cl = collapse_children(pn.children, map)
|
566
|
+
chlines = []
|
567
|
+
sum = 0
|
568
|
+
cl.each do |meth, times, cn|
|
569
|
+
self_ms = cn.total_ms_per_call * times
|
570
|
+
sum += self_ms
|
571
|
+
self_sec = self_ms.to_f / 1000
|
572
|
+
total = self_sec * pn.calls
|
573
|
+
|
574
|
+
next if total < 0.01
|
575
|
+
vars = ["", times, cn.total_ms_per_call,
|
576
|
+
self_sec, total, get_name(cn.method)]
|
577
|
+
chlines << "%-8s %8d %8.2f %8.2f %8.2f %s [#{meth}]" % vars
|
578
|
+
end
|
579
|
+
vars = ["[#{pn.method}]", pn.calls,
|
580
|
+
pn.self_ms_per_call, pn.self_sec, pn.total_sec,
|
581
|
+
get_name(pn.method)]
|
582
|
+
f.puts "%-8s %8d %8.2f %8.2f %8.2f %s" % vars
|
583
|
+
f.puts *chlines if chlines.size > 0
|
584
|
+
f.puts "-" * 70
|
585
|
+
end
|
586
|
+
end
|
587
|
+
|
588
|
+
def get_name(info, per_cs=false)
|
589
|
+
if per_cs
|
590
|
+
@methods[info.first].to_s + " @ " + @files[info[1]].to_s + ":#{info[2]}"
|
591
|
+
else
|
592
|
+
@methods[info].to_s
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
class EntryPointProfiler < DefaultProfiler
|
598
|
+
def initialize(file, sig, depth=nil)
|
599
|
+
super(file)
|
600
|
+
@entry = sig
|
601
|
+
@start = Hash.new { |h,k| h[k] = false }
|
602
|
+
@max_depth = depth
|
603
|
+
@depth = Hash.new { |h,k| h[k] = 0 }
|
604
|
+
end
|
605
|
+
|
606
|
+
def process_call(thread, klass, kind, method, time)
|
607
|
+
#return unless thread == "b7533c3c"
|
608
|
+
if @entry == [klass, kind, method].to_s
|
609
|
+
@start[thread] = true
|
610
|
+
STDERR.puts "entered #{@entry} at #{time} in #{thread}"
|
611
|
+
return
|
612
|
+
end
|
613
|
+
|
614
|
+
return unless @start[thread]
|
615
|
+
|
616
|
+
# puts "#{[klass, kind, method]} (#{@depth[thread]})"
|
617
|
+
|
618
|
+
@depth[thread] += 1
|
619
|
+
|
620
|
+
#puts "call"
|
621
|
+
#p @depth
|
622
|
+
return if @max_depth and @depth[thread] > @max_depth
|
623
|
+
# puts "call: #{@depth} #{[klass, kind, method]}"
|
624
|
+
super
|
625
|
+
end
|
626
|
+
|
627
|
+
def process_return(thread, klass, kind, method, time)
|
628
|
+
#return unless thread == "b7533c3c"
|
629
|
+
if @entry == [klass, kind, method].to_s
|
630
|
+
@start[thread] = false
|
631
|
+
STDERR.puts "exitted #{@entry} at #{time} in #{thread}"
|
632
|
+
return
|
633
|
+
end
|
634
|
+
|
635
|
+
return unless @start[thread]
|
636
|
+
|
637
|
+
if !@max_depth or @depth[thread] <= @max_depth
|
638
|
+
super
|
639
|
+
end
|
640
|
+
|
641
|
+
@depth[thread] -= 1
|
642
|
+
end
|
643
|
+
|
644
|
+
def print(f=STDERR,max=nil)
|
645
|
+
f.puts "Calls only shown if performed from #{@entry}."
|
646
|
+
if @max_depth
|
647
|
+
f.puts "Calls only processed #{@max_depth} level(s) deep."
|
648
|
+
end
|
649
|
+
f.puts
|
650
|
+
super
|
651
|
+
end
|
652
|
+
end
|
653
|
+
end
|
data/silhouette_ext.c
ADDED
@@ -0,0 +1,254 @@
|
|
1
|
+
|
2
|
+
#include <time.h>
|
3
|
+
#ifdef HAVE_SYS_TIMES_H
|
4
|
+
#include <sys/times.h>
|
5
|
+
#endif
|
6
|
+
#include <unistd.h>
|
7
|
+
#include <ruby.h>
|
8
|
+
#include <node.h>
|
9
|
+
#include <st.h>
|
10
|
+
#include <limits.h>
|
11
|
+
|
12
|
+
static FILE *pro_file;
|
13
|
+
static struct timeval global_tv;
|
14
|
+
static int profiling_pid;
|
15
|
+
static st_table *method_tbl;
|
16
|
+
static st_table *file_tbl;
|
17
|
+
static char *time_magic = "@";
|
18
|
+
static char *start_magic = "!";
|
19
|
+
static char *binary_magic = "<>";
|
20
|
+
static char *method_magic = "&";
|
21
|
+
static char *file_magic = "*";
|
22
|
+
static char *call_magic = "c";
|
23
|
+
static char *return_magic = "r";
|
24
|
+
static char *null_ptr = "\0";
|
25
|
+
static int profiler_cost = 0;
|
26
|
+
static char *method_size_magic = "(";
|
27
|
+
static char *file_size_magic = ")";
|
28
|
+
static int method_idx_size = 0;
|
29
|
+
static int file_idx_size = 0;
|
30
|
+
|
31
|
+
#define PER_TIME 10
|
32
|
+
#define HASH_SIZE 128
|
33
|
+
|
34
|
+
#define CLOCK clock()
|
35
|
+
#define STR(x) RSTRING(x)->ptr
|
36
|
+
|
37
|
+
static void
|
38
|
+
extprof_event_hook(rb_event_t event, NODE *node,
|
39
|
+
VALUE self, ID mid, VALUE in_klass) {
|
40
|
+
|
41
|
+
static int profiling = 0;
|
42
|
+
static char *method, *s_klass;
|
43
|
+
static char *kind, *file;
|
44
|
+
static int line;
|
45
|
+
static VALUE klass;
|
46
|
+
static char method_ent[1024];
|
47
|
+
static char file_ent[1024];
|
48
|
+
static int method_idx = 1;
|
49
|
+
static int file_idx = 1;
|
50
|
+
unsigned int current_clock = CLOCK;
|
51
|
+
unsigned int i, j, m_idx, f_idx, size;
|
52
|
+
unsigned short s_m_idx, s_f_idx;
|
53
|
+
|
54
|
+
if(!mid) return;
|
55
|
+
|
56
|
+
method = rb_id2name(mid);
|
57
|
+
if(!method) {
|
58
|
+
method = "<undefined>";
|
59
|
+
}
|
60
|
+
|
61
|
+
/* If we've forked, dont profile anymore. */
|
62
|
+
if(getpid() != profiling_pid) {
|
63
|
+
return;
|
64
|
+
}
|
65
|
+
|
66
|
+
if (mid == ID_ALLOCATOR) return;
|
67
|
+
if (profiling) return;
|
68
|
+
profiling++;
|
69
|
+
|
70
|
+
if(rb_obj_is_kind_of(self, rb_cModule)) {
|
71
|
+
kind = ".";
|
72
|
+
klass = rb_class_name(self);
|
73
|
+
i = RSTRING(klass)->len;
|
74
|
+
s_klass = RSTRING(klass)->ptr;
|
75
|
+
memcpy(method_ent, s_klass, i);
|
76
|
+
*(method_ent + i) = '.';
|
77
|
+
} else {
|
78
|
+
kind = "#";
|
79
|
+
klass = rb_class_name(in_klass);
|
80
|
+
i = RSTRING(klass)->len;
|
81
|
+
s_klass = RSTRING(klass)->ptr;
|
82
|
+
memcpy(method_ent, s_klass, i);
|
83
|
+
*(method_ent + i) = '#';
|
84
|
+
}
|
85
|
+
|
86
|
+
j = strlen(method);
|
87
|
+
|
88
|
+
memcpy(method_ent + i + 1, method, j);
|
89
|
+
*(method_ent + i + 1 + j) = 0;
|
90
|
+
|
91
|
+
// sprintf(method_ent, "%s%s%s", s_klass, kind, method);
|
92
|
+
if(!st_lookup(method_tbl, (st_data_t)method_ent, (st_data_t*)&m_idx)) {
|
93
|
+
m_idx = ++method_idx;
|
94
|
+
if(method_idx > USHRT_MAX) {
|
95
|
+
method_idx_size = 1;
|
96
|
+
fwrite(&method_size_magic, 1, 1, pro_file);
|
97
|
+
}
|
98
|
+
st_insert(method_tbl, (st_data_t)method_ent, method_idx);
|
99
|
+
fwrite(method_magic, 1, 1, pro_file);
|
100
|
+
size = 4 + RSTRING(klass)->len + 4 + j;
|
101
|
+
fwrite(&size, 4, 1, pro_file);
|
102
|
+
fwrite(&method_idx, 4, 1, pro_file);
|
103
|
+
fwrite(RSTRING(klass)->ptr, RSTRING(klass)->len, 1, pro_file);
|
104
|
+
fwrite(null_ptr, 1, 1, pro_file);
|
105
|
+
fwrite(kind, 1, 1, pro_file);
|
106
|
+
fwrite(null_ptr, 1, 1, pro_file);
|
107
|
+
fwrite(method, j, 1, pro_file);
|
108
|
+
fwrite(null_ptr, 1, 1, pro_file);
|
109
|
+
// fprintf(pro_file, "& %d %s %s %s\n", method_idx, s_klass, kind, method);
|
110
|
+
}
|
111
|
+
|
112
|
+
if(node) {
|
113
|
+
file = node->nd_file;
|
114
|
+
line = nd_line(node);
|
115
|
+
// sprintf(file_ent, "%s:%d", node->nd_file, nd_line(node));
|
116
|
+
if(!st_lookup(file_tbl, (st_data_t)(node->nd_file), (st_data_t*)&f_idx)) {
|
117
|
+
f_idx = ++file_idx;
|
118
|
+
if(file_idx > USHRT_MAX) {
|
119
|
+
file_idx_size = 1;
|
120
|
+
fwrite(&file_size_magic, 1, 1, pro_file);
|
121
|
+
}
|
122
|
+
st_insert(file_tbl, (st_data_t)(node->nd_file), file_idx);
|
123
|
+
fwrite(file_magic, 1, 1, pro_file);
|
124
|
+
j = strlen(node->nd_file);
|
125
|
+
size = j + 5;
|
126
|
+
fwrite(&size, 4, 1, pro_file);
|
127
|
+
fwrite(&file_idx, 4, 1, pro_file);
|
128
|
+
fwrite(node->nd_file, j + 1, 1, pro_file);
|
129
|
+
//fprintf(pro_file, "* %d %s\n", file_idx, node->nd_file);
|
130
|
+
}
|
131
|
+
} else {
|
132
|
+
file = "<unknown>";
|
133
|
+
line = 0;
|
134
|
+
f_idx = 0;
|
135
|
+
}
|
136
|
+
#define PL_S(type) fprintf(pro_file, #type " %x %d %d %d\n", (int)rb_thread_current(), \
|
137
|
+
m_idx, f_idx, CLOCK);
|
138
|
+
#define PL(type) fprintf(pro_file, #type " %x %s %s %s %d\n", (int)rb_thread_current(), \
|
139
|
+
STR(rb_class_name(klass)), kind, method, CLOCK)
|
140
|
+
#define PL_EXT(type) fprintf(pro_file, #type " %x %s %x %s %s %s %d %f\n", (int)rb_thread_current(), \
|
141
|
+
STR(rb_class_name(klass)), (int)self, kind, method, file, line, CLOCK)
|
142
|
+
|
143
|
+
switch(event) {
|
144
|
+
case RUBY_EVENT_RETURN:
|
145
|
+
case RUBY_EVENT_C_RETURN:
|
146
|
+
fwrite(return_magic, 1, 1, pro_file);
|
147
|
+
goto output;
|
148
|
+
case RUBY_EVENT_CALL:
|
149
|
+
case RUBY_EVENT_C_CALL:
|
150
|
+
fwrite(call_magic, 1, 1, pro_file);
|
151
|
+
output:
|
152
|
+
j = (int)rb_thread_current();
|
153
|
+
fwrite(&j, 4, 1, pro_file);
|
154
|
+
|
155
|
+
if(method_idx_size) {
|
156
|
+
fwrite(&m_idx, sizeof(m_idx), 1, pro_file);
|
157
|
+
} else {
|
158
|
+
s_m_idx = m_idx;
|
159
|
+
fwrite(&s_m_idx, sizeof(s_m_idx), 1, pro_file);
|
160
|
+
}
|
161
|
+
|
162
|
+
if(file_idx_size) {
|
163
|
+
fwrite(&f_idx, sizeof(f_idx), 1, pro_file);
|
164
|
+
} else {
|
165
|
+
s_f_idx = f_idx;
|
166
|
+
fwrite(&s_f_idx, sizeof(s_f_idx), 1, pro_file);
|
167
|
+
}
|
168
|
+
|
169
|
+
if(node) {
|
170
|
+
j = nd_line(node);
|
171
|
+
} else {
|
172
|
+
j = 0;
|
173
|
+
}
|
174
|
+
fwrite(&j, 4, 1, pro_file);
|
175
|
+
fwrite(¤t_clock, 4, 1, pro_file);
|
176
|
+
break;
|
177
|
+
}
|
178
|
+
|
179
|
+
profiler_cost = profiler_cost + (CLOCK - current_clock);
|
180
|
+
profiling--;
|
181
|
+
}
|
182
|
+
|
183
|
+
static VALUE extprof_start(int argc, VALUE *argv, VALUE self) {
|
184
|
+
struct timeval tv;
|
185
|
+
char path[1024];
|
186
|
+
int size, i;
|
187
|
+
|
188
|
+
if(argc == 0) {
|
189
|
+
pro_file = fopen("silhouette.out","w");
|
190
|
+
} else {
|
191
|
+
StringValue(argv[0]);
|
192
|
+
pro_file = fopen(STR(argv[0]), "w");
|
193
|
+
}
|
194
|
+
|
195
|
+
profiling_pid = getpid();
|
196
|
+
|
197
|
+
method_tbl = st_init_strtable_with_size(HASH_SIZE);
|
198
|
+
file_tbl = st_init_strtable_with_size(HASH_SIZE);
|
199
|
+
|
200
|
+
getcwd(path, 1023);
|
201
|
+
size = strlen(path);
|
202
|
+
path[size] = 0;
|
203
|
+
|
204
|
+
fwrite(binary_magic, 2 ,1 , pro_file);
|
205
|
+
fwrite(start_magic, 1, 1, pro_file);
|
206
|
+
i = size + 9;
|
207
|
+
fwrite(&i, 4, 1, pro_file);
|
208
|
+
fwrite(path, size + 1, 1, pro_file);
|
209
|
+
i = CLOCKS_PER_SEC;
|
210
|
+
fwrite(&i, 4, 1, pro_file);
|
211
|
+
i = CLOCK;
|
212
|
+
fwrite(&i, 4, 1, pro_file);
|
213
|
+
// fprintf(pro_file, "! %s %d %d\n", getcwd(path, 1023), getpid(),
|
214
|
+
// CLOCKS_PER_SEC);
|
215
|
+
/*
|
216
|
+
gettimeofday(&tv, NULL);
|
217
|
+
fprintf(pro_file, "@ %d %d %d\n", (int)tv.tv_sec,
|
218
|
+
(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);
|
223
|
+
|
224
|
+
return Qtrue;
|
225
|
+
}
|
226
|
+
|
227
|
+
static VALUE extprof_end(VALUE self) {
|
228
|
+
struct timeval tv;
|
229
|
+
int i;
|
230
|
+
|
231
|
+
rb_remove_event_hook(extprof_event_hook);
|
232
|
+
|
233
|
+
fwrite(time_magic, 1, 1, pro_file);
|
234
|
+
i = CLOCK;
|
235
|
+
fwrite(&i, 4, 1, pro_file);
|
236
|
+
fwrite(&profiler_cost, 4, 1, pro_file);
|
237
|
+
/*
|
238
|
+
gettimeofday(&tv, NULL);
|
239
|
+
fprintf(pro_file, "@ %d %d %d\n", (int)tv.tv_sec,
|
240
|
+
(int)tv.tv_usec, CLOCK);
|
241
|
+
*/
|
242
|
+
fflush(pro_file);
|
243
|
+
fclose(pro_file);
|
244
|
+
return Qtrue;
|
245
|
+
}
|
246
|
+
|
247
|
+
void Init_silhouette_ext() {
|
248
|
+
VALUE extprof;
|
249
|
+
extprof = rb_define_module("Silhouette");
|
250
|
+
rb_define_singleton_method(extprof, "start_profile", extprof_start, -1);
|
251
|
+
rb_define_singleton_method(extprof, "stop_profile", extprof_end, 0);
|
252
|
+
}
|
253
|
+
|
254
|
+
/* vim: set filetype=c ts=4 sw=4 noexpandtab : */
|
data/test/silhouette.out
ADDED
Binary file
|
data/test/test.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
def plus
|
3
|
+
1 + 1
|
4
|
+
end
|
5
|
+
|
6
|
+
def blah
|
7
|
+
100.times { plus }
|
8
|
+
100.times { plus }
|
9
|
+
bleh
|
10
|
+
end
|
11
|
+
|
12
|
+
def bleh
|
13
|
+
bloh
|
14
|
+
end
|
15
|
+
|
16
|
+
def bloh
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def one
|
21
|
+
blah
|
22
|
+
end
|
23
|
+
|
24
|
+
def two
|
25
|
+
blah
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
one
|
30
|
+
two
|
31
|
+
two
|
32
|
+
rescue
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: silhouette
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 1.0.0
|
7
|
+
date: 2005-12-06 00:00:00 -08:00
|
8
|
+
summary: A 2 stage profiler
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: evan@fallingsnow.net
|
12
|
+
homepage:
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable: silhouette
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
-
|
22
|
+
- ">"
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.0.0
|
25
|
+
version:
|
26
|
+
platform: ruby
|
27
|
+
signing_key:
|
28
|
+
cert_chain:
|
29
|
+
authors:
|
30
|
+
- Evan Webb
|
31
|
+
files:
|
32
|
+
- bin/silhouette
|
33
|
+
- extconf.rb
|
34
|
+
- lib/silhouette/processor.rb
|
35
|
+
- lib/silhouette.rb
|
36
|
+
- Manifest.txt
|
37
|
+
- Rakefile
|
38
|
+
- README
|
39
|
+
- silhouette_ext.c
|
40
|
+
- test/silhouette.out
|
41
|
+
- test/test.rb
|
42
|
+
test_files: []
|
43
|
+
rdoc_options: []
|
44
|
+
extra_rdoc_files: []
|
45
|
+
executables:
|
46
|
+
- silhouette
|
47
|
+
extensions:
|
48
|
+
- extconf.rb
|
49
|
+
requirements: []
|
50
|
+
dependencies: []
|