groonga-query-log 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/.yardopts +5 -0
  2. data/Gemfile +21 -0
  3. data/README.md +49 -0
  4. data/Rakefile +51 -0
  5. data/bin/groonga-query-log-analyzer +28 -0
  6. data/doc/text/lgpl-2.1.txt +502 -0
  7. data/doc/text/news.md +5 -0
  8. data/groonga-query-log.gemspec +65 -0
  9. data/lib/groonga/query-log.rb +21 -0
  10. data/lib/groonga/query-log/analyzer.rb +212 -0
  11. data/lib/groonga/query-log/analyzer/reporter.rb +101 -0
  12. data/lib/groonga/query-log/analyzer/reporter/console.rb +291 -0
  13. data/lib/groonga/query-log/analyzer/reporter/html.rb +325 -0
  14. data/lib/groonga/query-log/analyzer/reporter/json.rb +78 -0
  15. data/lib/groonga/query-log/analyzer/sized-grouped-operations.rb +84 -0
  16. data/lib/groonga/query-log/analyzer/sized-statistics.rb +172 -0
  17. data/lib/groonga/query-log/analyzer/statistic.rb +160 -0
  18. data/lib/groonga/query-log/analyzer/streamer.rb +42 -0
  19. data/lib/groonga/query-log/parser.rb +77 -0
  20. data/lib/groonga/query-log/version.rb +23 -0
  21. data/test/command/test-select.rb +162 -0
  22. data/test/fixtures/n_entries.expected +19 -0
  23. data/test/fixtures/no-report-summary.expected +15 -0
  24. data/test/fixtures/order/-elapsed.expected +28 -0
  25. data/test/fixtures/order/-start-time.expected +28 -0
  26. data/test/fixtures/order/elapsed.expected +28 -0
  27. data/test/fixtures/order/start-time.expected +28 -0
  28. data/test/fixtures/query.log +7 -0
  29. data/test/fixtures/reporter/console.expected +28 -0
  30. data/test/fixtures/reporter/html.expected +196 -0
  31. data/test/fixtures/reporter/json.expected +4 -0
  32. data/test/groonga-query-log-test-utils.rb +79 -0
  33. data/test/run-test.rb +43 -0
  34. data/test/test-analyzer.rb +82 -0
  35. data/test/test-parser.rb +90 -0
  36. metadata +235 -0
