request-log-analyzer 1.1.0 → 1.1.1

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 (40) hide show
  1. data/README.rdoc +4 -3
  2. data/bin/request-log-analyzer +4 -5
  3. data/lib/cli/command_line_arguments.rb +2 -2
  4. data/lib/request_log_analyzer.rb +28 -6
  5. data/lib/request_log_analyzer/{aggregator/base.rb → aggregator.rb} +5 -1
  6. data/lib/request_log_analyzer/aggregator/summarizer.rb +2 -3
  7. data/lib/request_log_analyzer/controller.rb +11 -16
  8. data/lib/request_log_analyzer/file_format.rb +71 -38
  9. data/lib/request_log_analyzer/file_format/merb.rb +32 -26
  10. data/lib/request_log_analyzer/file_format/rails.rb +73 -71
  11. data/lib/request_log_analyzer/file_format/rails_development.rb +93 -95
  12. data/lib/request_log_analyzer/filter.rb +38 -0
  13. data/lib/request_log_analyzer/filter/anonimize.rb +1 -1
  14. data/lib/request_log_analyzer/line_definition.rb +1 -1
  15. data/lib/request_log_analyzer/output.rb +6 -8
  16. data/lib/request_log_analyzer/output/fixed_width.rb +133 -117
  17. data/lib/request_log_analyzer/output/html.rb +138 -60
  18. data/lib/request_log_analyzer/request.rb +3 -1
  19. data/lib/request_log_analyzer/{source/base.rb → source.rb} +5 -0
  20. data/lib/request_log_analyzer/source/{log_file.rb → log_parser.rb} +15 -6
  21. data/lib/request_log_analyzer/tracker.rb +58 -0
  22. data/lib/request_log_analyzer/tracker/category.rb +7 -8
  23. data/lib/request_log_analyzer/tracker/duration.rb +15 -12
  24. data/lib/request_log_analyzer/tracker/hourly_spread.rb +8 -8
  25. data/lib/request_log_analyzer/tracker/timespan.rb +10 -10
  26. data/spec/controller_spec.rb +5 -4
  27. data/spec/database_inserter_spec.rb +5 -8
  28. data/spec/file_format_spec.rb +2 -2
  29. data/spec/file_formats/spec_format.rb +2 -1
  30. data/spec/filter_spec.rb +0 -3
  31. data/spec/log_parser_spec.rb +6 -6
  32. data/spec/merb_format_spec.rb +38 -38
  33. data/spec/rails_format_spec.rb +2 -2
  34. data/spec/request_spec.rb +2 -2
  35. data/spec/spec_helper.rb +3 -37
  36. data/tasks/github-gem.rake +2 -1
  37. metadata +7 -8
  38. data/lib/request_log_analyzer/filter/base.rb +0 -32
  39. data/lib/request_log_analyzer/log_parser.rb +0 -173
  40. data/lib/request_log_analyzer/tracker/base.rb +0 -54
@@ -1,80 +1,158 @@
1
- class RequestLogAnalyzer::Output::HTML < RequestLogAnalyzer::Output
2
-
3
- # def initialize(io, options = {})
4
- # super(io, options)
5
- # end
6
-
7
- def print(str)
8
- @io << str
9
- end
1
+ module RequestLogAnalyzer::Output
2
+
3
+ class HTML < Base
4
+
5
+ # def initialize(io, options = {})
6
+ # super(io, options)
7
+ # end
8
+
9
+ def print(str)
10
+ @io << str
11
+ end
10
12
 
11
- alias :<< :print
13
+ alias :<< :print
12
14
 
13
- def puts(str = '')
14
- @io << str << "<br />\n"
15
- end
15
+ def puts(str = '')
16
+ @io << str << "<br/>\n"
17
+ end
16
18
 
17
- def title(title)
18
- @io.puts(tag(:h2, title))
19
- end
19
+ def title(title)
20
+ @io.puts(tag(:h2, title))
21
+ end
20
22
 
21
- def line(*font)
22
- @io.puts(tag(:hr))
23
- end
23
+ def line(*font)
24
+ @io.puts(tag(:hr))
25
+ end
24
26
 
