wvanbergen-request-log-analyzer 0.2.2 → 0.3.0

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