librato-rails 1.4.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG.md +3 -0
  3. data/README.md +52 -73
  4. data/lib/librato/rails/configuration.rb +22 -3
  5. data/lib/librato/rails/railtie.rb +1 -2
  6. data/lib/librato/rails/subscribers/cache.rb +5 -3
  7. data/lib/librato/rails/subscribers/controller.rb +15 -12
  8. data/lib/librato/rails/subscribers/job.rb +17 -8
  9. data/lib/librato/rails/subscribers/mail.rb +11 -8
  10. data/lib/librato/rails/subscribers/method.rb +7 -9
  11. data/lib/librato/rails/subscribers/render.rb +13 -9
  12. data/lib/librato/rails/subscribers/sql.rb +11 -11
  13. data/lib/librato/rails/subscribers/status.rb +7 -9
  14. data/lib/librato/rails/tracker.rb +1 -1
  15. data/lib/librato/rails/version.rb +1 -1
  16. data/test/dummy/app/controllers/instrument_action_controller.rb +4 -0
  17. data/test/dummy/app/mailers/user_mailer.rb +5 -1
  18. data/test/dummy/config/routes.rb +1 -0
  19. data/test/fixtures/config/empty.yml +1 -1
  20. data/test/fixtures/config/librato.yml +4 -3
  21. data/test/fixtures/config/simple.yml +0 -2
  22. data/test/integration/cache_test.rb +5 -5
  23. data/test/integration/custom_test.rb +4 -4
  24. data/test/integration/instrument_action_test.rb +71 -23
  25. data/test/integration/job_test.rb +13 -3
  26. data/test/integration/mail_test.rb +9 -1
  27. data/test/integration/render_test.rb +12 -12
  28. data/test/integration/request_test.rb +40 -25
  29. data/test/integration/sql_test.rb +17 -7
  30. data/test/support/integration_case.rb +5 -1
  31. data/test/unit/configuration_test.rb +16 -11
  32. metadata +80 -55
  33. metadata.gz.sig +0 -0
  34. checksums.yaml +0 -7
  35. checksums.yaml.gz.sig +0 -0
  36. data/lib/librato/rails/subscribers/action.rb +0 -39