@@ -0,0 +1,172 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2011-2012 Kouhei Sutou <kou@clear-code.com>
4
+ # Copyright (C) 2012 Haruka Yoshihara <yoshihara@clear-code.com>
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License as published by the Free Software Foundation; either
9
+ # version 2.1 of the License, or (at your option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with this library; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
+
20
+ require "groonga/query-log/analyzer/sized-grouped-operations"
21
+ require "groonga/query-log/analyzer/reporter/console"
22
+ require "groonga/query-log/analyzer/reporter/html"
23
+ require "groonga/query-log/analyzer/reporter/json"
24
+
25
+ module Groonga
26
+ module QueryLog
27
+ class Analyzer
28
+ class SizedStatistics < Array
29
+ attr_reader :n_responses, :n_slow_responses, :n_slow_operations
30
+ attr_reader :slow_operations, :total_elapsed
31
+ attr_reader :start_time, :last_time
32
+ attr_accessor :slow_operation_threshold, :slow_response_threshold
33
+ def initialize
34
+ @max_size = 10
35
+ self.order = "-elapsed"
36
+ @slow_operation_threshold = 0.1
37
+ @slow_response_threshold = 0.2
38
+ @start_time = nil
39
+ @last_time = nil
40
+ @n_responses = 0
41
+ @n_slow_responses = 0
42
+ @n_slow_operations = 0
43
+ @slow_operations = SizedGroupedOperations.new
44
+ @total_elapsed = 0
45
+ @collect_slow_statistics = true
46
+ end
47
+
48
+ def order=(new_order)
49
+ @order = new_order
50
+ @sorter = create_sorter
51
+ end
52
+
53
+ def apply_options(options)
54
+ @max_size = options[:n_entries] || @max_size
55
+ self.order = options[:order] || @order
56
+ @slow_operation_threshold =
57
+ options[:slow_operation_threshold] || @slow_operation_threshold
58
+ @slow_response_threshold =
59
+ options[:slow_response_threshold] || @slow_response_threshold
60
+ unless options[:report_summary].nil?
61
+ @collect_slow_statistics = options[:report_summary]
62
+ end
63
+ @slow_operations.apply_options(options)
64
+ end
65
+
66
+ def <<(statistic)
67
+ update_statistic(statistic)
68
+ if size < @max_size
69
+ super(statistic)
70
+ replace(self)
71
+ else
72
+ if @sorter.call(statistic) < @sorter.call(last)
73
+ super(statistic)
74
+ replace(self)
75
+ end
76
+ end
77
+ self
78
+ end
79
+
80
+ def replace(other)
81
+ sorted_other = other.sort_by(&@sorter)
82
+ if sorted_other.size > @max_size
83
+ super(sorted_other[0, @max_size])
84
+ else
85
+ super(sorted_other)
86
+ end
87
+ end
88
+
89
+ def responses_per_second
90
+ _period = period
91
+ if _period.zero?
92
+ 0
93
+ else
94
+ @n_responses.to_f / _period
95
+ end
96
+ end
97
+
98
+ def slow_response_ratio
99
+ if @n_responses.zero?
100
+ 0
101
+ else
102
+ (@n_slow_responses.to_f / @n_responses) * 100
103
+ end
104
+ end
105
+
106
+ def period
107
+ if @start_time and @last_time
108
+ @last_time - @start_time
109
+ else
110
+ 0
111
+ end
112
+ end
113
+
114
+ def each_slow_operation
115
+ @slow_operations.each do |grouped_operation|
116
+ total_elapsed = grouped_operation[:total_elapsed]
117
+ n_operations = grouped_operation[:n_operations]
118
+ ratios = {
119
+ :total_elapsed_ratio => total_elapsed / @total_elapsed * 100,
120
+ :n_operations_ratio => n_operations / @n_slow_operations.to_f * 100,
121
+ }
122
+ yield(grouped_operation.merge(ratios))
123
+ end
124
+ end
125
+
126
+ private
127
+ def create_sorter
128
+ case @order
129
+ when "-elapsed"
130
+ lambda do |statistic|
131
+ -statistic.elapsed
132
+ end
133
+ when "elapsed"
134
+ lambda do |statistic|
135
+ statistic.elapsed
136
+ end
137
+ when "-start-time"
138
+ lambda do |statistic|
139
+ -statistic.start_time.to_f
140
+ end
141
+ else
142
+ lambda do |statistic|
143
+ statistic.start_time.to_f
144
+ end
145
+ end
146
+ end
147
+
148
+ def update_statistic(statistic)
149
+ statistic.slow_response_threshold = @slow_response_threshold
150
+ statistic.slow_operation_threshold = @slow_operation_threshold
151
+ @start_time ||= statistic.start_time
152
+ @start_time = [@start_time, statistic.start_time].min
153
+ @last_time ||= statistic.last_time
154
+ @last_time = [@last_time, statistic.last_time].max
155
+ @n_responses += 1
156
+ @total_elapsed += statistic.elapsed_in_seconds
157
+ return unless @collect_slow_statistics
158
+ if statistic.slow?
159
+ @n_slow_responses += 1
160
+ if statistic.select_command?
161
+ statistic.each_operation do |operation|
162
+ next unless operation[:slow?]
163
+ @n_slow_operations += 1
164
+ @slow_operations << operation
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,160 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2011-2012 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "groonga/command"
20
+
21
+ module Groonga
22
+ module QueryLog
23
+ class Analyzer
24
+ class Statistic
25
+ attr_reader :context_id, :start_time, :raw_command
26
+ attr_reader :elapsed, :return_code
27
+ attr_accessor :slow_operation_threshold, :slow_response_threshold
28
+ def initialize(context_id)
29
+ @context_id = context_id
30
+ @start_time = nil
31
+ @command = nil
32
+ @raw_command = nil
33
+ @operations = []
34
+ @elapsed = nil
35
+ @return_code = 0
36
+ @slow_operation_threshold = 0.1
37
+ @slow_response_threshold = 0.2
38
+ end
39
+
40
+ def start(start_time, command)
41
+ @start_time = start_time
42
+ @raw_command = command
43
+ end
44
+
45
+ def finish(elapsed, return_code)
46
+ @elapsed = elapsed
47
+ @return_code = return_code
48
+ end
49
+
50
+ def command
51
+ Groonga::Command::Parser.parse(@raw_command) do |status, command|
52
+ case status
53
+ when :on_load_start
54
+ @loading = false
55
+ @command ||= command
56
+ when :on_command
57
+ @command ||= command
58
+ end
59
+ end
60
+ @command
61
+ end
62
+
63
+ def elapsed_in_seconds
64
+ nano_seconds_to_seconds(@elapsed)
65
+ end
66
+
67
+ def last_time
68
+ @start_time + elapsed_in_seconds
69
+ end
70
+
71
+ def slow?
72
+ elapsed_in_seconds >= @slow_response_threshold
73
+ end
74
+
75
+ def each_operation
76
+ previous_elapsed = 0
77
+ ensure_parse_command
78
+ operation_context_context = {
79
+ :filter_index => 0,
80
+ :drilldown_index => 0,
81
+ }
82
+ @operations.each_with_index do |operation, i|
83
+ relative_elapsed = operation[:elapsed] - previous_elapsed
84
+ relative_elapsed_in_seconds = nano_seconds_to_seconds(relative_elapsed)
85
+ previous_elapsed = operation[:elapsed]
86
+ parsed_operation = {
87
+ :i => i,
88
+ :elapsed => operation[:elapsed],
89
+ :elapsed_in_seconds => nano_seconds_to_seconds(operation[:elapsed]),
90
+ :relative_elapsed => relative_elapsed,
91
+ :relative_elapsed_in_seconds => relative_elapsed_in_seconds,
92
+ :name => operation[:name],
93
+ :context => operation_context(operation[:name],
94
+ operation_context_context),
95
+ :n_records => operation[:n_records],
96
+ :slow? => slow_operation?(relative_elapsed_in_seconds),
97
+ }
98
+ yield parsed_operation
99
+ end
100
+ end
101
+
102
+ def add_operation(operation)
103
+ @operations << operation
104
+ end
105
+
106
+ def operations
107
+ _operations = []
108
+ each_operation do |operation|
109
+ _operations << operation
110
+ end
111
+ _operations
112
+ end
113
+
114
+ def select_command?
115
+ command.name == "select"
116
+ end
117
+
118
+ private
119
+ def nano_seconds_to_seconds(nano_seconds)
120
+ nano_seconds / 1000.0 / 1000.0 / 1000.0
121
+ end
122
+
123
+ def operation_context(label, context)
124
+ case label
125
+ when "filter"
126
+ if @select_command.query and context[:query_used].nil?
127
+ context[:query_used] = true
128
+ "query: #{@select_command.query}"
129
+ else
130
+ index = context[:filter_index]
131
+ context[:filter_index] += 1
132
+ @select_command.conditions[index]
133
+ end
134
+ when "sort"
135
+ @select_command.sortby
136
+ when "score"
137
+ @select_command.scorer
138
+ when "output"
139
+ @select_command.output_columns
140
+ when "drilldown"
141
+ index = context[:drilldown_index]
142
+ context[:drilldown_index] += 1
143
+ @select_command.drilldowns[index]
144
+ else
145
+ nil
146
+ end
147
+ end
148
+
149
+ def ensure_parse_command
150
+ return unless select_command?
151
+ @select_command = Groonga::Command::Parser.parse(@raw_command)
152
+ end
153
+
154
+ def slow_operation?(elapsed)
155
+ elapsed >= @slow_operation_threshold
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,42 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2011-2012 Kouhei Sutou <kou@clear-code.com>
4
+ # Copyright (C) 2012 Haruka Yoshihara <yoshihara@clear-code.com>
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License as published by the Free Software Foundation; either
9
+ # version 2.1 of the License, or (at your option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with this library; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
+
20
+ module Groonga
21
+ module QueryLog
22
+ class Analyzer
23
+ class Streamer
24
+ def initialize(reporter)
25
+ @reporter = reporter
26
+ end
27
+
28
+ def start
29
+ @reporter.start
30
+ end
31
+
32
+ def <<(statistic)
33
+ @reporter.report_statistic(statistic)
34
+ end
35
+
36
+ def finish
37
+ @reporter.finish
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,77 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2011-2012 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "English"
20
+
21
+ require "groonga/query-log/analyzer/statistic"
22
+
23
+ module Groonga
24
+ module QueryLog
25
+ class Parser
26
+ def initialize
27
+ end
28
+
29
+ def parse(input, &block)
30
+ current_statistics = {}
31
+ input.each_line do |line|
32
+ case line
33
+ when /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)\.(\d+)\|(.+?)\|([>:<])/
34
+ year, month, day, hour, minutes, seconds, micro_seconds =
35
+ $1, $2, $3, $4, $5, $6, $7
36
+ context_id = $8
37
+ type = $9
38
+ rest = $POSTMATCH.strip
39
+ time_stamp = Time.local(year, month, day, hour, minutes, seconds,
40
+ micro_seconds)
41
+ parse_line(current_statistics,
42
+ time_stamp, context_id, type, rest, &block)
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+ def parse_line(current_statistics,
49
+ time_stamp, context_id, type, rest, &block)
50
+ case type
51
+ when ">"
52
+ statistic = Analyzer::Statistic.new(context_id)
53
+ statistic.start(time_stamp, rest)
54
+ current_statistics[context_id] = statistic
55
+ when ":"
56
+ return unless /\A(\d+) (.+)\((\d+)\)/ =~ rest
57
+ elapsed = $1
58
+ name = $2
59
+ n_records = $3.to_i
60
+ statistic = current_statistics[context_id]
61
+ return if statistic.nil?
62
+ statistic.add_operation(:name => name,
63
+ :elapsed => elapsed.to_i,
64
+ :n_records => n_records)
65
+ when "<"
66
+ return unless /\A(\d+) rc=(\d+)/ =~ rest
67
+ elapsed = $1
68
+ return_code = $2
69
+ statistic = current_statistics.delete(context_id)
70
+ return if statistic.nil?
71
+ statistic.finish(elapsed.to_i, return_code.to_i)
72
+ block.call(statistic)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end