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.
- data/.yardopts +5 -0
- data/Gemfile +21 -0
- data/README.md +49 -0
- data/Rakefile +51 -0
- data/bin/groonga-query-log-analyzer +28 -0
- data/doc/text/lgpl-2.1.txt +502 -0
- data/doc/text/news.md +5 -0
- data/groonga-query-log.gemspec +65 -0
- data/lib/groonga/query-log.rb +21 -0
- data/lib/groonga/query-log/analyzer.rb +212 -0
- data/lib/groonga/query-log/analyzer/reporter.rb +101 -0
- data/lib/groonga/query-log/analyzer/reporter/console.rb +291 -0
- data/lib/groonga/query-log/analyzer/reporter/html.rb +325 -0
- data/lib/groonga/query-log/analyzer/reporter/json.rb +78 -0
- data/lib/groonga/query-log/analyzer/sized-grouped-operations.rb +84 -0
- data/lib/groonga/query-log/analyzer/sized-statistics.rb +172 -0
- data/lib/groonga/query-log/analyzer/statistic.rb +160 -0
- data/lib/groonga/query-log/analyzer/streamer.rb +42 -0
- data/lib/groonga/query-log/parser.rb +77 -0
- data/lib/groonga/query-log/version.rb +23 -0
- data/test/command/test-select.rb +162 -0
- data/test/fixtures/n_entries.expected +19 -0
- data/test/fixtures/no-report-summary.expected +15 -0
- data/test/fixtures/order/-elapsed.expected +28 -0
- data/test/fixtures/order/-start-time.expected +28 -0
- data/test/fixtures/order/elapsed.expected +28 -0
- data/test/fixtures/order/start-time.expected +28 -0
- data/test/fixtures/query.log +7 -0
- data/test/fixtures/reporter/console.expected +28 -0
- data/test/fixtures/reporter/html.expected +196 -0
- data/test/fixtures/reporter/json.expected +4 -0
- data/test/groonga-query-log-test-utils.rb +79 -0
- data/test/run-test.rb +43 -0
- data/test/test-analyzer.rb +82 -0
- data/test/test-parser.rb +90 -0
- 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
|