request-log-analyzer 1.3.4 → 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 +19 -17
- 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 +22 -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 +20 -20
- data/lib/request_log_analyzer/file_format/apache.rb +37 -30
- data/lib/request_log_analyzer/file_format/merb.rb +30 -13
- data/lib/request_log_analyzer/file_format/rack.rb +11 -0
- 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 +48 -48
- 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 +88 -69
- 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 +41 -134
- 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 +106 -0
- data/lib/request_log_analyzer/tracker.rb +139 -32
- data/lib/request_log_analyzer.rb +5 -5
- 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 +19 -15
- 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 +48 -11
- 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 +107 -0
- data/tasks/github-gem.rake +125 -75
- data/tasks/request_log_analyzer.rake +2 -2
- metadata +19 -10
|
@@ -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
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module RequestLogAnalyzer::Tracker
|
|
2
|
+
|
|
3
|
+
# Analyze the average and total traffic of requests
|
|
4
|
+
#
|
|
5
|
+
# === Options
|
|
6
|
+
# * <tt>:category</tt> Proc that handles request categorization for given fileformat (REQUEST_CATEGORIZER)
|
|
7
|
+
# * <tt>:traffic</tt> The field containing the duration in the request hash.
|
|
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
|
+
# * <tt>:title</tt> Title do be displayed above the report
|
|
11
|
+
# * <tt>:unless</tt> Handle request if this proc is false for the handled request.
|
|
12
|
+
class Traffic < Base
|
|
13
|
+
|
|
14
|
+
attr_reader :categories
|
|
15
|
+
|
|
16
|
+
include RequestLogAnalyzer::Tracker::StatisticsTracking
|
|
17
|
+
|
|
18
|
+
# Check if duration and catagory option have been received,
|
|
19
|
+
def prepare
|
|
20
|
+
raise "No traffic field set up for category tracker #{self.inspect}" unless options[:traffic]
|
|
21
|
+
raise "No categorizer set up for duration tracker #{self.inspect}" unless options[:category]
|
|
22
|
+
|
|
23
|
+
@categorizer = create_lambda(options[:category])
|
|
24
|
+
@trafficizer = create_lambda(options[:traffic])
|
|
25
|
+
@categories = {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Get the duration information from the request and store it in the different categories.
|
|
30
|
+
# <tt>request</tt> The request.
|
|
31
|
+
def update(request)
|
|
32
|
+
category = @categorizer.call(request)
|
|
33
|
+
traffic = @trafficizer.call(request)
|
|
34
|
+
update_statistics(category, traffic) if traffic.kind_of?(Numeric) && !category.nil?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Block function to build a result table using a provided sorting function.
|
|
38
|
+
# <tt>output</tt> The output object.
|
|
39
|
+
# <tt>amount</tt> The number of rows in the report table (default 10).
|
|
40
|
+
# === Options
|
|
41
|
+
# * </tt>:title</tt> The title of the table
|
|
42
|
+
# * </tt>:sort</tt> The key to sort on (:hits, :cumulative, :average, :min or :max)
|
|
43
|
+
def report_table(output, sort, options = {}, &block)
|
|
44
|
+
output.puts
|
|
45
|
+
|
|
46
|
+
top_categories = output.slice_results(sorted_by(sort))
|
|
47
|
+
output.with_style(:top_line => true) do
|
|
48
|
+
output.table(*statistics_header(:title => options[:title],:highlight => sort)) do |rows|
|
|
49
|
+
top_categories.each { |(cat, info)| rows.push(statistics_row(cat)) }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
output.puts
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Formats the traffic number using x B/kB/MB/GB etc notation
|
|
56
|
+
def display_value(bytes)
|
|
57
|
+
return "-" if bytes.nil?
|
|
58
|
+
return "0 B" if bytes.zero?
|
|
59
|
+
case Math.log10(bytes).floor
|
|
60
|
+
when 1...4 then '%d B' % bytes
|
|
61
|
+
when 4...7 then '%d kB' % (bytes / 1000)
|
|
62
|
+
when 7...10 then '%d MB' % (bytes / 1000_000)
|
|
63
|
+
when 10...13 then '%d GB' % (bytes / 1000_000_000)
|
|
64
|
+
else '%d TB' % (bytes / 1000_000_000_000)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Generate a request duration report to the given output object
|
|
69
|
+
# By default colulative and average duration are generated.
|
|
70
|
+
# Any options for the report should have been set during initialize.
|
|
71
|
+
# <tt>output</tt> The output object
|
|
72
|
+
def report(output)
|
|
73
|
+
|
|
74
|
+
sortings = output.options[:sort] || [:sum, :mean]
|
|
75
|
+
|
|
76
|
+
sortings.each do |report|
|
|
77
|
+
case report
|
|
78
|
+
when :mean
|
|
79
|
+
report_table(output, :mean, :title => "#{title} - sorted by mean")
|
|
80
|
+
when :stddev
|
|
81
|
+
report_table(output, :stddev, :title => "#{title} - sorted by standard deviation")
|
|
82
|
+
when :sum
|
|
83
|
+
report_table(output, :sum, :title => "#{title} - sorted by sum")
|
|
84
|
+
when :hits
|
|
85
|
+
report_table(output, :hits, :title => "#{title} - sorted by hits")
|
|
86
|
+
else
|
|
87
|
+
raise "Unknown duration report specified: #{report}!"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
output.puts
|
|
92
|
+
output.puts "#{output.colorize(title, :white, :bold)} - observed total: " + output.colorize(display_value(sum_overall), :brown, :bold)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns the title of this tracker for reports
|
|
96
|
+
def title
|
|
97
|
+
options[:title] || 'Request traffic'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns all the categories and the tracked duration as a hash than can be exported to YAML
|
|
101
|
+
def to_yaml_object
|
|
102
|
+
return nil if @categories.empty?
|
|
103
|
+
@categories
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -16,84 +16,191 @@ module RequestLogAnalyzer::Tracker
|
|
|
16
16
|
#
|
|
17
17
|
# For example :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }
|
|
18
18
|
class Base
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
attr_reader :options
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
# Initialize the class
|
|
23
23
|
# Note that the options are only applicable if should_update? is not overwritten
|
|
24
24
|
# by the inheriting class.
|
|
25
|
-
#
|
|
25
|
+
#
|
|
26
26
|
# === Options
|
|
27
27
|
# * <tt>:if</tt> Handle request if this proc is true for the handled request.
|
|
28
28
|
# * <tt>:unless</tt> Handle request if this proc is false for the handled request.
|
|
29
29
|
# * <tt>:line_type</tt> Line type this tracker will accept.
|
|
30
30
|
def initialize(options ={})
|
|
31
31
|
@options = options
|
|
32
|
+
setup_should_update_checks!
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Sets up the tracker's should_update? checks.
|
|
36
|
+
def setup_should_update_checks!
|
|
37
|
+
@should_update_checks = []
|
|
38
|
+
@should_update_checks.push( lambda { |request| request.has_line_type?(options[:line_type]) } ) if options[:line_type]
|
|
39
|
+
@should_update_checks.push(options[:if]) if options[:if].respond_to?(:call)
|
|
40
|
+
@should_update_checks.push( lambda { |request| request[options[:if]] }) if options[:if].kind_of?(Symbol)
|
|
41
|
+
@should_update_checks.push( lambda { |request| !options[:unless].call(request) }) if options[:unless].respond_to?(:call)
|
|
42
|
+
@should_update_checks.push( lambda { |request| !request[options[:unless]] }) if options[:unless].kind_of?(Symbol)
|
|
32
43
|
end
|
|
33
44
|
|
|
45
|
+
# Creates a lambda expression to return a static field from a request. If the
|
|
46
|
+
# argument already is a lambda exprssion, it will simply return the argument.
|
|
47
|
+
def create_lambda(arg)
|
|
48
|
+
case arg
|
|
49
|
+
when Proc then arg
|
|
50
|
+
when Symbol then lambda { |request| request[arg] }
|
|
51
|
+
else raise "Canot create a lambda expression from this argument: #{arg.inspect}!"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
34
55
|
# Hook things that need to be done before running here.
|
|
35
56
|
def prepare
|
|
36
57
|
end
|
|
37
|
-
|
|
58
|
+
|
|
38
59
|
# Will be called with each request.
|
|
39
60
|
# <tt>request</tt> The request to track data in.
|
|
40
61
|
def update(request)
|
|
41
62
|
end
|
|
42
|
-
|
|
63
|
+
|
|
43
64
|
# Hook things that need to be done after running here.
|
|
44
65
|
def finalize
|
|
45
66
|
end
|
|
46
|
-
|
|
67
|
+
|
|
47
68
|
# Determine if we should run the update function at all.
|
|
48
69
|
# Usually the update function will be heavy, so a light check is done here
|
|
49
70
|
# determining if we need to call update at all.
|
|
50
71
|
#
|
|
51
|
-
# Default this checks if defined:
|
|
72
|
+
# Default this checks if defined:
|
|
52
73
|
# * :line_type is also in the request hash.
|
|
53
74
|
# * :if is true for this request.
|
|
54
75
|
# * :unless if false for this request
|
|
55
76
|
#
|
|
56
77
|
# <tt>request</tt> The request object.
|
|
57
78
|
def should_update?(request)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if options[:if]
|
|
61
|
-
if options[:if].kind_of?(Symbol)
|
|
62
|
-
return false unless request[options[:if]]
|
|
63
|
-
elsif options[:if].respond_to?(:call)
|
|
64
|
-
return false unless options[:if].call(request)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
if options[:unless]
|
|
69
|
-
if options[:unless].kind_of?(Symbol)
|
|
70
|
-
return false if request[options[:unless]]
|
|
71
|
-
elsif options[:unless].respond_to?(:call)
|
|
72
|
-
return false if options[:unless].call(request)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
return true
|
|
79
|
+
@should_update_checks.all? { |c| c.call(request) }
|
|
77
80
|
end
|
|
78
|
-
|
|
81
|
+
|
|
79
82
|
# Hook report generation here.
|
|
80
83
|
# Defaults to self.inspect
|
|
81
84
|
# <tt>output</tt> The output object the report will be passed to.
|
|
82
85
|
def report(output)
|
|
83
86
|
output << self.inspect
|
|
84
|
-
output << "\n"
|
|
87
|
+
output << "\n"
|
|
85
88
|
end
|
|
86
|
-
|
|
89
|
+
|
|
87
90
|
# The title of this tracker. Used for reporting.
|
|
88
91
|
def title
|
|
89
92
|
self.class.to_s
|
|
90
93
|
end
|
|
91
|
-
|
|
94
|
+
|
|
92
95
|
# This method is called by RequestLogAnalyzer::Aggregator:Summarizer to retrieve an
|
|
93
96
|
# object with all the results of this tracker, that can be dumped to YAML format.
|
|
94
97
|
def to_yaml_object
|
|
95
98
|
nil
|
|
96
99
|
end
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
99
206
|
end
|
data/lib/request_log_analyzer.rb
CHANGED
|
@@ -8,10 +8,10 @@ Encoding.default_external = 'binary' if defined? Encoding and Encoding.respond_t
|
|
|
8
8
|
# - This module itselfs contains some functions to help with class and source file loading.
|
|
9
9
|
# - The actual application resides in the RequestLogAnalyzer::Controller class.
|
|
10
10
|
module RequestLogAnalyzer
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
# The current version of request-log-analyzer.
|
|
13
|
-
#
|
|
14
|
-
VERSION = "1.
|
|
13
|
+
# Do not change the value by hand; it will be updated automatically by the gem release script.
|
|
14
|
+
VERSION = "1.4.0"
|
|
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.
|
|
@@ -25,7 +25,7 @@ module RequestLogAnalyzer
|
|
|
25
25
|
# <tt>const</tt>:: The constant to load from the base constant as a string or symbol. This should be 'Bar' or :Bar when the constant Foo::Bar is being loaded.
|
|
26
26
|
def self.load_default_class_file(base, const)
|
|
27
27
|
require "#{to_underscore("#{base.name}::#{const}")}"
|
|
28
|
-
base.const_get(const)
|
|
28
|
+
base.const_get(const) if base.const_defined?(const)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
# Convert a string/symbol in camelcase (RequestLogAnalyzer::Controller) to underscores (request_log_analyzer/controller)
|
|
@@ -35,7 +35,7 @@ module RequestLogAnalyzer
|
|
|
35
35
|
str.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
# Convert a string/symbol in underscores (<tt>request_log_analyzer/controller</tt>) to camelcase
|
|
38
|
+
# Convert a string/symbol in underscores (<tt>request_log_analyzer/controller</tt>) to camelcase
|
|
39
39
|
# (<tt>RequestLogAnalyzer::Controller</tt>). This can be used to find the class that is defined in a given filename.
|
|
40
40
|
# <tt>str</tt>:: The string to convert in the following format: <tt>module_name/class_name</tt>
|
|
41
41
|
def self.to_camelcase(str)
|
|
@@ -1,36 +1,40 @@
|
|
|
1
1
|
Gem::Specification.new do |s|
|
|
2
2
|
s.name = "request-log-analyzer"
|
|
3
|
-
s.version = "1.3.4"
|
|
4
|
-
s.date = "2009-09-14"
|
|
5
3
|
|
|
4
|
+
# Do not set the version and date field manually, this is done by the release script
|
|
5
|
+
s.version = "1.4.0"
|
|
6
|
+
s.date = "2009-09-30"
|
|
7
|
+
|
|
6
8
|
s.rubyforge_project = 'r-l-a'
|
|
7
|
-
|
|
9
|
+
|
|
8
10
|
s.bindir = 'bin'
|
|
9
11
|
s.executables = ['request-log-analyzer']
|
|
10
12
|
s.default_executable = 'request-log-analyzer'
|
|
11
|
-
|
|
12
|
-
s.summary = "A command line tool to analyze request logs for Apache, Rails, Merb and other application servers"
|
|
13
|
+
|
|
14
|
+
s.summary = "A command line tool to analyze request logs for Apache, Rails, Merb and other web application servers"
|
|
13
15
|
s.description = <<-eos
|
|
14
16
|
Request log analyzer's purpose is to find ot how your web application is being used and to focus your optimization efforts.
|
|
15
|
-
This tool will parse all requests in the application's log file and aggregate the information. Once it is finished parsing
|
|
16
|
-
the log file(s), it will show the requests that take op most server time using various metrics. It can also insert all
|
|
17
|
-
parsed request information into a database so you can roll your own analysis. It supports Rails- and Merb-based applications
|
|
18
|
-
and Apache access log files out of the box, but file formats of other applications can easily be supported by supplying an
|
|
17
|
+
This tool will parse all requests in the application's log file and aggregate the information. Once it is finished parsing
|
|
18
|
+
the log file(s), it will show the requests that take op most server time using various metrics. It can also insert all
|
|
19
|
+
parsed request information into a database so you can roll your own analysis. It supports Rails- and Merb-based applications
|
|
20
|
+
and Apache access log files out of the box, but file formats of other applications can easily be supported by supplying an
|
|
19
21
|
easy to write log file format definition.
|
|
20
22
|
eos
|
|
21
|
-
|
|
23
|
+
|
|
22
24
|
s.rdoc_options << '--title' << s.name << '--main' << 'README.rdoc' << '--line-numbers' << '--inline-source'
|
|
23
25
|
s.extra_rdoc_files = ['README.rdoc']
|
|
24
|
-
|
|
26
|
+
|
|
25
27
|
s.requirements << "To use the database inserter, ActiveRecord and an appropriate database adapter are required."
|
|
26
|
-
|
|
28
|
+
|
|
27
29
|
s.add_development_dependency('rspec', '>= 1.2.4')
|
|
28
30
|
s.add_development_dependency('git', '>= 1.1.0')
|
|
29
|
-
|
|
31
|
+
|
|
30
32
|
s.authors = ['Willem van Bergen', 'Bart ten Brinke']
|
|
31
33
|
s.email = ['willem@railsdoctors.com', 'bart@railsdoctors.com']
|
|
32
34
|
s.homepage = 'http://railsdoctors.com'
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
# The files and test_files directives are set automatically by the release script.
|
|
37
|
+
# Do not change them by hand, but make sure to add the files to the git repository.
|
|
38
|
+
s.files = %w(spec/unit/filter/anonymize_filter_spec.rb spec/fixtures/rails_22_cached.log lib/request_log_analyzer/line_definition.rb lib/request_log_analyzer/output/html.rb lib/request_log_analyzer/controller.rb spec/lib/macros.rb lib/request_log_analyzer/file_format/rails_development.rb spec/fixtures/apache_combined.log spec/fixtures/apache_common.log spec/fixtures/merb_prefixed.log lib/request_log_analyzer/file_format/amazon_s3.rb tasks/request_log_analyzer.rake spec/unit/file_format/file_format_api_spec.rb spec/unit/file_format/apache_format_spec.rb spec/integration/command_line_usage_spec.rb lib/request_log_analyzer/database.rb spec/fixtures/decompression.log.bz2 spec/fixtures/rails_unordered.log lib/request_log_analyzer/log_processor.rb lib/request_log_analyzer/tracker.rb lib/request_log_analyzer/filter.rb bin/request-log-analyzer request-log-analyzer.gemspec DESIGN.rdoc spec/unit/filter/timespan_filter_spec.rb spec/unit/aggregator/database_inserter_spec.rb spec/lib/matchers.rb lib/request_log_analyzer/filter/field.rb lib/request_log_analyzer/tracker/frequency.rb spec/fixtures/decompression.log.gz spec/fixtures/decompression.log spec/lib/testing_format.rb spec/fixtures/test_order.log spec/fixtures/rails.db lib/request_log_analyzer/output/fixed_width.rb lib/request_log_analyzer/filter/anonymize.rb lib/request_log_analyzer/tracker/timespan.rb lib/request_log_analyzer/database/base.rb lib/request_log_analyzer/aggregator.rb spec/unit/request_spec.rb lib/cli/progressbar.rb lib/request_log_analyzer/mailer.rb README.rdoc lib/request_log_analyzer/database/warning.rb spec/fixtures/merb.log lib/request_log_analyzer/tracker/hourly_spread.rb .gitignore spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb spec/unit/file_format/amazon_s3_format_spec.rb lib/request_log_analyzer/aggregator/echo.rb spec/unit/mailer_spec.rb spec/unit/controller/log_processor_spec.rb spec/spec_helper.rb lib/request_log_analyzer.rb spec/database.yml Rakefile lib/request_log_analyzer/database/connection.rb spec/unit/filter/filter_spec.rb spec/fixtures/test_language_combined.log lib/request_log_analyzer/aggregator/database_inserter.rb lib/request_log_analyzer/aggregator/summarizer.rb lib/request_log_analyzer/file_format/rack.rb lib/request_log_analyzer/database/source.rb lib/request_log_analyzer/file_format/rails.rb spec/fixtures/decompression.tar.gz spec/unit/tracker/traffic_tracker_spec.rb spec/unit/filter/field_filter_spec.rb spec/unit/database/base_class_spec.rb lib/request_log_analyzer/filter/timespan.rb lib/request_log_analyzer/source/log_parser.rb spec/fixtures/decompression.tgz spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/fixtures/header_and_footer.log lib/cli/tools.rb lib/request_log_analyzer/file_format/merb.rb spec/fixtures/multiple_files_1.log spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb lib/request_log_analyzer/source.rb lib/request_log_analyzer/request.rb lib/cli/database_console.rb spec/unit/database/connection_spec.rb spec/unit/controller/controller_spec.rb spec/lib/mocks.rb spec/lib/helpers.rb spec/fixtures/rails_1x.log lib/cli/database_console_init.rb lib/request_log_analyzer/output.rb lib/request_log_analyzer/file_format/apache.rb spec/fixtures/decompression.log.zip spec/unit/source/log_parser_spec.rb spec/fixtures/test_file_format.log tasks/github-gem.rake spec/unit/database/database_spec.rb lib/request_log_analyzer/tracker/duration.rb lib/request_log_analyzer/tracker/traffic.rb lib/request_log_analyzer/file_format.rb spec/unit/aggregator/summarizer_spec.rb spec/fixtures/syslog_1x.log spec/fixtures/rails_22.log lib/request_log_analyzer/database/request.rb spec/fixtures/multiple_files_2.log LICENSE lib/request_log_analyzer/source/database_loader.rb spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb lib/cli/command_line_arguments.rb)
|
|
39
|
+
s.test_files = %w(spec/unit/filter/anonymize_filter_spec.rb spec/unit/file_format/file_format_api_spec.rb spec/unit/file_format/apache_format_spec.rb spec/integration/command_line_usage_spec.rb spec/unit/filter/timespan_filter_spec.rb spec/unit/aggregator/database_inserter_spec.rb spec/unit/request_spec.rb spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb spec/unit/file_format/amazon_s3_format_spec.rb spec/unit/mailer_spec.rb spec/unit/controller/log_processor_spec.rb spec/unit/filter/filter_spec.rb spec/unit/tracker/traffic_tracker_spec.rb spec/unit/filter/field_filter_spec.rb spec/unit/database/base_class_spec.rb spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb spec/unit/database/connection_spec.rb spec/unit/controller/controller_spec.rb spec/unit/source/log_parser_spec.rb spec/unit/database/database_spec.rb spec/unit/aggregator/summarizer_spec.rb spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb)
|
|
36
40
|
end
|
data/spec/database.yml
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
# This file determines what databases are used to test the database
|
|
2
|
+
# functionality of request-log-analyzer. By default, only an SQLite3
|
|
3
|
+
# memory database is enabled.
|
|
4
|
+
#
|
|
5
|
+
# The syntax of this file is exactly the same as Rails's database.yml.
|
|
6
|
+
|
|
1
7
|
sqlite3:
|
|
2
8
|
adapter: "sqlite3"
|
|
3
9
|
database: ":memory:"
|
data/spec/fixtures/rails_22.log
CHANGED
|
@@ -9,4 +9,4 @@ Rendered shared/_analytics (0.2ms)
|
|
|
9
9
|
Rendered layouts/_actions (0.6ms)
|
|
10
10
|
Rendered layouts/_menu (2.2ms)
|
|
11
11
|
Rendered layouts/_tabbar (0.5ms)
|
|
12
|
-
Completed in 614ms (View: 120, DB: 31) | 200 OK [http://www.example.
|
|
12
|
+
Completed in 614ms (View: 120, DB: 31) | 200 OK [http://www.example.com/demo]
|