Completed
Push — master ( cb5396...b3a8d6 )
by Michael
03:06
created

lib/nose/cli/benchmark.rb (6 issues)

1 1
require 'csv'
0 ignored issues
show
Missing frozen string literal comment.
Loading history...
2 1
require 'table_print'
3
4 1
module NoSE
5 1
  module CLI
6
    # Run performance tests on plans for a particular schema
7 1
    class NoSECLI < Thor
8 1
      desc 'benchmark PLAN_FILE', 'test performance of plans in PLAN_FILE'
9
10 1
      long_desc <<-LONGDESC
11
        `nose benchmark` will take a JSON file output by `nose search`,
12
        execute each statement, and output a summary of the execution times.
13
        Before runnng benchmark, `nose create` and `nose load` must be used to
14
        prepare the target database.
15
      LONGDESC
16
17 1
      shared_option :mix
18
19 1
      option :num_iterations, type: :numeric, default: 100,
20
                              banner: 'ITERATIONS',
21
                              desc: 'the number of times to execute each ' \
22
                                    'statement'
23 1
      option :repeat, type: :numeric, default: 1,
24
                      desc: 'how many times to repeat the benchmark'
25 1
      option :group, type: :string, default: nil, aliases: '-g',
26
                     desc: 'restrict the benchmark to statements in the ' \
27
                           'given group'
28 1
      option :fail_on_empty, type: :boolean, default: true,
29
                             desc: 'abort if a column family is empty'
30 1
      option :totals, type: :boolean, default: false, aliases: '-t',
31
                      desc: 'whether to include group totals in the output'
32 1
      option :format, type: :string, default: 'txt',
33
                      enum: %w(txt csv), aliases: '-f',
34
                      desc: 'the format of the output data'
35
36 1
      def benchmark(plan_file)
0 ignored issues
show
The Assignment, Branch, Condition size for benchmark is considered too high. [117/20]. The ABC size is based on assignments, branches (method calls), and conditions.
Loading history...
The method benchmark seems to be too complex. Perceived cyclomatic complexity is 15 with a maxiumum of 10 permitted.
Loading history...
This method is 71 lines long. Your coding style permits a maximum length of 20.
Loading history...
Complexity Coding Style introduced by
The method benchmark seems to be too complex. Perceived complexity is 15 with a maxiumum of 10 permitted.
Loading history...
37
        label = File.basename plan_file, '.*'
38
        result = load_results plan_file, options[:mix]
39
40
        backend = get_backend(options, result)
41
42
        index_values = index_values result.indexes, backend,
43
                                    options[:num_iterations],
44
                                    options[:fail_on_empty]
45
46
        group_tables = Hash.new { |h, k| h[k] = [] }
47
        group_totals = Hash.new { |h, k| h[k] = 0 }
48
        result.plans.each do |plan|
49
          query = plan.query
50
          weight = result.workload.statement_weights[query]
51
          next if query.is_a?(SupportQuery) || !weight
52
          @logger.debug { "Executing #{query.text}" }
53
54
          next unless options[:group].nil? || plan.group == options[:group]
55
56
          indexes = plan.select do |step|
57
            step.is_a? Plans::IndexLookupPlanStep
58
          end.map(&:index)
59
60
          measurement = bench_query backend, indexes, plan, index_values,
61
                                    options[:num_iterations], options[:repeat],
62
                                    weight: weight
63
          next if measurement.empty?
64
65
          measurement.estimate = plan.cost
66
          group_totals[plan.group] += measurement.mean
67
          group_tables[plan.group] << measurement
68
        end
69
70
        result.workload.updates.each do |update|
71
          weight = result.workload.statement_weights[update]
72
          next unless weight
73
74
          plans = (result.update_plans || []).select do |possible_plan|
75
            possible_plan.statement == update
76
          end
77
          next if plans.empty?
78
79
          @logger.debug { "Executing #{update.text}" }
80
81
          plans.each do |plan|
82
            next unless options[:group].nil? || plan.group == options[:group]
83
84
            # Get all indexes used by support queries
85
            indexes = plan.query_plans.flat_map(&:indexes) << plan.index
86
87
            measurement = bench_update backend, indexes, plan, index_values,
88
                                       options[:num_iterations],
89
                                       options[:repeat], weight: weight
90
            next if measurement.empty?
91
92
            measurement.estimate = plan.cost
93
            group_totals[plan.group] += measurement.mean
94
            group_tables[plan.group] << measurement
95
          end
96
        end
97
98
        total = 0
99
        table = []
100
        group_totals.each do |group, group_total|
101
          total += group_total
102
          total_measurement = Measurements::Measurement.new nil, 'TOTAL'
103
          group_table = group_tables[group]
104
          total_measurement << group_table.map(&:weighted_mean) \
105
                               .inject(0, &:+)
106
          group_table << total_measurement if options[:totals]
107
          table << OpenStruct.new(label: label, group: group,
108
                                  measurements: group_table)
109
        end
110
111
        if options[:totals]
112
          total_measurement = Measurements::Measurement.new nil, 'TOTAL'
113
          total_measurement << table.map do |group|
114 View Code Duplication
            group.measurements.find { |m| m.name == 'TOTAL' }.mean
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
115
          end.inject(0, &:+)
116
          table << OpenStruct.new(label: label, group: 'TOTAL',
117
                                  measurements: [total_measurement])
118
        end
119
120
        case options[:format]
121
        when 'txt'
122
          output_table table
123
        else
124
          output_csv table
125
        end
126
      end
127
128 1
      private
129
130
      # Get a sample of values from each index used by the queries
131
      # @return [Hash]
132 1
      def index_values(indexes, backend, iterations, fail_on_empty = true)
133
        Hash[indexes.map do |index|
134
          values = backend.index_sample(index, iterations).to_a
135
          fail "Index #{index.key} is empty and will produce no results" \
136
            if values.empty? && fail_on_empty
137
138
          [index, values]
139
        end]
140
      end
141
    end
142
  end
143
end
144