wvanbergen-request-log-analyzer 0.2.2 → 0.3.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 (67) hide show
  1. data/{README → README.textile} +29 -36
  2. data/Rakefile +3 -70
  3. data/TODO +43 -8
  4. data/bin/request-log-analyzer +32 -99
  5. data/lib/base/summarizer.rb +14 -0
  6. data/lib/bashcolorizer.rb +1 -1
  7. data/lib/command_line/arguments.rb +15 -2
  8. data/lib/command_line/flag.rb +12 -0
  9. data/lib/rails_analyzer/summarizer.rb +12 -4
  10. data/lib/rails_analyzer/virtual_mongrel.rb +91 -0
  11. data/lib/request_log_analyzer/aggregator/base.rb +34 -0
  12. data/lib/request_log_analyzer/aggregator/database.rb +86 -0
  13. data/lib/request_log_analyzer/aggregator/echo.rb +10 -0
  14. data/lib/request_log_analyzer/aggregator/summarizer.rb +53 -0
  15. data/lib/request_log_analyzer/controller.rb +90 -0
  16. data/lib/request_log_analyzer/file_format/merb.rb +30 -0
  17. data/lib/request_log_analyzer/file_format/rails.rb +84 -0
  18. data/lib/request_log_analyzer/file_format.rb +91 -0
  19. data/lib/request_log_analyzer/log_parser.rb +122 -0
  20. data/lib/request_log_analyzer/request.rb +72 -0
  21. data/lib/request_log_analyzer.rb +5 -0
  22. data/output/blockers.rb +2 -4
  23. data/output/errors.rb +1 -2
  24. data/output/hourly_spread.rb +3 -3
  25. data/output/mean_db_time.rb +1 -2
  26. data/output/mean_rendering_time.rb +2 -3
  27. data/output/mean_time.rb +2 -3
  28. data/output/most_requested.rb +1 -2
  29. data/output/timespan.rb +10 -8
  30. data/output/total_db_time.rb +2 -3
  31. data/output/total_time.rb +2 -3
  32. data/output/usage.rb +3 -2
  33. data/spec/controller_spec.rb +33 -0
  34. data/spec/database_inserter_spec.rb +81 -0
  35. data/{test/log_fragments/merb_1.log → spec/fixtures/merb.log} +0 -0
  36. data/spec/fixtures/multiple_files_1.log +5 -0
  37. data/spec/fixtures/multiple_files_2.log +2 -0
  38. data/{test/log_fragments/fragment_1.log → spec/fixtures/rails_1x.log} +5 -5
  39. data/{test/log_fragments/fragment_3.log → spec/fixtures/rails_22.log} +2 -2
  40. data/spec/fixtures/rails_22_cached.log +10 -0
  41. data/spec/fixtures/rails_unordered.log +24 -0
  42. data/{test/log_fragments/fragment_2.log → spec/fixtures/syslog_1x.log} +0 -0
  43. data/spec/fixtures/test_file_format.log +11 -0
  44. data/spec/fixtures/test_language_combined.log +14 -0
  45. data/spec/fixtures/test_order.log +16 -0
  46. data/spec/line_definition_spec.rb +34 -0
  47. data/spec/log_parser_spec.rb +92 -0
  48. data/spec/merb_format_spec.rb +58 -0
  49. data/spec/rails_format_spec.rb +95 -0
  50. data/spec/request_spec.rb +76 -0
  51. data/spec/spec_helper.rb +49 -0
  52. data/spec/summarizer_spec.rb +109 -0
  53. data/tasks/github-gem.rake +177 -0
  54. data/tasks/request_log_analyzer.rake +10 -0
  55. data/tasks/rspec.rake +6 -0
  56. data/test/base_summarizer_test.rb +30 -0
  57. metadata +46 -22
  58. data/bin/request-log-database +0 -81
  59. data/lib/base/log_parser.rb +0 -78
  60. data/lib/base/record_inserter.rb +0 -139
  61. data/lib/merb_analyzer/log_parser.rb +0 -26
  62. data/lib/rails_analyzer/log_parser.rb +0 -35
  63. data/lib/rails_analyzer/record_inserter.rb +0 -39
  64. data/test/merb_log_parser_test.rb +0 -39
  65. data/test/rails_log_parser_test.rb +0 -95
  66. data/test/record_inserter_test.rb +0 -45
  67. data/test/tasks.rake +0 -8
