request-log-analyzer 1.5.2 → 1.5.3
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/request-log-analyzer +9 -5
- data/lib/request_log_analyzer.rb +1 -1
- data/lib/request_log_analyzer/aggregator/echo.rb +2 -1
- data/lib/request_log_analyzer/aggregator/summarizer.rb +5 -37
- data/lib/request_log_analyzer/controller.rb +4 -3
- data/lib/request_log_analyzer/file_format.rb +68 -1
- data/lib/request_log_analyzer/file_format/amazon_s3.rb +3 -1
- data/lib/request_log_analyzer/file_format/apache.rb +7 -2
- data/lib/request_log_analyzer/file_format/merb.rb +3 -1
- data/lib/request_log_analyzer/file_format/mysql.rb +10 -5
- data/lib/request_log_analyzer/file_format/rack.rb +1 -3
- data/lib/request_log_analyzer/file_format/rails.rb +5 -5
- data/lib/request_log_analyzer/output.rb +4 -0
- data/lib/request_log_analyzer/output/fancy_html.rb +31 -0
- data/lib/request_log_analyzer/tracker.rb +0 -105
- data/lib/request_log_analyzer/tracker/duration.rb +3 -62
- data/lib/request_log_analyzer/tracker/frequency.rb +18 -5
- data/lib/request_log_analyzer/tracker/numeric_value.rb +223 -0
- data/lib/request_log_analyzer/tracker/traffic.rb +6 -72
- data/request-log-analyzer.gemspec +4 -4
- data/spec/lib/mocks.rb +1 -0
- data/spec/unit/file_format/common_regular_expressions_spec.rb +53 -0
- data/spec/unit/file_format/mysql_format_spec.rb +1 -2
- data/spec/unit/file_format/rails_format_spec.rb +9 -4
- data/spec/unit/tracker/duration_tracker_spec.rb +2 -83
- data/spec/unit/tracker/{count_tracker_spec.rb → numeric_value_tracker_spec.rb} +50 -7
- data/spec/unit/tracker/tracker_api_spec.rb +3 -2
- data/spec/unit/tracker/traffic_tracker_spec.rb +0 -79
- metadata +8 -5
- data/lib/request_log_analyzer/tracker/count.rb +0 -93
data/bin/request-log-analyzer
CHANGED
@@ -70,11 +70,11 @@ rescue CommandLine::Error => e
|
|
70
70
|
puts "Usage: request-log-analyzer [LOGFILES*] <OPTIONS>"
|
71
71
|
puts
|
72
72
|
puts "Input options:"
|
73
|
-
puts " --format <format>, -f: Log file format. amazon_s3, apache, merb, mysql, rack or rails (default)."
|
74
73
|
puts " --after <date> Only consider requests from <date> or later."
|
75
74
|
puts " --before <date> Only consider requests before <date>."
|
76
|
-
puts " --
|
75
|
+
puts " --format <format>, -f: Log file format. amazon_s3, apache, merb, mysql, rack or rails."
|
77
76
|
puts " --reject <field> <value> Only consider requests where <field> does not match <value>."
|
77
|
+
puts " --select <field> <value> Only consider requests where <field> matches <value>."
|
78
78
|
puts
|
79
79
|
puts "Output options:"
|
80
80
|
puts " --boring, -b Output reports without ASCII colors."
|
@@ -83,13 +83,17 @@ rescue CommandLine::Error => e
|
|
83
83
|
puts " --file <filename> Redirect output to file."
|
84
84
|
puts " --mail <emailaddress> Send report to an email address."
|
85
85
|
puts " --mailhost <server> Use the given server as the SMTP server for sending email."
|
86
|
-
puts " --
|
86
|
+
puts " --no-progress Hide the progress bar."
|
87
|
+
puts " --output <format> Output format. Supports 'html' and 'fixed_width'."
|
88
|
+
puts " --report-width <amount> Width of ASCII report. Defaults to terminal width."
|
89
|
+
puts " --report-amount <amount> Maximum numer of results per report."
|
87
90
|
puts " --yaml <filename> Dump the results in YAML format in the given file."
|
88
91
|
puts
|
89
92
|
puts "Examples:"
|
90
|
-
puts " request-log-analyzer
|
91
|
-
puts " request-log-analyzer
|
93
|
+
puts " request-log-analyzer production.log"
|
94
|
+
puts " request-log-analyzer mongrel.0.log mongrel.1.log --output HTML --mail root@localhost"
|
92
95
|
puts " request-log-analyzer --format merb -d requests.db production.log"
|
96
|
+
puts " request-log-analyzer mysql_slow_query.log --reject query /SQL_NO_CACHE/"
|
93
97
|
puts
|
94
98
|
puts "To install rake tasks in your Rails application, "
|
95
99
|
puts "run the following command in your application's root directory:"
|
data/lib/request_log_analyzer.rb
CHANGED
@@ -11,7 +11,7 @@ module RequestLogAnalyzer
|
|
11
11
|
|
12
12
|
# The current version of request-log-analyzer.
|
13
13
|
# Do not change the value by hand; it will be updated automatically by the gem release script.
|
14
|
-
VERSION = "1.5.
|
14
|
+
VERSION = "1.5.3"
|
15
15
|
|
16
16
|
# Loads constants in the RequestLogAnalyzer namespace using self.load_default_class_file(base, const)
|
17
17
|
# <tt>const</tt>:: The constant that is not yet loaded in the RequestLogAnalyzer namespace. This should be passed as a string or symbol.
|
@@ -11,7 +11,8 @@ module RequestLogAnalyzer::Aggregator
|
|
11
11
|
|
12
12
|
# Display every parsed line immediately to the terminal
|
13
13
|
def aggregate(request)
|
14
|
-
puts "\nRequest: " + request.lines.
|
14
|
+
puts "\nRequest: \n" + request.lines.map { |l|
|
15
|
+
"\t#{l[:lineno]}:#{l[:line_type]}: #{l.reject { |(k,v)| [:lineno, :line_type].include?(k) }.inspect}" }.join("\n")
|
15
16
|
end
|
16
17
|
|
17
18
|
# Capture all warnings during parsing
|
@@ -26,44 +26,12 @@ module RequestLogAnalyzer::Aggregator
|
|
26
26
|
def method_missing(tracker_method, *args)
|
27
27
|
track(tracker_method, *args)
|
28
28
|
end
|
29
|
-
|
30
|
-
# Track the frequency of a specific category
|
31
|
-
# <tt>category_field</tt> Field to track
|
32
|
-
# <tt>options</tt> options are passed to new frequency tracker
|
33
|
-
def frequency(category_field, options = {})
|
34
|
-
if category_field.kind_of?(Symbol)
|
35
|
-
track(:frequency, options.merge(:category => category_field))
|
36
|
-
elsif category_field.kind_of?(Hash)
|
37
|
-
track(:frequency, category_field.merge(options))
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# Track the duration of a specific category
|
42
|
-
# <tt>duration_field</tt> Field to track
|
43
|
-
# <tt>options</tt> options are passed to new frequency tracker
|
44
|
-
def duration(duration_field, options = {})
|
45
|
-
if duration_field.kind_of?(Symbol)
|
46
|
-
track(:duration, options.merge(:duration => duration_field))
|
47
|
-
elsif duration_field.kind_of?(Hash)
|
48
|
-
track(:duration, duration_field.merge(options))
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# Track the frequency of a specific category
|
53
|
-
# <tt>category_field</tt> Field to track
|
54
|
-
# <tt>options</tt> options are passed to new frequency tracker
|
55
|
-
def count(category_field, options = {})
|
56
|
-
if category_field.kind_of?(Symbol)
|
57
|
-
track(:count, options.merge(:category => category_field))
|
58
|
-
elsif category_field.kind_of?(Hash)
|
59
|
-
track(:count, category_field.merge(options))
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
29
|
+
|
63
30
|
# Helper function to initialize a tracker and add it to the tracker array.
|
64
31
|
# <tt>tracker_class</tt> The class to include
|
65
32
|
# <tt>optiont</tt> The options to pass to the trackers.
|
66
|
-
def track(tracker_klass,
|
33
|
+
def track(tracker_klass, value_field = {}, other_options = {})
|
34
|
+
options = value_field.kind_of?(Symbol) ? other_options.merge(:value => value_field) : value_field.merge(other_options)
|
67
35
|
tracker_klass = RequestLogAnalyzer::Tracker.const_get(RequestLogAnalyzer::to_camelcase(tracker_klass)) if tracker_klass.kind_of?(Symbol)
|
68
36
|
@trackers << tracker_klass.new(options)
|
69
37
|
end
|
@@ -125,7 +93,7 @@ module RequestLogAnalyzer::Aggregator
|
|
125
93
|
def report(output)
|
126
94
|
report_header(output)
|
127
95
|
if source.parsed_requests > 0
|
128
|
-
@trackers.each { |tracker|
|
96
|
+
@trackers.each { |tracker| output.report_tracker(tracker) }
|
129
97
|
else
|
130
98
|
output.puts
|
131
99
|
output.puts('There were no requests analyzed.')
|
@@ -156,7 +124,7 @@ module RequestLogAnalyzer::Aggregator
|
|
156
124
|
if has_log_ordering_warnings?
|
157
125
|
output.title("Parse warnings")
|
158
126
|
|
159
|
-
output.puts "
|
127
|
+
output.puts "Parsable lines were encountered without a header line before it. It"
|
160
128
|
output.puts "could be that logging is not setup correctly for your application."
|
161
129
|
output.puts "Visit this website for logging configuration tips:"
|
162
130
|
output.puts output.link("http://github.com/wvanbergen/request-log-analyzer/wikis/configure-logging")
|
@@ -53,8 +53,9 @@ module RequestLogAnalyzer
|
|
53
53
|
|
54
54
|
# Handle output format casing
|
55
55
|
if options[:output].class == String
|
56
|
-
options[:output] = '
|
57
|
-
options[:output] = '
|
56
|
+
options[:output] = 'FancyHTML' if options[:output] =~ /^fancy_?html$/i
|
57
|
+
options[:output] = 'HTML' if options[:output] =~ /^html$/i
|
58
|
+
options[:output] = 'FixedWidth' if options[:output] =~ /^fixed_?width$/i
|
58
59
|
end
|
59
60
|
|
60
61
|
# Register sources
|
@@ -142,7 +143,7 @@ module RequestLogAnalyzer
|
|
142
143
|
# Set the output class
|
143
144
|
output_args = {}
|
144
145
|
output_object = nil
|
145
|
-
if options[:output].is_a?
|
146
|
+
if options[:output].is_a?(Class)
|
146
147
|
output_class = options[:output]
|
147
148
|
else
|
148
149
|
output_class = RequestLogAnalyzer::Output::const_get(options[:output])
|
@@ -88,10 +88,77 @@ module RequestLogAnalyzer::FileFormat
|
|
88
88
|
|
89
89
|
# As Apache matches several simular formats, subtracting 1 will make a specific matcher have a higher score
|
90
90
|
score -= 1 if parser.file_format.class == RequestLogAnalyzer::FileFormat::Apache
|
91
|
-
|
91
|
+
|
92
92
|
score
|
93
93
|
end
|
94
94
|
|
95
|
+
# This module contains some methods to construct regular expressions for log fragments
|
96
|
+
# that are commonly used, like IP addresses and timestamp.
|
97
|
+
#
|
98
|
+
# You need to extend (or include in an unlikely case) this module in your file format
|
99
|
+
# to use these regular expression constructors.
|
100
|
+
module CommonRegularExpressions
|
101
|
+
|
102
|
+
TIMESTAMP_PARTS = {
|
103
|
+
'a' => '(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)',
|
104
|
+
'b' => '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)',
|
105
|
+
'y' => '\d{2}', 'Y' => '\d{4}', 'm' => '\d{2}', 'd' => '\d{2}',
|
106
|
+
'H' => '\d{2}', 'M' => '\d{2}', 'S' => '\d{2}', 'k' => '(?:\d| )\d',
|
107
|
+
'z' => '(?:[+-]\d{4}|[A-Z]{3,4})',
|
108
|
+
'Z' => '(?:[+-]\d{4}|[A-Z]{3,4})',
|
109
|
+
'%' => '%'
|
110
|
+
}
|
111
|
+
|
112
|
+
# Create a regular expression for a timestamp, generated by a strftime call.
|
113
|
+
# Provide the format string to construct a matching regular expression.
|
114
|
+
# Set blank to true to allow and empty string, or set blank to a string to set
|
115
|
+
# a substitute for the nil value.
|
116
|
+
def timestamp(format_string, blank = false)
|
117
|
+
regexp = ''
|
118
|
+
format_string.scan(/([^%]*)(?:%([A-Za-z%]))?/) do |literal, variable|
|
119
|
+
regexp << Regexp.quote(literal)
|
120
|
+
if variable
|
121
|
+
if TIMESTAMP_PARTS.has_key?(variable)
|
122
|
+
regexp << TIMESTAMP_PARTS[variable]
|
123
|
+
else
|
124
|
+
raise "Unknown variable: %#{variable}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
regexp = Regexp.new(regexp)
|
130
|
+
return case blank
|
131
|
+
when String then Regexp.union(regexp, Regexp.new(Regexp.quote(blank)))
|
132
|
+
when true then Regexp.union(regexp, //)
|
133
|
+
else regexp
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Construct a regular expression to parse IPv4 and IPv6 addresses.
|
138
|
+
#
|
139
|
+
# Allow nil values if the blank option is given. This can be true to
|
140
|
+
# allow an empty string or to a string substitute for the nil value.
|
141
|
+
def ip_address(blank = false)
|
142
|
+
|
143
|
+
# IP address regexp copied from Resolv::IPv4 and Resolv::IPv6,
|
144
|
+
# but adjusted to work for the purpose of request-log-analyzer.
|
145
|
+
ipv4_regexp = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
146
|
+
ipv6_regex_8_hex = /(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}/
|
147
|
+
ipv6_regex_compressed_hex = /(?:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::(?:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)/
|
148
|
+
ipv6_regex_6_hex_4_dec = /(?:(?:[0-9A-Fa-f]{1,4}:){6})#{ipv4_regexp}/
|
149
|
+
ipv6_regex_compressed_hex_4_dec = /(?:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::(?:(?:[0-9A-Fa-f]{1,4}:)*)#{ipv4_regexp}/
|
150
|
+
ipv6_regexp = Regexp.union(ipv6_regex_8_hex, ipv6_regex_compressed_hex, ipv6_regex_6_hex_4_dec, ipv6_regex_compressed_hex_4_dec)
|
151
|
+
|
152
|
+
# Allow the field to be blank if this option is given. This can be true to
|
153
|
+
# allow an empty string or a string alternative for the nil value.
|
154
|
+
ip_regexp = case blank
|
155
|
+
when String then Regexp.union(ipv4_regexp, ipv6_regexp, Regexp.new(Regexp.quote(blank)))
|
156
|
+
when true then Regexp.union(ipv4_regexp, ipv6_regexp, //)
|
157
|
+
else Regexp.union(ipv4_regexp, ipv6_regexp)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
95
162
|
# Base class for all log file format definitions. This class provides functions for subclasses to
|
96
163
|
# define their LineDefinitions and to define a summary report.
|
97
164
|
#
|
@@ -5,11 +5,13 @@ module RequestLogAnalyzer::FileFormat
|
|
5
5
|
# Access logs are disabled by default on Amazon S3. To enable logging, see
|
6
6
|
# http://docs.amazonwebservices.com/AmazonS3/latest/index.html?ServerLogs.html
|
7
7
|
class AmazonS3 < Base
|
8
|
+
|
9
|
+
extend CommonRegularExpressions
|
8
10
|
|
9
11
|
line_definition :access do |line|
|
10
12
|
line.header = true
|
11
13
|
line.footer = true
|
12
|
-
line.regexp = /^([^\ ]+) ([^\ ]+) \[(
|
14
|
+
line.regexp = /^([^\ ]+) ([^\ ]+) \[(#{timestamp('%d/%b/%Y:%H:%M:%S %z')})?\] (#{ip_address}) ([^\ ]+) ([^\ ]+) (\w+(?:\.\w+)*) ([^\ ]+) "([^"]+)" (\d+) ([^\ ]+) (\d+) (\d+) (\d+) (\d+) "([^"]+)" "([^"]+)"/
|
13
15
|
line.captures << { :name => :bucket_owner, :type => :string } <<
|
14
16
|
{ :name => :bucket, :type => :string } <<
|
15
17
|
{ :name => :timestamp, :type => :timestamp } <<
|
@@ -14,6 +14,8 @@ module RequestLogAnalyzer::FileFormat
|
|
14
14
|
# command line option.
|
15
15
|
class Apache < Base
|
16
16
|
|
17
|
+
extend CommonRegularExpressions
|
18
|
+
|
17
19
|
# A hash of predefined Apache log formats
|
18
20
|
LOG_FORMAT_DEFAULTS = {
|
19
21
|
:common => '%h %l %u %t "%r" %>s %b',
|
@@ -23,17 +25,20 @@ module RequestLogAnalyzer::FileFormat
|
|
23
25
|
:agent => '%{User-agent}i'
|
24
26
|
}
|
25
27
|
|
28
|
+
# I have encountered two timestamp types, with timezone and without. Parse both.
|
29
|
+
APACHE_TIMESTAMP = Regexp.union(timestamp('%d/%b/%Y:%H:%M:%S %z'), timestamp('%d/%b/%Y %H:%M:%S'))
|
30
|
+
|
26
31
|
# A hash that defines how the log format directives should be parsed.
|
27
32
|
LOG_DIRECTIVES = {
|
28
33
|
'%' => { :regexp => '%', :captures => [] },
|
29
34
|
'h' => { :regexp => '([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+)', :captures => [{:name => :remote_host, :type => :string}] },
|
30
|
-
'a' => { :regexp =>
|
35
|
+
'a' => { :regexp => "(#{ip_address})", :captures => [{:name => :remote_ip, :type => :string}] },
|
31
36
|
'b' => { :regexp => '(\d+|-)', :captures => [{:name => :bytes_sent, :type => :traffic}] },
|
32
37
|
'c' => { :regexp => '(\+|\-|\X)', :captures => [{:name => :connection_status, :type => :integer}] },
|
33
38
|
'D' => { :regexp => '(\d+|-)', :captures => [{:name => :duration, :type => :duration, :unit => :musec}] },
|
34
39
|
'l' => { :regexp => '([\w-]+)', :captures => [{:name => :remote_logname, :type => :nillable_string}] },
|
35
40
|
'T' => { :regexp => '((?:\d+(?:\.\d+))|-)', :captures => [{:name => :duration, :type => :duration, :unit => :sec}] },
|
36
|
-
't' => { :regexp =>
|
41
|
+
't' => { :regexp => "\\[(#{APACHE_TIMESTAMP})?\\]", :captures => [{:name => :timestamp, :type => :timestamp}] },
|
37
42
|
's' => { :regexp => '(\d{3})', :captures => [{:name => :http_status, :type => :integer}] },
|
38
43
|
'u' => { :regexp => '(\w+|-)', :captures => [{:name => :user, :type => :nillable_string}] },
|
39
44
|
'U' => { :regexp => '(\/\S*)', :captures => [{:name => :path, :type => :string}] },
|
@@ -5,11 +5,13 @@ module RequestLogAnalyzer::FileFormat
|
|
5
5
|
# the different request durations that can be used for analysis.
|
6
6
|
class Merb < Base
|
7
7
|
|
8
|
+
extend CommonRegularExpressions
|
9
|
+
|
8
10
|
# ~ Started request handling: Fri Aug 29 11:10:23 +0200 2008
|
9
11
|
line_definition :started do |line|
|
10
12
|
line.header = true
|
11
13
|
line.teaser = /Started request handling\:/
|
12
|
-
line.regexp = /Started request handling\:\ (
|
14
|
+
line.regexp = /Started request handling\:\ (#{timestamp('%a %b %d %H:%M:%S %z %Y')})/
|
13
15
|
line.captures << { :name => :timestamp, :type => :timestamp }
|
14
16
|
end
|
15
17
|
|
@@ -2,17 +2,19 @@ module RequestLogAnalyzer::FileFormat
|
|
2
2
|
|
3
3
|
class Mysql < Base
|
4
4
|
|
5
|
+
extend CommonRegularExpressions
|
6
|
+
|
5
7
|
line_definition :time do |line|
|
6
8
|
line.header = :alternative
|
7
9
|
line.teaser = /\# Time: /
|
8
|
-
line.regexp = /\# Time: (
|
10
|
+
line.regexp = /\# Time: (#{timestamp('%y%m%d %k:%M:%S')})/
|
9
11
|
line.captures << { :name => :timestamp, :type => :timestamp }
|
10
12
|
end
|
11
13
|
|
12
14
|
line_definition :user_host do |line|
|
13
15
|
line.header = :alternative
|
14
16
|
line.teaser = /\# User\@Host\: /
|
15
|
-
line.regexp = /\# User\@Host\: ([\w-]+)\[[\w-]+\] \@ ([\w\.-]*) \[(
|
17
|
+
line.regexp = /\# User\@Host\: ([\w-]+)\[[\w-]+\] \@ ([\w\.-]*) \[(#{ip_address(true)})\]/
|
16
18
|
line.captures << { :name => :user, :type => :string } <<
|
17
19
|
{ :name => :host, :type => :string } <<
|
18
20
|
{ :name => :ip, :type => :string }
|
@@ -53,9 +55,12 @@ module RequestLogAnalyzer::FileFormat
|
|
53
55
|
analyze.frequency :user, :title => "Users with most queries"
|
54
56
|
analyze.duration :query_time, :category => PER_USER, :title => 'Query time per user'
|
55
57
|
analyze.duration :query_time, :category => PER_USER_QUERY, :title => 'Query time'
|
56
|
-
|
57
|
-
analyze.
|
58
|
-
|
58
|
+
|
59
|
+
analyze.duration :lock_time, :category => PER_USER_QUERY, :title => 'Lock time',
|
60
|
+
:if => lambda { |request| request[:lock_time] > 0.0 }
|
61
|
+
|
62
|
+
analyze.numeric_value :rows_examined, :category => PER_USER_QUERY, :title => "Rows examined"
|
63
|
+
analyze.numeric_value :rows_sent, :category => PER_USER_QUERY, :title => "Rows sent"
|
59
64
|
end
|
60
65
|
|
61
66
|
class Request < RequestLogAnalyzer::Request
|
@@ -7,6 +7,8 @@ module RequestLogAnalyzer::FileFormat
|
|
7
7
|
# method as first argument.
|
8
8
|
class Rails < Base
|
9
9
|
|
10
|
+
extend CommonRegularExpressions
|
11
|
+
|
10
12
|
# Creates a Rails FileFormat instance.
|
11
13
|
#
|
12
14
|
# The lines that will be parsed can be defined by the argument to this function,
|
@@ -64,11 +66,11 @@ module RequestLogAnalyzer::FileFormat
|
|
64
66
|
end
|
65
67
|
|
66
68
|
if lines.has_key?(:rendered)
|
67
|
-
analyze.duration :render_duration, :category => :render_file, :
|
69
|
+
analyze.duration :render_duration, :category => :render_file, :multiple => true, :title => 'Partial rendering duration'
|
68
70
|
end
|
69
71
|
|
70
72
|
if lines.has_key?(:query_executed)
|
71
|
-
analyze.duration :query_duration, :category => :query_sql, :
|
73
|
+
analyze.duration :query_duration, :category => :query_sql, :multiple => true, :title => 'Query duration'
|
72
74
|
end
|
73
75
|
|
74
76
|
return analyze.trackers + report_definer.trackers
|
@@ -86,7 +88,7 @@ module RequestLogAnalyzer::FileFormat
|
|
86
88
|
LINE_DEFINITIONS = {
|
87
89
|
:processing => RequestLogAnalyzer::LineDefinition.new(:processing, :header => true,
|
88
90
|
:teaser => /Processing /,
|
89
|
-
:regexp => /Processing ((?:\w+::)
|
91
|
+
:regexp => /Processing ((?:\w+::)*\w+)#(\w+)(?: to (\w+))? \(for (#{ip_address}) at (#{timestamp('%Y-%m-%d %H:%M:%S')})\) \[([A-Z]+)\]/,
|
90
92
|
:captures => [{ :name => :controller, :type => :string },
|
91
93
|
{ :name => :action, :type => :string },
|
92
94
|
{ :name => :format, :type => :string, :default => 'html' },
|
@@ -170,7 +172,5 @@ module RequestLogAnalyzer::FileFormat
|
|
170
172
|
sql.gsub(/\b\d+\b/, ':int').gsub(/`([^`]+)`/, '\1').gsub(/'[^']*'/, ':string').rstrip
|
171
173
|
end
|
172
174
|
end
|
173
|
-
|
174
175
|
end
|
175
|
-
|
176
176
|
end
|
@@ -56,6 +56,10 @@ module RequestLogAnalyzer::Output
|
|
56
56
|
@options = options
|
57
57
|
@style = options[:style] || { :cell_separator => true, :table_border => false }
|
58
58
|
end
|
59
|
+
|
60
|
+
def report_tracker(tracker)
|
61
|
+
tracker.report(self)
|
62
|
+
end
|
59
63
|
|
60
64
|
# Apply a style block.. with style :)
|
61
65
|
def with_style(temp_style = {})
|
@@ -0,0 +1,31 @@
|
|
1
|
+
begin
|
2
|
+
require 'rubygems'
|
3
|
+
require 'gchart'
|
4
|
+
rescue LoadError
|
5
|
+
$stderr.puts "The FancyHTML output format requires the googlechart gem:"
|
6
|
+
$stderr.puts " (sudo) gem install googlecharts"
|
7
|
+
end
|
8
|
+
|
9
|
+
module RequestLogAnalyzer::Output
|
10
|
+
|
11
|
+
class FancyHTML < HTML
|
12
|
+
|
13
|
+
# Load class files if needed
|
14
|
+
def self.const_missing(const)
|
15
|
+
RequestLogAnalyzer::load_default_class_file(self, const)
|
16
|
+
end
|
17
|
+
|
18
|
+
def report_tracker(tracker)
|
19
|
+
case tracker
|
20
|
+
when RequestLogAnalyzer::Tracker::HourlySpread then report_hourly_spread(tracker)
|
21
|
+
else tracker.report(self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def report_hourly_spread(tracker)
|
26
|
+
title tracker.title
|
27
|
+
puts tag(:img, nil, :width => '700', :height => '120', :src =>
|
28
|
+
Gchart.sparkline(:data => tracker.hour_frequencies, :size => '700x120', :line_colors => '0077CC'))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -98,109 +98,4 @@ module RequestLogAnalyzer::Tracker
|
|
98
98
|
nil
|
99
99
|
end
|
100
100
|
end
|
101
|
-
|
102
|
-
module StatisticsTracking
|
103
|
-
|
104
|
-
# Update sthe running calculation of statistics with the newly found numeric value.
|
105
|
-
# <tt>category</tt>:: The category for which to update the running statistics calculations
|
106
|
-
# <tt>number</tt>:: The numeric value to update the calculations with.
|
107
|
-
def update_statistics(category, number)
|
108
|
-
@categories[category] ||= {:hits => 0, :sum => 0, :mean => 0.0, :sum_of_squares => 0.0, :min => number, :max => number }
|
109
|
-
delta = number - @categories[category][:mean]
|
110
|
-
|
111
|
-
@categories[category][:hits] += 1
|
112
|
-
@categories[category][:mean] += (delta / @categories[category][:hits])
|
113
|
-
@categories[category][:sum_of_squares] += delta * (number - @categories[category][:mean])
|
114
|
-
@categories[category][:sum] += number
|
115
|
-
@categories[category][:min] = number if number < @categories[category][:min]
|
116
|
-
@categories[category][:max] = number if number > @categories[category][:max]
|
117
|
-
end
|
118
|
-
|
119
|
-
# Get the number of hits of a specific category.
|
120
|
-
# <tt>cat</tt> The category
|
121
|
-
def hits(cat)
|
122
|
-
@categories[cat][:hits]
|
123
|
-
end
|
124
|
-
|
125
|
-
# Get the total duration of a specific category.
|
126
|
-
# <tt>cat</tt> The category
|
127
|
-
def sum(cat)
|
128
|
-
@categories[cat][:sum]
|
129
|
-
end
|
130
|
-
|
131
|
-
# Get the minimal duration of a specific category.
|
132
|
-
# <tt>cat</tt> The category
|
133
|
-
def min(cat)
|
134
|
-
@categories[cat][:min]
|
135
|
-
end
|
136
|
-
|
137
|
-
# Get the maximum duration of a specific category.
|
138
|
-
# <tt>cat</tt> The category
|
139
|
-
def max(cat)
|
140
|
-
@categories[cat][:max]
|
141
|
-
end
|
142
|
-
|
143
|
-
# Get the average duration of a specific category.
|
144
|
-
# <tt>cat</tt> The category
|
145
|
-
def mean(cat)
|
146
|
-
@categories[cat][:mean]
|
147
|
-
end
|
148
|
-
|
149
|
-
# Get the standard deviation of the duration of a specific category.
|
150
|
-
# <tt>cat</tt> The category
|
151
|
-
def stddev(cat)
|
152
|
-
Math.sqrt(variance(cat))
|
153
|
-
end
|
154
|
-
|
155
|
-
# Get the variance of the duration of a specific category.
|
156
|
-
# <tt>cat</tt> The category
|
157
|
-
def variance(cat)
|
158
|
-
return 0.0 if @categories[cat][:hits] <= 1
|
159
|
-
(@categories[cat][:sum_of_squares] / (@categories[cat][:hits] - 1))
|
160
|
-
end
|
161
|
-
|
162
|
-
# Get the average duration of a all categories.
|
163
|
-
def mean_overall
|
164
|
-
sum_overall / hits_overall
|
165
|
-
end
|
166
|
-
|
167
|
-
# Get the cumlative duration of a all categories.
|
168
|
-
def sum_overall
|
169
|
-
@categories.inject(0.0) { |sum, (name, cat)| sum + cat[:sum] }
|
170
|
-
end
|
171
|
-
|
172
|
-
# Get the total hits of a all categories.
|
173
|
-
def hits_overall
|
174
|
-
@categories.inject(0) { |sum, (name, cat)| sum + cat[:hits] }
|
175
|
-
end
|
176
|
-
|
177
|
-
# Return categories sorted by a given key.
|
178
|
-
# <tt>by</tt> The key to sort on. This parameter can be omitted if a sorting block is provided instead
|
179
|
-
def sorted_by(by = nil)
|
180
|
-
if block_given?
|
181
|
-
categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) }
|
182
|
-
else
|
183
|
-
categories.sort { |a, b| send(by, b[0]) <=> send(by, a[0]) }
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
# Returns the column header for a statistics table to report on the statistics result
|
188
|
-
def statistics_header(options)
|
189
|
-
[
|
190
|
-
{:title => options[:title], :width => :rest},
|
191
|
-
{:title => 'Hits', :align => :right, :highlight => (options[:highlight] == :hits), :min_width => 4},
|
192
|
-
{:title => 'Sum', :align => :right, :highlight => (options[:highlight] == :sum), :min_width => 6},
|
193
|
-
{:title => 'Mean', :align => :right, :highlight => (options[:highlight] == :mean), :min_width => 6},
|
194
|
-
{:title => 'StdDev', :align => :right, :highlight => (options[:highlight] == :stddev), :min_width => 6},
|
195
|
-
{:title => 'Min', :align => :right, :highlight => (options[:highlight] == :min), :min_width => 6},
|
196
|
-
{:title => 'Max', :align => :right, :highlight => (options[:highlight] == :max), :min_width => 6}
|
197
|
-
]
|
198
|
-
end
|
199
|
-
|
200
|
-
# Returns a row of statistics information for a report table, given a category
|
201
|
-
def statistics_row(cat)
|
202
|
-
[cat, hits(cat), display_value(sum(cat)), display_value(mean(cat)), display_value(stddev(cat)),
|
203
|
-
display_value(min(cat)), display_value(max(cat))]
|
204
|
-
end
|
205
|
-
end
|
206
101
|
end
|