25
- def link(text, url = nil)
26
- url = text if url.nil?
27
- tag(:a, text, :href => url)
28
- end
27
+ def link(text, url = nil)
28
+ url = text if url.nil?
29
+ tag(:a, text, :href => url)
30
+ end
29
31
 
30
- def table(*columns, &block)
31
- rows = Array.new
32
- yield(rows)
32
+ def table(*columns, &block)
33
+ rows = Array.new
34
+ yield(rows)
33
35
 
34
- @io << tag(:table) do |content|
35
- if table_has_header?(columns)
36
- content << tag(:tr) do
37
- columns.map { |col| tag(:th, col[:title]) }.join("\n")
36
+ @io << tag(:table, {:id => 'mytable', :cellspacing => 0}) do |content|
37
+ if table_has_header?(columns)
38
+ content << tag(:tr) do
39
+ columns.map { |col| tag(:th, col[:title]) }.join("\n")
40
+ end
38
41
  end
39
- end
40
42
 
41
- rows.each do |row|
42
- content << tag(:tr) do
43
- row.map { |cell| tag(:td, cell) }.join("\n")
43
+ odd = false
44
+ rows.each do |row|
45
+ odd = !odd
46
+ content << tag(:tr) do
47
+ if odd
48
+ row.map { |cell| tag(:td, cell, :class => 'alt') }.join("\n")
49
+ else
50
+ row.map { |cell| tag(:td, cell) }.join("\n")
51
+ end
52
+ end
44
53
  end
45
54
  end
46
- end
47
55
 
48
- end
56
+ end
49
57
 
50
- def header
51
- @io << "<html>"
52
- @io << tag(:head) do |headers|
53
- headers << tag(:title, 'Request-log-analyzer report')
58
+ def header
59
+ @io << "<html>"
60
+ @io << tag(:head) do |headers|
61
+ headers << tag(:title, 'Request-log-analyzer report')
62
+ headers << tag(:style, '
63
+ body {
64
+ font: normal 11px auto "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
65
+ color: #4f6b72;
66
+ background: #E6EAE9;
67
+ padding-left:20px;
68
+ padding-top:20px;
69
+ padding-bottom:20px;
70
+ }
71
+
72
+ a {
73
+ color: #c75f3e;
74
+ }
75
+
76
+ .color_bar {
77
+ border: 1px solid;
78
+ height:10px;
79
+ background: #CAE8EA;
80
+ }
81
+
82
+ #mytable {
83
+ width: 700px;
84
+ padding: 0;
85
+ margin: 0;
86
+ padding-bottom:10px;
87
+ }
88
+
89
+ caption {
90
+ padding: 0 0 5px 0;
91
+ width: 700px;
92
+ font: italic 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
93
+ text-align: right;
94
+ }
95
+
96
+ th {
97
+ font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
98
+ color: #4f6b72;
99
+ border-right: 1px solid #C1DAD7;
100
+ border-bottom: 1px solid #C1DAD7;
101
+ border-top: 1px solid #C1DAD7;
102
+ letter-spacing: 2px;
103
+ text-transform: uppercase;
104
+ text-align: left;
105
+ padding: 6px 6px 6px 12px;
106
+ background: #CAE8EA url(images/bg_header.jpg) no-repeat;
107
+ }
108
+
109
+ td {
110
+ font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
111
+ border-right: 1px solid #C1DAD7;
112
+ border-bottom: 1px solid #C1DAD7;
113
+ background: #fff;
114
+ padding: 6px 6px 6px 12px;
115
+ color: #4f6b72;
116
+ }
117
+
118
+ td.alt {
119
+ background: #F5FAFA;
120
+ color: #797268;
121
+ }
122
+ ', :type => "text/css")
123
+ end
124
+ @io << '<body>'
125
+ @io << tag(:h1, 'Request-log-analyzer summary report')
126
+ @io << tag(:p, "Version #{RequestLogAnalyzer::VERSION} - written by Willem van Bergen and Bart ten Brinke")
54
127
  end
