#!/usr/bin/env ruby

require 'rubygems'

require 'optparse'
require 'ostruct'

$:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
require 'railsbench/perf_info'

# parse options
o = OpenStruct.new
o.selection = []
o.title = "Performance Graph"
o.files = []
o.names = []
o.labels = []
o.graph_type = :line
o.graph_width = '800x600'
o.font_size = 14
o.output_file = nil
o.colors = nil
o.theme = nil
o.engine = :gruff

parser = OptionParser.new do |opts|
  opts.banner = "Usage: perf_plot [options] file1 file2 ..."

  opts.separator ""
  opts.separator "Options:"

  opts.on("-t", "--title T",
          "Specify the title for your plot") do |t|
    o.title = t
  end

  opts.on("--only LIST", Array,
          "Restrict plot to a subset of the benchmarks") do |t|
    o.selection = t.map{|s| s.to_i}
  end

  opts.on("--line",
          "Plot a line graph") do |t|
    o.graph_type = :line
  end

  opts.on("--bar",
          "Plot a bar graph") do |t|
    o.graph_type = :bar
  end

  opts.on("-w", "--width W", Integer,
          "Width of the plot (pixels)") do |w|
    o.graph_width = w
  end

  opts.on("-g", "--geometry WxH", /\d+x\d+/,
          "Specify plot dimensions (pixels)") do |d|
    o.graph_width = d
  end

  opts.on("-c", "--colors LIST", Array,
          "Use specified colors for lines/bars") do |t|
    o.colors = t
  end

  opts.on("--theme NAME",
          "Use specified theme") do |t|
    o.theme = t
  end

  opts.on("-e", "--engine ENGINE", [:gruff, :gnuplot],
          "Select plotting engine: (gruff, gnuplot)") do |e|
    o.engine = e
  end

  opts.on("-n", "--names LIST", Array,
          "Use specified names for the legend") do |t|
    o.names = t
  end

  opts.on("-l", "--labels LIST", Array,
          "Use specified labels instead of benchmark names") do |t|
    o.labels = t
  end

  opts.on("-o", "--out FILE",
          "Specify output file") do |f|
    o.output_file = f
  end

  opts.on("-f", "--font-size N", Integer,
          "Overall font size to use in the plot (points)") do |n|
    o.font_size = n
  end

  opts.on_tail("-h", "--help", "Show this message") do
    puts opts
    exit
  end
end

# option compatibility with older versions
args=[]
ARGV.each do |arg|
  arg = arg.sub("font_size", "font-size") if arg =~ /^-font_size/
  arg = "-" + arg if arg =~ /^-(title|width|out|geometry|font-size|names|labels|colors|line|bar|only)/
  args << arg
end

parser.parse!(args)

o.output_file ||= o.engine == :gruff ? "graph.png" : "graph.pdf"

args.each do |arg|
  o.files << File.open_or_die(arg)
  o.names[o.files.length-1] ||= File.basename(arg).sub(/\.txt$/, '').sub(/^\d\d-\d\d\.[^\.]+./, '')
end

o.files.length > 0 or die(parser.banner)

class Plotter
  attr_reader :o
  def initialize(options)
    @o = options
    setup
  end

  def setup
    pi = nil
    @perf_data = []
    o.files.each do |file|
      pi = PerfInfo.new(file)
      iter = pi.iterations
      urls = pi.requests_per_key
      @perf_data << pi.keys.map{ |key| iter*urls/pi.timings_mean(key) }
      file.close
    end
    o.selection = (1..(@perf_data.last.length)).to_a if o.selection.empty?
    if o.labels.empty?
      o.labels = pi.keys.restrict_to(o.selection.map{|i| i-1})
    end
    @perf_data = @perf_data.map{|d| d.restrict_to(o.selection.map{|i| i-1})}
  end

  def plot_with_gruff
    require 'gruff'

    g = o.graph_type == :line ? Gruff::Line.new(o.graph_width) : Gruff::Bar.new(o.graph_width)
    g.send "theme_#{o.theme}" if o.theme

    # on OS X, ImagMagick can't find it's default font (arial) sometimes, so specify some font
    #g.font = 'Arial-Normal' if RUBY_PLATFORM =~ /darwin/
    g.font = 'Helvetica' if RUBY_PLATFORM =~ /darwin/

    g.replace_colors(o.colors) if o.colors
    g.title = o.title
    g.sort = false
    g.legend_font_size = o.font_size
    g.legend_box_size = o.font_size
    g.marker_font_size = o.font_size
    g.minimum_value = 0
    g.maximum_value = @perf_data.flatten.max.ceil
    g.labels = o.labels.index_map
    @perf_data.each_with_index{|d,i| g.data(o.names[i], d)}
    g.write(o.output_file)
  end

  def plot_with_gnuplot
    require 'gnuplot'
    Gnuplot.open(false) do |gnuplot|
      Gnuplot::Plot.new(gnuplot) do |plot|
        plot.terminal "pdf noenhanced color fname 'Helvetica' fsize 5"
        plot.output o.output_file
        plot.xlabel "Benchmarks"
        plot.ylabel "Requests per second"
        plot.yrange "[0:*]"
        plot.title  o.title
        plot.style  "fill solid 1.0 noborder"
        plot.style  "data #{o.graph_type == :line ? "linespoints" : "histogram"}"
        plot.style  "histogram cluster gap 1"
        plot.xtics  "nomirror rotate by -45"
        label_specs = []
        o.labels.each_with_index{ |l,i| label_specs << "\"#{l}\" #{i}" }
        plot.xtics  "(#{label_specs.join(', ')})"
        plot.key    "outside reverse"
        #plot.boxwidth "0.95"
        plot.grid "nopolar"
        plot.grid "noxtics nomxtics ytics nomytics noztics nomztics nox2tics nomx2tics noy2tics nomy2tics nocbtics nomcbtics"
        plot.grid "back linetype 0 linewidth 0.7"

        @perf_data.each_with_index do |d,i|
          plot.data << Gnuplot::DataSet.new([[o.names[i]] + d]) do |ds|
            ds.using = "1 title 1"
          end
        end
      end
    end
  end

  def plot
    send "plot_with_#{o.engine}"
  end
end

Plotter.new(o).plot


__END__

#    Copyright (C) 2005-2008  Stefan Kaes
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
