#!/usr/bin/ruby

require 'optparse'
require 'ostruct'

NFS_MNT="/fs/nfs"

$cfield = "kernel"
$cfield_hash = Hash.new
$cfield_array = Array.new
$cases = Hash.new
$sum = Array.new
$grep = //
$vgrep = /SOME IMPOSSIBLE PATTERN sdfghjkl:1234567890;/

$evaluate = "write_bw"

$nfs_nr_commits = 0
$nfs_nr_writes = 0

$nfs_write_total = 0
$nfs_commit_size = 0
$nfs_write_size = 0

$nfs_write_queue_time = 0
$nfs_write_rtt_time = 0
$nfs_write_execute_time = 0

$nfs_commit_queue_time = 0
$nfs_commit_rtt_time = 0
$nfs_commit_execute_time = 0

opts = OptionParser.new do |opts|
        opts.banner = "Usage: compare.rb [options] cases..."

        opts.separator ""
        opts.separator "options:"

        opts.on("-c FIELD", "--compare FIELD", "compare FIELD: fs/kernel/job/thresh/bg_thresh") do |field|
                $cfield = field
		# puts "#{$cfield}\n"
        end
       
        opts.on("-e FIELD", "--evaluate FIELD", "evaluate FIELD: write_bw/nfs_*") do |field|
                $evaluate = field
        end
       
        opts.on("-g PATTERN", "--grep PATTERN", "only compare cases that match PATTERN") do |pattern|
                $grep = Regexp.new(pattern)
        end
       
        opts.on("-v PATTERN", "--reverse-grep PATTERN", "exclude cases that match PATTERN") do |pattern|
                $vgrep = Regexp.new(pattern)
        end
       
        opts.on_tail("-h", "--help", "Show this message") do
                puts opts
                exit
        end

end

opts.parse!(ARGV)

# http://bits.stephan-brumme.com/roundUpToNextPowerOfTwo.html
def roundUpToNextPowerOfTwo(x)
	x -= 1
	x |= x >> 1;  # handle  2 bit numbers
	x |= x >> 2;  # handle  4 bit numbers
	x |= x >> 4;  # handle  8 bit numbers
	x |= x >> 8;  # handle 16 bit numbers
	x |= x >> 16; # handle 32 bit numbers
	x += 1
	return x;
end

def iostat_cpu(path)
	file = "#{path}/iostat-cpu"
	eval "$#{$evaluate} = 0"
	vars=["user", "nice", "system", "iowait", "steal", "idle"]
	vars.each { |var| eval "$cpu_#{var} = 0.0" }
	return if not File.exist?(file)
	stat = File.new(file).readlines
	stat.each_with_index do |line, i|
		next if i < 3
		vals = line.split
		vars.each_with_index { |var, i| eval "$cpu_#{var} += #{vals[i]}" }
	end
	vars.each { |var| eval "$cpu_#{var} /= #{stat.size-3}" }
end

def vmstat(path)
	file = "#{path}/vmstat-end"
	eval "$#{$evaluate} = 0"
	return if not File.exist?(file)
	
	stat = File.new(file).readlines
	stat.each do |line|
		var, val = line.split
		eval "$#{var} = #{val}"
	end
end

def nfs_stats(path)
	file = "#{path}/mountstats-end"

	$nfs_nr_commits = 0
	$nfs_nr_writes = 0

	$nfs_write_mb = 0
	$nfs_nr_commits_per_mb = 0
	$nfs_nr_writes_per_mb = 0

	$nfs_write_queue_time = 0
	$nfs_write_rtt_time = 0
	$nfs_write_execute_time = 0

	$nfs_commit_queue_time = 0
	$nfs_commit_rtt_time = 0
	$nfs_commit_execute_time = 0

	return if not File.exist?(file)
	
	stat = File.new(file).readlines
	nfsmnt = nil
	stat.each do |line|
		if line.index("mounted on /")
			nfsmnt = line.index("mounted on #{NFS_MNT}")
		end
		next unless nfsmnt
		if line.index("WRITE: ")
			n = line.split
			$nfs_nr_writes = n[1].to_i
			next if $nfs_nr_writes == 0
			$nfs_write_total = n[4].to_f
			$nfs_write_size = $nfs_write_total / $nfs_nr_writes / (1<<20)
			$nfs_write_queue_time   = n[6].to_f / $nfs_nr_writes
			$nfs_write_rtt_time     = n[7].to_f / $nfs_nr_writes
			$nfs_write_execute_time = n[8].to_f / $nfs_nr_writes
			# puts line
			# puts $nfs_nr_writes
		end
		if line.index("COMMIT: ")
			n = line.split
			$nfs_nr_commits = n[1].to_i
			next if $nfs_nr_commits == 0
			$nfs_commit_size = $nfs_write_total / $nfs_nr_commits / (1<<20)
			$nfs_commit_queue_time   = n[6].to_f / $nfs_nr_commits
			$nfs_commit_rtt_time     = n[7].to_f / $nfs_nr_commits
			$nfs_commit_execute_time = n[8].to_f / $nfs_nr_commits
		end
	end
