request-log-analyzer 1.1.1 → 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 -38
- 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 +22 -16
- data/lib/request_log_analyzer/controller.rb +21 -5
- data/lib/request_log_analyzer/file_format/merb.rb +22 -9
- data/lib/request_log_analyzer/file_format/rails.rb +37 -23
- data/lib/request_log_analyzer/file_format/rails_development.rb +33 -88
- data/lib/request_log_analyzer/file_format.rb +50 -18
- data/lib/request_log_analyzer/filter/{anonimize.rb → anonymize.rb} +3 -8
- data/lib/request_log_analyzer/filter/field.rb +7 -6
- data/lib/request_log_analyzer/filter/timespan.rb +7 -3
- data/lib/request_log_analyzer/filter.rb +0 -4
- data/lib/request_log_analyzer/line_definition.rb +22 -88
- data/lib/request_log_analyzer/log_processor.rb +5 -25
- 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 +62 -4
- data/lib/request_log_analyzer/source/database.rb +76 -0
- data/lib/request_log_analyzer/source/log_parser.rb +104 -52
- 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/{category.rb → frequency.rb} +24 -12
- 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/lib/testing_format.rb +39 -0
- data/spec/spec_helper.rb +5 -25
- 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/unit/controller/log_processor_spec.rb +20 -0
- data/spec/{file_format_spec.rb → unit/file_format/file_format_api_spec.rb} +18 -6
- data/spec/unit/file_format/line_definition_spec.rb +64 -0
- data/spec/{merb_format_spec.rb → unit/file_format/merb_format_spec.rb} +3 -3
- data/spec/{rails_format_spec.rb → unit/file_format/rails_format_spec.rb} +24 -15
- 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} +9 -10
- 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 +56 -28
- data/spec/controller_spec.rb +0 -45
- data/spec/file_formats/spec_format.rb +0 -27
- data/spec/filter_spec.rb +0 -157
- data/spec/line_definition_spec.rb +0 -124
- data/spec/log_processor_spec.rb +0 -57
- data/spec/summarizer_spec.rb +0 -9
data/DESIGN
CHANGED
|
@@ -4,21 +4,35 @@ This allows you to easily add extra reports, filters and outputs.
|
|
|
4
4
|
|
|
5
5
|
1) Build pipeline.
|
|
6
6
|
-> Aggregator (database)
|
|
7
|
-
Source -> Filter -> Filter -> Aggregator (summary report)
|
|
7
|
+
Source -> Filter -> Filter -> Aggregator (summary report) -> Output
|
|
8
8
|
-> Aggregator (...)
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
2) Start chunk producer and push chunks through pipeline.
|
|
11
|
-
|
|
11
|
+
Controller.start
|
|
12
|
+
|
|
13
|
+
RequestLogAnalyzer::Source is an Object that pushes requests into the chain.
|
|
14
|
+
At the moment you can only use the log-parser as a source.
|
|
15
|
+
It accepts files or stdin and can parse then into request objects using a RequestLogAnalyzer::FileFormat definition.
|
|
16
|
+
In the future we want to be able to have a generated request database as source as this will make interactive
|
|
17
|
+
down drilling possible.
|
|
18
|
+
|
|
19
|
+
The filters are all subclasses of the RequestLogAnalyzer::Filter class.
|
|
20
|
+
They accept a request object, manipulate or drop it, and then pass the request object on to the next filter
|
|
21
|
+
in the chain.
|
|
22
|
+
At the moment there are three types of filters available: Anonymize, Field and Timespan.
|
|
23
|
+
|
|
24
|
+
The Aggregators all inherit from the RequestLogAnalyzer::Aggregator class.
|
|
25
|
+
All the requests that come out of the Filterchain are fed into all the aggregators in parallel.
|
|
26
|
+
These aggregators can do anything what they want with the given request.
|
|
27
|
+
For example: the Database aggregator will just store all the requests into a SQLite database while the Summarizer will
|
|
28
|
+
generate a wide range of statistical reports from them.
|
|
12
29
|
|
|
13
30
|
3) Gather output from pipeline.
|
|
14
31
|
Controller.report
|
|
15
32
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
This will make interactive downdrilling possible.
|
|
19
|
-
|
|
33
|
+
All Aggregators are asked to report what they have done. For example the database will report: I stuffed x requests
|
|
34
|
+
into SQLite database Y. The Summarizer will output its reports.
|
|
20
35
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
tables, lines and comments and push them into the output class.
|
|
36
|
+
The output is pushed to a RequestLogAnalyzer::Output object, which takes care of the output.
|
|
37
|
+
It can generate either ASCII, UTF8 or even HTML output.
|
|
24
38
|
|
data/README.rdoc
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
This is a simple command line tool to analyze request log files of both Rails and
|
|
4
4
|
Merb to produce a performance report. Its purpose is to find what actions are best candidates for optimization.
|
|
5
5
|
|
|
6
|
-
* Analyzes Rails log files (all versions)
|
|
6
|
+
* Analyzes Rails log files (all versions), Merb logs, or any other log format
|
|
7
7
|
* Can combine multiple files (handy if you are using logrotate)
|
|
8
8
|
* Uses several metrics, including cumulative request time, average request time, process blockers, database and rendering time, HTTP methods and statuses, Rails action cache statistics, etc.) (Sample output: http://wiki.github.com/wvanbergen/request-log-analyzer/sample-output)
|
|
9
9
|
* Low memory footprint (server-safe)
|
data/Rakefile
CHANGED
data/bin/request-log-analyzer
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/ruby
|
|
2
2
|
require File.dirname(__FILE__) + '/../lib/request_log_analyzer'
|
|
3
3
|
require File.dirname(__FILE__) + '/../lib/cli/command_line_arguments'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
IO.popen('stty -a') do |pipe|
|
|
7
|
-
column_line = pipe.detect { |line| /(\d+) columns/ =~ line }
|
|
8
|
-
width = column_line ? $1.to_i : default
|
|
9
|
-
end
|
|
10
|
-
rescue
|
|
11
|
-
default
|
|
12
|
-
end
|
|
4
|
+
require File.dirname(__FILE__) + '/../lib/cli/progressbar'
|
|
5
|
+
require File.dirname(__FILE__) + '/../lib/cli/tools'
|
|
13
6
|
|
|
14
7
|
# Parse the arguments given via commandline
|
|
15
8
|
begin
|
|
@@ -27,17 +20,9 @@ begin
|
|
|
27
20
|
strip.switch(:keep_junk_lines, :j)
|
|
28
21
|
end
|
|
29
22
|
|
|
30
|
-
command_line.command(:anonymize) do |anonymize|
|
|
31
|
-
anonymize.minimum_parameters = 1
|
|
32
|
-
anonymize.option(:format, :alias => :f, :default => 'rails')
|
|
33
|
-
anonymize.option(:output, :alias => :o)
|
|
34
|
-
anonymize.switch(:discard_teaser_lines, :t)
|
|
35
|
-
anonymize.switch(:keep_junk_lines, :j)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
23
|
command_line.option(:format, :alias => :f, :default => 'rails')
|
|
39
24
|
command_line.option(:file, :alias => :e)
|
|
40
|
-
command_line.
|
|
25
|
+
command_line.option(:parse_strategy, :default => 'assume-correct')
|
|
41
26
|
|
|
42
27
|
command_line.option(:aggregator, :alias => :a, :multiple => true)
|
|
43
28
|
command_line.option(:database, :alias => :d)
|
|
@@ -58,6 +43,7 @@ begin
|
|
|
58
43
|
end
|
|
59
44
|
|
|
60
45
|
rescue CommandLine::Error => e
|
|
46
|
+
puts "Request-log-analyzer, by Willem van Bergen and Bart ten Brinke - version #{RequestLogAnalyzer::VERSION}"
|
|
61
47
|
puts "ARGUMENT ERROR: " + e.message if e.message
|
|
62
48
|
puts
|
|
63
49
|
puts "Usage: request-log-analyzer [LOGFILES*] <OPTIONS>"
|
|
@@ -88,32 +74,12 @@ rescue CommandLine::Error => e
|
|
|
88
74
|
exit(0)
|
|
89
75
|
end
|
|
90
76
|
|
|
91
|
-
def install_rake_tasks(install_type)
|
|
92
|
-
if install_type == 'rails'
|
|
93
|
-
require 'ftools'
|
|
94
|
-
if File.directory?('./lib/tasks/')
|
|
95
|
-
File.copy(File.dirname(__FILE__) + '/../tasks/request_log_analyzer.rake', './lib/tasks/request_log_analyze.rake')
|
|
96
|
-
puts "Installed rake tasks."
|
|
97
|
-
puts "To use, run: rake log:analyze"
|
|
98
|
-
else
|
|
99
|
-
puts "Cannot find /lib/tasks folder. Are you in your Rails directory?"
|
|
100
|
-
puts "Installation aborted."
|
|
101
|
-
end
|
|
102
|
-
else
|
|
103
|
-
raise "Cannot perform this install type! (#{install_type})"
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
|
|
108
77
|
case arguments.command
|
|
109
78
|
when :install
|
|
110
79
|
install_rake_tasks(arguments.parameters[0])
|
|
111
80
|
when :strip
|
|
112
81
|
require File.dirname(__FILE__) + '/../lib/request_log_analyzer/log_processor'
|
|
113
82
|
RequestLogAnalyzer::LogProcessor.build(:strip, arguments).run!
|
|
114
|
-
when :anonymize
|
|
115
|
-
require File.dirname(__FILE__) + '/../lib/request_log_analyzer/log_processor'
|
|
116
|
-
RequestLogAnalyzer::LogProcessor.build(:anonymize, arguments).run!
|
|
117
83
|
else
|
|
118
84
|
puts "Request-log-analyzer, by Willem van Bergen and Bart ten Brinke - version #{RequestLogAnalyzer::VERSION}"
|
|
119
85
|
puts "Website: http://github.com/wvanbergen/request-log-analyzer"
|
data/lib/cli/progressbar.rb
CHANGED
|
@@ -119,23 +119,6 @@ module CommandLine
|
|
|
119
119
|
end
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
-
def get_width
|
|
123
|
-
# FIXME: I don't know how portable it is.
|
|
124
|
-
default_width = 80
|
|
125
|
-
begin
|
|
126
|
-
tiocgwinsz = 0x5413
|
|
127
|
-
data = [0, 0, 0, 0].pack("SSSS")
|
|
128
|
-
if @out.ioctl(tiocgwinsz, data) >= 0 then
|
|
129
|
-
rows, cols, xpixels, ypixels = data.unpack("SSSS")
|
|
130
|
-
if cols >= 0 then cols else default_width end
|
|
131
|
-
else
|
|
132
|
-
default_width
|
|
133
|
-
end
|
|
134
|
-
rescue Exception
|
|
135
|
-
default_width
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
122
|
def show
|
|
140
123
|
arguments = @format_arguments.map {|method|
|
|
141
124
|
method = sprintf("fmt_%s", method)
|
|
@@ -143,7 +126,7 @@ module CommandLine
|
|
|
143
126
|
}
|
|
144
127
|
line = sprintf(@format, *arguments)
|
|
145
128
|
|
|
146
|
-
width =
|
|
129
|
+
width = terminal_width(80)
|
|
147
130
|
if line.length == width - 1
|
|
148
131
|
@out.print(line + eol)
|
|
149
132
|
@out.flush
|
|
@@ -176,7 +159,7 @@ module CommandLine
|
|
|
176
159
|
public
|
|
177
160
|
def clear
|
|
178
161
|
@out.print "\r"
|
|
179
|
-
@out.print(" " * (
|
|
162
|
+
@out.print(" " * (terminal_width(80) - 1))
|
|
180
163
|
@out.print "\r"
|
|
181
164
|
end
|
|
182
165
|
|
data/lib/cli/tools.rb
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Try to determine the terminal with.
|
|
2
|
+
# If it is not possible to to so, it returns the default_width.
|
|
3
|
+
# <tt>default_width</tt> Defaults to 81
|
|
4
|
+
def terminal_width(default_width = 81)
|
|
5
|
+
tiocgwinsz = 0x5413
|
|
6
|
+
data = [0, 0, 0, 0].pack("SSSS")
|
|
7
|
+
if @out.ioctl(tiocgwinsz, data) >= 0
|
|
8
|
+
rows, cols, xpixels, ypixels = data.unpack("SSSS")
|
|
9
|
+
raise unless cols > 0
|
|
10
|
+
cols
|
|
11
|
+
else
|
|
12
|
+
raise
|
|
13
|
+
end
|
|
14
|
+
rescue
|
|
15
|
+
begin
|
|
16
|
+
IO.popen('stty -a') do |pipe|
|
|
17
|
+
column_line = pipe.detect { |line| /(\d+) columns/ =~ line }
|
|
18
|
+
raise unless column_line
|
|
19
|
+
$1.to_i
|
|
20
|
+
end
|
|
21
|
+
rescue
|
|
22
|
+
default_width
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Copies request-log-analyzer analyzer rake tasks into the /lib/tasks folder of a project, for easy access and
|
|
27
|
+
# environment integration.
|
|
28
|
+
# <tt>install_type</tt> Type of project to install into. Defaults to :rails.
|
|
29
|
+
# Raises if it cannot find the project folder or if the install_type is now known.
|
|
30
|
+
def install_rake_tasks(install_type = :rails)
|
|
31
|
+
if install_type.to_sym == :rails
|
|
32
|
+
require 'ftools'
|
|
33
|
+
if File.directory?('./lib/tasks/')
|
|
34
|
+
File.copy(File.dirname(__FILE__) + '/../tasks/request_log_analyzer.rake', './lib/tasks/request_log_analyze.rake')
|
|
35
|
+
puts "Installed rake tasks."
|
|
36
|
+
puts "To use, run: rake log:analyze"
|
|
37
|
+
else
|
|
38
|
+
puts "Cannot find /lib/tasks folder. Are you in your Rails directory?"
|
|
39
|
+
puts "Installation aborted."
|
|
40
|
+
end
|
|
41
|
+
else
|
|
42
|
+
raise "Cannot perform this install type! (#{install_type.to_s})"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
@@ -30,7 +30,8 @@ module RequestLogAnalyzer::Aggregator
|
|
|
30
30
|
def aggregate(request)
|
|
31
31
|
@request_object = @request_class.new(:first_lineno => request.first_lineno, :last_lineno => request.last_lineno)
|
|
32
32
|
request.lines.each do |line|
|
|
33
|
-
|
|
33
|
+
class_columns = @orm_module.const_get("#{line[:line_type]}_line".classify).column_names.reject { |column| ['id'].include?(column) }
|
|
34
|
+
attributes = Hash[*line.select { |(k, v)| class_columns.include?(k.to_s) }.flatten]
|
|
34
35
|
@request_object.send("#{line[:line_type]}_lines").build(attributes)
|
|
35
36
|
end
|
|
36
37
|
@request_object.save!
|
|
@@ -72,7 +73,16 @@ module RequestLogAnalyzer::Aggregator
|
|
|
72
73
|
t.column(:request_id, :integer)
|
|
73
74
|
t.column(:lineno, :integer)
|
|
74
75
|
definition.captures.each do |capture|
|
|
75
|
-
|
|
76
|
+
|
|
77
|
+
# Add a field for every capture
|
|
78
|
+
t.column(capture[:name], column_type(capture[:type]))
|
|
79
|
+
|
|
80
|
+
# If the capture provides other field as well, create them too
|
|
81
|
+
if capture[:provides].kind_of?(Hash)
|
|
82
|
+
capture[:provides].each do |field, field_type|
|
|
83
|
+
t.column(field, column_type(field_type))
|
|
84
|
+
end
|
|
85
|
+
end
|
|
76
86
|
end
|
|
77
87
|
end
|
|
78
88
|
ActiveRecord::Migration.add_index("#{name}_lines", [:request_id])
|
|
@@ -86,6 +96,11 @@ module RequestLogAnalyzer::Aggregator
|
|
|
86
96
|
class_name = "#{name}_line".camelize
|
|
87
97
|
klass = Class.new(ActiveRecord::Base)
|
|
88
98
|
klass.send(:belongs_to, :request)
|
|
99
|
+
|
|
100
|
+
definition.captures.each do |capture|
|
|
101
|
+
klass.send(:serialize, capture[:name], Hash) if capture[:provides]
|
|
102
|
+
end
|
|
103
|
+
|
|
89
104
|
@orm_module.const_set(class_name, klass) unless @orm_module.const_defined?(class_name)
|
|
90
105
|
@request_class.send(:has_many, "#{name}_lines".to_sym)
|
|
91
106
|
end
|
|
@@ -136,12 +151,22 @@ module RequestLogAnalyzer::Aggregator
|
|
|
136
151
|
|
|
137
152
|
# Function to determine the column type for a field
|
|
138
153
|
# TODO: make more robust / include in file-format definition
|
|
139
|
-
def column_type(
|
|
140
|
-
case
|
|
141
|
-
when :
|
|
142
|
-
when :
|
|
143
|
-
when :
|
|
144
|
-
|
|
154
|
+
def column_type(type_indicator)
|
|
155
|
+
case type_indicator
|
|
156
|
+
when :eval; :text
|
|
157
|
+
when :text; :text
|
|
158
|
+
when :string; :string
|
|
159
|
+
when :sec; :double
|
|
160
|
+
when :msec; :double
|
|
161
|
+
when :duration; :double
|
|
162
|
+
when :float; :double
|
|
163
|
+
when :double; :double
|
|
164
|
+
when :integer; :integer
|
|
165
|
+
when :int; :int
|
|
166
|
+
when :timestamp; :datetime
|
|
167
|
+
when :datetime; :datetime
|
|
168
|
+
when :date; :date
|
|
169
|
+
else :string
|
|
145
170
|
end
|
|
146
171
|
end
|
|
147
172
|
end
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require File.dirname(__FILE__) + '/../tracker'
|
|
2
|
-
|
|
3
1
|
module RequestLogAnalyzer::Aggregator
|
|
4
2
|
|
|
5
3
|
class Summarizer < Base
|
|
@@ -11,16 +9,24 @@ module RequestLogAnalyzer::Aggregator
|
|
|
11
9
|
def initialize
|
|
12
10
|
@trackers = []
|
|
13
11
|
end
|
|
12
|
+
|
|
13
|
+
def initialize_copy(other)
|
|
14
|
+
@trackers = other.trackers.dup
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def reset!
|
|
18
|
+
@trackers = []
|
|
19
|
+
end
|
|
14
20
|
|
|
15
21
|
def method_missing(tracker_method, *args)
|
|
16
22
|
track(tracker_method, args.first)
|
|
17
23
|
end
|
|
18
24
|
|
|
19
|
-
def
|
|
25
|
+
def frequency(category_field, options = {})
|
|
20
26
|
if category_field.kind_of?(Symbol)
|
|
21
|
-
track(:
|
|
27
|
+
track(:frequency, options.merge(:category => category_field))
|
|
22
28
|
elsif category_field.kind_of?(Hash)
|
|
23
|
-
track(:
|
|
29
|
+
track(:frequency, category_field.merge(options))
|
|
24
30
|
end
|
|
25
31
|
end
|
|
26
32
|
|
|
@@ -54,7 +60,7 @@ module RequestLogAnalyzer::Aggregator
|
|
|
54
60
|
def prepare
|
|
55
61
|
raise "No trackers set up in Summarizer!" if @trackers.nil? || @trackers.empty?
|
|
56
62
|
@trackers.each { |tracker| tracker.prepare }
|
|
57
|
-
|
|
63
|
+
end
|
|
58
64
|
|
|
59
65
|
def aggregate(request)
|
|
60
66
|
@trackers.each do |tracker|
|
|
@@ -82,23 +88,23 @@ module RequestLogAnalyzer::Aggregator
|
|
|
82
88
|
|
|
83
89
|
output.with_style(:cell_separator => false) do
|
|
84
90
|
output.table({:width => 20}, {:font => :bold}) do |rows|
|
|
85
|
-
rows << ['Parsed lines:',
|
|
86
|
-
rows << ['Parsed
|
|
87
|
-
rows << ['Skipped lines:',
|
|
91
|
+
rows << ['Parsed lines:', source.parsed_lines]
|
|
92
|
+
rows << ['Parsed requests:', source.parsed_requests]
|
|
93
|
+
rows << ['Skipped lines:', source.skipped_lines]
|
|
88
94
|
|
|
89
|
-
rows <<
|
|
95
|
+
rows << ["Warnings:", @warnings_encountered.map { |(key, value)| "#{key}: #{value}" }.join(', ')] if has_warnings?
|
|
90
96
|
end
|
|
91
97
|
end
|
|
92
98
|
output << "\n"
|
|
93
99
|
end
|
|
94
100
|
|
|
95
101
|
def report_footer(output)
|
|
96
|
-
if
|
|
97
|
-
|
|
102
|
+
if has_log_ordering_warnings?
|
|
98
103
|
output.title("Parse warnings")
|
|
99
104
|
|
|
100
|
-
output.puts "
|
|
101
|
-
output.puts "is not setup correctly
|
|
105
|
+
output.puts "Parseable lines were ancountered without a header line before it. It"
|
|
106
|
+
output.puts "could be that logging is not setup correctly for your application."
|
|
107
|
+
output.puts "Visit this website for logging configuration tips:"
|
|
102
108
|
output.puts output.link("http://github.com/wvanbergen/request-log-analyzer/wikis/configure-logging")
|
|
103
109
|
output.puts
|
|
104
110
|
end
|
|
@@ -108,8 +114,8 @@ module RequestLogAnalyzer::Aggregator
|
|
|
108
114
|
@warnings_encountered.inject(0) { |result, (key, value)| result += value } > 0
|
|
109
115
|
end
|
|
110
116
|
|
|
111
|
-
def
|
|
112
|
-
@warnings_encountered
|
|
117
|
+
def has_log_ordering_warnings?
|
|
118
|
+
@warnings_encountered[:no_current_request] && @warnings_encountered[:no_current_request] > 0
|
|
113
119
|
end
|
|
114
120
|
|
|
115
121
|
def warning(type, message, lineno)
|
|
@@ -64,7 +64,7 @@ module RequestLogAnalyzer
|
|
|
64
64
|
|
|
65
65
|
controller = Controller.new(RequestLogAnalyzer::Source::LogParser.new(file_format, options), options)
|
|
66
66
|
|
|
67
|
-
options[:
|
|
67
|
+
options[:parse_strategy] = arguments[:parse_strategy]
|
|
68
68
|
|
|
69
69
|
# register filters
|
|
70
70
|
if arguments[:after] || arguments[:before]
|
|
@@ -131,7 +131,7 @@ module RequestLogAnalyzer
|
|
|
131
131
|
def handle_progress(message, value = nil)
|
|
132
132
|
case message
|
|
133
133
|
when :started
|
|
134
|
-
@progress_bar = CommandLine::ProgressBar.new(File.basename(value), File.size(value))
|
|
134
|
+
@progress_bar = CommandLine::ProgressBar.new(File.basename(value), File.size(value), STDOUT)
|
|
135
135
|
when :finished
|
|
136
136
|
@progress_bar.finish
|
|
137
137
|
@progress_bar = nil
|
|
@@ -160,6 +160,24 @@ module RequestLogAnalyzer
|
|
|
160
160
|
@filters << filter.new(file_format, @options.merge(filter_options))
|
|
161
161
|
end
|
|
162
162
|
|
|
163
|
+
# Push a request through the entire filterchain (@filters).
|
|
164
|
+
# <tt>request</tt> The request to filter.
|
|
165
|
+
# Returns the filtered request or nil.
|
|
166
|
+
def filter_request(request)
|
|
167
|
+
@filters.each do |filter|
|
|
168
|
+
request = filter.filter(request)
|
|
169
|
+
return nil if request.nil?
|
|
170
|
+
end
|
|
171
|
+
return request
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Push a request to all the aggregators (@aggregators).
|
|
175
|
+
# <tt>request</tt> The request to push to the aggregators.
|
|
176
|
+
def aggregate_request(request)
|
|
177
|
+
return unless request
|
|
178
|
+
@aggregators.each { |agg| agg.aggregate(request) }
|
|
179
|
+
end
|
|
180
|
+
|
|
163
181
|
# Runs RequestLogAnalyzer
|
|
164
182
|
# 1. Call prepare on every aggregator
|
|
165
183
|
# 2. Generate requests from source object
|
|
@@ -170,13 +188,11 @@ module RequestLogAnalyzer
|
|
|
170
188
|
# 6. Finalize Source
|
|
171
189
|
def run!
|
|
172
190
|
|
|
173
|
-
@filters.each { |filter| filter.prepare }
|
|
174
191
|
@aggregators.each { |agg| agg.prepare }
|
|
175
192
|
|
|
176
193
|
begin
|
|
177
194
|
@source.each_request do |request|
|
|
178
|
-
|
|
179
|
-
@aggregators.each { |agg| agg.aggregate(request) } if request
|
|
195
|
+
aggregate_request(filter_request(request))
|
|
180
196
|
end
|
|
181
197
|
rescue Interrupt => e
|
|
182
198
|
handle_progress(:interrupted)
|
|
@@ -7,15 +7,16 @@ module RequestLogAnalyzer::FileFormat
|
|
|
7
7
|
line.header = true
|
|
8
8
|
line.teaser = /Started/
|
|
9
9
|
line.regexp = /Started request handling\:\ (.+)/
|
|
10
|
-
line.captures << { :name => :timestamp, :type => :timestamp
|
|
10
|
+
line.captures << { :name => :timestamp, :type => :timestamp }
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
# ~ Params: {"action"=>"create", "controller"=>"session"}
|
|
14
14
|
# ~ Params: {"_method"=>"delete", "authenticity_token"=>"[FILTERED]", "action"=>"d}
|
|
15
15
|
line_definition :params do |line|
|
|
16
16
|
line.teaser = /Params/
|
|
17
|
-
line.regexp = /Params\:\ \{
|
|
18
|
-
line.captures << { :name => :
|
|
17
|
+
line.regexp = /Params\:\ (\{.+\})/
|
|
18
|
+
line.captures << { :name => :params, :type => :eval, :provides => {
|
|
19
|
+
:namespace => :string, :controller => :string, :action => :string, :format => :string } }
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
# ~ {:dispatch_time=>0.006117, :after_filters_time=>6.1e-05, :before_filters_time=>0.000712, :action_time=>0.005833}
|
|
@@ -23,17 +24,29 @@ module RequestLogAnalyzer::FileFormat
|
|
|
23
24
|
line.footer = true
|
|
24
25
|
line.teaser = /\{:dispatch_time/
|
|
25
26
|
line.regexp = /\{\:dispatch_time=>(\d+\.\d+(?:e-?\d+)?), (?:\:after_filters_time=>(\d+\.\d+(?:e-?\d+)?), )?(?:\:before_filters_time=>(\d+\.\d+(?:e-?\d+)?), )?\:action_time=>(\d+\.\d+(?:e-?\d+)?)\}/
|
|
26
|
-
line.captures << { :name => :dispatch_time, :type => :
|
|
27
|
-
<< { :name => :after_filters_time, :type => :
|
|
28
|
-
<< { :name => :before_filters_time, :type => :
|
|
29
|
-
<< { :name => :action_time, :type => :
|
|
27
|
+
line.captures << { :name => :dispatch_time, :type => :duration } \
|
|
28
|
+
<< { :name => :after_filters_time, :type => :duration } \
|
|
29
|
+
<< { :name => :before_filters_time, :type => :duration } \
|
|
30
|
+
<< { :name => :action_time, :type => :duration }
|
|
30
31
|
end
|
|
31
32
|
|
|
33
|
+
REQUEST_CATEGORIZER = Proc.new do |request|
|
|
34
|
+
category = "#{request[:controller]}##{request[:action]}"
|
|
35
|
+
category = "#{request[:namespace]}::#{category}" if request[:namespace]
|
|
36
|
+
category = "#{category}.#{request[:format]}" if request[:format]
|
|
37
|
+
category
|
|
38
|
+
end
|
|
32
39
|
|
|
33
40
|
report do |analyze|
|
|
34
|
-
|
|
41
|
+
analyze.timespan :line_type => :started
|
|
42
|
+
analyze.frequency :category => REQUEST_CATEGORIZER, :amount => 20, :title => "Top 20 by hits"
|
|
43
|
+
analyze.hourly_spread :line_type => :started
|
|
44
|
+
analyze.duration :dispatch_time, :category => REQUEST_CATEGORIZER, :title => 'Request dispatch duration'
|
|
45
|
+
# analyze.duration :action_time, :category => REQUEST_CATEGORIZER, :title => 'Request action duration'
|
|
46
|
+
# analyze.duration :after_filters_time, :category => REQUEST_CATEGORIZER, :title => 'Request after_filter duration'
|
|
47
|
+
# analyze.duration :before_filters_time, :category => REQUEST_CATEGORIZER, :title => 'Request before_filter duration'
|
|
35
48
|
end
|
|
36
|
-
|
|
49
|
+
|
|
37
50
|
end
|
|
38
51
|
|
|
39
52
|
end
|
|
@@ -9,9 +9,9 @@ module RequestLogAnalyzer::FileFormat
|
|
|
9
9
|
line.regexp = /Processing ((?:\w+::)?\w+)#(\w+)(?: to (\w+))? \(for (\d+\.\d+\.\d+\.\d+) at (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)\) \[([A-Z]+)\]/
|
|
10
10
|
line.captures << { :name => :controller, :type => :string } \
|
|
11
11
|
<< { :name => :action, :type => :string } \
|
|
12
|
-
<< { :name => :format, :type => :
|
|
13
|
-
<< { :name => :ip, :type => :string
|
|
14
|
-
<< { :name => :timestamp, :type => :timestamp
|
|
12
|
+
<< { :name => :format, :type => :format } \
|
|
13
|
+
<< { :name => :ip, :type => :string } \
|
|
14
|
+
<< { :name => :timestamp, :type => :timestamp } \
|
|
15
15
|
<< { :name => :method, :type => :string }
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -28,7 +28,7 @@ module RequestLogAnalyzer::FileFormat
|
|
|
28
28
|
<< { :name => :message, :type => :string } \
|
|
29
29
|
<< { :name => :line, :type => :integer } \
|
|
30
30
|
<< { :name => :file, :type => :string } \
|
|
31
|
-
<< { :name => :stack_trace, :type => :string
|
|
31
|
+
<< { :name => :stack_trace, :type => :string }
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
|
|
@@ -49,42 +49,56 @@ module RequestLogAnalyzer::FileFormat
|
|
|
49
49
|
line.teaser = /Completed in /
|
|
50
50
|
line.regexp = Regexp.new("(?:#{RAILS_21_COMPLETED}|#{RAILS_22_COMPLETED})")
|
|
51
51
|
|
|
52
|
-
line.captures << { :name => :duration, :type => :
|
|
53
|
-
<< { :name => :view, :type => :
|
|
54
|
-
<< { :name => :db, :type => :
|
|
52
|
+
line.captures << { :name => :duration, :type => :duration, :unit => :sec } \
|
|
53
|
+
<< { :name => :view, :type => :duration, :unit => :sec } \
|
|
54
|
+
<< { :name => :db, :type => :duration, :unit => :sec } \
|
|
55
55
|
<< { :name => :status, :type => :integer } \
|
|
56
|
-
<< { :name => :url, :type => :string
|
|
56
|
+
<< { :name => :url, :type => :string } # Old variant
|
|
57
57
|
|
|
58
|
-
line.captures << { :name => :duration, :type => :
|
|
59
|
-
<< { :name => :view, :type => :
|
|
60
|
-
<< { :name => :db, :type => :
|
|
61
|
-
<< { :name => :status, :type => :integer} \
|
|
62
|
-
<< { :name => :url, :type => :string
|
|
58
|
+
line.captures << { :name => :duration, :type => :duration, :unit => :msec } \
|
|
59
|
+
<< { :name => :view, :type => :duration, :unit => :msec } \
|
|
60
|
+
<< { :name => :db, :type => :duration, :unit => :msec } \
|
|
61
|
+
<< { :name => :status, :type => :integer } \
|
|
62
|
+
<< { :name => :url, :type => :string } # 2.2 variant
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
REQUEST_CATEGORIZER = Proc.new do |request|
|
|
68
|
-
format
|
|
69
|
-
"#{request[:controller]}##{request[:action]}.#{format} [#{request[:method]}]"
|
|
68
|
+
"#{request[:controller]}##{request[:action]}.#{request[:format]} [#{request[:method]}]"
|
|
70
69
|
end
|
|
71
70
|
|
|
72
71
|
report do |analyze|
|
|
73
72
|
analyze.timespan :line_type => :processing
|
|
74
|
-
analyze.
|
|
75
|
-
analyze.
|
|
76
|
-
analyze.
|
|
77
|
-
analyze.
|
|
73
|
+
analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
|
|
74
|
+
analyze.frequency :method, :title => 'HTTP methods'
|
|
75
|
+
analyze.frequency :status, :title => 'HTTP statuses returned'
|
|
76
|
+
analyze.frequency :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
|
|
78
77
|
|
|
79
78
|
analyze.duration :duration, :category => REQUEST_CATEGORIZER, :title => "Request duration", :line_type => :completed
|
|
80
|
-
analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "
|
|
81
|
-
analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "
|
|
79
|
+
analyze.duration :view, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
|
|
80
|
+
analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "Database time", :line_type => :completed
|
|
82
81
|
|
|
83
|
-
analyze.
|
|
82
|
+
analyze.frequency :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
|
|
84
83
|
:if => lambda { |request| request[:duration] && request[:duration] > 1.0 }, :amount => 20
|
|
85
84
|
|
|
86
85
|
analyze.hourly_spread :line_type => :processing
|
|
87
|
-
analyze.
|
|
86
|
+
analyze.frequency :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Define a custom Request class for the Rails file format to speed up timestamp handling
|
|
90
|
+
# and to ensure that a format is always set.
|
|
91
|
+
class Request < RequestLogAnalyzer::Request
|
|
92
|
+
|
|
93
|
+
# Do not use DateTime.parse
|
|
94
|
+
def convert_timestamp(value, definition)
|
|
95
|
+
value.gsub(/[^0-9]/, '')[0...14].to_i unless value.nil?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Set 'html' as default format for a request
|
|
99
|
+
def convert_format(value, definition)
|
|
100
|
+
value || 'html'
|
|
101
|
+
end
|
|
88
102
|
end
|
|
89
103
|
|
|
90
104
|
end
|