# Benchmark Class for SystemTap
# Copyright (C) 2006, 2007 Red Hat Inc.
#
# This file is part of systemtap, and is free software.  You can
# redistribute it and/or modify it under the terms of the GNU General
# Public License (GPL); either version 2, or (at your option) any
# later version.

# Where to find laptop frequency files
MAXFILE = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"
MINFILE = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq"

# more constants
STREAM = 1
BULK= 2

at_exit {Bench.done}

class Bench
  def initialize(desc)
    @desc = desc
    @code = nil
    @file = nil
    @trans = STREAM
    @outfile= "xxx"
    @failures = []
    @results = []
    if @@printed_header == 0
      print_header
    end
    if @@staprun.nil?
      for path in ['/usr/bin/staprun', '/usr/local/bin/staprun'] 
	if File.exist?(path)
	  @@staprun = path
	  break
	end
      end
      # now do make
      `make`
      if $? != 0 || !File.exist?('itest')
	puts "ERROR: Compiling itest failed\n"
	exit
      end
    end
    if @@runtime.nil?
      for path in ['/usr/share/systemtap/runtime', '/usr/local/share/systemtap/runtime'] 
	if File.exist?(path)
	  @@runtime = path
	  break
	end
      end
    end
    Signal.trap("INT") do
      cleanup
      exit
    end
  end

  attr_writer :code, :file, :trans, :outfile
  attr_reader :failures, :results

  def run
    compile
    @results = []
    @failures = []
    @@num_threads.each do |threads|
      load
      sum=0
      `./itest #{threads} > #{@dir}/bench`
      `sudo killall -HUP stapio`
      Process.wait	# wait for staprun to exit
      @results[threads] = `cat #{@dir}/bench`.split[0].to_i - @@ftime[threads]
      File.open("#{@dir}/xxx.out") do |file|
	file.each_line do |line|
	  m = line.match(/WARNING: There were ([\d]*)/)
	  if (m)
	    @failures[threads] = m[1]
	    break
	  end
	end
      end
    end
    cleanup
  end

  def print
    if @results
      if self.kind_of? Stapbench
	printf("S")
      else
	printf("R")
      end
      if @trans == BULK
	printf("B")
      else
	printf(" ")
      end
      if @outfile == "/dev/null"
	printf("N")
      else
	printf(" ")
      end
      printf(": %-20s", @desc)
      @@num_threads.each {|thread| printf("\t%5d",@results[thread])}
      printf("\n")
      @@num_threads.each do |thread|
	printf("WARNING: %d transport failures with %d threads\n", \
	       @failures[thread], thread) unless @failures[thread].nil?
      end
    end
  end

  def Bench.done
    if @@minfreq != 0
      `sudo /bin/sh -c \"echo #{@@minfreq} > #{MINFILE}\"`
    end
  end

  private
  @dir = nil

  protected

  @@ftime = []
  @@printed_header = 0
  @@staprun = nil
  @@runtime = nil
  @@num_threads = []
  @@minfreq = 0

  def cleanup
    system('sudo killall -HUP stapio &> /dev/null')
    system('sudo /sbin/rmmod bench &> /dev/null')
    `/bin/rm -f stap.out` if File.exists?("stap.out")
    `/bin/rm -f bench.stp` if File.exists?("bench.stp")
    `/bin/rm -rf #{@dir}` unless @dir.nil?
    `/bin/rm -f stpd_cpu* probe.out`
  end

  def load
    fork do exec "sudo #{@@staprun} -o #{@outfile} #{@dir}/bench.ko &> #{@dir}/xxx.out" end
    sleep 5
  end

  def compile
    @dir = `mktemp -dt benchXXXXX`.strip
    emit_code
    modules = "/lib/modules/" + `uname -r`.strip + "/build"
    res=`make -C \"#{modules}\" M=\"#{@dir}\" modules V=1`
    if !File.exist?("#{@dir}/bench.ko")
      puts res
      cleanup
      exit
    end
  end

  def emit_code
    if @code
      begin
	getuid="0x"+`grep sys_getuid$ /proc/kallsyms`.split[0]
	f = File.new("#{@dir}/bench.c","w")
	f << "#include \"runtime.h\"
