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

Commit

Permalink
Support for tagged measurements (#123)
Browse files Browse the repository at this point in the history
  • Loading branch information
Chance Feick authored Jan 25, 2017
1 parent db8ef5f commit f991e77
Show file tree
Hide file tree
Showing 31 changed files with 373 additions and 287 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### master
* Add support for tagged measurements (#123)

### Version 1.4.2
* Bump librato-rack dependency to fix warns with ruby 2.4

Expand Down
125 changes: 52 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,41 @@ To see all config options or for information on combining config files and envir

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.

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

heroku config:add LIBRATO_SOURCE=myappname
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).

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

Full information on configuration options is available on the [configuration wiki page](https://github.com/librato/librato-rails/wiki/Configuration).
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).

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.
##### Detected Tags

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

```yaml
production:
user: <your-email>
token: <your-api-key>
tags:
service: 'myapp'
environment: 'production'
host: 'myapp-prod-1'
```

##### Custom Tags

In addition to the default tags, you can also provide custom tags:

```yaml
production:
user: <your-email>
token: <your-api-key>
tags:
region: 'us-east-1'
```

Full information on configuration options is available on the [configuration wiki page](https://github.com/librato/librato-rails/wiki/Configuration).

## Automatic Measurements

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

* *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`
* *rails_method*: `rails.request.method.*` metrics (GET, POST, etc)
* *rails_status*: `rails.request.status.*` metrics broken out by individual status codes and class (200, 2xx, etc)
* *rails_action*: `rails.action.*` metrics specific to individual controller actions via the [instrument_action](#instrument_action-experimental) helper
* *rails_method*: `rails.request.method` metric with `method` tag name and HTTP method tag value, e.g. `method=POST`
* *rails_status*: `rails.request.status` metric with `status` tag name and HTTP status code tag value, e.g. `status=200`

###### System-Specific Metrics

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

* *rack*: The `rack.request.total`, `rack.request.time`, `rack.request.slow`, and `rack.request.queue.time` metrics
* *rack_method*: `rack.request.method.*` metrics (GET, POST, etc)
* *rack_status*: `rack.request.status.*` metrics metrics broken out by individual status codes and class (200, 2xx, etc)
* *rack_method*: `rack.request.method` metric with `method` tag name and HTTP method tag value, e.g. `method=POST`
* *rack_status*: `rack.request.status` metric with `status` tag name and HTTP status code tag value, e.g. `status=200`

###### Queue Time

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

```ruby
# increment the 'sales_completed' metric by one
Librato.increment 'sales_completed'
Librato.increment 'sales.completed'
# => {:service=>"myapp", :environment=>"production", :host=>"myapp-prod-1"}
# increment by five
Librato.increment 'items_purchased', by: 5
Librato.increment 'items.purchased', by: 5
# => {:service=>"myapp", :environment=>"production", :host=>"myapp-prod-1"}
# increment with a custom source
Librato.increment 'user.purchases', source: user.id
# increment with custom per-measurement tags
Librato.increment 'user.purchases', tags: { user_id: user.id, currency: 'USD' }
# => {:user_id=>43, :currency=>"USD"}
# increment with custom per-measurement tags and inherited default tags
Librato.increment 'user.purchases', tags: { user_id: user.id, currency: 'USD' }, inherit_tags: true
# => {:service=>"myapp", :environment=>"production", :host=>"myapp-prod-1", :user_id=>43, :currency=>"USD"}
```

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
Expand All @@ -175,7 +205,7 @@ Especially with custom sources you may want the opposite behavior - reporting a

```ruby
# report a value for 'user.uploaded_file' only during non-zero intervals
Librato.increment 'user.uploaded_file', source: user.id, sporadic: true
Librato.increment 'user.uploaded_file', tags: { user: user.id, bucket: bucket.name }, sporadic: true
```

#### measure
Expand All @@ -185,8 +215,8 @@ Use when you want to track an average value _per_-request. Examples:
```ruby
Librato.measure 'user.social_graph.nodes', 212
# report from a custom source
Librato.measure 'jobs.queued', 3, source: 'worker.12'
# report from custom per-measurement tags
Librato.measure 'jobs.queued', 3, tags: { priority: 'high', worker: 'worker.12' }
```

#### timing
Expand All @@ -196,8 +226,8 @@ Like `Librato.measure` this is per-request, but specialized for timing informati
```ruby
Librato.timing 'twitter.lookup.time', 21.2
# report from a custom source
Librato.measure 'api.response.time', time, source: node_name
# report from custom per-measurement tags
Librato.measure 'api.response.time', time, tags: { node: node_name, db: 'rr1' }
```

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

Symbols can be used interchangably with strings for metric names.

## Controller Helpers

`librato-rails` also has special helpers which are available inside your controllers:

#### instrument_action
_experimental_, this interface may change:

Use when you want to profile execution time or request volume for a specific controller action:

```ruby
class CommentController < ApplicationController
instrument_action :create # can accept a list
def create
# ...
end
end
```

Optionally, you can instrument all controller actions:

```ruby
class ArticlesController < ApplicationController
instrument_action :all
def create
# ...
end
def show
# ...
end
end
```

Once you instrument an action, `librato-rails` will start reporting a set of metrics specific to that action including:

* rails.action.request.total (# of requests)
* rails.action.request.slow (requests >= 200ms to produce)
* rails.action.request.exceptions
* rails.action.request.time (total time spent in action)
* rails.action.request.time.db (db interaction time)
* rails.action.request.time.view (view rendering time)

Each instrumented action will appear as a source for the `rails.action.*` metrics, for example `mycontroller.action.html`.

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.

## Use with ActiveSupport::Notifications

`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.
Expand All @@ -324,7 +306,7 @@ ActiveSupport::Notifications.subscribe 'my.event' do |*args|
Librato.timing 'my.event.time', event.duration
# use payload data to do user-specific tracking
Librato.increment 'user.did.event', source: user.id, sporadic: true
Librato.increment 'user.did.event', tags: { user: user.id }, sporadic: true
# do conditional tracking
if user.feature_on?(:sample_group)
Expand Down Expand Up @@ -357,14 +339,11 @@ Never fear, [we have some guidelines](https://github.com/librato/librato-rails/w

## Cross-Process Aggregation

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

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

If you want to report per-process instead, you can set `source_pids` to `true` in
your config, which will append the process id to the source name used by each thread.

## Troubleshooting

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.
Expand Down
25 changes: 22 additions & 3 deletions lib/librato/rails/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require "socket"
require "yaml"

module Librato
Expand All @@ -8,9 +9,9 @@ module Rails
# https://github.com/librato/librato-rack/blob/master/lib/librato/rack/configuration.rb
#
class Configuration < Rack::Configuration
CONFIG_SETTABLE = %w{user token flush_interval log_level prefix source source_pids proxy suites}
CONFIG_SETTABLE = %w{flush_interval log_level prefix proxy suites tags token user}

DEFAULT_SUITES = [:rails_action, :rails_cache, :rails_controller, :rails_mail, :rails_method, :rails_render, :rails_sql, :rails_status, :rails_job]
DEFAULT_SUITES = [:rails_cache, :rails_controller, :rails_mail, :rails_method, :rails_render, :rails_sql, :rails_status, :rails_job]

attr_accessor :config_by, :config_file

Expand All @@ -32,6 +33,8 @@ def load_configuration
super
end

self.tags = detect_tags

# respect autorun and log_level env vars regardless of config method
self.autorun = detect_autorun
self.log_level = :info if log_level.blank?
Expand All @@ -46,14 +49,30 @@ def configure_with_config_file
env_specific = YAML.load(ERB.new(File.read(config_file)).result)[::Rails.env]
if env_specific
settable = CONFIG_SETTABLE & env_specific.keys
settable.each { |key| self.send("#{key}=", env_specific[key]) }
settable.each do |key|
value = env_specific[key]
value.symbolize_keys! if key == "tags"
self.send("#{key}=", value)
end
end
end

def default_suites
super + DEFAULT_SUITES
end

def default_tags
{
service: ::Rails.application.class.to_s.split("::").first.underscore,
environment: ::Rails.env,
host: Socket.gethostname.downcase
}
end

def detect_tags
has_tags? ? default_tags.merge(self.tags) : default_tags
end

end
end
end
3 changes: 1 addition & 2 deletions lib/librato/rails/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ class Railtie < ::Rails::Railtie
end

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

Librato::Rails::VersionSpecifier.supported(min: '4.2') do
require_relative 'subscribers/job'if tracker.suite_enabled?(:rails_job)
require_relative 'subscribers/job' if tracker.suite_enabled?(:rails_job)
end
end

Expand Down
39 changes: 0 additions & 39 deletions lib/librato/rails/subscribers/action.rb

This file was deleted.

8 changes: 5 additions & 3 deletions lib/librato/rails/subscribers/cache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ module Subscribers
%w{read generate fetch_hit write delete}.each do |metric|

ActiveSupport::Notifications.subscribe "cache_#{metric}.active_support" do |*args|

event = ActiveSupport::Notifications::Event.new(*args)

collector.group "rails.cache" do |c|
c.increment metric
c.timing "#{metric}.time", event.duration
end
end
end # end group

end # end subscribe

end

end
end
end
end
27 changes: 15 additions & 12 deletions lib/librato/rails/subscribers/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@ module Librato
module Rails
module Subscribers

# Controllers
# ActionController

ActiveSupport::Notifications.subscribe 'process_action.action_controller' do |*args|
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|

event = ActiveSupport::Notifications::Event.new(*args)
exception = event.payload[:exception]
format = event.payload[:format].to_s || "all"
format = "all" if format == "*/*"
tags = {
controller: event.payload[:controller],
action: event.payload[:action],
format: format,
}

collector.group "rails.request" do |r|
r.increment "total", tags: tags, inherit_tags: true
r.timing "time", event.duration, tags: tags, inherit_tags: true, percentile: 95
r.timing "time.db", event.payload[:db_runtime] || 0, tags: tags, inherit_tags: true, percentile: 95
r.timing "time.view", event.payload[:view_runtime] || 0, tags: tags, inherit_tags: true, percentile: 95

r.increment 'total'
r.timing 'time', event.duration, percentile: 95

if exception
r.increment 'exceptions'
else
r.timing 'time.db', event.payload[:db_runtime] || 0, percentile: 95
r.timing 'time.view', event.payload[:view_runtime] || 0, percentile: 95
if event.duration > 200.0
r.increment "slow", tags: tags, inherit_tags: true
end

r.increment 'slow' if event.duration > 200.0
end # end group

end # end subscribe
Expand Down
Loading

0 comments on commit f991e77

Please sign in to comment.