wvanbergen-request-log-analyzer 1.0.0 → 1.0.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 (55) hide show
  1. data/DESIGN +14 -0
  2. data/HACKING +7 -0
  3. data/README.textile +9 -98
  4. data/Rakefile +2 -2
  5. data/bin/request-log-analyzer +1 -1
  6. data/lib/cli/bashcolorizer.rb +60 -0
  7. data/lib/cli/command_line_arguments.rb +301 -0
  8. data/lib/cli/progressbar.rb +236 -0
  9. data/lib/request_log_analyzer/aggregator/base.rb +51 -0
  10. data/lib/request_log_analyzer/aggregator/database.rb +97 -0
  11. data/lib/request_log_analyzer/aggregator/echo.rb +25 -0
  12. data/lib/request_log_analyzer/aggregator/summarizer.rb +116 -0
  13. data/lib/request_log_analyzer/controller.rb +206 -0
  14. data/lib/request_log_analyzer/file_format/merb.rb +33 -0
  15. data/lib/request_log_analyzer/file_format/rails.rb +119 -0
  16. data/lib/request_log_analyzer/file_format.rb +77 -0
  17. data/lib/request_log_analyzer/filter/base.rb +29 -0
  18. data/lib/request_log_analyzer/filter/field.rb +36 -0
  19. data/lib/request_log_analyzer/filter/timespan.rb +32 -0
  20. data/lib/request_log_analyzer/line_definition.rb +159 -0
  21. data/lib/request_log_analyzer/log_parser.rb +183 -0
  22. data/lib/request_log_analyzer/log_processor.rb +121 -0
  23. data/lib/request_log_analyzer/request.rb +115 -0
  24. data/lib/request_log_analyzer/source/base.rb +42 -0
  25. data/lib/request_log_analyzer/source/log_file.rb +180 -0
  26. data/lib/request_log_analyzer/tracker/base.rb +54 -0
  27. data/lib/request_log_analyzer/tracker/category.rb +71 -0
  28. data/lib/request_log_analyzer/tracker/duration.rb +81 -0
  29. data/lib/request_log_analyzer/tracker/hourly_spread.rb +80 -0
  30. data/lib/request_log_analyzer/tracker/timespan.rb +54 -0
  31. data/spec/file_format_spec.rb +78 -0
  32. data/spec/file_formats/spec_format.rb +26 -0
  33. data/spec/filter_spec.rb +137 -0
  34. data/spec/log_processor_spec.rb +57 -0
  35. data/tasks/rspec.rake +6 -0
  36. metadata +53 -55
  37. data/TODO +0 -58
  38. data/bin/request-log-database +0 -81
  39. data/lib/base/log_parser.rb +0 -78
  40. data/lib/base/record_inserter.rb +0 -139
  41. data/lib/command_line/arguments.rb +0 -129
  42. data/lib/command_line/flag.rb +0 -51
  43. data/lib/merb_analyzer/log_parser.rb +0 -26
  44. data/lib/rails_analyzer/log_parser.rb +0 -35
  45. data/lib/rails_analyzer/record_inserter.rb +0 -39
  46. data/tasks/test.rake +0 -8
  47. data/test/log_fragments/fragment_1.log +0 -59
  48. data/test/log_fragments/fragment_2.log +0 -5
  49. data/test/log_fragments/fragment_3.log +0 -12
  50. data/test/log_fragments/fragment_4.log +0 -10
  51. data/test/log_fragments/fragment_5.log +0 -24
  52. data/test/log_fragments/merb_1.log +0 -84
  53. data/test/merb_log_parser_test.rb +0 -39
  54. data/test/rails_log_parser_test.rb +0 -94
  55. data/test/record_inserter_test.rb +0 -45
