wvanbergen-request-log-analyzer 1.1.2 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/DESIGN +24 -10
- data/bin/request-log-analyzer +2 -27
- data/lib/cli/progressbar.rb +2 -19
- data/lib/cli/tools.rb +46 -0
- data/lib/request_log_analyzer/aggregator/database.rb +16 -5
- data/lib/request_log_analyzer/aggregator/echo.rb +1 -0
- data/lib/request_log_analyzer/aggregator/summarizer.rb +15 -13
- data/lib/request_log_analyzer/controller.rb +8 -4
- data/lib/request_log_analyzer/file_format/merb.rb +17 -4
- data/lib/request_log_analyzer/file_format/rails_development.rb +30 -92
- data/lib/request_log_analyzer/file_format.rb +8 -4
- data/lib/request_log_analyzer/filter/anonymize.rb +0 -3
- data/lib/request_log_analyzer/filter/field.rb +6 -1
- data/lib/request_log_analyzer/filter/timespan.rb +7 -1
- data/lib/request_log_analyzer/filter.rb +0 -4
- data/lib/request_log_analyzer/line_definition.rb +13 -2
- data/lib/request_log_analyzer/output/fixed_width.rb +7 -1
- data/lib/request_log_analyzer/output/html.rb +1 -0
- data/lib/request_log_analyzer/request.rb +4 -0
- data/lib/request_log_analyzer/source/log_parser.rb +19 -21
- data/lib/request_log_analyzer/source.rb +2 -1
- data/lib/request_log_analyzer/tracker/duration.rb +74 -14
- data/lib/request_log_analyzer/tracker/frequency.rb +22 -10
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +18 -6
- data/lib/request_log_analyzer/tracker/timespan.rb +15 -8
- data/lib/request_log_analyzer.rb +4 -8
- data/spec/integration/command_line_usage_spec.rb +71 -0
- data/spec/lib/helper.rb +33 -0
- data/spec/lib/mocks.rb +47 -0
- data/spec/{file_formats/spec_format.rb → lib/testing_format.rb} +6 -1
- data/spec/spec_helper.rb +5 -41
- data/spec/{database_inserter_spec.rb → unit/aggregator/database_inserter_spec.rb} +40 -37
- data/spec/unit/aggregator/summarizer_spec.rb +28 -0
- data/spec/unit/controller/controller_spec.rb +43 -0
- data/spec/{log_processor_spec.rb → unit/controller/log_processor_spec.rb} +4 -3
- data/spec/{file_format_spec.rb → unit/file_format/file_format_api_spec.rb} +16 -4
- data/spec/{line_definition_spec.rb → unit/file_format/line_definition_spec.rb} +13 -6
- data/spec/{merb_format_spec.rb → unit/file_format/merb_format_spec.rb} +2 -2
- data/spec/{rails_format_spec.rb → unit/file_format/rails_format_spec.rb} +19 -11
- data/spec/unit/filter/anonymize_filter_spec.rb +22 -0
- data/spec/unit/filter/field_filter_spec.rb +69 -0
- data/spec/unit/filter/timespan_filter_spec.rb +61 -0
- data/spec/{log_parser_spec.rb → unit/source/log_parser_spec.rb} +7 -7
- data/spec/{request_spec.rb → unit/source/request_spec.rb} +5 -5
- data/spec/unit/tracker/duration_tracker_spec.rb +90 -0
- data/spec/unit/tracker/frequency_tracker_spec.rb +83 -0
- data/spec/unit/tracker/timespan_tracker_spec.rb +64 -0
- data/spec/unit/tracker/tracker_api_test.rb +45 -0
- metadata +50 -26
- data/spec/controller_spec.rb +0 -64
- data/spec/filter_spec.rb +0 -157
- data/spec/summarizer_spec.rb +0 -9
@@ -1,5 +1,7 @@
|
|
1
1
|
module RequestLogAnalyzer::Output
|
2
2
|
|
3
|
+
# Fixed Width output class.
|
4
|
+
# Outputs a fixed width ASCII or UF8 report.
|
3
5
|
class FixedWidth < Base
|
4
6
|
|
5
7
|
module Monochrome
|
@@ -126,8 +128,12 @@ module RequestLogAnalyzer::Output
|
|
126
128
|
end
|
127
129
|
|
128
130
|
if column_widths.include?(nil)
|
131
|
+
fill_column = columns[column_widths.index(nil)]
|
129
132
|
width_left = options[:width] - ((columns.length - 1) * (style[:cell_separator] ? 3 : 1)) - column_widths.compact.inject(0) { |sum, col| sum + col}
|
130
|
-
column_widths[column_widths.index(nil)] =
|
133
|
+
column_widths[column_widths.index(nil)] = case fill_column[:type]
|
134
|
+
when :ratio; width_left # max out
|
135
|
+
else; [width_left, fill_column[:actual_width]].min
|
136
|
+
end
|
131
137
|
end
|
132
138
|
|
133
139
|
# Print table header
|
@@ -30,6 +30,10 @@ module RequestLogAnalyzer
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
def convert_eval(value, capture_definition)
|
34
|
+
eval(value)
|
35
|
+
end
|
36
|
+
|
33
37
|
# Slow default method to parse timestamps
|
34
38
|
def convert_timestamp(value, capture_definition)
|
35
39
|
DateTime.parse(value).strftime('%Y%m%d%H%M%S').to_i unless value.nil?
|
@@ -11,6 +11,9 @@ module RequestLogAnalyzer::Source
|
|
11
11
|
# occurs.
|
12
12
|
class LogParser < Base
|
13
13
|
|
14
|
+
DEFAULT_PARSE_STRATEGY = 'assume-correct'
|
15
|
+
PARSE_STRATEGIES = ['cautious', 'assume-correct']
|
16
|
+
|
14
17
|
attr_reader :source_files
|
15
18
|
|
16
19
|
# Initializes the parser instance.
|
@@ -25,9 +28,10 @@ module RequestLogAnalyzer::Source
|
|
25
28
|
@skipped_requests = 0
|
26
29
|
@current_io = nil
|
27
30
|
@source_files = options[:source_files]
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
+
|
32
|
+
@options[:parse_strategy] ||= DEFAULT_PARSE_STRATEGY
|
33
|
+
raise "Unknown parse strategy" unless PARSE_STRATEGIES.include?(@options[:parse_strategy])
|
34
|
+
|
31
35
|
self.register_file_format(format)
|
32
36
|
end
|
33
37
|
|
@@ -69,24 +73,17 @@ module RequestLogAnalyzer::Source
|
|
69
73
|
# Yeilds a Hash when it encounters a chunk of information.
|
70
74
|
def parse_io(io, options = {}, &block)
|
71
75
|
|
72
|
-
# parse every line type by default
|
73
|
-
line_types = options[:line_types] || file_format.line_definitions.keys
|
74
|
-
|
75
|
-
# check whether all provided line types are valid
|
76
|
-
unknown = line_types.reject { |line_type| file_format.line_definitions.has_key?(line_type) }
|
77
|
-
raise "Unknown line types: #{unknown.join(', ')}" unless unknown.empty?
|
78
|
-
|
79
76
|
@current_io = io
|
80
77
|
@current_io.each_line do |line|
|
81
78
|
|
82
79
|
@progress_handler.call(:progress, @current_io.pos) if @progress_handler && @current_io.kind_of?(File)
|
83
80
|
|
84
81
|
request_data = nil
|
85
|
-
|
86
|
-
|
87
|
-
break if request_data
|
82
|
+
file_format.line_definitions.each do |line_type, definition|
|
83
|
+
request_data = definition.matches(line, @current_io.lineno, self)
|
84
|
+
break if request_data
|
88
85
|
end
|
89
|
-
|
86
|
+
|
90
87
|
if request_data
|
91
88
|
@parsed_lines += 1
|
92
89
|
update_current_request(request_data, &block)
|
@@ -130,16 +127,17 @@ module RequestLogAnalyzer::Source
|
|
130
127
|
def update_current_request(request_data, &block)
|
131
128
|
if header_line?(request_data)
|
132
129
|
unless @current_request.nil?
|
133
|
-
|
134
|
-
|
135
|
-
@current_request
|
136
|
-
|
130
|
+
case options[:parse_strategy]
|
131
|
+
when 'assume-correct'
|
132
|
+
handle_request(@current_request, &block)
|
133
|
+
@current_request = @file_format.request(request_data)
|
134
|
+
when 'cautious'
|
137
135
|
@skipped_lines += 1
|
138
|
-
warn(:unclosed_request, "Encountered header line, but previous request was not closed!")
|
136
|
+
warn(:unclosed_request, "Encountered header line (#{request_data[:line_definition].name.inspect}), but previous request was not closed!")
|
139
137
|
@current_request = nil # remove all data that was parsed, skip next request as well.
|
140
138
|
end
|
141
139
|
else
|
142
|
-
@current_request = @file_format.
|
140
|
+
@current_request = @file_format.request(request_data)
|
143
141
|
end
|
144
142
|
else
|
145
143
|
unless @current_request.nil?
|
@@ -150,7 +148,7 @@ module RequestLogAnalyzer::Source
|
|
150
148
|
end
|
151
149
|
else
|
152
150
|
@skipped_lines += 1
|
153
|
-
warn(:no_current_request, "Parsebale line found outside of a request!")
|
151
|
+
warn(:no_current_request, "Parsebale line (#{request_data[:line_definition].name.inspect}) found outside of a request!")
|
154
152
|
end
|
155
153
|
end
|
156
154
|
end
|
@@ -4,6 +4,7 @@ module RequestLogAnalyzer::Source
|
|
4
4
|
RequestLogAnalyzer::load_default_class_file(self, const)
|
5
5
|
end
|
6
6
|
|
7
|
+
# Base Source class. All other sources inherit from this class
|
7
8
|
class Base
|
8
9
|
|
9
10
|
include RequestLogAnalyzer::FileFormat::Awareness
|
@@ -36,7 +37,7 @@ module RequestLogAnalyzer::Source
|
|
36
37
|
def prepare
|
37
38
|
end
|
38
39
|
|
39
|
-
def
|
40
|
+
def each_request(&block)
|
40
41
|
return true
|
41
42
|
end
|
42
43
|
|
@@ -31,26 +31,86 @@ module RequestLogAnalyzer::Tracker
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def update(request)
|
34
|
-
|
35
|
-
|
34
|
+
if options[:multiple]
|
35
|
+
categories = request.every(options[:category])
|
36
|
+
durations = request.every(options[:duration])
|
37
|
+
|
38
|
+
if categories.length == durations.length
|
39
|
+
categories.each_with_index do |category, index|
|
40
|
+
@categories[category] ||= {:hits => 0, :cumulative => 0.0}
|
41
|
+
@categories[category][:hits] += 1
|
42
|
+
@categories[category][:cumulative] += durations[index]
|
43
|
+
end
|
44
|
+
else
|
45
|
+
raise "Capture mismatch for multiple values in a request"
|
46
|
+
end
|
47
|
+
else
|
48
|
+
category = options[:category].respond_to?(:call) ? options[:category].call(request) : request[options[:category]]
|
49
|
+
duration = options[:duration].respond_to?(:call) ? options[:duration].call(request) : request[options[:duration]]
|
36
50
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
51
|
+
if !duration.nil? && !category.nil?
|
52
|
+
@categories[category] ||= {:hits => 0, :cumulative => 0.0}
|
53
|
+
@categories[category][:hits] += 1
|
54
|
+
@categories[category][:cumulative] += duration
|
55
|
+
end
|
41
56
|
end
|
42
57
|
end
|
58
|
+
|
59
|
+
def hits(cat)
|
60
|
+
categories[cat][:hits]
|
61
|
+
end
|
62
|
+
|
63
|
+
def cumulative_duration(cat)
|
64
|
+
categories[cat][:cumulative]
|
65
|
+
end
|
66
|
+
|
67
|
+
def average_duration(cat)
|
68
|
+
categories[cat][:cumulative] / categories[cat][:hits]
|
69
|
+
end
|
70
|
+
|
71
|
+
def overall_average_duration
|
72
|
+
overall_cumulative_duration / overall_hits
|
73
|
+
end
|
74
|
+
|
75
|
+
def overall_cumulative_duration
|
76
|
+
categories.inject(0.0) { |sum, (name, cat)| sum + cat[:cumulative] }
|
77
|
+
end
|
78
|
+
|
79
|
+
def overall_hits
|
80
|
+
categories.inject(0) { |sum, (name, cat)| sum + cat[:hits] }
|
81
|
+
end
|
43
82
|
|
83
|
+
def sorted_by_hits
|
84
|
+
sorted_by(:hits)
|
85
|
+
end
|
86
|
+
|
87
|
+
def sorted_by_cumulative
|
88
|
+
sorted_by(:cumulative)
|
89
|
+
end
|
90
|
+
|
91
|
+
def sorted_by_average
|
92
|
+
sorted_by { |cat| cat[:cumulative] / cat[:hits] }
|
93
|
+
end
|
94
|
+
|
95
|
+
def sorted_by(by = nil)
|
96
|
+
if block_given?
|
97
|
+
categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) }
|
98
|
+
else
|
99
|
+
categories.sort { |a, b| b[1][by] <=> a[1][by] }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Builds a result table using a provided sorting function
|
44
104
|
def report_table(output, amount = 10, options = {}, &block)
|
45
105
|
|
46
106
|
output.title(options[:title])
|
47
107
|
|
48
108
|
top_categories = @categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) }.slice(0...amount)
|
49
|
-
output.table({:title => '
|
109
|
+
output.table({:title => 'Category', :width => :rest}, {:title => 'Hits', :align => :right, :min_width => 4},
|
50
110
|
{:title => 'Cumulative', :align => :right, :min_width => 10}, {:title => 'Average', :align => :right, :min_width => 8}) do |rows|
|
51
111
|
|
52
112
|
top_categories.each do |(cat, info)|
|
53
|
-
rows << [cat, info[:
|
113
|
+
rows << [cat, info[:hits], "%0.02fs" % info[:cumulative], "%0.02fs" % (info[:cumulative] / info[:hits])]
|
54
114
|
end
|
55
115
|
end
|
56
116
|
|
@@ -59,19 +119,19 @@ module RequestLogAnalyzer::Tracker
|
|
59
119
|
def report(output)
|
60
120
|
|
61
121
|
options[:title] ||= 'Request duration'
|
62
|
-
options[:report] ||= [:
|
122
|
+
options[:report] ||= [:cumulative, :average]
|
63
123
|
options[:top] ||= 20
|
64
124
|
|
65
125
|
options[:report].each do |report|
|
66
126
|
case report
|
67
127
|
when :average
|
68
|
-
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by average time") { |
|
69
|
-
when :
|
70
|
-
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by cumulative time") { |
|
128
|
+
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by average time") { |cat| cat[:cumulative] / cat[:hits] }
|
129
|
+
when :cumulative
|
130
|
+
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by cumulative time") { |cat| cat[:cumulative] }
|
71
131
|
when :hits
|
72
|
-
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by hits") { |
|
132
|
+
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by hits") { |cat| cat[:hits] }
|
73
133
|
else
|
74
|
-
|
134
|
+
raise "Unknown duration report specified: #{report}!"
|
75
135
|
end
|
76
136
|
end
|
77
137
|
end
|
@@ -21,36 +21,48 @@ module RequestLogAnalyzer::Tracker
|
|
21
21
|
# DELETE | 512 hits (1.1%) |
|
22
22
|
class Frequency < Base
|
23
23
|
|
24
|
-
attr_reader :
|
24
|
+
attr_reader :frequencies
|
25
25
|
|
26
26
|
def prepare
|
27
27
|
raise "No categorizer set up for category tracker #{self.inspect}" unless options[:category]
|
28
|
-
@
|
28
|
+
@frequencies = {}
|
29
29
|
if options[:all_categories].kind_of?(Enumerable)
|
30
|
-
options[:all_categories].each { |cat| @
|
30
|
+
options[:all_categories].each { |cat| @frequencies[cat] = 0 }
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
def update(request)
|
35
35
|
cat = options[:category].respond_to?(:call) ? options[:category].call(request) : request[options[:category]]
|
36
36
|
if !cat.nil? || options[:nils]
|
37
|
-
@
|
38
|
-
@
|
37
|
+
@frequencies[cat] ||= 0
|
38
|
+
@frequencies[cat] += 1
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
+
def frequency(cat)
|
43
|
+
frequencies[cat] || 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def overall_frequency
|
47
|
+
frequencies.inject(0) { |carry, item| carry + item[1] }
|
48
|
+
end
|
49
|
+
|
50
|
+
def sorted_by_frequency
|
51
|
+
@frequencies.sort { |a, b| b[1] <=> a[1] }
|
52
|
+
end
|
53
|
+
|
42
54
|
def report(output)
|
43
55
|
output.title(options[:title]) if options[:title]
|
44
56
|
|
45
|
-
if @
|
57
|
+
if @frequencies.empty?
|
46
58
|
output << "None found.\n"
|
47
59
|
else
|
48
|
-
|
49
|
-
total_hits
|
50
|
-
|
60
|
+
sorted_frequencies = @frequencies.sort { |a, b| b[1] <=> a[1] }
|
61
|
+
total_hits = sorted_frequencies.inject(0) { |carry, item| carry + item[1] }
|
62
|
+
sorted_frequencies = sorted_frequencies.slice(0...options[:amount]) if options[:amount]
|
51
63
|
|
52
64
|
output.table({:align => :left}, {:align => :right }, {:align => :right}, {:type => :ratio, :width => :rest}) do |rows|
|
53
|
-
|
65
|
+
sorted_frequencies.each do |(cat, count)|
|
54
66
|
rows << [cat, "#{count} hits", '%0.1f%%' % ((count.to_f / total_hits.to_f) * 100.0), (count.to_f / total_hits.to_f)]
|
55
67
|
end
|
56
68
|
end
|
@@ -44,19 +44,31 @@ module RequestLogAnalyzer::Tracker
|
|
44
44
|
@last = timestamp if @last.nil? || timestamp > @last
|
45
45
|
end
|
46
46
|
|
47
|
+
def total_requests
|
48
|
+
@request_time_graph.inject(0) { |sum, value| sum + value }
|
49
|
+
end
|
50
|
+
|
51
|
+
def first_timestamp
|
52
|
+
DateTime.parse(@first.to_s, '%Y%m%d%H%M%S') rescue nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def last_timestamp
|
56
|
+
DateTime.parse(@last.to_s, '%Y%m%d%H%M%S') rescue nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def timespan
|
60
|
+
last_timestamp - first_timestamp
|
61
|
+
end
|
62
|
+
|
47
63
|
def report(output)
|
48
64
|
output.title("Requests graph - average per day per hour")
|
49
65
|
|
50
|
-
if
|
66
|
+
if total_requests == 0
|
51
67
|
output << "None found.\n"
|
52
68
|
return
|
53
69
|
end
|
54
70
|
|
55
|
-
|
56
|
-
last_date = DateTime.parse(@last.to_s, '%Y%m%d%H%M%S')
|
57
|
-
days = (@last && @first) ? (last_date - first_date).ceil : 1
|
58
|
-
total_requests = @request_time_graph.inject(0) { |sum, value| sum + value }
|
59
|
-
|
71
|
+
days = [1, timespan].max
|
60
72
|
output.table({}, {:align => :right}, {:type => :ratio, :width => :rest, :treshold => 0.15}) do |rows|
|
61
73
|
@request_time_graph.each_with_index do |requests, index|
|
62
74
|
ratio = requests.to_f / total_requests.to_f
|
@@ -31,20 +31,27 @@ module RequestLogAnalyzer::Tracker
|
|
31
31
|
@last = timestamp if @last.nil? || timestamp > @last
|
32
32
|
end
|
33
33
|
|
34
|
+
def first_timestamp
|
35
|
+
DateTime.parse(@first.to_s, '%Y%m%d%H%M%S') rescue nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def last_timestamp
|
39
|
+
DateTime.parse(@last.to_s, '%Y%m%d%H%M%S') rescue nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def timespan
|
43
|
+
last_timestamp - first_timestamp
|
44
|
+
end
|
45
|
+
|
34
46
|
def report(output)
|
35
47
|
output.title(options[:title]) if options[:title]
|
36
48
|
|
37
|
-
first_date = DateTime.parse(@first.to_s, '%Y%m%d%H%M%S') rescue nil
|
38
|
-
last_date = DateTime.parse(@last.to_s, '%Y%m%d%H%M%S') rescue nil
|
39
|
-
|
40
49
|
if @last && @first
|
41
|
-
days = (@last && @first) ? (last_date - first_date).ceil : 1
|
42
|
-
|
43
50
|
output.with_style(:cell_separator => false) do
|
44
51
|
output.table({:width => 20}, {}) do |rows|
|
45
|
-
rows << ['First request:',
|
46
|
-
rows << ['Last request:',
|
47
|
-
rows << ['Total time analyzed:', "#{
|
52
|
+
rows << ['First request:', first_timestamp.strftime('%Y-%m-%d %H:%M:%I')]
|
53
|
+
rows << ['Last request:', last_timestamp.strftime('%Y-%m-%d %H:%M:%I')]
|
54
|
+
rows << ['Total time analyzed:', "#{timespan.ceil} days"]
|
48
55
|
end
|
49
56
|
end
|
50
57
|
end
|
data/lib/request_log_analyzer.rb
CHANGED
@@ -4,6 +4,10 @@ require File.dirname(__FILE__) + '/cli/progressbar'
|
|
4
4
|
module RequestLogAnalyzer
|
5
5
|
|
6
6
|
VERSION = '1.1'
|
7
|
+
|
8
|
+
def self.const_missing(const)
|
9
|
+
load_default_class_file(RequestLogAnalyzer, const)
|
10
|
+
end
|
7
11
|
|
8
12
|
# Function to implement
|
9
13
|
def self.load_default_class_file(base, const)
|
@@ -25,12 +29,4 @@ module RequestLogAnalyzer
|
|
25
29
|
end
|
26
30
|
end
|
27
31
|
|
28
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/file_format'
|
29
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/line_definition'
|
30
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/request'
|
31
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/aggregator'
|
32
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/filter'
|
33
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/controller'
|
34
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/source'
|
35
|
-
require File.dirname(__FILE__) + '/request_log_analyzer/output'
|
36
32
|
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
|
+
|
3
|
+
describe RequestLogAnalyzer, 'running from command line' do
|
4
|
+
|
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
|
+
|
11
|
+
before(:each) do
|
12
|
+
File.unlink(TEMP_DATABASE_FILE) if File.exist?(TEMP_DATABASE_FILE)
|
13
|
+
File.unlink(TEMP_REPORT_FILE) if File.exist?(TEMP_REPORT_FILE)
|
14
|
+
end
|
15
|
+
|
16
|
+
after(:each) do
|
17
|
+
File.unlink(TEMP_DATABASE_FILE) if File.exist?(TEMP_DATABASE_FILE)
|
18
|
+
File.unlink(TEMP_REPORT_FILE) if File.exist?(TEMP_REPORT_FILE)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should find 4 requests in default mode" do
|
22
|
+
output = run("#{log_fixture(:rails_1x)}")
|
23
|
+
output.detect { |line| /Parsed requests\:\s*4/ =~ line }.should_not be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should find 3 requests with a --select option" do
|
27
|
+
output = run("#{log_fixture(:rails_1x)} --select controller PeopleController")
|
28
|
+
output.detect { |line| /Parsed requests\:\s*4/ =~ line }.should_not be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should find 1 requests with a --reject option" do
|
32
|
+
output = run("#{log_fixture(:rails_1x)} --reject controller PeopleController")
|
33
|
+
output.detect { |line| /Parsed requests\:\s*4/ =~ line }.should_not be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should write output to a file with the --file option" do
|
37
|
+
run("#{log_fixture(:rails_1x)} --file #{TEMP_REPORT_FILE}")
|
38
|
+
File.exist?(TEMP_REPORT_FILE).should be_true
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should write only ASCII characters to a file with the --file option" do
|
42
|
+
run("#{log_fixture(:rails_1x)} --file #{TEMP_REPORT_FILE}")
|
43
|
+
/^[\x00-\x7F]*$/.match(File.read(TEMP_REPORT_FILE)).should_not be_nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should write HTML if --output HTML is provided" do
|
47
|
+
output = run("#{log_fixture(:rails_1x)} --output HTML")
|
48
|
+
output.any? { |line| /<html.*>/ =~ line}
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should run with the --database option" do
|
52
|
+
run("#{log_fixture(:rails_1x)} --database #{TEMP_DATABASE_FILE}")
|
53
|
+
File.exist?(TEMP_DATABASE_FILE).should be_true
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should use no colors in the report with the --boring option" do
|
57
|
+
output = run("#{log_fixture(:rails_1x)} --boring")
|
58
|
+
output.any? { |line| /\e/ =~ line }.should be_false
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should use only ASCII characters in the report with the --boring option" do
|
62
|
+
output = run("#{log_fixture(:rails_1x)} --boring")
|
63
|
+
output.all? { |line| /^[\x00-\x7F]*$/ =~ line }.should be_true
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should parse a Merb file if --format merb is set" do
|
67
|
+
output = run("#{log_fixture(:merb)} --format merb")
|
68
|
+
output.detect { |line| /Parsed requests\:\s*11/ =~ line }.should_not be_nil
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/spec/lib/helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module RequestLogAnalyzer::Spec::Helper
|
2
|
+
|
3
|
+
include RequestLogAnalyzer::Spec::Mocks
|
4
|
+
|
5
|
+
|
6
|
+
def testing_format
|
7
|
+
@testing_format ||= TestingFormat.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def log_fixture(name)
|
11
|
+
File.dirname(__FILE__) + "/../fixtures/#{name}.log"
|
12
|
+
end
|
13
|
+
|
14
|
+
def request(fields, format = testing_format)
|
15
|
+
if fields.kind_of?(Array)
|
16
|
+
format.request(*fields)
|
17
|
+
else
|
18
|
+
format.request(fields)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def run(arguments)
|
23
|
+
binary = "#{File.dirname(__FILE__)}/../../bin/request-log-analyzer"
|
24
|
+
arguments = arguments.join(' ') if arguments.kind_of?(Array)
|
25
|
+
|
26
|
+
output = []
|
27
|
+
IO.popen("#{binary} #{arguments}") do |pipe|
|
28
|
+
output = pipe.readlines
|
29
|
+
end
|
30
|
+
$?.exitstatus.should == 0
|
31
|
+
output
|
32
|
+
end
|
33
|
+
end
|
data/spec/lib/mocks.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module RequestLogAnalyzer::Spec::Mocks
|
2
|
+
|
3
|
+
def mock_source
|
4
|
+
source = mock('RequestLogAnalyzer::Source::Base')
|
5
|
+
source.stub!(:file_format).and_return(testing_format)
|
6
|
+
source.stub!(:parsed_requests).and_return(2)
|
7
|
+
source.stub!(:skipped_requests).and_return(1)
|
8
|
+
source.stub!(:parse_lines).and_return(10)
|
9
|
+
|
10
|
+
source.stub!(:warning=)
|
11
|
+
source.stub!(:progress=)
|
12
|
+
|
13
|
+
source.stub!(:prepare)
|
14
|
+
source.stub!(:finalize)
|
15
|
+
|
16
|
+
source.stub!(:each_request).and_return do |block|
|
17
|
+
block.call(testing_format.request(:field => 'value1'))
|
18
|
+
block.call(testing_format.request(:field => 'value2'))
|
19
|
+
end
|
20
|
+
|
21
|
+
return source
|
22
|
+
end
|
23
|
+
|
24
|
+
def mock_io
|
25
|
+
mio = mock('IO')
|
26
|
+
mio.stub!(:print)
|
27
|
+
mio.stub!(:puts)
|
28
|
+
mio.stub!(:write)
|
29
|
+
return mio
|
30
|
+
end
|
31
|
+
|
32
|
+
def mock_output
|
33
|
+
output = mock('RequestLogAnalyzer::Output::Base')
|
34
|
+
output.stub!(:header)
|
35
|
+
output.stub!(:footer)
|
36
|
+
output.stub!(:puts)
|
37
|
+
output.stub!(:<<)
|
38
|
+
output.stub!(:title)
|
39
|
+
output.stub!(:line)
|
40
|
+
output.stub!(:with_style)
|
41
|
+
output.stub!(:table) { yield [] }
|
42
|
+
output.stub!(:io).and_return(mock_io)
|
43
|
+
return output
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class
|
1
|
+
class TestingFormat < RequestLogAnalyzer::FileFormat::Base
|
2
2
|
|
3
3
|
format_definition.first do |line|
|
4
4
|
line.header = true
|
@@ -14,6 +14,11 @@ class SpecFormat < RequestLogAnalyzer::FileFormat::Base
|
|
14
14
|
{ :name => :duration, :type => :duration, :unit => :msec }]
|
15
15
|
end
|
16
16
|
|
17
|
+
format_definition.eval do |line|
|
18
|
+
line.regexp = /evaluation (\{.*\})/
|
19
|
+
line.captures = [{ :name => :evaluated, :type => :eval, :provides => { :greating => :string, :what => :string } }]
|
20
|
+
end
|
21
|
+
|
17
22
|
format_definition.last do |line|
|
18
23
|
line.footer = true
|
19
24
|
line.teaser = /finishing /
|
data/spec/spec_helper.rb
CHANGED
@@ -1,49 +1,13 @@
|
|
1
|
+
$:.reject! { |e| e.include? 'TextMate' }
|
1
2
|
$: << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
3
|
|
3
4
|
require 'rubygems'
|
4
5
|
require 'spec'
|
5
6
|
require 'request_log_analyzer'
|
6
7
|
|
7
|
-
module
|
8
|
-
|
9
|
-
def format_file(format)
|
10
|
-
File.dirname(__FILE__) + "/file_formats/#{format}.rb"
|
11
|
-
end
|
12
|
-
|
13
|
-
def spec_format
|
14
|
-
@spec_format ||= begin
|
15
|
-
require format_file(:spec_format)
|
16
|
-
SpecFormat.new
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def log_fixture(name)
|
21
|
-
File.dirname(__FILE__) + "/fixtures/#{name}.log"
|
22
|
-
end
|
23
|
-
|
24
|
-
def request(fields, format = spec_format)
|
25
|
-
if fields.kind_of?(Array)
|
26
|
-
format.create_request(*fields)
|
27
|
-
else
|
28
|
-
format.create_request(fields)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def mock_io
|
33
|
-
mio = mock('IO')
|
34
|
-
mio.stub!(:print)
|
35
|
-
mio.stub!(:puts)
|
36
|
-
mio.stub!(:write)
|
37
|
-
return mio
|
38
|
-
end
|
39
|
-
|
40
|
-
def mock_output
|
41
|
-
output = mock('RequestLogAnalyzer::Output')
|
42
|
-
output.stub!(:header)
|
43
|
-
output.stub!(:footer)
|
44
|
-
output.stub!(:io).and_return(mock_io)
|
45
|
-
return output
|
46
|
-
end
|
47
|
-
|
8
|
+
module RequestLogAnalyzer::Spec
|
48
9
|
end
|
49
10
|
|
11
|
+
require File.dirname(__FILE__) + '/lib/testing_format'
|
12
|
+
require File.dirname(__FILE__) + '/lib/mocks'
|
13
|
+
require File.dirname(__FILE__) + '/lib/helper'
|