request-log-analyzer 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/{DESIGN → DESIGN.rdoc} +13 -10
- data/README.rdoc +7 -5
- data/RELEASE_NOTES.rdoc +10 -0
- data/bin/request-log-analyzer +7 -3
- data/lib/cli/tools.rb +2 -2
- data/lib/request_log_analyzer/aggregator/summarizer.rb +51 -2
- data/lib/request_log_analyzer/controller.rb +4 -0
- data/lib/request_log_analyzer/output/fixed_width.rb +36 -1
- data/lib/request_log_analyzer/output/html.rb +20 -2
- data/lib/request_log_analyzer/output.rb +36 -1
- data/lib/request_log_analyzer/request.rb +2 -2
- data/lib/request_log_analyzer/source/database.rb +19 -4
- data/lib/request_log_analyzer/source/log_parser.rb +26 -3
- data/lib/request_log_analyzer/tracker/duration.rb +49 -8
- data/lib/request_log_analyzer/tracker/frequency.rb +30 -7
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +41 -15
- data/lib/request_log_analyzer/tracker/timespan.rb +22 -2
- data/lib/request_log_analyzer/tracker.rb +38 -1
- data/lib/request_log_analyzer.rb +2 -4
- data/spec/fixtures/decompression.log +12 -0
- data/spec/fixtures/decompression.log.bz2 +0 -0
- data/spec/fixtures/decompression.log.gz +0 -0
- data/spec/fixtures/decompression.log.zip +0 -0
- data/spec/fixtures/decompression.tar.gz +0 -0
- data/spec/fixtures/decompression.tgz +0 -0
- data/spec/integration/command_line_usage_spec.rb +14 -14
- data/spec/lib/helper.rb +19 -3
- data/spec/lib/testing_format.rb +1 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/aggregator/summarizer_spec.rb +1 -1
- data/spec/unit/source/log_parser_spec.rb +39 -0
- data/spec/unit/tracker/{tracker_api_test.rb → tracker_api_spec.rb} +7 -1
- data/tasks/github-gem.rake +1 -2
- data/tasks/request_log_analyzer.rake +23 -7
- metadata +16 -29
- data/HACKING +0 -7
@@ -67,18 +67,41 @@ module RequestLogAnalyzer::Source
|
|
67
67
|
def parse_files(files, options = {}, &block) # :yields: request
|
68
68
|
files.each { |file| parse_file(file, options, &block) }
|
69
69
|
end
|
70
|
+
|
71
|
+
# Check if a file has a compressed extention in the filename.
|
72
|
+
# If recognized, return the command string used to decompress the file
|
73
|
+
def decompress_file?(filename)
|
74
|
+
nice_command = "nice -n 5"
|
75
|
+
|
76
|
+
return "#{nice_command} gunzip -c -d #{filename}" if filename.match(/\.tar.gz$/) || filename.match(/\.tgz$/) || filename.match(/\.gz$/)
|
77
|
+
return "#{nice_command} bunzip2 -c -d #{filename}" if filename.match(/\.bz2$/)
|
78
|
+
return "#{nice_command} unzip -p #{filename}" if filename.match(/\.zip$/)
|
79
|
+
|
80
|
+
return ""
|
81
|
+
end
|
70
82
|
|
71
83
|
# Parses a log file. Creates an IO stream for the provided file, and sends it to parse_io for
|
72
84
|
# further handling. This method supports progress updates that can be used to display a progressbar
|
85
|
+
#
|
86
|
+
# If the logfile is compressed, it is uncompressed to stdout and read.
|
87
|
+
# TODO: Check if IO.popen encounters problems with the given command line.
|
88
|
+
# TODO: Fix progress bar that is broken for IO.popen, as it returns a single string.
|
89
|
+
#
|
73
90
|
# <tt>file</tt>:: The file that should be parsed.
|
74
91
|
# <tt>options</tt>:: A Hash of options that will be pased to parse_io.
|
75
92
|
def parse_file(file, options = {}, &block)
|
93
|
+
|
76
94
|
@progress_handler.call(:started, file) if @progress_handler
|
77
|
-
|
95
|
+
|
96
|
+
if decompress_file?(file).empty?
|
97
|
+
File.open(file, 'r') { |f| parse_io(f, options, &block) }
|
98
|
+
else
|
99
|
+
IO.popen(decompress_file?(file), 'r') { |f| parse_io(f, options, &block) }
|
100
|
+
end
|
101
|
+
|
78
102
|
@progress_handler.call(:finished, file) if @progress_handler
|
79
103
|
end
|
80
104
|
|
81
|
-
|
82
105
|
# Parses an IO stream. It will simply call parse_io. This function does not support progress updates
|
83
106
|
# because the length of a stream is not known.
|
84
107
|
# <tt>stream</tt>:: The IO stream that should be parsed.
|
@@ -103,7 +126,7 @@ module RequestLogAnalyzer::Source
|
|
103
126
|
|
104
127
|
@current_io = io
|
105
128
|
@current_io.each_line do |line|
|
106
|
-
|
129
|
+
|
107
130
|
@progress_handler.call(:progress, @current_io.pos) if @progress_handler && @current_io.kind_of?(File)
|
108
131
|
|
109
132
|
request_data = nil
|
@@ -2,13 +2,14 @@ module RequestLogAnalyzer::Tracker
|
|
2
2
|
|
3
3
|
# Analyze the duration of a specific attribute
|
4
4
|
#
|
5
|
-
# Options
|
6
|
-
# * <tt>:
|
7
|
-
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
8
|
-
# * <tt>:title</tt> Title do be displayed above the report
|
5
|
+
# === Options
|
6
|
+
# * <tt>:amount</tt> The amount of lines in the report
|
9
7
|
# * <tt>:category</tt> Proc that handles request categorization for given fileformat (REQUEST_CATEGORIZER)
|
10
8
|
# * <tt>:duration</tt> The field containing the duration in the request hash.
|
11
|
-
# * <tt>:
|
9
|
+
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
10
|
+
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
11
|
+
# * <tt>:title</tt> Title do be displayed above the report
|
12
|
+
# * <tt>:unless</tt> Handle request if this proc is false for the handled request.
|
12
13
|
#
|
13
14
|
# The items in the update request hash are set during the creation of the Duration tracker.
|
14
15
|
#
|
@@ -23,6 +24,7 @@ module RequestLogAnalyzer::Tracker
|
|
23
24
|
|
24
25
|
attr_reader :categories
|
25
26
|
|
27
|
+
# Check if duration and catagory option have been received,
|
26
28
|
def prepare
|
27
29
|
raise "No duration field set up for category tracker #{self.inspect}" unless options[:duration]
|
28
30
|
raise "No categorizer set up for duration tracker #{self.inspect}" unless options[:category]
|
@@ -30,6 +32,8 @@ module RequestLogAnalyzer::Tracker
|
|
30
32
|
@categories = {}
|
31
33
|
end
|
32
34
|
|
35
|
+
# Get the duration information fron the request and store it in the different categories.
|
36
|
+
# <tt>request</tt> The request.
|
33
37
|
def update(request)
|
34
38
|
if options[:multiple]
|
35
39
|
categories = request.every(options[:category])
|
@@ -58,50 +62,68 @@ module RequestLogAnalyzer::Tracker
|
|
58
62
|
end
|
59
63
|
end
|
60
64
|
|
65
|
+
# Get the number of hits of a specific category.
|
66
|
+
# <tt>cat</tt> The category
|
61
67
|
def hits(cat)
|
62
68
|
categories[cat][:hits]
|
63
69
|
end
|
64
70
|
|
71
|
+
# Get the total duration of a specific category.
|
72
|
+
# <tt>cat</tt> The category
|
65
73
|
def cumulative_duration(cat)
|
66
74
|
categories[cat][:cumulative]
|
67
75
|
end
|
68
76
|
|
77
|
+
# Get the minimal duration of a specific category.
|
78
|
+
# <tt>cat</tt> The category
|
69
79
|
def min_duration(cat)
|
70
80
|
categories[cat][:min]
|
71
81
|
end
|
72
82
|
|
83
|
+
# Get the maximum duration of a specific category.
|
84
|
+
# <tt>cat</tt> The category
|
73
85
|
def max_duration(cat)
|
74
86
|
categories[cat][:max]
|
75
87
|
end
|
76
88
|
|
89
|
+
# Get the average duration of a specific category.
|
90
|
+
# <tt>cat</tt> The category
|
77
91
|
def average_duration(cat)
|
78
92
|
categories[cat][:cumulative] / categories[cat][:hits]
|
79
93
|
end
|
80
94
|
|
95
|
+
# Get the average duration of a all categories.
|
81
96
|
def overall_average_duration
|
82
97
|
overall_cumulative_duration / overall_hits
|
83
98
|
end
|
84
99
|
|
100
|
+
# Get the cumlative duration of a all categories.
|
85
101
|
def overall_cumulative_duration
|
86
102
|
categories.inject(0.0) { |sum, (name, cat)| sum + cat[:cumulative] }
|
87
103
|
end
|
88
104
|
|
105
|
+
# Get the total hits of a all categories.
|
89
106
|
def overall_hits
|
90
107
|
categories.inject(0) { |sum, (name, cat)| sum + cat[:hits] }
|
91
108
|
end
|
92
109
|
|
110
|
+
# Return categories sorted by hits.
|
93
111
|
def sorted_by_hits
|
94
112
|
sorted_by(:hits)
|
95
113
|
end
|
96
114
|
|
115
|
+
# Return categories sorted by cumulative duration.
|
97
116
|
def sorted_by_cumulative
|
98
117
|
sorted_by(:cumulative)
|
99
118
|
end
|
100
119
|
|
120
|
+
# Return categories sorted by cumulative duration.
|
101
121
|
def sorted_by_average
|
102
122
|
sorted_by { |cat| cat[:cumulative] / cat[:hits] }
|
103
123
|
end
|
104
124
|
|
125
|
+
# Return categories sorted by a given key.
|
126
|
+
# <tt>by</tt> The key.
|
105
127
|
def sorted_by(by = nil)
|
106
128
|
if block_given?
|
107
129
|
categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) }
|
@@ -110,7 +132,12 @@ module RequestLogAnalyzer::Tracker
|
|
110
132
|
end
|
111
133
|
end
|
112
134
|
|
113
|
-
#
|
135
|
+
# Block function to build a result table using a provided sorting function.
|
136
|
+
# <tt>output</tt> The output object.
|
137
|
+
# <tt>amount</tt> The number of rows in the report table (default 10).
|
138
|
+
# === Options
|
139
|
+
# * </tt>:title</tt> The title of the table
|
140
|
+
# * </tt>:sort</tt> The key to sort on (:hits, :cumulative, :average, :min or :max)
|
114
141
|
def report_table(output, amount = 10, options = {}, &block)
|
115
142
|
|
116
143
|
output.title(options[:title])
|
@@ -128,9 +155,12 @@ module RequestLogAnalyzer::Tracker
|
|
128
155
|
"%0.02fs" % info[:min], "%0.02fs" % info[:max]]
|
129
156
|
end
|
130
157
|
end
|
131
|
-
|
132
158
|
end
|
133
159
|
|
160
|
+
# Generate a request duration report to the given output object
|
161
|
+
# By default colulative and average duration are generated.
|
162
|
+
# Any options for the report should have been set during initialize.
|
163
|
+
# <tt>output</tt> The output object
|
134
164
|
def report(output)
|
135
165
|
|
136
166
|
options[:title] ||= 'Request duration'
|
@@ -149,6 +179,17 @@ module RequestLogAnalyzer::Tracker
|
|
149
179
|
raise "Unknown duration report specified: #{report}!"
|
150
180
|
end
|
151
181
|
end
|
152
|
-
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Returns the title of this tracker for reports
|
185
|
+
def title
|
186
|
+
options[:title] || 'Request duration'
|
187
|
+
end
|
188
|
+
|
189
|
+
# Returns all the categories and the tracked duration as a hash than can be exported to YAML
|
190
|
+
def to_yaml_object
|
191
|
+
return nil if @categories.empty?
|
192
|
+
@categories
|
193
|
+
end
|
153
194
|
end
|
154
195
|
end
|
@@ -3,26 +3,29 @@ module RequestLogAnalyzer::Tracker
|
|
3
3
|
# Catagorize requests by frequency.
|
4
4
|
# Count and analyze requests for a specific attribute
|
5
5
|
#
|
6
|
-
#
|
7
|
-
# * <tt>:
|
6
|
+
# === Options
|
7
|
+
# * <tt>:amount</tt> The amount of lines in the report
|
8
|
+
# * <tt>:category</tt> Proc that handles the request categorization.
|
8
9
|
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
10
|
+
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
11
|
+
# * <tt>:nils</tt> Track undetermined methods.
|
9
12
|
# * <tt>:title</tt> Title do be displayed above the report.
|
10
|
-
# * <tt>:
|
11
|
-
# * <tt>:amount</tt> The amount of lines in the report
|
13
|
+
# * <tt>:unless</tt> Proc that has to return nil for a request to be passed to the tracker.
|
12
14
|
#
|
13
15
|
# The items in the update request hash are set during the creation of the Duration tracker.
|
14
16
|
#
|
15
17
|
# Example output:
|
16
18
|
# HTTP methods
|
17
19
|
# ----------------------------------------------------------------------
|
18
|
-
# GET | 22248 hits (46.2%)
|
19
|
-
# PUT | 13685 hits (28.4%)
|
20
|
-
# POST | 11662 hits (24.2%)
|
20
|
+
# GET | 22248 hits (46.2%) |=================
|
21
|
+
# PUT | 13685 hits (28.4%) |===========
|
22
|
+
# POST | 11662 hits (24.2%) |=========
|
21
23
|
# DELETE | 512 hits (1.1%) |
|
22
24
|
class Frequency < Base
|
23
25
|
|
24
26
|
attr_reader :frequencies
|
25
27
|
|
28
|
+
# Check if categories are set up
|
26
29
|
def prepare
|
27
30
|
raise "No categorizer set up for category tracker #{self.inspect}" unless options[:category]
|
28
31
|
@frequencies = {}
|
@@ -31,6 +34,8 @@ module RequestLogAnalyzer::Tracker
|
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
37
|
+
# Check HTTP method of a request and store that in the frequencies hash.
|
38
|
+
# <tt>request</tt> The request.
|
34
39
|
def update(request)
|
35
40
|
cat = options[:category].respond_to?(:call) ? options[:category].call(request) : request[options[:category]]
|
36
41
|
if !cat.nil? || options[:nils]
|
@@ -39,18 +44,25 @@ module RequestLogAnalyzer::Tracker
|
|
39
44
|
end
|
40
45
|
end
|
41
46
|
|
47
|
+
# Return the amount of times a HTTP method has been encountered
|
48
|
+
# <tt>cat</tt> The HTTP method (:get, :put, :post or :delete)
|
42
49
|
def frequency(cat)
|
43
50
|
frequencies[cat] || 0
|
44
51
|
end
|
45
52
|
|
53
|
+
# Return the overall frequency
|
46
54
|
def overall_frequency
|
47
55
|
frequencies.inject(0) { |carry, item| carry + item[1] }
|
48
56
|
end
|
49
57
|
|
58
|
+
# Return the methods sorted by frequency
|
50
59
|
def sorted_by_frequency
|
51
60
|
@frequencies.sort { |a, b| b[1] <=> a[1] }
|
52
61
|
end
|
53
62
|
|
63
|
+
# Generate a HTTP method frequency report to the given output object.
|
64
|
+
# Any options for the report should have been set during initialize.
|
65
|
+
# <tt>output</tt> The output object
|
54
66
|
def report(output)
|
55
67
|
output.title(options[:title]) if options[:title]
|
56
68
|
|
@@ -69,5 +81,16 @@ module RequestLogAnalyzer::Tracker
|
|
69
81
|
|
70
82
|
end
|
71
83
|
end
|
84
|
+
|
85
|
+
# Returns a hash with the frequencies of every category that can be exported to YAML
|
86
|
+
def to_yaml_object
|
87
|
+
return nil if @frequencies.empty?
|
88
|
+
@frequencies
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns the title of this tracker for reports
|
92
|
+
def title
|
93
|
+
options[:title] || 'Request frequency'
|
94
|
+
end
|
72
95
|
end
|
73
96
|
end
|
@@ -4,8 +4,10 @@ module RequestLogAnalyzer::Tracker
|
|
4
4
|
# This spread is shown in a graph form.
|
5
5
|
#
|
6
6
|
# Accepts the following options:
|
7
|
-
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
8
7
|
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
8
|
+
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
9
|
+
# * <tt>:output</tt> Direct output here (defaults to STDOUT)
|
10
|
+
# * <tt>:unless</tt> Proc that has to return nil for a request to be passed to the tracker.
|
9
11
|
#
|
10
12
|
# Expects the following items in the update request hash
|
11
13
|
# * <tt>:timestamp</tt> in YYYYMMDDHHMMSS format.
|
@@ -13,28 +15,31 @@ module RequestLogAnalyzer::Tracker
|
|
13
15
|
# Example output:
|
14
16
|
# Requests graph - average per day per hour
|
15
17
|
# --------------------------------------------------
|
16
|
-
# 7:00 - 330 hits :
|
17
|
-
# 8:00 - 704 hits :
|
18
|
-
# 9:00 - 830 hits :
|
19
|
-
# 10:00 - 822 hits :
|
20
|
-
# 11:00 - 823 hits :
|
21
|
-
# 12:00 - 729 hits :
|
22
|
-
# 13:00 - 614 hits :
|
23
|
-
# 14:00 - 690 hits :
|
24
|
-
# 15:00 - 492 hits :
|
25
|
-
# 16:00 - 355 hits :
|
26
|
-
# 17:00 - 213 hits :
|
27
|
-
# 18:00 - 107 hits :
|
18
|
+
# 7:00 - 330 hits : =======
|
19
|
+
# 8:00 - 704 hits : =================
|
20
|
+
# 9:00 - 830 hits : ====================
|
21
|
+
# 10:00 - 822 hits : ===================
|
22
|
+
# 11:00 - 823 hits : ===================
|
23
|
+
# 12:00 - 729 hits : =================
|
24
|
+
# 13:00 - 614 hits : ==============
|
25
|
+
# 14:00 - 690 hits : ================
|
26
|
+
# 15:00 - 492 hits : ===========
|
27
|
+
# 16:00 - 355 hits : ========
|
28
|
+
# 17:00 - 213 hits : =====
|
29
|
+
# 18:00 - 107 hits : ==
|
28
30
|
# ................
|
29
31
|
class HourlySpread < Base
|
30
32
|
|
31
33
|
attr_reader :first, :last, :request_time_graph
|
32
34
|
|
35
|
+
# Check if timestamp field is set in the options and prepare the result time graph.
|
33
36
|
def prepare
|
34
37
|
options[:field] ||= :timestamp
|
35
38
|
@request_time_graph = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
36
39
|
end
|
37
|
-
|
40
|
+
|
41
|
+
# Check if the timestamp in the request and store it.
|
42
|
+
# <tt>request</tt> The request.
|
38
43
|
def update(request)
|
39
44
|
request = request.attributes
|
40
45
|
timestamp = request[options[:field]]
|
@@ -44,24 +49,31 @@ module RequestLogAnalyzer::Tracker
|
|
44
49
|
@last = timestamp if @last.nil? || timestamp > @last
|
45
50
|
end
|
46
51
|
|
52
|
+
# Total amount of requests tracked
|
47
53
|
def total_requests
|
48
54
|
@request_time_graph.inject(0) { |sum, value| sum + value }
|
49
55
|
end
|
50
56
|
|
57
|
+
# First timestamp encountered
|
51
58
|
def first_timestamp
|
52
59
|
DateTime.parse(@first.to_s, '%Y%m%d%H%M%S') rescue nil
|
53
60
|
end
|
54
61
|
|
62
|
+
# Last timestamp encountered
|
55
63
|
def last_timestamp
|
56
64
|
DateTime.parse(@last.to_s, '%Y%m%d%H%M%S') rescue nil
|
57
65
|
end
|
58
66
|
|
67
|
+
# Difference between last and first timestamp.
|
59
68
|
def timespan
|
60
69
|
last_timestamp - first_timestamp
|
61
70
|
end
|
62
71
|
|
72
|
+
# Generate an hourly spread report to the given output object.
|
73
|
+
# Any options for the report should have been set during initialize.
|
74
|
+
# <tt>output</tt> The output object
|
63
75
|
def report(output)
|
64
|
-
output.title(
|
76
|
+
output.title(title)
|
65
77
|
|
66
78
|
if total_requests == 0
|
67
79
|
output << "None found.\n"
|
@@ -77,5 +89,19 @@ module RequestLogAnalyzer::Tracker
|
|
77
89
|
end
|
78
90
|
end
|
79
91
|
end
|
92
|
+
|
93
|
+
# Returns the title of this tracker for reports
|
94
|
+
def title
|
95
|
+
options[:title] || "Request distribution per hour"
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the found frequencies per hour as a hash for YAML exporting
|
99
|
+
def to_yaml_object
|
100
|
+
yaml_object = {}
|
101
|
+
@request_time_graph.each_with_index do |freq, hour|
|
102
|
+
yaml_object["#{hour}:00 - #{hour+1}:00"] = freq
|
103
|
+
end
|
104
|
+
yaml_object
|
105
|
+
end
|
80
106
|
end
|
81
107
|
end
|
@@ -4,10 +4,11 @@ module RequestLogAnalyzer::Tracker
|
|
4
4
|
# Also determines the amount of days inbetween these.
|
5
5
|
#
|
6
6
|
# Accepts the following options:
|
7
|
-
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
8
|
-
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
9
7
|
# * <tt>:field</tt> The timestamp field that is looked at. Defaults to :timestamp.
|
8
|
+
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
9
|
+
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
10
10
|
# * <tt>:title</tt> Title do be displayed above the report.
|
11
|
+
# * <tt>:unless</tt> Proc that has to return nil for a request to be passed to the tracker.
|
11
12
|
#
|
12
13
|
# Expects the following items in the update request hash
|
13
14
|
# * <tt>:timestamp</tt> in YYYYMMDDHHMMSS format.
|
@@ -20,10 +21,13 @@ module RequestLogAnalyzer::Tracker
|
|
20
21
|
|
21
22
|
attr_reader :first, :last, :request_time_graph
|
22
23
|
|
24
|
+
# Check if timestamp field is set in the options.
|
23
25
|
def prepare
|
24
26
|
options[:field] ||= :timestamp
|
25
27
|
end
|
26
28
|
|
29
|
+
# Check if the timestamp in the request and store it.
|
30
|
+
# <tt>request</tt> The request.
|
27
31
|
def update(request)
|
28
32
|
timestamp = request[options[:field]]
|
29
33
|
|
@@ -31,18 +35,24 @@ module RequestLogAnalyzer::Tracker
|
|
31
35
|
@last = timestamp if @last.nil? || timestamp > @last
|
32
36
|
end
|
33
37
|
|
38
|
+
# First timestamp encountered
|
34
39
|
def first_timestamp
|
35
40
|
DateTime.parse(@first.to_s, '%Y%m%d%H%M%S') rescue nil
|
36
41
|
end
|
37
42
|
|
43
|
+
# Last timestamp encountered
|
38
44
|
def last_timestamp
|
39
45
|
DateTime.parse(@last.to_s, '%Y%m%d%H%M%S') rescue nil
|
40
46
|
end
|
41
47
|
|
48
|
+
# Difference between last and first timestamp.
|
42
49
|
def timespan
|
43
50
|
last_timestamp - first_timestamp
|
44
51
|
end
|
45
52
|
|
53
|
+
# Generate an hourly spread report to the given output object.
|
54
|
+
# Any options for the report should have been set during initialize.
|
55
|
+
# <tt>output</tt> The output object
|
46
56
|
def report(output)
|
47
57
|
output.title(options[:title]) if options[:title]
|
48
58
|
|
@@ -55,7 +65,17 @@ module RequestLogAnalyzer::Tracker
|
|
55
65
|
end
|
56
66
|
end
|
57
67
|
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the title of this tracker for reports
|
71
|
+
def title
|
72
|
+
options[:title] || 'Request timespan'
|
73
|
+
end
|
58
74
|
|
75
|
+
# A hash that can be exported to YAML with the first and last timestamp encountered.
|
76
|
+
def to_yaml_object
|
77
|
+
{ :first => first_timestamp, :last =>last_timestamp }
|
59
78
|
end
|
79
|
+
|
60
80
|
end
|
61
81
|
end
|
@@ -9,28 +9,51 @@ module RequestLogAnalyzer::Tracker
|
|
9
9
|
# Base Tracker class. All other trackers inherit from this class
|
10
10
|
#
|
11
11
|
# Accepts the following options:
|
12
|
-
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
13
12
|
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
13
|
+
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
14
14
|
# * <tt>:output</tt> Direct output here (defaults to STDOUT)
|
15
|
+
# * <tt>:unless</tt> Proc that has to return nil for a request to be passed to the tracker.
|
15
16
|
#
|
16
17
|
# For example :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }
|
17
18
|
class Base
|
18
19
|
|
19
20
|
attr_reader :options
|
20
21
|
|
22
|
+
# Initialize the class
|
23
|
+
# Note that the options are only applicable if should_update? is not overwritten
|
24
|
+
# by the inheriting class.
|
25
|
+
#
|
26
|
+
# === Options
|
27
|
+
# * <tt>:if</tt> Handle request if this proc is true for the handled request.
|
28
|
+
# * <tt>:unless</tt> Handle request if this proc is false for the handled request.
|
29
|
+
# * <tt>:line_type</tt> Line type this tracker will accept.
|
21
30
|
def initialize(options ={})
|
22
31
|
@options = options
|
23
32
|
end
|
24
33
|
|
34
|
+
# Hook things that need to be done before running here.
|
25
35
|
def prepare
|
26
36
|
end
|
27
37
|
|
38
|
+
# Will be called with each request.
|
39
|
+
# <tt>request</tt> The request to track data in.
|
28
40
|
def update(request)
|
29
41
|
end
|
30
42
|
|
43
|
+
# Hook things that need to be done after running here.
|
31
44
|
def finalize
|
32
45
|
end
|
33
46
|
|
47
|
+
# Determine if we should run the update function at all.
|
48
|
+
# Usually the update function will be heavy, so a light check is done here
|
49
|
+
# determining if we need to call update at all.
|
50
|
+
#
|
51
|
+
# Default this checks if defined:
|
52
|
+
# * :line_type is also in the request hash.
|
53
|
+
# * :if is true for this request.
|
54
|
+
# * :unless if false for this request
|
55
|
+
#
|
56
|
+
# <tt>request</tt> The request object.
|
34
57
|
def should_update?(request)
|
35
58
|
return false if options[:line_type] && !request.has_line_type?(options[:line_type])
|
36
59
|
|
@@ -49,10 +72,24 @@ module RequestLogAnalyzer::Tracker
|
|
49
72
|
return true
|
50
73
|
end
|
51
74
|
|
75
|
+
# Hook report generation here.
|
76
|
+
# Defaults to self.inspect
|
77
|
+
# <tt>output</tt> The output object the report will be passed to.
|
52
78
|
def report(output)
|
53
79
|
output << self.inspect
|
54
80
|
output << "\n"
|
55
81
|
end
|
82
|
+
|
83
|
+
# The title of this tracker. Used for reporting.
|
84
|
+
def title
|
85
|
+
self.class.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
# This method is called by RequestLogAnalyzer::Aggregator:Summarizer to retrieve an
|
89
|
+
# object with all the results of this tracker, that can be dumped to YAML format.
|
90
|
+
def to_yaml_object
|
91
|
+
nil
|
92
|
+
end
|
56
93
|
|
57
94
|
end
|
58
95
|
end
|
data/lib/request_log_analyzer.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
require 'date'
|
2
2
|
|
3
3
|
# Satisfy ruby 1.9 sensitivity about encoding.
|
4
|
-
if defined? Encoding and Encoding.respond_to? 'default_external='
|
5
|
-
Encoding.default_external = 'binary'
|
6
|
-
end
|
4
|
+
Encoding.default_external = 'binary' if defined? Encoding and Encoding.respond_to? 'default_external='
|
7
5
|
|
8
6
|
# RequestLogAnalyzer is the base namespace in which all functionality of RequestLogAnalyzer is implemented.
|
9
7
|
#
|
@@ -13,7 +11,7 @@ module RequestLogAnalyzer
|
|
13
11
|
|
14
12
|
# The current version of request-log-analyzer.
|
15
13
|
# This will be diplayed in output reports etc.
|
16
|
-
VERSION = '1.1
|
14
|
+
VERSION = '1.2.1'
|
17
15
|
|
18
16
|
# Loads constants in the RequestLogAnalyzer namespace using self.load_default_class_file(base, const)
|
19
17
|
# <tt>const</tt>:: The constant that is not yet loaded in the RequestLogAnalyzer namespace. This should be passed as a string or symbol.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Processing PageController#demo (for 127.0.0.1 at 2008-12-10 16:28:09) [GET]
|
2
|
+
Parameters: {"action"=>"demo", "controller"=>"page"}
|
3
|
+
Logging in from session data...
|
4
|
+
Logged in as test@example.com
|
5
|
+
Using locale: en-US, http-accept: ["en-US"], session: , det browser: en-US, det domain:
|
6
|
+
Rendering template within layouts/demo
|
7
|
+
Rendering page/demo
|
8
|
+
Rendered shared/_analytics (0.2ms)
|
9
|
+
Rendered layouts/_actions (0.6ms)
|
10
|
+
Rendered layouts/_menu (2.2ms)
|
11
|
+
Rendered layouts/_tabbar (0.5ms)
|
12
|
+
Completed in 614ms (View: 120, DB: 31) | 200 OK [http://www.example.coml/demo]
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -3,19 +3,13 @@ require File.dirname(__FILE__) + '/../spec_helper.rb'
|
|
3
3
|
describe RequestLogAnalyzer, 'running from command line' do
|
4
4
|
|
5
5
|
include RequestLogAnalyzer::Spec::Helper
|
6
|
-
|
7
|
-
TEMPORARY_DIRECTORY = "#{File.dirname(__FILE__)}/../fixtures"
|
8
|
-
TEMP_DATABASE_FILE = TEMPORARY_DIRECTORY + "/output.db"
|
9
|
-
TEMP_REPORT_FILE = TEMPORARY_DIRECTORY + "/report"
|
10
6
|
|
11
7
|
before(:each) do
|
12
|
-
|
13
|
-
File.unlink(TEMP_REPORT_FILE) if File.exist?(TEMP_REPORT_FILE)
|
8
|
+
cleanup_temp_files!
|
14
9
|
end
|
15
10
|
|
16
11
|
after(:each) do
|
17
|
-
|
18
|
-
File.unlink(TEMP_REPORT_FILE) if File.exist?(TEMP_REPORT_FILE)
|
12
|
+
cleanup_temp_files!
|
19
13
|
end
|
20
14
|
|
21
15
|
it "should find 4 requests in default mode" do
|
@@ -34,13 +28,13 @@ describe RequestLogAnalyzer, 'running from command line' do
|
|
34
28
|
end
|
35
29
|
|
36
30
|
it "should write output to a file with the --file option" do
|
37
|
-
run("#{log_fixture(:rails_1x)} --file #{
|
38
|
-
File.exist?(
|
31
|
+
run("#{log_fixture(:rails_1x)} --file #{temp_output_file(:report)}")
|
32
|
+
File.exist?(temp_output_file(:report)).should be_true
|
39
33
|
end
|
40
34
|
|
41
35
|
it "should write only ASCII characters to a file with the --file option" do
|
42
|
-
run("#{log_fixture(:rails_1x)} --file #{
|
43
|
-
/^[\x00-\x7F]*$/.match(File.read(
|
36
|
+
run("#{log_fixture(:rails_1x)} --file #{temp_output_file(:report)}")
|
37
|
+
/^[\x00-\x7F]*$/.match(File.read(temp_output_file(:report))).should_not be_nil
|
44
38
|
end
|
45
39
|
|
46
40
|
it "should write HTML if --output HTML is provided" do
|
@@ -49,8 +43,8 @@ describe RequestLogAnalyzer, 'running from command line' do
|
|
49
43
|
end
|
50
44
|
|
51
45
|
it "should run with the --database option" do
|
52
|
-
run("#{log_fixture(:rails_1x)} --database #{
|
53
|
-
File.exist?(
|
46
|
+
run("#{log_fixture(:rails_1x)} --database #{temp_output_file(:database)}")
|
47
|
+
File.exist?(temp_output_file(:database)).should be_true
|
54
48
|
end
|
55
49
|
|
56
50
|
it "should use no colors in the report with the --boring option" do
|
@@ -68,4 +62,10 @@ describe RequestLogAnalyzer, 'running from command line' do
|
|
68
62
|
output.detect { |line| /Parsed requests\:\s*11/ =~ line }.should_not be_nil
|
69
63
|
end
|
70
64
|
|
65
|
+
it "should dump the results to a YAML file" do
|
66
|
+
run("#{log_fixture(:rails_1x)} --dump #{temp_output_file(:dump)}")
|
67
|
+
File.exist?(temp_output_file(:dump)).should be_true
|
68
|
+
YAML::load(File.read(temp_output_file(:dump))).should have_at_least(1).item
|
69
|
+
end
|
70
|
+
|
71
71
|
end
|