request-log-analyzer 1.3.4 → 1.3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -57,11 +57,14 @@ module RequestLogAnalyzer::Aggregator
57
57
 
58
58
  # Records source changes in the sources table
59
59
  def source_change(change, filename)
60
- case change
61
- when :started
62
- @sources[filename] = database.source_class.create!(:filename => filename)
63
- when :finished
64
- @sources[filename].update_attributes!(:filesize => File.size(filename), :mtime => File.mtime(filename))
60
+ if File.exist?(filename)
61
+ case change
62
+ when :started
63
+ p database.source_class
64
+ @sources[filename] = database.source_class.create!(:filename => filename)
65
+ when :finished
66
+ @sources[filename].update_attributes!(:filesize => File.size(filename), :mtime => File.mtime(filename))
67
+ end
65
68
  end
66
69
  end
67
70
 
@@ -13,8 +13,9 @@ class RequestLogAnalyzer::Database::Base < ActiveRecord::Base
13
13
  def line_type
14
14
  self.class.name.underscore.gsub(/_line$/, '').to_sym
15
15
  end
16
-
17
- cattr_accessor :database, :line_definition
16
+
17
+ class_inheritable_accessor :line_definition
18
+ cattr_accessor :database
18
19
 
19
20
  def self.subclass_from_line_definition(definition)
20
21
  klass = Class.new(RequestLogAnalyzer::Database::Base)
@@ -9,7 +9,7 @@ module RequestLogAnalyzer::FileFormat
9
9
  line_definition :access do |line|
10
10
  line.header = true
11
11
  line.footer = true
12
- line.regexp = /^([^\ ]+) ([^\ ]+) \[([^\]]{26})\] (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) ([^\ ]+) ([^\ ]+) (\w+(?:\.\w+)*) ([^\ ]+) "([^"]+)" (\d+) ([^\ ]+) (\d+) (\d+) (\d+) (\d+) "([^"]+)" "([^"]+)"/
12
+ line.regexp = /^([^\ ]+) ([^\ ]+) \[(\d{2}\/[A-Za-z]{3}\/\d{4}.\d{2}:\d{2}:\d{2})(?: .\d{4})?\] (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) ([^\ ]+) ([^\ ]+) (\w+(?:\.\w+)*) ([^\ ]+) "([^"]+)" (\d+) ([^\ ]+) (\d+) (\d+) (\d+) (\d+) "([^"]+)" "([^"]+)"/
13
13
  line.captures << { :name => :bucket_owner, :type => :string } <<
14
14
  { :name => :bucket, :type => :string } <<
15
15
  { :name => :timestamp, :type => :timestamp } <<
@@ -21,8 +21,8 @@ module RequestLogAnalyzer::FileFormat
21
21
  { :name => :request_uri, :type => :string } <<
22
22
  { :name => :http_status, :type => :integer } <<
23
23
  { :name => :error_code, :type => :nillable_string } <<
24
- { :name => :bytes_sent, :type => :integer } <<
25
- { :name => :object_size, :type => :integer } <<
24
+ { :name => :bytes_sent, :type => :traffic, :unit => :byte } <<
25
+ { :name => :object_size, :type => :traffic, :unit => :byte } <<
26
26
  { :name => :total_time, :type => :duration, :unit => :msec } <<
27
27
  { :name => :turnaround_time, :type => :duration, :unit => :msec } <<
28
28
  { :name => :referer, :type => :referer } <<
@@ -34,7 +34,8 @@ module RequestLogAnalyzer::FileFormat
34
34
  analyze.hourly_spread
35
35
 
36
36
  analyze.frequency :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Most popular items"
37
- analyze.duration :duration => :total_time, :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Duration"
37
+ analyze.duration :duration => :total_time, :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Request duration"
38
+ analyze.traffic :traffic => :bytes_sent, :category => lambda { |r| "#{r[:bucket]}/#{r[:key]}"}, :amount => 20, :title => "Traffic"
38
39
  analyze.frequency :category => :http_status, :title => 'HTTP status codes'
39
40
  analyze.frequency :category => :error_code, :title => 'Error codes'
40
41
  end
@@ -47,7 +48,7 @@ module RequestLogAnalyzer::FileFormat
47
48
  # Do not use DateTime.parse, but parse the timestamp ourselves to return a integer
48
49
  # to speed up parsing.
49
50
  def convert_timestamp(value, definition)
50
- d = /^(\d{2})\/(\w{3})\/(\d{4}):(\d{2}):(\d{2}):(\d{2})/.match(value).captures
51
+ d = /^(\d{2})\/([A-Za-z]{3})\/(\d{4}).(\d{2}):(\d{2}):(\d{2})/.match(value).captures
51
52
  "#{d[2]}#{MONTHS[d[1]]}#{d[0]}#{d[3]}#{d[4]}#{d[5]}".to_i
52
53
  end
53
54
 
@@ -17,26 +17,30 @@ module RequestLogAnalyzer::FileFormat
17
17
  # A hash of predefined Apache log formats
18
18
  LOG_FORMAT_DEFAULTS = {
19
19
  :common => '%h %l %u %t "%r" %>s %b',
20
- :combined => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"'
20
+ :combined => '%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-agent}i"',
21
+ :rack => '%h %l %u %t "%r" %>s %b %T',
22
+ :referer => '%{Referer}i -> %U',
23
+ :agent => '%{User-agent}i'
21
24
  }
22
-
25
+
23
26
  # A hash that defines how the log format directives should be parsed.
