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
data/doc/text/news.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- mode: ruby; coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 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
|
+
base_dir = File.dirname(__FILE__)
|
20
|
+
lib_dir = File.join(base_dir, "lib")
|
21
|
+
|
22
|
+
$LOAD_PATH.unshift(lib_dir)
|
23
|
+
require "groonga/query-log/version"
|
24
|
+
|
25
|
+
clean_white_space = lambda do |entry|
|
26
|
+
entry.gsub(/(\A\n+|\n+\z)/, '') + "\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
Gem::Specification.new do |spec|
|
30
|
+
spec.name = "groonga-query-log"
|
31
|
+
spec.version = Groonga::QueryLog::VERSION.dup
|
32
|
+
|
33
|
+
spec.authors = ["Kouhei Sutou"]
|
34
|
+
spec.email = ["kou@clear-code.com"]
|
35
|
+
|
36
|
+
readme = File.read("README.md")
|
37
|
+
readme.force_encoding("UTF-8") if readme.respond_to?(:force_encoding)
|
38
|
+
entries = readme.split(/^\#\#\s(.*)$/)
|
39
|
+
description = clean_white_space.call(entries[entries.index("Description") + 1])
|
40
|
+
spec.summary, spec.description, = description.split(/\n\n+/, 3)
|
41
|
+
|
42
|
+
spec.files = ["README.md", "Rakefile", "Gemfile", "#{spec.name}.gemspec"]
|
43
|
+
spec.files += Dir.glob("lib/**/*.rb")
|
44
|
+
spec.files += Dir.glob("doc/text/*")
|
45
|
+
spec.files += [".yardopts"]
|
46
|
+
spec.test_files += Dir.glob("test/**/*")
|
47
|
+
Dir.chdir("bin") do
|
48
|
+
spec.executables = Dir.glob("*")
|
49
|
+
end
|
50
|
+
|
51
|
+
spec.homepage = "https://github.com/groonga/groonga-query-log"
|
52
|
+
spec.licenses = ["LGPLv2.1+"]
|
53
|
+
spec.require_paths = ["lib"]
|
54
|
+
|
55
|
+
spec.add_runtime_dependency("groonga-command")
|
56
|
+
|
57
|
+
spec.add_development_dependency("test-unit")
|
58
|
+
spec.add_development_dependency("test-unit-notify")
|
59
|
+
spec.add_development_dependency("rake")
|
60
|
+
spec.add_development_dependency("bundler")
|
61
|
+
spec.add_development_dependency("packnga")
|
62
|
+
spec.add_development_dependency("yard")
|
63
|
+
spec.add_development_dependency("redcarpet")
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Copyright (C) 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/query-log/version"
|
20
|
+
require "groonga/query-log/analyzer"
|
21
|
+
require "groonga/query-log/parser"
|
@@ -0,0 +1,212 @@
|
|
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 "optparse"
|
21
|
+
require "json"
|
22
|
+
require "groonga/query-log/parser"
|
23
|
+
require "groonga/query-log/analyzer/streamer"
|
24
|
+
require "groonga/query-log/analyzer/sized-statistics"
|
25
|
+
|
26
|
+
module Groonga
|
27
|
+
module QueryLog
|
28
|
+
class Analyzer
|
29
|
+
class Error < StandardError
|
30
|
+
end
|
31
|
+
|
32
|
+
class UnsupportedReporter < Error
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
setup_options
|
37
|
+
end
|
38
|
+
|
39
|
+
def run(*argv)
|
40
|
+
log_paths = @option_parser.parse!(argv)
|
41
|
+
|
42
|
+
stream = @options[:stream]
|
43
|
+
dynamic_sort = @options[:dynamic_sort]
|
44
|
+
statistics = SizedStatistics.new
|
45
|
+
statistics.apply_options(@options)
|
46
|
+
parser = Groonga::QueryLog::Parser.new
|
47
|
+
if stream
|
48
|
+
streamer = Streamer.new(create_reporter(statistics))
|
49
|
+
streamer.start
|
50
|
+
process_statistic = lambda do |statistic|
|
51
|
+
streamer << statistic
|
52
|
+
end
|
53
|
+
elsif dynamic_sort
|
54
|
+
process_statistic = lambda do |statistic|
|
55
|
+
statistics << statistic
|
56
|
+
end
|
57
|
+
else
|
58
|
+
full_statistics = []
|
59
|
+
process_statistic = lambda do |statistic|
|
60
|
+
full_statistics << statistic
|
61
|
+
end
|
62
|
+
end
|
63
|
+
if log_paths.empty?
|
64
|
+
parser.parse(ARGF, &process_statistic)
|
65
|
+
else
|
66
|
+
log_paths.each do |log_path|
|
67
|
+
File.open(log_path) do |log|
|
68
|
+
parser.parse(log, &process_statistic)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
if stream
|
73
|
+
streamer.finish
|
74
|
+
return
|
75
|
+
end
|
76
|
+
statistics.replace(full_statistics) unless dynamic_sort
|
77
|
+
|
78
|
+
reporter = create_reporter(statistics)
|
79
|
+
reporter.apply_options(@options)
|
80
|
+
reporter.report
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def setup_options
|
85
|
+
@options = {}
|
86
|
+
@options[:n_entries] = 10
|
87
|
+
@options[:order] = "-elapsed"
|
88
|
+
@options[:color] = :auto
|
89
|
+
@options[:output] = "-"
|
90
|
+
@options[:slow_operation_threshold] = 0.1
|
91
|
+
@options[:slow_response_threshold] = 0.2
|
92
|
+
@options[:reporter] = "console"
|
93
|
+
@options[:dynamic_sort] = true
|
94
|
+
@options[:stream] = false
|
95
|
+
@options[:report_summary] = true
|
96
|
+
|
97
|
+
@option_parser = OptionParser.new do |parser|
|
98
|
+
parser.banner += " LOG1 ..."
|
99
|
+
|
100
|
+
parser.on("-n", "--n-entries=N",
|
101
|
+
Integer,
|
102
|
+
"Show top N entries",
|
103
|
+
"(#{@options[:n_entries]})") do |n|
|
104
|
+
@options[:n_entries] = n
|
105
|
+
end
|
106
|
+
|
107
|
+
available_orders = ["elapsed", "-elapsed", "start-time", "-start-time"]
|
108
|
+
parser.on("--order=ORDER",
|
109
|
+
available_orders,
|
110
|
+
"Sort by ORDER",
|
111
|
+
"available values: [#{available_orders.join(', ')}]",
|
112
|
+
"(#{@options[:order]})") do |order|
|
113
|
+
@options[:order] = order
|
114
|
+
end
|
115
|
+
|
116
|
+
color_options = [
|
117
|
+
[:auto, :auto],
|
118
|
+
["-", false],
|
119
|
+
["no", false],
|
120
|
+
["false", false],
|
121
|
+
["+", true],
|
122
|
+
["yes", true],
|
123
|
+
["true", true],
|
124
|
+
]
|
125
|
+
parser.on("--[no-]color=[auto]",
|
126
|
+
color_options,
|
127
|
+
"Enable color output",
|
128
|
+
"(#{@options[:color]})") do |color|
|
129
|
+
if color.nil?
|
130
|
+
@options[:color] = true
|
131
|
+
else
|
132
|
+
@options[:color] = color
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
parser.on("--output=PATH",
|
137
|
+
"Output to PATH.",
|
138
|
+
"'-' PATH means standard output.",
|
139
|
+
"(#{@options[:output]})") do |output|
|
140
|
+
@options[:output] = output
|
141
|
+
end
|
142
|
+
|
143
|
+
parser.on("--slow-operation-threshold=THRESHOLD",
|
144
|
+
Float,
|
145
|
+
"Use THRESHOLD seconds to detect slow operations.",
|
146
|
+
"(#{@options[:slow_operation_threshold]})") do |threshold|
|
147
|
+
@options[:slow_operation_threshold] = threshold
|
148
|
+
end
|
149
|
+
|
150
|
+
parser.on("--slow-response-threshold=THRESHOLD",
|
151
|
+
Float,
|
152
|
+
"Use THRESHOLD seconds to detect slow operations.",
|
153
|
+
"(#{@options[:sloq_response_threshold]})") do |threshold|
|
154
|
+
@options[:sloq_response_threshold] = threshold
|
155
|
+
end
|
156
|
+
|
157
|
+
available_reporters = ["console", "json", "html"]
|
158
|
+
parser.on("--reporter=REPORTER",
|
159
|
+
available_reporters,
|
160
|
+
"Reports statistics by REPORTER.",
|
161
|
+
"available values: [#{available_reporters.join(', ')}]",
|
162
|
+
"(#{@options[:reporter]})") do |reporter|
|
163
|
+
@options[:reporter] = reporter
|
164
|
+
end
|
165
|
+
|
166
|
+
parser.on("--[no-]dynamic-sort",
|
167
|
+
"Sorts dynamically.",
|
168
|
+
"Memory and CPU usage reduced for large query log.",
|
169
|
+
"(#{@options[:dynamic_sort]})") do |sort|
|
170
|
+
@options[:dynamic_sort] = sort
|
171
|
+
end
|
172
|
+
|
173
|
+
parser.on("--[no-]stream",
|
174
|
+
"Outputs analyzed query on the fly.",
|
175
|
+
"NOTE: --n-entries and --order are ignored.",
|
176
|
+
"(#{@options[:stream]})") do |stream|
|
177
|
+
@options[:stream] = stream
|
178
|
+
end
|
179
|
+
|
180
|
+
parser.on("--[no-]report-summary",
|
181
|
+
"Reports summary at the end.",
|
182
|
+
"(#{@options[:report_summary]})") do |report_summary|
|
183
|
+
@options[:report_summary] = report_summary
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def create_reporter(statistics)
|
189
|
+
case @options[:reporter]
|
190
|
+
when "json"
|
191
|
+
JSONReporter.new(statistics)
|
192
|
+
when "html"
|
193
|
+
HTMLReporter.new(statistics)
|
194
|
+
else
|
195
|
+
ConsoleReporter.new(statistics)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def create_stream_reporter
|
200
|
+
case @options[:reporter]
|
201
|
+
when "json"
|
202
|
+
require 'json'
|
203
|
+
Groonga::QueryLog::StreamJSONQueryLogReporter.new
|
204
|
+
when "html"
|
205
|
+
raise UnsupportedReporter, "HTML reporter doesn't support --stream."
|
206
|
+
else
|
207
|
+
Groonga::QueryLog::StreamConsoleQueryLogReporter.new
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,101 @@
|
|
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 Reporter
|
24
|
+
include Enumerable
|
25
|
+
|
26
|
+
attr_reader :output
|
27
|
+
def initialize(statistics)
|
28
|
+
@statistics = statistics
|
29
|
+
@report_summary = true
|
30
|
+
@output = $stdout
|
31
|
+
end
|
32
|
+
|
33
|
+
def apply_options(options)
|
34
|
+
self.output = options[:output] || @output
|
35
|
+
unless options[:report_summary].nil?
|
36
|
+
@report_summary = options[:report_summary]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def output=(output)
|
41
|
+
@output = output
|
42
|
+
@output = $stdout if @output == "-"
|
43
|
+
end
|
44
|
+
|
45
|
+
def each
|
46
|
+
@statistics.each do |statistic|
|
47
|
+
yield statistic
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def report
|
52
|
+
setup do
|
53
|
+
report_summary if @report_summary
|
54
|
+
report_statistics
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def report_statistics
|
59
|
+
each do |statistic|
|
60
|
+
report_statistic(statistic)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def setup
|
66
|
+
setup_output do
|
67
|
+
start
|
68
|
+
yield
|
69
|
+
finish
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def setup_output
|
74
|
+
original_output = @output
|
75
|
+
if @output.is_a?(String)
|
76
|
+
File.open(@output, "w") do |output|
|
77
|
+
@output = output
|
78
|
+
yield(@output)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
yield(@output)
|
82
|
+
end
|
83
|
+
ensure
|
84
|
+
@output = original_output
|
85
|
+
end
|
86
|
+
|
87
|
+
def write(*args)
|
88
|
+
@output.write(*args)
|
89
|
+
end
|
90
|
+
|
91
|
+
def format_time(time)
|
92
|
+
if time.nil?
|
93
|
+
"NaN"
|
94
|
+
else
|
95
|
+
time.strftime("%Y-%m-%d %H:%M:%S.%u")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,291 @@
|
|
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 ConsoleReporter < Reporter
|
26
|
+
class Color
|
27
|
+
NAMES = ["black", "red", "green", "yellow",
|
28
|
+
"blue", "magenta", "cyan", "white"]
|
29
|
+
|
30
|
+
attr_reader :name
|
31
|
+
def initialize(name, options={})
|
32
|
+
@name = name
|
33
|
+
@foreground = options[:foreground]
|
34
|
+
@foreground = true if @foreground.nil?
|
35
|
+
@intensity = options[:intensity]
|
36
|
+
@bold = options[:bold]
|
37
|
+
@italic = options[:italic]
|
38
|
+
@underline = options[:underline]
|
39
|
+
end
|
40
|
+
|
41
|
+
def foreground?
|
42
|
+
@foreground
|
43
|
+
end
|
44
|
+
|
45
|
+
def intensity?
|
46
|
+
@intensity
|
47
|
+
end
|
48
|
+
|
49
|
+
def bold?
|
50
|
+
@bold
|
51
|
+
end
|
52
|
+
|
53
|
+
def italic?
|
54
|
+
@italic
|
55
|
+
end
|
56
|
+
|
57
|
+
def underline?
|
58
|
+
@underline
|
59
|
+
end
|
60
|
+
|
61
|
+
def ==(other)
|
62
|
+
self.class === other and
|
63
|
+
[name, foreground?, intensity?,
|
64
|
+
bold?, italic?, underline?] ==
|
65
|
+
[other.name, other.foreground?, other.intensity?,
|
66
|
+
other.bold?, other.italic?, other.underline?]
|
67
|
+
end
|
68
|
+
|
69
|
+
def sequence
|
70
|
+
sequence = []
|
71
|
+
if @name == "none"
|
72
|
+
elsif @name == "reset"
|
73
|
+
sequence << "0"
|
74
|
+
else
|
75
|
+
foreground_parameter = foreground? ? 3 : 4
|
76
|
+
foreground_parameter += 6 if intensity?
|
77
|
+
sequence << "#{foreground_parameter}#{NAMES.index(@name)}"
|
78
|
+
end
|
79
|
+
sequence << "1" if bold?
|
80
|
+
sequence << "3" if italic?
|
81
|
+
sequence << "4" if underline?
|
82
|
+
sequence
|
83
|
+
end
|
84
|
+
|
85
|
+
def escape_sequence
|
86
|
+
"\e[#{sequence.join(';')}m"
|
87
|
+
end
|
88
|
+
|
89
|
+
def +(other)
|
90
|
+
MixColor.new([self, other])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class MixColor
|
95
|
+
attr_reader :colors
|
96
|
+
def initialize(colors)
|
97
|
+
@colors = colors
|
98
|
+
end
|
99
|
+
|
100
|
+
def sequence
|
101
|
+
@colors.inject([]) do |result, color|
|
102
|
+
result + color.sequence
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def escape_sequence
|
107
|
+
"\e[#{sequence.join(';')}m"
|
108
|
+
end
|
109
|
+
|
110
|
+
def +(other)
|
111
|
+
self.class.new([self, other])
|
112
|
+
end
|
113
|
+
|
114
|
+
def ==(other)
|
115
|
+
self.class === other and colors == other.colors
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def initialize(statistics)
|
120
|
+
super
|
121
|
+
@color = :auto
|
122
|
+
@reset_color = Color.new("reset")
|
123
|
+
@color_schema = {
|
124
|
+
:elapsed => {:foreground => :white, :background => :green},
|
125
|
+
:time => {:foreground => :white, :background => :cyan},
|
126
|
+
:slow => {:foreground => :white, :background => :red},
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
def apply_options(options)
|
131
|
+
super
|
132
|
+
@color = options[:color] || @color
|
133
|
+
end
|
134
|
+
|
135
|
+
def report_statistics
|
136
|
+
write("\n")
|
137
|
+
write("Slow Queries:\n")
|
138
|
+
super
|
139
|
+
end
|
140
|
+
|
141
|
+
def report_statistic(statistic)
|
142
|
+
@index += 1
|
143
|
+
write("%*d) %s" % [@digit, @index, format_heading(statistic)])
|
144
|
+
report_parameters(statistic)
|
145
|
+
report_operations(statistic)
|
146
|
+
end
|
147
|
+
|
148
|
+
def start
|
149
|
+
@index = 0
|
150
|
+
if @statistics.size.zero?
|
151
|
+
@digit = 1
|
152
|
+
else
|
153
|
+
@digit = Math.log10(@statistics.size).truncate + 1
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def finish
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
def setup
|
162
|
+
super do
|
163
|
+
setup_color do
|
164
|
+
yield
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def report_summary
|
170
|
+
write("Summary:\n")
|
171
|
+
write(" Threshold:\n")
|
172
|
+
write(" slow response : #{@statistics.slow_response_threshold}\n")
|
173
|
+
write(" slow operation : #{@statistics.slow_operation_threshold}\n")
|
174
|
+
write(" # of responses : #{@statistics.n_responses}\n")
|
175
|
+
write(" # of slow responses : #{@statistics.n_slow_responses}\n")
|
176
|
+
write(" responses/sec : #{@statistics.responses_per_second}\n")
|
177
|
+
write(" start time : #{format_time(@statistics.start_time)}\n")
|
178
|
+
write(" last time : #{format_time(@statistics.last_time)}\n")
|
179
|
+
write(" period(sec) : #{@statistics.period}\n")
|
180
|
+
slow_response_ratio = @statistics.slow_response_ratio
|
181
|
+
write(" slow response ratio : %5.3f%%\n" % slow_response_ratio)
|
182
|
+
write(" total response time : #{@statistics.total_elapsed}\n")
|
183
|
+
report_slow_operations
|
184
|
+
end
|
185
|
+
|
186
|
+
def report_slow_operations
|
187
|
+
write(" Slow Operations:\n")
|
188
|
+
total_elapsed_digit = nil
|
189
|
+
total_elapsed_decimal_digit = 6
|
190
|
+
n_operations_digit = nil
|
191
|
+
@statistics.each_slow_operation do |grouped_operation|
|
192
|
+
total_elapsed = grouped_operation[:total_elapsed]
|
193
|
+
total_elapsed_digit ||= Math.log10(total_elapsed).truncate + 1
|
194
|
+
n_operations = grouped_operation[:n_operations]
|
195
|
+
n_operations_digit ||= Math.log10(n_operations).truncate + 1
|
196
|
+
parameters = [total_elapsed_digit + 1 + total_elapsed_decimal_digit,
|
197
|
+
total_elapsed_decimal_digit,
|
198
|
+
total_elapsed,
|
199
|
+
grouped_operation[:total_elapsed_ratio],
|
200
|
+
n_operations_digit,
|
201
|
+
n_operations,
|
202
|
+
grouped_operation[:n_operations_ratio],
|
203
|
+
grouped_operation[:name],
|
204
|
+
grouped_operation[:context]]
|
205
|
+
write(" [%*.*f](%5.2f%%) [%*d](%5.2f%%) %9s: %s\n" % parameters)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def report_parameters(statistic)
|
210
|
+
command = statistic.command
|
211
|
+
write(" name: <#{command.name}>\n")
|
212
|
+
write(" parameters:\n")
|
213
|
+
command.arguments.each do |key, value|
|
214
|
+
write(" <#{key}>: <#{value}>\n")
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def report_operations(statistic)
|
219
|
+
statistic.each_operation do |operation|
|
220
|
+
relative_elapsed_in_seconds = operation[:relative_elapsed_in_seconds]
|
221
|
+
formatted_elapsed = "%8.8f" % relative_elapsed_in_seconds
|
222
|
+
if operation[:slow?]
|
223
|
+
formatted_elapsed = colorize(formatted_elapsed, :slow)
|
224
|
+
end
|
225
|
+
operation_report = " %2d) %s: %10s" % [operation[:i] + 1,
|
226
|
+
formatted_elapsed,
|
227
|
+
operation[:name]]
|
228
|
+
if operation[:n_records]
|
229
|
+
operation_report << "(%6d)" % operation[:n_records]
|
230
|
+
else
|
231
|
+
operation_report << "(%6s)" % ""
|
232
|
+
end
|
233
|
+
context = operation[:context]
|
234
|
+
if context
|
235
|
+
context = colorize(context, :slow) if operation[:slow?]
|
236
|
+
operation_report << " " << context
|
237
|
+
end
|
238
|
+
write("#{operation_report}\n")
|
239
|
+
end
|
240
|
+
write("\n")
|
241
|
+
end
|
242
|
+
|
243
|
+
def guess_color_availability(output)
|
244
|
+
return false unless output.tty?
|
245
|
+
case ENV["TERM"]
|
246
|
+
when /term(?:-color)?\z/, "screen"
|
247
|
+
true
|
248
|
+
else
|
249
|
+
return true if ENV["EMACS"] == "t"
|
250
|
+
false
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def setup_color
|
255
|
+
color = @color
|
256
|
+
@color = guess_color_availability(@output) if @color == :auto
|
257
|
+
yield
|
258
|
+
ensure
|
259
|
+
@color = color
|
260
|
+
end
|
261
|
+
|
262
|
+
def format_heading(statistic)
|
263
|
+
formatted_elapsed = colorize("%8.8f" % statistic.elapsed_in_seconds,
|
264
|
+
:elapsed)
|
265
|
+
"[%s-%s (%s)](%d): %s" % [format_time(statistic.start_time),
|
266
|
+
format_time(statistic.last_time),
|
267
|
+
formatted_elapsed,
|
268
|
+
statistic.return_code,
|
269
|
+
statistic.raw_command]
|
270
|
+
end
|
271
|
+
|
272
|
+
def format_time(time)
|
273
|
+
colorize(super, :time)
|
274
|
+
end
|
275
|
+
|
276
|
+
def colorize(text, schema_name)
|
277
|
+
return text unless @color
|
278
|
+
options = @color_schema[schema_name]
|
279
|
+
color = Color.new("none")
|
280
|
+
if options[:foreground]
|
281
|
+
color += Color.new(options[:foreground].to_s, :bold => true)
|
282
|
+
end
|
283
|
+
if options[:background]
|
284
|
+
color += Color.new(options[:background].to_s, :foreground => false)
|
285
|
+
end
|
286
|
+
"%s%s%s" % [color.escape_sequence, text, @reset_color.escape_sequence]
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|