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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e4c9fa8b3b0b60100946a5df72813837b22277c
4
- data.tar.gz: 2a1d31d54834f9d28a5bc03a405ff5ad86237188
3
+ metadata.gz: ad4c72f244576ac785fb4d34c2f6b80de5d89ca2
4
+ data.tar.gz: 5c6c617e535205dd682ed86f116bda2e2831753b
5
5
  SHA512:
6
- metadata.gz: d0b524467f23268cd28a8d18692b000332ca91191cb9d0be03d4aea78c18b3febf879cf367fe4bf8ab2149c62fdcdddc7d0b07f41838541b8f99e7c93b9b4551
7
- data.tar.gz: d002bcdf062824738b004dec08d192dd27303eb2c082cd575d3c1bbe63506a739ca72b3ed6d1fd5bf92c2a5a0b84f42144dd8d23f772e9f0f04d42e167fda622
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