24
27
  LOG_DIRECTIVES = {
25
28
  '%' => { :regexp => '%', :captures => [] },
26
29
  'h' => { :regexp => '([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+)', :captures => [{:name => :remote_host, :type => :string}] },
27
30
  'a' => { :regexp => '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', :captures => [{:name => :remote_ip, :type => :string}] },
28
- 'b' => { :regexp => '(\d+|-)', :captures => [{:name => :bytes_sent, :type => :integer}] },
31
+ 'b' => { :regexp => '(\d+|-)', :captures => [{:name => :bytes_sent, :type => :traffic}] },
29
32
  'c' => { :regexp => '(\+|\-|\X)', :captures => [{:name => :connection_status, :type => :integer}] },
30
- 'D' => { :regexp => '(\d+|-)', :captures => [{:name => :duration, :type => :duration, :unit => :msec}] },
33
+ 'D' => { :regexp => '(\d+|-)', :captures => [{:name => :duration, :type => :duration, :unit => :musec}] },
31
34
  'l' => { :regexp => '([\w-]+)', :captures => [{:name => :remote_logname, :type => :nillable_string}] },
32
- 'T' => { :regexp => '(\d+|-)', :captures => [{:name => :duration, :type => :duration, :unit => :sec}] },
33
- 't' => { :regexp => '\[([^\]]{26})\]', :captures => [{:name => :timestamp, :type => :timestamp}] },
35
+ 'T' => { :regexp => '((?:\d+(?:\.\d+))|-)', :captures => [{:name => :duration, :type => :duration, :unit => :sec}] },
36
+ 't' => { :regexp => '\[(\d{2}\/[A-Za-z]{3}\/\d{4}.\d{2}:\d{2}:\d{2})(?: .\d{4})?\]', :captures => [{:name => :timestamp, :type => :timestamp}] },
34
37
  's' => { :regexp => '(\d{3})', :captures => [{:name => :http_status, :type => :integer}] },
35
38
  'u' => { :regexp => '(\w+|-)', :captures => [{:name => :user, :type => :nillable_string}] },
36
- 'r' => { :regexp => '([A-Z]+) ([^\s]+) HTTP\/(\d+(?:\.\d+)*)', :captures => [{:name => :http_method, :type => :string},
39
+ 'U' => { :regexp => '(\/\S*)', :captures => [{:name => :path, :type => :string}] },
40
+ 'r' => { :regexp => '([A-Z]+) (\S+) HTTP\/(\d+(?:\.\d+)*)', :captures => [{:name => :http_method, :type => :string},
37
41
  {:name => :path, :type => :path}, {:name => :http_version, :type => :string}]},
38
- 'i' => { 'Referer' => { :regexp => '([^\s]+)', :captures => [{:name => :referer, :type => :nillable_string}] },
39
- 'User-agent' => { :regexp => '(.*)', :captures => [{:name => :user_agent, :type => :user_agent}] }
42
+ 'i' => { 'Referer' => { :regexp => '(\S+)', :captures => [{:name => :referer, :type => :nillable_string}] },
43
+ 'User-agent' => { :regexp => '(.*)', :captures => [{:name => :user_agent, :type => :user_agent}] }
40
44
  }
41
45
  }
42
46
 
@@ -97,6 +101,10 @@ module RequestLogAnalyzer::FileFormat
97
101
  analyze.duration :duration => :duration, :category => :path , :title => 'Request duration'
98
102
  end
99
103
 
104
+ if line_definition.captures?(:path) && line_definition.captures?(:bytes_sent)
105
+ analyze.traffic :traffic => :bytes_sent, :category => :path , :title => 'Traffic'
106
+ end
107
+
100
108
  return analyze.trackers
101
109
  end
102
110
 
@@ -109,7 +117,7 @@ module RequestLogAnalyzer::FileFormat
109
117
  # Do not use DateTime.parse, but parse the timestamp ourselves to return a integer
110
118
  # to speed up parsing.
111
119
  def convert_timestamp(value, definition)
112
- d = /^(\d{2})\/(\w{3})\/(\d{4}):(\d{2}):(\d{2}):(\d{2})/.match(value).captures
120
+ d = /^(\d{2})\/([A-Za-z]{3})\/(\d{4}).(\d{2}):(\d{2}):(\d{2})/.match(value).captures
113
121
  "#{d[2]}#{MONTHS[d[1]]}#{d[0]}#{d[3]}#{d[4]}#{d[5]}".to_i
114
122
  end
115
123
 
@@ -0,0 +1,11 @@
1
+ module RequestLogAnalyzer::FileFormat
2
+
3
+ class Rack < Apache
4
+
5
+ def self.create(*args)
6
+ super(:rack, *args)
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -104,7 +104,7 @@ module RequestLogAnalyzer::Output
104
104
  end
105
105
 
106
106
  # Write a link
107
- # <tt>text</tt> The text in the link
107
+ # <tt>text</tt> The text in the link, or the URL itself if no text is given
108
108
  # <tt>url</tt> The url to link to.
109
109
  def link(text, url = nil)
110
110
  if url.nil?
@@ -114,13 +114,13 @@ module RequestLogAnalyzer::Output
114
114
  end
115
115
  end
116
116
 
117
- # Generate a header for a report
117
+ # Generate a header for a report
118
118
  def header
119
119
  if io.kind_of?(File)
120
- puts "Request-log-analyzer summary report"
121
- line
120
+ puts colorize("Request-log-analyzer summary report", :white, :bold)
121
+ line(:green)
122
122
  puts "Version #{RequestLogAnalyzer::VERSION} - written by Willem van Bergen and Bart ten Brinke"
123
- puts "Request-log-analyzer website: http://github.com/wvanbergen/request-log-analyzer"
123
+ puts "Website: #{link('http://github.com/wvanbergen/request-log-analyzer')}"
124
124
  end
125
125
  end
126
126
 
@@ -128,8 +128,9 @@ module RequestLogAnalyzer::Output
128
128
  def footer
129
129
  puts
130
130
  puts "Need an expert to analyze your application?"
131
- puts "Mail to contact@railsdoctors.com or visit us at http://railsdoctors.com"
132
- puts "Thanks for using request-log-analyzer!"
131
+ puts "Mail to #{link('contact@railsdoctors.com')} or visit us at #{link('http://railsdoctors.com')}."
132
+ line(:green)
133
+ puts "Thanks for using #{colorize('request-log-analyzer', :white, :bold)}!"
133
134
  end
134
135
 
135
136
  # Generate a report table and push it into the output object.
@@ -41,13 +41,25 @@ module RequestLogAnalyzer
41
41
  DateTime.parse(value).strftime('%Y%m%d%H%M%S').to_i unless value.nil?
42
42
  end
43
43
 
44
+ def convert_traffic(value, capture_definition)
45
+ return nil if value.nil?
46
+ case capture_definition[:unit]
47
+ when :GB, :G, :gigabyte then (value.to_f * 1000_000_000).round
48
+ when :GiB, :gibibyte then (value.to_f * (2 ** 30)).round
49
+ when :MB, :M, :megabyte then (value.to_f * 1000_000).round
50
+ when :MiB, :mebibyte then (value.to_f * (2 ** 20)).round
51
+ when :KB, :K, :kilobyte, :kB then (value.to_f * 1000).round
52
+ when :KiB, :kibibyte then (value.to_f * (2 ** 10)).round
53
+ else value.to_i
54
+ end
55
+ end
56
+
44
57
  def convert_duration(value, capture_definition)
45
- if value.nil?
46
- nil
47
- elsif capture_definition[:unit] == :msec
48
- value.to_f / 1000.0
49
- else
50
- value.to_f
58
+ return nil if value.nil?
59
+ case capture_definition[:unit]
60
+ when :microsec, :musec then value.to_f / 1000000.0
61
+ when :msec, :millisec then value.to_f / 1000.0
62
+ else value.to_f
51
63
  end
52
64
  end
53
65
  end
@@ -51,10 +51,10 @@ module RequestLogAnalyzer::Tracker
51
51
  raise "Capture mismatch for multiple values in a request"
52
52
  end
53
53
  else
54
- category = @categorizer.call(request) # options[:category].respond_to?(:call) ? options[:category].call(request) : request[options[:category]]
55
- duration = @durationizer.call(request) # options[:duration].respond_to?(:call) ? options[:duration].call(request) : request[options[:duration]]
54
+ category = @categorizer.call(request)
55
+ duration = @durationizer.call(request)
56
56
 
57
- if duration.kind_of?(Float) && category.kind_of?(String)
57
+ if duration.kind_of?(Numeric) && !category.nil?
58
58
  @categories[category] ||= {:hits => 0, :cumulative => 0.0, :min => duration, :max => duration }
59
59
  @categories[category][:hits] += 1
60
60
  @categories[category][:cumulative] += duration
@@ -0,0 +1,186 @@
1
+ module RequestLogAnalyzer::Tracker
2
+
3
+ # Analyze the average and total traffic of requests
4
+ #
5
+ # === Options
6
+ # * <tt>:amount</tt> The amount of lines in the report
7
+ # * <tt>:category</tt> Proc that handles request categorization for given fileformat (REQUEST_CATEGORIZER)
8
+ # * <tt>:traffic</tt> The field containing the duration in the request hash.
9
+ # * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
10
+ # * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
11
+ # * <tt>:title</tt> Title do be displayed above the report
12
+ # * <tt>:unless</tt> Handle request if this proc is false for the handled request.
13
+ class Traffic < Base
14
+
15
+ attr_reader :categories
16
+
17
+ # Check if duration and catagory option have been received,
18
+ def prepare
19
+ raise "No traffic field set up for category tracker #{self.inspect}" unless options[:traffic]
20
+ raise "No categorizer set up for duration tracker #{self.inspect}" unless options[:category]
21
+
22
+ @categorizer = options[:category].respond_to?(:call) ? options[:category] : lambda { |request| request[options[:category]] }
23
+ @trafficizer = options[:traffic].respond_to?(:call) ? options[:traffic] : lambda { |request| request[options[:traffic]] }
24
+ @categories = {}
25
+ end
26
+
27
+ # Get the duration information fron the request and store it in the different categories.
28
+ # <tt>request</tt> The request.
29
+ def update(request)
30
+ category = @categorizer.call(request)
31
+ traffic = @trafficizer.call(request)
32
+
33
+ if traffic.kind_of?(Numeric) && !category.nil?
34
+ @categories[category] ||= {:hits => 0, :cumulative => 0, :min => traffic, :max => traffic }
35
+ @categories[category][:hits] += 1
36
+ @categories[category][:cumulative] += traffic
37
+ @categories[category][:min] = traffic if traffic < @categories[category][:min]
38
+ @categories[category][:max] = traffic if traffic > @categories[category][:max]
39
+ end
40
+ end
41
+
42
+ # Get the number of hits of a specific category.
43
+ # <tt>cat</tt> The category
44
+ def hits(cat)
45
+ categories[cat][:hits]
46
+ end
47
+
48
+ # Get the total duration of a specific category.
49
+ # <tt>cat</tt> The category
50
+ def cumulative_traffic(cat)
51
+ categories[cat][:cumulative]
52
+ end
53
+
54
+ # Get the minimal duration of a specific category.
55
+ # <tt>cat</tt> The category
56
+ def min_traffic(cat)
57
+ categories[cat][:min]
58
+ end
59
+
60
+ # Get the maximum duration of a specific category.
61
+ # <tt>cat</tt> The category
62
+ def max_traffic(cat)
63
+ categories[cat][:max]
64
+ end
65
+
66
+ # Get the average duration of a specific category.
67
+ # <tt>cat</tt> The category
68
+ def average_traffic(cat)
69
+ categories[cat][:cumulative].to_f / categories[cat][:hits]
70
+ end
71
+
72
+ # Get the average duration of a all categories.
73
+ def overall_average_traffic
74
+ overall_cumulative_duration.to_f / overall_hits
75
+ end
76
+
77
+ # Get the cumlative duration of a all categories.
78
+ def overall_cumulative_traffic
79
+ categories.inject(0) { |sum, (name, cat)| sum + cat[:cumulative] }
80
+ end
81
+
82
+ # Get the total hits of a all categories.
83
+ def overall_hits
84
+ categories.inject(0) { |sum, (name, cat)| sum + cat[:hits] }
85
+ end
86
+
87
+ # Return categories sorted by hits.
88
+ def sorted_by_hits
89
+ sorted_by(:hits)
90
+ end
91
+
92
+ # Return categories sorted by cumulative duration.
93
+ def sorted_by_cumulative
94
+ sorted_by(:cumulative)
95
+ end
96
+
97
+ # Return categories sorted by cumulative duration.
98
+ def sorted_by_average
99
+ sorted_by { |cat| cat[:cumulative].to_f / cat[:hits] }
100
+ end
101
+
102
+ # Return categories sorted by a given key.
103
+ # <tt>by</tt> The key.
104
+ def sorted_by(by = nil)
105
+ if block_given?
106
+ categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) }
107
+ else
108
+ categories.sort { |a, b| b[1][by] <=> a[1][by] }
109
+ end
110
+ end
111
+
112
+ # Block function to build a result table using a provided sorting function.
113
+ # <tt>output</tt> The output object.
114
+ # <tt>amount</tt> The number of rows in the report table (default 10).
115
+ # === Options
116
+ # * </tt>:title</tt> The title of the table
117
+ # * </tt>:sort</tt> The key to sort on (:hits, :cumulative, :average, :min or :max)
118
+ def report_table(output, amount = 10, options = {}, &block)
119
+
120
+ output.title(options[:title])
121
+
122
+ top_categories = @categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) }.slice(0...amount)
123
+ output.table({:title => 'Category', :width => :rest},
124
+ {:title => 'Hits', :align => :right, :highlight => (options[:sort] == :hits), :min_width => 4},
125
+ {:title => 'Cumulative', :align => :right, :highlight => (options[:sort] == :cumulative), :min_width => 10},
126
+ {:title => 'Average', :align => :right, :highlight => (options[:sort] == :average), :min_width => 8},
127
+ {:title => 'Min', :align => :right, :highlight => (options[:sort] == :min)},
128
+ {:title => 'Max', :align => :right, :highlight => (options[:sort] == :max)}) do |rows|
129
+
130
+ top_categories.each do |(cat, info)|
131
+ rows << [cat, info[:hits], format_traffic(info[:cumulative]), format_traffic((info[:cumulative] / info[:hits]).round),
132
+ format_traffic(info[:min]), format_traffic(info[:max])]
133
+ end
134
+ end
135
+ end
136
+
137
+ # Formats the traffic number using x B/kB/MB/GB etc notation
138
+ def format_traffic(bytes)
139
+ return "0 B" if bytes.zero?
140
+ case Math.log10(bytes).floor
141
+ when 1...4 then '%d B' % bytes
142
+ when 4...7 then '%d kB' % (bytes / 1000)
143
+ when 7...10 then '%d MB' % (bytes / 1000_000)
144
+ when 10...13 then '%d GB' % (bytes / 1000_000_000)
145
+ else '%d TB' % (bytes / 1000_000_000_000)
146
+ end
147
+ end
148
+
149
+ # Generate a request duration report to the given output object
150
+ # By default colulative and average duration are generated.
151
+ # Any options for the report should have been set during initialize.
152
+ # <tt>output</tt> The output object
153
+ def report(output)
154
+
155
+ options[:report] ||= [:cumulative, :average]
156
+ options[:top] ||= 20
157
+
158
+ options[:report].each do |report|
159
+ case report
160
+ when :average
161
+ report_table(output, options[:top], :title => "#{title} - top #{options[:top]} by average", :sort => :average) { |cat| cat[:cumulative] / cat[:hits] }
162
+ when :cumulative
163
+ report_table(output, options[:top], :title => "#{title} - top #{options[:top]} by sum", :sort => :cumulative) { |cat| cat[:cumulative] }
164
+ when :hits
165
+ report_table(output, options[:top], :title => "#{title} - top #{options[:top]} by hits", :sort => :hits) { |cat| cat[:hits] }
166
+ else
167
+ raise "Unknown duration report specified: #{report}!"
168
+ end
169
+ end
170
+
171
+ output.puts
172
+ output.puts "#{output.colorize(title, :white, :bold)} - observed total: " + output.colorize(format_traffic(overall_cumulative_traffic), :brown, :bold)
173
+ end
174
+
175
+ # Returns the title of this tracker for reports
176
+ def title
177
+ options[:title] || 'Request traffic'
178
+ end
179
+
180
+ # Returns all the categories and the tracked duration as a hash than can be exported to YAML
181
+ def to_yaml_object
182
+ return nil if @categories.empty?
183
+ @categories
184
+ end
185
+ end
186
+ end
@@ -10,8 +10,8 @@ Encoding.default_external = 'binary' if defined? Encoding and Encoding.respond_t
10
10
  module RequestLogAnalyzer