55
- @io << '<body>'
56
- end
57
128
 
58
- def footer
59
- @io << "</body></html>\n"
60
- end
129
+ def footer
130
+ @io << tag(:hr) << tag(:h2, 'Thanks for using request-log-analyzer')
131
+ @io << tag(:p, 'Please visit the ' + link('Request-log-analyzer website', 'http://github.com/wvanbergen/request-log-analyzer'))
132
+ @io << "</body></html>\n"
133
+ end
61
134
 
62
- protected
135
+ protected
63
136
 
64
- def tag(tag, content = nil, attributes = nil)
65
- if block_given?
66
- attributes = content.nil? ? '' : ' ' + content.map { |(key, value)| "#{key}=\"#{value}\"" }.join(' ')
67
- content_string = ''
68
- content = yield(content_string)
69
- content = content_string unless content_string.empty?
70
- "<#{tag}#{attributes}>#{content}</#{tag}>"
71
- else
72
- attributes = attributes.nil? ? '' : ' ' + attributes.map { |(key, value)| "#{key}=\"#{value}\"" }.join(' ')
73
- if content.nil?
74
- "<#{tag}#{attributes} />"
75
- else
137
+ def tag(tag, content = nil, attributes = nil)
138
+ if block_given?
139
+ attributes = content.nil? ? '' : ' ' + content.map { |(key, value)| "#{key}=\"#{value}\"" }.join(' ')
140
+ content_string = ''
141
+ content = yield(content_string)
142
+ content = content_string unless content_string.empty?
76
143
  "<#{tag}#{attributes}>#{content}</#{tag}>"
144
+ else
145
+ attributes = attributes.nil? ? '' : ' ' + attributes.map { |(key, value)| "#{key}=\"#{value}\"" }.join(' ')
146
+ if content.nil?
147
+ "<#{tag}#{attributes} />"
148
+ else
149
+ if content.class == Float
150
+ "<#{tag}#{attributes}><div class='color_bar' style=\"width:#{(content*200).floor}px;\"/></#{tag}>"
151
+ else
152
+ "<#{tag}#{attributes}>#{content}</#{tag}>"
153
+ end
154
+ end
77
155
  end
78
- end
79
- end
156
+ end
157
+ end
80
158
  end
@@ -32,11 +32,13 @@ module RequestLogAnalyzer
32
32
 
33
33
  # Adds another line to the request.
34
34
  # The line should be provides as a hash of the fields parsed from the line.
35
- def << (request_info_hash)
35
+ def add_parsed_line (request_info_hash)
36
36
  @lines << request_info_hash
37
37
  @attributes = request_info_hash.merge(@attributes)
38
38
  end
39
39
 
40
+ alias :<< :add_parsed_line
41
+
40
42
  # Checks whether the given line type was parsed from the log file for this request
41
43
  def has_line_type?(line_type)
42
44
  return true if @lines.length == 1 && @lines[0][:line_type] == line_type.to_sym
@@ -1,4 +1,9 @@
1
1
  module RequestLogAnalyzer::Source
2
+
3
+ def self.const_missing(const)
4
+ RequestLogAnalyzer::load_default_class_file(self, const)
5
+ end
6
+
2
7
  class Base
3
8
 
4
9
  include RequestLogAnalyzer::FileFormat::Awareness
@@ -9,7 +9,7 @@ module RequestLogAnalyzer::Source
9
9
  # the log file simultaneously by different mongrel processes. This problem is detected by the
10
10
  # parser, but the requests that are mixed up cannot be parsed. It will emit warnings when this
11
11
  # occurs.
12
- class LogFile < RequestLogAnalyzer::Source::Base
12
+ class LogParser < Base
13
13
 
14
14
  attr_reader :source_files
15
15
 
@@ -22,6 +22,7 @@ module RequestLogAnalyzer::Source
22
22
  @parsed_lines = 0
23
23
  @parsed_requests = 0
24
24
  @skipped_lines = 0
25
+ @skipped_requests = 0
25
26
  @current_io = nil
26
27
  @source_files = options[:source_files]
