summaryrefslogtreecommitdiff
path: root/tasks/benchmark.rake
blob: 69c0fc27c48777699638097dfe4f39d0e99e199b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
require 'benchmark'
require 'tmpdir'
require 'csv'

namespace :benchmark do
  def generate_scenario_tasks(location, name)
    desc File.read(File.join(location, 'description'))
    task name => "#{name}:run"
    # Load a BenchmarkerTask to handle config of the benchmark
    task_handler_file = File.expand_path(File.join(location, 'benchmarker_task.rb'))
    if File.exist?(task_handler_file)
      require task_handler_file
      run_args = BenchmarkerTask.run_args
    else
      run_args = []
    end

    namespace name do
      task :setup do
        ENV['ITERATIONS'] ||= '10'
        ENV['SIZE'] ||= '100'
        ENV['TARGET'] ||= Dir.mktmpdir(name)
        ENV['TARGET'] = File.expand_path(ENV['TARGET'])

        mkdir_p(ENV['TARGET'])

        require File.expand_path(File.join(location, 'benchmarker.rb'))

        @benchmark = Benchmarker.new(ENV['TARGET'], ENV['SIZE'].to_i)
      end

      task :generate => :setup do
        @benchmark.generate
        @benchmark.setup
      end

      desc "Run the #{name} scenario."
      task :run, [*run_args] =>  :generate do |_, args|
        format = if RUBY_VERSION =~ /^1\.8/
                   Benchmark::FMTSTR
                 else
                   Benchmark::FORMAT
                 end
        report = []
        details = []
        Benchmark.benchmark(Benchmark::CAPTION, 10, format, "> total:", "> avg:") do |b|
          times = []
          ENV['ITERATIONS'].to_i.times do |i|
            start_time = Time.now.to_i
            times << b.report("Run #{i + 1}") do
              details << @benchmark.run(args)
            end
            report << [to_millis(start_time), to_millis(times.last.real), 200, true, name]
          end

          sum = times.inject(Benchmark::Tms.new, &:+)

          [sum, sum / times.length]
        end

        write_csv("#{name}.samples",
                  %w{timestamp elapsed responsecode success name},
                  report)

        # report details, if any were produced
        if details[0].is_a?(Array) && details[0][0].is_a?(Benchmark::Tms)
          # assume all entries are Tms if the first is
          # turn each into a hash of label => tms (since labels are lost when doing arithmetic on Tms)
          hashed = details.reduce([]) do |memo, measures|
            memo << measures.reduce({}) {|memo2, measure| memo2[measure.label] = measure; memo2}
            memo
          end
          # sum across all hashes
          result = {}

          hashed_totals = hashed.reduce {|memo, h| memo.merge(h) {|k, old, new| old + new }}
          # average the totals
          hashed_totals.keys.each {|k| hashed_totals[k] /= details.length }
          min_width = 14
          max_width = (hashed_totals.keys.map(&:length) << min_width).max
          puts "\n"
          puts sprintf("%2$*1$s %3$s", -max_width, 'Details (avg)', "      user     system      total        real")
          puts "-" * (46 + max_width)
          hashed_totals.sort.each {|k,v| puts sprintf("%2$*1$s %3$s", -max_width, k, v.format) }
        end
      end

      desc "Profile a single run of the #{name} scenario."
      task :profile, [:warm_up_runs, *run_args] => :generate do |_, args|
        warm_up_runs = (args[:warm_up_runs] || '0').to_i
        warm_up_runs.times do
          @benchmark.run(args)
        end

        require 'ruby-prof'

        result = RubyProf.profile do
          @benchmark.run(args)
        end

        printer = RubyProf::CallTreePrinter.new(result)
        File.open(File.join("callgrind.#{name}.#{Time.now.to_i}.trace"), "w") do |f|
          printer.print(f)
        end
      end

      def to_millis(seconds)
        (seconds * 1000).round
      end

      def write_csv(file, header, data)
        CSV.open(file, 'w') do |csv|
          csv << header
          data.each do |line|
            csv << line
          end
        end
      end
    end
  end

  scenarios = []
  Dir.glob('benchmarks/*') do |location|
    name = File.basename(location)
    scenarios << name
    generate_scenario_tasks(location, File.basename(location))
  end

  namespace :all do
    desc "Profile all of the scenarios. (#{scenarios.join(', ')})"
    task :profile do
      scenarios.each do |name|
        sh "rake benchmark:#{name}:profile"
      end
    end

    desc "Run all of the scenarios. (#{scenarios.join(', ')})"
    task :run do
      scenarios.each do |name|
        sh "rake benchmark:#{name}:run"
      end
    end
  end
end