11
11
 
12
12
  # The current version of request-log-analyzer.
13
- # This will be diplayed in output reports etc.
14
- VERSION = "1.3.4"
13
+ # This will be diplayed in output reports etc.
14
+ VERSION = "1.3.5"
15
15
 
16
16
  # Loads constants in the RequestLogAnalyzer namespace using self.load_default_class_file(base, const)
17
17
  # <tt>const</tt>:: The constant that is not yet loaded in the RequestLogAnalyzer namespace. This should be passed as a string or symbol.
@@ -25,7 +25,7 @@ module RequestLogAnalyzer
25
25
  # <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.
26
26
  def self.load_default_class_file(base, const)
27
27
  require "#{to_underscore("#{base.name}::#{const}")}"
28
- base.const_get(const)
28
+ base.const_get(const) if base.const_defined?(const)
29
29
  end
30
30
 
31
31
  # Convert a string/symbol in camelcase (RequestLogAnalyzer::Controller) to underscores (request_log_analyzer/controller)
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "request-log-analyzer"
3
- s.version = "1.3.4"
4
- s.date = "2009-09-14"
3
+ s.version = "1.3.5"
4
+ s.date = "2009-09-16"
5
5
 
6
6
  s.rubyforge_project = 'r-l-a'
7
7
 
