silhouette 1.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 -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: []
|