rubinius-profiler 2.0.2 → 2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rubinius/profiler/profiler.rb +0 -440
- data/lib/rubinius/profiler/version.rb +1 -1
- data/rubinius-profiler.gemspec +0 -1
- metadata +6 -9
- data/ext/rubinius/profiler/extconf.rb +0 -3
- data/ext/rubinius/profiler/profiler.cpp +0 -1059
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad4c72f244576ac785fb4d34c2f6b80de5d89ca2
|
4
|
+
data.tar.gz: 5c6c617e535205dd682ed86f116bda2e2831753b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f90fc9103e3c7c43558c3c92024a76bca443beb88ca2e6e9b664a4065b9bb367ed2bac1908ee5ddd19adb3eab603e9e4c8b19c859916447dc3ac68f1d184b437
|
7
|
+
data.tar.gz: 78d961bb9c71a2da06ee26e4b47601846c64768badfa6afdb65dd00040c3603f8ab4533cc17bfc24c1547ade5625803e98c2c5cdd9c9c24d9299852d8be2fffd
|
@@ -1,444 +1,4 @@
|
|
1
1
|
module Rubinius
|
2
2
|
module Profiler
|
3
|
-
|
4
|
-
##
|
5
|
-
# Interface to VM's instrumenting profiler.
|
6
|
-
|
7
|
-
class Instrumenter
|
8
|
-
include Stats::Units
|
9
|
-
|
10
|
-
attr_reader :info, :options
|
11
|
-
|
12
|
-
def self.available?
|
13
|
-
Rubinius::Tooling.available?
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.active?
|
17
|
-
Rubinius::Tooling.active?
|
18
|
-
end
|
19
|
-
|
20
|
-
@loaded = false
|
21
|
-
def self.load
|
22
|
-
return if @loaded
|
23
|
-
Rubinius::Tooling.load File.expand_path("../ext/profiler", __FILE__)
|
24
|
-
@loaded = true
|
25
|
-
end
|
26
|
-
|
27
|
-
def initialize(options = {})
|
28
|
-
Instrumenter.load
|
29
|
-
|
30
|
-
@options = { :sort => :percent }
|
31
|
-
set_options options
|
32
|
-
set_options :full_report => true if Config["profiler.full_report"]
|
33
|
-
set_options :graph => true if Config["profiler.graph"]
|
34
|
-
|
35
|
-
if path = Config["profiler.json"]
|
36
|
-
set_options :json => path
|
37
|
-
end
|
38
|
-
|
39
|
-
if name = Config["profiler.output"]
|
40
|
-
set_options :output => "#{File.expand_path(name)}-#{$$}"
|
41
|
-
end
|
42
|
-
|
43
|
-
if Config['profiler.cumulative_percentage']
|
44
|
-
set_options :cumulative_percentage => true
|
45
|
-
end
|
46
|
-
|
47
|
-
if Config['profiler.classes']
|
48
|
-
set_options :classes => true
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# Set options for profiler output. Presently, the only option
|
53
|
-
# is :sort. It takes a single symbol or an array of symbols to
|
54
|
-
# specify the column(s) to sort by. The recognized symbols are:
|
55
|
-
#
|
56
|
-
# Symbol Profiler heading
|
57
|
-
# :percent % time
|
58
|
-
# :total_seconds cumulative seconds
|
59
|
-
# :self_seconds self seconds
|
60
|
-
# :calls calls
|
61
|
-
# :self_ms self ms/call
|
62
|
-
# :total_ms total ms/call
|
63
|
-
# :name name
|
64
|
-
#
|
65
|
-
# @todo Add options for GC allocation counts
|
66
|
-
def set_options(options)
|
67
|
-
@options.merge!(options)
|
68
|
-
end
|
69
|
-
|
70
|
-
def start
|
71
|
-
Rubinius::Tooling.enable
|
72
|
-
end
|
73
|
-
|
74
|
-
def __stop__
|
75
|
-
Rubinius::Tooling.disable
|
76
|
-
end
|
77
|
-
|
78
|
-
def stop
|
79
|
-
@info = __stop__
|
80
|
-
end
|
81
|
-
|
82
|
-
# Convenience method to profile snippets of code in a larger script or
|
83
|
-
# program. Enables the profiler and yields to the given block.
|
84
|
-
#
|
85
|
-
# pr = Rubinius::Profiler::Instrumenter.new
|
86
|
-
# pr.profile { # do some work here }
|
87
|
-
def profile(display = true)
|
88
|
-
start
|
89
|
-
yield if block_given?
|
90
|
-
stop
|
91
|
-
show if display
|
92
|
-
@info
|
93
|
-
end
|
94
|
-
|
95
|
-
SHORT_LINES = 45
|
96
|
-
|
97
|
-
def show(out=$stdout)
|
98
|
-
unless self.class.available? and @info
|
99
|
-
out.puts "No profiling data was available"
|
100
|
-
return
|
101
|
-
end
|
102
|
-
|
103
|
-
begin
|
104
|
-
if name = options[:output]
|
105
|
-
out = File.open name, "w"
|
106
|
-
end
|
107
|
-
|
108
|
-
if options[:json]
|
109
|
-
json options[:json]
|
110
|
-
elsif options[:graph]
|
111
|
-
graph out
|
112
|
-
else
|
113
|
-
flat out
|
114
|
-
end
|
115
|
-
ensure
|
116
|
-
out.close if options[:output]
|
117
|
-
end
|
118
|
-
|
119
|
-
nil
|
120
|
-
end
|
121
|
-
|
122
|
-
def epilogue(out, size, calls)
|
123
|
-
unless options[:full_report] or size < SHORT_LINES
|
124
|
-
out.puts "\n#{comma(size-SHORT_LINES)} methods omitted"
|
125
|
-
end
|
126
|
-
out.puts "\n#{comma(size)} methods called a total of #{comma(calls)} times"
|
127
|
-
end
|
128
|
-
|
129
|
-
def flat(out)
|
130
|
-
keys = @info.keys.sort
|
131
|
-
|
132
|
-
keys.each do |t_id|
|
133
|
-
thread_flat out, t_id, @info[t_id]
|
134
|
-
puts
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def thread_flat(out, t_id, info)
|
139
|
-
total_calls = 0
|
140
|
-
total = 0.0
|
141
|
-
|
142
|
-
all_selves = 0.0
|
143
|
-
|
144
|
-
# Because the info is actually in a tree form and we to just see it
|
145
|
-
# flat for each method, we need to go through and collect all the stats
|
146
|
-
# for each unique method.
|
147
|
-
|
148
|
-
info[:nodes].each do |n_id, data|
|
149
|
-
sub = data[4].inject(0) do |a,n|
|
150
|
-
next unless x = info[:nodes][n]
|
151
|
-
a + x[1]
|
152
|
-
end
|
153
|
-
|
154
|
-
meth = info[:methods][data[0]]
|
155
|
-
if cur = meth[:edge_total]
|
156
|
-
meth[:edge_total] = cur + sub
|
157
|
-
else
|
158
|
-
meth[:edge_total] = sub
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
data = info[:methods].values.map do |m|
|
163
|
-
cumulative = m[:cumulative]
|
164
|
-
method_total = m[:total]
|
165
|
-
edges_total = m[:edge_total]
|
166
|
-
self_total = method_total - edges_total
|
167
|
-
called = m[:called]
|
168
|
-
total += method_total
|
169
|
-
total_calls += called
|
170
|
-
|
171
|
-
all_selves += self_total
|
172
|
-
|
173
|
-
name = m[:name]
|
174
|
-
name = "#toplevel" if name == "<metaclass>#__script__ {}"
|
175
|
-
[ 0,
|
176
|
-
sec(cumulative),
|
177
|
-
sec(self_total),
|
178
|
-
called,
|
179
|
-
msec(self_total) / called,
|
180
|
-
msec(cumulative) / called,
|
181
|
-
name ]
|
182
|
-
end
|
183
|
-
|
184
|
-
all_selves = sec(all_selves)
|
185
|
-
|
186
|
-
if options[:cumulative_percentage]
|
187
|
-
data.each do |d|
|
188
|
-
d[0] = (d[1] / sec(info[:runtime])) * 100
|
189
|
-
end
|
190
|
-
else
|
191
|
-
data.each do |d|
|
192
|
-
d[0] = (d[2] / all_selves) * 100
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
columns = sort_order
|
197
|
-
data = data.sort_by do |row|
|
198
|
-
columns.map {|col| row[col] }
|
199
|
-
end.reverse
|
200
|
-
|
201
|
-
out.puts "Thread #{t_id}: total running time: #{sec(info[:runtime])}s"
|
202
|
-
out.puts ""
|
203
|
-
out.puts " % cumulative self self total"
|
204
|
-
out.puts " time seconds seconds calls ms/call ms/call name"
|
205
|
-
out.puts "------------------------------------------------------------"
|
206
|
-
|
207
|
-
report = options[:full_report] ? data : data.first(SHORT_LINES)
|
208
|
-
report.each do |d|
|
209
|
-
out.printf " %6s", ("%.2f" % d[0])
|
210
|
-
out.printf "%8.2f %8.2f %10d %8.2f %8.2f %s\n", *d.last(6)
|
211
|
-
end
|
212
|
-
|
213
|
-
epilogue out, data.size, total_calls
|
214
|
-
|
215
|
-
if options[:classes]
|
216
|
-
classes = Hash.new { |h,k| h[k] = 0.0 }
|
217
|
-
|
218
|
-
data.each do |row|
|
219
|
-
if m = /(.*)[#\.]/.match(row.last)
|
220
|
-
classes[m[1]] += ((row[2] / all_selves) * 100)
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
out.puts "\nUsage percentage by class:"
|
225
|
-
sorted = classes.to_a.sort_by { |row| row[1] }.reverse
|
226
|
-
sorted.each do |row|
|
227
|
-
out.printf "%6s: %s\n", ("%.2f" % row[1]), row[0]
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
def json(path)
|
233
|
-
File.open path, "w" do |f|
|
234
|
-
t_final = @info.size - 1
|
235
|
-
t_idx = 0
|
236
|
-
|
237
|
-
f.puts "["
|
238
|
-
|
239
|
-
@info.each do |t_id, info|
|
240
|
-
f.puts "{"
|
241
|
-
f.puts " \"thread_id\": #{t_id},"
|
242
|
-
f.puts " \"runtime\": #{info[:runtime]},"
|
243
|
-
f.puts " \"total_nodes\": #{info[:total_nodes]},"
|
244
|
-
roots = info[:roots].map { |x| x.to_s.dump }.join(',')
|
245
|
-
f.puts " \"roots\": [ #{roots} ],"
|
246
|
-
f.puts " \"nodes\": {"
|
247
|
-
idx = 0
|
248
|
-
final = info[:nodes].size - 1
|
249
|
-
|
250
|
-
info[:nodes].each do |n_id, data|
|
251
|
-
f.puts " \"#{n_id}\": {"
|
252
|
-
f.puts " \"method\": #{data[0]}, \"total\": #{data[1]}, \"called\": #{data[2]},"
|
253
|
-
f.puts " \"total_nodes\": #{data[3]}, \"sub_nodes\": [ #{data[4].join(', ')} ]"
|
254
|
-
if idx == final
|
255
|
-
f.puts " }"
|
256
|
-
else
|
257
|
-
f.puts " },"
|
258
|
-
end
|
259
|
-
idx += 1
|
260
|
-
end
|
261
|
-
|
262
|
-
f.puts " },"
|
263
|
-
f.puts " \"methods\": {"
|
264
|
-
|
265
|
-
idx = 0
|
266
|
-
final = info[:methods].size - 1
|
267
|
-
info[:methods].each do |m_id, m|
|
268
|
-
f.puts " \"#{m_id}\": {"
|
269
|
-
f.puts " \"name\": \"#{m[:name]}\", \"file\": \"#{m[:file]}\", \"line\": #{m[:line] || 0},"
|
270
|
-
f.puts " \"cumulative\": #{m[:cumulative]}, \"total\": #{m[:total]}, \"called\": #{m[:called]}"
|
271
|
-
if idx == final
|
272
|
-
f.puts " }"
|
273
|
-
else
|
274
|
-
f.puts " },"
|
275
|
-
end
|
276
|
-
idx += 1
|
277
|
-
end
|
278
|
-
|
279
|
-
f.puts " }"
|
280
|
-
|
281
|
-
if t_idx == t_final
|
282
|
-
f.puts "}"
|
283
|
-
else
|
284
|
-
f.puts "},"
|
285
|
-
end
|
286
|
-
|
287
|
-
t_idx += 1
|
288
|
-
end
|
289
|
-
f.puts "]"
|
290
|
-
end
|
291
|
-
|
292
|
-
puts "Wrote JSON to: #{path}"
|
293
|
-
end
|
294
|
-
|
295
|
-
def graph(out)
|
296
|
-
keys = @info.keys.sort
|
297
|
-
|
298
|
-
keys.each do |t_id|
|
299
|
-
thread_graph out, t_id, @info[t_id]
|
300
|
-
out.puts
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
# Prints an entry for each method, along with the method's callers and
|
305
|
-
# the methods called. The entry is delimited by the dashed lines. The
|
306
|
-
# line for the method itself is called the "primary" line. The callers
|
307
|
-
# are printed above the primary line and the methods called are printed
|
308
|
-
# below.
|
309
|
-
def thread_graph(out, t_id, info)
|
310
|
-
total_calls = 0
|
311
|
-
run_total = 0.0
|
312
|
-
|
313
|
-
data = info[:nodes]
|
314
|
-
|
315
|
-
methods = info[:methods]
|
316
|
-
|
317
|
-
run_total = info[:runtime].to_f
|
318
|
-
|
319
|
-
all_callers = Hash.new { |h,k| h[k] = [] }
|
320
|
-
|
321
|
-
data.each do |n_id, n_data|
|
322
|
-
n_data[4].each do |sub|
|
323
|
-
all_callers[sub] << n_id
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
indexes = data.keys.sort do |a, b|
|
328
|
-
data[b][1] <=> data[a][1]
|
329
|
-
end
|
330
|
-
|
331
|
-
indexes = indexes.first(SHORT_LINES) unless options[:full_report]
|
332
|
-
|
333
|
-
shown_indexes = {}
|
334
|
-
|
335
|
-
indexes.each_with_index do |id, index|
|
336
|
-
shown_indexes[id] = index + 1
|
337
|
-
end
|
338
|
-
|
339
|
-
out.puts "===== Thread #{t_id} ====="
|
340
|
-
out.puts "Total running time: #{sec(info[:runtime])}s"
|
341
|
-
out.puts "index % time self children called name"
|
342
|
-
out.puts "----------------------------------------------------------"
|
343
|
-
|
344
|
-
primary = "%-7s%6s %8.2f %9.2f %8d %s [%d]\n"
|
345
|
-
secondary = " %8.2f %9.2f %8d %s%s\n"
|
346
|
-
|
347
|
-
indexes.each do |id|
|
348
|
-
m_id, total, called, tn, sub_nodes = data[id]
|
349
|
-
|
350
|
-
# The idea is to report information about caller as a ratio of the
|
351
|
-
# time it called method.
|
352
|
-
#
|
353
|
-
|
354
|
-
callers = all_callers[id].sort_by do |c_id|
|
355
|
-
clr = data[c_id]
|
356
|
-
|
357
|
-
clr[total]
|
358
|
-
end
|
359
|
-
|
360
|
-
callers = callers.first(10) unless options[:full_report]
|
361
|
-
|
362
|
-
callers.each do |c_id|
|
363
|
-
clr_m_id, clr_total, clr_called, clr_tn, clr_sub = data[c_id]
|
364
|
-
|
365
|
-
sub_total = clr_sub.inject(0) { |a,s| a + data[s][1] }
|
366
|
-
|
367
|
-
self_total = clr_total - sub_total
|
368
|
-
out.printf(secondary, sec(self_total),
|
369
|
-
sec(sub_total),
|
370
|
-
clr_called,
|
371
|
-
methods[clr_m_id][:name],
|
372
|
-
graph_method_index(shown_indexes[c_id]))
|
373
|
-
end
|
374
|
-
|
375
|
-
# Now the primary line.
|
376
|
-
|
377
|
-
children = sub_nodes.inject(0) { |a,s| a + data[s][1] }
|
378
|
-
# children = method[:cumulative] * (method[:edges_total].to_f / method[:total])
|
379
|
-
|
380
|
-
self_total = total - children
|
381
|
-
out.printf primary, ("[%d]" % shown_indexes[id]),
|
382
|
-
percentage(total, run_total, 1, nil),
|
383
|
-
sec(self_total),
|
384
|
-
sec(children),
|
385
|
-
called,
|
386
|
-
methods[m_id][:name],
|
387
|
-
shown_indexes[id]
|
388
|
-
|
389
|
-
# Same as caller, the idea is to report information about callee methods
|
390
|
-
# as a ratio of the time it was called from method.
|
391
|
-
#
|
392
|
-
|
393
|
-
edges = sub_nodes.sort_by do |e_id|
|
394
|
-
if edge = data[e_id]
|
395
|
-
edge[1]
|
396
|
-
else
|
397
|
-
0.0
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
edges = edges.last(10) unless options[:full_report]
|
402
|
-
# method[:edges] = method[:edges].first(10) unless options[:full_report]
|
403
|
-
|
404
|
-
edges.reverse_each do |e_id|
|
405
|
-
c_m_id, c_total, c_called, c_tn, c_sub_nodes = data[e_id]
|
406
|
-
|
407
|
-
grandchildren = c_sub_nodes.inject(0) { |a,s| a + data[s][1] }
|
408
|
-
grandchildren = 0 if grandchildren < 0
|
409
|
-
|
410
|
-
self_total = c_total - grandchildren
|
411
|
-
out.printf secondary, sec(self_total),
|
412
|
-
sec(grandchildren),
|
413
|
-
c_called,
|
414
|
-
methods[c_m_id][:name],
|
415
|
-
graph_method_index(shown_indexes[e_id])
|
416
|
-
end
|
417
|
-
|
418
|
-
out.puts "-------------------------------------------------------"
|
419
|
-
end
|
420
|
-
|
421
|
-
epilogue out, data.size, total_calls
|
422
|
-
end
|
423
|
-
|
424
|
-
def graph_method_index(index)
|
425
|
-
index ? " [#{index}]" : ""
|
426
|
-
end
|
427
|
-
|
428
|
-
HEADER_INDEX = {
|
429
|
-
:percent => 0,
|
430
|
-
:total_seconds => 1,
|
431
|
-
:self_seconds => 2,
|
432
|
-
:calls => 3,
|
433
|
-
:self_ms => 4,
|
434
|
-
:total_ms => 5,
|
435
|
-
:name => 6
|
436
|
-
}
|
437
|
-
|
438
|
-
def sort_order
|
439
|
-
# call to_i so if unrecognized symbol is passed, column will be percent
|
440
|
-
Array(@options[:sort]).map { |header| HEADER_INDEX[header].to_i }
|
441
|
-
end
|
442
|
-
end
|
443
3
|
end
|
444
4
|
end
|