@@ -1,96 +1,93 @@
1
- Request log analyzer
2
- --------------------------------
1
+ h1. Request log analyzer
3
2
 
4
3
  This is a simple command line tool to analyze request log files of both Rails and
5
4
  Merb. Its purpose is to find what actions are best candidates for optimization.
6
5
 
7
- This tool will parse all requests in the logfile and aggregate the
8
- information. Once it is finished parsing the log file, it will show the
9
- requests that take op most server time. Different metrics are used (cumulative
10
- time, average time, blockers, DB time, etc)
6
+ * Analyzes Rails logs (all versions) and Merb logs
7
+ * Can combine multiple files (handy if you are using logrotate)
8
+ * Uses several metrics (cumulative time, average time, blockers, DB time, etc)
9
+ * Low memory footprint (server-safe)
10
+ * MIT licensed
11
+ * Fast
11
12
 
13
+ h2. Installation
12
14
 
13
- Installation
14
- --------------------------------
15
- gem sources -a http://gems.github.com
16
- sudo gem install wvanbergen-request-log-analyzer
15
+ @sudo gem install wvanbergen-request-log-analyzer --source http://gems.github.com@
17
16
 
18
- Usage
19
- --------------------------------
17
+ h2. Usage
20
18
 
19
+ <pre>
21
20
  Usage: request-log-analyzer [FILE] [OPTION]
22
21
  Analyze the given log FILE with the given OPTION
23
- Example: request-log-analyzer mongrel.log
22
+ Example: request-log-analyzer mongrel.log -z
24
23
 
25
- --fast, -t: Only use completed requests
24
+ --fast, -f: Only use completed requests
26
25
  --guess-database-time, -g: Guesses the database duration of requests if they are not in the log
27
26
  --output, -o: Comma-separated list of reports to show
28
27
  --amount, -c: Displays the top <amount> elements in the reports
29
28
  --colorize, -z: Fancy bash coloring
29
+ --install rails, -i rails: Install Rails task rake log:analyze
30
+ </pre>
30
31
 
31
32
 
32
-
33
- Example
34
- --------------------------------
33
+ h2. Example result
35
34
 
36
35
  Note that this example was shortened for your viewing pleasure.
37
- $ request-log-analyzer /var/log/my_app.log
36
+ @$ request-log-analyzer /var/log/my_app.log -o 5@
38
37
 
38
+ <pre>
39
39
  Request log analyzer, by Willem van Bergen and Bart ten Brinke
40
40
 
41
+ Processing started, failed, completed log lines from /var/log/my_app.log...
42
+
41
43
  Processing all log lines...
42
44
  ========================================================================
43
- Successfully analyzed 58908 requests from log file
44
-
45
45
  Timestamp first request: 2008-07-13T06:25:58+00:00
46
46
  Timestamp last request: 2008-07-20T06:18:53+00:00
47
47
  Total time analyzed: 7 days
48
- Methods: DELETE (1%), GET (50%), POST (22%), PUT (25%).
49
48
 
50
- Top 10 most requested actions
49
+ Total requests analyzed 58908 requests from log file
50
+ Methods: GET (50.6%), POST (22.8%), PUT (25.4%), DELETE (1.1%), unknown (0.0%).
51
+
52
+ Top 5 most requested actions
51
53
  ========================================================================
52
54
  /overview/:date/ : 19359 requests
53
55
  /overview/day/:date/ : 6365 requests
54
56
  /overview/:date/set/ : 5589 requests
55
57
  /overview/ : 3985 requests
56
58
  /clients/:id/ : 1976 requests
57
- ........
58
59
 
59
- Top 10 actions by time - cumulative
60
+ Top 5 actions by time - cumulative
60
61
  ========================================================================
61
62
  /overview/:date/ : 9044.582s [19359 requests]
62
63
  /overview/ : 8478.767s [3985 requests]
63
64
  /overview/:date/set/ : 3309.041s [5589 requests]
64
65
  /clients/:id/products/:id/ : 1479.911s [924 requests]