@@ -31,6 +31,6 @@ Gem::Specification.new do |s|
31
31
  s.email = ['willem@railsdoctors.com', 'bart@railsdoctors.com']
32
32
  s.homepage = 'http://railsdoctors.com'
33
33
 
34
- s.files = %w(spec/unit/filter/anonymize_filter_spec.rb lib/request_log_analyzer/line_definition.rb lib/request_log_analyzer/output/html.rb lib/request_log_analyzer/controller.rb spec/fixtures/rails_22_cached.log spec/lib/macros.rb lib/request_log_analyzer/file_format/rails_development.rb spec/fixtures/apache_combined.log spec/fixtures/apache_common.log spec/fixtures/merb_prefixed.log lib/request_log_analyzer/file_format/amazon_s3.rb tasks/request_log_analyzer.rake spec/unit/file_format/file_format_api_spec.rb spec/unit/file_format/apache_format_spec.rb spec/integration/command_line_usage_spec.rb lib/request_log_analyzer/database.rb spec/fixtures/decompression.log.bz2 lib/request_log_analyzer/log_processor.rb lib/request_log_analyzer/tracker.rb lib/request_log_analyzer/filter.rb spec/fixtures/rails_unordered.log bin/request-log-analyzer request-log-analyzer.gemspec DESIGN.rdoc spec/unit/filter/timespan_filter_spec.rb spec/unit/aggregator/database_inserter_spec.rb spec/lib/matchers.rb lib/request_log_analyzer/filter/field.rb lib/request_log_analyzer/tracker/frequency.rb spec/fixtures/decompression.log.gz spec/fixtures/decompression.log spec/lib/testing_format.rb spec/fixtures/test_order.log spec/fixtures/rails.db lib/request_log_analyzer/output/fixed_width.rb lib/request_log_analyzer/filter/anonymize.rb lib/request_log_analyzer/tracker/timespan.rb lib/request_log_analyzer/database/base.rb lib/request_log_analyzer/aggregator.rb lib/cli/progressbar.rb lib/request_log_analyzer/mailer.rb README.rdoc spec/fixtures/merb.log lib/request_log_analyzer/tracker/hourly_spread.rb .gitignore spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb spec/unit/file_format/amazon_s3_format_spec.rb lib/request_log_analyzer/aggregator/echo.rb spec/unit/controller/log_processor_spec.rb spec/spec_helper.rb lib/request_log_analyzer.rb spec/database.yml Rakefile lib/request_log_analyzer/database/connection.rb spec/unit/filter/filter_spec.rb spec/fixtures/test_language_combined.log lib/request_log_analyzer/aggregator/database_inserter.rb lib/request_log_analyzer/aggregator/summarizer.rb lib/request_log_analyzer/file_format/rails.rb spec/fixtures/decompression.tar.gz spec/unit/filter/field_filter_spec.rb spec/unit/database/base_class_spec.rb lib/request_log_analyzer/filter/timespan.rb lib/request_log_analyzer/source/log_parser.rb spec/fixtures/decompression.tgz spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/fixtures/header_and_footer.log lib/cli/tools.rb lib/request_log_analyzer/file_format/merb.rb spec/fixtures/multiple_files_1.log spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb lib/request_log_analyzer/source.rb lib/request_log_analyzer/request.rb lib/cli/database_console.rb spec/unit/database/connection_spec.rb spec/unit/controller/controller_spec.rb spec/lib/mocks.rb spec/lib/helpers.rb lib/cli/database_console_init.rb lib/request_log_analyzer/output.rb lib/request_log_analyzer/file_format/apache.rb spec/fixtures/rails_1x.log spec/fixtures/decompression.log.zip spec/unit/source/request_spec.rb spec/unit/source/log_parser_spec.rb spec/fixtures/test_file_format.log tasks/github-gem.rake spec/unit/database/database_spec.rb lib/request_log_analyzer/tracker/duration.rb lib/request_log_analyzer/file_format.rb spec/unit/aggregator/summarizer_spec.rb spec/fixtures/rails_22.log spec/fixtures/multiple_files_2.log spec/fixtures/syslog_1x.log LICENSE lib/request_log_analyzer/source/database_loader.rb spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb lib/cli/command_line_arguments.rb)
35
- s.test_files = %w(spec/unit/filter/anonymize_filter_spec.rb spec/unit/file_format/file_format_api_spec.rb spec/unit/file_format/apache_format_spec.rb spec/integration/command_line_usage_spec.rb spec/unit/filter/timespan_filter_spec.rb spec/unit/aggregator/database_inserter_spec.rb spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb spec/unit/file_format/amazon_s3_format_spec.rb spec/unit/controller/log_processor_spec.rb spec/unit/filter/filter_spec.rb spec/unit/filter/field_filter_spec.rb spec/unit/database/base_class_spec.rb spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb spec/unit/database/connection_spec.rb spec/unit/controller/controller_spec.rb spec/unit/source/request_spec.rb spec/unit/source/log_parser_spec.rb spec/unit/database/database_spec.rb spec/unit/aggregator/summarizer_spec.rb spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb)
34
+ s.files = %w(spec/unit/filter/anonymize_filter_spec.rb lib/request_log_analyzer/line_definition.rb lib/request_log_analyzer/output/html.rb lib/request_log_analyzer/controller.rb spec/fixtures/rails_22_cached.log spec/lib/macros.rb lib/request_log_analyzer/file_format/rails_development.rb spec/fixtures/apache_combined.log spec/fixtures/apache_common.log spec/fixtures/merb_prefixed.log lib/request_log_analyzer/file_format/amazon_s3.rb tasks/request_log_analyzer.rake spec/unit/file_format/file_format_api_spec.rb spec/unit/file_format/apache_format_spec.rb spec/integration/command_line_usage_spec.rb lib/request_log_analyzer/database.rb spec/fixtures/decompression.log.bz2 spec/fixtures/rails_unordered.log lib/request_log_analyzer/log_processor.rb lib/request_log_analyzer/tracker.rb lib/request_log_analyzer/filter.rb bin/request-log-analyzer request-log-analyzer.gemspec DESIGN.rdoc spec/unit/filter/timespan_filter_spec.rb spec/unit/aggregator/database_inserter_spec.rb spec/lib/matchers.rb lib/request_log_analyzer/filter/field.rb lib/request_log_analyzer/tracker/frequency.rb spec/fixtures/decompression.log.gz spec/fixtures/decompression.log spec/lib/testing_format.rb spec/fixtures/test_order.log spec/fixtures/rails.db lib/request_log_analyzer/output/fixed_width.rb lib/request_log_analyzer/filter/anonymize.rb lib/request_log_analyzer/tracker/timespan.rb lib/request_log_analyzer/database/base.rb lib/request_log_analyzer/aggregator.rb lib/cli/progressbar.rb lib/request_log_analyzer/mailer.rb README.rdoc spec/fixtures/merb.log lib/request_log_analyzer/tracker/hourly_spread.rb .gitignore spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb spec/unit/file_format/amazon_s3_format_spec.rb lib/request_log_analyzer/aggregator/echo.rb spec/unit/controller/log_processor_spec.rb spec/spec_helper.rb lib/request_log_analyzer.rb spec/database.yml Rakefile lib/request_log_analyzer/database/connection.rb spec/unit/filter/filter_spec.rb spec/fixtures/test_language_combined.log lib/request_log_analyzer/aggregator/database_inserter.rb lib/request_log_analyzer/aggregator/summarizer.rb lib/request_log_analyzer/file_format/rack.rb lib/request_log_analyzer/file_format/rails.rb spec/fixtures/decompression.tar.gz spec/unit/tracker/traffic_tracker_spec.rb spec/unit/filter/field_filter_spec.rb spec/unit/database/base_class_spec.rb lib/request_log_analyzer/filter/timespan.rb lib/request_log_analyzer/source/log_parser.rb spec/fixtures/decompression.tgz spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/fixtures/header_and_footer.log lib/cli/tools.rb lib/request_log_analyzer/file_format/merb.rb spec/fixtures/multiple_files_1.log spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb lib/request_log_analyzer/source.rb lib/request_log_analyzer/request.rb lib/cli/database_console.rb spec/unit/database/connection_spec.rb spec/unit/controller/controller_spec.rb spec/lib/mocks.rb spec/lib/helpers.rb lib/cli/database_console_init.rb lib/request_log_analyzer/output.rb lib/request_log_analyzer/file_format/apache.rb spec/fixtures/rails_1x.log spec/fixtures/decompression.log.zip spec/unit/source/request_spec.rb spec/unit/source/log_parser_spec.rb spec/fixtures/test_file_format.log tasks/github-gem.rake spec/unit/database/database_spec.rb lib/request_log_analyzer/tracker/duration.rb lib/request_log_analyzer/tracker/traffic.rb lib/request_log_analyzer/file_format.rb spec/unit/aggregator/summarizer_spec.rb spec/fixtures/syslog_1x.log spec/fixtures/rails_22.log spec/fixtures/multiple_files_2.log LICENSE lib/request_log_analyzer/source/database_loader.rb spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb lib/cli/command_line_arguments.rb)
35
+ s.test_files = %w(spec/unit/filter/anonymize_filter_spec.rb spec/unit/file_format/file_format_api_spec.rb spec/unit/file_format/apache_format_spec.rb spec/integration/command_line_usage_spec.rb spec/unit/filter/timespan_filter_spec.rb spec/unit/aggregator/database_inserter_spec.rb spec/unit/tracker/tracker_api_spec.rb spec/unit/tracker/duration_tracker_spec.rb spec/unit/file_format/amazon_s3_format_spec.rb spec/unit/controller/log_processor_spec.rb spec/unit/filter/filter_spec.rb spec/unit/tracker/traffic_tracker_spec.rb spec/unit/filter/field_filter_spec.rb spec/unit/database/base_class_spec.rb spec/unit/tracker/timespan_tracker_spec.rb spec/unit/tracker/hourly_spread_spec.rb spec/unit/file_format/merb_format_spec.rb spec/unit/file_format/line_definition_spec.rb spec/unit/database/connection_spec.rb spec/unit/controller/controller_spec.rb spec/unit/source/request_spec.rb spec/unit/source/log_parser_spec.rb spec/unit/database/database_spec.rb spec/unit/aggregator/summarizer_spec.rb spec/unit/tracker/frequency_tracker_spec.rb spec/unit/file_format/rails_format_spec.rb)
36
36
  end