data/DESIGN ADDED
@@ -0,0 +1,14 @@
1
+ Request-log-analyzer is set up like a simple pipe and filter system.
2
+
3
+ This allows you to easily add extra reports, filters and outputs.
4
+
5
+ 1) Build pipeline.
6
+ -> Aggregator (database)
7
+ Source -> Filter -> Filter -> Aggregator (summary report)
8
+ -> Aggregator (...)
9
+
10
+ 2) Start chunk producer and push chunks through pipeline.
11
+ Controller.start
12
+
13
+ 3) Gather output from pipeline.
14
+ Controller.report
data/HACKING ADDED
@@ -0,0 +1,7 @@
1
+ HACKING on r-l-a
2
+ ----------------
3
+
4
+ - See DESIGN for the basic internal design of r-l-a
5
+ - See http://wiki.github.com/wvanbergen/request-log-analyzer/development for
6
+ more information about developing
7
+ - Contact willem AT vanbergen DOT org for any questions
data/README.textile CHANGED
@@ -3,13 +3,18 @@ h1. Request log analyzer
3
3
  This is a simple command line tool to analyze request log files of both Rails and
4
4
  Merb. Its purpose is to find what actions are best candidates for optimization.
5
5
 
6
- * Analyzes Rails logs (all versions) and Merb logs
6
+ * Analyzes Rails log files (all versions)
7
7
  * Can combine multiple files (handy if you are using logrotate)