data.tar.gz.sig CHANGED
Binary file
@@ -1,3 +1,6 @@
1
+ ### Version 2.0.0
2
+ * Add support for tagged measurements (#123)
3
+
1
4
  ### Version 1.4.2
2
5
  * Bump librato-rack dependency to fix warns with ruby 2.4
3
6
 
data/README.md CHANGED
@@ -68,17 +68,41 @@ To see all config options or for information on combining config files and envir
68
68
 
69
69
  If you are using the Librato Heroku addon, your user and token environment variables will already be set in your Heroku environment. If you are running without the addon you will need to provide them yourself.
70
70
 
71
- In either case you will need to specify a custom source for your app to track properly. If `librato-rails` does not detect an explicit source it will not start. You can set the source in your environment:
71
+ If Heroku idles your application, measurements will not be sent until it receives another request and is restarted. If you see intermittent gaps in your measurements during periods of low traffic, this is the most likely cause.
72
72
 
73
- heroku config:add LIBRATO_SOURCE=myappname
73
+ If you are using Librato as a Heroku addon, [a paid plan](https://elements.heroku.com/addons/librato#pricing) is required for reporting custom metrics with librato-rails. You can view more about available addon levels [here](https://elements.heroku.com/addons/librato#pricing).
74
74
 
75
- If you are using a config file, add your source entry to that instead.
75
+ ## Default Tags
76
76
 
77
- Full information on configuration options is available on the [configuration wiki page](https://github.com/librato/librato-rails/wiki/Configuration).
77
+ Librato Metrics supports tagged measurements that are associated with a metric, one or more tag pairs, and a point in time. For more information on tagged measurements, visit our [API documentation](https://www.librato.com/docs/api/#measurements).
78
78
 
79
- If Heroku idles your application, measurements will not be sent until it receives another request and is restarted. If you see intermittent gaps in your measurements during periods of low traffic, this is the most likely cause.
79
+ ##### Detected Tags
80
80
 
81
- If you are using Librato as a Heroku addon, [a paid plan](https://elements.heroku.com/addons/librato#pricing) is required for reporting custom metrics with librato-rails. You can view more about available addon levels [here](https://elements.heroku.com/addons/librato#pricing).
81
+ By default, `service`, `environment` and `host` are detected and applied as default tags for submitted measurements. Optionally, you can override the detected values in your configuration file:
82
+
83
+ ```yaml
84
+ production:
85
+ user: <your-email>
86
+ token: <your-api-key>
87
+ tags:
88
+ service: 'myapp'
89
+ environment: 'production'
90
+ host: 'myapp-prod-1'
91
+ ```
92
+
93
+ ##### Custom Tags
94
+
95
+ In addition to the default tags, you can also provide custom tags:
96
+
97
+ ```yaml
98
+ production:
99
+ user: <your-email>
100
+ token: <your-api-key>
101
+ tags:
102
+ region: 'us-east-1'
103
+ ```
104
+
105
+ Full information on configuration options is available on the [configuration wiki page](https://github.com/librato/librato-rails/wiki/Configuration).
82
106
 
83
107
  ## Automatic Measurements
84
108
 
@@ -93,9 +117,8 @@ The metrics automatically recorded by `librato-rails` are organized into named m
93
117
  ###### Request Metrics
94
118
 
95
119
  * *rails_controller*: Metrics which provide a high level overview of request performance including `rails.request.total`, `rails.request.time`, `rails.request.time.db`, `rails.request.time.view`, and `rails.request.slow`
96
- * *rails_method*: `rails.request.method.*` metrics (GET, POST, etc)
97
- * *rails_status*: `rails.request.status.*` metrics broken out by individual status codes and class (200, 2xx, etc)
98
- * *rails_action*: `rails.action.*` metrics specific to individual controller actions via the [instrument_action](#instrument_action-experimental) helper
120
+ * *rails_method*: `rails.request.method` metric with `method` tag name and HTTP method tag value, e.g. `method=POST`
121
+ * *rails_status*: `rails.request.status` metric with `status` tag name and HTTP status code tag value, e.g. `status=200`
99
122
 
100
123
  ###### System-Specific Metrics
101
124
 
@@ -110,8 +133,8 @@ The metrics automatically recorded by `librato-rails` are organized into named m
110
133
  Rack measurements are taken from the very beginning of your [rack middleware stack](http://guides.rubyonrails.org/rails_on_rack.html). They include all time spent in your ruby process (not just in Rails proper). They will also show requests that are handled entirely in middleware and don't appear in the `rails` suites above.
111
134
 
112
135
  * *rack*: The `rack.request.total`, `rack.request.time`, `rack.request.slow`, and `rack.request.queue.time` metrics
113
- * *rack_method*: `rack.request.method.*` metrics (GET, POST, etc)
114
- * *rack_status*: `rack.request.status.*` metrics metrics broken out by individual status codes and class (200, 2xx, etc)
136
+ * *rack_method*: `rack.request.method` metric with `method` tag name and HTTP method tag value, e.g. `method=POST`
137
+ * *rack_status*: `rack.request.status` metric with `status` tag name and HTTP status code tag value, e.g. `status=200`
115
138
 
116
139
  ###### Queue Time
117
140
 
@@ -156,13 +179,20 @@ Use for tracking a running total of something _across_ requests, examples:
156
179
 
157
180
  ```ruby
158
181
  # increment the 'sales_completed' metric by one
159
- Librato.increment 'sales_completed'
182
+ Librato.increment 'sales.completed'
183
+ # => {:service=>"myapp", :environment=>"production", :host=>"myapp-prod-1"}
160
184
 
161
185
  # increment by five
162
- Librato.increment 'items_purchased', by: 5
186
+ Librato.increment 'items.purchased', by: 5
187
+ # => {:service=>"myapp", :environment=>"production", :host=>"myapp-prod-1"}
163
188
 
164
- # increment with a custom source
165
- Librato.increment 'user.purchases', source: user.id
189
+ # increment with custom per-measurement tags
190
+ Librato.increment 'user.purchases', tags: { user_id: user.id, currency: 'USD' }
191
+ # => {:user_id=>43, :currency=>"USD"}
192
+
193
+ # increment with custom per-measurement tags and inherited default tags
194
+ Librato.increment 'user.purchases', tags: { user_id: user.id, currency: 'USD' }, inherit_tags: true
195
+ # => {:service=>"myapp", :environment=>"production", :host=>"myapp-prod-1", :user_id=>43, :currency=>"USD"}
166
196
  ```
167
197
 
168
198
  Other things you might track this way: user signups, requests of a certain type or to a certain route, total jobs queued or processed, emails sent or received
@@ -175,7 +205,7 @@ Especially with custom sources you may want the opposite behavior - reporting a
175
205
 
176
206
  ```ruby
177
207
  # report a value for 'user.uploaded_file' only during non-zero intervals
178
- Librato.increment 'user.uploaded_file', source: user.id, sporadic: true
208
+ Librato.increment 'user.uploaded_file', tags: { user: user.id, bucket: bucket.name }, sporadic: true
179
209
  ```
180
210
 
181
211
  #### measure
@@ -185,8 +215,8 @@ Use when you want to track an average value _per_-request. Examples:
185
215
  ```ruby
186
216
  Librato.measure 'user.social_graph.nodes', 212
187
217
 
188
- # report from a custom source
189
- Librato.measure 'jobs.queued', 3, source: 'worker.12'
218
+ # report from custom per-measurement tags
219
+ Librato.measure 'jobs.queued', 3, tags: { priority: 'high', worker: 'worker.12' }
190
220
  ```
191
221
 
192
222
  #### timing
@@ -196,8 +226,8 @@ Like `Librato.measure` this is per-request, but specialized for timing informati
196
226
  ```ruby
197
227
  Librato.timing 'twitter.lookup.time', 21.2
198
228
 
199
- # report from a custom source
200
- Librato.measure 'api.response.time', time, source: node_name
229
+ # report from custom per-measurement tags
230
+ Librato.measure 'api.response.time', time, tags: { node: node_name, db: 'rr1' }
201
231
  ```
202
232
 
203
233
  The block form auto-submits the time it took for its contents to execute as the measurement value:
@@ -250,54 +280,6 @@ end
250
280
 
251
281
  Symbols can be used interchangably with strings for metric names.
252
282
 
253
- ## Controller Helpers
254
-
255
- `librato-rails` also has special helpers which are available inside your controllers:
256
-
257
- #### instrument_action
258
- _experimental_, this interface may change:
259
-
260
- Use when you want to profile execution time or request volume for a specific controller action:
261
-
262
- ```ruby
263
- class CommentController < ApplicationController
264
- instrument_action :create # can accept a list
265
-
266
- def create
267
- # ...
268
- end
269
- end
270
- ```
271
-
272
- Optionally, you can instrument all controller actions:
273
-
274
- ```ruby
275
- class ArticlesController < ApplicationController
276
- instrument_action :all
277
-
278
- def create
279
- # ...
280
- end
281
-
282
- def show
283
- # ...
284
- end
285
- end
286
- ```
287
-
288
- Once you instrument an action, `librato-rails` will start reporting a set of metrics specific to that action including:
289
-
290
- * rails.action.request.total (# of requests)
291
- * rails.action.request.slow (requests >= 200ms to produce)
292
- * rails.action.request.exceptions
293
- * rails.action.request.time (total time spent in action)
294
- * rails.action.request.time.db (db interaction time)
295
- * rails.action.request.time.view (view rendering time)
296
-
297
- Each instrumented action will appear as a source for the `rails.action.*` metrics, for example `mycontroller.action.html`.
298
-
299
- IMPORTANT NOTE: Metrics from `instrument_action` take into account all time spent in the ActionController stack for that action, including before/after filters and any global processing. They are _not_ equivalent to using a `Librato.timing` block inside the method body.
300
-
301
283
  ## Use with ActiveSupport::Notifications
302
284
 
303
285
  `librato-rails` and [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) work great together. In fact, many of the Rails metrics provided are produced by subscribing to the [instrumentation events](http://edgeguides.rubyonrails.org/active_support_instrumentation.html) built into Rails.
@@ -324,7 +306,7 @@ ActiveSupport::Notifications.subscribe 'my.event' do |*args|
324
306
  Librato.timing 'my.event.time', event.duration
325
307
 
326
308
  # use payload data to do user-specific tracking
327
- Librato.increment 'user.did.event', source: user.id, sporadic: true
309
+ Librato.increment 'user.did.event', tags: { user: user.id }, sporadic: true
328
310
 
329
311
  # do conditional tracking
330
312
  if user.feature_on?(:sample_group)
@@ -357,14 +339,11 @@ Never fear, [we have some guidelines](https://github.com/librato/librato-rails/w
357
339
 
358
340
  ## Cross-Process Aggregation
359
341
 
360
- `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.
342
+ `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 default tags (detects `service`, `environment` and `host`) before persisting the data.
361
343
 
362
344
  For example if you have 4 hosts with 8 unicorn instances each (i.e. 32 processes total), on the Librato site you'll find 4 data streams (1 per host) instead of 32.
363
345
  Current pricing applies after aggregation, so in this case you will be charged for 4 streams instead of 32.
364
346
 
365
- If you want to report per-process instead, you can set `source_pids` to `true` in
366
- your config, which will append the process id to the source name used by each thread.
367
-
368
347
  ## Troubleshooting
369
348
 
370
349
  Note that it may take 2-3 minutes for the first results to show up in your Librato account after you have started your servers with `librato-rails` enabled and the first request has been received.
@@ -1,3 +1,4 @@
1
+ require "socket"
1
2
  require "yaml"
2
3
 
3
4
  module Librato
@@ -8,9 +9,9 @@ module Librato
8
9
  # https://github.com/librato/librato-rack/blob/master/lib/librato/rack/configuration.rb
9
10
  #
10
11
  class Configuration < Rack::Configuration
11
- CONFIG_SETTABLE = %w{user token flush_interval log_level prefix source source_pids proxy suites}
12
+ CONFIG_SETTABLE = %w{flush_interval log_level prefix proxy suites tags token user}
12
13
 
13
- DEFAULT_SUITES = [:rails_action, :rails_cache, :rails_controller, :rails_mail, :rails_method, :rails_render, :rails_sql, :rails_status, :rails_job]
14
+ DEFAULT_SUITES = [:rails_cache, :rails_controller, :rails_mail, :rails_method, :rails_render, :rails_sql, :rails_status, :rails_job]
14
15
 
15
16
  attr_accessor :config_by, :config_file
16
17
 
@@ -32,6 +33,8 @@ module Librato
32
33
  super
33
34
  end
34
35
 
36
+ self.tags = detect_tags
37
+
35
38
  # respect autorun and log_level env vars regardless of config method
36
39
  self.autorun = detect_autorun
37
40
  self.log_level = :info if log_level.blank?
@@ -46,7 +49,11 @@ module Librato
46
49
  env_specific = YAML.load(ERB.new(File.read(config_file)).result)[::Rails.env]
47
50
  if env_specific
48
51
  settable = CONFIG_SETTABLE & env_specific.keys
49
- settable.each { |key| self.send("#{key}=", env_specific[key]) }
52
+ settable.each do |key|
53
+ value = env_specific[key]
54
+ value.symbolize_keys! if key == "tags"
55
+ self.send("#{key}=", value)
56
+ end
50
57
  end
51
58
  end
52
59
 
@@ -54,6 +61,18 @@ module Librato
54
61
  super + DEFAULT_SUITES
55
62
  end
56
63
 
64
+ def default_tags
65
+ {
66
+ service: ::Rails.application.class.to_s.split("::").first.underscore,
67
+ environment: ::Rails.env,
68
+ host: Socket.gethostname.downcase
69
+ }
70
+ end
71
+
72
+ def detect_tags
73
+ has_tags? ? default_tags.merge(self.tags) : default_tags
74
+ end
75
+
57
76
  end
58
77
  end
59
78
  end
@@ -40,7 +40,6 @@ module Librato
40
40
  end
41
41
 
42
42
  tracker = config.librato_rails.tracker
43
- require_relative 'subscribers/action' if tracker.suite_enabled?(:rails_action)
44
43
  require_relative 'subscribers/cache' if tracker.suite_enabled?(:rails_cache)
45
44
  require_relative 'subscribers/controller' if tracker.suite_enabled?(:rails_controller)
46
45
  require_relative 'subscribers/mail' if tracker.suite_enabled?(:rails_mail)
@@ -50,7 +49,7 @@ module Librato
50
49
  require_relative 'subscribers/status' if tracker.suite_enabled?(:rails_status)
51
50
 
52
51
  Librato::Rails::VersionSpecifier.supported(min: '4.2') do
53
- require_relative 'subscribers/job'if tracker.suite_enabled?(:rails_job)
52
+ require_relative 'subscribers/job' if tracker.suite_enabled?(:rails_job)
54
53
  end
55
54
  end
56
55
 
@@ -7,16 +7,18 @@ module Librato
7
7
  %w{read generate fetch_hit write delete}.each do |metric|
8
8
 
9
9
  ActiveSupport::Notifications.subscribe "cache_#{metric}.active_support" do |*args|
10
+
10
11
  event = ActiveSupport::Notifications::Event.new(*args)
11
12
 
12
13
  collector.group "rails.cache" do |c|
13
14
  c.increment metric
14
15
  c.timing "#{metric}.time", event.duration
15
- end
16
- end
16
+ end # end group
17
+
18
+ end # end subscribe
17
19
 
18
20
  end
19
21
 
20
22
  end
21
23
  end
22
- end
24
+ end
@@ -2,26 +2,29 @@ module Librato
2
2
  module Rails
3
3
  module Subscribers
4
4
 
5
- # Controllers
5
+ # ActionController
6
6
 
7
- ActiveSupport::Notifications.subscribe 'process_action.action_controller' do |*args|
7
+ ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
8
8
 
9
9
  event = ActiveSupport::Notifications::Event.new(*args)
10
10
  exception = event.payload[:exception]
11
+ format = event.payload[:format].to_s || "all"
12
+ format = "all" if format == "*/*"
13
+ tags = {
14
+ controller: event.payload[:controller],
15
+ action: event.payload[:action],
16
+ format: format,
17
+ }
11
18
 
12
19
  collector.group "rails.request" do |r|
20
+ r.increment "total", tags: tags, inherit_tags: true
21
+ r.timing "time", event.duration, tags: tags, inherit_tags: true, percentile: 95
22
+ r.timing "time.db", event.payload[:db_runtime] || 0, tags: tags, inherit_tags: true, percentile: 95
23
+ r.timing "time.view", event.payload[:view_runtime] || 0, tags: tags, inherit_tags: true, percentile: 95
13
24
 
14
- r.increment 'total'
15
- r.timing 'time', event.duration, percentile: 95
16
-
17
- if exception
18
- r.increment 'exceptions'
19
- else
20
- r.timing 'time.db', event.payload[:db_runtime] || 0, percentile: 95
21
- r.timing 'time.view', event.payload[:view_runtime] || 0, percentile: 95
25
+ if event.duration > 200.0
26
+ r.increment "slow", tags: tags, inherit_tags: true
22
27
  end
23
-
24
- r.increment 'slow' if event.duration > 200.0
25
28
  end # end group
26
29
 
27
30
  end # end subscribe
@@ -1,17 +1,26 @@
1
1
  module Librato
2
2
  module Rails
3
3
  module Subscribers
4
- hooks = %w{enqueue_at enqueue perform_start perform}
5
4
 
6
- hooks.each do |hook|
7
- ActiveSupport::Notifications.subscribe "#{hook}.active_job" do |*args|
5
+ # ActionJob
6
+
7
+ %w{enqueue_at enqueue perform_start perform}.each do |metric|
8
+
9
+ ActiveSupport::Notifications.subscribe "#{metric}.active_job" do |*args|
10
+
8
11
  event = ActiveSupport::Notifications::Event.new(*args)
12
+ tags = {
13
+ adapter: event.payload[:adapter].to_s.demodulize.underscore,
14
+ job: event.payload[:job].class.to_s.demodulize.underscore
15
+ }
16
+
17
+ collector.group "rails.job" do |c|
18
+ c.increment metric, tags: tags, inherit_tags: true
19
+ c.timing "#{metric}.time", event.duration, tags: tags, inherit_tags: true
20
+ end # end group
21
+
22
+ end # end subscribe
9
23
 
10
- collector.group 'rails.job' do |c|
11
- c.increment hook
12
- c.timing "#{hook}.time", event.duration, source: event.payload[:job].class
13
- end
14
- end
15
24
  end
16
25
  end
17
26
  end
@@ -4,15 +4,18 @@ module Librato
4
4
 
5
5
  # ActionMailer
6
6
 
7
- ActiveSupport::Notifications.subscribe 'deliver.action_mailer' do |*args|
8
- # payload[:mailer] => 'UserMailer'
9
- collector.increment "rails.mail.sent"
10
- end
7
+ ActiveSupport::Notifications.subscribe "deliver.action_mailer" do |*args|
8
+ event = ActiveSupport::Notifications::Event.new(*args)
9
+ tags = { mailer: event.payload[:mailer] }
10
+ collector.increment "rails.mail.sent", tags: tags, inherit_tags: true
11
+ end # end subscribe
11
12
 
12
- ActiveSupport::Notifications.subscribe 'receive.action_mailer' do |*args|
13
- collector.increment "rails.mail.received"
14
- end
13
+ ActiveSupport::Notifications.subscribe "receive.action_mailer" do |*args|
14
+ event = ActiveSupport::Notifications::Event.new(*args)
15
+ tags = { mailer: event.payload[:mailer] }
16
+ collector.increment "rails.mail.received", tags: tags, inherit_tags: true
17
+ end # end subscribe
15
18
 
16
19
  end
17
20
  end
18
- end
21
+ end
@@ -2,19 +2,17 @@ module Librato
2
2
  module Rails
3
3
  module Subscribers
4
4
 
5
- # Controller Method
5
+ # ActionController Method
6
6
 
7
- ActiveSupport::Notifications.subscribe 'process_action.action_controller' do |*args|
7
+ ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
8
8
 
9
9
  event = ActiveSupport::Notifications::Event.new(*args)
10
- http_method = event.payload[:method]
10
+ tags = { method: event.payload[:method].to_s.downcase }
11
11
 
12
- if http_method
13
- verb = http_method.to_s.downcase
14
-
15
- collector.group "rails.request.method" do |m|
16
- m.increment verb
17
- m.timing "#{verb}.time", event.duration
12
+ if tags[:method]
13
+ collector.group "rails.request" do |m|
14
+ m.increment "method", tags: tags, inherit_tags: true
15
+ m.timing "method.time", event.duration, tags: tags, inherit_tags: true
18
16
  end # end group
19
17
  end
20
18
 
@@ -7,22 +7,26 @@ module Librato
7
7
  %w{partial template}.each do |metric|
8
8
 
9
9
  ActiveSupport::Notifications.subscribe "render_#{metric}.action_view" do |*args|
10
+
10
11
  event = ActiveSupport::Notifications::Event.new(*args)
11
12
  path = event.payload[:identifier].split('/views/', 2)
13
+ metric = metric.to_sym
12
14
 
13
15
  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
16
+ identifier = path[1].gsub('/', ':')
17
+ # trim leading underscore for partials
18
+ identifier.gsub!(':_', ':') if metric == :partial
19
+ tags = { metric => identifier }
20
+ collector.group "rails.view.render" do |c|
21
+ c.increment metric, tags: tags, inherit_tags: true, sporadic: true
22
+ c.timing "#{metric}.time", event.duration, tags: tags, inherit_tags: true, sporadic: true
23
+ end # end group
21
24
  end
22
- end
25
+
26
+ end # end subscribe
23
27
 
24
28
  end
25
29
 
26
30
  end
27
31
  end
28
- end
32
+ end