data/spec/lib/mocks.rb CHANGED
@@ -25,7 +25,7 @@ module RequestLogAnalyzer::Spec::Mocks
25
25
  def mock_io
26
26
  mio = mock('IO')
27
27
  mio.stub!(:print)
28
- mio.stub!(:puts)
28
+ mio.stub!(:puts)
29
29
  mio.stub!(:write)
30
30
  return mio
31
31
  end
@@ -35,12 +35,15 @@ module RequestLogAnalyzer::Spec::Mocks
35
35
  output.stub!(:header)
36
36
  output.stub!(:footer)
37
37
  output.stub!(:puts)
38
- output.stub!(:<<)
38
+ output.stub!(:<<)
39
+ output.stub!(:colorize).and_return("Fancy text")
40
+ output.stub!(:link)
39
41
  output.stub!(:title)
40
42
  output.stub!(:line)
41
43
  output.stub!(:with_style)
42
44
  output.stub!(:table).and_yield([])
43
45
  output.stub!(:io).and_return(mock_io)
46
+
44
47
  return output
45
48
  end
46
49
 
@@ -99,12 +99,49 @@ describe RequestLogAnalyzer::FileFormat::Apache do
99
99
  @log_parser.parse_file(log_fixture(:apache_common)) { counter.hit! }
