request-log-analyzer 1.1.2 → 1.2.0
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/README.rdoc +1 -1
- data/Rakefile +1 -2
- data/bin/request-log-analyzer +4 -27
- data/lib/cli/progressbar.rb +2 -19
- data/lib/cli/tools.rb +46 -0
- data/lib/request_log_analyzer/aggregator/database.rb +33 -8
- 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.rb +2 -2
- 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 +12 -2
- data/lib/request_log_analyzer/output/fixed_width.rb +14 -3
- data/lib/request_log_analyzer/output/html.rb +1 -0
- data/lib/request_log_analyzer/request.rb +6 -0
- data/lib/request_log_analyzer/source/database.rb +76 -0
- data/lib/request_log_analyzer/source/log_parser.rb +101 -48
- data/lib/request_log_analyzer/source.rb +28 -6
- data/lib/request_log_analyzer/tracker/duration.rb +90 -15
- data/lib/request_log_analyzer/tracker/frequency.rb +22 -10
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +20 -9
- data/lib/request_log_analyzer/tracker/timespan.rb +15 -8
- data/lib/request_log_analyzer.rb +29 -16
- 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 +99 -0
- data/spec/unit/tracker/frequency_tracker_spec.rb +83 -0
- data/spec/unit/tracker/hourly_spread_spec.rb +75 -0
- data/spec/unit/tracker/timespan_tracker_spec.rb +65 -0
- data/spec/unit/tracker/tracker_api_test.rb +45 -0
- data/tasks/rspec.rake +12 -0
- metadata +54 -26
- data/spec/controller_spec.rb +0 -64
- data/spec/filter_spec.rb +0 -157
- data/spec/summarizer_spec.rb +0 -9
|
@@ -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,25 +44,36 @@ 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
|
|
63
|
-
requests_per_day = requests / days
|
|
64
|
-
|
|
65
|
-
rows << ["#{index.to_s.rjust(3)}:00", "#{requests_per_day} hits", ratio]
|
|
75
|
+
requests_per_day = (requests / days).ceil
|
|
76
|
+
rows << ["#{index.to_s.rjust(3)}:00", "%d hits" % requests_per_day, ratio]
|
|
66
77
|
end
|
|
67
78
|
end
|
|
68
79
|
end
|
|
@@ -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
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
require 'date'
|
|
2
|
-
require File.dirname(__FILE__) + '/cli/progressbar'
|
|
3
2
|
|
|
3
|
+
# Satisfy ruby 1.9 sensitivity about encoding.
|
|
4
|
+
if defined? Encoding and Encoding.respond_to? 'default_external='
|
|
5
|
+
Encoding.default_external = 'binary'
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# RequestLogAnalyzer is the base namespace in which all functionality of RequestLogAnalyzer is implemented.
|
|
9
|
+
#
|
|
10
|
+
# - This module itselfs contains some functions to help with class and source file loading.
|
|
11
|
+
# - The actual application resides in the RequestLogAnalyzer::Controller class.
|
|
4
12
|
module RequestLogAnalyzer
|
|
5
|
-
|
|
6
|
-
|
|
13
|
+
|
|
14
|
+
# The current version of request-log-analyzer.
|
|
15
|
+
# This will be diplayed in output reports etc.
|
|
16
|
+
VERSION = '1.1.7'
|
|
17
|
+
|
|
18
|
+
# Loads constants in the RequestLogAnalyzer namespace using self.load_default_class_file(base, const)
|
|
19
|
+
# <tt>const</tt>:: The constant that is not yet loaded in the RequestLogAnalyzer namespace. This should be passed as a string or symbol.
|
|
20
|
+
def self.const_missing(const)
|
|
21
|
+
load_default_class_file(RequestLogAnalyzer, const)
|
|
22
|
+
end
|
|
7
23
|
|
|
8
|
-
#
|
|
24
|
+
# Loads constants that reside in the RequestLogAnalyzer tree using the constant name
|
|
25
|
+
# and its base constant to determine the filename.
|
|
26
|
+
# <tt>base</tt>:: The base constant to load the constant from. This should be Foo when the constant Foo::Bar is being loaded.
|
|
27
|
+
# <tt>const</tt>:: The constant to load from the base constant as a string or symbol. This should be 'Bar' or :Bar when the constant Foo::Bar is being loaded.
|
|
9
28
|
def self.load_default_class_file(base, const)
|
|
10
29
|
path = to_underscore(base.to_s)
|
|
11
30
|
basename = to_underscore(const.to_s)
|
|
@@ -15,22 +34,16 @@ module RequestLogAnalyzer
|
|
|
15
34
|
end
|
|
16
35
|
|
|
17
36
|
# Convert a string/symbol in camelcase (RequestLogAnalyzer::Controller) to underscores (request_log_analyzer/controller)
|
|
37
|
+
# This function can be used to load the file (using require) in which the given constant is defined.
|
|
38
|
+
# <tt>str</tt>:: The string to convert in the following format: <tt>ModuleName::ClassName</tt>
|
|
18
39
|
def self.to_underscore(str)
|
|
19
40
|
str.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
|
|
20
41
|
end
|
|
21
42
|
|
|
22
|
-
# Convert a string/symbol in underscores (request_log_analyzer/controller) to camelcase
|
|
43
|
+
# Convert a string/symbol in underscores (<tt>request_log_analyzer/controller</tt>) to camelcase
|
|
44
|
+
# (<tt>RequestLogAnalyzer::Controller</tt>). This can be used to find the class that is defined in a given filename.
|
|
45
|
+
# <tt>str</tt>:: The string to convert in the following format: <tt>module_name/class_name</tt>
|
|
23
46
|
def self.to_camelcase(str)
|
|
24
|
-
str.to_s.
|
|
47
|
+
str.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
|
25
48
|
end
|
|
26
49
|
end
|
|
27
|
-
|
|
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
|
-
|
|
@@ -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'
|
|
@@ -1,45 +1,41 @@
|
|
|
1
|
-
require File.dirname(__FILE__) + '
|
|
2
|
-
require 'request_log_analyzer/aggregator/database'
|
|
3
|
-
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
4
2
|
|
|
5
3
|
describe RequestLogAnalyzer::Aggregator::Database, "schema creation" do
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
include RequestLogAnalyzer::Spec::Helper
|
|
6
|
+
|
|
10
7
|
before(:each) do
|
|
11
|
-
log_parser = RequestLogAnalyzer::Source::LogParser.new(
|
|
12
|
-
@database_inserter = RequestLogAnalyzer::Aggregator::Database.new(log_parser, :database =>
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
after(:each) do
|
|
16
|
-
File.unlink(TEST_DATABASE_FILE) if File.exist?(TEST_DATABASE_FILE)
|
|
8
|
+
log_parser = RequestLogAnalyzer::Source::LogParser.new(testing_format)
|
|
9
|
+
@database_inserter = RequestLogAnalyzer::Aggregator::Database.new(log_parser, :database => ':memory:')
|
|
17
10
|
end
|
|
11
|
+
|
|
18
12
|
|
|
19
13
|
it "should create the correct tables" do
|
|
20
14
|
ActiveRecord::Migration.should_receive(:create_table).with("warnings")
|
|
21
15
|
ActiveRecord::Migration.should_receive(:create_table).with("requests")
|
|
22
16
|
ActiveRecord::Migration.should_receive(:create_table).with("first_lines")
|
|
23
17
|
ActiveRecord::Migration.should_receive(:create_table).with("test_lines")
|
|
24
|
-
ActiveRecord::Migration.should_receive(:create_table).with("
|
|
18
|
+
ActiveRecord::Migration.should_receive(:create_table).with("eval_lines")
|
|
19
|
+
ActiveRecord::Migration.should_receive(:create_table).with("last_lines")
|
|
25
20
|
|
|
21
|
+
ActiveRecord::Migration.should_receive(:add_index).with("eval_lines", [:request_id])
|
|
26
22
|
ActiveRecord::Migration.should_receive(:add_index).with("first_lines", [:request_id])
|
|
27
|
-
ActiveRecord::Migration.should_receive(:add_index).with("test_lines",
|
|
28
|
-
ActiveRecord::Migration.should_receive(:add_index).with("last_lines",
|
|
23
|
+
ActiveRecord::Migration.should_receive(:add_index).with("test_lines", [:request_id])
|
|
24
|
+
ActiveRecord::Migration.should_receive(:add_index).with("last_lines", [:request_id])
|
|
29
25
|
|
|
30
26
|
@database_inserter.prepare
|
|
31
27
|
end
|
|
32
28
|
|
|
33
29
|
it "should create a default Request class" do
|
|
34
30
|
@database_inserter.prepare
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
TestingFormat::Database::Request.ancestors.should include(ActiveRecord::Base)
|
|
32
|
+
TestingFormat::Database::Request.column_names.should include('first_lineno')
|
|
33
|
+
TestingFormat::Database::Request.column_names.should include('last_lineno')
|
|
38
34
|
end
|
|
39
35
|
|
|
40
36
|
it "should create associations for the default Request class" do
|
|
41
37
|
@database_inserter.prepare
|
|
42
|
-
@request =
|
|
38
|
+
@request = TestingFormat::Database::Request.new
|
|
43
39
|
@request.should respond_to(:test_lines)
|
|
44
40
|
@request.test_lines.should
|
|
45
41
|
end
|
|
@@ -47,7 +43,7 @@ describe RequestLogAnalyzer::Aggregator::Database, "schema creation" do
|
|
|
47
43
|
it "should create the default table names" do
|
|
48
44
|
@database_inserter.prepare
|
|
49
45
|
@database_inserter.file_format.line_definitions.each do |name, definition|
|
|
50
|
-
klass =
|
|
46
|
+
klass = TestingFormat::Database.const_get("#{name}_line".camelize)
|
|
51
47
|
klass.column_names.should include('id')
|
|
52
48
|
klass.column_names.should include('lineno')
|
|
53
49
|
klass.column_names.should include('request_id')
|
|
@@ -57,46 +53,53 @@ describe RequestLogAnalyzer::Aggregator::Database, "schema creation" do
|
|
|
57
53
|
it "should create the correct fields in the table" do
|
|
58
54
|
@database_inserter.prepare
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
TestingFormat::Database::FirstLine.column_names.should include('request_no')
|
|
57
|
+
TestingFormat::Database::LastLine.column_names.should include('request_no')
|
|
58
|
+
TestingFormat::Database::TestLine.column_names.should include('test_capture')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "should create fields for provides" do
|
|
62
|
+
@database_inserter.prepare
|
|
63
|
+
TestingFormat::Database::EvalLine.column_names.should include('evaluated')
|
|
64
|
+
TestingFormat::Database::EvalLine.column_names.should include('greating')
|
|
65
|
+
TestingFormat::Database::EvalLine.column_names.should include('what')
|
|
63
66
|
end
|
|
64
67
|
|
|
65
68
|
end
|
|
66
69
|
|
|
67
70
|
describe RequestLogAnalyzer::Aggregator::Database, "record insertion" do
|
|
68
|
-
include
|
|
71
|
+
include RequestLogAnalyzer::Spec::Helper
|
|
69
72
|
|
|
70
73
|
before(:each) do
|
|
71
|
-
log_parser = RequestLogAnalyzer::Source::LogParser.new(
|
|
72
|
-
@database_inserter = RequestLogAnalyzer::Aggregator::Database.new(log_parser, :database =>
|
|
74
|
+
log_parser = RequestLogAnalyzer::Source::LogParser.new(testing_format)
|
|
75
|
+
@database_inserter = RequestLogAnalyzer::Aggregator::Database.new(log_parser, :database => ':memory:')
|
|
73
76
|
@database_inserter.prepare
|
|
74
77
|
|
|
75
|
-
@incomplete_request =
|
|
76
|
-
@completed_request =
|
|
77
|
-
{:line_type => :test, :test_capture => "
|
|
78
|
+
@incomplete_request = testing_format.request( {:line_type => :first, :request_no => 564})
|
|
79
|
+
@completed_request = testing_format.request( {:line_type => :first, :request_no => 564},
|
|
80
|
+
{:line_type => :test, :test_capture => "awesome"},
|
|
81
|
+
{:line_type => :test, :test_capture => "indeed"},
|
|
82
|
+
{:line_type => :eval, :evaluated => { :greating => 'howdy'}, :greating => 'howdy' },
|
|
83
|
+
{:line_type => :last, :request_no => 564})
|
|
78
84
|
end
|
|
79
85
|
|
|
80
|
-
after(:each) do
|
|
81
|
-
File.unlink(TEST_DATABASE_FILE) if File.exist?(TEST_DATABASE_FILE)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
86
|
it "should insert a record in the request table" do
|
|
85
|
-
|
|
87
|
+
TestingFormat::Database::Request.count.should == 0
|
|
86
88
|
@database_inserter.aggregate(@incomplete_request)
|
|
87
|
-
|
|
89
|
+
TestingFormat::Database::Request.count.should == 1
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
it "should insert records in all relevant line tables" do
|
|
91
93
|
@database_inserter.aggregate(@completed_request)
|
|
92
|
-
request =
|
|
94
|
+
request = TestingFormat::Database::Request.first
|
|
93
95
|
request.should have(2).test_lines
|
|
94
96
|
request.should have(1).first_lines
|
|
97
|
+
request.should have(1).eval_lines
|
|
95
98
|
request.should have(1).last_lines
|
|
96
99
|
end
|
|
97
100
|
|
|
98
101
|
it "should log a warning in the warnings table" do
|
|
99
|
-
|
|
102
|
+
TestingFormat::Database::Warning.should_receive(:create!).with(hash_including(:warning_type => 'test_warning'))
|
|
100
103
|
@database_inserter.warning(:test_warning, "Testing the warning system", 12)
|
|
101
104
|
end
|
|
102
105
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RequestLogAnalyzer::Aggregator::Summarizer do
|
|
4
|
+
|
|
5
|
+
include RequestLogAnalyzer::Spec::Helper
|
|
6
|
+
|
|
7
|
+
before(:each) do
|
|
8
|
+
@summarizer = RequestLogAnalyzer::Aggregator::Summarizer.new(mock_source, :output => mock_output)
|
|
9
|
+
@summarizer.prepare
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "not raise exception when creating a report after aggregating multiple requests" do
|
|
13
|
+
@summarizer.aggregate(request(:data => 'bluh1'))
|
|
14
|
+
@summarizer.aggregate(request(:data => 'bluh2'))
|
|
15
|
+
|
|
16
|
+
lambda { @summarizer.report(mock_output) }.should_not raise_error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "not raise exception when creating a report after aggregating a single request" do
|
|
20
|
+
@summarizer.aggregate(request(:data => 'bluh1'))
|
|
21
|
+
lambda { @summarizer.report(mock_output) }.should_not raise_error
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "not raise exception when creating a report after aggregating no requests" do
|
|
25
|
+
lambda { @summarizer.report(mock_output) }.should_not raise_error
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
|
|
3
|
+
describe RequestLogAnalyzer::Controller do
|
|
4
|
+
|
|
5
|
+
include RequestLogAnalyzer::Spec::Helper
|
|
6
|
+
|
|
7
|
+
it "should use a custom output generator correctly" do
|
|
8
|
+
|
|
9
|
+
mock_output = mock('RequestLogAnalyzer::Output::Base')
|
|
10
|
+
mock_output.stub!(:io).and_return(mock_io)
|
|
11
|
+
mock_output.should_receive(:header)
|
|
12
|
+
mock_output.should_receive(:footer)
|
|
13
|
+
|
|
14
|
+
controller = RequestLogAnalyzer::Controller.new(mock_source, :output => mock_output)
|
|
15
|
+
|
|
16
|
+
controller.run!
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "should call aggregators correctly when run" do
|
|
20
|
+
controller = RequestLogAnalyzer::Controller.new(mock_source, :output => mock_output)
|
|
21
|
+
|
|
22
|
+
mock_aggregator = mock('RequestLogAnalyzer::Aggregator::Base')
|
|
23
|
+
mock_aggregator.should_receive(:prepare).once.ordered
|
|
24
|
+
mock_aggregator.should_receive(:aggregate).with(an_instance_of(testing_format.request_class)).twice.ordered
|
|
25
|
+
mock_aggregator.should_receive(:finalize).once.ordered
|
|
26
|
+
mock_aggregator.should_receive(:report).once.ordered
|
|
27
|
+
|
|
28
|
+
controller.aggregators << mock_aggregator
|
|
29
|
+
controller.run!
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "should call filters when run" do
|
|
33
|
+
controller = RequestLogAnalyzer::Controller.new(mock_source, :output => mock_output)
|
|
34
|
+
|
|
35
|
+
mock_filter = mock('RequestLogAnalyzer::Filter::Base')
|
|
36
|
+
mock_filter.should_receive(:filter).twice.and_return(nil)
|
|
37
|
+
controller.should_receive(:aggregate_request).twice.and_return(nil)
|
|
38
|
+
|
|
39
|
+
controller.filters << mock_filter
|
|
40
|
+
controller.run!
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
require File.dirname(__FILE__) + '
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
|
2
|
+
|
|
2
3
|
require 'request_log_analyzer/log_processor'
|
|
3
4
|
|
|
4
5
|
describe RequestLogAnalyzer::LogProcessor, 'stripping log files' do
|
|
5
6
|
|
|
6
|
-
include
|
|
7
|
+
include RequestLogAnalyzer::Spec::Helper
|
|
7
8
|
|
|
8
9
|
before(:each) do
|
|
9
|
-
@log_stripper = RequestLogAnalyzer::LogProcessor.new(
|
|
10
|
+
@log_stripper = RequestLogAnalyzer::LogProcessor.new(testing_format, :strip, {})
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
it "should remove a junk line" do
|