end

def write_bw(path)
	bw = 0 # MB/s
	file = "#{path}/trace-global_dirty_state-flusher"
	cache = "#{path}/write-bandwidth"
	if File.exist?(cache)
		cached_bw = File.new(cache).readlines
		return cached_bw[0].to_f
	end
	if File.exist?(file)
		state = File.new(file).readlines
		n = [state.size / 10, 100].min
		return 0 if n == 0
		time0, dirty, writeback, unstable, bg_thresh, thresh, limit, dirtied, written0 = state[0].split
		time, dirty, writeback, unstable, bg_thresh, thresh, limit, dirtied, written = state[-n].split
		bw = (written.to_i - written0.to_i) / (time.to_f - time0.to_f) / 256
		File.open(cache, "w") { |f| f.puts "#{bw}" }
	end
	return bw
end

def add_dd(path)
	if $evaluate == "write_bw"
		bw = write_bw(path)
		return if bw == 0 
	elsif $evaluate.index("nfs_") == 0
		nfs_stats(path)
		eval "bw = $#{$evaluate}"
	elsif $evaluate.index("cpu_") == 0
		iostat_cpu(path)
		eval "bw = $#{$evaluate}"
	else
		vmstat(path)
		eval "bw = $#{$evaluate}"
	end
	prefix = ""
	if path =~ /(.*\/)(.*)/
		prefix = $1
		path = $2
	end
	# nfs-10dd-1M-1p-32069M-20:10-3.1.0-rc4+
	path =~ /([a-z0-9]+)-([0-9]+dd)-(\d+[kMg])-(\d+p)-(\d+M)-([0-9M]+):([0-9M]+)-(.*)/;
	all, fs, job, bs, cpu, mem, thresh, bg_thresh, kernel = *$~
	if ! kernel
		path =~ /([a-z0-9]+)-(fio_[a-z_0-9]+)-(\d+[kM])-(\d+p)-(\d+M)-([0-9M]+):([0-9M]+)-(.*)/;
		all, fs, job, bs, cpu, mem, thresh, bg_thresh, kernel = *$~
	end
	if ! kernel
		path =~ /([a-z0-9]+)-(fio_[a-z_0-9]+)-(\d+[kM])-(\d+p)-(\d+M)-(.*)/;
		all, fs, job, bs, cpu, mem, kernel = *$~
		thresh = "20"
		bg_thresh = "10"
	end
	m = roundUpToNextPowerOfTwo(mem.to_i)
	mem = "#{m}M"
	ckey = ""

	prefix = "" if $cfield =~ /thresh/
	eval "ckey = #{$cfield}; #{$cfield} = 'X'"
	if ckey and !$cfield_hash.has_key?(ckey)
		$cfield_array.push(ckey)
		$cfield_hash[ckey] = 1
		$sum.push 0.0
	end
	# bs="4k"
	key = "#{prefix}#{fs}-#{job}-#{bs}-#{cpu}-#{mem}-#{thresh}:#{bg_thresh}-#{kernel}"
	if !$cases.has_key?(key)
		$cases[key] = { ckey => bw }
	else
		$cases[key][ckey] = bw
	end
	# print "#{fs}-#{job}-#{mem}-#{bw}\n"
end

ARGV.each { |path|
	if path =~ $grep and not path =~ $vgrep
		add_dd path
	end
}

if $cfield =~ /thresh|mem/
	name = $cfield + "="
else
	name = ""
end
$cfield_array.each { |ckey|
	printf "%24s  ", name + ckey
}
puts
$cfield_array.each {
	printf "------------------------  "
}
puts
$cases.sort.each { |key, value|
	n = 0
	$cfield_hash.each_key { |ckey|
		n += 1 if $cases[key][ckey]
	}
	next if n < 2
	$cfield_array.each_index { |i|
		ckey = $cfield_array[i]
		bw = $cases[key][ckey] || 0
		bw0 = $cases[key][$cfield_array[0]] || 0
		if i == 0 || bw == 0 || bw0 == 0
			printf "%24.2f  ", bw
		else
			printf "%+10.1f%% %12.2f  ", 100.0 * (bw - bw0) / bw0, bw
		end
		$sum[i] += bw
	}
	printf "%s\n", key
}

bw0 = $sum[0]
$sum.each_with_index { |bw, i|
	if i == 0
		printf "%24.2f  ", bw
	else
		printf "%+10.1f%% %12.2f  ", 100.0 * (bw - bw0) / bw0, bw
	end
}
puts "TOTAL #{$evaluate}"
