[Groonga-commit] groonga/groonga [master] [query-log][analyzer] add HTML reporter.

Back to archive index

null+****@clear***** null+****@clear*****
2011年 6月 10日 (金) 12:27:39 JST


Kouhei Sutou	2011-06-10 03:27:39 +0000 (Fri, 10 Jun 2011)

  New Revision: 37d86bcfde08d756bcab930d7e9d09cf784f1d8d

  Log:
    [query-log][analyzer] add HTML reporter.

  Modified files:
    tools/groonga-query-log-analyzer.rb

  Modified: tools/groonga-query-log-analyzer.rb (+195 -24)
===================================================================
--- tools/groonga-query-log-analyzer.rb    2011-06-10 02:12:01 +0000 (d78b1b9)
+++ tools/groonga-query-log-analyzer.rb    2011-06-10 03:27:39 +0000 (29742da)
@@ -6,8 +6,15 @@ require 'cgi'
 require 'thread'
 require 'shellwords'
 require 'time'
+require 'erb'
 
 class GroongaQueryLogAnaylzer
+  class Error < StandardError
+  end
+
+  class UnsupportedReporter < Error
+  end
+
   def initialize
     setup_options
   end
@@ -119,7 +126,7 @@ class GroongaQueryLogAnaylzer
         @options[:sloq_response_threshold] = threshold
       end
 
-      available_reporters = ["console", "json"]
+      available_reporters = ["console", "json", "html"]
       parser.on("--reporter=REPORTER",
                 available_reporters,
                 "Reports statistics by REPORTER.",
@@ -154,6 +161,8 @@ class GroongaQueryLogAnaylzer
       when "json"
         require 'json'
         JSONQueryLogReporter.new(statistics)
+      when "html"
+        HTMLQueryLogReporter.new(statistics)
       else
         ConsoleQueryLogReporter.new(statistics)
       end
@@ -164,6 +173,8 @@ class GroongaQueryLogAnaylzer
       when "json"
         require 'json'
         StreamJSONQueryLogReporter.new
+      when "html"
+        raise UnsupportedReporter, "HTML reporter doesn't support --stream."
       else
         StreamConsoleQueryLogReporter.new
       end
@@ -264,6 +275,7 @@ class GroongaQueryLogAnaylzer
   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
@@ -272,6 +284,8 @@ class GroongaQueryLogAnaylzer
       @operations = []
       @elapsed = nil
       @return_code = 0
+      @slow_operation_threshold = 0.1
+      @slow_response_threshold = 0.2
     end
 
     def start(start_time, command)
@@ -296,6 +310,10 @@ class GroongaQueryLogAnaylzer
       @start_time + elapsed_in_seconds
     end
 
+    def slow?
+      elapsed_in_seconds >= @slow_response_threshold
+    end
+
     def each_operation
       previous_elapsed = 0
       ensure_parse_command
@@ -305,17 +323,19 @@ class GroongaQueryLogAnaylzer
       }
       @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 => nano_seconds_to_seconds(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
@@ -372,6 +392,10 @@ class GroongaQueryLogAnaylzer
       return unless select_command?
       @select_command = SelectCommand.parse(@raw_command)
     end
+
+    def slow_operation?(elapsed)
+      elapsed >= @slow_operation_threshold
+    end
   end
 
   class SizedGroupedOperations < Array
@@ -531,6 +555,8 @@ class GroongaQueryLogAnaylzer
     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.end_time
@@ -538,22 +564,17 @@ class GroongaQueryLogAnaylzer
       @n_responses += 1
       @total_elapsed += statistic.elapsed_in_seconds
       return unless @collect_slow_statistics
-      if slow?(statistic)
+      if statistic.slow?
         @n_slow_responses += 1
         if statistic.select_command?
           statistic.each_operation do |operation|
-            relative_elapsed_in_seconds = operation[:relative_elapsed_in_seconds]
-            next unless relative_elapsed_in_seconds >= @slow_operation_threshold
+            next unless operation[:slow?]
             @n_slow_operations += 1
             @slow_operations << operation
           end
         end
       end
     end
-
-    def slow?(statistic)
-      statistic.elapsed_in_seconds >= @slow_response_threshold
-    end
   end
 
   class QueryLogParser
@@ -633,17 +654,14 @@ class GroongaQueryLogAnaylzer
     include Enumerable
 
     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]
@@ -663,18 +681,18 @@ class GroongaQueryLogAnaylzer
 
     def report
       setup do
-        each do |statistic|
-          report_statistic(statistic)
-        end
+        report_statistics
         report_summary
       end
     end
 
-    private
-    def slow?(elapsed)
-      elapsed >= @slow_threshold
+    def report_statistics
+      each do |statistic|
+        report_statistic(statistic)
+      end
     end
 
+    private
     def setup
       setup_output do
         start
@@ -893,7 +911,7 @@ class GroongaQueryLogAnaylzer
       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)
+        if operation[:slow?]
           formatted_elapsed = colorize(formatted_elapsed, :slow)
         end
         operation_report = " %2d) %s: %10s" % [operation[:i] + 1,
@@ -906,12 +924,10 @@ class GroongaQueryLogAnaylzer
         end
         context = operation[:context]
         if context
-          if slow?(relative_elapsed_in_seconds)
-            context = colorize(context, :slow)
-          end
+          context = colorize(context, :slow) if operation[:slow?]
           operation_report << " " << context
         end
-              write("#{operation_report}\n")
+        write("#{operation_report}\n")
       end
       write("\n")
     end
@@ -1010,9 +1026,164 @@ class GroongaQueryLogAnaylzer
       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_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.end_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>
+span.slow
+{
+  color: red;
+}
+    </style>
+  </head>
+  <body>
+      EOH
+    end
+
+    def footer
+      erb(<<-EOH, __LINE__ + 1)
+  </body>
+</html>
+      EOH
+    end
+
+    def statistics_header
+      erb(<<-EOH, __LINE__ + 1)
+    <h1>groonga query naalyzer</h1>
+    <h2>Analyze Parameters</h2>
+    <dl>
+      <dt>slow response threshold</dt>
+      <dd><%= h(@statistics.slow_response_threshold) %>sec</dd>
+      <dt>slow operation threshold</dt>
+      <dd><%= h(@statistics.slow_operation_threshold) %>sec</dd>
+    </dl>
+    <h2>Slow Queries</h2>
+    <div>
+      EOH
+    end
+
+    def statistics_footer
+      erb(<<-EOH, __LINE__ + 1)
+    </div>
+      EOH
+    end
+
+    def format_time(time)
+      span({:class => "time"}, h(time.strftime("%Y-%m-%d %H:%M:%S.%u")))
+    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 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 = GroongaQueryLogAnaylzer.new
-  analyzer.run
+  begin
+    analyzer.run
+  rescue GroongaQueryLogAnaylzer::Error
+    $stderr.puts($!.message)
+    exit(false)
+  end
 end




Groonga-commit メーリングリストの案内
Back to archive index