librato-rails 0.9.0 → 0.10.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/CHANGELOG.md +14 -0
  2. data/FAQ.md +25 -0
  3. data/README.md +11 -2
  4. data/lib/librato/rails/configuration.rb +26 -29
  5. data/lib/librato/rails/railtie.rb +30 -5
  6. data/lib/librato/rails/subscribers/cache.rb +22 -0
  7. data/lib/librato/rails/subscribers/controller.rb +56 -0
  8. data/lib/librato/rails/subscribers/mail.rb +18 -0
  9. data/lib/librato/rails/subscribers/render.rb +28 -0
  10. data/lib/librato/rails/subscribers/sql.rb +24 -0
  11. data/lib/librato/rails/subscribers.rb +14 -69
  12. data/lib/librato/rails/tracker.rb +13 -0
  13. data/lib/librato/rails/version.rb +1 -1
  14. data/lib/librato/rails.rb +9 -214
  15. data/test/dummy/app/assets/javascripts/application.js +0 -3
  16. data/test/dummy/app/controllers/cache_controller.rb +44 -0
  17. data/test/dummy/app/controllers/render_controller.rb +4 -0
  18. data/test/dummy/app/models/user.rb +7 -5
  19. data/test/dummy/app/views/render/_first.html.erb +1 -0
  20. data/test/dummy/app/views/render/_second.html.erb +1 -0
  21. data/test/dummy/app/views/render/partial.html.erb +2 -0
  22. data/test/dummy/app/views/render/template.html.erb +1 -0
  23. data/test/dummy/config/application.rb +8 -6
  24. data/test/dummy/config/environments/test.rb +1 -1
  25. data/test/dummy/config/routes.rb +12 -3
  26. data/test/integration/cache_test.rb +40 -0
  27. data/test/integration/mail_test.rb +2 -4
  28. data/test/integration/render_test.rb +27 -0
  29. data/test/integration/request_test.rb +15 -11
  30. data/test/integration/sql_test.rb +6 -6
  31. data/test/support/integration_case.rb +11 -7
  32. data/test/unit/configuration_test.rb +63 -73
  33. data/test/unit/tracker_test.rb +15 -0
  34. metadata +36 -53
  35. data/lib/librato/rack/middleware.rb +0 -47
  36. data/lib/librato/rack.rb +0 -4
  37. data/lib/librato/rails/aggregator.rb +0 -95
  38. data/lib/librato/rails/collector.rb +0 -45
  39. data/lib/librato/rails/counter_cache.rb +0 -122
  40. data/lib/librato/rails/group.rb +0 -27
  41. data/lib/librato/rails/logging.rb +0 -77
  42. data/lib/librato/rails/validating_queue.rb +0 -31
  43. data/lib/librato/rails/worker.rb +0 -54
  44. data/lib/tasks/metrics-rails_tasks.rake +0 -4
  45. data/test/librato-rails_test.rb +0 -44
  46. data/test/remote/rails_remote_test.rb +0 -193
  47. data/test/unit/aggregator_test.rb +0 -53
  48. data/test/unit/counter_cache_test.rb +0 -90
  49. data/test/unit/group_test.rb +0 -54
  50. data/test/unit/middleware_test.rb +0 -82
  51. data/test/unit/worker_test.rb +0 -31
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ### Version 0.10.0.pre1
2
+ * Add render instrumentation metrics
3
+ * Add cache instrumentation metrics
4
+ * Add metrics on HTTP method use
5
+ * Rack middleware moved from end of stack to beginning, improving value of rack metrics (Thibaud Guillaume-Gentil)
6
+ * Add ability to control startup with LIBRATO_AUTORUN
7
+ * Add ability to force startup in console mode with LIBRATO_AUTORUN
8
+ * Refactor to use librato-rack
9
+ * Remove old deprecated heroku-specific stats
10
+ * Fix startup bug when using unicorn with preload false
11
+ * Fix bug where grouped instrumentation could lose some options
12
+ * Sign gem when building
13
+ * Documentation improvements
14
+
1
15
  ### Version 0.9.0
2
16
  * Bump librato-metrics dependency version for new functionality
3
17
 
