request-log-analyzer 1.2.0 → 1.2.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/{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
|