65
66
  /clients/:id/ : 750.080s [1976 requests]
66
- ........
67
67
 
68
- Top 10 actions by time - per request mean
68
+ Top 5 actions by time - per request mean
69
69
  ========================================================================
70
70
  /overview/ : 2.128s [3985 requests]
71
71
  /clients/:id/products/:id/ : 1.602s [924 requests]
72
72
  /overview/:date/set/ : 0.592s [5589 requests]
73
73
  /overview/:date/ : 0.467s [19359 requests]
74
74
  /clients/:id/ : 0.380s [1976 requests]
75
- ........
76
75
 
77
- Top 10 worst DB offenders - cumulative time
76
+ Top 5 worst DB offenders - cumulative time
78
77
  ========================================================================
79
78
  /overview/:date/ : 8773.993s [19359 requests]
80
79
  /overview/ : 8394.754s [3985 requests]
81
80
  /overview/:date/set/ : 3307.928s [5589 requests]
82
81
  /clients/:id/products/:id/ : 1425.220s [924 requests]
83
82
  /clients/:id/ : 535.229s [1976 requests]
84
- ........
85
83
 
86
- Top 10 worst DB offenders - mean time
84
+ Top 5 worst DB offenders - mean time
87
85
  ========================================================================
88
86
  /overview/:id/:id/:id/print/ : 6.994s [448 requests]
89
87
  /overview/ : 2.128s [3985 requests]
90
88
  /clients/:id/products/:id/ : 1.602s [924 requests]
91
89
  /overview/:date/set/ : 0.592s [5589 requests]
92
90
  /overview/:date/ : 0.467s [19359 requests]
93
- ........
94
91
 
95
92
  Mongrel process blockers (> 1.0 seconds)
96
93
  ========================================================================
@@ -99,7 +96,6 @@ Mongrel process blockers (> 1.0 seconds)
99
96
  /overview/:date/set/ : 1149.235s [803 requests]
100
97
  /overview/:id/:id/:id/print/new/ : 613.693s [341 requests]
101
98
  /clients/:id/products/:id/ : 1370.693s [313 requests]
102
- ........
103
99
 
104
100
  Requests graph - per hour
105
101
  ========================================================================
@@ -120,13 +116,10 @@ Requests graph - per hour
120
116
 
121
117
  Errors
122
118
  ========================================================================
123
- ArgumentError: [237 requests]
124
- -> invalid date
125
119
  StaleObjectError: [28 requests]
126
120
  -> Attempted to update a stale object
127
- RuntimeError: [3 requests]
128
- -> Cannot destroy rule before it was created
129
121
  StatementError: [2 requests]
130
122
  -> Mysql::Error: Deadlock found when trying to get lock; try restarting transaction
131
123
  NoMethodError: [1 requests]
