ngmoco-request-log-analyzer 1.4.2

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 (112) hide show
  1. data/.gitignore +10 -0
  2. data/DESIGN.rdoc +41 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +8 -0
  6. data/bin/request-log-analyzer +114 -0
  7. data/lib/cli/command_line_arguments.rb +301 -0
  8. data/lib/cli/database_console.rb +26 -0
  9. data/lib/cli/database_console_init.rb +43 -0
  10. data/lib/cli/progressbar.rb +213 -0
  11. data/lib/cli/tools.rb +46 -0
  12. data/lib/request_log_analyzer.rb +44 -0
  13. data/lib/request_log_analyzer/aggregator.rb +49 -0
  14. data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
  15. data/lib/request_log_analyzer/aggregator/echo.rb +29 -0
  16. data/lib/request_log_analyzer/aggregator/summarizer.rb +175 -0
  17. data/lib/request_log_analyzer/controller.rb +332 -0
  18. data/lib/request_log_analyzer/database.rb +102 -0
  19. data/lib/request_log_analyzer/database/base.rb +115 -0
  20. data/lib/request_log_analyzer/database/connection.rb +38 -0
  21. data/lib/request_log_analyzer/database/request.rb +22 -0
  22. data/lib/request_log_analyzer/database/source.rb +13 -0
  23. data/lib/request_log_analyzer/database/warning.rb +14 -0
  24. data/lib/request_log_analyzer/file_format.rb +160 -0
  25. data/lib/request_log_analyzer/file_format/amazon_s3.rb +71 -0
  26. data/lib/request_log_analyzer/file_format/apache.rb +141 -0
  27. data/lib/request_log_analyzer/file_format/merb.rb +67 -0
  28. data/lib/request_log_analyzer/file_format/rack.rb +11 -0
  29. data/lib/request_log_analyzer/file_format/rails.rb +176 -0
  30. data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
  31. data/lib/request_log_analyzer/filter.rb +30 -0
  32. data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
  33. data/lib/request_log_analyzer/filter/field.rb +42 -0
  34. data/lib/request_log_analyzer/filter/timespan.rb +45 -0
  35. data/lib/request_log_analyzer/line_definition.rb +111 -0
  36. data/lib/request_log_analyzer/log_processor.rb +99 -0
  37. data/lib/request_log_analyzer/mailer.rb +62 -0
  38. data/lib/request_log_analyzer/output.rb +113 -0
  39. data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
  40. data/lib/request_log_analyzer/output/html.rb +184 -0
  41. data/lib/request_log_analyzer/request.rb +175 -0
  42. data/lib/request_log_analyzer/source.rb +72 -0
  43. data/lib/request_log_analyzer/source/database_loader.rb +87 -0
  44. data/lib/request_log_analyzer/source/log_parser.rb +274 -0
  45. data/lib/request_log_analyzer/tracker.rb +206 -0
  46. data/lib/request_log_analyzer/tracker/duration.rb +104 -0
  47. data/lib/request_log_analyzer/tracker/frequency.rb +95 -0
  48. data/lib/request_log_analyzer/tracker/hourly_spread.rb +107 -0
  49. data/lib/request_log_analyzer/tracker/timespan.rb +81 -0
  50. data/lib/request_log_analyzer/tracker/traffic.rb +106 -0
  51. data/request-log-analyzer.gemspec +40 -0
  52. data/spec/database.yml +23 -0
  53. data/spec/fixtures/apache_combined.log +5 -0
  54. data/spec/fixtures/apache_common.log +10 -0
  55. data/spec/fixtures/decompression.log +12 -0
  56. data/spec/fixtures/decompression.log.bz2 +0 -0
  57. data/spec/fixtures/decompression.log.gz +0 -0
  58. data/spec/fixtures/decompression.log.zip +0 -0
  59. data/spec/fixtures/decompression.tar.gz +0 -0
  60. data/spec/fixtures/decompression.tgz +0 -0
  61. data/spec/fixtures/header_and_footer.log +6 -0
  62. data/spec/fixtures/merb.log +84 -0
  63. data/spec/fixtures/merb_prefixed.log +9 -0
  64. data/spec/fixtures/multiple_files_1.log +5 -0
  65. data/spec/fixtures/multiple_files_2.log +2 -0
  66. data/spec/fixtures/rails.db +0 -0
  67. data/spec/fixtures/rails_1x.log +59 -0
  68. data/spec/fixtures/rails_22.log +12 -0
  69. data/spec/fixtures/rails_22_cached.log +10 -0
  70. data/spec/fixtures/rails_unordered.log +24 -0
  71. data/spec/fixtures/syslog_1x.log +5 -0
  72. data/spec/fixtures/test_file_format.log +13 -0
  73. data/spec/fixtures/test_language_combined.log +14 -0
  74. data/spec/fixtures/test_order.log +16 -0
  75. data/spec/integration/command_line_usage_spec.rb +84 -0
  76. data/spec/integration/munin_plugins_rails_spec.rb +58 -0
  77. data/spec/integration/scout_spec.rb +151 -0
  78. data/spec/lib/helpers.rb +52 -0
  79. data/spec/lib/macros.rb +18 -0
  80. data/spec/lib/matchers.rb +77 -0
  81. data/spec/lib/mocks.rb +76 -0
  82. data/spec/lib/testing_format.rb +46 -0
  83. data/spec/spec_helper.rb +24 -0
  84. data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
  85. data/spec/unit/aggregator/summarizer_spec.rb +26 -0
  86. data/spec/unit/controller/controller_spec.rb +41 -0
  87. data/spec/unit/controller/log_processor_spec.rb +18 -0
  88. data/spec/unit/database/base_class_spec.rb +183 -0
  89. data/spec/unit/database/connection_spec.rb +34 -0
  90. data/spec/unit/database/database_spec.rb +133 -0
  91. data/spec/unit/file_format/amazon_s3_format_spec.rb +49 -0
  92. data/spec/unit/file_format/apache_format_spec.rb +203 -0
  93. data/spec/unit/file_format/file_format_api_spec.rb +69 -0
  94. data/spec/unit/file_format/line_definition_spec.rb +75 -0
  95. data/spec/unit/file_format/merb_format_spec.rb +52 -0
  96. data/spec/unit/file_format/rails_format_spec.rb +164 -0
  97. data/spec/unit/filter/anonymize_filter_spec.rb +21 -0
  98. data/spec/unit/filter/field_filter_spec.rb +66 -0
  99. data/spec/unit/filter/filter_spec.rb +17 -0
  100. data/spec/unit/filter/timespan_filter_spec.rb +58 -0
  101. data/spec/unit/mailer_spec.rb +30 -0
  102. data/spec/unit/request_spec.rb +111 -0
  103. data/spec/unit/source/log_parser_spec.rb +119 -0
  104. data/spec/unit/tracker/duration_tracker_spec.rb +130 -0
  105. data/spec/unit/tracker/frequency_tracker_spec.rb +88 -0
  106. data/spec/unit/tracker/hourly_spread_spec.rb +79 -0
  107. data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
  108. data/spec/unit/tracker/tracker_api_spec.rb +124 -0
  109. data/spec/unit/tracker/traffic_tracker_spec.rb +107 -0
  110. data/tasks/github-gem.rake +323 -0
  111. data/tasks/request_log_analyzer.rake +26 -0
  112. metadata +220 -0
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Mailer, 'mailer' do
4
+
5
+ it "should store printed data" do
6
+ @mailer = RequestLogAnalyzer::Mailer.new('alfa@beta.com', 'localhost', :debug => true)
7
+
8
+ @mailer << 'test1'
9
+ @mailer.puts 'test2'
10
+
11
+ @mailer.data.should eql(['test1', 'test2'])
12
+ end
13
+
14
+ it "should send mail" do
15
+ @mailer = RequestLogAnalyzer::Mailer.new('alfa@beta.com', 'localhost', :debug => true)
16
+
17
+ @mailer << 'test1'
18
+ @mailer.puts 'test2'
19
+
20
+ mail = @mailer.mail
21
+
22
+ mail[0].should include("contact@railsdoctors.com")
23
+ mail[0].should include("test1")
24
+ mail[0].should include("test2")
25
+
26
+ mail[1].should include("contact@railsdoctors.com")
27
+ mail[2].should include("alfa@beta.com")
28
+ end
29
+
30
+ end
@@ -0,0 +1,111 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Request do
4
+
5
+ before(:each) do
6
+ @request = testing_format.request
7
+ end
8
+
9
+ it "should be empty without any captured lines in it" do
10
+ @request.should be_empty
11
+ end
12
+
13
+ context :incomplete do
14
+
15
+ before(:each) do
16
+ @request << { :line_type => :test, :lineno => 1, :test_capture => 'awesome!' }
17
+ end
18
+
19
+ it "should be single if only one line has been added" do
20
+ @request.should_not be_empty
21
+ end
22
+
23
+ it "should not be a completed request" do
24
+ @request.should_not be_completed
25
+ end
26
+
27
+ it "should take the line type of the first line as global line_type" do
28
+ @request.lines[0][:line_type].should == :test
29
+ @request.should =~ :test
30
+ end
31
+
32
+ it "should return the first field value" do
33
+ @request[:test_capture].should == 'awesome!'
34
+ end
35
+
36
+ it "should return nil if no such field is present" do
37
+ @request[:nonexisting].should be_nil
38
+ end
39
+ end
40
+
41
+ context :completed do
42
+
43
+ before(:each) do
44
+ @request << { :line_type => :first, :lineno => 1, :name => 'first line!' }
45
+ @request << { :line_type => :test, :lineno => 4, :test_capture => 'testing' }
46
+ @request << { :line_type => :test, :lineno => 7, :test_capture => 'testing some more' }
47
+ @request << { :line_type => :last, :lineno => 10, :time => 0.03 }
48
+ end
49
+
50
+ it "should not be empty when multiple liness are added" do
51
+ @request.should_not be_empty
52
+ end
53
+
54
+ it "should be a completed request" do
55
+ @request.should be_completed
56
+ end
57
+
58
+ it "should recognize all line types" do
59
+ [:first, :test, :last].each { |type| @request.should =~ type }
60
+ end
61
+
62
+ it "should detect the correct field value" do
63
+ @request[:name].should == 'first line!'
64
+ @request[:time].should == 0.03
65
+ end
66
+
67
+ it "should detect the first matching field value" do
68
+ @request.first(:test_capture).should == 'testing'
69
+ end
70
+
71
+ it "should detect the every matching field value" do
72
+ @request.every(:test_capture).should == ['testing', "testing some more"]
73
+ end
74
+
75
+ it "should set the first_lineno for a request to the lowest lineno encountered" do
76
+ @request.first_lineno.should eql(1)
77
+ end
78
+
79
+ it "should set the first_lineno for a request if a line with a lower lineno is added" do
80
+ @request << { :line_type => :test, :lineno => 0 }
81
+ @request.first_lineno.should eql(0)
82
+ end
83
+
84
+ it "should set the last_lineno for a request to the highest encountered lineno" do
85
+ @request.last_lineno.should eql(10)
86
+ end
87
+
88
+ it "should not set the last_lineno for a request if a line with a lower lineno is added" do
89
+ @request << { :line_type => :test, :lineno => 7 }
90
+ @request.last_lineno.should eql(10)
91
+ end
92
+
93
+ it "should not have a timestamp if no such field is captured" do
94
+ @request.timestamp.should be_nil
95
+ end
96
+
97
+ it "should set return a timestamp field if such a field is captured" do
98
+ @request << { :line_type => :first, :lineno => 1, :name => 'first line!', :timestamp => Time.now}
99
+ @request.timestamp.should_not be_nil
100
+ end
101
+ end
102
+
103
+ context 'single line' do
104
+ # combined is both a header and a footer line
105
+ before(:each) { @request << { :line_type => :combined, :lineno => 1 } }
106
+
107
+ it "should be a completed request if the line is both header and footer" do
108
+ @request.should be_completed
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,119 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Source::LogParser, :requests do
4
+
5
+ before(:each) do
6
+ @log_parser = RequestLogAnalyzer::Source::LogParser.new(testing_format)
7
+ end
8
+
9
+ it "should have multiple line definitions" do
10
+ @log_parser.file_format.line_definitions.length.should >= 2
11
+ end
12
+
13
+ it "should have a valid language" do
14
+ @log_parser.file_format.should be_valid
15
+ end
16
+
17
+ it "should set the :source for every parsed line" do
18
+ @log_parser.parse_file(log_fixture(:rails_22)) do |request|
19
+ request.lines.all? { |line| line[:source] == log_fixture(:rails_22) }.should be_true
20
+ end
21
+ end
22
+
23
+ it "should set the :lineno for every parsed line" do
24
+ @log_parser.parse_file(log_fixture(:rails_22)) do |request|
25
+ request.lines.all? { |line| line.has_key?(:lineno) }.should be_true
26
+ end
27
+ end
28
+
29
+ it "should parse more lines than requests" do
30
+ @log_parser.should_receive(:handle_request).with(an_instance_of(TestingFormat::Request)).twice
31
+ @log_parser.parse_file(log_fixture(:test_language_combined))
32
+ @log_parser.parsed_lines.should > 2
33
+ end
34
+
35
+ it "should parse requests spanned over multiple files" do
36
+ @log_parser.should_receive(:handle_request).with(an_instance_of(TestingFormat::Request)).once
37
+ @log_parser.parse_files([log_fixture(:multiple_files_1), log_fixture(:multiple_files_2)])
38
+ end
39
+
40
+ it "should parse all request values when spanned over multiple files" do
41
+ @log_parser.parse_files([log_fixture(:multiple_files_1), log_fixture(:multiple_files_2)]) do |request|
42
+ request.lines.should have(4).items
43
+ request[:request_no].should == 1
44
+ request[:test_capture].should == "Testing is amazing" # Note the custom converter
45
+ end
46
+ end
47
+
48
+ it "should parse a stream and find valid requests" do
49
+ io = File.new(log_fixture(:test_file_format), 'r')
50
+ @log_parser.parse_io(io) do |request|
51
+ request.should be_kind_of(RequestLogAnalyzer::Request)
52
+ request.should =~ :test
53
+ request[:test_capture].should_not be_nil
54
+ end
55
+ io.close
56
+ end
57
+
58
+ it "should parse a request that only consists of one line" do
59
+ @log_parser.parse_file(log_fixture(:header_and_footer))
60
+ @log_parser.parsed_requests.should == 2
61
+ end
62
+ end
63
+
64
+ describe RequestLogAnalyzer::Source::LogParser, :warnings do
65
+
66
+ before(:each) do
67
+ @log_parser = RequestLogAnalyzer::Source::LogParser.new(testing_format, :parse_strategy => 'cautious')
68
+ end
69
+
70
+ it "should warn about teaser matching problems" do
71
+ @log_parser.should_receive(:warn).with(:teaser_check_failed, anything).exactly(5).times
72
+ @log_parser.parse_file(log_fixture(:test_file_format))
73
+ end
74
+
75
+ it "should warn about unmatching request headers and footers" do
76
+ @log_parser.should_receive(:warn).with(:unclosed_request, anything).at_least(1).times
77
+ @log_parser.should_receive(:warn).with(:no_current_request, anything).at_least(1).times
78
+ @log_parser.should_not_receive(:handle_request)
79
+ @log_parser.parse_file(log_fixture(:test_order))
80
+ end
81
+ end
82
+
83
+ describe RequestLogAnalyzer::Source::LogParser, :decompression do
84
+
85
+ before(:each) do
86
+ @log_parser = RequestLogAnalyzer::Source::LogParser.new(RequestLogAnalyzer::FileFormat::Rails.create)
87
+ end
88
+
89
+ it "should parse a rails gzipped log file" do
90
+ @log_parser.should_receive(:handle_request).once
91
+ @log_parser.parse_file(log_fixture(:decompression, "log.gz"))
92
+ @log_parser.parsed_lines.should > 0
93
+ end
94
+
95
+ it "should parse a rails tar gzipped log folder" do
96
+ @log_parser.should_receive(:handle_request).twice
97
+ @log_parser.parse_file(log_fixture(:decompression, "tar.gz"))
98
+ @log_parser.parsed_lines.should > 1
99
+ end
100
+
101
+ it "should parse a rails tar gzipped log folder" do
102
+ @log_parser.should_receive(:handle_request).twice
103
+ @log_parser.parse_file(log_fixture(:decompression, "tgz"))
104
+ @log_parser.parsed_lines.should > 1
105
+ end
106
+
107
+ it "should parse a rails bz2 zipped log file" do
108
+ @log_parser.should_receive(:handle_request).once
109
+ @log_parser.parse_file(log_fixture(:decompression, "log.bz2"))
110
+ @log_parser.parsed_lines.should > 0
111
+ end
112
+
113
+ it "should parse a rails zipped log file" do
114
+ @log_parser.should_receive(:handle_request).once
115
+ @log_parser.parse_file(log_fixture(:decompression, "log.zip"))
116
+ @log_parser.parsed_lines.should > 0
117
+ end
118
+
119
+ end
@@ -0,0 +1,130 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Tracker::Duration do
4
+
5
+ context 'using a static category' do
6
+
7
+ before(:each) do
8
+ @tracker = RequestLogAnalyzer::Tracker::Duration.new(:duration => :duration, :category => :category)
9
+ @tracker.prepare
10
+ end
11
+
12
+ it "should create a category for every request using the category field" do
13
+ @tracker.update(request(:category => 'a', :duration => 0.2))
14
+ @tracker.categories.keys.should include('a')
15
+ end
16
+
17
+ it "should register a request as hit in the right category" do
18
+ @tracker.update(request(:category => 'a', :duration => 0.2))
19
+ @tracker.update(request(:category => 'b', :duration => 0.3))
20
+ @tracker.update(request(:category => 'b', :duration => 0.4))
21
+
22
+ @tracker.hits('a').should == 1
23
+ @tracker.hits('b').should == 2
24
+ end
25
+ end
26
+
27
+ context 'using a dynamic category' do
28
+ before(:each) do
29
+ @categorizer = Proc.new { |request| request[:duration] > 0.2 ? 'slow' : 'fast' }
30
+ @tracker = RequestLogAnalyzer::Tracker::Duration.new(:duration => :duration, :category => @categorizer)
31
+ @tracker.prepare
32
+ end
33
+
34
+ it "should use the categorizer to determine the right category" do
35
+ @tracker.update(request(:category => 'a', :duration => 0.2))
36
+ @tracker.update(request(:category => 'b', :duration => 0.3))
37
+ @tracker.update(request(:category => 'b', :duration => 0.4))
38
+
39
+ @tracker.hits('fast').should == 1
40
+ @tracker.hits('slow').should == 2
41
+ end
42
+ end
43
+
44
+ describe '#update' do
45
+
46
+ before(:each) do
47
+ @tracker = RequestLogAnalyzer::Tracker::Duration.new(:duration => :duration, :category => :category)
48
+ @tracker.prepare
49
+
50
+ @tracker.update(request(:category => 'a', :duration => 0.4))
51
+ @tracker.update(request(:category => 'a', :duration => 0.2))
52
+ @tracker.update(request(:category => 'a', :duration => 0.3))
53
+ end
54
+
55
+ it "should sum of the durations for a category correctly" do
56
+ @tracker.sum('a').should be_close(0.9, 0.000001)
57
+ end
58
+
59
+ it "should overall sum of the durations correctly" do
60
+ @tracker.sum_overall.should be_close(0.9, 0.000001)
61
+ end
62
+
63
+ it "should keep track of the minimum and maximum duration" do
64
+ @tracker.min('a').should == 0.2
65
+ @tracker.max('a').should == 0.4
66
+ end
67
+
68
+ it "should calculate the mean duration correctly" do
69
+ @tracker.mean('a').should be_close(0.3, 0.000001)
70
+ end
71
+
72
+ it "should calculate the overall mean duration correctly" do
73
+ @tracker.mean_overall.should be_close(0.3, 0.000001)
74
+ end
75
+
76
+ it "should calculate the duration variance correctly" do
77
+ @tracker.variance('a').should be_close(0.01, 0.000001)
78
+ end
79
+
80
+ it "should calculate the duration standard deviation correctly" do
81
+ @tracker.stddev('a').should be_close(0.1, 0.000001)
82
+ end
83
+ end
84
+
85
+ describe '#report' do
86
+
87
+ before(:each) do
88
+ @tracker = RequestLogAnalyzer::Tracker::Duration.new(:category => :category, :duration => :duration)
89
+ @tracker.prepare
90
+ end
91
+
92
+ it "should generate a report without errors when one category is present" do
93
+ @tracker.update(request(:category => 'a', :duration => 0.2))
94
+ lambda { @tracker.report(mock_output) }.should_not raise_error
95
+ end
96
+
97
+ it "should generate a report without errors when no category is present" do
98
+ lambda { @tracker.report(mock_output) }.should_not raise_error
99
+ end
100
+
101
+ it "should generate a report without errors when multiple categories are present" do
102
+ @tracker.update(request(:category => 'a', :duration => 0.2))
103
+ @tracker.update(request(:category => 'b', :duration => 0.2))
104
+ lambda { @tracker.report(mock_output) }.should_not raise_error
105
+ end
106
+
107
+ it "should generate a YAML output" do
108
+ @tracker.update(request(:category => 'a', :duration => 0.2))
109
+ @tracker.update(request(:category => 'b', :duration => 0.2))
110
+ @tracker.to_yaml_object.should == {"a"=>{:hits=>1, :min=>0.2, :mean=>0.2, :max=>0.2, :sum_of_squares=>0.0, :sum=>0.2}, "b"=>{:hits=>1, :min=>0.2, :mean=>0.2, :max=>0.2, :sum_of_squares=>0.0, :sum=>0.2}}
111
+ end
112
+ end
113
+
114
+ describe '#display_value' do
115
+ before(:each) { @tracker = RequestLogAnalyzer::Tracker::Duration.new(:category => :category, :duration => :duration) }
116
+
117
+ it "should only display seconds when time < 60" do
118
+ @tracker.display_value(33.12).should == '33.12s'
119
+ end
120
+
121
+ it "should display minutes and wholeseconds when time > 60" do
122
+ @tracker.display_value(63.12).should == '1m03s'
123
+ end
124
+
125
+ it "should display minutes and wholeseconds when time > 60" do
126
+ @tracker.display_value(3601.12).should == '1h00m01s'
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,88 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Tracker::Frequency do
4
+
5
+ context 'static category' do
6
+ before(:each) do
7
+ @tracker = RequestLogAnalyzer::Tracker::Frequency.new(:category => :category)
8
+ @tracker.prepare
9
+ end
10
+
11
+ it "should register a request in the right category" do
12
+ @tracker.update(request(:category => 'a', :blah => 0.2))
13
+ @tracker.categories.should include('a')
14
+ end
15
+
16
+ it "should register a request in the right category" do
17
+ @tracker.update(request(:category => 'a', :blah => 0.2))
18
+ @tracker.update(request(:category => 'b', :blah => 0.2))
19
+ @tracker.update(request(:category => 'b', :blah => 0.2))
20
+
21
+ @tracker.frequency('a').should == 1
22
+ @tracker.frequency('b').should == 2
23
+ @tracker.overall_frequency.should == 3
24
+ end
25
+
26
+ it "should sort correctly by frequency" do
27
+ @tracker.update(request(:category => 'a', :blah => 0.2))
28
+ @tracker.update(request(:category => 'b', :blah => 0.2))
29
+ @tracker.update(request(:category => 'b', :blah => 0.2))
30
+
31
+ @tracker.sorted_by_frequency.should == [['b', 2], ['a', 1]]
32
+ end
33
+ end
34
+
35
+
36
+ context 'dynamic category' do
37
+ before(:each) do
38
+ @categorizer = Proc.new { |request| request[:duration] > 0.2 ? 'slow' : 'fast' }
39
+ @tracker = RequestLogAnalyzer::Tracker::Frequency.new(:category => @categorizer)
40
+ @tracker.prepare
41
+ end
42
+
43
+ it "should use the categorizer to determine the right category" do
44
+ @tracker.update(request(:category => 'a', :duration => 0.2))
45
+ @tracker.update(request(:category => 'b', :duration => 0.3))
46
+ @tracker.update(request(:category => 'b', :duration => 0.4))
47
+
48
+ @tracker.frequency('fast').should == 1
49
+ @tracker.frequency('slow').should == 2
50
+ @tracker.frequency('moderate').should == 0
51
+ end
52
+ end
53
+
54
+ describe '#report' do
55
+ before(:each) do
56
+ @tracker = RequestLogAnalyzer::Tracker::Frequency.new(:category => :category)
57
+ @tracker.prepare
58
+ end
59
+
60
+ it "should generate a report without errors when one category is present" do
61
+ @tracker.update(request(:category => 'a', :blah => 0.2))
62
+ lambda { @tracker.report(mock_output) }.should_not raise_error
63
+ end
64
+
65
+ it "should generate a report without errors when no category is present" do
66
+ lambda { @tracker.report(mock_output) }.should_not raise_error
67
+ end
68
+
69
+ it "should generate a report without errors when multiple categories are present" do
70
+ @tracker.update(request(:category => 'a', :blah => 0.2))
71
+ @tracker.update(request(:category => 'b', :blah => 0.2))
72
+ lambda { @tracker.report(mock_output) }.should_not raise_error
73
+ end
74
+ end
75
+
76
+ describe '#to_yaml_object' do
77
+ before(:each) do
78
+ @tracker = RequestLogAnalyzer::Tracker::Frequency.new(:category => :category)
79
+ @tracker.prepare
80
+ end
81
+
82
+ it "should generate a YAML output" do
83
+ @tracker.update(request(:category => 'a', :blah => 0.2))
84
+ @tracker.update(request(:category => 'b', :blah => 0.2))
85
+ @tracker.to_yaml_object.should == { "a" => 1, "b" => 1 }
86
+ end
87
+ end
88
+ end