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.
Files changed (62) hide show
  1. data/DESIGN +24 -10
  2. data/README.rdoc +1 -1
  3. data/Rakefile +1 -2
  4. data/bin/request-log-analyzer +4 -38
  5. data/lib/cli/progressbar.rb +2 -19
  6. data/lib/cli/tools.rb +46 -0
  7. data/lib/request_log_analyzer/aggregator/database.rb +33 -8
  8. data/lib/request_log_analyzer/aggregator/echo.rb +1 -0
  9. data/lib/request_log_analyzer/aggregator/summarizer.rb +22 -16
  10. data/lib/request_log_analyzer/controller.rb +21 -5
  11. data/lib/request_log_analyzer/file_format/merb.rb +22 -9
  12. data/lib/request_log_analyzer/file_format/rails.rb +37 -23
  13. data/lib/request_log_analyzer/file_format/rails_development.rb +33 -88
  14. data/lib/request_log_analyzer/file_format.rb +50 -18
  15. data/lib/request_log_analyzer/filter/{anonimize.rb → anonymize.rb} +3 -8
  16. data/lib/request_log_analyzer/filter/field.rb +7 -6
  17. data/lib/request_log_analyzer/filter/timespan.rb +7 -3
  18. data/lib/request_log_analyzer/filter.rb +0 -4
  19. data/lib/request_log_analyzer/line_definition.rb +22 -88
  20. data/lib/request_log_analyzer/log_processor.rb +5 -25
  21. data/lib/request_log_analyzer/output/fixed_width.rb +14 -3
  22. data/lib/request_log_analyzer/output/html.rb +1 -0
  23. data/lib/request_log_analyzer/request.rb +62 -4
  24. data/lib/request_log_analyzer/source/database.rb +76 -0
  25. data/lib/request_log_analyzer/source/log_parser.rb +104 -52
  26. data/lib/request_log_analyzer/source.rb +28 -6
  27. data/lib/request_log_analyzer/tracker/duration.rb +90 -15
  28. data/lib/request_log_analyzer/tracker/{category.rb → frequency.rb} +24 -12
  29. data/lib/request_log_analyzer/tracker/hourly_spread.rb +20 -9
  30. data/lib/request_log_analyzer/tracker/timespan.rb +15 -8
  31. data/lib/request_log_analyzer.rb +29 -16
  32. data/spec/integration/command_line_usage_spec.rb +71 -0
  33. data/spec/lib/helper.rb +33 -0
  34. data/spec/lib/mocks.rb +47 -0
  35. data/spec/lib/testing_format.rb +39 -0
  36. data/spec/spec_helper.rb +5 -25
  37. data/spec/{database_inserter_spec.rb → unit/aggregator/database_inserter_spec.rb} +40 -37
  38. data/spec/unit/aggregator/summarizer_spec.rb +28 -0
  39. data/spec/unit/controller/controller_spec.rb +43 -0
  40. data/spec/unit/controller/log_processor_spec.rb +20 -0
  41. data/spec/{file_format_spec.rb → unit/file_format/file_format_api_spec.rb} +18 -6
  42. data/spec/unit/file_format/line_definition_spec.rb +64 -0
  43. data/spec/{merb_format_spec.rb → unit/file_format/merb_format_spec.rb} +3 -3
  44. data/spec/{rails_format_spec.rb → unit/file_format/rails_format_spec.rb} +24 -15
  45. data/spec/unit/filter/anonymize_filter_spec.rb +22 -0
  46. data/spec/unit/filter/field_filter_spec.rb +69 -0
  47. data/spec/unit/filter/timespan_filter_spec.rb +61 -0
  48. data/spec/{log_parser_spec.rb → unit/source/log_parser_spec.rb} +9 -10
  49. data/spec/{request_spec.rb → unit/source/request_spec.rb} +5 -5
  50. data/spec/unit/tracker/duration_tracker_spec.rb +99 -0
  51. data/spec/unit/tracker/frequency_tracker_spec.rb +83 -0
  52. data/spec/unit/tracker/hourly_spread_spec.rb +75 -0
  53. data/spec/unit/tracker/timespan_tracker_spec.rb +65 -0
  54. data/spec/unit/tracker/tracker_api_test.rb +45 -0
  55. data/tasks/rspec.rake +12 -0
  56. metadata +56 -28
  57. data/spec/controller_spec.rb +0 -45
  58. data/spec/file_formats/spec_format.rb +0 -27
  59. data/spec/filter_spec.rb +0 -157
  60. data/spec/line_definition_spec.rb +0 -124
  61. data/spec/log_processor_spec.rb +0 -57
  62. 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
