⚠️ You are looking at the README a development branch of this gem. If you are using this gem you should have a look at the latest released version (1.0.3) instead.
Automatically instrument your Ruby on Rails applications and write the metrics directly into InfluxDB.
Add the gem to your Gemfile
:
echo 'gem "influxdb-rails"' >> Gemfile
bundle install
To get things set up, create an initializer:
bundle exec rails generate influxdb
This creates a file config/initializers/influxdb_rails.rb
, which allows
configuration of this gem.
Out of the box, you'll automatically get reporting for the Ruby on Rails components mentioned below.
Reported ActiveSupport instrumentation hooks:
Reported fields:
controller: 48.467,
view: 46.848,
db: 0.157,
started: 1465839830100400200,
request_id: "d5bf620b-3494-425b-b7e1-4953597ea744"
Reported tags:
{
hook: "process_action",
server: Socket.gethostname,
app_name: configuration.application_name,
method: "PostsController#index",
http_method: "GET",
format: "html",
status: ["500"],
exception: "ArgumentError"
}
Reported ActiveSupport instrumentation hooks:
Reported fields:
value: 48.467,
count: 3,
cache_hits: 0,
request_id: "d5bf620b-3494-425b-b7e1-4953597ea744"
Reported tags:
hook: ["render_template", "render_partial", "render_collection"],
server: Socket.gethostname,
app_name: configuration.application_name,
location: "PostsController#index",
filename: "/some/file/action.html"
Reported ActiveSupport instrumentation hooks:
Reported fields:
sql: "SELECT \"posts\".* FROM \"posts\"",
request_id: "d5bf620b-3494-425b-b7e1-4953597ea744"
Reported SQL tags:
hook: "sql",
server: Socket.gethostname,
app_name: configuration.application_name,
location: "PostsController#index",
operation: "SELECT",
class_name: "POST",
name: "Post Load"
Reported instantiation values:
record_count: 1,
request_id: "d5bf620b-3494-425b-b7e1-4953597ea744"
value: 7.689
Reported instantiation tags:
hook: "instantiation",
server: Socket.gethostname,
app_name: configuration.application_name,
location: "PostsController#index",
class_name: "POST"
Reported ActiveSupport instrumentation hooks:
Reported fields:
value: 89.467
Reported tags:
hook: ["enqueue", "perform"],
state: ["queued", "succeeded", "failed"],
job: "SomeJobClassName",
queue: "queue_name"
Note: Only the measurements with the hook perform
report a duration in the value.
The enqueue hook is a counter and always reports a value of 1
.
Reported ActiveSupport instrumentation hooks:
Reported fields:
value: 1
Reported tags:
hook: "deliver",
mailer: "SomeMailerClassName"
Note: The hook is just a counter and always report a value of 1
.
The only settings you actually need to configure are the organization, bucket and access token.
InfluxDB::Rails.configure do |config|
config.client.org = "org"
config.client.bucket = "bucket"
config.client.token = "token"
end
You'll find all of the configuration settings in the initializer file.
You can modify the tags sent to InfluxDB by defining a middleware, which
receives the current tag set as argument and returns a hash in the same
form. The middleware can be any object, as long it responds to #call
(like a Proc
):
InfluxDB::Rails.configure do |config|
config.tags_middleware = lambda do |tags|
tags.merge(env: Rails.env)
end
end
The tags
argument is a Hash (mapping Symbol keys to String values). The
actual keys and values depend on the series name (tags[:series]
, see
next section).
If you want to add dynamically tags or fields per request, you can access
InfluxDB::Rails.current
to do so. For instance, you could add the current
user as tag or redis query time to every data point:
class ApplicationController
before_action :set_influx_data
def set_influx_data
InfluxDB::Rails.current.tags = { user: current_user.id }
InfluxDB::Rails.current.fields = { redis_value: redis_value }
end
end
If you want to add custom instrumentation, you can wrap any code into a block instrumentation
InfluxDB::Rails.instrument "expensive_operation", tags: { }, fields: { } do
expensive_operation
end
Reported tags:
hook: "block_instrumentation",
server: Socket.gethostname,
app_name: configuration.application_name,
location: "PostsController#index",
name: "expensive_operation"
Reported fields:
value: 100 # execution time of the block in ms
You can also overwrite the value
InfluxDB::Rails.instrument "user_count", fields: { value: 1 } do
User.create(name: 'mickey', surname: 'mouse')
end
or call it even without a block
InfluxDB::Rails.instrument "temperature", fields: { value: 25 }
The settings named config.client.*
are used to construct an InfluxDB::Client
instance, which is used internally to transmit the reporting data points
to your InfluxDB server. You can access this client as well, and perform
arbitrary operations on your data:
InfluxDB::Rails.write_api.write(
name: "events",
tags: { url: "/foo", user_id: current_user.id, location: InfluxDB::Rails.current.location },
fields: { value: 0 }
)
If you do that, it might be useful to add the current context to these custom
data points which can get accessed with InfluxDB::Rails.current.location
.
See influxdb-client for a full list of configuration options and detailed usage.
If you are not interested in certain reports you can disable them in the configuration.
InfluxDB::Rails.configure do |config|
config.ignored_hooks = ['sql.active_record', 'render_template.action_view']
end
Want to see this in action? Check out our sample dashboard.
By default, this gem writes data points with millisecond time precision to the InfluxDB server. If you have more than 1000 requests/second (and/or multiple parallel requests), only the last data point (within the same tag set) is stored. See InfluxDB server docs for further details.
To work around this limitation, set the config.client.time_precision
to one of "us"
(microseconds, 1·10-6s) or "ns"
(nanoseconds,
1·10-9s).
Please note: This will only ever reduce the likelihood of data points overwriting each other, but not eliminate it completely.
This gem subscribes to various ActiveSupport::Notifications
hooks.
(cf. guide · docs · impl). The
controller notifications are run after a controller action has finished,
and should not impact the response time.
Other notification hooks (rendering and SQL queries) run inline in the request processing. The amount of overhead introduced should be negligible, though. However reporting of SQL queries relies on Ruby string parsing which might cause performance issues on traffic intensive applications. Disable it in the configuration if you are not willing to tolerate this.
By default, this gem performs writes to InfluxDB asynchronously. A single hook usually only performs some time delta calculations, and then enqueues the data point into a worker queue (which is processed by a background thread).
If you, however, use a synchronous client (config.client.async = false
),
the data points are immediately sent to the InfluxDB server. Depending on
the network link, this might cause the HTTP thread to block a lot longer.
By default, the InfluxDB client will retry indefinitely, until a write
succeeds (see client docs for details). This has two important
implications, depending on the value of config.client.async
:
- if the client runs asynchronously (i.e. in a separate thread), the queue might fill up with hundreds of megabytes of data points
- if the client runs synchronously (i.e. inline in the request/response cycle), it might block all available request threads
In both cases, your application server might become unresponsive and needs to be restarted.
If you setup a maximum retry value (Integer === config.client.retry
),
the client will try up to that amount of times to send the data to the server
and (on final error) log an error and discard the values.
What happens, when the InfluxDB client or this gem throws an exception? Will the user see 500 errors?
No. The controller instrumentation is wrapped in a rescue StandardError
clause, i.e. this gem will only write the error to the client.logger
(Rails.logger
by default) and not disturb the user experience.
The data points are simply discarded.
- Fork this repository on GitHub
- Make your changes
- Add tests.
- Add an entry in the
CHANGELOG.md
in the "unreleased" section on top.
- Run the tests:
-
Either run them manually:
rake test:all
-
or wait for our CI to pick up your changes, after you made a pull request.
-
- Send a pull request.
- If your changes are looking good, we'll merge them.
rake # unit tests + Rubocop linting
rake spec # only unit tests
rake rubocop # only Rubocop linter
rake test:all # integration tests with various Rails version