rubinius-profiler 2.0.2 → 2.1
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.
- 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
|