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.
Files changed (52) hide show
  1. data/DESIGN +24 -10
  2. data/bin/request-log-analyzer +2 -27
  3. data/lib/cli/progressbar.rb +2 -19
  4. data/lib/cli/tools.rb +46 -0
  5. data/lib/request_log_analyzer/aggregator/database.rb +16 -5
  6. data/lib/request_log_analyzer/aggregator/echo.rb +1 -0
  7. data/lib/request_log_analyzer/aggregator/summarizer.rb +15 -13
  8. data/lib/request_log_analyzer/controller.rb +8 -4
  9. data/lib/request_log_analyzer/file_format/merb.rb +17 -4
  10. data/lib/request_log_analyzer/file_format/rails_development.rb +30 -92
  11. data/lib/request_log_analyzer/file_format.rb +8 -4
  12. data/lib/request_log_analyzer/filter/anonymize.rb +0 -3
  13. data/lib/request_log_analyzer/filter/field.rb +6 -1
  14. data/lib/request_log_analyzer/filter/timespan.rb +7 -1
  15. data/lib/request_log_analyzer/filter.rb +0 -4
  16. data/lib/request_log_analyzer/line_definition.rb +13 -2
  17. data/lib/request_log_analyzer/output/fixed_width.rb +7 -1
  18. data/lib/request_log_analyzer/output/html.rb +1 -0
  19. data/lib/request_log_analyzer/request.rb +4 -0
  20. data/lib/request_log_analyzer/source/log_parser.rb +19 -21
  21. data/lib/request_log_analyzer/source.rb +2 -1
  22. data/lib/request_log_analyzer/tracker/duration.rb +74 -14
  23. data/lib/request_log_analyzer/tracker/frequency.rb +22 -10
  24. data/lib/request_log_analyzer/tracker/hourly_spread.rb +18 -6
  25. data/lib/request_log_analyzer/tracker/timespan.rb +15 -8
  26. data/lib/request_log_analyzer.rb +4 -8
  27. data/spec/integration/command_line_usage_spec.rb +71 -0
  28. data/spec/lib/helper.rb +33 -0
  29. data/spec/lib/mocks.rb +47 -0
  30. data/spec/{file_formats/spec_format.rb → lib/testing_format.rb} +6 -1
  31. data/spec/spec_helper.rb +5 -41
  32. data/spec/{database_inserter_spec.rb → unit/aggregator/database_inserter_spec.rb} +40 -37
  33. data/spec/unit/aggregator/summarizer_spec.rb +28 -0
  34. data/spec/unit/controller/controller_spec.rb +43 -0
  35. data/spec/{log_processor_spec.rb → unit/controller/log_processor_spec.rb} +4 -3
  36. data/spec/{file_format_spec.rb → unit/file_format/file_format_api_spec.rb} +16 -4
  37. data/spec/{line_definition_spec.rb → unit/file_format/line_definition_spec.rb} +13 -6
  38. data/spec/{merb_format_spec.rb → unit/file_format/merb_format_spec.rb} +2 -2
  39. data/spec/{rails_format_spec.rb → unit/file_format/rails_format_spec.rb} +19 -11
  40. data/spec/unit/filter/anonymize_filter_spec.rb +22 -0
  41. data/spec/unit/filter/field_filter_spec.rb +69 -0
  42. data/spec/unit/filter/timespan_filter_spec.rb +61 -0
  43. data/spec/{log_parser_spec.rb → unit/source/log_parser_spec.rb} +7 -7
  44. data/spec/{request_spec.rb → unit/source/request_spec.rb} +5 -5
  45. data/spec/unit/tracker/duration_tracker_spec.rb +90 -0
  46. data/spec/unit/tracker/frequency_tracker_spec.rb +83 -0
  47. data/spec/unit/tracker/timespan_tracker_spec.rb +64 -0
  48. data/spec/unit/tracker/tracker_api_test.rb +45 -0
  49. metadata +50 -26
  50. data/spec/controller_spec.rb +0 -64
  51. data/spec/filter_spec.rb +0 -157
  52. 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)] = width_left
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
@@ -1,5 +1,6 @@
1
1
  module RequestLogAnalyzer::Output
2
2
 
3
+ # HTML Output class. Generated a HTML-formatted report, including CSS.
3
4
  class HTML < Base
4
5
 
5
6
  # def initialize(io, options = {})
@@ -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
- # install the file format module (see RequestLogAnalyzer::FileFormat)
30
- # and register all the line definitions to the parser
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
- line_types.each do |line_type|
86
- line_type_definition = file_format.line_definitions[line_type]
87
- break if request_data = line_type_definition.matches(line, @current_io.lineno, self)
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
- if options[:assume_correct_order]
134
- handle_request(@current_request, &block) #yield @current_request
135
- @current_request = @file_format.create_request(request_data)
136
- else
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.create_request(request_data)
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 requests(&block)
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
- category = options[:category].respond_to?(:call) ? options[:category].call(request) : request[options[:category]]
35
- duration = options[:duration].respond_to?(:call) ? options[:duration].call(request) : request[options[:duration]]
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
- if !duration.nil? && !category.nil?
38
- @categories[category] ||= {:count => 0, :total_duration => 0.0}
39
- @categories[category][:count] += 1
40
- @categories[category][:total_duration] += duration
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 => 'Action'}, {:title => 'Hits', :align => :right, :min_width => 4},
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[:count], "%0.02fs" % info[:total_duration], "%0.02fs" % (info[:total_duration] / info[:count])]
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] ||= [:total, :average]
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") { |request| request[:total_duration] / request[:count] }
69
- when :total
70
- report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by cumulative time") { |request| request[:total_duration] }
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") { |request| request[:count] }
132
+ report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by hits") { |cat| cat[:hits] }
73
133
  else
74
- output << "Unknown duration report specified\n"
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 :categories
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
- @categories = {}
28
+ @frequencies = {}
29
29
  if options[:all_categories].kind_of?(Enumerable)
30
- options[:all_categories].each { |cat| @categories[cat] = 0 }
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
- @categories[cat] ||= 0
38
- @categories[cat] += 1
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 @categories.empty?
57
+ if @frequencies.empty?
46
58
  output << "None found.\n"
47
59
  else
48
- sorted_categories = @categories.sort { |a, b| b[1] <=> a[1] }
49
- total_hits = sorted_categories.inject(0) { |carry, item| carry + item[1] }
50
- sorted_categories = sorted_categories.slice(0...options[:amount]) if options[:amount]
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
- sorted_categories.each do |(cat, count)|
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 @request_time_graph == [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
66
+ if total_requests == 0
51
67
  output << "None found.\n"
52
68
  return
53
69
  end
54
70
 
55
- first_date = DateTime.parse(@first.to_s, '%Y%m%d%H%M%S')
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:', first_date.strftime('%Y-%m-%d %H:%M:%I')]
46
- rows << ['Last request:', last_date.strftime('%Y-%m-%d %H:%M:%I')]
47
- rows << ['Total time analyzed:', "#{days} days"]
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
@@ -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
@@ -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 SpecFormat < RequestLogAnalyzer::FileFormat::Base
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 RequestLogAnalyzerSpecHelper
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'