27
28
 
@@ -30,7 +31,7 @@ module RequestLogAnalyzer::Source
30
31
  self.register_file_format(format)
31
32
  end
32
33
 
33
- def requests(options = {}, &block)
34
+ def each_request(options = {}, &block)
34
35
 
35
36
  case @source_files
36
37
  when IO;
@@ -131,22 +132,22 @@ module RequestLogAnalyzer::Source
131
132
  unless @current_request.nil?
132
133
  if options[:assume_correct_order]
133
134
  @parsed_requests += 1
134
- yield @current_request
135
- @current_request = RequestLogAnalyzer::Request.create(@file_format, request_data)
135
+ handle_request(@current_request, &block) #yield @current_request
136
+ @current_request = @file_format.create_request(request_data)
136
137
  else
137
138
  @skipped_lines += 1
138
139
  warn(:unclosed_request, "Encountered header line, but previous request was not closed!")
139
140
  @current_request = nil # remove all data that was parsed, skip next request as well.
140
141
  end
141
142
  else
142
- @current_request = RequestLogAnalyzer::Request.create(@file_format, request_data)
143
+ @current_request = @file_format.create_request(request_data)
143
144
  end
144
145
  else
145
146
  unless @current_request.nil?
146
147
  @current_request << request_data
147
148
  if footer_line?(request_data)
148
149
  @parsed_requests += 1
149
- yield @current_request
150
+ handle_request(@current_request, &block) # yield @current_request
150
151
  @current_request = nil
151
152
  end
152
153
  else
@@ -155,6 +156,14 @@ module RequestLogAnalyzer::Source
155
156
  end
156
157
  end
157
158
  end
159
+
160
+ # Handles the parsed request by calling the request handler.
161
+ # The default controller will send the request to every running aggegator.
162
+ def handle_request(request, &block)
163
+ @parsed_requests += 1
164
+ accepted = block_given? ? yield(request) : true
165
+ @skipped_requests += 1 if not accepted
166
+ end
158
167
 
159
168
  # Checks whether a given line hash is a header line.
160
169
  def header_line?(hash)
@@ -0,0 +1,58 @@
1
+ module RequestLogAnalyzer::Tracker
2
+
3
+ # const_missing: this function is used to load subclasses in the RequestLogAnalyzer::Track namespace.
4
+ # It will automatically load the required file based on the class name
5
+ def self.const_missing(const)
6
+ RequestLogAnalyzer::load_default_class_file(self, const)
7
+ end
8
+
9
+ # Base Tracker class. All other trackers inherit from this class
10
+ #
11
+ # Accepts the following options:
12
+ # * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
13
+ # * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
14
+ # * <tt>:output</tt> Direct output here (defaults to STDOUT)
15
+ #
16
+ # For example :if => lambda { |request| request[:duration] && request[:duration] > 1.0 }
17
+ class Base
18
+
19
+ attr_reader :options
20
+
21
+ def initialize(options ={})
22
+ @options = options
23
+ end
24
+
25
+ def prepare
26
+ end
27
+
28
+ def update(request)
29
+ end
30
+
31
+ def finalize
32
+ end
33
+
34
+ def should_update?(request)
35
+ return false if options[:line_type] && !request.has_line_type?(options[:line_type])
36
+
37
+ if options[:if].kind_of?(Symbol)
38
+ return false unless request[options[:if]]
39
+ elsif options[:if].respond_to?(:call)
40
+ return false unless options[:if].call(request)
41
+ end
42
+
43
+ if options[:unless].kind_of?(Symbol)
44
+ return false if request[options[:unless]]
45
+ elsif options[:unless].respond_to?(:call)
46
+ return false if options[:unless].call(request)
47
+ end
48
+
49
+ return true
50
+ end
51
+
52
+ def report(output)
53
+ output << self.inspect
54
+ output << "\n"
55
+ end
56
+
57
+ end
58
+ end
@@ -1,5 +1,5 @@
1
1
  module RequestLogAnalyzer::Tracker
2
-
2
+
3
3
  # Catagorize requests.