- Controller.start
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
- At the moment the supported sources are file and STDIN.
17
- In the future we want to be able to have a generated request database as source.
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
- For the report generation output we now use the File and the STDOUT class, as they both support <<.
22
- In the future we want to have a OutputFile, OutputSTDOUT and OutputHTML class, so that reports can generate
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
@@ -1,4 +1,3 @@
1
1
  Dir[File.dirname(__FILE__) + "/tasks/*.rake"].each { |file| load(file) }
2
2
 
3
- task :default => :spec
4
-
3
+ task :default => :spec
@@ -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
- def terminal_width(default = 81)
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.switch(:assume_correct_order)
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"
@@ -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 = get_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(" " * (get_width - 1))
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
- attributes = line.reject { |k, v| [:line_type].include?(k) }
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
- t.column(capture[:name], column_type(capture))
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(capture)
140
- case capture[:type]
141
- when :sec; :double
142
- when :msec; :double
143
- when :float; :double
144
- else capture[:type]
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,6 @@
1
1
  module RequestLogAnalyzer::Aggregator
2
2
 
3
+ # Echo Aggregator. Writes everything passed to it
3
4
  class Echo < Base
4
5
 
5
6
  def prepare
@@ -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 category(category_field, options = {})
25
+ def frequency(category_field, options = {})
20
26
  if category_field.kind_of?(Symbol)
21
- track(:category, options.merge(:category => category_field))
27
+ track(:frequency, options.merge(:category => category_field))
22
28
  elsif category_field.kind_of?(Hash)
23
- track(:category, category_field.merge(options))
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
- end
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:', source.parsed_lines]
86
- rows << ['Parsed request:', source.parsed_requests]
87
- rows << ['Skipped lines:', source.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 << ["Warnings:", @warnings_encountered.map { |(key, value)| "#{key.inspect}: #{value}" }.join(', ')] if has_warnings?
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 has_serious_warnings?
97
-
102
+ if has_log_ordering_warnings?
98
103
  output.title("Parse warnings")
99
104
 
100
- output.puts "Multiple warnings were encountered during parsing. Possibly, your logging "
101
- output.puts "is not setup correctly. Visit this website for logging configuration tips:"
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 has_serious_warnings?
112
- @warnings_encountered.inject(0) { |result, (key, value)| result += value } > 10
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[:assume_correct_order] = arguments[:assume_correct_order]
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
- @filters.each { |filter| request = filter.filter(request) }
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, :anonymize => :slightly }
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 => :raw_hash, :type => :string}
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 => :sec, :anonymize => :slightly } \
27
- << { :name => :after_filters_time, :type => :sec, :anonymize => :slightly } \
28
- << { :name => :before_filters_time, :type => :sec, :anonymize => :slightly } \
29
- << { :name => :action_time, :type => :sec, :anonymize => :slightly }
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
- # FIX ME
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 => :string } \
13
- << { :name => :ip, :type => :string, :anonymize => :ip } \
14
- << { :name => :timestamp, :type => :timestamp, :anonymize => :slightly } \
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, :anonymize => true }
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 => :sec, :anonymize => :slightly } \
53
- << { :name => :view, :type => :sec, :anonymize => :slightly } \
54
- << { :name => :db, :type => :sec, :anonymize => :slightly } \
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, :anonymize => :url } # Old variant
56
+ << { :name => :url, :type => :string } # Old variant
57
57
 
58
- line.captures << { :name => :duration, :type => :msec, :anonymize => :slightly } \
59
- << { :name => :view, :type => :msec, :anonymize => :slightly } \
60
- << { :name => :db, :type => :msec, :anonymize => :slightly } \
61
- << { :name => :status, :type => :integer} \
62
- << { :name => :url, :type => :string, :anonymize => :url } # 2.2 variant
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 = request[:format] || 'html'
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.category :category => REQUEST_CATEGORIZER, :title => 'Top 20 hits', :amount => 20, :line_type => :processing
75
- analyze.category :method, :title => 'HTTP methods'
76
- analyze.category :status, :title => 'HTTP statuses returned'
77
- analyze.category :category => lambda { |request| request =~ :cache_hit ? 'Cache hit' : 'No hit' }, :title => 'Rails action cache hits'
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 => "Database time", :line_type => :completed
81
- analyze.duration :db, :category => REQUEST_CATEGORIZER, :title => "View rendering time", :line_type => :completed
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.category :category => REQUEST_CATEGORIZER, :title => 'Process blockers (> 1 sec duration)',
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.category :error, :title => 'Failed requests', :line_type => :failed, :amount => 20
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