Skip to content
This repository was archived by the owner on Dec 11, 2023. It is now read-only.

Commit f991e77

Browse files
author
Chance Feick
authored
Support for tagged measurements (#123)
1 parent db8ef5f commit f991e77

31 files changed

+373
-287
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
### master
2+
* Add support for tagged measurements (#123)
3+
14
### Version 1.4.2
25
* Bump librato-rack dependency to fix warns with ruby 2.4
36

README.md

+52-73
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,41 @@ To see all config options or for information on combining config files and envir
6868

6969
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.
7070

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.
7272

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).
7474

75-
If you are using a config file, add your source entry to that instead.
75+
## Default Tags
7676

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).
7878

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
8080

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).
82106

83107
## Automatic Measurements
84108

@@ -93,9 +117,8 @@ The metrics automatically recorded by `librato-rails` are organized into named m
93117
###### Request Metrics
94118

95119
* *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`
99122

100123
###### System-Specific Metrics
101124

@@ -110,8 +133,8 @@ The metrics automatically recorded by `librato-rails` are organized into named m
110133
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.
111134

112135
* *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`
115138

116139
###### Queue Time
117140

@@ -156,13 +179,20 @@ Use for tracking a running total of something _across_ requests, examples:
156179

157180
```ruby
158181
# 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"}
160184
161185
# 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"}
163188
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"}
166196
```
167197

168198
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
175205

176206
```ruby
177207
# 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
179209
```
180210

181211
#### measure
@@ -185,8 +215,8 @@ Use when you want to track an average value _per_-request. Examples:
185215
```ruby
186216
Librato.measure 'user.social_graph.nodes', 212
187217
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' }
190220
```
191221

192222
#### timing
@@ -196,8 +226,8 @@ Like `Librato.measure` this is per-request, but specialized for timing informati
196226
```ruby
197227
Librato.timing 'twitter.lookup.time', 21.2
198228
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' }
201231
```
202232

203233
The block form auto-submits the time it took for its contents to execute as the measurement value:
@@ -250,54 +280,6 @@ end
250280

251281
Symbols can be used interchangably with strings for metric names.
252282

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-
301283
## Use with ActiveSupport::Notifications
302284

303285
`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|
324306
Librato.timing 'my.event.time', event.duration
325307
326308
# 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
328310
329311
# do conditional tracking
330312
if user.feature_on?(:sample_group)
@@ -357,14 +339,11 @@ Never fear, [we have some guidelines](https://github.com/librato/librato-rails/w
357339

358340
## Cross-Process Aggregation
359341

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.
361343

362344
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.
363345
Current pricing applies after aggregation, so in this case you will be charged for 4 streams instead of 32.
364346

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-
368347
## Troubleshooting
369348

370349
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.

lib/librato/rails/configuration.rb

+22-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require "socket"
12
require "yaml"
23

34
module Librato
@@ -8,9 +9,9 @@ module Rails
89
# https://github.com/librato/librato-rack/blob/master/lib/librato/rack/configuration.rb
910
#
1011
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}
1213

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]
1415

1516
attr_accessor :config_by, :config_file
1617

@@ -32,6 +33,8 @@ def load_configuration
3233
super
3334
end
3435

36+
self.tags = detect_tags
37+
3538
# respect autorun and log_level env vars regardless of config method
3639
self.autorun = detect_autorun
3740
self.log_level = :info if log_level.blank?
@@ -46,14 +49,30 @@ def configure_with_config_file
4649
env_specific = YAML.load(ERB.new(File.read(config_file)).result)[::Rails.env]
4750
if env_specific
4851
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
5057
end
5158
end
5259

5360
def default_suites
5461
super + DEFAULT_SUITES
5562
end
5663

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+
5776
end
5877
end
5978
end

lib/librato/rails/railtie.rb

+1-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ class Railtie < ::Rails::Railtie
4040
end
4141

4242
tracker = config.librato_rails.tracker
43-
require_relative 'subscribers/action' if tracker.suite_enabled?(:rails_action)
4443
require_relative 'subscribers/cache' if tracker.suite_enabled?(:rails_cache)
4544
require_relative 'subscribers/controller' if tracker.suite_enabled?(:rails_controller)
4645
require_relative 'subscribers/mail' if tracker.suite_enabled?(:rails_mail)
@@ -50,7 +49,7 @@ class Railtie < ::Rails::Railtie
5049
require_relative 'subscribers/status' if tracker.suite_enabled?(:rails_status)
5150

5251
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)
5453
end
5554
end
5655

lib/librato/rails/subscribers/action.rb

-39
This file was deleted.

lib/librato/rails/subscribers/cache.rb

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@ module Subscribers
77
%w{read generate fetch_hit write delete}.each do |metric|
88

99
ActiveSupport::Notifications.subscribe "cache_#{metric}.active_support" do |*args|
10+
1011
event = ActiveSupport::Notifications::Event.new(*args)
1112

1213
collector.group "rails.cache" do |c|
1314
c.increment metric
1415
c.timing "#{metric}.time", event.duration
15-
end
16-
end
16+
end # end group
17+
18+
end # end subscribe
1719

1820
end
1921

2022
end
2123
end
22-
end
24+
end

lib/librato/rails/subscribers/controller.rb

+15-12
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,29 @@ module Librato
22
module Rails
33
module Subscribers
44

5-
# Controllers
5+
# ActionController
66

7-
ActiveSupport::Notifications.subscribe 'process_action.action_controller' do |*args|
7+
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
88

99
event = ActiveSupport::Notifications::Event.new(*args)
1010
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+
}
1118

1219
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
1324

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
2227
end
23-
24-
r.increment 'slow' if event.duration > 200.0
2528
end # end group
2629

2730
end # end subscribe

0 commit comments

Comments
 (0)