request-log-analyzer 1.3.5 → 1.4.0
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/LICENSE +3 -3
- data/README.rdoc +1 -1
- data/bin/request-log-analyzer +17 -14
- data/lib/cli/command_line_arguments.rb +51 -51
- data/lib/cli/database_console.rb +3 -3
- data/lib/cli/database_console_init.rb +4 -3
- data/lib/cli/progressbar.rb +10 -10
- data/lib/cli/tools.rb +3 -3
- data/lib/request_log_analyzer/aggregator/database_inserter.rb +13 -14
- data/lib/request_log_analyzer/aggregator/echo.rb +14 -9
- data/lib/request_log_analyzer/aggregator/summarizer.rb +26 -26
- data/lib/request_log_analyzer/aggregator.rb +11 -15
- data/lib/request_log_analyzer/controller.rb +162 -89
- data/lib/request_log_analyzer/database/base.rb +21 -21
- data/lib/request_log_analyzer/database/connection.rb +3 -3
- data/lib/request_log_analyzer/database/request.rb +22 -0
- data/lib/request_log_analyzer/database/source.rb +13 -0
- data/lib/request_log_analyzer/database/warning.rb +14 -0
- data/lib/request_log_analyzer/database.rb +28 -103
- data/lib/request_log_analyzer/file_format/amazon_s3.rb +17 -18
- data/lib/request_log_analyzer/file_format/apache.rb +26 -27
- data/lib/request_log_analyzer/file_format/merb.rb +30 -13
- data/lib/request_log_analyzer/file_format/rack.rb +4 -4
- data/lib/request_log_analyzer/file_format/rails.rb +143 -73
- data/lib/request_log_analyzer/file_format/rails_development.rb +4 -49
- data/lib/request_log_analyzer/file_format.rb +16 -28
- data/lib/request_log_analyzer/filter/anonymize.rb +8 -7
- data/lib/request_log_analyzer/filter/field.rb +9 -9
- data/lib/request_log_analyzer/filter/timespan.rb +12 -10
- data/lib/request_log_analyzer/filter.rb +12 -16
- data/lib/request_log_analyzer/line_definition.rb +15 -14
- data/lib/request_log_analyzer/log_processor.rb +27 -29
- data/lib/request_log_analyzer/mailer.rb +15 -9
- data/lib/request_log_analyzer/output/fixed_width.rb +40 -41
- data/lib/request_log_analyzer/output/html.rb +20 -20
- data/lib/request_log_analyzer/output.rb +53 -12
- data/lib/request_log_analyzer/request.rb +75 -68
- data/lib/request_log_analyzer/source/database_loader.rb +10 -14
- data/lib/request_log_analyzer/source/log_parser.rb +57 -50
- data/lib/request_log_analyzer/source.rb +10 -12
- data/lib/request_log_analyzer/tracker/duration.rb +39 -132
- data/lib/request_log_analyzer/tracker/frequency.rb +31 -32
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +21 -21
- data/lib/request_log_analyzer/tracker/timespan.rb +17 -17
- data/lib/request_log_analyzer/tracker/traffic.rb +36 -116
- data/lib/request_log_analyzer/tracker.rb +139 -32
- data/lib/request_log_analyzer.rb +4 -4
- data/request-log-analyzer.gemspec +19 -15
- data/spec/database.yml +6 -0
- data/spec/fixtures/rails_22.log +1 -1
- data/spec/integration/command_line_usage_spec.rb +7 -1
- data/spec/lib/helpers.rb +7 -7
- data/spec/lib/macros.rb +3 -3
- data/spec/lib/matchers.rb +41 -27
- data/spec/lib/mocks.rb +15 -14
- data/spec/lib/testing_format.rb +9 -9
- data/spec/spec_helper.rb +6 -6
- data/spec/unit/aggregator/database_inserter_spec.rb +16 -16
- data/spec/unit/aggregator/summarizer_spec.rb +4 -4
- data/spec/unit/controller/controller_spec.rb +2 -2
- data/spec/unit/controller/log_processor_spec.rb +1 -1
- data/spec/unit/database/base_class_spec.rb +26 -33
- data/spec/unit/database/connection_spec.rb +3 -3
- data/spec/unit/database/database_spec.rb +33 -38
- data/spec/unit/file_format/amazon_s3_format_spec.rb +5 -5
- data/spec/unit/file_format/apache_format_spec.rb +13 -13
- data/spec/unit/file_format/file_format_api_spec.rb +13 -13
- data/spec/unit/file_format/line_definition_spec.rb +24 -17
- data/spec/unit/file_format/merb_format_spec.rb +41 -45
- data/spec/unit/file_format/rails_format_spec.rb +157 -117
- data/spec/unit/filter/anonymize_filter_spec.rb +2 -2
- data/spec/unit/filter/field_filter_spec.rb +13 -13
- data/spec/unit/filter/filter_spec.rb +1 -1
- data/spec/unit/filter/timespan_filter_spec.rb +15 -15
- data/spec/unit/mailer_spec.rb +30 -0
- data/spec/unit/{source/request_spec.rb → request_spec.rb} +30 -30
- data/spec/unit/source/log_parser_spec.rb +27 -27
- data/spec/unit/tracker/duration_tracker_spec.rb +115 -78
- data/spec/unit/tracker/frequency_tracker_spec.rb +74 -63
- data/spec/unit/tracker/hourly_spread_spec.rb +28 -20
- data/spec/unit/tracker/timespan_tracker_spec.rb +25 -13
- data/spec/unit/tracker/tracker_api_spec.rb +117 -42
- data/spec/unit/tracker/traffic_tracker_spec.rb +81 -79
- data/tasks/github-gem.rake +125 -75
- data/tasks/request_log_analyzer.rake +2 -2
- metadata +13 -8
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# The RequestLogAnalyzer::Source module contains all functionality that loads requests from a given source
|
|
2
2
|
# and feed them to the pipeline for further processing. The requests (see RequestLogAnalyzer::Request) that
|
|
3
|
-
# will be parsed from a source, will be piped throug filters (see RequestLogAnalyzer::Filter) and are then
|
|
4
|
-
# fed to an aggregator (see RequestLogAnalyzer::Aggregator). The source instance is thus the beginning of
|
|
3
|
+
# will be parsed from a source, will be piped throug filters (see RequestLogAnalyzer::Filter) and are then
|
|
4
|
+
# fed to an aggregator (see RequestLogAnalyzer::Aggregator). The source instance is thus the beginning of
|
|
5
5
|
# the RequestLogAnalyzer chain.
|
|
6
6
|
#
|
|
7
7
|
# - The base class for all sources is RequestLogAnalyzer::Source::Base. All source classes should inherit from this class.
|
|
@@ -14,15 +14,12 @@ module RequestLogAnalyzer::Source
|
|
|
14
14
|
def self.const_missing(const)
|
|
15
15
|
RequestLogAnalyzer::load_default_class_file(self, const)
|
|
16
16
|
end
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
# The base Source class. All other sources should inherit from this class.
|
|
19
19
|
#
|
|
20
20
|
# A source implememtation should at least implement the each_request method, which should yield
|
|
21
21
|
# RequestLogAnalyzer::Request instances that will be fed through the pipleine.
|
|
22
22
|
class Base
|
|
23
|
-
|
|
24
|
-
# Make the Spurce instance aware of the current file format
|
|
25
|
-
include RequestLogAnalyzer::FileFormat::Awareness
|
|
26
23
|
|
|
27
24
|
# A hash of options
|
|
28
25
|
attr_reader :options
|
|
@@ -42,23 +39,24 @@ module RequestLogAnalyzer::Source
|
|
|
42
39
|
# The total number of skipped requests because of filters.
|
|
43
40
|
attr_reader :skipped_requests
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
# The FileFormat instance that describes the format of this source.
|
|
43
|
+
attr_reader :file_format
|
|
46
44
|
|
|
47
45
|
# Initializer, which will register the file format and save any options given as a hash.
|
|
48
46
|
# <tt>format</tt>:: The file format instance
|
|
49
47
|
# <tt>options</tt>:: A hash of options that can be used by a specific Source implementation
|
|
50
48
|
def initialize(format, options = {})
|
|
51
|
-
@options
|
|
52
|
-
|
|
49
|
+
@options = options
|
|
50
|
+
@file_format = format
|
|
53
51
|
end
|
|
54
|
-
|
|
52
|
+
|
|
55
53
|
# The prepare method is called before the RequestLogAnalyzer::Source::Base#each_request method is called.
|
|
56
54
|
# Use this method to implement any initialization that should occur before this source can produce Request
|
|
57
55
|
# instances.
|
|
58
56
|
def prepare
|
|
59
57
|
end
|
|
60
|
-
|
|
61
|
-
# This function is called to actually produce the requests that will be send into the pipeline.
|
|
58
|
+
|
|
59
|
+
# This function is called to actually produce the requests that will be send into the pipeline.
|
|
62
60
|
# The implementation should yield instances of RequestLogAnalyzer::Request.
|
|
63
61
|
# <tt>options</tt>:: A Hash of options that can be used in the implementation.
|
|
64
62
|
def each_request(options = {}, &block) # :yields: request
|
|
@@ -3,12 +3,11 @@ module RequestLogAnalyzer::Tracker
|
|
|
3
3
|
# Analyze the duration of a specific attribute
|
|
4
4
|
#
|
|
5
5
|
# === Options
|
|
6
|
-
# * <tt>:amount</tt> The amount of lines in the report
|
|
7
6
|
# * <tt>:category</tt> Proc that handles request categorization for given fileformat (REQUEST_CATEGORIZER)
|
|
8
7
|
# * <tt>:duration</tt> The field containing the duration in the request hash.
|
|
9
8
|
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
|
10
9
|
# * <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
|
|
10
|
+
# * <tt>:title</tt> Title do be displayed above the report
|
|
12
11
|
# * <tt>:unless</tt> Handle request if this proc is false for the handled request.
|
|
13
12
|
#
|
|
14
13
|
# The items in the update request hash are set during the creation of the Duration tracker.
|
|
@@ -21,141 +20,62 @@ module RequestLogAnalyzer::Tracker
|
|
|
21
20
|
# EmployeeController#index.html [GET] | 5802 | 1477.32s | 0.25s
|
|
22
21
|
# .............
|
|
23
22
|
class Duration < Base
|
|
24
|
-
|
|
23
|
+
|
|
24
|
+
include RequestLogAnalyzer::Tracker::StatisticsTracking
|
|
25
|
+
|
|
25
26
|
attr_reader :categories
|
|
26
27
|
|
|
27
28
|
# Check if duration and catagory option have been received,
|
|
28
29
|
def prepare
|
|
29
30
|
raise "No duration field set up for category tracker #{self.inspect}" unless options[:duration]
|
|
30
|
-
raise "No categorizer set up for duration tracker #{self.inspect}"
|
|
31
|
+
raise "No categorizer set up for duration tracker #{self.inspect}" unless options[:category]
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
unless options[:multiple]
|
|
34
|
+
@categorizer = create_lambda(options[:category])
|
|
35
|
+
@durationizer = create_lambda(options[:duration])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@categories = {}
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
# Get the duration information fron the request and store it in the different categories.
|
|
38
42
|
# <tt>request</tt> The request.
|
|
39
43
|
def update(request)
|
|
40
44
|
if options[:multiple]
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
categories.each_with_index do |category, index|
|
|
46
|
-
@categories[category] ||= {:hits => 0, :cumulative => 0.0}
|
|
47
|
-
@categories[category][:hits] += 1
|
|
48
|
-
@categories[category][:cumulative] += durations[index]
|
|
49
|
-
end
|
|
50
|
-
else
|
|
51
|
-
raise "Capture mismatch for multiple values in a request"
|
|
52
|
-
end
|
|
45
|
+
found_categories = request.every(options[:category])
|
|
46
|
+
found_durations = request.every(options[:duration])
|
|
47
|
+
raise "Capture mismatch for multiple values in a request" unless found_categories.length == found_durations.length
|
|
48
|
+
found_categories.each_with_index { |cat, index| update_statistics(cat, found_durations[index]) }
|
|
53
49
|
else
|
|
54
50
|
category = @categorizer.call(request)
|
|
55
51
|
duration = @durationizer.call(request)
|
|
56
|
-
|
|
57
|
-
if duration.kind_of?(Numeric) && !category.nil?
|
|
58
|
-
@categories[category] ||= {:hits => 0, :cumulative => 0.0, :min => duration, :max => duration }
|
|
59
|
-
@categories[category][:hits] += 1
|
|
60
|
-
@categories[category][:cumulative] += duration
|
|
61
|
-
@categories[category][:min] = duration if duration < @categories[category][:min]
|
|
62
|
-
@categories[category][:max] = duration if duration > @categories[category][:max]
|
|
63
|
-
end
|
|
52
|
+
update_statistics(category, duration) if duration.kind_of?(Numeric) && category
|
|
64
53
|
end
|
|
65
54
|
end
|
|
66
|
-
|
|
67
|
-
# Get the number of hits of a specific category.
|
|
68
|
-
# <tt>cat</tt> The category
|
|
69
|
-
def hits(cat)
|
|
70
|
-
categories[cat][:hits]
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Get the total duration of a specific category.
|
|
74
|
-
# <tt>cat</tt> The category
|
|
75
|
-
def cumulative_duration(cat)
|
|
76
|
-
categories[cat][:cumulative]
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Get the minimal duration of a specific category.
|
|
80
|
-
# <tt>cat</tt> The category
|
|
81
|
-
def min_duration(cat)
|
|
82
|
-
categories[cat][:min]
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Get the maximum duration of a specific category.
|
|
86
|
-
# <tt>cat</tt> The category
|
|
87
|
-
def max_duration(cat)
|
|
88
|
-
categories[cat][:max]
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Get the average duration of a specific category.
|
|
92
|
-
# <tt>cat</tt> The category
|
|
93
|
-
def average_duration(cat)
|
|
94
|
-
categories[cat][:cumulative] / categories[cat][:hits]
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Get the average duration of a all categories.
|
|
98
|
-
def overall_average_duration
|
|
99
|
-
overall_cumulative_duration / overall_hits
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Get the cumlative duration of a all categories.
|
|
103
|
-
def overall_cumulative_duration
|
|
104
|
-
categories.inject(0.0) { |sum, (name, cat)| sum + cat[:cumulative] }
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Get the total hits of a all categories.
|
|
108
|
-
def overall_hits
|
|
109
|
-
categories.inject(0) { |sum, (name, cat)| sum + cat[:hits] }
|
|
110
|
-
end
|
|
111
55
|
|
|
112
|
-
# Return categories sorted by hits.
|
|
113
|
-
def sorted_by_hits
|
|
114
|
-
sorted_by(:hits)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Return categories sorted by cumulative duration.
|
|
118
|
-
def sorted_by_cumulative
|
|
119
|
-
sorted_by(:cumulative)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# Return categories sorted by cumulative duration.
|
|
123
|
-
def sorted_by_average
|
|
124
|
-
sorted_by { |cat| cat[:cumulative] / cat[:hits] }
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Return categories sorted by a given key.
|
|
128
|
-
# <tt>by</tt> The key.
|
|
129
|
-
def sorted_by(by = nil)
|
|
130
|
-
if block_given?
|
|
131
|
-
categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) }
|
|
132
|
-
else
|
|
133
|
-
categories.sort { |a, b| b[1][by] <=> a[1][by] }
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
56
|
# Block function to build a result table using a provided sorting function.
|
|
138
57
|
# <tt>output</tt> The output object.
|
|
139
58
|
# <tt>amount</tt> The number of rows in the report table (default 10).
|
|
140
59
|
# === Options
|
|
141
60
|
# * </tt>:title</tt> The title of the table
|
|
142
61
|
# * </tt>:sort</tt> The key to sort on (:hits, :cumulative, :average, :min or :max)
|
|
143
|
-
def report_table(output,
|
|
144
|
-
|
|
145
|
-
output.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
62
|
+
def report_table(output, sort, options = {}, &block)
|
|
63
|
+
output.puts
|
|
64
|
+
top_categories = output.slice_results(sorted_by(sort))
|
|
65
|
+
output.with_style(:top_line => true) do
|
|
66
|
+
output.table(*statistics_header(:title => options[:title], :highlight => sort)) do |rows|
|
|
67
|
+
top_categories.each { |(cat, info)| rows << statistics_row(cat) }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Display a duration
|
|
73
|
+
def display_value(time)
|
|
74
|
+
case time
|
|
75
|
+
when nil then '-'
|
|
76
|
+
when 0...60 then "%0.02fs" % time
|
|
77
|
+
when 60...3600 then "%dm%02ds" % [time / 60, (time % 60).round]
|
|
78
|
+
else "%dh%02dm%02ds" % [time / 3600, (time % 3600) / 60, (time % 60).round]
|
|
159
79
|
end
|
|
160
80
|
end
|
|
161
81
|
|
|
@@ -164,33 +84,20 @@ module RequestLogAnalyzer::Tracker
|
|
|
164
84
|
# Any options for the report should have been set during initialize.
|
|
165
85
|
# <tt>output</tt> The output object
|
|
166
86
|
def report(output)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
options[:top] ||= 20
|
|
171
|
-
|
|
172
|
-
options[:report].each do |report|
|
|
173
|
-
case report
|
|
174
|
-
when :average
|
|
175
|
-
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by average time", :sort => :average) { |cat| cat[:cumulative] / cat[:hits] }
|
|
176
|
-
when :cumulative
|
|
177
|
-
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by cumulative time", :sort => :cumulative) { |cat| cat[:cumulative] }
|
|
178
|
-
when :hits
|
|
179
|
-
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by hits", :sort => :hits) { |cat| cat[:hits] }
|
|
180
|
-
else
|
|
181
|
-
raise "Unknown duration report specified: #{report}!"
|
|
182
|
-
end
|
|
87
|
+
sortings = output.options[:sort] || [:sum, :mean]
|
|
88
|
+
sortings.each do |sorting|
|
|
89
|
+
report_table(output, sorting, :title => "#{title} - by #{sorting}")
|
|
183
90
|
end
|
|
184
91
|
end
|
|
185
|
-
|
|
92
|
+
|
|
186
93
|
# Returns the title of this tracker for reports
|
|
187
94
|
def title
|
|
188
95
|
options[:title] || 'Request duration'
|
|
189
96
|
end
|
|
190
|
-
|
|
97
|
+
|
|
191
98
|
# Returns all the categories and the tracked duration as a hash than can be exported to YAML
|
|
192
99
|
def to_yaml_object
|
|
193
|
-
return nil if @categories.empty?
|
|
100
|
+
return nil if @categories.empty?
|
|
194
101
|
@categories
|
|
195
102
|
end
|
|
196
103
|
end
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Tracker
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# Catagorize requests by frequency.
|
|
4
|
-
# Count and analyze requests for a specific attribute
|
|
4
|
+
# Count and analyze requests for a specific attribute
|
|
5
5
|
#
|
|
6
6
|
# === Options
|
|
7
|
-
# * <tt>:amount</tt> The amount of lines in the report
|
|
8
7
|
# * <tt>:category</tt> Proc that handles the request categorization.
|
|
9
8
|
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
|
10
9
|
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
|
@@ -23,41 +22,42 @@ module RequestLogAnalyzer::Tracker
|
|
|
23
22
|
# DELETE | 512 hits (1.1%) |
|
|
24
23
|
class Frequency < Base
|
|
25
24
|
|
|
26
|
-
attr_reader :
|
|
25
|
+
attr_reader :categories
|
|
27
26
|
|
|
28
27
|
# Check if categories are set up
|
|
29
28
|
def prepare
|
|
30
29
|
raise "No categorizer set up for category tracker #{self.inspect}" unless options[:category]
|
|
31
|
-
@
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
@categorizer = create_lambda(options[:category])
|
|
31
|
+
|
|
32
|
+
# Initialize the categories. Use the list of category names to
|
|
33
|
+
@categories = {}
|
|
34
|
+
options[:all_categories].each { |cat| @categories[cat] = 0 } if options[:all_categories].kind_of?(Enumerable)
|
|
35
35
|
end
|
|
36
|
-
|
|
37
|
-
# Check HTTP method of a request and store that in the
|
|
36
|
+
|
|
37
|
+
# Check HTTP method of a request and store that in the categories hash.
|
|
38
38
|
# <tt>request</tt> The request.
|
|
39
39
|
def update(request)
|
|
40
|
-
cat =
|
|
41
|
-
if
|
|
42
|
-
@
|
|
43
|
-
@
|
|
40
|
+
cat = @categorizer.call(request)
|
|
41
|
+
if cat || options[:nils]
|
|
42
|
+
@categories[cat] ||= 0
|
|
43
|
+
@categories[cat] += 1
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# Return the amount of times a HTTP method has been encountered
|
|
48
48
|
# <tt>cat</tt> The HTTP method (:get, :put, :post or :delete)
|
|
49
49
|
def frequency(cat)
|
|
50
|
-
|
|
50
|
+
categories[cat] || 0
|
|
51
51
|
end
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
# Return the overall frequency
|
|
54
54
|
def overall_frequency
|
|
55
|
-
|
|
55
|
+
categories.inject(0) { |carry, item| carry + item[1] }
|
|
56
56
|
end
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
# Return the methods sorted by frequency
|
|
59
59
|
def sorted_by_frequency
|
|
60
|
-
@
|
|
60
|
+
@categories.sort { |a, b| b[1] <=> a[1] }
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# Generate a HTTP method frequency report to the given output object.
|
|
@@ -65,29 +65,28 @@ module RequestLogAnalyzer::Tracker
|
|
|
65
65
|
# <tt>output</tt> The output object
|
|
66
66
|
def report(output)
|
|
67
67
|
output.title(options[:title]) if options[:title]
|
|
68
|
-
|
|
69
|
-
if @
|
|
70
|
-
output << "None found.\n"
|
|
68
|
+
|
|
69
|
+
if @categories.empty?
|
|
70
|
+
output << "None found.\n"
|
|
71
71
|
else
|
|
72
|
-
|
|
73
|
-
total_hits
|
|
74
|
-
sorted_frequencies = sorted_frequencies.slice(0...options[:amount]) if options[:amount]
|
|
72
|
+
sorted_categories = output.slice_results(sorted_by_frequency)
|
|
73
|
+
total_hits = overall_frequency
|
|
75
74
|
|
|
76
|
-
output.table({:align => :left}, {:align => :right }, {:align => :right}, {:type => :ratio, :width => :rest}) do |rows|
|
|
77
|
-
|
|
75
|
+
output.table({:align => :left}, {:align => :right }, {:align => :right}, {:type => :ratio, :width => :rest}) do |rows|
|
|
76
|
+
sorted_categories.each do |(cat, count)|
|
|
78
77
|
rows << [cat, "#{count} hits", '%0.1f%%' % ((count.to_f / total_hits.to_f) * 100.0), (count.to_f / total_hits.to_f)]
|
|
79
78
|
end
|
|
80
79
|
end
|
|
81
80
|
|
|
82
81
|
end
|
|
83
82
|
end
|
|
84
|
-
|
|
85
|
-
# Returns a hash with the
|
|
83
|
+
|
|
84
|
+
# Returns a hash with the categories of every category that can be exported to YAML
|
|
86
85
|
def to_yaml_object
|
|
87
|
-
return nil if @
|
|
88
|
-
@
|
|
86
|
+
return nil if @categories.empty?
|
|
87
|
+
@categories
|
|
89
88
|
end
|
|
90
|
-
|
|
89
|
+
|
|
91
90
|
# Returns the title of this tracker for reports
|
|
92
91
|
def title
|
|
93
92
|
options[:title] || 'Request frequency'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Tracker
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# Determines the average hourly spread of the parsed requests.
|
|
4
4
|
# This spread is shown in a graph form.
|
|
5
5
|
#
|
|
@@ -30,51 +30,51 @@ module RequestLogAnalyzer::Tracker
|
|
|
30
30
|
# ................
|
|
31
31
|
class HourlySpread < Base
|
|
32
32
|
|
|
33
|
-
attr_reader :
|
|
33
|
+
attr_reader :hour_frequencies, :first, :last
|
|
34
34
|
|
|
35
35
|
# Check if timestamp field is set in the options and prepare the result time graph.
|
|
36
36
|
def prepare
|
|
37
37
|
options[:field] ||= :timestamp
|
|
38
|
-
@
|
|
38
|
+
@hour_frequencies = (0...24).map { 0 }
|
|
39
|
+
@first, @last = 99999999999999, 0
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
# Check if the timestamp in the request and store it.
|
|
42
43
|
# <tt>request</tt> The request.
|
|
43
44
|
def update(request)
|
|
44
|
-
|
|
45
|
-
timestamp
|
|
46
|
-
|
|
47
|
-
@
|
|
48
|
-
@first = timestamp if @first.nil? || timestamp < @first
|
|
49
|
-
@last = timestamp if @last.nil? || timestamp > @last
|
|
45
|
+
timestamp = request.first(options[:field])
|
|
46
|
+
@hour_frequencies[timestamp.to_s[8..9].to_i] +=1
|
|
47
|
+
@first = timestamp if timestamp < @first
|
|
48
|
+
@last = timestamp if timestamp > @last
|
|
50
49
|
end
|
|
51
50
|
|
|
52
51
|
# Total amount of requests tracked
|
|
53
52
|
def total_requests
|
|
54
|
-
@
|
|
53
|
+
@hour_frequencies.inject(0) { |sum, value| sum + value }
|
|
55
54
|
end
|
|
56
|
-
|
|
55
|
+
|
|
56
|
+
|
|
57
57
|
# First timestamp encountered
|
|
58
58
|
def first_timestamp
|
|
59
59
|
DateTime.parse(@first.to_s, '%Y%m%d%H%M%S') rescue nil
|
|
60
60
|
end
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
# Last timestamp encountered
|
|
63
63
|
def last_timestamp
|
|
64
64
|
DateTime.parse(@last.to_s, '%Y%m%d%H%M%S') rescue nil
|
|
65
65
|
end
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
# Difference between last and first timestamp.
|
|
68
68
|
def timespan
|
|
69
69
|
last_timestamp - first_timestamp
|
|
70
70
|
end
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
# Generate an hourly spread report to the given output object.
|
|
73
73
|
# Any options for the report should have been set during initialize.
|
|
74
74
|
# <tt>output</tt> The output object
|
|
75
75
|
def report(output)
|
|
76
76
|
output.title(title)
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
if total_requests == 0
|
|
79
79
|
output << "None found.\n"
|
|
80
80
|
return
|
|
@@ -82,23 +82,23 @@ module RequestLogAnalyzer::Tracker
|
|
|
82
82
|
|
|
83
83
|
days = [1, timespan].max
|
|
84
84
|
output.table({}, {:align => :right}, {:type => :ratio, :width => :rest, :treshold => 0.15}) do |rows|
|
|
85
|
-
@
|
|
86
|
-
ratio
|
|
85
|
+
@hour_frequencies.each_with_index do |requests, index|
|
|
86
|
+
ratio = requests.to_f / total_requests.to_f
|
|
87
87
|
requests_per_day = (requests / days).ceil
|
|
88
|
-
rows << ["#{index.to_s.rjust(3)}:00", "%d hits" % requests_per_day, ratio]
|
|
88
|
+
rows << ["#{index.to_s.rjust(3)}:00", "%d hits/day" % requests_per_day, ratio]
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
# Returns the title of this tracker for reports
|
|
94
94
|
def title
|
|
95
95
|
options[:title] || "Request distribution per hour"
|
|
96
96
|
end
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
# Returns the found frequencies per hour as a hash for YAML exporting
|
|
99
99
|
def to_yaml_object
|
|
100
100
|
yaml_object = {}
|
|
101
|
-
@
|
|
101
|
+
@hour_frequencies.each_with_index do |freq, hour|
|
|
102
102
|
yaml_object["#{hour}:00 - #{hour+1}:00"] = freq
|
|
103
103
|
end
|
|
104
104
|
yaml_object
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module RequestLogAnalyzer::Tracker
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
# Determines the datetime of the first request and the last request
|
|
4
4
|
# Also determines the amount of days inbetween these.
|
|
5
5
|
#
|
|
@@ -19,32 +19,32 @@ module RequestLogAnalyzer::Tracker
|
|
|
19
19
|
# Total time analyzed: 7 days
|
|
20
20
|
class Timespan < Base
|
|
21
21
|
|
|
22
|
-
attr_reader :first, :last
|
|
22
|
+
attr_reader :first, :last
|
|
23
23
|
|
|
24
24
|
# Check if timestamp field is set in the options.
|
|
25
25
|
def prepare
|
|
26
26
|
options[:field] ||= :timestamp
|
|
27
|
+
@first, @last = 99999999999999, 0
|
|
27
28
|
end
|
|
28
|
-
|
|
29
|
+
|
|
29
30
|
# Check if the timestamp in the request and store it.
|
|
30
31
|
# <tt>request</tt> The request.
|
|
31
32
|
def update(request)
|
|
32
33
|
timestamp = request[options[:field]]
|
|
33
|
-
|
|
34
|
-
@
|
|
35
|
-
@last = timestamp if @last.nil? || timestamp > @last
|
|
34
|
+
@first = timestamp if timestamp < @first
|
|
35
|
+
@last = timestamp if timestamp > @last
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# First timestamp encountered
|
|
39
39
|
def first_timestamp
|
|
40
40
|
DateTime.parse(@first.to_s, '%Y%m%d%H%M%S') rescue nil
|
|
41
41
|
end
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
# Last timestamp encountered
|
|
44
44
|
def last_timestamp
|
|
45
|
-
DateTime.parse(@last.to_s, '%Y%m%d%H%M%S') rescue nil
|
|
45
|
+
DateTime.parse(@last.to_s, '%Y%m%d%H%M%S') rescue nil
|
|
46
46
|
end
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
# Difference between last and first timestamp.
|
|
49
49
|
def timespan
|
|
50
50
|
last_timestamp - first_timestamp
|
|
@@ -55,27 +55,27 @@ module RequestLogAnalyzer::Tracker
|
|
|
55
55
|
# <tt>output</tt> The output object
|
|
56
56
|
def report(output)
|
|
57
57
|
output.title(options[:title]) if options[:title]
|
|
58
|
-
|
|
59
|
-
if @last && @first
|
|
60
|
-
output.with_style(:cell_separator => false) do
|
|
58
|
+
|
|
59
|
+
if @last > 0 && @first < 99999999999999
|
|
60
|
+
output.with_style(:cell_separator => false) do
|
|
61
61
|
output.table({:width => 20}, {}) do |rows|
|
|
62
62
|
rows << ['First request:', first_timestamp.strftime('%Y-%m-%d %H:%M:%I')]
|
|
63
|
-
rows << ['Last request:',
|
|
64
|
-
rows << ['Total time analyzed:', "#{timespan.ceil} days"]
|
|
63
|
+
rows << ['Last request:', last_timestamp.strftime('%Y-%m-%d %H:%M:%I')]
|
|
64
|
+
rows << ['Total time analyzed:', "#{timespan.ceil} days"]
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
# Returns the title of this tracker for reports
|
|
71
71
|
def title
|
|
72
72
|
options[:title] || 'Request timespan'
|
|
73
73
|
end
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
# A hash that can be exported to YAML with the first and last timestamp encountered.
|
|
76
76
|
def to_yaml_object
|
|
77
77
|
{ :first => first_timestamp, :last =>last_timestamp }
|
|
78
78
|
end
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
end
|
|
81
81
|
end
|