#!/usr/bin/env ruby

LKP_SRC = ENV['LKP_SRC'] || File.dirname(File.dirname(File.realpath($PROGRAM_NAME)))

require "#{LKP_SRC}/lib/array_ext"
require "#{LKP_SRC}/lib/string_ext"

# example input:
# ============================================================================================================================
# Type         Ops/sec     Hits/sec   Misses/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec
# ----------------------------------------------------------------------------------------------------------------------------
# Sets        21659.56          ---          ---         1.72237         1.68700         2.99100         4.28700     22716.77
# Gets        86638.24     13005.02     73633.22         1.72807         1.69500         3.00700         4.31900     16523.21
# Waits           0.00          ---          ---             ---             ---             ---             ---          ---
# Totals     108297.80     13005.02     73633.22         1.72693         1.69500         3.00700         4.31900     39239.98
# 
# 
# Request Latency Distribution
# Type     <= msec         Percent
# ------------------------------------------------------------------------
# SET       0.279         0.00
# SET       1.095         5.00
# ...
# SET     346.111       100.00
# ---
# GET       0.279         0.00
# GET       1.095         5.00
# ...
# GET     348.159       100.00
# ---
# WAIT      0.000       100.00
# 

$histo_sets_sum = Array.new(9, 0)
$histo_sets_num = Array.new(9, 0)
$histo_gets_sum = Array.new(9, 0)
$histo_gets_num = Array.new(9, 0)
$histo_waits_sum = Array.new(9, 0)
$histo_waits_num = Array.new(9, 0)
$histo_totals_sum = Array.new(9, 0)
$histo_totals_num = Array.new(9, 0)
set_latencies = []
get_latencies = []
proc_set_latencies = []
proc_get_latencies = []
PERCENTILE_STRS = ['90', '95', '99', '99.9'].freeze
PERCENTILES = PERCENTILE_STRS.map(&:to_f)

def extract_memtier(line, histo_sum, histo_num)
  data = line.split
  (1..data.size - 1).each do |i|
    histo_sum[i] += data[i].to_f
    histo_num[i] += 1
  end
end

def memtier(line)
  case line
  when /^preload duration: (\d+\.\d+)$/
    puts "preload_duration: #{$1}"
  end
end

while (line = STDIN.gets)
  line = line.resolve_invalid_bytes

  if line =~ /^Sets/
    extract_memtier(line, $histo_sets_sum, $histo_sets_num)
  elsif line =~ /^Gets/
    extract_memtier(line, $histo_gets_sum, $histo_gets_num)
  elsif line =~ /^Waits/
    extract_memtier(line, $histo_waits_sum, $histo_waits_num)
  elsif line =~ /^Totals/
    extract_memtier(line, $histo_totals_sum, $histo_totals_num)
  elsif line =~ /^Request Latency Distribution/
    proc_set_latencies = []
    proc_set_latencies = []
  elsif line =~ /^SET/
    is_set = true
    data = line.split
    s_10us = (data[1].to_f * 100).to_i
    proc_set_latencies[s_10us] = data[2].to_f
  elsif line =~ /^GET/
    is_set = false
    data = line.split
    s_10us = (data[1].to_f * 100).to_i
    proc_get_latencies[s_10us] = data[2].to_f
  elsif line =~ /^---$/
    if is_set
      set_latencies << proc_set_latencies
    else
      get_latencies << proc_get_latencies
    end
  else
    memtier(line)
  end
end

def gen_output_sum(type, histo_sum)
  puts "total_#{type}_ops/s: #{histo_sum[1]}"
  puts "total_#{type}_hits/s: #{histo_sum[2]}"
  puts "total_#{type}_misses/s: #{histo_sum[3]}"
  puts "total_#{type}_avg_latency_ms: #{histo_sum[4]}"
  puts "total_#{type}_p50_latency_ms: #{histo_sum[5]}"
  puts "total_#{type}_p99_latency_ms: #{histo_sum[6]}"
  puts "total_#{type}_p99.9_latency_ms: #{histo_sum[7]}"
  puts "total_#{type}_kb/s: #{histo_sum[8]}"
end

def gen_output_avg(type, histo_sum, histo_num)
  avg_tmp = histo_sum[1] / histo_num[1]
  puts "avg_#{type}_ops/s: #{avg_tmp}"
  avg_tmp = histo_sum[2] / histo_num[2]
  puts "avg_#{type}_hits/s: #{avg_tmp}"
  avg_tmp = histo_sum[3] / histo_num[3]
  puts "avg_#{type}_misses/s: #{avg_tmp}"
  avg_tmp = histo_sum[4] / histo_num[4]
  puts "avg_#{type}_avg_latency_ms: #{avg_tmp}"
  avg_tmp = histo_sum[5] / histo_num[5]
  puts "avg_#{type}_p50_latency_ms: #{avg_tmp}"
  avg_tmp = histo_sum[6] / histo_num[6]
  puts "avg_#{type}_p99_latency_ms: #{avg_tmp}"
  avg_tmp = histo_sum[7] / histo_num[7]
  puts "avg_#{type}_p99.9_latency_ms: #{avg_tmp}"
  avg_tmp = histo_sum[8] / histo_num[8]
  puts "avg_#{type}_kb/s: #{avg_tmp}"
end

def gen_output_miss_rate(type, histo_sum, histo_num)
  avg_hits = histo_sum[2] / histo_num[2]
  avg_misses = histo_sum[3] / histo_num[3]
  avg_total = avg_hits + avg_misses
  return unless avg_total != 0

  miss_rate = 100 * avg_misses / avg_total
  puts "#{type}_miss_rate_%: #{miss_rate}"
end

gen_output_sum('sets', $histo_sets_sum)
gen_output_sum('gets', $histo_gets_sum)
gen_output_sum('waits', $histo_waits_sum)
gen_output_sum('totals', $histo_totals_sum)
gen_output_avg('sets', $histo_sets_sum, $histo_sets_num)
gen_output_avg('gets', $histo_gets_sum, $histo_gets_num)
gen_output_miss_rate('gets', $histo_gets_sum, $histo_gets_num)
gen_output_avg('waits', $histo_waits_sum, $histo_waits_num)
gen_output_avg('totals', $histo_totals_sum, $histo_totals_num)

def normalize_latencies(latencies)
  maxcol = latencies.map(&:size).max
  latencies.each do |proc_latencies|
    prev = 0
    (0...maxcol).map do |i|
      percent = proc_latencies[i]
      percent ||= prev
      prev = percent
      proc_latencies[i] = percent
    end
  end
end

def show_latencies(latencies, name)
  pi = 0
  latencies.transpose.each_with_index do |ps, i|
    sum = ps.sum
    next unless sum != 0

    avg = sum.to_f / ps.length
    while pi < PERCENTILES.length && avg > PERCENTILES[pi]
      i_ms = i.to_f / 100
      puts "#{name}_latency_#{PERCENTILE_STRS[pi]}%_ms: #{i_ms}"
      pi += 1
    end
    break if avg > PERCENTILES[-1]
  end
end

normalize_latencies(set_latencies)
normalize_latencies(get_latencies)

show_latencies(set_latencies, 'set')
show_latencies(get_latencies, 'get')
