librato-rails 0.5.2 → 0.6.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/README.md +28 -3
- data/lib/librato/rails/aggregator.rb +40 -5
- data/lib/librato/rails/collector.rb +45 -0
- data/lib/librato/rails/counter_cache.rb +86 -8
- data/lib/librato/rails/railtie.rb +1 -3
- data/lib/librato/rails/version.rb +1 -1
- data/lib/librato/rails.rb +21 -35
- data/test/dummy/log/test.log +3644 -0
- data/test/remote/rails_remote_test.rb +14 -0
- data/test/unit/aggregator_test.rb +14 -0
- data/test/unit/counter_cache_test.rb +73 -2
- data/test/unit/group_test.rb +6 -1
- metadata +7 -6
data/README.md
CHANGED
@@ -28,7 +28,9 @@ Create a `config/librato.yml` like the following:
|
|
28
28
|
production:
|
29
29
|
user: <your-email>
|
30
30
|
token: <your-api-key>
|
31
|
-
|
31
|
+
|
32
|
+
(the file is parsed via ERB in case you need to add some magic in there - useful in some cloud environments)
|
33
|
+
|
32
34
|
OR provide `LIBRATO_METRICS_USER` and `LIBRATO_METRICS_TOKEN` environment variables. If both env variables and a config file are present, environment variables will take precendence.
|
33
35
|
|
34
36
|
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.
|
@@ -53,17 +55,30 @@ Use for tracking a running total of something _across_ requests, examples:
|
|
53
55
|
Librato.increment 'sales_completed'
|
54
56
|
|
55
57
|
# increment by five
|
56
|
-
Librato.increment 'items_purchased', 5
|
58
|
+
Librato.increment 'items_purchased', :by => 5
|
59
|
+
|
60
|
+
# increment with a custom source
|
61
|
+
Librato.increment 'user.purchases', :source => user.id
|
57
62
|
|
58
63
|
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
|
59
64
|
|
65
|
+
###### Sporadic Increment Reporting
|
66
|
+
|
67
|
+
Note that `increment` is primarily used for tracking the rate of occurrence of some event. Given this increment metrics are _continuous by default_: after being called on a metric once they will report on every interval, reporting zeros for any interval when increment was not called on the metric.
|
68
|
+
|
69
|
+
Especially with custom sources you may want the opposite behavior - reporting a measurement only during intervals where `increment` was called on the metric:
|
70
|
+
|
71
|
+
# report a value for 'user.uploaded_file' only during non-zero intervals
|
72
|
+
Librato.increment 'user.uploaded_file', :source => user.id, :sporadic => true
|
73
|
+
|
60
74
|
#### measure
|
61
75
|
|
62
76
|
Use when you want to track an average value _per_-request. Examples:
|
63
77
|
|
64
78
|
Librato.measure 'user.social_graph.nodes', 212
|
65
79
|
|
66
|
-
|
80
|
+
# report from a custom source
|
81
|
+
Librato.measure 'jobs.queued', 3, :source => 'worker.12'
|
67
82
|
|
68
83
|
|
69
84
|
#### timing
|
@@ -96,6 +111,16 @@ Can also be written as:
|
|
96
111
|
|
97
112
|
Symbols can be used interchangably with strings for metric names.
|
98
113
|
|
114
|
+
## Cross-process Aggregation
|
115
|
+
|
116
|
+
`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.
|
117
|
+
|
118
|
+
For example if you have 4 hosts with 8 unicorn instances each (i.e. 32 processes total), on the Metrics site you'll find 4 data streams (1 per host) instead of 32.
|
119
|
+
Current pricing applies after aggregation, so in this case you will be charged for 4 streams instead of 32.
|
120
|
+
|
121
|
+
If you want to report per-process instead, you can set `source_pids` to `true` in
|
122
|
+
your config, which will append the process id to the source name used by each thread.
|
123
|
+
|
99
124
|
## Contribution
|
100
125
|
|
101
126
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
@@ -3,7 +3,7 @@ module Librato
|
|
3
3
|
class Aggregator
|
4
4
|
extend Forwardable
|
5
5
|
|
6
|
-
def_delegators :@cache, :empty
|
6
|
+
def_delegators :@cache, :empty?, :prefix, :prefix=
|
7
7
|
|
8
8
|
def initialize(options={})
|
9
9
|
@cache = Librato::Metrics::Aggregator.new(:prefix => options[:prefix])
|
@@ -11,11 +11,19 @@ module Librato
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def [](key)
|
14
|
+
fetch(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch(key, options={})
|
14
18
|
return nil if @cache.empty?
|
15
19
|
gauges = nil
|
20
|
+
source = options[:source]
|
16
21
|
@lock.synchronize { gauges = @cache.queued[:gauges] }
|
17
22
|
gauges.each do |metric|
|
18
|
-
|
23
|
+
if metric[:name] == key.to_s
|
24
|
+
return metric if !source && !metric[:source]
|
25
|
+
return metric if source.to_s == metric[:source]
|
26
|
+
end
|
19
27
|
end
|
20
28
|
nil
|
21
29
|
end
|
@@ -24,8 +32,7 @@ module Librato
|
|
24
32
|
@lock.synchronize { @cache.clear }
|
25
33
|
end
|
26
34
|
|
27
|
-
# transfer all measurements to
|
28
|
-
# reset internal status
|
35
|
+
# transfer all measurements to queue and reset internal status
|
29
36
|
def flush_to(queue, options={})
|
30
37
|
queued = nil
|
31
38
|
@lock.synchronize do
|
@@ -36,9 +43,26 @@ module Librato
|
|
36
43
|
queue.merge!(queued) if queued
|
37
44
|
end
|
38
45
|
|
46
|
+
# @example Simple measurement
|
47
|
+
# measure 'sources_returned', sources.length
|
48
|
+
#
|
49
|
+
# @example Simple timing in milliseconds
|
50
|
+
# timing 'twitter.lookup', 2.31
|
51
|
+
#
|
52
|
+
# @example Block-based timing
|
53
|
+
# timing 'db.query' do
|
54
|
+
# do_my_query
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# @example Custom source
|
58
|
+
# measure 'user.all_orders', user.order_count, :source => user.id
|
59
|
+
#
|
39
60
|
def measure(*args, &block)
|
61
|
+
options = {}
|
40
62
|
event = args[0].to_s
|
41
63
|
returned = nil
|
64
|
+
|
65
|
+
# handle block or specified argument
|
42
66
|
if block_given?
|
43
67
|
start = Time.now
|
44
68
|
returned = yield
|
@@ -48,8 +72,19 @@ module Librato
|
|
48
72
|
else
|
49
73
|
raise "no value provided"
|
50
74
|
end
|
75
|
+
|
76
|
+
# detect options hash if present
|
77
|
+
if args.length > 1 and args[-1].respond_to?(:each)
|
78
|
+
options = args[-1]
|
79
|
+
end
|
80
|
+
source = options[:source]
|
81
|
+
|
51
82
|
@lock.synchronize do
|
52
|
-
|
83
|
+
if source
|
84
|
+
@cache.add event => {:source => source, :value => value}
|
85
|
+
else
|
86
|
+
@cache.add event => value
|
87
|
+
end
|
53
88
|
end
|
54
89
|
returned
|
55
90
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
# an abstract collector object which can be given measurement values
|
3
|
+
# and can periodically report those values back to the Metrics service
|
4
|
+
|
5
|
+
module Librato
|
6
|
+
module Rails
|
7
|
+
class Collector
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :counters, :increment
|
11
|
+
def_delegators :aggregate, :measure, :timing
|
12
|
+
|
13
|
+
# access to internal aggregator object
|
14
|
+
def aggregate
|
15
|
+
@aggregator_cache ||= Aggregator.new(:prefix => @prefix)
|
16
|
+
end
|
17
|
+
|
18
|
+
# access to internal counters object
|
19
|
+
def counters
|
20
|
+
@counter_cache ||= CounterCache.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# remove any accumulated but unsent metrics
|
24
|
+
def delete_all
|
25
|
+
aggregate.delete_all
|
26
|
+
counters.delete_all
|
27
|
+
end
|
28
|
+
|
29
|
+
def group(prefix)
|
30
|
+
group = Group.new(prefix)
|
31
|
+
yield group
|
32
|
+
end
|
33
|
+
|
34
|
+
# update prefix
|
35
|
+
def prefix=(new_prefix)
|
36
|
+
@prefix = new_prefix
|
37
|
+
aggregate.prefix = @prefix
|
38
|
+
end
|
39
|
+
|
40
|
+
def prefix
|
41
|
+
@prefix
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -2,6 +2,8 @@ module Librato
|
|
2
2
|
module Rails
|
3
3
|
|
4
4
|
class CounterCache
|
5
|
+
DEFAULT_SOURCE = '%%'
|
6
|
+
|
5
7
|
extend Forwardable
|
6
8
|
|
7
9
|
def_delegators :@cache, :empty?
|
@@ -9,32 +11,108 @@ module Librato
|
|
9
11
|
def initialize
|
10
12
|
@cache = {}
|
11
13
|
@lock = Mutex.new
|
14
|
+
@sporadics = {}
|
12
15
|
end
|
13
16
|
|
17
|
+
# Retrieve the current value for a given metric. This is a short
|
18
|
+
# form for convenience which only retrieves metrics with no custom
|
19
|
+
# source specified. For more options see #fetch.
|
20
|
+
#
|
21
|
+
# @param [String|Symbol] key metric name
|
22
|
+
# @return [Integer|Float] current value
|
14
23
|
def [](key)
|
15
|
-
@lock.synchronize
|
24
|
+
@lock.synchronize do
|
25
|
+
@cache[key.to_s][DEFAULT_SOURCE]
|
26
|
+
end
|
16
27
|
end
|
17
28
|
|
29
|
+
# removes all tracked metrics. note this removes all measurement
|
30
|
+
# data AND metric names any continuously tracked metrics will not
|
31
|
+
# report until they get another measurement
|
18
32
|
def delete_all
|
19
33
|
@lock.synchronize { @cache.clear }
|
20
34
|
end
|
21
35
|
|
36
|
+
|
37
|
+
def fetch(key, options={})
|
38
|
+
source = DEFAULT_SOURCE
|
39
|
+
if options[:source]
|
40
|
+
source = options[:source].to_s
|
41
|
+
end
|
42
|
+
@lock.synchronize do
|
43
|
+
return nil unless @cache[key.to_s]
|
44
|
+
@cache[key.to_s][source]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# transfer all measurements to queue and reset internal status
|
22
49
|
def flush_to(queue)
|
23
50
|
counts = nil
|
24
51
|
@lock.synchronize do
|
25
|
-
|
26
|
-
|
52
|
+
# work off of a duplicate data set so we block for
|
53
|
+
# as little time as possible
|
54
|
+
counts = Marshal.load(Marshal.dump(@cache))
|
55
|
+
reset_cache
|
27
56
|
end
|
28
|
-
counts.each do |key,
|
29
|
-
|
57
|
+
counts.each do |key, data|
|
58
|
+
data.each do |source, value|
|
59
|
+
if source == DEFAULT_SOURCE
|
60
|
+
queue.add key => value
|
61
|
+
else
|
62
|
+
queue.add key => {:value => value, :source => source}
|
63
|
+
end
|
64
|
+
end
|
30
65
|
end
|
31
66
|
end
|
32
67
|
|
33
|
-
|
68
|
+
# Increment a given metric
|
69
|
+
#
|
70
|
+
# @example Increment metric 'foo' by 1
|
71
|
+
# increment :foo
|
72
|
+
#
|
73
|
+
# @example Increment metric 'bar' by 2
|
74
|
+
# increment :bar, :by => 2
|
75
|
+
#
|
76
|
+
# @example Increment metric 'foo' by 1 with a custom source
|
77
|
+
# increment :foo, :source => user.id
|
78
|
+
#
|
79
|
+
def increment(counter, options={})
|
34
80
|
counter = counter.to_s
|
81
|
+
if options.is_a?(Fixnum)
|
82
|
+
# suppport legacy style
|
83
|
+
options = {:by => options}
|
84
|
+
end
|
85
|
+
by = options[:by] || 1
|
86
|
+
source = DEFAULT_SOURCE
|
87
|
+
if options[:source]
|
88
|
+
source = options[:source].to_s
|
89
|
+
end
|
90
|
+
if options[:sporadic]
|
91
|
+
make_sporadic(counter, source)
|
92
|
+
end
|
35
93
|
@lock.synchronize do
|
36
|
-
@cache[counter] ||=
|
37
|
-
@cache[counter]
|
94
|
+
@cache[counter] ||= {}
|
95
|
+
@cache[counter][source] ||= 0
|
96
|
+
@cache[counter][source] += by
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def make_sporadic(metric, source)
|
103
|
+
@sporadics[metric] ||= Set.new
|
104
|
+
@sporadics[metric] << source
|
105
|
+
end
|
106
|
+
|
107
|
+
def reset_cache
|
108
|
+
# remove any source/metric pairs that aren't continuous
|
109
|
+
@sporadics.each do |key, sources|
|
110
|
+
sources.each { |source| @cache[key].delete(source) }
|
111
|
+
end
|
112
|
+
@sporadics.clear
|
113
|
+
# reset all continuous source/metric pairs to 0
|
114
|
+
@cache.each_key do |key|
|
115
|
+
@cache[key].each_key { |source| @cache[key][source] = 0 }
|
38
116
|
end
|
39
117
|
end
|
40
118
|
|
@@ -8,9 +8,7 @@ module Librato
|
|
8
8
|
initializer 'librato_rails.setup' do |app|
|
9
9
|
# don't start in test mode or in the console
|
10
10
|
unless ::Rails.env.test? || defined?(::Rails::Console)
|
11
|
-
Librato::Rails.setup
|
12
|
-
|
13
|
-
app.middleware.use Librato::Rack::Middleware
|
11
|
+
Librato::Rails.setup(app)
|
14
12
|
end
|
15
13
|
end
|
16
14
|
end
|
data/lib/librato/rails.rb
CHANGED
@@ -7,6 +7,7 @@ require 'librato/metrics'
|
|
7
7
|
|
8
8
|
require 'librato/rack'
|
9
9
|
require 'librato/rails/aggregator'
|
10
|
+
require 'librato/rails/collector'
|
10
11
|
require 'librato/rails/counter_cache'
|
11
12
|
require 'librato/rails/group'
|
12
13
|
require 'librato/rails/worker'
|
@@ -28,33 +29,23 @@ module Librato
|
|
28
29
|
mattr_accessor :user
|
29
30
|
mattr_accessor :token
|
30
31
|
mattr_accessor :flush_interval
|
31
|
-
mattr_accessor :prefix
|
32
32
|
mattr_accessor :source_pids
|
33
33
|
|
34
34
|
# config defaults
|
35
35
|
self.flush_interval = 60 # seconds
|
36
|
-
self.source_pids =
|
36
|
+
self.source_pids = false # append process id to the source?
|
37
37
|
|
38
|
-
|
39
|
-
def_delegators :aggregate, :
|
38
|
+
# a collector instance handles all measurement addition/storage
|
39
|
+
def_delegators :collector, :aggregate, :counters, :delete_all, :group, :increment,
|
40
|
+
:measure, :prefix, :prefix=, :timing
|
40
41
|
|
41
42
|
class << self
|
42
43
|
|
43
|
-
# access to internal aggregator object
|
44
|
-
def aggregate
|
45
|
-
@aggregator_cache ||= Aggregator.new(:prefix => self.prefix)
|
46
|
-
end
|
47
|
-
|
48
44
|
# set custom api endpoint
|
49
45
|
def api_endpoint=(endpoint)
|
50
46
|
@api_endpoint = endpoint
|
51
47
|
end
|
52
48
|
|
53
|
-
# access to client instance
|
54
|
-
def client
|
55
|
-
@client ||= prepare_client
|
56
|
-
end
|
57
|
-
|
58
49
|
# detect / update configuration
|
59
50
|
def check_config
|
60
51
|
if self.config_file && File.exists?(self.config_file)
|
@@ -80,15 +71,14 @@ module Librato
|
|
80
71
|
end
|
81
72
|
end
|
82
73
|
|
83
|
-
# access to
|
84
|
-
def
|
85
|
-
@
|
74
|
+
# access to client instance
|
75
|
+
def client
|
76
|
+
@client ||= prepare_client
|
86
77
|
end
|
87
|
-
|
88
|
-
#
|
89
|
-
def
|
90
|
-
|
91
|
-
counters.delete_all
|
78
|
+
|
79
|
+
# collector instance which is tracking all measurement additions
|
80
|
+
def collector
|
81
|
+
@collector ||= Collector.new
|
92
82
|
end
|
93
83
|
|
94
84
|
# send all current data to Metrics
|
@@ -101,12 +91,7 @@ module Librato
|
|
101
91
|
logger.debug queue.queued
|
102
92
|
queue.submit unless queue.empty?
|
103
93
|
rescue Exception => error
|
104
|
-
logger.error "[librato-rails] submission failed permanently
|
105
|
-
end
|
106
|
-
|
107
|
-
def group(prefix)
|
108
|
-
group = Group.new(prefix)
|
109
|
-
yield group
|
94
|
+
logger.error "[librato-rails] submission failed permanently: #{error}"
|
110
95
|
end
|
111
96
|
|
112
97
|
def logger
|
@@ -119,16 +104,13 @@ module Librato
|
|
119
104
|
end
|
120
105
|
|
121
106
|
# run once during Rails startup sequence
|
122
|
-
def setup
|
107
|
+
def setup(app)
|
123
108
|
check_config
|
124
|
-
|
109
|
+
return unless credentials_present?
|
125
110
|
logger.info "[librato-rails] starting up with #{app_server}..."
|
126
111
|
@pid = $$
|
127
|
-
|
128
|
-
|
129
|
-
else
|
130
|
-
start_worker # start immediately
|
131
|
-
end
|
112
|
+
app.middleware.use Librato::Rack::Middleware
|
113
|
+
start_worker unless forking_server?
|
132
114
|
end
|
133
115
|
|
134
116
|
def source
|
@@ -169,6 +151,10 @@ module Librato
|
|
169
151
|
:other
|
170
152
|
end
|
171
153
|
end
|
154
|
+
|
155
|
+
def credentials_present?
|
156
|
+
self.user && self.token
|
157
|
+
end
|
172
158
|
|
173
159
|
def forking_server?
|
174
160
|
FORKING_SERVERS.include?(app_server)
|