wvanbergen-request-log-analyzer 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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