librato-rails 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|