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.
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'