data/FAQ.md ADDED
@@ -0,0 +1,25 @@
1
+ # Frequently asked questions for librato-rails
2
+
3
+ #### What is the difference between rack.request.time and rails.request.time, etc?
4
+
5
+ The `rails.request.*` metrics are reported using rails' built-in instrumentation and show rails' [internal benchmarks](http://edgeguides.rubyonrails.org/active_support_instrumentation.html) for request execution.
6
+
7
+ The `rack.request.*` metrics are generated from within the rack middleware and are guaranteed to always be greater than or equal to rails' real execution time.
8
+
9
+ On versions of `librato-rails` prior to 0.9.0 these can appear similar as the middleware is loaded on the top of the stack but on 0.9.0 or greater the entire middleware stack is recorded. Recording the whole stack gives you a better sense of actual request processing time and also the ability to differentiate how many of your requests are being handled in middleware and what percentage of your request time is being taken up by middleware.
10
+
11
+ #### I've noticed that the HTTP request sending my metrics sometimes takes a while, isn't that slowing down my web server or limiting how many requests I can handle?
12
+
13
+ Submissions to the Librato service take place in a background thread which runs extremely quickly and has minimal impact on your primary request-serving thread or threads.
14
+
15
+ The only time that the worker uses any CPU is to package up submissions, which is highly optimized. While the worker thread is sleeping or waiting for a response it is non-blocking and doesn't compete with your request-serving threads.
16
+
17
+ Even if you are using a single-threaded _app server_ like unicorn or thin, you are still running in a multi-threaded _environment_ (the ruby interpreter) so the worker thread will run and report successfully.
18
+
19
+ #### With a `LOG_LEVEL` of debug or trace I sometimes don't see a worker start for each process
20
+
21
+ `librato-rails` logs metrics for requests both within the rack middleware and within rails itself. Certain requests may be handled entirely within the rack middleware - these never reach the rails tier at all.
22
+
23
+ If the first request a process gets is a rack-only process the rails log is unavailable and the startup message may be lost.
24
+
25
+ However, with a level of `debug` or `trace` you should be able to see flush messages for every worker that is running, even if the startup message is not logged. You can also use the `rack.processes` metric to see how many processes are reporting.
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  librato-rails
2
2
  =======
3
3
 
4
- [![Build Status](https://secure.travis-ci.org/librato/librato-rails.png?branch=master)](http://travis-ci.org/librato/librato-rails)
4
+ [![Gem Version](https://badge.fury.io/rb/librato-rails.png)](http://badge.fury.io/rb/librato-rails) [![Build Status](https://secure.travis-ci.org/librato/librato-rails.png?branch=master)](http://travis-ci.org/librato/librato-rails) [![Code Climate](https://codeclimate.com/github/librato/librato-rails.png)](https://codeclimate.com/github/librato/librato-rails)
5
5
 
6
6
  `librato-rails` will report key statistics for your Rails app to [Librato Metrics](https://metrics.librato.com/) and allow you to easily track your own custom metrics. Metrics are delivered asynchronously behind the scenes so they won't affect performance of your requests.
7
7
 
@@ -207,10 +207,17 @@ end
207
207
 
208
208
  These are just a few examples. Combining `ActiveSupport::Notifications` instrumentation with Librato can be extremely powerful. As an added benefit, using the instrument/subscribers model allows you to isolate complex instrumentation code from your main application codebase.
209
209
 
210
- ## Custom Prefix
210
+ ## Custom Prefixes
211
211
 
212
212
  You can set an optional prefix to all metrics reported by `librato-rails` in your [configuration](https://github.com/librato/librato-rails/wiki/Configuration). This can be helpful for isolating test data or forcing different apps to use different metric names.
213
213
 
214
+ ## Use with Background Workers / Cron Jobs
215
+
216
+ `librato-rails` is designed to run within a long-running process and report periodically. Intermittently running rake tasks and most background job tools (delayed job, resque, queue_classic) don't run long enough for this to work.
217
+
218
+ Never fear, [we have some guidelines](https://github.com/librato/librato-rails/wiki/Monitoring-Background-Workers) for how to instrument your workers properly.
219
+
220
+
214
221
  ## Cross-Process Aggregation
215
222
 
216
223
  `librato-rails` submits measurements back to the Librato platform on a _per-process_ basis. By default these measurements are then combined into a single measurement per source (default is your hostname) before persisting the data.
@@ -227,6 +234,8 @@ Note that it may take 2-3 minutes for the first results to show up in your Metri
227
234
 
228
235
  If you want to get more information about `librato-rails` submissions to the Metrics service you can set your `log_level` to `debug` (see [configuration](https://github.com/librato/librato-rails/wiki/Configuration)) to get detailed information added to your logs about the settings `librato-rails` is seeing at startup and when it is submitting.
229
236
 
237
+ Be sure to tail your logs manually (`tail -F <logfile>`) as the log output you get when using the `rails server` command often skips startup log lines.
238
+
230
239
  If you are having an issue with a specific metric, using a `log_level` of `trace` will add the exact measurements being sent to your logs along with lots of other information about `librato-rails` as it executes. Neither of these modes are recommended long-term in production as they will add quite a bit of volume to your log file and will slow operation somewhat. Note that submission I/O is non-blocking, submission times are total time - your process will continue to handle requests during submissions.
231
240
 
232
241
  If you are debugging setting up `librato-rails` locally you can set `flush_interval` to something shorter (say 10s) to force submission more frequently. Don't change your `flush_interval` in production as it will not result in measurements showing up more quickly, but may affect performance.
@@ -1,52 +1,49 @@
1
1
  module Librato
2
2
  module Rails
3
- module Configuration
3
+ # Adds yaml-based config and extra properties to the configuration
4
+ # class provided by librato-rack
5
+ #
6
+ # https://github.com/librato/librato-rack/blob/master/lib/librato/rack/configuration.rb
7
+ #
8
+ class Configuration < Rack::Configuration
4
9
  CONFIG_SETTABLE = %w{user token flush_interval log_level prefix source source_pids}
5
10
 
6
- mattr_accessor :config_file
7
- self.config_file = 'config/librato.yml'
11
+ attr_accessor :config_by, :config_file
8
12
 
9
- # set custom api endpoint
10
- def api_endpoint=(endpoint)
11
- @api_endpoint = endpoint
13
+ # options:
14
+ # * :config_file - alternate config file location
15
+ #
16
+ def initialize(options={})
17
+ self.config_file = options[:config_file] || 'config/librato.yml'
18
+ super()
19
+ self.log_prefix = '[librato-rails] '
12
20
  end
13
21
 
14
- # detect / update configuration
15
- def check_config
16
- self.log_level = ENV['LIBRATO_LOG_LEVEL'] if ENV['LIBRATO_LOG_LEVEL']
22
+ # detect and load configuration from config file or env vars
23
+ def load_configuration
17
24
  if self.config_file && File.exists?(self.config_file)
18
25
  configure_with_config_file
19
26
  else
20
- configure_with_environment
27
+ self.config_by = :environment
28
+ super
21
29
  end
30
+
31
+ # respect autorun and log_level env vars regardless of config method
32
+ self.autorun = detect_autorun
33
+ self.log_level = ENV['LIBRATO_LOG_LEVEL'] if ENV['LIBRATO_LOG_LEVEL']
22
34
  end
23
35
 
24
36
  private
25
37
 
26
38
  def configure_with_config_file
27
- log :debug, "configuring with librato.yml; ignoring environment variables.."
28
- if env_specific = YAML.load(ERB.new(File.read(config_file)).result)[::Rails.env]
39
+ self.config_by = :config_file
40
+ env_specific = YAML.load(ERB.new(File.read(config_file)).result)[::Rails.env]
41
+ if env_specific
29
42
  settable = CONFIG_SETTABLE & env_specific.keys
30
43
  settable.each { |key| self.send("#{key}=", env_specific[key]) }
31
- else
32
- log :debug, "halting: current environment (#{::Rails.env}) not in config file."
33
- end
34
- end
35
-
36
- def configure_with_environment
37
- log :debug, "using environment variables for configuration.."
38
- %w{user token source log_level prefix}.each do |settable|
39
- legacy_env_var = "LIBRATO_METRICS_#{settable.upcase}"
40
- if ENV[legacy_env_var]
41
- log :warn, "#{legacy_env_var} is deprecated, use LIBRATO_#{settable.upcase} instead"
42
- send("#{settable}=", ENV[legacy_env_var])
43
- end
44
- # if both are present, new-style dominates
45
- env_var = "LIBRATO_#{settable.upcase}"
46
- send("#{settable}=", ENV[env_var]) if ENV[env_var]
47
44
  end
48
45
  end
49
46
 
50
47
  end
51
48
  end
52
- end
49
+ end
@@ -2,15 +2,40 @@ module Librato
2
2
  module Rails
3
3
  class Railtie < ::Rails::Railtie
4
4
 
5
+ # don't have any custom http vars anymore, check if hostname is UUID
6
+ on_heroku = Socket.gethostname =~ /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i
7
+
5
8
  # make configuration proxy for config inside Rails
6
- config.librato_rails = Librato::Rails
9
+ config.librato_rails = Configuration.new
10
+
11
+ # set up tracker
12
+ tracker = Tracker.new(config.librato_rails)
13
+ config.librato_rails.tracker = tracker
14
+ Librato.register_tracker(tracker)
15
+
16
+ unless ::Rails.env.test?
17
+ unless defined?(::Rails::Console) && ENV['LIBRATO_AUTORUN'] != '1'
18
+
19
+ initializer 'librato_rails.setup' do |app|
20
+ # set up logging; heroku needs logging to STDOUT
21
+ if on_heroku
22
+ logger = Logger.new(STDOUT)
23
+ logger.level = Logger::INFO
24
+ else
25
+ logger = ::Rails.logger
26
+ end
27
+ config.librato_rails.log_target = logger
28
+ tracker.log(:debug) { "config: #{config.librato_rails.dump}" }
29
+
30
+ if tracker.should_start?
31
+ tracker.log :info, "starting up (pid #{$$}, using #{config.librato_rails.config_by})..."
32
+ app.middleware.insert(0, Librato::Rack, :config => config.librato_rails)
33
+ end
34
+ end
7
35
 
8
- initializer 'librato_rails.setup' do |app|
9
- # don't start in test mode or in the console
10
- unless ::Rails.env.test? || defined?(::Rails::Console)
11
- Librato::Rails.setup(app)
12
36
  end
13
37
  end
38
+
14
39
  end
15
40
  end
16
41
  end
@@ -0,0 +1,22 @@
1
+ module Librato
2
+ module Rails
3
+ module Subscribers
4
+
5
+ # Cache
6
+
7
+ %w{read generate fetch_hit write delete}.each do |metric|
8
+
9
+ ActiveSupport::Notifications.subscribe "cache_#{metric}.active_support" do |*args|
10
+ event = ActiveSupport::Notifications::Event.new(*args)
11
+
12
+ collector.group "rails.cache" do |c|
13
+ c.increment metric
14
+ c.timing "#{metric}.time", event.duration
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,56 @@
1
+ module Librato
2
+ module Rails
3
+ module Subscribers
4
+
5
+ # Controllers
6
+
7
+ ActiveSupport::Notifications.subscribe 'process_action.action_controller' do |*args|
8
+
9
+ event = ActiveSupport::Notifications::Event.new(*args)
10
+ controller = event.payload[:controller]
11
+ action = event.payload[:action]
12
+
13
+ format = event.payload[:format] || "all"
14
+ format = "all" if format == "*/*"
15
+ status = event.payload[:status]
16
+ http_method = event.payload[:method]
17
+ exception = event.payload[:exception]
18
+ # page_key = "request.#{controller}.#{action}_#{format}."
19
+
20
+ collector.group "rails.request" do |r|
21
+
22
+ r.increment 'total'
23
+ r.timing 'time', event.duration
24
+
25
+ if exception
26
+ r.increment 'exceptions'
27
+ else
28
+ r.timing 'time.db', event.payload[:db_runtime] || 0
29
+ r.timing 'time.view', event.payload[:view_runtime] || 0
30
+ end
31
+
32
+ if http_method
33
+ http_method.downcase!
34
+ r.group 'method' do |m|
35
+ m.increment http_method
36
+ m.timing "#{http_method}.time", event.duration
37
+ end
38
+ end
39
+
40
+ unless status.blank?
41
+ r.group 'status' do |s|
42
+ s.increment status
43
+ s.increment "#{status.to_s[0]}xx"
44
+ s.timing "#{status}.time", event.duration
45
+ s.timing "#{status.to_s[0]}xx.time", event.duration
46
+ end
47
+ end
48
+
49
+ r.increment 'slow' if event.duration > 200.0
50
+ end # end group
51
+
52
+ end # end subscribe
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,18 @@
1
+ module Librato
2
+ module Rails
3
+ module Subscribers
4
+
5
+ # ActionMailer
6
+
7
+ ActiveSupport::Notifications.subscribe 'deliver.action_mailer' do |*args|
8
+ # payload[:mailer] => 'UserMailer'
9
+ collector.increment "rails.mail.sent"
10
+ end
11
+
12
+ ActiveSupport::Notifications.subscribe 'receive.action_mailer' do |*args|
13
+ collector.increment "rails.mail.received"
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ module Librato
2
+ module Rails
3
+ module Subscribers
4
+
5
+ # Render operations
6
+
7
+ %w{partial template}.each do |metric|
8
+
9
+ ActiveSupport::Notifications.subscribe "render_#{metric}.action_view" do |*args|
10
+ event = ActiveSupport::Notifications::Event.new(*args)
11
+ path = event.payload[:identifier].split('/views/', 2)
12
+
13
+ if path[1]
14
+ source = path[1].gsub('/', ':')
15
+ # trim leading underscore for partial sources
16
+ source.gsub!(':_', ':') if metric == 'partial'
17
+ collector.group "rails.view" do |c|
18
+ c.increment "render_#{metric}", source: source, sporadic: true
19
+ c.timing "render_#{metric}.time", event.duration, source: source, sporadic: true
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module Librato
2
+ module Rails
3
+ module Subscribers
4
+
5
+ # SQL
6
+
7
+ ActiveSupport::Notifications.subscribe 'sql.active_record' do |*args|
8
+ payload = args.last
9
+
10
+ collector.group "rails.sql" do |s|
11
+ # puts (event.payload[:name] || 'nil') + ":" + event.payload[:sql] + "\n"
12
+ s.increment 'queries'
13
+
14
+ sql = payload[:sql].strip
15
+ s.increment 'selects' if sql.starts_with?('SELECT')
16
+ s.increment 'inserts' if sql.starts_with?('INSERT')
17
+ s.increment 'updates' if sql.starts_with?('UPDATE')
18
+ s.increment 'deletes' if sql.starts_with?('DELETE')
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -1,77 +1,22 @@
1
+ require 'active_support/notifications'
2
+
1
3
  module Librato
2
4
  module Rails
3
5
 
4
- # controllers
5
-
6
- ActiveSupport::Notifications.subscribe /process_action.action_controller/ do |*args|
7
-
8
- event = ActiveSupport::Notifications::Event.new(*args)
9
- controller = event.payload[:controller]
10
- action = event.payload[:action]
11
-
12
- format = event.payload[:format] || "all"
13
- format = "all" if format == "*/*"
14
- status = event.payload[:status]
15
- exception = event.payload[:exception]
16
- # page_key = "request.#{controller}.#{action}_#{format}."
17
-
18
- group "rails.request" do |r|
19
-
20
- r.increment 'total'
21
- r.timing 'time', event.duration
22
-
23
- if exception
24
- r.increment 'exceptions'
25
- else
26
- r.timing 'time.db', event.payload[:db_runtime] || 0
27
- r.timing 'time.view', event.payload[:view_runtime] || 0
28
- end
29
-
30
- unless status.blank?
31
- r.group 'status' do |s|
32
- s.increment status
33
- s.increment "#{status.to_s[0]}xx"
34
- s.timing "#{status}.time", event.duration
35
- s.timing "#{status.to_s[0]}xx.time", event.duration
36
- end
37
- end
38
-
39
- r.increment 'slow' if event.duration > 200.0
40
- end # end group
41
-
42
- end # end subscribe
43
-
44
- # SQL
6
+ # defines basic context that all librato-rails subscribers will run in
7
+ module Subscribers
45
8
 
46
- ActiveSupport::Notifications.subscribe 'sql.active_record' do |*args|
47
- payload = args.last
48
-
49
- group "rails.sql" do |s|
50
- # puts (event.payload[:name] || 'nil') + ":" + event.payload[:sql] + "\n"
51
- s.increment 'queries'
52
-
53
- sql = payload[:sql].strip
54
- s.increment 'selects' if sql.starts_with?('SELECT')
55
- s.increment 'inserts' if sql.starts_with?('INSERT')
56
- s.increment 'updates' if sql.starts_with?('UPDATE')
57
- s.increment 'deletes' if sql.starts_with?('DELETE')
58
- end
59
- end
60
-
61
- # ActionMailer
62
-
63
- ActiveSupport::Notifications.subscribe 'deliver.action_mailer' do |*args|
64
- # payload[:mailer] => 'UserMailer'
65
- group "rails.mail" do |m|
66
- m.increment 'sent'
9
+ # make collector object directly available, it won't be changing
10
+ def self.collector
11
+ @collector ||= Librato.tracker.collector
67
12
  end
68
- end
69
13
 
70
- ActiveSupport::Notifications.subscribe 'receive.action_mailer' do |*args|
71
- group "rails.mail" do |m|
72
- m.increment 'received'
73
- end
74
14
  end
75
-
76
15
  end
77
- end
16
+ end
17
+
18
+ require_relative 'subscribers/cache'
19
+ require_relative 'subscribers/controller'
20
+ require_relative 'subscribers/render'
21
+ require_relative 'subscribers/sql'
22
+ require_relative 'subscribers/mail'
@@ -0,0 +1,13 @@
1
+ module Librato
2
+ module Rails
3
+ class Tracker < Rack::Tracker
4
+
5
+ private
6
+
7
+ def version_string
8
+ "librato-rails/#{Librato::Rails::VERSION}"
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  module Librato
2
2
  module Rails
3
- VERSION = "0.9.0"
3
+ VERSION = "0.10.0.pre1"
4
4
  end
5
5
  end