100
100
  end
101
101
  end
102
+
103
+ context '"Rack" access log parser' do
104
+ before(:each) do
105
+ @file_format = RequestLogAnalyzer::FileFormat.load(:rack)
106
+ @log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
107
+ @sample_1 = '127.0.0.1 - - [16/Sep/2009 06:40:08] "GET /favicon.ico HTTP/1.1" 500 63183 0.0453'
108
+ end
109
+
110
+ it "should create a kind of an Apache file format" do
111
+ @file_format.should be_kind_of(RequestLogAnalyzer::FileFormat::Apache)
112
+ end
113
+
114
+ it "should have a valid language definitions" do
115
+ @file_format.should be_valid
116
+ end
117
+
118
+ it "should parse a valid access log line" do
119
+ @file_format.line_definitions[:access].matches(@sample_1).should be_kind_of(Hash)
120
+ end
121
+
122
+ it "should not parse a valid access log line" do
123
+ @file_format.line_definitions[:access].matches('addasdsasadadssadasd').should be_false
124
+ end
125
+
126
+ it "should read the correct values from a valid 404 access log line" do
127
+ @log_parser.parse_io(StringIO.new(@sample_1)) do |request|
128
+ request[:remote_host].should == '127.0.0.1'
129
+ request[:timestamp].should == 20090916064008
130
+ request[:http_status].should == 500
131
+ request[:http_method].should == 'GET'
132
+ request[:http_version].should == '1.1'
133
+ request[:bytes_sent].should == 63183
134
+ request[:user].should == nil
135
+ request[:duration].should == 0.0453
136
+ end
137
+ end
138
+ end
102
139
 
