nbogie-production_log_analyzer 1.5.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,45 @@
1
+ === 1.5.1.1
2
+
3
+ * Groups by http verb (GET/PUT/POST/DELETE/HEAD)
4
+ * Fixed bug where "to csv", "to xml" log entries were being completely ignored.
5
+ * Groups by format (xml/json/csv,etc)
6
+ * Forked from http://github.com/seattlerb/production_log_analyzer
7
+
8
+ === 1.5.1
9
+
10
+ * 1.9 and 1.8.7 compatibility.
11
+
12
+ === 1.5.0
13
+
14
+ * Fixed empty log bug. Patch by Tim Lucas.
15
+ * Fixed bug where sometimes lines would be logged before the
16
+ Processing line. Patch by Geoff Grosenbach.
17
+
18
+ === 1.4.0
19
+
20
+ * Switched to Hoe
21
+ * Allowed action_errors to suppress routing errors with > 3 occurances
22
+ * action_grep now works correctly with components
23
+ * pl_analyze now works correctly with components
24
+ * Added action_errors to extract error counts from logs
25
+ * Retabbed to match the rest of the world
26
+
27
+ === 1.3.0
28
+
29
+ * Added action_grep
30
+ * Added support for newer log format
31
+
32
+ === 1.2.0
33
+
34
+ * pl_analyze calculates per-action statistics
35
+ * pl_analyze can send an email with its output
36
+
37
+ === 1.1.0
38
+
39
+ * RDoc
40
+ * Other various fixes lost to time.
41
+
42
+ === 1.0.0
43
+
44
+ * Birthday!
45
+
data/LICENSE.txt ADDED
@@ -0,0 +1,27 @@
1
+ Copyright 2005, 2007 Eric Hodel, The Robot Co-op. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions
5
+ are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright
8
+ notice, this list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright
10
+ notice, this list of conditions and the following disclaimer in the
11
+ documentation and/or other materials provided with the distribution.
12
+ 3. Neither the names of the authors nor the names of their contributors
13
+ may be used to endorse or promote products derived from this software
14
+ without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
17
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
20
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
21
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
22
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
23
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
26
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+
data/Manifest.txt ADDED
@@ -0,0 +1,18 @@
1
+ History.txt
2
+ LICENSE.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ bin/action_errors
7
+ bin/action_grep
8
+ bin/pl_analyze
9
+ lib/production_log/action_grep.rb
10
+ lib/production_log/analyzer.rb
11
+ lib/production_log/parser.rb
12
+ test/test.syslog.0.14.x.log
13
+ test/test.syslog.1.2.shortname.log
14
+ test/test.syslog.empty.log
15
+ test/test.syslog.log
16
+ test/test_action_grep.rb
17
+ test/test_analyzer.rb
18
+ test/test_parser.rb
data/README.txt ADDED
@@ -0,0 +1,148 @@
1
+ = production_log_analyzer
2
+
3
+ * http://seattlerb.rubyforge.org/production_log_analyzer
4
+ * http://rubyforge.org/projects/seattlerb
5
+
6
+ == DESCRIPTION
7
+
8
+ production_log_analyzer lets you find out which actions on a Rails
9
+ site are slowing you down.
10
+
11
+ Bug reports:
12
+
13
+ http://rubyforge.org/tracker/?func=add&group_id=1513&atid=5921
14
+
15
+ == About
16
+
17
+ production_log_analyzer provides three tools to analyze log files
18
+ created by SyslogLogger. pl_analyze for getting daily reports,
19
+ action_grep for pulling log lines for a single action and
20
+ action_errors to summarize errors with counts.
21
+
22
+ The analyzer currently requires the use of SyslogLogger because the
23
+ default Logger doesn't give any way to associate lines logged to a
24
+ request.
25
+
26
+ The PL Analyzer also includes action_grep which lets you grab lines from a log
27
+ that only match a single action.
28
+
29
+ action_grep RssController#uber /var/log/production.log
30
+
31
+ == Installing
32
+
33
+ sudo gem install production_log_analyzer
34
+
35
+ === Setup
36
+
37
+ First:
38
+
39
+ Set up SyslogLogger according to the instructions here:
40
+
41
+ http://seattlerb.rubyforge.org/SyslogLogger/
42
+
43
+ Then:
44
+
45
+ Set up a cronjob (or something like that) to run log files through pl_analyze.
46
+
47
+ == Using pl_analyze
48
+
49
+ To run pl_analyze simply give it the name of a log file to analyze.
50
+
51
+ pl_analyze /var/log/production.log
52
+
53
+ If you want, you can run it from a cron something like this:
54
+
55
+ /usr/bin/gzip -dc /var/log/production.log.0.gz | /usr/local/bin/pl_analyze /dev/stdin
56
+
57
+ Or, have pl_analyze email you (which is preferred, because tabs get preserved):
58
+
59
+ /usr/bin/gzip -dc /var/log/production.log.0.gz | /usr/local/bin/pl_analyze /dev/stdin -e devnull@robotcoop.com -s "pl_analyze for `date -v-1d "+%D"`"
60
+
61
+ In the future, pl_analyze will be able to read from STDIN.
62
+
63
+ == Sample output
64
+
65
+ Request Times Summary: Count Avg Std Dev Min Max
66
+ ALL REQUESTS: 11 0.576 0.508 0.000 1.470
67
+
68
+ ThingsController#view: 3 0.716 0.387 0.396 1.260
69
+ TeamsController#progress: 2 0.841 0.629 0.212 1.470
70
+ RssController#uber: 2 0.035 0.000 0.035 0.035
71
+ PeopleController#progress: 2 0.489 0.489 0.000 0.977
72
+ PeopleController#view: 2 0.731 0.371 0.360 1.102
73
+
74
+ Average Request Time: 0.634
75
+ Request Time Std Dev: 0.498
76
+
77
+ Slowest Request Times:
78
+ TeamsController#progress took 1.470s
79
+ ThingsController#view took 1.260s
80
+ PeopleController#view took 1.102s
81
+ PeopleController#progress took 0.977s
82
+ ThingsController#view took 0.492s
83
+ ThingsController#view took 0.396s
84
+ PeopleController#view took 0.360s
85
+ TeamsController#progress took 0.212s
86
+ RssController#uber took 0.035s
87
+ RssController#uber took 0.035s
88
+
89
+ ------------------------------------------------------------------------
90
+
91
+ DB Times Summary: Count Avg Std Dev Min Max
92
+ ALL REQUESTS: 11 0.366 0.393 0.000 1.144
93
+
94
+ ThingsController#view: 3 0.403 0.362 0.122 0.914
95
+ TeamsController#progress: 2 0.646 0.497 0.149 1.144
96
+ RssController#uber: 2 0.008 0.000 0.008 0.008
97
+ PeopleController#progress: 2 0.415 0.415 0.000 0.830
98
+ PeopleController#view: 2 0.338 0.149 0.189 0.486
99
+
100
+ Average DB Time: 0.402
101
+ DB Time Std Dev: 0.394
102
+
103
+ Slowest Total DB Times:
104
+ TeamsController#progress took 1.144s
105
+ ThingsController#view took 0.914s
106
+ PeopleController#progress took 0.830s
107
+ PeopleController#view took 0.486s
108
+ PeopleController#view took 0.189s
109
+ ThingsController#view took 0.173s
110
+ TeamsController#progress took 0.149s
111
+ ThingsController#view took 0.122s
112
+ RssController#uber took 0.008s
113
+ RssController#uber took 0.008s
114
+
115
+ ------------------------------------------------------------------------
116
+
117
+ Render Times Summary: Count Avg Std Dev Min Max
118
+ ALL REQUESTS: 11 0.219 0.253 0.000 0.695
119
+
120
+ ThingsController#view: 3 0.270 0.171 0.108 0.506
121
+ TeamsController#progress: 2 0.000 0.000 0.000 0.000
122
+ RssController#uber: 2 0.012 0.000 0.012 0.012
123
+ PeopleController#progress: 2 0.302 0.302 0.000 0.604
124
+ PeopleController#view: 2 0.487 0.209 0.278 0.695
125
+
126
+ Average Render Time: 0.302
127
+ Render Time Std Dev: 0.251
128
+
129
+ Slowest Total Render Times:
130
+ PeopleController#view took 0.695s
131
+ PeopleController#progress took 0.604s
132
+ ThingsController#view took 0.506s
133
+ PeopleController#view took 0.278s
134
+ ThingsController#view took 0.197s
135
+ ThingsController#view took 0.108s
136
+ RssController#uber took 0.012s
137
+ RssController#uber took 0.012s
138
+ TeamsController#progress took 0.000s
139
+ TeamsController#progress took 0.000s
140
+
141
+ == What's missing
142
+
143
+ * More reports
144
+ * Command line arguments including:
145
+ * Help
146
+ * What type of log file you've got (if somebody sends patches with tests)
147
+ * Read from STDIN
148
+
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ ENV.delete 'GEM_PATH'
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ Hoe.plugin :email, :perforce # not on minitest yet
7
+
8
+ Hoe.spec 'nbogie-production_log_analyzer' do
9
+ developer 'Eric Hodel', 'drbrain@segment7.net'
10
+ self.name = 'nbogie-production_log_analyzer'
11
+ self.version = '1.5.1.1'
12
+
13
+ extra_deps << ['rails_analyzer_tools', '>= 1.4.0']
14
+ end
data/bin/action_errors ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/local/bin/ruby -ws
2
+
3
+ $h ||= false
4
+ $r ||= false
5
+ $o ||= false
6
+
7
+ $r = $r ? ($r.to_i rescue false) : false
8
+
9
+ if $h then
10
+ $stderr.puts "Usage: #{$0} [-r=N] LOGFILE"
11
+ $stderr.puts "\t-r=N\tShow routing errors with N or more occurances"
12
+ $stderr.puts "\t-o\tShow errors with one occurance"
13
+ exit
14
+ end
15
+
16
+ errors = {}
17
+ counts = Hash.new 0
18
+
19
+ ARGF.each_line do |line|
20
+ line =~ /\]: (.*?) (.*)/
21
+ next if $1.nil?
22
+ msg = $1
23
+ trace = $2
24
+ key = msg.gsub(/\d/, '#')
25
+ counts[key] += 1
26
+ next if counts[key] > 1
27
+ trace = trace.split(' ')[0..-2].map { |l| l.strip }.join("\n\t")
28
+ error = "#{msg}\n\t#{trace}"
29
+ errors[key] = error
30
+ end
31
+
32
+ counts.sort_by { |_,c| -c }.each do |key, count|
33
+ next if count == 1 and not $o
34
+ error = errors[key]
35
+
36
+ if error =~ /^ActionController::RoutingError/ then
37
+ next unless $r
38
+ next if $r and count < $r
39
+ end
40
+
41
+ puts "count: #{count}"
42
+ puts "{{{"
43
+ puts error
44
+ puts "}}}"
45
+ end
46
+
data/bin/action_grep ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ require 'production_log/action_grep'
4
+
5
+ action_name = ARGV.shift
6
+ file_name = ARGV.shift
7
+
8
+ if action_name.nil? or file_name.nil? then
9
+ puts "Usage: #{$0} action_name file_name"
10
+ exit 1
11
+ end
12
+
13
+ begin
14
+ ActionGrep.grep action_name, file_name
15
+ rescue ArgumentError => e
16
+ puts e
17
+ exit 1
18
+ end
19
+
data/bin/pl_analyze ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ require 'production_log/analyzer'
4
+
5
+ file_name = ARGV.shift
6
+
7
+ if file_name.nil? then
8
+ puts "Usage: #{$0} file_name [-e email_recipient [-s subject]] [count]"
9
+ exit 1
10
+ end
11
+
12
+ email_recipient = nil
13
+ subject = nil
14
+
15
+ if ARGV.first == '-e' then
16
+ ARGV.shift # -e
17
+ email_recipient = ARGV.shift
18
+ end
19
+
20
+ if email_recipient and ARGV.first == '-s' then
21
+ ARGV.shift # -s
22
+ subject = ARGV.shift
23
+ end
24
+
25
+ count = ARGV.shift
26
+ count = count.nil? ? 10 : Integer(count)
27
+
28
+ if email_recipient.nil? then
29
+ analyzer = Analyzer.new file_name
30
+ analyzer.process
31
+ puts analyzer.report(count)
32
+ else
33
+ Analyzer.email file_name, email_recipient, subject, count
34
+ end
35
+
@@ -0,0 +1,42 @@
1
+ module ActionGrep; end
2
+
3
+ class << ActionGrep
4
+
5
+ def grep(action_name, file_name)
6
+ unless action_name =~ /\A([A-Z][A-Za-z\d]*)(?:#([A-Za-z]\w*))?\Z/ then
7
+ raise ArgumentError, "Invalid action name #{action_name} expected something like SomeController#action"
8
+ end
9
+
10
+ unless File.file? file_name and File.readable? file_name then
11
+ raise ArgumentError, "Unable to read #{file_name}"
12
+ end
13
+
14
+ buckets = Hash.new { |h,k| h[k] = [] }
15
+ comp_count = Hash.new 0
16
+
17
+ File.open file_name do |fp|
18
+ fp.each_line do |line|
19
+ line =~ / ([^ ]+) ([^ ]+)\[(\d+)\]: (.*)/
20
+ next if $2.nil? or $2 == 'newsyslog'
21
+ bucket = [$1, $2, $3].join '-'
22
+ data = $4
23
+
24
+ buckets[bucket] << line
25
+
26
+ case data
27
+ when /^Start rendering component / then
28
+ comp_count[bucket] += 1
29
+ when /^End of component rendering$/ then
30
+ comp_count[bucket] -= 1
31
+ when /^Completed/ then
32
+ next unless comp_count[bucket] == 0
33
+ action = buckets.delete bucket
34
+ next unless action.any? { |l| l =~ /: Processing #{action_name}/ }
35
+ puts action.join
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+
@@ -0,0 +1,405 @@
1
+ $TESTING = false unless defined? $TESTING
2
+
3
+ require 'production_log/parser'
4
+
5
+ module Enumerable
6
+
7
+ ##
8
+ # Sum of all the elements of the Enumerable
9
+
10
+ def sum
11
+ return self.inject(0) { |acc, i| acc + i }
12
+ end
13
+
14
+ ##
15
+ # Average of all the elements of the Enumerable
16
+ #
17
+ # The Enumerable must respond to #length
18
+
19
+ def average
20
+ return self.sum / self.length.to_f
21
+ end
22
+
23
+ ##
24
+ # Sample variance of all the elements of the Enumerable
25
+ #
26
+ # The Enumerable must respond to #length
27
+
28
+ def sample_variance
29
+ avg = self.average
30
+ sum = self.inject(0) { |acc, i| acc + (i - avg) ** 2 }
31
+ return (1 / self.length.to_f * sum)
32
+ end
33
+
34
+ ##
35
+ # Standard deviation of all the elements of the Enumerable
36
+ #
37
+ # The Enumerable must respond to #length
38
+
39
+ def standard_deviation
40
+ return Math.sqrt(self.sample_variance)
41
+ end
42
+
43
+ end
44
+
45
+ ##
46
+ # A list that only stores +limit+ items.
47
+
48
+ class SizedList < Array
49
+
50
+ ##
51
+ # Creates a new SizedList that can hold up to +limit+ items. Whenever
52
+ # adding a new item to the SizedList would make the list larger than
53
+ # +limit+, +delete_block+ is called.
54
+ #
55
+ # +delete_block+ is passed the list and the item being added.
56
+ # +delete_block+ must take action to remove an item and return true or
57
+ # return false if the item should not be added to the list.
58
+
59
+ def initialize(limit, &delete_block)
60
+ @limit = limit
61
+ @delete_block = delete_block
62
+ end
63
+
64
+ ##
65
+ # Attempts to add +obj+ to the list.
66
+
67
+ def <<(obj)
68
+ return super if self.length < @limit
69
+ return super if @delete_block.call self, obj
70
+ end
71
+
72
+ end
73
+
74
+ ##
75
+ # Stores +limit+ time/object pairs, keeping only the largest +limit+ items.
76
+ #
77
+ # Sample usage:
78
+ #
79
+ # l = SlowestTimes.new 5
80
+ #
81
+ # l << [Time.now, 'one']
82
+ # l << [Time.now, 'two']
83
+ # l << [Time.now, 'three']
84
+ # l << [Time.now, 'four']
85
+ # l << [Time.now, 'five']
86
+ # l << [Time.now, 'six']
87
+ #
88
+ # p l.map { |i| i.last }
89
+
90
+ class SlowestTimes < SizedList
91
+
92
+ ##
93
+ # Creates a new SlowestTimes SizedList that holds only +limit+ time/object
94
+ # pairs.
95
+
96
+ def initialize(limit)
97
+ super limit do |arr, new_item|
98
+ fastest_time = arr.sort_by { |time, name| [time, name] }.first
99
+ if fastest_time.first < new_item.first then
100
+ arr.delete_at index(fastest_time)
101
+ true
102
+ else
103
+ false
104
+ end
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ ##
111
+ # Calculates statistics for production logs.
112
+
113
+ class Analyzer
114
+
115
+ ##
116
+ # The version of the production log analyzer you are using.
117
+
118
+ VERSION = '1.5.1'
119
+
120
+ ##
121
+ # The logfile being read by the Analyzer.
122
+
123
+ attr_reader :logfile_name
124
+
125
+ ##
126
+ # An Array of all the request total times for the log file.
127
+
128
+ attr_reader :request_times
129
+
130
+ ##
131
+ # An Array of all the request database times for the log file.
132
+
133
+ attr_reader :db_times
134
+
135
+ ##
136
+ # An Array of all the request render times for the log file.
137
+
138
+ attr_reader :render_times
139
+
140
+ ##
141
+ # Generates and sends an email report with lots of fun stuff in it. This
142
+ # way, Mail.app will behave when given tabs.
143
+
144
+ def self.email(file_name, recipient, subject, count = 10)
145
+ analyzer = self.new file_name
146
+ analyzer.process
147
+ body = analyzer.report count
148
+
149
+ email = self.envelope(recipient, subject)
150
+ email << nil
151
+ email << "<pre>#{body}</pre>"
152
+ email = email.join($/) << $/
153
+
154
+ return email if $TESTING
155
+
156
+ IO.popen("/usr/sbin/sendmail -i -t", "w+") do |sm|
157
+ sm.print email
158
+ sm.flush
159
+ end
160
+ end
161
+
162
+ def self.envelope(recipient, subject = nil) # :nodoc:
163
+ envelope = {}
164
+
165
+ # HACK: this is a hack and the tests should be made order independent
166
+ envelope['Subject'] = subject || "pl_analyze"
167
+ envelope['To'] = recipient
168
+ envelope['Content-Type'] = "text/html"
169
+
170
+ envelope.sort.map { |(k,v)| "#{k}: #{v}" }
171
+ end
172
+
173
+ ##
174
+ # Creates a new Analyzer that will read data from +logfile_name+.
175
+
176
+ def initialize(logfile_name)
177
+ @logfile_name = logfile_name
178
+ @request_times = Hash.new { |h,k| h[k] = [] }
179
+ @db_times = Hash.new { |h,k| h[k] = [] }
180
+ @render_times = Hash.new { |h,k| h[k] = [] }
181
+ end
182
+
183
+ ##
184
+ # Processes the log file collecting statistics from each found LogEntry.
185
+
186
+ def process
187
+ File.open @logfile_name do |fp|
188
+ LogParser.parse fp do |entry|
189
+ entry_page = entry.page
190
+ next if entry_page.nil?
191
+ key_components=[entry.page.to_s]
192
+ key_components << entry.verb.to_s unless entry.verb.nil?
193
+ key_components << entry.format.to_s unless entry.format.nil?
194
+ entry_key = key_components.join(".")
195
+ @request_times[entry_key] << entry.request_time
196
+ @db_times[entry_key] << entry.db_time
197
+ @render_times[entry_key] << entry.render_time
198
+ end
199
+ end
200
+ end
201
+
202
+ ##
203
+ # The average total request time for all requests.
204
+
205
+ def average_request_time
206
+ return time_average(@request_times)
207
+ end
208
+
209
+ ##
210
+ # The standard deviation of the total request time for all requests.
211
+
212
+ def request_time_std_dev
213
+ return time_std_dev(@request_times)
214
+ end
215
+
216
+ ##
217
+ # The +limit+ slowest total request times.
218
+
219
+ def slowest_request_times(limit = 10)
220
+ return slowest_times(@request_times, limit)
221
+ end
222
+
223
+ ##
224
+ # The average total database time for all requests.
225
+
226
+ def average_db_time
227
+ return time_average(@db_times)
228
+ end
229
+
230
+ ##
231
+ # The standard deviation of the total database time for all requests.
232
+
233
+ def db_time_std_dev
234
+ return time_std_dev(@db_times)
235
+ end
236
+
237
+ ##
238
+ # The +limit+ slowest total database times.
239
+
240
+ def slowest_db_times(limit = 10)
241
+ return slowest_times(@db_times, limit)
242
+ end
243
+
244
+ ##
245
+ # The average total render time for all requests.
246
+
247
+ def average_render_time
248
+ return time_average(@render_times)
249
+ end
250
+
251
+ ##
252
+ # The standard deviation of the total render time for all requests.
253
+
254
+ def render_time_std_dev
255
+ return time_std_dev(@render_times)
256
+ end
257
+
258
+ ##
259
+ # The +limit+ slowest total render times for all requests.
260
+
261
+ def slowest_render_times(limit = 10)
262
+ return slowest_times(@render_times, limit)
263
+ end
264
+
265
+ ##
266
+ # A list of count/min/max/avg/std dev for request times.
267
+
268
+ def request_times_summary
269
+ return summarize("Request Times", @request_times)
270
+ end
271
+
272
+ ##
273
+ # A list of count/min/max/avg/std dev for database times.
274
+
275
+ def db_times_summary
276
+ return summarize("DB Times", @db_times)
277
+ end
278
+
279
+ ##
280
+ # A list of count/min/max/avg/std dev for request times.
281
+
282
+ def render_times_summary
283
+ return summarize("Render Times", @render_times)
284
+ end
285
+
286
+ ##
287
+ # Builds a report containing +count+ slow items.
288
+
289
+ def report(count)
290
+ return "No requests to analyze" if request_times.empty?
291
+
292
+ text = []
293
+
294
+ text << request_times_summary
295
+ text << nil
296
+ text << "Slowest Request Times:"
297
+ slowest_request_times(count).each do |time, name|
298
+ text << "\t#{name} took #{'%0.3f' % time}s"
299
+ end
300
+ text << nil
301
+ text << "-" * 72
302
+ text << nil
303
+
304
+ text << db_times_summary
305
+ text << nil
306
+ text << "Slowest Total DB Times:"
307
+ slowest_db_times(count).each do |time, name|
308
+ text << "\t#{name} took #{'%0.3f' % time}s"
309
+ end
310
+ text << nil
311
+ text << "-" * 72
312
+ text << nil
313
+
314
+ text << render_times_summary
315
+ text << nil
316
+ text << "Slowest Total Render Times:"
317
+ slowest_render_times(count).each do |time, name|
318
+ text << "\t#{name} took #{'%0.3f' % time}s"
319
+ end
320
+ text << nil
321
+
322
+ return text.join($/)
323
+ end
324
+
325
+ private unless $TESTING
326
+
327
+ def summarize(title, records) # :nodoc:
328
+ record = nil
329
+ list = []
330
+
331
+ # header
332
+ record = [pad_request_name("#{title} Summary"), 'Count', 'Avg', 'Std Dev',
333
+ 'Min', 'Max']
334
+ list << record.join("\t")
335
+
336
+ # all requests
337
+ all_times = records.values.flatten
338
+ record = [
339
+ all_times.average, all_times.standard_deviation, all_times.min,
340
+ all_times.max
341
+ ]
342
+ record.map! { |v| "%0.3f" % v }
343
+ record.unshift [pad_request_name('ALL REQUESTS'), all_times.size]
344
+ list << record.join("\t")
345
+
346
+ # spacer
347
+ list << nil
348
+
349
+ records.sort_by { |k,v| [-v.size, k] }.each do |req, times|
350
+ record = [times.average, times.standard_deviation, times.min, times.max]
351
+ record.map! { |v| "%0.3f" % v }
352
+ record.unshift ["#{pad_request_name req}", times.size]
353
+ list << record.join("\t")
354
+ end
355
+
356
+ return list.join("\n")
357
+ end
358
+
359
+ def slowest_times(records, limit) # :nodoc:
360
+ slowest_times = SlowestTimes.new limit
361
+
362
+ records.each do |name, times|
363
+ times.each do |time|
364
+ slowest_times << [time, name]
365
+ end
366
+ end
367
+
368
+ return slowest_times.sort_by { |time, name| [-time, name] }
369
+ end
370
+
371
+ def time_average(records) # :nodoc:
372
+ times = records.values.flatten
373
+ times.delete 0
374
+ return times.average
375
+ end
376
+
377
+ def time_std_dev(records) # :nodoc:
378
+ times = records.values.flatten
379
+ times.delete 0
380
+ return times.standard_deviation
381
+ end
382
+
383
+ def longest_request_name # :nodoc:
384
+ return @longest_req if defined? @longest_req
385
+
386
+ names = @request_times.keys.map do |name|
387
+ (name||'Unknown').length + 1 # + : - HACK where does nil come from?
388
+ end
389
+
390
+ @longest_req = names.max
391
+
392
+ @longest_req = 'Unknown'.length + 1 if @longest_req.nil?
393
+
394
+ return @longest_req
395
+ end
396
+
397
+ def pad_request_name(name) # :nodoc:
398
+ name = (name||'Unknown') + ':' # HACK where does nil come from?
399
+ padding_width = longest_request_name - name.length
400
+ padding_width = 0 if padding_width < 0
401
+ name += (' ' * padding_width)
402
+ end
403
+
404
+ end
405
+