4
4
  # Count and analyze requests for a specific attribute
5
5
  #
@@ -19,10 +19,10 @@ module RequestLogAnalyzer::Tracker
19
19
  # PUT | 13685 hits (28.4%) |░░░░░░░░░░░
20
20
  # POST | 11662 hits (24.2%) |░░░░░░░░░
21
21
  # DELETE | 512 hits (1.1%) |
22
- class Category < RequestLogAnalyzer::Tracker::Base
23
-
22
+ class Category < Base
23
+
24
24
  attr_reader :categories
25
-
25
+
26
26
  def prepare
27
27
  raise "No categorizer set up for category tracker #{self.inspect}" unless options[:category]
28
28
  @categories = {}
@@ -30,7 +30,7 @@ module RequestLogAnalyzer::Tracker
30
30
  options[:all_categories].each { |cat| @categories[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]
@@ -38,10 +38,10 @@ module RequestLogAnalyzer::Tracker
38
38
  @categories[cat] += 1
39
39
  end
40
40
  end
41
-
41
+
42
42
  def report(output)
43
43
  output.title(options[:title]) if options[:title]
44
-
44
+
45
45
  if @categories.empty?
46
46
  output << "None found.\n"
47
47
  else
@@ -57,6 +57,5 @@ module RequestLogAnalyzer::Tracker
57
57
 
58
58
  end
59
59
  end
60
-
61
60
  end
62
61
  end
@@ -19,46 +19,49 @@ module RequestLogAnalyzer::Tracker
19
19
  # EmployeeController#update.html [POST] | 4647 | 2731.23s | 0.59s
20
20
  # EmployeeController#index.html [GET] | 5802 | 1477.32s | 0.25s
21
21
  # .............
22
- class Duration < RequestLogAnalyzer::Tracker::Base
22
+ class Duration < Base
23
+
23
24
  attr_reader :categories
24
-
25
+
25
26
  def prepare
26
27
  raise "No duration field set up for category tracker #{self.inspect}" unless options[:duration]
27
28
  raise "No categorizer set up for duration tracker #{self.inspect}" unless options[:category]
28
-
29
+
29
30
  @categories = {}
30
31
  end
31
-
32
+
32
33
  def update(request)
33
34
  category = options[:category].respond_to?(:call) ? options[:category].call(request) : request[options[:category]]
34
35
  duration = options[:duration].respond_to?(:call) ? options[:duration].call(request) : request[options[:duration]]
35
-
36
+
36
37
  if !duration.nil? && !category.nil?
37
38
  @categories[category] ||= {:count => 0, :total_duration => 0.0}
38
39
  @categories[category][:count] += 1
39
40
  @categories[category][:total_duration] += duration
40
41
  end
41
42
  end
42
-
43
+
43
44
  def report_table(output, amount = 10, options = {}, &block)
44
-
45
+
46
+ output.title(options[:title])
47
+
45
48
  top_categories = @categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) }.slice(0...amount)
46
- output.table({:title => options[:title]}, {:title => 'Hits', :align => :right, :min_width => 4},
49
+ output.table({:title => 'Action'}, {:title => 'Hits', :align => :right, :min_width => 4},
47
50
  {:title => 'Cumulative', :align => :right, :min_width => 10}, {:title => 'Average', :align => :right, :min_width => 8}) do |rows|
48
-
51
+
49
52
  top_categories.each do |(cat, info)|
50
53
  rows << [cat, info[:count], "%0.02fs" % info[:total_duration], "%0.02fs" % (info[:total_duration] / info[:count])]
51
54
  end
52
55
  end
53
56
 
54
57
  end
55
-
58
+
56
59
  def report(output)
57
60
 
58
61
  options[:title] ||= 'Request duration'
59
62
  options[:report] ||= [:total, :average]
60
63
  options[:top] ||= 20
61
-
64
+
62
65
  options[:report].each do |report|
63
66
  case report
64
67
  when :average
@@ -73,4 +76,4 @@ module RequestLogAnalyzer::Tracker
73
76
  end
74
77
  end
75
78
  end
76
- end
79
+ end