2 # Interface to VM's sampling profiler.
5 def initialize(freq=nil)
7 @frequency ||= ENV['PROFILE_FREQ'].to_i
8 @frequency = 100 if @frequency == 0
10 @call_graph = ENV['PROFILE_FULL']
14 @start_clock = activate(@frequency)
19 @results, @last_clock, @gc_cycles = terminate()
24 # Records call information.
28 attr_accessor :descendants_slices
30 attr_accessor :parents
31 attr_accessor :children
36 @descendants_slices = 0
38 @parents = Hash.new { |h,k| h[k] = 0 }
39 @children = Hash.new { |h,k| h[k] = 0 }
43 @slices + @descendants_slices
46 def count_parent(call)
49 call.children[self] += 1
54 def display(out=STDOUT)
56 @calls = Hash.new { |h,k| h[k] = Call.new(k) }
58 @results.each do |ent|
66 # skip context unwinding for a primitive
67 next if ent.kind_of? Fixnum
70 # count parents and children
71 call.count_parent(find_call(ent.sender))
74 seen_calls = { call => 1 }
82 # unwind to the root, but count each call only once
86 c.descendants_slices += 1
92 @calls["VM.garbage_collection"].slices = @gc_cycles
94 out << "Total slices: #{@total_slices}, #{@last_clock - @start_clock} clocks\n\n"
95 out << "=== FLAT PROFILE ===\n\n"
96 out << " % time slices name\n"
98 @calls.sort { |a, b| b[1].slices <=> a[1].slices }.each do |name, call|
99 out.printf " %6.2f %8d %s\n", percent(call.slices), call.slices, name
103 out << "\n=== CALL GRAPH ===\n\n"
104 out << " % time slices % self slices name\n"
105 @calls.sort { |a, b| b[1].total_slices <=> a[1].total_slices }.each do |name, call|
106 print_relatives(out, call.parents.sort { |a,b| a[1] <=> b[1] })
108 out.printf " %6.2f %8d %6.2f %8d %s\n",
109 percent(call.total_slices), call.total_slices,
110 percent(call.slices), call.slices,
113 print_relatives(out, call.children.sort { |a,b| b[1] <=> a[1] })
115 out << "----------------------------------------------------------------------\n"
121 def context_name(entry)
122 # a Fixnum means that a primitive was running
123 if entry.kind_of? Fixnum
124 "VM.primitive => #{Rubinius::Primitives[entry]}"
126 entry.normalized_name
131 @calls[context_name(entry)]
135 100.0 * slices / @total_slices
138 def print_relatives(out, rels)
139 rels[0,5].each do |rel|
140 out << " #{rel[0].name} (#{rel[1]})\n"
145 # Displays Selector statistics.
148 def show_stats(range=30)
150 count = Selector::ALL.size
152 entries = Selector::ALL.values.map { |s| [s, s.receives] }
154 entries.delete_if { |e| e.last < 10 }
156 sort = entries.sort { |a,b| b.last <=> a.last }
158 puts "\nTotal Selectors: #{count}"
159 puts "Top #{range}, by receives:"
160 puts "%-20s| %-20s| %s" % ["name", "receives", "send sites"]
161 puts "=========================================================="
162 sort[0,range].each do |entry|
163 puts "%-20s| %-20d| %d" % [entry.first.name, entry.last, entry.first.send_sites.size]
169 # Displays SendSite statistics.
172 def show_stats(range=30)
173 send_sites = Selector::ALL.values.inject([]) { |acc,s| acc.concat(s.send_sites) }
174 count = send_sites.size
176 send_sites.delete_if { |e| (e.hits + e.misses) < 10 }
178 sort = send_sites.sort { |a,b| (b.hits + b.misses) <=> (a.hits + a.misses) }
180 puts "\nTotal SendSites: #{count}"
181 puts "Top #{range}, by sends:"
182 puts "%-32s| %-18s | %-18s| %-10s| %s" % ["sender", "receiver", "name", "hits", "misses"]
183 puts "============================================================================================="
184 sort[0,range].each do |entry|
185 mod = entry.sender.staticscope.module if entry.sender.staticscope
186 sender = "#{mod}##{entry.sender.name}"
187 puts "%-32s| %-18s | %-18s| %-10d| %d" % [sender, entry.receiver, entry.name, entry.hits, entry.misses]