request-log-analyzer 1.1.2 → 1.1.3
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 +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'
|