#!/usr/bin/ruby
# 2013-01-15 12:48
# val1-Rtable.rb  DWF
# Convert profiling output from val1 to an R table with fixed columns.
#
# Usage:  val1-Rtable.rb [file ...]
# Input:  one or more perf scripts, or a concatenation of perf scripts,
#         in the format -f ip,sym,dso [NO CALLCHAINS]
#           ... OR ...
#         one or more opreport outputs in the format -l
#           ... OR ...
#         one or more gprof outputs in the format -b --no-graph
# Output:  sample counts by function, one row per run, in R table format.

# Hardcoded fixed columns for val1.
$fns = ["function1", "function2", "function3", "function4", "function5", "main", "other"]
$counts = Array.new($fns.size, 0)

# Print array contents on one line with spaces between fields.
def space_nicely(a)
  a.take(a.size-1).each { |x| print x.to_s + " " }
  puts a.last
end

# Add x samples for function f, redirecting to "other" as applicable.
def tally(f,x)
  fni = $fns.index(f)
  fni = $fns.size-1 if fni.nil?
  $counts[fni] += x
end

space_nicely($fns)

# Auto-identify the input format based on the first character.
# (This DOES consume the first line of input.)
magiknum = ARGF.lines.first[0]
if magiknum == "#"
  # perf format
  state = 0
  ARGF.lines do |line|
    if line[0] != "#"
      if state == 0
	$counts.fill(0)
	state = 1
      end
      raise "Perf input format error" if line[0] != " "
      tally(line.split(' ')[1], 1)
    else
      if state == 1
	space_nicely($counts)
	state = 0
      end
    end
  end
  space_nicely($counts) if state == 1
elsif magiknum == 'C'
  # OProfile format (opreport)
  state = 0
  ARGF.lines do |line|
    if "0123456789".include? line[0]
      if state == 0
	$counts.fill(0)
	state = 1
      end
      ls = line.split(' ')
      # Sometimes we get image name column, sometimes not.
      tally(ls.last, ls[0].to_i)
    else
      if state == 1
	space_nicely($counts)
	state = 0
      end
    end
  end
  space_nicely($counts) if state == 1
elsif magiknum == 'F'
  # Gprof format.

  # Gprof does not actually have discrete sample counts for symbols.  It uses
  # a binning scheme in which the credit for a given sample can be
  # fractionated among multiple symbols.  These results are stored as doubles
  # and then scaled by the sample rate (100 Hz) to obtain self seconds.
  # Multiplying self seconds by 100 therefore gives the effective sample
  # count, rounded to an integer.

  state = 0
  ARGF.lines do |line|
    if state == 0
      $counts.fill(0)
      state = 1
    elsif state == 1
      raise "Gprof input format error 1" if line.chomp! != "Each sample counts as 0.01 seconds."
      state = 2
    elsif state < 4
      state += 1
    else
      if "1 ".include? line[0]
        ls = line.split(' ')
        tally(ls.last, (ls[2].to_r * 100).to_i)
      else
	space_nicely($counts)
	raise "Gprof input format error 2" if line.chomp! != "Flat profile:"
	state = 0
      end
    end
  end
  space_nicely($counts) if state == 4
end