MODULE_DESCRIPTION(\"SystemTap probe: bench\");
MODULE_AUTHOR(\"automatically generated by bench2/run_bench\");\n\n"
	f << "static int inst_sys_getuid (struct kprobe *p, struct pt_regs *regs) {\n"
	f << @code
	f << "\n  return 0;\n}\n"
	f << " static struct kprobe kp = {\n"
	f << "  .addr = (kprobe_opcode_t *)#{getuid}, \n"
	f << ".pre_handler = inst_sys_getuid\n  };\n
int probe_start(void)\n{\n  return register_kprobe(&kp);\n}\n
void probe_exit (void)\n{\n  unregister_kprobe (&kp); \n}\n"
      rescue
	puts "Error writing source file"
	exit
      ensure
	f.close unless f.nil?
      end
      makefile = File.new("#{@dir}/Makefile","w")
      makefile << "CFLAGS += -Wno-unused -Werror
CFLAGS += -I \"#{@@runtime}\"
obj-m := bench.o
"
      if @trans == BULK then makefile << "CFLAGS += -DSTP_BULKMODE" end
      makefile.close
    else
      puts "NO CODE!"
    end
  end

  def print_header
    @@printed_header = 1
    nproc=`grep ^processor /proc/cpuinfo`.count("\n")
    if nproc >= 16
      @@num_threads = [1,2,4,16]
    elsif nproc >= 8
      @@num_threads = [1,2,4,8]
    elsif nproc >= 4
      @@num_threads = [1,2,4]
    elsif nproc >= 2
      @@num_threads = [1,2]
    else
      @@num_threads = [1]
    end
    arch=`uname -m`.strip
    if (arch.match(/ppc64/))
	cpu=`grep "cpu" /proc/cpuinfo`.match(/(cpu\t\t: )([^\n]*)/)[2]
	clock=`grep "clock" /proc/cpuinfo`.match(/(clock\t\t: )([^\n]*)/)[2]
	revision=`grep "revision" /proc/cpuinfo`.match(/(revision\t: )([^\n]*)/)[2]
	cpuinfo=cpu + " " + clock + " revision: " + revision
    else
       	physical_cpus=`grep "physical id" /proc/cpuinfo`.split("\n").uniq.length
       	model=`grep "model name" /proc/cpuinfo`.match(/(model name\t: )([^\n]*)/)[2]
	cpuinfo="(#{physical_cpus} physical) #{model}"
    end	
    puts "SystemTap BENCH2 \t" + `date`
    puts "kernel: " + `uname -r`.strip + " " + `uname -m`.strip
    puts IO.read("/etc/redhat-release") if File.exists?("/etc/redhat-release")
    puts `uname -n`.strip + ": " + `uptime`
    puts "processors: #{nproc} #{cpuinfo}"

    begin
      mem=IO.read("/proc/meminfo").split("\n")
      puts mem[0] + "\t" + mem[1]
    rescue
    end
    puts "-"*64
    check_cpuspeed
    @@num_threads.each do |threads|
      @@ftime[threads] = `./itest #{threads} 10000000`.to_i
    end
    puts "Times below are nanoseconds per probe and include kprobe overhead."
    puts "-"*64
    puts "+---- S = Script, R = Runtime"
    puts "|+--- B = Bulk"
    puts "||+-- N = No output\t\t  Threads"
    printf "||| NAME                 "
    @@num_threads.each {|n| printf("\t    %d",n)}
    printf "\n"
  end

  def check_cpuspeed
    if @@minfreq == 0 && File.exist?(MAXFILE)
      maxfreq = `cat #{MAXFILE}`.to_i
      @@minfreq = `cat #{MINFILE}`.to_i
      if @@minfreq != maxfreq
	`sudo /bin/sh -c \"echo #{maxfreq} > #{MINFILE}\"`
	sleep 1
      end
    end
  end
end

#### STAPBENCH ######

class Stapbench < Bench

  def run
    compile
    @results = []
    @failures = []
    @@num_threads.each do |threads|
      load
      sum=0
      `./itest #{threads} > bench`
      `sudo killall -HUP stapio`
      if ($? != 0)
	puts "status=#{$?}"
	system("tail xxx.out")
      end
      Process.wait	# wait for stapio to exit
      @results[threads] = `cat bench`.split[0].to_i - @@ftime[threads]
      File.open("xxx.out") do |file|
	file.each_line do |line|
	  m = line.match(/WARNING: There were ([\d]*)/)
	  if (m)
	    @failures[threads] = m[1]
	    break
	  end
	end
      end
    end
    cleanup
  end

  protected

  def load
    fork do exec "sudo #{@@staprun} bench.ko &> xxx.out" end
    sleep 5
  end
  
  def compile
    if @file
      File.open("bench.stp","w") do |b|
	File.open(@file,"r") do |f|
	  f.each_line do |line|
	    b.puts(line.sub(/TEST/,'kernel.function("sys_getuid")'))
	  end
	end
      end
    elsif @code
      begin
	f = File.new("bench.stp","w")
	f << "probe kernel.function(\"sys_getuid\") {\n"
	f << @code
	f << "\n}\n"
      rescue
	puts "Error writing source file"
      ensure
	f.close unless f.nil?
      end
    else
      puts "NO CODE!"
    end
    args = "-p4 -vv -DSTP_NO_OVERLOAD"
    if @trans == BULK then args = "-p4 -bvv -DSTP_NO_OVERLOAD" end
    `stap #{args} -m bench.ko bench.stp &> xxx.out`
    if ($? != 0)
      puts "compile failed. status=#{$?}"
      system("tail xxx.out")
      exit
    end
  end
  
end