8
- * Uses several metrics (cumulative time, average time, blockers, DB time, etc)
8
+ * Uses several metrics, including cumulative request time, average request time, process blockers, database and rendering time, HTTP methods and states, Rails action cache statistics, etc.) ("Sample output":http://wiki.github.com/wvanbergen/request-log-analyzer/sample-output)
9
9
  * Low memory footprint (server-safe)
10
10
  * MIT licensed
11
11
  * Fast
12
12
 
13
+ h2. Additional information
14
+
15
+ * "Project wiki at GitHub":http://wiki.github.com/wvanbergen/request-log-analyzer
16
+ * "wvanbergen's blog posts":http://techblog.floorplanner.com/tag/request-log-analyzer/
17
+
13
18
  h2. Installation
14
19
 
15
20
  @sudo gem install wvanbergen-request-log-analyzer --source http://gems.github.com@
@@ -30,10 +35,11 @@ h2. Usage
30
35
  --boring, -b Output reports without ASCII colors.
31
36
  --database <filename>, -d: Creates an SQLite3 database of all the parsed request information.
32
37
  --debug Print debug information while parsing.
38
+ --file <filename> Output to file.
33
39
 
34
40
  Examples:
35
41
  request-log-analyzer development.log
36
- request-log-analyzer -z mongrel.0.log mongrel.1.log mongrel.2.log
42
+ request-log-analyzer mongrel.0.log mongrel.1.log mongrel.2.log
37
43
  request-log-analyzer --format merb -d requests.db production.log
38
44
 
39
45
  To install rake tasks in your Rails application,
@@ -41,98 +47,3 @@ h2. Usage
41
47
 
42
48
  request-log-analyzer install rails
43
49
  </pre>
44
-
45
-
46
- h2. Example result
47
-
48
- Note that this example was shortened for your viewing pleasure.
49
- @$ request-log-analyzer /var/log/my_app.log -o 5@
50
-
51
- <pre>
52
- Request log analyzer, by Willem van Bergen and Bart ten Brinke
53
-
54
- Processing started, failed, completed log lines from /var/log/my_app.log...
55
-
56
- Processing all log lines...
57
- ========================================================================
58
- Timestamp first request: 2008-07-13T06:25:58+00:00
59
- Timestamp last request: 2008-07-20T06:18:53+00:00
60
- Total time analyzed: 7 days
61
-
62
- Total requests analyzed 58908 requests from log file
63
- Methods: GET (50.6%), POST (22.8%), PUT (25.4%), DELETE (1.1%), unknown (0.0%).
64
-
65
- Top 5 most requested actions
66
- ========================================================================
67
- /overview/:date/ : 19359 requests
68
- /overview/day/:date/ : 6365 requests
69
- /overview/:date/set/ : 5589 requests
70
- /overview/ : 3985 requests
71
- /clients/:id/ : 1976 requests
72
-
73
- Top 5 actions by time - cumulative
74
- ========================================================================
75
- /overview/:date/ : 9044.582s [19359 requests]
76
- /overview/ : 8478.767s [3985 requests]
77
- /overview/:date/set/ : 3309.041s [5589 requests]
78
- /clients/:id/products/:id/ : 1479.911s [924 requests]
79
- /clients/:id/ : 750.080s [1976 requests]
80
-
81
- Top 5 actions by time - per request mean
82
- ========================================================================
83
- /overview/ : 2.128s [3985 requests]
84
- /clients/:id/products/:id/ : 1.602s [924 requests]
85
- /overview/:date/set/ : 0.592s [5589 requests]
86
- /overview/:date/ : 0.467s [19359 requests]
87
- /clients/:id/ : 0.380s [1976 requests]
88
-
89
- Top 5 worst DB offenders - cumulative time
90
- ========================================================================
91
- /overview/:date/ : 8773.993s [19359 requests]
92
- /overview/ : 8394.754s [3985 requests]
93
- /overview/:date/set/ : 3307.928s [5589 requests]
94
- /clients/:id/products/:id/ : 1425.220s [924 requests]
95
- /clients/:id/ : 535.229s [1976 requests]
96
-
97
- Top 5 worst DB offenders - mean time
98
- ========================================================================
99
- /overview/:id/:id/:id/print/ : 6.994s [448 requests]
100
- /overview/ : 2.128s [3985 requests]
101
- /clients/:id/products/:id/ : 1.602s [924 requests]
102
- /overview/:date/set/ : 0.592s [5589 requests]
103
- /overview/:date/ : 0.467s [19359 requests]
104
-
105
- Mongrel process blockers (> 1.0 seconds)
106
- ========================================================================
107
- /overview/:date/ : 7494.233s [3144 requests]
108
- /overview/ : 8320.293s [1549 requests]
109
- /overview/:date/set/ : 1149.235s [803 requests]
110
- /overview/:id/:id/:id/print/new/ : 613.693s [341 requests]
111
- /clients/:id/products/:id/ : 1370.693s [313 requests]
112
-
113
- Requests graph - per hour
114
- ========================================================================
115
- ........
116
- 7:00 - 2731 : XXXXXXX
117
- 8:00 - 6139 : XXXXXXXXXXXXXXXX
118
- 9:00 - 7465 : XXXXXXXXXXXXXXXXXXXX
119
- 10:00 - 7118 : XXXXXXXXXXXXXXXXXXX
120
- 11:00 - 7409 : XXXXXXXXXXXXXXXXXXX
121
- 12:00 - 6450 : XXXXXXXXXXXXXXXXX
122
- 13:00 - 5377 : XXXXXXXXXXXXXX
123
- 14:00 - 6058 : XXXXXXXXXXXXXXXX
124
- 15:00 - 4156 : XXXXXXXXXXX
125
- 16:00 - 2767 : XXXXXXX
126
- 17:00 - 1598 : XXXX
127
- 18:00 - 792 : XX
128
- ........
129
-
130
- Errors
131
- ========================================================================
132
- StaleObjectError: [28 requests]
133
- -> Attempted to update a stale object
134
- StatementError: [2 requests]
135
- -> Mysql::Error: Deadlock found when trying to get lock; try restarting transaction
136
- NoMethodError: [1 requests]
137
- -> undefined method `code' for nil:NilClass
138
- </pre>
data/Rakefile CHANGED
@@ -1,5 +1,5 @@
1
1
  Dir[File.dirname(__FILE__) + "/tasks/*.rake"].each { |file| load(file) }
2
2
 
3
- desc 'Default: run unit tests for request-log-analyzer.'
4
- task :default => :test
3
+ desc 'Default: run RSpec for request-log-analyzer.'
4
+ task :default => :spec
5
5
 
@@ -114,7 +114,7 @@ when :anonymize
114
114
  require File.dirname(__FILE__) + '/../lib/request_log_analyzer/log_processor'
115
115
  RequestLogAnalyzer::LogProcessor.build(:anonymize, arguments).run!
116
116
  else
117
- puts "Request log analyzer, by Willem van Bergen and Bart ten Brinke - Version 0.4.0\n\n"
117
+ puts "Request log analyzer, by Willem van Bergen and Bart ten Brinke - Version 1.0\n\n"
118
118
 
119
119
  # Run the request_log_analyzer!
120
120
  RequestLogAnalyzer::Controller.build(arguments, terminal_width).run!
@@ -0,0 +1,60 @@
1
+ # Colorize a text output with the given color if.
2
+ # <tt>text</tt> The text to colorize.
3
+ # <tt>color_code</tt> The color code string to set
4
+ # <tt>color</tt> Does not color if false. Defaults to false
5
+ def colorize(text, color_code, color = false)
6
+ color ? "#{color_code}#{text}\e[0m" : text
7
+ end
8
+
9
+ # Draw a red line of text
10
+ def red(text, color = false)
11
+ colorize(text, "\e[31m", color)
12
+ end
13
+
14
+ # Draw a Green line of text
15
+ def green(text, color = false)
16
+ colorize(text, "\e[32m", color)
17
+ end
18
+
19
+ # Draw a Yellow line of text
20
+ def yellow(text, color = false)
21
+ colorize(text, "\e[33m", color)
22
+ end
23
+
24
+ # Draw a Yellow line of text
25
+ def blue(text, color = false)
26
+ colorize(text, "\e[34m", color)
27
+ end
28
+
29
+ def white(text, color = false)
30
+ colorize(text, "\e[37m", color)
31
+ end
32
+
33
+
34
+ #STYLE = {
35
+ # :default => “33[0m”,
36
+ # # styles
37
+ # :bold => “33[1m”,
38
+ # :underline => “33[4m”,
39
+ # :blink => “33[5m”,
40
+ # :reverse => “33[7m”,
41
+ # :concealed => “33[8m”,
42
+ # # font colors
43
+ # :black => “33[30m”,
44
+ # :red => “33[31m”,
45
+ # :green => “33[32m”,
46
+ # :yellow => “33[33m”,
47
+ # :blue => “33[34m”,
48
+ # :magenta => “33[35m”,
49
+ # :cyan => “33[36m”,
50
+ # :white => “33[37m”,
51
+ # # background colors
52
+ # :on_black => “33[40m”,
53
+ # :on_red => “33[41m”,
54
+ # :on_green => “33[42m”,
55
+ # :on_yellow => “33[43m”,
56
+ # :on_blue => “33[44m”,
57
+ # :on_magenta => “33[45m”,
58
+ # :on_cyan => “33[46m”,
59
+ # :on_white => “33[47m” }
60
+ #
@@ -0,0 +1,301 @@
1
+ module CommandLine
2
+
3
+ class Option
4
+
5
+ attr_reader :name, :alias
6
+ attr_reader :parameter_count
7
+ attr_reader :default_value
8
+
9
+ # Rewrites a command line keyword by replacing the underscores with dashes
10
+ # <tt>sym</tt> The symbol to rewrite
11
+ def self.rewrite(sym)
12
+ sym.to_s.gsub(/_/, '-').to_sym
13
+ end
14
+
15
+ # Initialize new CommandLine::Option
16
+ # <tt>name</tt> The name of the flag
17
+ # <tt>definition</tt> The definition of the flag.
18
+ def initialize(name, definition = {})
19
+ @name = CommandLine::Option.rewrite(name)
20
+ @alias = definition[:alias].to_sym if definition[:alias]
21
+ @required = definition.has_key?(:required) && definition[:required] == true
22
+ @parameter_count = definition[:parameters] || 1
23
+ @multiple = definition[:multiple] || false
24
+ @default_value = definition[:default] || false
25
+ end
26
+
27
+ def parse(arguments_parser)
28
+ if @parameter_count == 0
29
+ return true
30
+ elsif @parameter_count == 1
31
+ parameter = arguments_parser.next_parameter
32
+ raise CommandLine::ParameterExpected, self if parameter.nil?
33
+ return parameter
34
+ elsif @parameter_count == :any
35
+ parameters = []
36
+ while parameter = arguments_parser.next_parameter && parameter != '--'
37
+ parameters << parameter
38
+ end
39
+ return parameters
40
+ else
41
+ parameters = []
42
+ @parameter_count.times do |n|
43
+ parameter = arguments_parser.next_parameter
44
+ raise CommandLine::ParameterExpected, self if parameter.nil?
45
+ parameters << parameter
46
+ end
47
+ return parameters
48
+ end
49
+ end
50
+
51
+ def =~(test)
52
+ [@name, @alias].include?(CommandLine::Option.rewrite(test))
53
+ end
54
+
55
+ # Argument representation of the flag (--fast)
56
+ def to_option
57
+ "--#{@name}"
58
+ end
59
+
60
+ # Argument alias representation of the flag (-f)
61
+ def to_alias
62
+ "-#{@alias}"
63
+ end
64
+
65
+ # Check if flag has an alias
66
+ def has_alias?
67
+ !@alias.nil?
68
+ end
69
+
70
+ # Check if flag is required
71
+ def required?
72
+ @required
73
+ end
74
+
75
+ # Check if flag is optional
76
+ def optional?
77
+ !@required
78
+ end
79
+
80
+ def multiple?
81
+ @multiple
82
+ end
83
+
84
+ def has_default?
85
+ !@default_value.nil?
86
+ end
87
+ end
88
+
89
+ class Arguments
90
+
91
+ class Definition
92
+
93
+ ENDLESS_PARAMETERS = 99999
94
+
95
+ attr_reader :commands, :options, :parameters
96
+
97
+ def initialize(parent)
98
+ @parent = parent
99
+ @options = {}
100
+ @commands = {}
101
+ @parameters = nil
102
+ end
103
+
104
+ def [](option_name)
105
+ option_symbol = CommandLine::Option.rewrite(option_name)
106
+ if the_option = @options.detect { |(name, odef)| odef =~ option_symbol }
107
+ the_option[1]
108
+ else
109
+ raise CommandLine::UnknownOption, option_name
110
+ end
111
+ end
112
+
113
+ def minimum_parameters=(count_specifier)
114
+ @parameters = count_specifier..ENDLESS_PARAMETERS
115
+ end
116
+
117
+ def parameters=(count_specifier)
118
+ @parameters = count_specifier
119
+ end
120
+
121
+ alias :files= :parameters=
122
+
123
+ def option(name, options = {})
124
+ clo = CommandLine::Option.new(name, options)
125
+ @options[clo.name] = clo
126
+ end
127
+
128
+ def switch(name, switch_alias = nil)
129
+ option(name, :alias => switch_alias, :parameters => 0)
130
+ end
131
+
132
+ def command(name, &block)
133
+ command_definition = Definition.new(self)
134
+ yield(command_definition) if block_given?
135
+ @commands[CommandLine::Option.rewrite(name)] = command_definition
136
+ end
137
+
138
+ def has_command?(command)
139
+ @commands[CommandLine::Option.rewrite(command)]
140
+ end
141
+ end
142
+
143
+ OPTION_REGEXP = /^\-\-([A-z0-9-]+)$/;
144
+ ALIASES_REGEXP = /^\-([A-z0-9]+)$/
145
+
146
+ attr_reader :definition
147
+ attr_reader :tokens
148
+ attr_reader :command, :options, :parameters
149
+
150
+ def self.parse(tokens = $*, &block)
151
+ cla = Arguments.new
152
+ cla.define(&block)
153
+ return cla.parse!(tokens)
154
+ end
155
+
156
+ def initialize
157
+ @tokens = []
158
+ @definition = Definition.new(self)
159
+ @current_definition = @definition
160
+ end
161
+
162
+ def define(&block)
163
+ yield(@definition)
164
+ end
165
+
166
+ def [](option)
167
+ if the_option = @options.detect { |(key, value)| key =~ option }
168
+ the_option[1]
169
+ else
170
+ @current_definition[option].default_value
171
+ end
172
+ end
173
+
174
+ def next_token
175
+ @current_token = @tokens.shift
176
+ return @current_token
177
+ end
178
+
179
+ def next_parameter
180
+ parameter_candidate = @tokens.first
181
+ parameter = (parameter_candidate.nil? || OPTION_REGEXP =~ parameter_candidate || ALIASES_REGEXP =~ parameter_candidate) ? nil : @tokens.shift
182
+ return parameter
183
+ end
184
+
185
+ def parse!(tokens)
186
+ @current_definition = @definition
187
+ @first_token = true
188
+ @tokens = tokens.clone
189
+
190
+ @options = {}
191
+ @parameters = []
192
+ @command = nil
193
+
194
+ prepare_result!
195
+
196
+ while next_token
197
+
198
+ if @first_token && command_definition = @definition.has_command?(@current_token)
199
+ @current_definition = command_definition
200
+ @command = CommandLine::Option.rewrite(@current_token)
201
+ else
202
+ case @current_token
203
+ when ALIASES_REGEXP; handle_alias_expansion($1)
204
+ when OPTION_REGEXP; handle_option($1)
205
+ else; handle_other_parameter(@current_token)
206
+ end
207
+ @first_token = false
208
+ end
209
+
210
+ end
211
+
212
+ validate_arguments!
213
+
214
+ return self
215
+ end
216
+
217
+ protected
218
+
219
+ def prepare_result!
220
+ multiple_options = Hash[*@current_definition.options.select { |name, o| o.multiple? }.flatten]
221
+ multiple_options.each { |name, definition| @options[definition] = [] }
222
+ end
223
+
224
+ def validate_arguments!
225
+ if @current_definition.parameters && !(@current_definition.parameters === @parameters.length)
226
+ raise CommandLine::ParametersOutOfRange.new(@current_definition.parameters, @parameters.length)
227
+ end
228
+
229
+ required_options = Hash[*@current_definition.options.select { |name, o| o.required? }.flatten]
230
+ required_options.each do |name, definition|
231
+ raise CommandLine::RequiredOptionMissing, definition unless self[name]
232
+ end
233
+ end
234
+
235
+ def handle_alias_expansion(aliases)
236
+ aliases.reverse.scan(/./) do |alias_char|
237
+ if option_definition = @current_definition[alias_char]
238
+ @tokens.unshift(option_definition.to_option)
239
+ else
240
+ raise CommandLine::UnknownOption, alias_char
241
+ end
242
+ end
243
+ end
244
+
245
+ def handle_other_parameter(parameter)
246
+ @parameters << parameter
247
+ end
248
+
249
+ def handle_option(option_name)
250
+ option_definition = @current_definition[option_name]
251
+ raise CommandLine::UnknownOption, option_name if option_definition.nil?
252
+
253
+ if option_definition.multiple?
254
+ @options[option_definition] << option_definition.parse(self)
255
+ else
256
+ @options[option_definition] = option_definition.parse(self)
257
+ end
258
+ end
259
+
260
+ end
261
+
262
+ # Commandline parsing errors and exceptions
263
+ class Error < Exception
264
+ end
265
+
266
+ # Missing a required flag
267
+ class RequiredOptionMissing < CommandLine::Error
268
+ def initialize(option)
269
+ super("You have to provide the #{option.name} option!")
270
+ end
271
+ end
272
+
273
+ # Missing a required file
274
+ class ParametersOutOfRange < CommandLine::Error
275
+ def initialize(expected, actual)
276
+ if expected.kind_of?(Range)
277
+ if expected.end == CommandLine::Arguments::Definition::ENDLESS_PARAMETERS
278
+ super("The command expected at least #{expected.begin} parameters, but found #{actual}!")
279
+ else
280
+ super("The command expected between #{expected.begin} and #{expected.end} parameters, but found #{actual}!")
281
+ end
282
+ else
283
+ super("The command expected #{expected} parameters, but found #{actual}!")
284
+ end
285
+ end
286
+ end
287
+
288
+ # Missing a required flag argument
289
+ class ParameterExpected < CommandLine::Error
290
+ def initialize(option)
291
+ super("The option #{option.inspect} expects a parameter!")
292
+ end
293
+ end
294
+
295
+ # Encountered an unkown flag
296
+ class UnknownOption < CommandLine::Error
297
+ def initialize(option_identifier)
298
+ super("#{option_identifier.inspect} not recognized as a valid option!")
299
+ end
300
+ end
301
+ end