null+****@clear*****
null+****@clear*****
2011年 6月 8日 (水) 20:23:42 JST
Kouhei Sutou 2011-06-08 11:23:42 +0000 (Wed, 08 Jun 2011) New Revision: eb0137a676d7605401ac10c7067d6c8483b9bf8a Log: [query-log][analyzer] add --summry option. Modified files: tools/groonga-query-log-analyzer.rb Modified: tools/groonga-query-log-analyzer.rb (+234 -100) =================================================================== --- tools/groonga-query-log-analyzer.rb 2011-06-06 08:37:38 +0000 (2dc6863) +++ tools/groonga-query-log-analyzer.rb 2011-06-08 11:23:42 +0000 (38c162c) @@ -16,7 +16,8 @@ class GroongaQueryLogAnaylzer stream = @options[:stream] dynamic_sort = @options[:dynamic_sort] - statistics = SizedStatistics.new(@options[:n_entries], @options[:order]) + statistics = SizedStatistics.new + statistics.apply_options(@options) if stream streamer = Streamer.new(create_reporter(statistics)) streamer.start @@ -50,10 +51,12 @@ class GroongaQueryLogAnaylzer @options[:order] = "-elapsed" @options[:color] = :auto @options[:output] = "-" - @options[:slow_threshold] = 0.05 + @options[:slow_operation_threshold] = 0.05 + @options[:slow_response_threshold] = 0.1 @options[:reporter] = "console" @options[:dynamic_sort] = true @options[:stream] = false + @options[:report_summary] = true @option_parser = OptionParser.new do |parser| parser.banner += " LOG1 ..." @@ -101,11 +104,18 @@ class GroongaQueryLogAnaylzer @options[:output] = output end - parser.on("--slow-threshold=THRESHOLD", + parser.on("--slow-operation-threshold=THRESHOLD", Float, "Use THRESHOLD seconds to detect slow operations.", - "(#{@options[:slow_threshold]})") do |threshold| - @options[:slow_threshold] = threshold + "(#{@options[:slow_operation_threshold]})") do |threshold| + @options[:slow_operation_threshold] = threshold + end + + parser.on("--slow-response-threshold=THRESHOLD", + Float, + "Use THRESHOLD seconds to detect slow operations.", + "(#{@options[:sloq_response_threshold]})") do |threshold| + @options[:sloq_response_threshold] = threshold end available_reporters = ["console", "json"] @@ -130,6 +140,12 @@ class GroongaQueryLogAnaylzer "(#{@options[:stream]})") do |stream| @options[:stream] = stream end + + parser.on("--[no-]report-summary", + "Reports summary at the end.", + "(#{@options[:report_summary]})") do |report_summary| + @options[:report_summary] = report_summary + end end def create_reporter(statistics) @@ -244,7 +260,7 @@ class GroongaQueryLogAnaylzer @start_time = nil @command = nil @raw_command = nil - @steps = [] + @operations = [] @elapsed = nil @return_code = 0 end @@ -271,40 +287,41 @@ class GroongaQueryLogAnaylzer @start_time + elapsed_in_seconds end - def each_step + def each_operation previous_elapsed = 0 ensure_parse_command - step_context_context = { + operation_context_context = { :filter_index => 0, :drilldown_index => 0, } - @steps.each_with_index do |step, i| - relative_elapsed = step[:elapsed] - previous_elapsed - previous_elapsed = step[:elapsed] - parsed_step = { + @operations.each_with_index do |operation, i| + relative_elapsed = operation[:elapsed] - previous_elapsed + previous_elapsed = operation[:elapsed] + parsed_operation = { :i => i, - :elapsed => step[:elapsed], - :elapsed_in_seconds => nano_seconds_to_seconds(step[:elapsed]), + :elapsed => operation[:elapsed], + :elapsed_in_seconds => nano_seconds_to_seconds(operation[:elapsed]), :relative_elapsed => relative_elapsed, :relative_elapsed_in_seconds => nano_seconds_to_seconds(relative_elapsed), - :name => step[:name], - :context => step_context(step[:name], step_context_context), - :n_records => step[:n_records], + :name => operation[:name], + :context => operation_context(operation[:name], + operation_context_context), + :n_records => operation[:n_records], } - yield parsed_step + yield parsed_operation end end - def add_step(step) - @steps << step + def add_operation(operation) + @operations << operation end - def steps - _steps = [] - each_step do |step| - _steps << step + def operations + _operations = [] + each_operation do |operation| + _operations << operation end - _steps + _operations end def select_command? @@ -316,7 +333,7 @@ class GroongaQueryLogAnaylzer nano_seconds / 1000.0 / 1000.0 / 1000.0 end - def step_context(label, context) + def operation_context(label, context) case label when "filter" index = context[:filter_index] @@ -344,21 +361,43 @@ class GroongaQueryLogAnaylzer end class SizedStatistics < Array - def initialize(size, order) - @size = size - @order = order - @sorter = sorter + attr_reader :n_responses, :slow_operations, :slow_responses, :total_elapsed + attr_accessor :slow_operation_threshold, :slow_response_threshold + def initialize + @max_size = 10 + self.order = "-elapsed" + @slow_operation_threshold = 0.05 + @slow_response_threshold = 0.1 + @start_time = nil + @last_time = nil + @n_responses = 0 + @slow_operations = [] + @slow_responses = [] + @total_elapsed = 0 end - def <<(other) - if size < @size - 1 - super(other) - elsif size == @size - 1 - super(other) + def order=(new_order) + @order = new_order + @sorter = create_sorter + end + + def apply_options(options) + @max_size = options[:n_entries] + self.order = options[:order] + @slow_operation_threshold = options[:slow_operation_threshold] + @slow_response_threshold = options[:slow_response_threshold] + end + + def <<(statistic) + update_statistic(statistic) + if size < @max_size - 1 + super(statistic) + elsif size == @max_size - 1 + super(statistic) sort_by!(&@sorter) else - if****@sorte*****(other) < @sorter.call(last) - super(other) + if****@sorte*****(statistic) < @sorter.call(last) + super(statistic) sort_by!(&@sorter) pop end @@ -369,19 +408,40 @@ class GroongaQueryLogAnaylzer def replace(other) super(other) sort_by!(&@sorter) - super(self[0, @size]) + super(self[0, @max_size]) + end + + def responses_per_second + _period = period + if _period.zero? + 0 + else + @n_responses.to_f / _period + end + end + + def slow_response_ratio + (@slow_responses.size.to_f / @n_responses) * 100 + end + + def period + if @start_time and @last_time + @last_time - @start_time + else + 0 + end end private - def sorter + def create_sorter case @order - when "elapsed" + when "-elapsed" lambda do |statistic| -statistic.elapsed end - when "-elapsed" + when "elapsed" lambda do |statistic| - -statistic.elapsed + statistic.elapsed end when "-start-time" lambda do |statistic| @@ -393,6 +453,29 @@ class GroongaQueryLogAnaylzer end end end + + def update_statistic(statistic) + @start_time ||= statistic.start_time + @start_time = [@start_time, statistic.start_time].min + @last_time ||= statistic.end_time + @last_time = [@last_time, statistic.end_time].max + @n_responses += 1 + @total_elapsed += statistic.elapsed_in_seconds + if slow?(statistic) + @slow_responses << statistic + if statistic.select_command? + statistic.each_operation do |operation| + if operation[:relative_elapsed_in_seconds] >= @slow_operation_threshold + @slow_operations << operation + end + end + end + end + end + + def slow?(statistic) + statistic.elapsed_in_seconds >= @slow_response_threshold + end end class QueryLogParser @@ -435,9 +518,9 @@ class GroongaQueryLogAnaylzer n_records = $3.to_i statistic = current_statistics[context_id] return if statistic.nil? - statistic.add_step(:name => name, - :elapsed => elapsed.to_i, - :n_records => n_records) + statistic.add_operation(:name => name, + :elapsed => elapsed.to_i, + :n_records => n_records) when "<" return unless /\A(\d+) rc=(\d+)/ =~ rest elapsed = $1 @@ -473,15 +556,20 @@ class GroongaQueryLogAnaylzer attr_reader :output attr_accessor :slow_threshold + attr_accessor :report_summary def initialize(statistics) @statistics = statistics @slow_threshold = 0.05 + @report_summary = true @output = $stdout end def apply_options(options) self.slow_threshold = options[:slow_threshold] || @slow_threshold self.output = options[:output] || @output + if options.has_key?(:report_summary) + self.report_summary = options[:report_summary] + end end def output=(output) @@ -495,11 +583,28 @@ class GroongaQueryLogAnaylzer end end + def report + setup do + each do |statistic| + report_statistic(statistic) + end + report_summary + end + end + private def slow?(elapsed) elapsed >= @slow_threshold end + def setup + setup_output do + start + yield + finish + end + end + def setup_output original_output = @output if****@outpu*****_a?(String) @@ -625,6 +730,13 @@ class GroongaQueryLogAnaylzer @color = options[:color] || @color end + def report_statistic(statistic) + @index += 1 + @output.puts "%*d) %s" % [@digit, @index, format_heading(statistic)] + report_parameters(@output, statistic) + report_operations(@output, statistic) + end + def start @index = 0 if****@stati*****? @@ -634,29 +746,61 @@ class GroongaQueryLogAnaylzer end end - def report - setup_output do |output| - setup_color(output) do - start - each do |statistic| - report_statistic(statistic) - end - finish + def finish + end + + private + def setup + super do + setup_color do + yield end end end - def finish + def report_summary + @output.puts("Summary:") + @output.puts(" # of responses : #{@statistics.n_responses}") + @output.puts(" # of slow responses : #{@statistics.slow_responses.size}") + @output.puts(" responses/sec : #{@statistics.responses_per_second}") + @output.puts(" slow response ratio : #{@statistics.slow_response_ratio}") + @output.puts(" total response time : #{@statistics.total_elapsed}") + report_slow_operations end - def report_statistic(statistic) - @index += 1 - @output.puts "%*d) %s" % [@digit, @index, format_heading(statistic)] - report_parameters(@output, statistic) - report_steps(@output, statistic) + def report_slow_operations + @output.puts("Slow Operations") + grouped_operations =****@stati*****_operations.group_by do |operation| + operation[:context] + end + operation_statistics = grouped_operations.collect do |context, operations| + total_elapsed = operations.inject(0) do |previous, operation| + previous + operation[:relative_elapsed_in_seconds] + end + { + :total_elapsed => total_elapsed, + :n_operations => operations.size, + :represent_operation => operations.first + } + end + sorted_operation_statistics = operation_statistics.sort_by do |operation_statistic| + -operation_statistic[:total_elapsed] + end + + sorted_operation_statistics.each do |operation_statistics| + total_elapsed = operation_statistics[:total_elapsed] + n_operations = operation_statistics[:n_operations] + operation = operation_statistics[:represent_operation] + parameters = [total_elapsed, + total_elapsed / @statistics.total_elapsed % 100, + n_operations, + n_operations / @statistics.slow_operations.size.to_f % 100, + operation[:name], + operation[:context]] + @output.puts(" [%10.6f](%3.2f%%) [%3d](%3.2f%%) %8s: %s" % parameters) + end end - private def report_parameters(output, statistic) command = statistic.command output.puts " name: <#{command.name}>" @@ -666,29 +810,29 @@ class GroongaQueryLogAnaylzer end end - def report_steps(output, statistic) - statistic.each_step do |step| - relative_elapsed_in_seconds = step[:relative_elapsed_in_seconds] + def report_operations(output, statistic) + statistic.each_operation do |operation| + relative_elapsed_in_seconds = operation[:relative_elapsed_in_seconds] formatted_elapsed = "%8.8f" % relative_elapsed_in_seconds if slow?(relative_elapsed_in_seconds) formatted_elapsed = colorize(formatted_elapsed, :slow) end - step_report = " %2d) %s: %10s" % [step[:i] + 1, - formatted_elapsed, - step[:name]] - if step[:n_records] - step_report << "(%6d)" % step[:n_records] + operation_report = " %2d) %s: %10s" % [operation[:i] + 1, + formatted_elapsed, + operation[:name]] + if operation[:n_records] + operation_report << "(%6d)" % operation[:n_records] else - step_report << "(%6s)" % "" + operation_report << "(%6s)" % "" end - context = step[:context] + context = operation[:context] if context if slow?(relative_elapsed_in_seconds) context = colorize(context, :slow) end - step_report << " " << context + operation_report << " " << context end - output.puts(step_report) + output.puts(operation_report) end output.puts end @@ -704,9 +848,9 @@ class GroongaQueryLogAnaylzer end end - def setup_color(output) + def setup_color color = @color - @color = guess_color_availability(output) if @color == :auto + @color = guess_color_availability(@output) if @color == :auto yield ensure @color = color @@ -741,33 +885,23 @@ class GroongaQueryLogAnaylzer end class JSONQueryLogReporter < QueryLogReporter + def report_statistic(statistic) + @output.print(",") if @index > 0 + @output.print("\n") + @output.print(format_statistic(statistic)) + @index += 1 + end + def start @index = 0 @output.print("[") end - def report - setup_output do - start - each do |statistic| - report_statistic(statistic) - end - finish - end - end - def finish @output.puts @output.puts("]") end - def report_statistic(statistic) - @output.print(",") if @index > 0 - @output.print("\n") - @output.print(format_statistic(statistic)) - @index += 1 - end - private def format_statistic(statistic) data = { @@ -785,15 +919,15 @@ class GroongaQueryLogAnaylzer "name" => command.name, "parameters" => parameters, } - steps = [] - statistic.each_step do |step| - step_data = {} - step_data["name"] = step[:name] - step_data["relative_elapsed"] = step[:relative_elapsed_in_seconds] - step_data["context"] = step[:context] - steps << step_data - end - data["steps"] = steps + operations = [] + statistic.each_operation do |operation| + operation_data = {} + operation_data["name"] = operation[:name] + operation_data["relative_elapsed"] = operation[:relative_elapsed_in_seconds] + operation_data["context"] = operation[:context] + operations << operation_data + end + data["operations"] = operations JSON.generate(data) end end