103
140
  context '"Combined" access log parsing' do
104
141
 
105
142
  before(:all) do
106
143
  @file_format = RequestLogAnalyzer::FileFormat.load(:apache, :combined)
107
- @log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
144
+ @log_parser = RequestLogAnalyzer::Source::LogParser.new(@file_format)
108
145
  @sample_1 = '69.41.0.45 - - [02/Sep/2009:12:02:40 +0200] "GET //phpMyAdmin/ HTTP/1.1" 404 209 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows 98)"'
109
146
  @sample_2 = '10.0.1.1 - - [02/Sep/2009:05:08:33 +0200] "GET / HTTP/1.1" 200 30 "-" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-us) AppleWebKit/531.9 (KHTML, like Gecko) Version/4.0.3 Safari/531.9"'
110
147
  end
@@ -0,0 +1,105 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Tracker::Traffic do
4
+
5
+ describe '#update' do
6
+
7
+ context 'using a field-based category' do
8
+ before(:each) do
9
+ @tracker = RequestLogAnalyzer::Tracker::Traffic.new(:traffic => :traffic, :category => :category)
10
+ @tracker.prepare
11
+ end
12
+
13
+ it "should register a request in the right category" do
14
+ @tracker.update(request(:category => 'a', :traffic => 200))
15
+ @tracker.categories.should include('a')
16
+ end
17
+
18
+ it "should register a hit in the right category" do
19
+ @tracker.update(request(:category => 'a', :traffic => 1))
20
+ @tracker.update(request(:category => 'b', :traffic => 2))
21
+ @tracker.update(request(:category => 'b', :traffic => 3))
22
+
23
+ @tracker.hits('a').should == 1
24
+ @tracker.hits('b').should == 2
25
+ end
26
+
27
+ it "should sum the traffics of the same category as cumulative traffic" do
28
+ @tracker.update(request(:category => 'a', :traffic => 1))
29
+ @tracker.update(request(:category => 'b', :traffic => 2))
30
+ @tracker.update(request(:category => 'b', :traffic => 3))
31
+
32
+ @tracker.cumulative_traffic('a').should == 1
33
+ @tracker.cumulative_traffic('b').should == 5
34
+ end
35
+
36
+ it "should calculate the average traffic correctly" do
37
+ @tracker.update(request(:category => 'a', :traffic => 1))
38
+ @tracker.update(request(:category => 'b', :traffic => 2))
39
+ @tracker.update(request(:category => 'b', :traffic => 3))
40
+
41
+ @tracker.average_traffic('a').should == 1.0
42
+ @tracker.average_traffic('b').should == 2.5
43
+ end
44
+
45
+ it "should set min and max traffic correctly" do
46
+ @tracker.update(request(:category => 'a', :traffic => 1))
47
+ @tracker.update(request(:category => 'b', :traffic => 2))
48
+ @tracker.update(request(:category => 'b', :traffic => 3))
49
+
50
+ @tracker.min_traffic('b').should == 2
51
+ @tracker.max_traffic('b').should == 3
52
+ end
53
+
54
+ end
55
+
56
+ context 'using a dynamic category' do
57
+ before(:each) do
58
+ @categorizer = Proc.new { |request| request[:traffic] < 2 ? 'few' : 'lots' }
59
+ @tracker = RequestLogAnalyzer::Tracker::Traffic.new(:traffic => :traffic, :category => @categorizer)
60
+ @tracker.prepare
61
+ end
62
+
63
+ it "should use the categorizer to determine the right category" do
64
+ @tracker.update(request(:category => 'a', :traffic => 1))
65
+ @tracker.update(request(:category => 'b', :traffic => 2))
66
+ @tracker.update(request(:category => 'b', :traffic => 3))
67
+
68
+ @tracker.categories.should include('few', 'lots')
69
+ end
70
+
71
+ it "should use the categorizer to aggregate the values correctly" do
72
+ @tracker.update(request(:category => 'a', :traffic => 1))
73
+ @tracker.update(request(:category => 'b', :traffic => 2))
74
+ @tracker.update(request(:category => 'b', :traffic => 3))
75
+
76
+ @tracker.max_traffic('few').should == 1
77
+ @tracker.min_traffic('lots').should == 2
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '#report' do
83
+ before(:each) do
84
+ @tracker = RequestLogAnalyzer::Tracker::Traffic.new(:category => :category, :traffic => :traffic)
85
+ @tracker.prepare
86
+ end
87
+
88
+ it "should generate a report without errors when one category is present" do
89
+ @tracker.update(request(:category => 'a', :traffic => 2))
90
+ @tracker.report(mock_output)
91
+ lambda { @tracker.report(mock_output) }.should_not raise_error
92
+ end
93
+
94
+ it "should generate a report without errors when no category is present" do
95
+ lambda { @tracker.report(mock_output) }.should_not raise_error
96
+ end
97
+
98
+ it "should generate a report without errors when multiple categories are present" do
99
+ @tracker.update(request(:category => 'a', :traffic => 2))
100
+ @tracker.update(request(:category => 'b', :traffic => 2))
101
+ lambda { @tracker.report(mock_output) }.should_not raise_error
102
+ end
103
+
104
+ end
105
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: request-log-analyzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.4
4
+ version: 1.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-09-14 00:00:00 +02:00
13
+ date: 2009-09-16 00:00:00 +02:00
14
14
  default_executable: request-log-analyzer
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -61,10 +61,10 @@ files:
61
61
  - spec/integration/command_line_usage_spec.rb
