groonga-query-log 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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