132
124
  -> undefined method `code' for nil:NilClass
125
+ </pre>
data/Rakefile CHANGED
@@ -1,72 +1,5 @@
1
- require 'rubygems'
2
-
3
- load 'test/tasks.rake'
1
+ Dir[File.dirname(__FILE__) + "/tasks/*.rake"].each { |file| load(file) }
4
2
 
5
- desc 'Default: run unit tests for request-log-analyzer.'
6
- task :default => :test
7
-
8
-
9
- namespace :gem do
3
+ desc 'Default: run RSpec for request-log-analyzer.'
4
+ task :default => :spec
10
5
 
11
- desc "Sets the version and date of the gem. Requires the VERSION environment variable."
12
- task :version => [:manifest] do
13
-
14
- require 'date'
15
-
16
- new_version = ENV['VERSION']
17
- raise "VERSION is required" unless /\d+(\.\d+)*/ =~ new_version
18
-
19
- spec_file = Dir['*.gemspec'].first
20
-
21
- spec = File.read(spec_file)
22
- spec.gsub!(/^(\s*s\.version\s*=\s*)('|")(.+)('|")(\s*)$/) { "#{$1}'#{new_version}'#{$5}" }
23
- spec.gsub!(/^(\s*s\.date\s*=\s*)('|")(.+)('|")(\s*)$/) { "#{$1}'#{Date.today.strftime('%Y-%m-%d')}'#{$5}" }
24
- File.open(spec_file, 'w') { |f| f << spec }
25
- end
26
-
27
- desc "Creates a git tag for the provided VERSION"
28
- task :tag => [:version] do
29
-
30
- new_version = ENV['VERSION']
31
- raise "VERSION is required" unless /\d+(\.\d+)*/ =~ new_version
32
-
33
- sh "git add request-log-analyzer.gemspec"
34
- sh "git commit -m \"Set gem version to #{new_version}\""
35
- sh "git push origin"
36
- sh "git tag -a \"request-log-analyzer-#{new_version}\" -m \"Tagged version #{new_version}\""
37
- sh "git push --tags"
38
- end
39
-
40
- desc "Builds a ruby gem for request-log-analyzer"
41
- task :build => [:manifest] do
42
- system %[gem build request-log-analyzer.gemspec]
43
- end
44
-
45
- desc %{Update ".manifest" with the latest list of project filenames. Respect\
46
- .gitignore by excluding everything that git ignores. Update `files` and\
47
- `test_files` arrays in "*.gemspec" file if it's present.}
48
- task :manifest do
49
- list = Dir['**/*'].sort
50
- spec_file = Dir['*.gemspec'].first
51
- list -= [spec_file] if spec_file
52
-
53
- File.read('.gitignore').each_line do |glob|
54
- glob = glob.chomp.sub(/^\//, '')
55
- list -= Dir[glob]
56
- list -= Dir["#{glob}/**/*"] if File.directory?(glob) and !File.symlink?(glob)
57
- puts "excluding #{glob}"
58
- end
59
-
60
- if spec_file
61
- spec = File.read spec_file
62
- spec.gsub! /^(\s* s.(test_)?files \s* = \s* )( \[ [^\]]* \] | %w\( [^)]* \) )/mx do
63
- assignment = $1
64
- bunch = $2 ? list.grep(/^test.*_test\.rb$/) : list
65
- '%s%%w(%s)' % [assignment, bunch.join(' ')]
66
- end
67
-
68
- File.open(spec_file, 'w') {|f| f << spec }
69
- end
70
- File.open('.manifest', 'w') {|f| f << list.join("\n") }
71
- end
72
- end
data/TODO CHANGED
@@ -2,17 +2,52 @@ TODO items for Rails-log-analyzer
2
2
  =================================
3
3
  Contact willem AT vanbergen DOT org if you want to help out with the development.
4
4
 
5
- Database:
5
+ General:
6
+ - Add more tests / specs
7
+
8
+ Datamining:
6
9
  - Add query functionality for the resulting database file (interactive reports?)
7
- - Link request processing line to request completed line
10
+ - Link request processing line to request completed line (VirtualMongrel?)
11
+ - Fix the database inserter and make it more robust for future changes
8
12
 
9
13
  Rails integration:
10
- - Create script that calls request-log-analyzer
11
14
  - Optionally use local or specific routes.rb file to parse URLs
12
- - Add rake tasks to Rails application when included
13
15
 
14
- General:
15
- - Add useful rake tasks
16
- - Add more tests
17
- - Fix multiple file handling
16
+ Other:
18
17
  - World domination
18
+
19
+
20
+
21
+
22
+ Datamining should look something like this:
23
+
24
+ > request-log-analyzer myapp.log --interactive
25
+ Request log analyzer builds a new database.
26
+ Columns come from the log_parser as the LOG_LINES store all the keys that they can detect.
27
+ Also add some extra columns like hashed_request_url etc.
28
+
29
+ Request log analyzer then parses the logfile for its individual requests using something like the
30
+ virtual mongrel (we need a new name for this, database_summarizer agregator? sheepdog?) combined with our
31
+ default log parser.
32
+
33
+ When this is done the user enters an interactive mode (like irb).
34
+ > Filters: None
35
+ > Total requests in database: 53232
36
+ > $
37
+
38
+ The user can add filters like this:
39
+ > $ FILTER SQL ["date > ?", Date.today-1]
40
+
41
+ The user will then see this:
42
+ > Filters:
43
+ > 1. ["date > ?", Date.today-1]
44
+ > Total requests: 2120
45
+ > $
46
+
47
+ At any point the user can destroy filters, show the raw requests or show reports
48
+ > $ REPORT ALL
49
+
50
+ The request remaining after the filter chain will then be processed through the summarizer and then trough the
51
+ output templates, generating reports specificly for the selected dataset.
52
+ Partials should also be possible
53
+ > $ REPORT TIMESPAN
@@ -1,61 +1,23 @@
1
1
  #!/usr/bin/ruby
2
+ require File.dirname(__FILE__) + '/../lib/request_log_analyzer'
2
3
  require File.dirname(__FILE__) + '/../lib/command_line/arguments'
3
- require File.dirname(__FILE__) + '/../lib/base/log_parser'
4
- require File.dirname(__FILE__) + '/../lib/base/summarizer'
5
- require File.dirname(__FILE__) + '/../lib/rails_analyzer/log_parser'
6
- require File.dirname(__FILE__) + '/../lib/rails_analyzer/summarizer'
7
- require File.dirname(__FILE__) + '/../lib/merb_analyzer/log_parser'
8
- require File.dirname(__FILE__) + '/../lib/merb_analyzer/summarizer'
9
- require File.dirname(__FILE__) + '/../lib/bashcolorizer'
10
- require File.dirname(__FILE__) + '/../lib/ruby-progressbar/progressbar.rb'
11
4
 
12
5
  puts "Request log analyzer, by Willem van Bergen and Bart ten Brinke\n\n"
13
6
 
14
- # Substitutes variable elements in a url (like the id field) with a fixed string (like ":id")
15
- # This is used to aggregate simular requests.
16
- # <tt>request</tt> The request to evaluate.
17
- # Returns uniformed url string.
18
- # Raises on mailformed request.
19
- def request_hasher(request)
20
- if request[:url]
21
- url = request[:url].downcase.split(/^http[s]?:\/\/[A-z0-9\.-]+/).last.split('?').first # only the relevant URL part
22
- url << '/' if url[-1] != '/'[0] && url.length > 1 # pad a trailing slash for consistency
23
-
24
- url.gsub!(/\/\d+-\d+-\d+(\/|$)/, '/:date') # Combine all (year-month-day) queries
25
- url.gsub!(/\/\d+-\d+(\/|$)/, '/:month') # Combine all date (year-month) queries
26
- url.gsub!(/\/\d+[\w-]*/, '/:id') # replace identifiers in URLs
27
-
28
- return url
29
- elsif request[:controller] && request[:action]
30
- return "#{request[:controller]}##{request[:action]}"
31
- else
32
- raise 'Cannot hash this request! ' + request.inspect
33
- end
34
- end
35
-
36
- # Print results using a ASCII table.
37
- # <tt>summarizer</tt> The summarizer containg information to draw the table.
38
- # <tt>field</tt> The field containing the data to be printed
39
- # <tt>amount</tt> The length of the table (defaults to 20)
40
- def print_table(summarizer, field, amount = 20)
41
- summarizer.sort_actions_by(field).reverse[0, amount.to_i].each do |a|
42
- # As we show count by default, show totaltime if we sort by count
43
- field = :total_time if field == :count
44
-
45
- puts "%-50s: %10.03fs [#{green("%d requests")}]" % [a[0], a[1][field], a[1][:count]]
46
- end
47
- end
48
-
49
7
  # Parse the arguments given via commandline
50
8
  begin
51
- $arguments = CommandLine::Arguments.parse do |command_line|
52
- command_line.switch(:guess_database_time, :g)
53
- command_line.switch(:fast, :f)
54
- command_line.switch(:colorize, :z)
55
- command_line.switch(:merb, :m)
56
- command_line.flag(:output, :alias => :o)
57
- command_line.flag(:amount, :alias => :c)
58
- command_line.required_files = 1
9
+ arguments = CommandLine::Arguments.parse do |command_line|
10
+
11
+ #command_line.flag(:install, :alias => :i) # command_line.command(:install)
12
+
13
+ command_line.flag(:format, :default => 'rails')
14
+ command_line.flag(:aggregator, :alias => :a, :multiple => true)
15
+ command_line.flag(:database, :alias => :d)
16
+
17
+ command_line.switch(:combined_requests, :c)
18
+ command_line.switch(:colorize, :z)
19
+ #command_line.switch(:estimate_database_time, :e)
20
+ #command_line.switch(:fast, :f) #
59
21
  end
60
22
 
61
23
  rescue CommandLine::Error => e
@@ -65,51 +27,22 @@ rescue CommandLine::Error => e
65
27
  exit(0)
66
28
  end
67
29
 
68
-
69
- if $arguments[:merb]
70
- $summarizer = MerbAnalyzer::Summarizer.new(:calculate_database => $arguments[:guess_database_time])
71
- else
72
- $summarizer = RailsAnalyzer::Summarizer.new(:calculate_database => $arguments[:guess_database_time])
73
- end
74
-
75
- if $arguments[:fast]
76
- line_types = [:completed]
77
- elsif $arguments[:merb]
78
- line_types = MerbAnalyzer::LogParser::LOG_LINES.keys
79
- else
80
- line_types = RailsAnalyzer::LogParser::LOG_LINES.keys
81
- end
82
-
83
- # Walk through al the files given via the arguments.
84
- $arguments.files.each do |log_file|
85
- puts "Processing #{line_types.join(', ')} log lines from #{log_file}..."
86
-
87
- if $arguments[:merb]
88
- parser = MerbAnalyzer::LogParser.new(log_file)
89
- else
90
- parser = RailsAnalyzer::LogParser.new(log_file)
91
- end
92
-
93
- # add progress bar
94
- unless $arguments[:fast]
95
- pbar = ProgressBar.new(green(log_file), File.size(log_file))
96
- parser.progress { |pos, total| (pos == :finished) ? pbar.finish : pbar.set(pos) }
97
- end
98
-
99
- parser.each(*line_types) do |request|
100
- $summarizer.group(request) { |r| request_hasher(r) }
101
- end
102
- end
103
-
104
- # Select the reports to output and generate them.
105
- output_reports = $arguments[:output].split(',') rescue [:timespan, :most_requested, :total_time, :mean_time, :total_db_time, :mean_db_time, :mean_rendering_time, :blockers, :hourly_spread, :errors]
106
-
107
- output_reports.each do |report|
108
- report_location = "#{File.dirname(__FILE__)}/../output/#{report}.rb"
109
-
110
- if File.exist?(report_location)
111
- load report_location
112
- else
113
- puts "\nERROR: Output report #{report} not found!"
114
- end
115
- end
30
+ # if arguments[:install]
31
+ # if arguments[:install] == '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!"
43
+ # end
44
+ # exit(0)
45
+ # end
46
+
47
+ # Run the request_log_analyzer!
48
+ request_log_analyzer = RequestLogAnalyzer::Controller.build(arguments).run!
@@ -67,5 +67,19 @@ module Base
67
67
  errors = min_count.nil? ? @errors.to_a : @errors.delete_if { |k, v| v[:count] < min_count}.to_a
68
68
  errors.sort { |a, b| a[1][field.to_sym] <=> b[1][field.to_sym] }
69
69
  end
70
+
71
+ # Compare date strings fast
72
+ # Assumes date formats: "2008-07-14 12:11:20" or alike.
73
+ # <tt>first_date</tt> The first date string
74
+ # <tt>second_date</tt> The second date string
75
+ # Returns -1 if first_date < second_date, nil if equal
76
+ # and 1 if first_date > second_date (<=>)
77
+ def hamburger_compare_string_dates first_date, second_date
78
+ return nil if first_date.nil? || second_date.nil?
79
+
80
+ first_date.gsub(/[^0-9|\s]/,'').to_i \
81
+ <=> \
82
+ second_date.gsub(/[^0-9|\s]/,'').to_i
83
+ end
70
84
  end
71
85
  end
data/lib/bashcolorizer.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  # <tt>text</tt> The text to colorize.
3
3
  # <tt>color_code</tt> The color code string to set
4
4
  # <tt>color</tt> Does not color if false. Defaults to ($arguments && $arguments[:colorize])
5
- def colorize(text, color_code, color = $arguments && $arguments[:colorize])
5
+ def colorize(text, color_code, color = $colorize)
6
6
  color ? "#{color_code}#{text}\e[0m" : text
7
7
  end
8
8
 
@@ -46,7 +46,7 @@ module CommandLine
46
46
  # <tt>flag</tt> A flag symbol like :fast
47
47
  # Options
48
48
  # * <tt>:expects</tt> Expects a value after the flag
49
- def flag(flag, options)
49
+ def flag(flag, options = {})
50
50
  options[:expects] = String unless options.has_key?(:expects)
51
51
  argument = Flag.new(flag, options)
52
52
  @flag_definitions[argument.to_argument] = argument
@@ -85,7 +85,14 @@ module CommandLine
85
85
  if flag.expects_argument?
86
86
 
87
87
  if @arguments.length > (i + 1) && @arguments[i + 1]
88
- @flags[flag.name] = @arguments[i + 1]
88
+
89
+ if flag.multiple?
90
+ @flags[flag.name] ||= []
91
+ @flags[flag.name] << @arguments[i + 1]
92
+ else
93
+ @flags[flag.name] = @arguments[i + 1]
94
+ end
95
+
89
96
  i += 1
90
97
  else
91
98
  raise CommandLine::FlagExpectsArgument.new(arg)
@@ -115,6 +122,12 @@ module CommandLine
115
122
  # Check if the parsed arguments meet their requirements.
116
123
  # Raises CommandLineexception on error.
117
124
  def check_parsed_arguments!
125
+
126
+ @flag_definitions.each do |flag, definition|
127
+ @flags[definition.name] ||= [] if definition.multiple? && !definition.default?
128
+ @flags[definition.name] ||= definition.default if definition.default?
129
+ end
130
+
118
131
  if @begins_with_command && @command.nil?
119
132
  raise CommandLine::CommandMissing.new
120
133
  end
@@ -6,6 +6,8 @@ module CommandLine
6
6
  attr_reader :name
7
7
  attr_reader :alias
8
8
  attr_reader :argument
9
+ attr_reader :default
10
+ attr_reader :multiple
9
11
 
10
12
  # Initialize new Flag
11
13
  # <tt>name</tt> The name of the flag
@@ -15,6 +17,8 @@ module CommandLine
15
17
  @alias = definition[:alias].to_sym if definition[:alias]
16
18
  @required = definition.has_key?(:required) && definition[:required] == true
17
19
  @argument = definition[:expects] if definition[:expects]
20
+ @multiple = definition[:multiple] || false
21
+ @default = definition[:default] if definition[:default]
18
22
  end
19
23
 
20
24
  # Argument representation of the flag (--fast)
@@ -37,6 +41,14 @@ module CommandLine
37
41
  !@required
38
42
  end
39
43
 
44
+ def multiple?
45
+ @multiple
46
+ end
47
+
48
+ def default?
49
+ !@default.nil?
50
+ end
51
+
40
52
  # Check if flag is required
41
53
  def required?
42
54
  @required
@@ -22,16 +22,24 @@ module RailsAnalyzer
22
22
  case request[:type]
23
23
  when :started
24
24
  if request[:timestamp]
25
- @first_request_at ||= request[:timestamp] # assume time-based order of file
26
- @last_request_at = request[:timestamp] # assume time-based order of file
27
- @request_time_graph[request[:timestamp][11..12].to_i] +=1
25
+ if @first_request_at.nil? || hamburger_compare_string_dates(request[:timestamp], @first_request_at) == -1
26
+ @first_request_at = request[:timestamp]
27
+ end
28
+
29
+ if @last_request_at.nil? || hamburger_compare_string_dates(request[:timestamp], @last_request_at) == 1
30
+ @last_request_at = request[:timestamp]
31
+ end
32
+
33
+ @request_time_graph[request[:timestamp][11..12].to_i] +=1
28
34
  end
35
+
29
36
  if request[:method]
30
37
  @methods[request[:method].to_sym] ||= 0
31
38
  @methods[request[:method].to_sym] += 1
32
39
  else
33
40
  @methods[:unknown] += 1
34
41
  end
42
+
35
43
  when :completed
36
44
  @request_count += 1
37
45
  hash = block_given? ? yield(request) : request.hash
@@ -42,7 +50,7 @@ module RailsAnalyzer
42
50
  @actions[hash][:count] += 1
43
51
  @actions[hash][:total_time] += request[:duration]
44
52
  @actions[hash][:total_db_time] += request[:db] if request[:db]
45
- @actions[hash][:total_db_time] += request[:duration] - request[:rendering] if @calculate_database
53
+ @actions[hash][:total_db_time] += request[:duration] - request[:rendering] if @calculate_database && request[:duration] && request[:rendering]
46
54
 
47
55
  @actions[hash][:total_rendering_time] += request[:rendering] if request[:rendering]
48
56
 
@@ -0,0 +1,91 @@
1
+ # Can calculate request counts, duratations, mean times etc. of all the requests given.
2
+ class VirtualMongrel
3
+ STATUS = [:started, :completed]
4
+
5
+ attr_reader :status
6
+ attr_reader :start_line
7
+ attr_reader :start_time
8
+ attr_reader :die_line
9
+ attr_reader :die_time
10
+ attr_reader :calculate_database
11
+ attr_reader :running_mongrels
12
+
13
+ attr_reader :data_hash
14
+
15
+ def initialize(options = {})
16
+ @status = :started
17
+
18
+ @start_line = options[:start_line] || 0
19
+ @die_line = options[:die_line] || @start_line + 10
20
+
21
+ @start_time = options[:start_time] || 0
22
+ @die_time = options[:die_time] || @start_time + 10
23
+
24
+ @data_hash = {}
25
+ @calculate_database = false
26
+ @running_mongrels = options[:running_mongrels] || 1
27
+ end
28
+
29
+ def update_running_mongrels(number)
30
+ @running_mongrels = number if number > @running_mongrels
31
+ end
32
+
33
+
34
+ def group(request, &block)
35
+ case request[:type]
36
+ when :started
37
+ data_hash.store(:timestamp, request[:timestamp])
38
+ data_hash.store(:method, request[:method])
39
+ @status = :started
40
+
41
+ when :completed
42
+ data_hash.store(:url, request[:url])
43
+ data_hash.store(:hashed_request, request_hasher(request))
44
+ data_hash.store(:rendering, request[:rendering])
45
+ data_hash.store(:duration, request[:duration])
46
+ data_hash.store(:db_time, request[:db])
47
+
48
+ if @calculate_database && request[:duration] && request[:rendering]
49
+ data_hash.store(:db_time, request[:duration] - request[:request])
50
+ end
51
+
52
+ @status = :completed
53
+
54
+ when :failed
55
+ data_hash.store(:error, request[:error])
56
+ data_hash.store(:exception_string, request[:exception_string])
57
+ @status = :completed
58
+
59
+ end
60
+ end
61
+
62
+ # Substitutes variable elements in a url (like the id field) with a fixed string (like ":id")
63
+ # This is used to aggregate simular requests.
64
+ # <tt>request</tt> The request to evaluate.
65
+ # Returns uniformed url string.
66
+ # Raises on mailformed request.
67
+ def request_hasher(request)
68
+ if request[:url]
69
+ url = request[:url].downcase.split(/^http[s]?:\/\/[A-z0-9\.-]+/).last.split('?').first # only the relevant URL part
70
+ url << '/' if url[-1] != '/'[0] && url.length > 1 # pad a trailing slash for consistency
71
+
72
+ url.gsub!(/\/\d+-\d+-\d+(\/|$)/, '/:date') # Combine all (year-month-day) queries
73
+ url.gsub!(/\/\d+-\d+(\/|$)/, '/:month') # Combine all date (year-month) queries
74
+ url.gsub!(/\/\d+[\w-]*/, '/:id') # replace identifiers in URLs
75
+
76
+ return url
77
+ elsif request[:controller] && request[:action]
78
+ return "#{request[:controller]}##{request[:action]}"
79
+ else
80
+ raise 'Cannot hash this request! ' + request.inspect
81
+ end
82
+ end
83
+
84
+ # Store this mongrel in the database
85
+ def save
86
+ puts 'Saving mongrel!'
87
+ puts "Number of other running mongrels (certainty) #{running_mongrels}"
88
+ puts data_hash.to_s
89
+ end
90
+
91
+ end