class StackProf::Report

Attributes

data[R]

Public Class Methods

new(data) click to toggle source
# File lib/stackprof/report.rb, line 6
def initialize(data)
  @data = data
end

Public Instance Methods

+(other) click to toggle source
# File lib/stackprof/report.rb, line 355
def +(other)
  raise ArgumentError, "cannot combine #{other.class}" unless self.class == other.class
  raise ArgumentError, "cannot combine #{modeline} with #{other.modeline}" unless modeline == other.modeline
  raise ArgumentError, "cannot combine v#{version} with v#{other.version}" unless version == other.version

  f1, f2 = normalized_frames, other.normalized_frames
  frames = (f1.keys + f2.keys).uniq.inject(Hash.new) do |hash, id|
    if f1[id].nil?
      hash[id] = f2[id]
    elsif f2[id]
      hash[id] = f1[id]
      hash[id][:total_samples] += f2[id][:total_samples]
      hash[id][:samples] += f2[id][:samples]
      if f2[id][:edges]
        edges = hash[id][:edges] ||= {}
        f2[id][:edges].each do |edge, weight|
          edges[edge] ||= 0
          edges[edge] += weight
        end
      end
      if f2[id][:lines]
        lines = hash[id][:lines] ||= {}
        f2[id][:lines].each do |line, weight|
          lines[line] = add_lines(lines[line], weight)
        end
      end
    else
      hash[id] = f1[id]
    end
    hash
  end

  d1, d2 = data, other.data
  data = {
    version: version,
    mode: d1[:mode],
    interval: d1[:interval],
    samples: d1[:samples] + d2[:samples],
    gc_samples: d1[:gc_samples] + d2[:gc_samples],
    missed_samples: d1[:missed_samples] + d2[:missed_samples],
    frames: frames
  }

  self.class.new(data)
end
add_lines(a, b) click to toggle source
# File lib/stackprof/report.rb, line 56
def add_lines(a, b)
  return b if a.nil?
  return a+b if a.is_a? Integer
  return [ a[0], a[1]+b ] if b.is_a? Integer
  [ a[0]+b[0], a[1]+b[1] ]
end
files() click to toggle source
# File lib/stackprof/report.rb, line 44
def files
  @data[:files] ||= @data[:frames].inject(Hash.new) do |hash, (addr, frame)|
    if file = frame[:file] and lines = frame[:lines]
      hash[file] ||= Hash.new
      lines.each do |line, weight|
        hash[file][line] = add_lines(hash[file][line], weight)
      end
    end
    hash
  end
end
flamegraph_row(f, x, y, weight, addr) click to toggle source
# File lib/stackprof/report.rb, line 144
def flamegraph_row(f, x, y, weight, addr)
  frame = frames[addr]
  f.print ',' if @rows_started
  @rows_started = true
  f.puts %Q{{"x":#{x},"y":#{y},"width":#{weight},"frame_id":#{addr},"frame":#{frame[:name].dump},"file":#{frame[:file].dump}}}
end
frames(sort_by_total=false) click to toggle source
# File lib/stackprof/report.rb, line 11
def frames(sort_by_total=false)
  @data[:"sorted_frames_#{sort_by_total}"] ||=
    @data[:frames].sort_by{ |iseq, stats| -stats[sort_by_total ? :total_samples : :samples] }.inject({}){|h, (k, v)| h[k] = v; h}
end
max_samples() click to toggle source
# File lib/stackprof/report.rb, line 40
def max_samples
  @data[:max_samples] ||= frames.max_by{ |addr, frame| frame[:samples] }.last[:samples]
end
modeline() click to toggle source
# File lib/stackprof/report.rb, line 32
def modeline
  "#{@data[:mode]}(#{@data[:interval]})"
end
normalized_frames() click to toggle source
# File lib/stackprof/report.rb, line 16
def normalized_frames
  id2hash = {}
  @data[:frames].each do |frame, info|
    id2hash[frame.to_s] = info[:hash] = Digest::MD5.hexdigest("#{info[:name]}#{info[:file]}#{info[:line]}")
  end
  @data[:frames].inject(Hash.new) do |hash, (frame, info)|
    info = hash[id2hash[frame.to_s]] = info.dup
    info[:edges] = info[:edges].inject(Hash.new){ |edges, (edge, weight)| edges[id2hash[edge.to_s]] = weight; edges } if info[:edges]
    hash
  end
end
overall_samples() click to toggle source
# File lib/stackprof/report.rb, line 36
def overall_samples
  @data[:samples]
end
print_callgrind(f = STDOUT) click to toggle source
print_debug() click to toggle source
print_dump(f=STDOUT) click to toggle source
print_file(filter, f = STDOUT) click to toggle source
print_files(sort_by_total=false, limit=nil, f = STDOUT) click to toggle source
print_flamegraph(f=STDOUT, skip_common=true) click to toggle source
print_graphviz(options = {}, f = STDOUT) click to toggle source
print_method(name, f = STDOUT) click to toggle source
print_stackcollapse() click to toggle source
print_text(sort_by_total=false, limit=nil, select_files= nil, reject_files=nil, select_names=nil, reject_names=nil, f = STDOUT) click to toggle source
version() click to toggle source
# File lib/stackprof/report.rb, line 28
def version
  @data[:version]
end
walk_method(name) click to toggle source

Walk up and down the stack from a given starting point (name). Loops until `:exit` is selected

# File lib/stackprof/report.rb, line 298
def walk_method(name)
  method_choice  = /#{Regexp.escape name}/
  invalid_choice = false

  # Continue walking up and down the stack until the users selects "exit"
  while method_choice != :exit
    print_method method_choice unless invalid_choice
    STDOUT.puts "\n\n"

    # Determine callers and callees for the current frame
    new_frames  = frames.select  {|_, info| info[:name] =~ method_choice }
    new_choices = new_frames.map {|frame, info| [
      callers_for(frame).sort_by(&:last).reverse.map(&:first),
      (info[:edges] || []).map{ |k, w| [data[:frames][k][:name], w] }.sort_by{ |k,v| -v }.map(&:first)
    ]}.flatten + [:exit]

    # Print callers and callees for selection
    STDOUT.puts "Select next method:"
    new_choices.each_with_index do |method, index|
      STDOUT.printf "%2d)  %s\n", index + 1, method.to_s
    end

    # Pick selection
    STDOUT.printf "> "
    selection = STDIN.gets.chomp.to_i - 1
    STDOUT.puts "\n\n\n"

    # Determine if it was a valid choice
    # (if not, don't re-run .print_method)
    if new_choice = new_choices[selection]
      invalid_choice = false
      method_choice = new_choice == :exit ? :exit : /^#{Regexp.escape new_choice}$/
    else
      invalid_choice = true
      STDOUT.puts "Invalid choice.  Please select again..."
    end
  end
end