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
data/doc/text/news.md ADDED
@@ -0,0 +1,5 @@
1
+ # News
2
+
3
+ ## 1.0.0: 2012-12-14
4
+
5
+ The first release!!!
@@ -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