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,325 @@
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 "erb"
21
+ require "groonga/query-log/analyzer/reporter"
22
+
23
+ module Groonga
24
+ module QueryLog
25
+ class Analyzer
26
+ class HTMLReporter < Reporter
27
+ include ERB::Util
28
+
29
+ def start
30
+ write(header)
31
+ end
32
+
33
+ def finish
34
+ write(footer)
35
+ end
36
+
37
+ def report_summary
38
+ summary_html = erb(<<-EOH, __LINE__ + 1, binding)
39
+ <h2>Summary</h2>
40
+ <div class="summary">
41
+ <%= analyze_parameters %>
42
+ <%= metrics %>
43
+ <%= slow_operations %>
44
+ </div>
45
+ EOH
46
+ write(summary_html)
47
+ end
48
+
49
+ def report_statistics
50
+ write(statistics_header)
51
+ super
52
+ write(statistics_footer)
53
+ end
54
+
55
+ def report_statistic(statistic)
56
+ command = statistic.command
57
+ statistic_html = erb(<<-EOH, __LINE__ + 1, binding)
58
+ <div class="statistic-heading">
59
+ <h3>Command</h3>
60
+ <div class="metrics">
61
+ [<%= format_time(statistic.start_time) %>
62
+ -
63
+ <%= format_time(statistic.last_time) %>
64
+ (<%= format_elapsed(statistic.elapsed_in_seconds,
65
+ :slow? => statistic.slow?) %>)]
66
+ (<%= span({:class => "return-code"}, h(statistic.return_code)) %>)
67
+ </div>
68
+ <%= div({:class => "raw-command"}, h(statistic.raw_command)) %>
69
+ </div>
70
+ <div class="statistic-parameters">
71
+ <h3>Parameters</h3>
72
+ <dl>
73
+ <dt>name</dt>
74
+ <dd><%= h(command.name) %></dd>
75
+ <% command.arguments.each do |key, value| %>
76
+ <dt><%= h(key) %></dt>
77
+ <dd><%= h(value) %></dd>
78
+ <% end %>
79
+ </dl>
80
+ </div>
81
+ <div class="statistic-operations">
82
+ <h3>Operations</h3>
83
+ <ol>
84
+ <% statistic.each_operation do |operation| %>
85
+ <li>
86
+ <%= format_elapsed(operation[:relative_elapsed_in_seconds],
87
+ :slow? => operation[:slow?]) %>:
88
+ <%= span({:class => "name"}, h(operation[:name])) %>:
89
+ <%= span({:class => "context"}, h(operation[:context])) %>
90
+ </li>
91
+ <% end %>
92
+ </ol>
93
+ </div>
94
+ EOH
95
+ write(statistic_html)
96
+ end
97
+
98
+ private
99
+ def erb(content, line, _binding=nil)
100
+ _erb = ERB.new(content, nil, "<>")
101
+ eval(_erb.src, _binding || binding, __FILE__, line)
102
+ end
103
+
104
+ def header
105
+ erb(<<-EOH, __LINE__ + 1)
106
+ <html>
107
+ <head>
108
+ <title>groonga query analyzer</title>
109
+ <style>
110
+ table,
111
+ table tr,
112
+ table tr th,
113
+ table tr td
114
+ {
115
+ border: 1px solid black;
116
+ }
117
+
118
+ span.slow
119
+ {
120
+ color: red;
121
+ }
122
+
123
+ div.parameters
124
+ {
125
+ float: left;
126
+ padding: 2em;
127
+ }
128
+
129
+ div.parameters h3
130
+ {
131
+ text-align: center;
132
+ }
133
+
134
+ div.parameters table
135
+ {
136
+ margin-right: auto;
137
+ margin-left: auto;
138
+ }
139
+
140
+ div.statistics
141
+ {
142
+ clear: both;
143
+ }
144
+
145
+ td.elapsed,
146
+ td.ratio,
147
+ td.n
148
+ {
149
+ text-align: right;
150
+ }
151
+
152
+ td.name
153
+ {
154
+ text-align: center;
155
+ }
156
+ </style>
157
+ </head>
158
+ <body>
159
+ <h1>groonga query analyzer</h1>
160
+ EOH
161
+ end
162
+
163
+ def footer
164
+ erb(<<-EOH, __LINE__ + 1)
165
+ </body>
166
+ </html>
167
+ EOH
168
+ end
169
+
170
+ def statistics_header
171
+ erb(<<-EOH, __LINE__ + 1)
172
+ <h2>Slow Queries</h2>
173
+ <div>
174
+ EOH
175
+ end
176
+
177
+ def statistics_footer
178
+ erb(<<-EOH, __LINE__ + 1)
179
+ </div>
180
+ EOH
181
+ end
182
+
183
+ def analyze_parameters
184
+ erb(<<-EOH, __LINE__ + 1)
185
+ <div class="parameters">
186
+ <h3>Analyze Parameters</h3>
187
+ <table>
188
+ <tr><th>Name</th><th>Value</th></tr>
189
+ <tr>
190
+ <th>Slow response threshold</th>
191
+ <td><%= h(@statistics.slow_response_threshold) %>sec</td>
192
+ </tr>
193
+ <tr>
194
+ <th>Slow operation threshold</th>
195
+ <td><%= h(@statistics.slow_operation_threshold) %>sec</td>
196
+ </tr>
197
+ </table>
198
+ </div>
199
+ EOH
200
+ end
201
+
202
+ def metrics
203
+ erb(<<-EOH, __LINE__ + 1)
204
+ <div class="parameters">
205
+ <h3>Metrics</h3>
206
+ <table>
207
+ <tr><th>Name</th><th>Value</th></tr>
208
+ <tr>
209
+ <th># of responses</th>
210
+ <td><%= h(@statistics.n_responses) %></td>
211
+ </tr>
212
+ <tr>
213
+ <th># of slow responses</th>
214
+ <td><%= h(@statistics.n_slow_responses) %></td>
215
+ </tr>
216
+ <tr>
217
+ <th>responses/sec</th>
218
+ <td><%= h(@statistics.responses_per_second) %></td>
219
+ </tr>
220
+ <tr>
221
+ <th>start time</th>
222
+ <td><%= format_time(@statistics.start_time) %></td>
223
+ </tr>
224
+ <tr>
225
+ <th>last time</th>
226
+ <td><%= format_time(@statistics.last_time) %></td>
227
+ </tr>
228
+ <tr>
229
+ <th>period</th>
230
+ <td><%= h(@statistics.period) %>sec</td>
231
+ </tr>
232
+ <tr>
233
+ <th>slow response ratio</th>
234
+ <td><%= h(@statistics.slow_response_ratio) %>%</td>
235
+ </tr>
236
+ <tr>
237
+ <th>total response time</th>
238
+ <td><%= h(@statistics.total_elapsed) %>sec</td>
239
+ </tr>
240
+ </table>
241
+ </div>
242
+ EOH
243
+ end
244
+
245
+ def slow_operations
246
+ erb(<<-EOH, __LINE__ + 1)
247
+ <div class="statistics">
248
+ <h3>Slow Operations</h3>
249
+ <table class="slow-operations">
250
+ <tr>
251
+ <th>total elapsed(sec)</th>
252
+ <th>total elapsed(%)</th>
253
+ <th># of operations</th>
254
+ <th># of operations(%)</th>
255
+ <th>operation name</th>
256
+ <th>context</th>
257
+ </tr>
258
+ <% @statistics.each_slow_operation do |grouped_operation| %>
259
+ <tr>
260
+ <td class="elapsed">
261
+ <%= format_elapsed(grouped_operation[:total_elapsed]) %>
262
+ </td>
263
+ <td class="ratio">
264
+ <%= format_ratio(grouped_operation[:total_elapsed_ratio]) %>
265
+ </td>
266
+ <td class="n">
267
+ <%= h(grouped_operation[:n_operations]) %>
268
+ </td>
269
+ <td class="ratio">
270
+ <%= format_ratio(grouped_operation[:n_operations_ratio]) %>
271
+ </td>
272
+ <td class="name"><%= h(grouped_operation[:name]) %></td>
273
+ <td class="context">
274
+ <%= format_context(grouped_operation[:context]) %>
275
+ </td>
276
+ </tr>
277
+ <% end %>
278
+ </table>
279
+ </div>
280
+ EOH
281
+ end
282
+
283
+ def format_time(time)
284
+ span({:class => "time"}, h(super))
285
+ end
286
+
287
+ def format_elapsed(elapsed, options={})
288
+ formatted_elapsed = span({:class => "elapsed"}, h("%8.8f" % elapsed))
289
+ if options[:slow?]
290
+ formatted_elapsed = span({:class => "slow"}, formatted_elapsed)
291
+ end
292
+ formatted_elapsed
293
+ end
294
+
295
+ def format_ratio(ratio)
296
+ h("%5.2f%%" % ratio)
297
+ end
298
+
299
+ def format_context(context)
300
+ h(context).gsub(/,/, ",<wbr />")
301
+ end
302
+
303
+ def tag(name, attributes, content)
304
+ html = "<#{name}"
305
+ html_attributes = attributes.collect do |key, value|
306
+ "#{h(key)}=\"#{h(value)}\""
307
+ end
308
+ html << " #{html_attributes.join(' ')}" unless attributes.empty?
309
+ html << ">"
310
+ html << content
311
+ html << "</#{name}>"
312
+ html
313
+ end
314
+
315
+ def span(attributes, content)
316
+ tag("span", attributes, content)
317
+ end
318
+
319
+ def div(attributes, content)
320
+ tag("div", attributes, content)
321
+ end
322
+ end
323
+ end
324
+ end
325
+ end
@@ -0,0 +1,78 @@
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/reporter"
21
+
22
+ module Groonga
23
+ module QueryLog
24
+ class Analyzer
25
+ class JSONReporter < Reporter
26
+ def report_statistic(statistic)
27
+ write(",") if @index > 0
28
+ write("\n")
29
+ write(format_statistic(statistic))
30
+ @index += 1
31
+ end
32
+
33
+ def start
34
+ @index = 0
35
+ write("[")
36
+ end
37
+
38
+ def finish
39
+ write("\n")
40
+ write("]\n")
41
+ end
42
+
43
+ def report_summary
44
+ # TODO
45
+ end
46
+
47
+ private
48
+ def format_statistic(statistic)
49
+ data = {
50
+ "start_time" => statistic.start_time.to_i,
51
+ "last_time" => statistic.last_time.to_i,
52
+ "elapsed" => statistic.elapsed_in_seconds,
53
+ "return_code" => statistic.return_code,
54
+ }
55
+ command = statistic.command
56
+ arguments = command.arguments.collect do |key, value|
57
+ {"key" => key, "value" => value}
58
+ end
59
+ data["command"] = {
60
+ "raw" => statistic.raw_command,
61
+ "name" => command.name,
62
+ "parameters" => arguments,
63
+ }
64
+ operations = []
65
+ statistic.each_operation do |operation|
66
+ operation_data = {}
67
+ operation_data["name"] = operation[:name]
68
+ operation_data["relative_elapsed"] = operation[:relative_elapsed_in_seconds]
69
+ operation_data["context"] = operation[:context]
70
+ operations << operation_data
71
+ end
72
+ data["operations"] = operations
73
+ JSON.generate(data)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,84 @@
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 SizedGroupedOperations < Array
24
+ def initialize
25
+ @max_size = 10
26
+ @sorter = create_sorter
27
+ end
28
+
29
+ def apply_options(options)
30
+ @max_size = options[:n_entries]
31
+ end
32
+
33
+ def each
34
+ i = 0
35
+ super do |grouped_operation|
36
+ break if i >= @max_size
37
+ i += 1
38
+ yield(grouped_operation)
39
+ end
40
+ end
41
+
42
+ def <<(operation)
43
+ each do |grouped_operation|
44
+ if grouped_operation[:name] == operation[:name] and
45
+ grouped_operation[:context] == operation[:context]
46
+ elapsed = operation[:relative_elapsed_in_seconds]
47
+ grouped_operation[:total_elapsed] += elapsed
48
+ grouped_operation[:n_operations] += 1
49
+ replace(sort_by(&@sorter))
50
+ return self
51
+ end
52
+ end
53
+
54
+ grouped_operation = {
55
+ :name => operation[:name],
56
+ :context => operation[:context],
57
+ :n_operations => 1,
58
+ :total_elapsed => operation[:relative_elapsed_in_seconds],
59
+ }
60
+ buffer_size = @max_size * 100
61
+ if size < buffer_size
62
+ super(grouped_operation)
63
+ replace(sort_by(&@sorter))
64
+ else
65
+ if @sorter.call(grouped_operation) < @sorter.call(last)
66
+ super(grouped_operation)
67
+ sorted_operations = sort_by(&@sorter)
68
+ sorted_operations.pop
69
+ replace(sorted_operations)
70
+ end
71
+ end
72
+ self
73
+ end
74
+
75
+ private
76
+ def create_sorter
77
+ lambda do |grouped_operation|
78
+ -grouped_operation[:total_elapsed]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end