nbogie-production_log_analyzer 1.5.1.1

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/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
+