62
62
  - lib/request_log_analyzer/database.rb
63
63
  - spec/fixtures/decompression.log.bz2
64
+ - spec/fixtures/rails_unordered.log
64
65
  - lib/request_log_analyzer/log_processor.rb
65
66
  - lib/request_log_analyzer/tracker.rb
66
67
  - lib/request_log_analyzer/filter.rb
67
- - spec/fixtures/rails_unordered.log
68
68
  - bin/request-log-analyzer
69
69
  - request-log-analyzer.gemspec
70
70
  - DESIGN.rdoc
@@ -103,8 +103,10 @@ files:
103
103
  - spec/fixtures/test_language_combined.log
104
104
  - lib/request_log_analyzer/aggregator/database_inserter.rb
105
105
  - lib/request_log_analyzer/aggregator/summarizer.rb
106
+ - lib/request_log_analyzer/file_format/rack.rb
106
107
  - lib/request_log_analyzer/file_format/rails.rb
107
108
  - spec/fixtures/decompression.tar.gz
109
+ - spec/unit/tracker/traffic_tracker_spec.rb
108
110
  - spec/unit/filter/field_filter_spec.rb
109
111
  - spec/unit/database/base_class_spec.rb
110
112
  - lib/request_log_analyzer/filter/timespan.rb
@@ -136,11 +138,12 @@ files:
136
138
  - tasks/github-gem.rake
137
139
  - spec/unit/database/database_spec.rb
138
140
  - lib/request_log_analyzer/tracker/duration.rb
141
+ - lib/request_log_analyzer/tracker/traffic.rb
139
142
  - lib/request_log_analyzer/file_format.rb
140
143
  - spec/unit/aggregator/summarizer_spec.rb
144
+ - spec/fixtures/syslog_1x.log
141
145
  - spec/fixtures/rails_22.log
142
146
  - spec/fixtures/multiple_files_2.log
143
- - spec/fixtures/syslog_1x.log
144
147
  - LICENSE
145
148
  - lib/request_log_analyzer/source/database_loader.rb
146
149
  - spec/unit/tracker/frequency_tracker_spec.rb
@@ -191,6 +194,7 @@ test_files:
191
194
  - spec/unit/file_format/amazon_s3_format_spec.rb
192
195
  - spec/unit/controller/log_processor_spec.rb
193
196
  - spec/unit/filter/filter_spec.rb
197
+ - spec/unit/tracker/traffic_tracker_spec.rb
194
198
  - spec/unit/filter/field_filter_spec.rb
195
199
  - spec/unit/database/base_class_spec.rb
196
200
  - spec/unit/tracker/timespan_tracker_spec.rb