Haruka Yoshihara
null+****@clear*****
Fri Dec 14 16:05:57 JST 2012
Haruka Yoshihara 2012-12-14 16:05:57 +0900 (Fri, 14 Dec 2012) New Revision: 110c5911447095cabdbdca802d85ed4a9b0b5d7e https://github.com/groonga/groonga/commit/110c5911447095cabdbdca802d85ed4a9b0b5d7e Log: Move groonga-query-log-analyzer to groonga-query-log gem groonga-query-log: https://github.com/groonga/groonga-query-log Removed files: tools/groonga-query-log-analyzer Deleted: tools/groonga-query-log-analyzer (+0 -1419) 100755 =================================================================== --- tools/groonga-query-log-analyzer 2012-12-14 12:40:02 +0900 (bb8aadb) +++ /dev/null @@ -1,1419 +0,0 @@ -#!/usr/bin/env ruby - -require 'English' -require 'optparse' -require 'cgi' -require 'thread' -require 'shellwords' -require 'time' -require 'erb' - -# For ruby 1.8.5 on CentOS 5.6. -class String - unless method_defined?(:start_with?) - def start_with?(string) - /\A#{Regexp.escape(string)}/ =~ self - end - end -end - -class Array - unless method_defined?(:each_slice) - def each_slice(n) - sub_elements = [] - each do |element| - sub_elements << element - if sub_elements.size == n - yield(sub_elements) - sub_elements = [] - end - end - yield(sub_elements) unless sub_elements.empty? - end - end -end - -class GroongaQueryLogAnalyzer - class Error < StandardError - end - - class UnsupportedReporter < Error - end - - def initialize - setup_options - end - - def run(argv=nil) - log_paths = @option_parser.parse!(argv || ARGV) - - stream = @options[:stream] - dynamic_sort = @options[:dynamic_sort] - statistics = SizedStatistics.new - statistics.apply_options(@options) - parser = QueryLogParser.new - if stream - streamer = Streamer.new(create_reporter(statistics)) - streamer.start - process_statistic = lambda do |statistic| - streamer << statistic - end - elsif dynamic_sort - process_statistic = lambda do |statistic| - statistics << statistic - end - else - full_statistics = [] - process_statistic = lambda do |statistic| - full_statistics << statistic - end - end - if log_paths.empty? - parser.parse(ARGF, &process_statistic) - else - log_paths.each do |log_path| - File.open(log_path) do |log| - parser.parse(log, &process_statistic) - end - end - end - if stream - streamer.finish - return - end - statistics.replace(full_statistics) unless dynamic_sort - - reporter = create_reporter(statistics) - reporter.apply_options(@options) - reporter.report - end - - private - def setup_options - @options = {} - @options[:n_entries] = 10 - @options[:order] = "-elapsed" - @options[:color] = :auto - @options[:output] = "-" - @options[:slow_operation_threshold] = 0.1 - @options[:slow_response_threshold] = 0.2 - @options[:reporter] = "console" - @options[:dynamic_sort] = true - @options[:stream] = false - @options[:report_summary] = true - - @option_parser = OptionParser.new do |parser| - parser.banner += " LOG1 ..." - - parser.on("-n", "--n-entries=N", - Integer, - "Show top N entries", - "(#{@options[:n_entries]})") do |n| - @options[:n_entries] = n - end - - available_orders = ["elapsed", "-elapsed", "start-time", "-start-time"] - parser.on("--order=ORDER", - available_orders, - "Sort by ORDER", - "available values: [#{available_orders.join(', ')}]", - "(#{@options[:order]})") do |order| - @options[:order] = order - end - - color_options = [ - [:auto, :auto], - ["-", false], - ["no", false], - ["false", false], - ["+", true], - ["yes", true], - ["true", true], - ] - parser.on("--[no-]color=[auto]", - color_options, - "Enable color output", - "(#{@options[:color]})") do |color| - if color.nil? - @options[:color] = true - else - @options[:color] = color - end - end - - parser.on("--output=PATH", - "Output to PATH.", - "'-' PATH means standard output.", - "(#{@options[:output]})") do |output| - @options[:output] = output - end - - parser.on("--slow-operation-threshold=THRESHOLD", - Float, - "Use THRESHOLD seconds to detect slow operations.", - "(#{@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", "html"] - parser.on("--reporter=REPORTER", - available_reporters, - "Reports statistics by REPORTER.", - "available values: [#{available_reporters.join(', ')}]", - "(#{@options[:reporter]})") do |reporter| - @options[:reporter] = reporter - end - - parser.on("--[no-]dynamic-sort", - "Sorts dynamically.", - "Memory and CPU usage reduced for large query log.", - "(#{@options[:dynamic_sort]})") do |sort| - @options[:dynamic_sort] = sort - end - - parser.on("--[no-]stream", - "Outputs analyzed query on the fly.", - "NOTE: --n-entries and --order are ignored.", - "(#{@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) - case @options[:reporter] - when "json" - require 'json' - JSONQueryLogReporter.new(statistics) - when "html" - HTMLQueryLogReporter.new(statistics) - else - ConsoleQueryLogReporter.new(statistics) - end - end - - def create_stream_reporter - case @options[:reporter] - when "json" - require 'json' - StreamJSONQueryLogReporter.new - when "html" - raise UnsupportedReporter, "HTML reporter doesn't support --stream." - else - StreamConsoleQueryLogReporter.new - end - end - end - - class Command - class << self - @@registered_commands = {} - def register(name, klass) - @@registered_commands[name] = klass - end - - def parse(input) - if input.start_with?("/d/") - parse_uri_path(input) - else - parse_command_line(input) - end - end - - private - def parse_uri_path(path) - name, parameters_string = path.split(/\?/, 2) - parameters = {} - parameters_string.split(/&/).each do |parameter_string| - key, value = parameter_string.split(/\=/, 2) - parameters[key] = CGI.unescape(value) - end - name = name.gsub(/\A\/d\//, '') - name, output_type = name.split(/\./, 2) - parameters["output_type"] = output_type if output_type - command_class = @@registered_commands[name] || self - command_class.new(name, parameters) - end - - def parse_command_line(command_line) - name, *options = Shellwords.shellwords(command_line) - parameters = {} - options.each_slice(2) do |key, value| - parameters[key.gsub(/\A--/, '')] = value - end - command_class = @@registered_commands[name] || self - command_class.new(name, parameters) - end - end - - attr_reader :name, :parameters - def initialize(name, parameters) - @name = name - @parameters = parameters - end - - def ==(other) - other.is_a?(self.class) and - @name == other.name and - @parameters == other.parameters - end - end - - class SelectCommand < Command - register("select", self) - - def sortby - @parameters["sortby"] - end - - def scorer - @parameters["scorer"] - end - - def query - @parameters["query"] - end - - def filter - @parameters["filter"] - end - - def conditions - @conditions ||= filter.split(/(?:&&|&!|\|\|)/).collect do |condition| - condition = condition.strip - condition = condition.gsub(/\A[\s\(]*/, '') - condition = condition.gsub(/[\s\)]*\z/, '') unless /\(/ =~ condition - condition - end - end - - def drilldowns - @drilldowns ||= (@parameters["drilldown"] || "").split(/\s*,\s*/) - end - - def output_columns - @parameters["output_columns"] - end - end - - class Statistic - attr_reader :context_id, :start_time, :raw_command - attr_reader :elapsed, :return_code - attr_accessor :slow_operation_threshold, :slow_response_threshold - def initialize(context_id) - @context_id = context_id - @start_time = nil - @command = nil - @raw_command = nil - @operations = [] - @elapsed = nil - @return_code = 0 - @slow_operation_threshold = 0.1 - @slow_response_threshold = 0.2 - end - - def start(start_time, command) - @start_time = start_time - @raw_command = command - end - - def finish(elapsed, return_code) - @elapsed = elapsed - @return_code = return_code - end - - def command - @command ||= Command.parse(@raw_command) - end - - def elapsed_in_seconds - nano_seconds_to_seconds(@elapsed) - end - - def last_time - @start_time + elapsed_in_seconds - end - - def slow? - elapsed_in_seconds >= @slow_response_threshold - end - - def each_operation - previous_elapsed = 0 - ensure_parse_command - operation_context_context = { - :filter_index => 0, - :drilldown_index => 0, - } - @operations.each_with_index do |operation, i| - relative_elapsed = operation[:elapsed] - previous_elapsed - relative_elapsed_in_seconds = nano_seconds_to_seconds(relative_elapsed) - previous_elapsed = operation[:elapsed] - parsed_operation = { - :i => i, - :elapsed => operation[:elapsed], - :elapsed_in_seconds => nano_seconds_to_seconds(operation[:elapsed]), - :relative_elapsed => relative_elapsed, - :relative_elapsed_in_seconds => relative_elapsed_in_seconds, - :name => operation[:name], - :context => operation_context(operation[:name], - operation_context_context), - :n_records => operation[:n_records], - :slow? => slow_operation?(relative_elapsed_in_seconds), - } - yield parsed_operation - end - end - - def add_operation(operation) - @operations << operation - end - - def operations - _operations = [] - each_operation do |operation| - _operations << operation - end - _operations - end - - def select_command? - command.name == "select" - end - - private - def nano_seconds_to_seconds(nano_seconds) - nano_seconds / 1000.0 / 1000.0 / 1000.0 - end - - def operation_context(label, context) - case label - when "filter" - if @select_command.query and context[:query_used].nil? - context[:query_used] = true - "query: #{@select_command.query}" - else - index = context[:filter_index] - context[:filter_index] += 1 - @select_command.conditions[index] - end - when "sort" - @select_command.sortby - when "score" - @select_command.scorer - when "output" - @select_command.output_columns - when "drilldown" - index = context[:drilldown_index] - context[:drilldown_index] += 1 - @select_command.drilldowns[index] - else - nil - end - end - - def ensure_parse_command - return unless select_command? - @select_command = SelectCommand.parse(@raw_command) - end - - def slow_operation?(elapsed) - elapsed >= @slow_operation_threshold - end - end - - class SizedGroupedOperations < Array - def initialize - @max_size = 10 - @sorter = create_sorter - end - - def apply_options(options) - @max_size = options[:n_entries] - end - - def each - i = 0 - super do |grouped_operation| - break if i >= @max_size - i += 1 - yield(grouped_operation) - end - end - - def <<(operation) - each do |grouped_operation| - if grouped_operation[:name] == operation[:name] and - grouped_operation[:context] == operation[:context] - elapsed = operation[:relative_elapsed_in_seconds] - grouped_operation[:total_elapsed] += elapsed - grouped_operation[:n_operations] += 1 - replace(sort_by(&@sorter)) - return self - end - end - - grouped_operation = { - :name => operation[:name], - :context => operation[:context], - :n_operations => 1, - :total_elapsed => operation[:relative_elapsed_in_seconds], - } - buffer_size = @max_size * 100 - if size < buffer_size - super(grouped_operation) - replace(sort_by(&@sorter)) - else - if****@sorte*****(grouped_operation) < @sorter.call(last) - super(grouped_operation) - sorted_operations = sort_by(&@sorter) - sorted_operations.pop - replace(sorted_operations) - end - end - self - end - - private - def create_sorter - lambda do |grouped_operation| - -grouped_operation[:total_elapsed] - end - end - end - - class SizedStatistics < Array - attr_reader :n_responses, :n_slow_responses, :n_slow_operations - attr_reader :slow_operations, :total_elapsed - attr_reader :start_time, :last_time - attr_accessor :slow_operation_threshold, :slow_response_threshold - def initialize - @max_size = 10 - self.order = "-elapsed" - @slow_operation_threshold = 0.1 - @slow_response_threshold = 0.2 - @start_time = nil - @last_time = nil - @n_responses = 0 - @n_slow_responses = 0 - @n_slow_operations = 0 - @slow_operations = SizedGroupedOperations.new - @total_elapsed = 0 - @collect_slow_statistics = true - end - - def order=(new_order) - @order = new_order - @sorter = create_sorter - end - - def apply_options(options) - @max_size = options[:n_entries] || @max_size - self.order = options[:order] || @order - @slow_operation_threshold = - options[:slow_operation_threshold] || @slow_operation_threshold - @slow_response_threshold = - options[:slow_response_threshold] || @slow_response_threshold - unless options[:report_summary].nil? - @collect_slow_statistics = options[:report_summary] - end - @slow_operations.apply_options(options) - end - - def <<(statistic) - update_statistic(statistic) - if size < @max_size - super(statistic) - replace(self) - else - if****@sorte*****(statistic) < @sorter.call(last) - super(statistic) - replace(self) - end - end - self - end - - def replace(other) - sorted_other = other.sort_by(&@sorter) - if sorted_other.size > @max_size - super(sorted_other[0, @max_size]) - else - super(sorted_other) - end - end - - def responses_per_second - _period = period - if _period.zero? - 0 - else - @n_responses.to_f / _period - end - end - - def slow_response_ratio - if @n_responses.zero? - 0 - else - (@n_slow_responses.to_f / @n_responses) * 100 - end - end - - def period - if @start_time and @last_time - @last_time - @start_time - else - 0 - end - end - - def each_slow_operation - @slow_operations.each do |grouped_operation| - total_elapsed = grouped_operation[:total_elapsed] - n_operations = grouped_operation[:n_operations] - ratios = { - :total_elapsed_ratio => total_elapsed / @total_elapsed * 100, - :n_operations_ratio => n_operations / @n_slow_operations.to_f * 100, - } - yield(grouped_operation.merge(ratios)) - end - end - - private - def create_sorter - case @order - when "-elapsed" - lambda do |statistic| - -statistic.elapsed - end - when "elapsed" - lambda do |statistic| - statistic.elapsed - end - when "-start-time" - lambda do |statistic| - -statistic.start_time - end - else - lambda do |statistic| - statistic.start_time - end - end - end - - def update_statistic(statistic) - statistic.slow_response_threshold = @slow_response_threshold - statistic.slow_operation_threshold = @slow_operation_threshold - @start_time ||= statistic.start_time - @start_time = [@start_time, statistic.start_time].min - @last_time ||= statistic.last_time - @last_time = [@last_time, statistic.last_time].max - @n_responses += 1 - @total_elapsed += statistic.elapsed_in_seconds - return unless @collect_slow_statistics - if statistic.slow? - @n_slow_responses += 1 - if statistic.select_command? - statistic.each_operation do |operation| - next unless operation[:slow?] - @n_slow_operations += 1 - @slow_operations << operation - end - end - end - end - end - - class QueryLogParser - def initialize - @mutex = Mutex.new - end - - def parse(input, &block) - current_statistics = {} - input.each_line do |line| - case line - when /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\.(\d+)\|(.+?)\|([>:<])/ - year, month, day, hour, minutes, seconds, micro_seconds = - $1, $2, $3, $4, $5, $6, $7 - context_id = $8 - type = $9 - rest = $POSTMATCH.strip - time_stamp = Time.local(year, month, day, hour, minutes, seconds, - micro_seconds) - parse_line(current_statistics, - time_stamp, context_id, type, rest, &block) - end - end - end - - private - def parse_line(current_statistics, - time_stamp, context_id, type, rest, &block) - case type - when ">" - statistic = Statistic.new(context_id) - statistic.start(time_stamp, rest) - current_statistics[context_id] = statistic - when ":" - return unless /\A(\d+) (.+)\((\d+)\)/ =~ rest - elapsed = $1 - name = $2 - n_records = $3.to_i - statistic = current_statistics[context_id] - return if statistic.nil? - statistic.add_operation(:name => name, - :elapsed => elapsed.to_i, - :n_records => n_records) - when "<" - return unless /\A(\d+) rc=(\d+)/ =~ rest - elapsed = $1 - return_code = $2 - statistic = current_statistics.delete(context_id) - return if statistic.nil? - statistic.finish(elapsed.to_i, return_code.to_i) - block.call(statistic) - end - end - end - - class Streamer - def initialize(reporter) - @reporter = reporter - end - - def start - @reporter.start - end - - def <<(statistic) - @reporter.report_statistic(statistic) - end - - def finish - @reporter.finish - end - end - - class QueryLogReporter - include Enumerable - - attr_reader :output - def initialize(statistics) - @statistics = statistics - @report_summary = true - @output = $stdout - end - - def apply_options(options) - self.output = options[:output] || @output - unless options[:report_summary].nil? - @report_summary = options[:report_summary] - end - end - - def output=(output) - @output = output - @output = $stdout if @output == "-" - end - - def each - @statistics.each do |statistic| - yield statistic - end - end - - def report - setup do - report_summary if @report_summary - report_statistics - end - end - - def report_statistics - each do |statistic| - report_statistic(statistic) - end - end - - private - def setup - setup_output do - start - yield - finish - end - end - - def setup_output - original_output = @output - if****@outpu*****_a?(String) - File.open(@output, "w") do |output| - @output = output - yield(@output) - end - else - yield(@output) - end - ensure - @output = original_output - end - - def write(*args) - @output.write(*args) - end - - def format_time(time) - if time.nil? - "NaN" - else - time.strftime("%Y-%m-%d %H:%M:%S.%u") - end - end - end - - class ConsoleQueryLogReporter < QueryLogReporter - class Color - NAMES = ["black", "red", "green", "yellow", - "blue", "magenta", "cyan", "white"] - - attr_reader :name - def initialize(name, options={}) - @name = name - @foreground = options[:foreground] - @foreground = true if****@foreg*****? - @intensity = options[:intensity] - @bold = options[:bold] - @italic = options[:italic] - @underline = options[:underline] - end - - def foreground? - @foreground - end - - def intensity? - @intensity - end - - def bold? - @bold - end - - def italic? - @italic - end - - def underline? - @underline - end - - def ==(other) - self.class === other and - [name, foreground?, intensity?, - bold?, italic?, underline?] == - [other.name, other.foreground?, other.intensity?, - other.bold?, other.italic?, other.underline?] - end - - def sequence - sequence = [] - if @name == "none" - elsif @name == "reset" - sequence << "0" - else - foreground_parameter = foreground? ? 3 : 4 - foreground_parameter += 6 if intensity? - sequence << "#{foreground_parameter}#{NAMES.index(@name)}" - end - sequence << "1" if bold? - sequence << "3" if italic? - sequence << "4" if underline? - sequence - end - - def escape_sequence - "\e[#{sequence.join(';')}m" - end - - def +(other) - MixColor.new([self, other]) - end - end - - class MixColor - attr_reader :colors - def initialize(colors) - @colors = colors - end - - def sequence - @colors.inject([]) do |result, color| - result + color.sequence - end - end - - def escape_sequence - "\e[#{sequence.join(';')}m" - end - - def +(other) - self.class.new([self, other]) - end - - def ==(other) - self.class === other and colors == other.colors - end - end - - def initialize(statistics) - super - @color = :auto - @reset_color = Color.new("reset") - @color_schema = { - :elapsed => {:foreground => :white, :background => :green}, - :time => {:foreground => :white, :background => :cyan}, - :slow => {:foreground => :white, :background => :red}, - } - end - - def apply_options(options) - super - @color = options[:color] || @color - end - - def report_statistics - write("\n") - write("Slow Queries:\n") - super - end - - def report_statistic(statistic) - @index += 1 - write("%*d) %s" % [@digit, @index, format_heading(statistic)]) - report_parameters(statistic) - report_operations(statistic) - end - - def start - @index = 0 - if****@stati*****? - @digit = 1 - else - @digit = Math.log10(@statistics.size).truncate + 1 - end - end - - def finish - end - - private - def setup - super do - setup_color do - yield - end - end - end - - def report_summary - write("Summary:\n") - write(" Threshold:\n") - write(" slow response : #{@statistics.slow_response_threshold}\n") - write(" slow operation : #{@statistics.slow_operation_threshold}\n") - write(" # of responses : #{@statistics.n_responses}\n") - write(" # of slow responses : #{@statistics.n_slow_responses}\n") - write(" responses/sec : #{@statistics.responses_per_second}\n") - write(" start time : #{format_time(@statistics.start_time)}\n") - write(" last time : #{format_time(@statistics.last_time)}\n") - write(" period(sec) : #{@statistics.period}\n") - slow_response_ratio =****@stati*****_response_ratio - write(" slow response ratio : %5.3f%%\n" % slow_response_ratio) - write(" total response time : #{@statistics.total_elapsed}\n") - report_slow_operations - end - - def report_slow_operations - write(" Slow Operations:\n") - total_elapsed_digit = nil - total_elapsed_decimal_digit = 6 - n_operations_digit = nil - @statistics.each_slow_operation do |grouped_operation| - total_elapsed = grouped_operation[:total_elapsed] - total_elapsed_digit ||= Math.log10(total_elapsed).truncate + 1 - n_operations = grouped_operation[:n_operations] - n_operations_digit ||= Math.log10(n_operations).truncate + 1 - parameters = [total_elapsed_digit + 1 + total_elapsed_decimal_digit, - total_elapsed_decimal_digit, - total_elapsed, - grouped_operation[:total_elapsed_ratio], - n_operations_digit, - n_operations, - grouped_operation[:n_operations_ratio], - grouped_operation[:name], - grouped_operation[:context]] - write(" [%*.*f](%5.2f%%) [%*d](%5.2f%%) %9s: %s\n" % parameters) - end - end - - def report_parameters(statistic) - command = statistic.command - write(" name: <#{command.name}>\n") - write(" parameters:\n") - command.parameters.each do |key, value| - write(" <#{key}>: <#{value}>\n") - end - end - - def report_operations(statistic) - statistic.each_operation do |operation| - relative_elapsed_in_seconds = operation[:relative_elapsed_in_seconds] - formatted_elapsed = "%8.8f" % relative_elapsed_in_seconds - if operation[:slow?] - formatted_elapsed = colorize(formatted_elapsed, :slow) - end - operation_report = " %2d) %s: %10s" % [operation[:i] + 1, - formatted_elapsed, - operation[:name]] - if operation[:n_records] - operation_report << "(%6d)" % operation[:n_records] - else - operation_report << "(%6s)" % "" - end - context = operation[:context] - if context - context = colorize(context, :slow) if operation[:slow?] - operation_report << " " << context - end - write("#{operation_report}\n") - end - write("\n") - end - - def guess_color_availability(output) - return false unless output.tty? - case ENV["TERM"] - when /term(?:-color)?\z/, "screen" - true - else - return true if ENV["EMACS"] == "t" - false - end - end - - def setup_color - color = @color - @color = guess_color_availability(@output) if @color == :auto - yield - ensure - @color = color - end - - def format_heading(statistic) - formatted_elapsed = colorize("%8.8f" % statistic.elapsed_in_seconds, - :elapsed) - "[%s-%s (%s)](%d): %s" % [format_time(statistic.start_time), - format_time(statistic.last_time), - formatted_elapsed, - statistic.return_code, - statistic.raw_command] - end - - def format_time(time) - colorize(super, :time) - end - - def colorize(text, schema_name) - return text unless @color - options = @color_schema[schema_name] - color = Color.new("none") - if options[:foreground] - color += Color.new(options[:foreground].to_s, :bold => true) - end - if options[:background] - color += Color.new(options[:background].to_s, :foreground => false) - end - "%s%s%s" % [color.escape_sequence, text, @reset_color.escape_sequence] - end - end - - class JSONQueryLogReporter < QueryLogReporter - def report_statistic(statistic) - write(",") if @index > 0 - write("\n") - write(format_statistic(statistic)) - @index += 1 - end - - def start - @index = 0 - write("[") - end - - def finish - write("\n") - write("]\n") - end - - def report_summary - # TODO - end - - private - def format_statistic(statistic) - data = { - "start_time" => statistic.start_time.to_i, - "last_time" => statistic.last_time.to_i, - "elapsed" => statistic.elapsed_in_seconds, - "return_code" => statistic.return_code, - } - command = statistic.command - parameters = command.parameters.collect do |key, value| - {"key" => key, "value" => value} - end - data["command"] = { - "raw" => statistic.raw_command, - "name" => command.name, - "parameters" => parameters, - } - 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 - - class HTMLQueryLogReporter < QueryLogReporter - include ERB::Util - - def report_statistic(statistic) - write(",") if @index > 0 - write("\n") - write(format_statistic(statistic)) - @index += 1 - end - - def start - write(header) - end - - def finish - write(footer) - end - - def report_summary - summary_html = erb(<<-EOH, __LINE__ + 1, binding) - <h2>Summary</h2> - <div class="summary"> -<%= analyze_parameters %> -<%= metrics %> -<%= slow_operations %> - </div> - EOH - write(summary_html) - end - - def report_statistics - write(statistics_header) - super - write(statistics_footer) - end - - def report_statistic(statistic) - command = statistic.command - statistic_html = erb(<<-EOH, __LINE__ + 1, binding) - <div class="statistic-heading"> - <h3>Command</h3> - <div class="metrics"> - [<%= format_time(statistic.start_time) %> - - - <%= format_time(statistic.last_time) %> - (<%= format_elapsed(statistic.elapsed_in_seconds, - :slow? => statistic.slow?) %>)] - (<%= span({:class => "return-code"}, h(statistic.return_code)) %>) - </div> - <%= div({:class => "raw-command"}, h(statistic.raw_command)) %> - </div> - <div class="statistic-parameters"> - <h3>Parameters</h3> - <dl> - <dt>name</dt> - <dd><%= h(command.name) %></dd> -<% command.parameters.each do |key, value| %> - <dt><%= h(key) %></dt> - <dd><%= h(value) %></dd> -<% end %> - </dl> - </div> - <div class="statistic-operations"> - <h3>Operations</h3> - <ol> -<% statistic.each_operation do |operation| %> - <li> - <%= format_elapsed(operation[:relative_elapsed_in_seconds], - :slow? => operation[:slow?]) %>: - <%= span({:class => "name"}, h(operation[:name])) %>: - <%= span({:class => "context"}, h(operation[:context])) %> - </li> -<% end %> - </ol> - </div> - EOH - write(statistic_html) - end - - private - def erb(content, line, _binding=nil) - _erb = ERB.new(content, nil, "<>") - eval(_erb.src, _binding || binding, __FILE__, line) - end - - def header - erb(<<-EOH, __LINE__ + 1) -<html> - <head> - <title>groonga query analyzer</title> - <style> -table, -table tr, -table tr th, -table tr td -{ - border: 1px solid black; -} - -span.slow -{ - color: red; -} - -div.parameters -{ - float: left; - padding: 2em; -} - -div.parameters h3 -{ - text-align: center; -} - -div.parameters table -{ - margin-right: auto; - margin-left: auto; -} - -div.statistics -{ - clear: both; -} - -td.elapsed, -td.ratio, -td.n -{ - text-align: right; -} - -td.name -{ - text-align: center; -} - </style> - </head> - <body> - <h1>groonga query analyzer</h1> - EOH - end - - def footer - erb(<<-EOH, __LINE__ + 1) - </body> -</html> - EOH - end - - def statistics_header - erb(<<-EOH, __LINE__ + 1) - <h2>Slow Queries</h2> - <div> - EOH - end - - def statistics_footer - erb(<<-EOH, __LINE__ + 1) - </div> - EOH - end - - def analyze_parameters - erb(<<-EOH, __LINE__ + 1) - <div class="parameters"> - <h3>Analyze Parameters</h3> - <table> - <tr><th>Name</th><th>Value</th></tr> - <tr> - <th>Slow response threshold</th> - <td><%= h(@statistics.slow_response_threshold) %>sec</td> - </tr> - <tr> - <th>Slow operation threshold</th> - <td><%= h(@statistics.slow_operation_threshold) %>sec</td> - </tr> - </table> - </div> - EOH - end - - def metrics - erb(<<-EOH, __LINE__ + 1) - <div class="parameters"> - <h3>Metrics</h3> - <table> - <tr><th>Name</th><th>Value</th></tr> - <tr> - <th># of responses</th> - <td><%= h(@statistics.n_responses) %></td> - </tr> - <tr> - <th># of slow responses</th> - <td><%= h(@statistics.n_slow_responses) %></td> - </tr> - <tr> - <th>responses/sec</th> - <td><%= h(@statistics.responses_per_second) %></td> - </tr> - <tr> - <th>start time</th> - <td><%= format_time(@statistics.start_time) %></td> - </tr> - <tr> - <th>last time</th> - <td><%= format_time(@statistics.last_time) %></td> - </tr> - <tr> - <th>period</th> - <td><%= h(@statistics.period) %>sec</td> - </tr> - <tr> - <th>slow response ratio</th> - <td><%= h(@statistics.slow_response_ratio) %>%</td> - </tr> - <tr> - <th>total response time</th> - <td><%= h(@statistics.total_elapsed) %>sec</td> - </tr> - </table> - </div> - EOH - end - - def slow_operations - erb(<<-EOH, __LINE__ + 1) - <div class="statistics"> - <h3>Slow Operations</h3> - <table class="slow-operations"> - <tr> - <th>total elapsed(sec)</th> - <th>total elapsed(%)</th> - <th># of operations</th> - <th># of operations(%)</th> - <th>operation name</th> - <th>context</th> - </tr> -<% @statistics.each_slow_operation do |grouped_operation| %> - <tr> - <td class="elapsed"> - <%= format_elapsed(grouped_operation[:total_elapsed]) %> - </td> - <td class="ratio"> - <%= format_ratio(grouped_operation[:total_elapsed_ratio]) %> - </td> - <td class="n"> - <%= h(grouped_operation[:n_operations]) %> - </td> - <td class="ratio"> - <%= format_ratio(grouped_operation[:n_operations_ratio]) %> - </td> - <td class="name"><%= h(grouped_operation[:name]) %></td> - <td class="context"> - <%= format_context(grouped_operation[:context]) %> - </td> - </tr> -<% end %> - </table> - </div> - EOH - end - - def format_time(time) - span({:class => "time"}, h(super)) - end - - def format_elapsed(elapsed, options={}) - formatted_elapsed = span({:class => "elapsed"}, h("%8.8f" % elapsed)) - if options[:slow?] - formatted_elapsed = span({:class => "slow"}, formatted_elapsed) - end - formatted_elapsed - end - - def format_ratio(ratio) - h("%5.2f%%" % ratio) - end - - def format_context(context) - h(context).gsub(/,/, ",<wbr />") - end - - def tag(name, attributes, content) - html = "<#{name}" - html_attributes = attributes.collect do |key, value| - "#{h(key)}=\"#{h(value)}\"" - end - html << " #{html_attributes.join(' ')}" unless attributes.empty? - html << ">" - html << content - html << "</#{name}>" - html - end - - def span(attributes, content) - tag("span", attributes, content) - end - - def div(attributes, content) - tag("div", attributes, content) - end - end -end - -if __FILE__ == $0 - analyzer = GroongaQueryLogAnalyzer.new - begin - analyzer.run - rescue GroongaQueryLogAnalyzer::Error - $stderr.puts($!.message) - exit(false) - end -end -------------- next part -------------- HTML����������������������������... Télécharger