metrics-rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +24 -0
- data/README.md +105 -0
- data/Rakefile +41 -0
- data/lib/metrics-rails.rb +1 -0
- data/lib/metrics/rails.rb +226 -0
- data/lib/metrics/rails/aggregator.rb +48 -0
- data/lib/metrics/rails/counter_cache.rb +41 -0
- data/lib/metrics/rails/group.rb +27 -0
- data/lib/metrics/rails/helpers.rb +19 -0
- data/lib/metrics/rails/railtie.rb +17 -0
- data/lib/metrics/rails/subscribers.rb +77 -0
- data/lib/metrics/rails/version.rb +5 -0
- data/lib/metrics/rails/worker.rb +58 -0
- data/lib/tasks/metrics-rails_tasks.rake +4 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/javascripts/home.js +2 -0
- data/test/dummy/app/assets/javascripts/status.js +2 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/assets/stylesheets/home.css +4 -0
- data/test/dummy/app/assets/stylesheets/status.css +4 -0
- data/test/dummy/app/controllers/application_controller.rb +10 -0
- data/test/dummy/app/controllers/home_controller.rb +33 -0
- data/test/dummy/app/controllers/status_controller.rb +5 -0
- data/test/dummy/app/controllers/user_controller.rb +13 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/helpers/home_helper.rb +2 -0
- data/test/dummy/app/helpers/status_helper.rb +2 -0
- data/test/dummy/app/mailers/user_mailer.rb +8 -0
- data/test/dummy/app/models/user.rb +18 -0
- data/test/dummy/app/views/home/index.html.erb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/status/code.html.erb +2 -0
- data/test/dummy/app/views/user_mailer/welcome_email.text.erb +1 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +63 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/silence_assets.rb +16 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +62 -0
- data/test/dummy/config/unicorn.rb +16 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20120719231810_create_users.rb +10 -0
- data/test/dummy/db/schema.rb +23 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +161447 -0
- data/test/dummy/log/test.log +19173 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/test_env.sh +2 -0
- data/test/dummy/tmp/cache/assets/C94/D50/sprockets%2F1a49716f234a1b88976e3f09954f8f14 +0 -0
- data/test/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
- data/test/dummy/tmp/cache/assets/CDF/870/sprockets%2Fb878faf942403e313a5b103e5d80488e +0 -0
- data/test/dummy/tmp/cache/assets/CE8/7E0/sprockets%2F178e2a1f9aa891d473009c7f3095df28 +0 -0
- data/test/dummy/tmp/cache/assets/CF9/7C0/sprockets%2F40fc2f3d2a468a00e463f1d313cb1683 +0 -0
- data/test/dummy/tmp/cache/assets/D04/890/sprockets%2F587335c079eef8d5a63784fc8f99905a +0 -0
- data/test/dummy/tmp/cache/assets/D05/D40/sprockets%2F1c9faaf28d05409b88ad3113374d613c +0 -0
- data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/test/dummy/tmp/cache/assets/D4F/000/sprockets%2F25e44896aac12384727e9dab827ebef9 +0 -0
- data/test/dummy/tmp/cache/assets/D51/0D0/sprockets%2Fe895d60a653d8b87f7c5717a4d4cf1f3 +0 -0
- data/test/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/test/dummy/tmp/cache/assets/D8B/F90/sprockets%2Ffe6ce696e9141eb755d8eed79128e17c +0 -0
- data/test/dummy/tmp/cache/assets/D98/8B0/sprockets%2Fedbef6e0d0a4742346cf479f2c522eb0 +0 -0
- data/test/dummy/tmp/cache/assets/DA1/8B0/sprockets%2F65acae5ede4e92f105c5e8631407c7fc +0 -0
- data/test/dummy/tmp/cache/assets/DA5/7B0/sprockets%2Fa0fc1785d4dc1bde68dd7d5652d27a95 +0 -0
- data/test/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/fixtures/config/metrics.yml +10 -0
- data/test/integration/helper_test.rb +29 -0
- data/test/integration/mail_test.rb +15 -0
- data/test/integration/request_test.rb +53 -0
- data/test/integration/sql_test.rb +30 -0
- data/test/metrics-rails_test.rb +39 -0
- data/test/remote/rails_remote_test.rb +105 -0
- data/test/support/integration_case.rb +19 -0
- data/test/test_helper.rb +23 -0
- data/test/unit/aggregator_test.rb +16 -0
- data/test/unit/configuration_test.rb +49 -0
- data/test/unit/counter_cache_test.rb +19 -0
- data/test/unit/group_test.rb +49 -0
- data/test/unit/worker_test.rb +31 -0
- metadata +280 -0
data/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Copyright (c) 2012. Librato, Inc.
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
* Redistributions of source code must retain the above copyright
|
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
|
10
|
+
documentation and/or other materials provided with the distribution.
|
|
11
|
+
* Neither the name of the <organization> nor the
|
|
12
|
+
names of its contributors may be used to endorse or promote products
|
|
13
|
+
derived from this software without specific prior written permission.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL LIBRATO, INC. BE LIABLE FOR ANY
|
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
metrics-rails
|
|
2
|
+
=======
|
|
3
|
+
|
|
4
|
+
[](http://travis-ci.org/librato/metrics-rails)
|
|
5
|
+
|
|
6
|
+
Report key statistics for your Rails app to [Librato Metrics](https://metrics.librato.com/), easily track your own custom metrics. Currently supports Rails 3.0+.
|
|
7
|
+
|
|
8
|
+
**NOTE: This is currently in alpha development and is not yet officially supported**
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
In your `Gemfile` add:
|
|
13
|
+
|
|
14
|
+
gem 'metrics-rails'
|
|
15
|
+
|
|
16
|
+
Then run `bundle install`.
|
|
17
|
+
|
|
18
|
+
## Configuration
|
|
19
|
+
|
|
20
|
+
If you don't have a Metrics account already, [sign up](https://metrics.librato.com/). In order to send measurements to Metrics you need to provide your account credentials to `metrics-rails`. You can provide these one of two ways:
|
|
21
|
+
|
|
22
|
+
Create a `config/metrics.yml` like the following:
|
|
23
|
+
|
|
24
|
+
production:
|
|
25
|
+
email: <your-email>
|
|
26
|
+
api_key: <your-api-key>
|
|
27
|
+
|
|
28
|
+
OR provide `METRICS_EMAIL` and `METRICS_API_KEY` environment variables. If both env variables and a config file are present, environment variables will take precendence.
|
|
29
|
+
|
|
30
|
+
Note that using a configuration file allows you to specify configurations per-environment. Submission will be disabled in any environment without credentials. However, if environment variables are set they will be used in all environments.
|
|
31
|
+
|
|
32
|
+
Full information on configuration options is available on the [configuration wiki page](https://github.com/librato/metrics-rails/wiki/Configuration).
|
|
33
|
+
|
|
34
|
+
## Automatic Measurements
|
|
35
|
+
|
|
36
|
+
After installing `metrics-rails` and restarting your app and you will see a number of new metrics appear in your Metrics account. These track request performance, sql queries, mail handling, and other key stats. All built-in performance metrics start with the prefix `rails` by convention — for example: `rails.request.total` is the total number of requests received during an interval.
|
|
37
|
+
|
|
38
|
+
If you have multiple apps reporting to the same Metrics account you can change this prefix in your [configuration](https://github.com/librato/metrics-rails/wiki/Configuration).
|
|
39
|
+
|
|
40
|
+
## Custom Measurements
|
|
41
|
+
|
|
42
|
+
Tracking anything that interests you is easy with Metrics. There are four primary helpers available:
|
|
43
|
+
|
|
44
|
+
#### increment
|
|
45
|
+
|
|
46
|
+
Use for tracking a running total of something _across_ requests, examples:
|
|
47
|
+
|
|
48
|
+
# increment the 'sales_completed' metric by one
|
|
49
|
+
Metrics.increment 'sales_completed'
|
|
50
|
+
|
|
51
|
+
# increment by five
|
|
52
|
+
Metrics.increment 'items_purchased', 5
|
|
53
|
+
|
|
54
|
+
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
|
|
55
|
+
|
|
56
|
+
#### measure
|
|
57
|
+
|
|
58
|
+
Use when you want to track an average value _per_-request. Examples:
|
|
59
|
+
|
|
60
|
+
Metrics.measure 'user.social_graph.nodes', 212
|
|
61
|
+
|
|
62
|
+
Metrics.measure 'jobs.queued', 3
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
#### timing
|
|
66
|
+
|
|
67
|
+
Like `Metrics.measure` this is per-request, but specialized for timing information:
|
|
68
|
+
|
|
69
|
+
Metrics.timing 'twitter.lookup.time', 21.2
|
|
70
|
+
|
|
71
|
+
The block form auto-submits the time it took for its contents to execute as the measurement value:
|
|
72
|
+
|
|
73
|
+
Metrics.timing 'twitter.lookup.time' do
|
|
74
|
+
@twitter = Twitter.lookup(user)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
#### group
|
|
78
|
+
|
|
79
|
+
There is also a grouping helper, to make managing nested metrics easier. So this:
|
|
80
|
+
|
|
81
|
+
Metrics.measure 'memcached.gets', 20
|
|
82
|
+
Metrics.measure 'memcached.sets', 2
|
|
83
|
+
Metrics.measure 'memcached.hits', 18
|
|
84
|
+
|
|
85
|
+
Can also be written as:
|
|
86
|
+
|
|
87
|
+
Metrics.group 'memcached' do |g|
|
|
88
|
+
g.measure 'gets', 20
|
|
89
|
+
g.measure 'sets', 2
|
|
90
|
+
g.measure 'hits', 18
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
Symbols can be used interchangably with strings for metric names.
|
|
94
|
+
|
|
95
|
+
## Contribution
|
|
96
|
+
|
|
97
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
|
98
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
|
99
|
+
* Fork the project and submit a pull request from a feature or bugfix branch.
|
|
100
|
+
* Please include tests. This is important so we don't break your changes unintentionally in a future version.
|
|
101
|
+
* Please don't modify the gemspec, Rakefile, version, or changelog. If you do change these files, please isolate a separate commit so we can cherry-pick around it.
|
|
102
|
+
|
|
103
|
+
## Copyright
|
|
104
|
+
|
|
105
|
+
Copyright (c) 2012 [Librato Inc.](http://librato.com) See LICENSE for details.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env rake
|
|
2
|
+
begin
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
rescue LoadError
|
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
6
|
+
end
|
|
7
|
+
begin
|
|
8
|
+
require 'rdoc/task'
|
|
9
|
+
rescue LoadError
|
|
10
|
+
require 'rdoc/rdoc'
|
|
11
|
+
require 'rake/rdoctask'
|
|
12
|
+
RDoc::Task = Rake::RDocTask
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
17
|
+
rdoc.title = 'MetricsRails'
|
|
18
|
+
rdoc.options << '--line-numbers'
|
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# IRB
|
|
24
|
+
desc "Open an irb session preloaded with this library"
|
|
25
|
+
task :console do
|
|
26
|
+
sh "pry -rubygems -r rails -r ./lib/metrics-rails.rb"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Bundler::GemHelper.install_tasks
|
|
30
|
+
|
|
31
|
+
require 'rake/testtask'
|
|
32
|
+
|
|
33
|
+
Rake::TestTask.new(:test) do |t|
|
|
34
|
+
t.libs << 'lib'
|
|
35
|
+
t.libs << 'test'
|
|
36
|
+
t.pattern = 'test/**/*_test.rb'
|
|
37
|
+
t.verbose = false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
task :default => :test
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'metrics/rails'
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'thread'
|
|
3
|
+
|
|
4
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
|
5
|
+
require 'active_support/notifications'
|
|
6
|
+
require 'librato/metrics'
|
|
7
|
+
|
|
8
|
+
require 'metrics/rails/aggregator'
|
|
9
|
+
require 'metrics/rails/counter_cache'
|
|
10
|
+
require 'metrics/rails/group'
|
|
11
|
+
require 'metrics/rails/helpers'
|
|
12
|
+
require 'metrics/rails/worker'
|
|
13
|
+
require 'metrics/rails/version'
|
|
14
|
+
|
|
15
|
+
module Metrics
|
|
16
|
+
extend SingleForwardable
|
|
17
|
+
def_delegators Metrics::Rails, :increment, :measure, :timing, :group
|
|
18
|
+
|
|
19
|
+
module Rails
|
|
20
|
+
extend SingleForwardable
|
|
21
|
+
CONFIG_SETTABLE = %w{api_key email flush_interval prefix source}
|
|
22
|
+
|
|
23
|
+
mattr_accessor :config_file
|
|
24
|
+
|
|
25
|
+
# config options
|
|
26
|
+
mattr_accessor :api_key
|
|
27
|
+
mattr_accessor :email
|
|
28
|
+
mattr_accessor :flush_interval
|
|
29
|
+
mattr_accessor :prefix
|
|
30
|
+
|
|
31
|
+
# config defaults
|
|
32
|
+
self.flush_interval = 60 # seconds
|
|
33
|
+
self.prefix = 'rails'
|
|
34
|
+
|
|
35
|
+
def_delegators :counters, :increment
|
|
36
|
+
def_delegators :aggregate, :measure, :timing
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
|
|
40
|
+
# access to internal aggregator object
|
|
41
|
+
def aggregate
|
|
42
|
+
@aggregator_cache ||= Aggregator.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# set custom api endpoint
|
|
46
|
+
def api_endpoint=(endpoint)
|
|
47
|
+
@api_endpoint = endpoint
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# access to client instance
|
|
51
|
+
def client
|
|
52
|
+
@client ||= prepare_client
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# detect / update configuration
|
|
56
|
+
def check_config
|
|
57
|
+
if self.config_file && File.exists?(self.config_file)
|
|
58
|
+
configs = YAML.load_file(config_file)
|
|
59
|
+
if env_specific = configs[::Rails.env]
|
|
60
|
+
settable = CONFIG_SETTABLE & env_specific.keys
|
|
61
|
+
settable.each { |key| self.send("#{key}=", env_specific[key]) }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
self.api_key = ENV['METRICS_API_KEY'] if ENV['METRICS_API_KEY']
|
|
65
|
+
self.email = ENV['METRICS_EMAIL'] if ENV['METRICS_EMAIL']
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# check to see if we've forked into a process where a worker
|
|
69
|
+
# isn't running yet, if so start it up!
|
|
70
|
+
def check_worker
|
|
71
|
+
if @pid != $$
|
|
72
|
+
start_worker
|
|
73
|
+
#aggregate.clear
|
|
74
|
+
#counters.clear
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# access to internal counters object
|
|
79
|
+
def counters
|
|
80
|
+
@counter_cache ||= CounterCache.new
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# remove any accumulated but unsent metrics
|
|
84
|
+
def delete_all
|
|
85
|
+
aggregate.delete_all
|
|
86
|
+
counters.delete_all
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# catches request-environment specific things we can't
|
|
90
|
+
# find through another means.
|
|
91
|
+
def evaluate_request(request)
|
|
92
|
+
if request.env.keys.include?('HTTP_X_HEROKU_QUEUE_DEPTH')
|
|
93
|
+
# heroku
|
|
94
|
+
group "#{prefix}.heroku" do |h|
|
|
95
|
+
h.measure 'queue.depth', request.env['HTTP_X_HEROKU_QUEUE_DEPTH'].to_f
|
|
96
|
+
h.timing 'queue.wait_time', request.env['HTTP_X_HEROKU_QUEUE_WAIT_TIME'].to_f
|
|
97
|
+
h.measure 'queue.dynos', request.env['HTTP_X_HEROKU_DYNOS_IN_USE'].to_f
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# send all current data to Metrics
|
|
103
|
+
def flush
|
|
104
|
+
logger.debug "[metrics-rails] flushing #{Process.pid} (#{Time.now}):"
|
|
105
|
+
queue = client.new_queue(:source => qualified_source)
|
|
106
|
+
# thread safety is handled internally for both stores
|
|
107
|
+
counters.flush_to(queue)
|
|
108
|
+
aggregate.flush_to(queue)
|
|
109
|
+
logger.debug queue.queued
|
|
110
|
+
queue.submit unless queue.empty?
|
|
111
|
+
rescue Exception => error
|
|
112
|
+
logger.error "[metrics-rails] submission failed permanently, worker exiting: #{error}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def group(prefix)
|
|
116
|
+
group = Group.new(prefix)
|
|
117
|
+
yield group
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def logger
|
|
121
|
+
@logger ||= ::Rails.logger
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# source including process pid
|
|
125
|
+
def qualified_source
|
|
126
|
+
"#{source}.#{$$}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# run once during Rails startup sequence
|
|
130
|
+
def setup
|
|
131
|
+
check_config
|
|
132
|
+
#return unless self.email && self.api_key
|
|
133
|
+
logger.info "[metrics-rails] starting up with #{app_server}..."
|
|
134
|
+
@pid = $$
|
|
135
|
+
install_request_evaluator
|
|
136
|
+
if forking_server?
|
|
137
|
+
install_worker_check
|
|
138
|
+
else
|
|
139
|
+
start_worker # start immediately
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def source
|
|
144
|
+
@source ||= Socket.gethostname
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# set a custom source
|
|
148
|
+
def source=(src)
|
|
149
|
+
@source = src
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# start the worker thread, one is needed per process.
|
|
153
|
+
# if this process has been forked from an one with an active
|
|
154
|
+
# worker thread we don't need to worry about cleanup as only
|
|
155
|
+
# the forking thread is copied.
|
|
156
|
+
def start_worker
|
|
157
|
+
return if @worker # already running
|
|
158
|
+
@pid = $$
|
|
159
|
+
logger.debug "[metrics-rails] >> starting up worker for pid #{@pid}..."
|
|
160
|
+
@worker = Thread.new do
|
|
161
|
+
worker = Worker.new
|
|
162
|
+
worker.run_periodically(self.flush_interval) do
|
|
163
|
+
flush
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
def app_server
|
|
171
|
+
if defined?(::Unicorn) && defined?(::Unicorn::HttpServer) && !::Unicorn.listener_names.empty?
|
|
172
|
+
:unicorn
|
|
173
|
+
elsif defined?(::IN_PHUSION_PASSENGER) || (defined?(::Passenger) && defined?(::Passenger::AbstractServer))
|
|
174
|
+
:passenger
|
|
175
|
+
elsif defined?(::Thin) && defined?(::Thin::Server)
|
|
176
|
+
:thin
|
|
177
|
+
else
|
|
178
|
+
:other
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def forking_server?
|
|
183
|
+
%w{unicorn passenger}.include?(app_server.to_s)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def install_worker_check
|
|
187
|
+
::ApplicationController.prepend_before_filter do |c|
|
|
188
|
+
Metrics::Rails.check_worker
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def install_request_evaluator
|
|
193
|
+
::ApplicationController.prepend_before_filter do |c|
|
|
194
|
+
Metrics::Rails.evaluate_request(request)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def prepare_client
|
|
199
|
+
check_config
|
|
200
|
+
client = Librato::Metrics::Client.new
|
|
201
|
+
client.authenticate email, api_key
|
|
202
|
+
client.api_endpoint = @api_endpoint if @api_endpoint
|
|
203
|
+
client.custom_user_agent = user_agent
|
|
204
|
+
client
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def ruby_engine
|
|
208
|
+
return RUBY_ENGINE if Object.constants.include?(:RUBY_ENGINE)
|
|
209
|
+
RUBY_DESCRIPTION.split[0]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def user_agent
|
|
213
|
+
ua_chunks = []
|
|
214
|
+
ua_chunks << "metrics-rails/#{Metrics::Rails::VERSION}"
|
|
215
|
+
ua_chunks << "(#{ruby_engine}; #{RUBY_VERSION}p#{RUBY_PATCHLEVEL}; #{RUBY_PLATFORM}; #{app_server})"
|
|
216
|
+
ua_chunks.join(' ')
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
end # end class << self
|
|
220
|
+
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# must load after all module setup
|
|
225
|
+
require 'metrics/rails/railtie'
|
|
226
|
+
require 'metrics/rails/subscribers'
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Metrics
|
|
2
|
+
module Rails
|
|
3
|
+
class Aggregator
|
|
4
|
+
extend Forwardable
|
|
5
|
+
|
|
6
|
+
def_delegators :@cache, :empty?
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@cache = Librato::Metrics::Aggregator.new
|
|
10
|
+
@lock = Mutex.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def [](key)
|
|
14
|
+
return nil if @cache.empty?
|
|
15
|
+
gauges = nil
|
|
16
|
+
@lock.synchronize { gauges = @cache.queued[:gauges] }
|
|
17
|
+
gauges.each do |metric|
|
|
18
|
+
return metric if metric[:name] == key.to_s
|
|
19
|
+
end
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def delete_all
|
|
24
|
+
@lock.synchronize { @cache.clear }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# transfer all measurements to a queue and
|
|
28
|
+
# reset internal status
|
|
29
|
+
def flush_to(queue, options={})
|
|
30
|
+
queued = nil
|
|
31
|
+
@lock.synchronize do
|
|
32
|
+
return if @cache.empty?
|
|
33
|
+
queued = @cache.queued
|
|
34
|
+
@cache.clear
|
|
35
|
+
end
|
|
36
|
+
queue.merge!(queued) if queued
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def measure(event, duration)
|
|
40
|
+
@lock.synchronize do
|
|
41
|
+
@cache.add event.to_s => duration
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
alias :timing :measure
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|