afstatsd 0.0.1 → 0.0.2
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/example/example.rb +69 -69
- data/lib/afstatsd/statsd_metrics.rb +2 -5
- data/lib/afstatsd.rb +281 -281
- metadata +2 -2
data/example/example.rb
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
require 'afstatsd'
|
|
2
|
-
|
|
3
|
-
#$statsd = Statsd.new 'statsd_server.my_company.com', 8125, 20
|
|
4
|
-
|
|
5
|
-
$statsd = Statsd.new # use defaults
|
|
6
|
-
$statsd.namespace = 'test.ruby'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
$statsd.increment 'counter1'
|
|
10
|
-
$statsd.increment 'counter1'
|
|
11
|
-
$statsd.decrement 'counter1' #counters accumulate
|
|
12
|
-
|
|
13
|
-
$statsd.gauge 'gauge1', 1024
|
|
14
|
-
$statsd.gauge 'gauge1', 1025
|
|
15
|
-
$statsd.gauge 'gauge1', 1026
|
|
16
|
-
$statsd.gauge 'gauge1', 1027
|
|
17
|
-
$statsd.gauge 'gauge1', 1028 # gauges get averaged when aggregated
|
|
18
|
-
|
|
19
|
-
$statsd.time('timing1' ){sleep 0.01}
|
|
20
|
-
$statsd.time('timing1' ){sleep 0.02}
|
|
21
|
-
$statsd.time('timing1' ){sleep 0.03}
|
|
22
|
-
$statsd.time('timing1' ){sleep 0.04} # timings get averaged when aggregated
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
=begin
|
|
26
|
-
|
|
27
|
-
100.times do
|
|
28
|
-
#$statsd.increment 'sampled', 0.1, 'sampled'
|
|
29
|
-
$statsd.increment 'sampled'
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
$statsd.set 'set1', 1099, "ez"
|
|
33
|
-
|
|
34
|
-
for i in 10..19 do
|
|
35
|
-
$statsd.increment "counter#{i}" # create a group of counters
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
1000.times do
|
|
39
|
-
$statsd.increment 'fast' # don't do this if aggregation is off
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# In this test program, this will give the aggregator time to run.
|
|
43
|
-
15.times do
|
|
44
|
-
sleep 2
|
|
45
|
-
$statsd.increment 'slow'
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
=end
|
|
49
|
-
|
|
50
|
-
=begin
|
|
51
|
-
# test for thread safety
|
|
52
|
-
threads = []
|
|
53
|
-
start = Time.now
|
|
54
|
-
for i in 0..9 do
|
|
55
|
-
threads << Thread.new(i) do |j|
|
|
56
|
-
start = Time.now
|
|
57
|
-
1000000.times do
|
|
58
|
-
$statsd.increment 'inthethread'
|
|
59
|
-
# sleep(0.01)
|
|
60
|
-
end
|
|
61
|
-
puts "thread #{j} says: I took #{((Time.now - start)*1000).round} ms"
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
threads.each { |t| t.join }
|
|
65
|
-
|
|
66
|
-
puts "total time: #{((Time.now - start)*1000).round} ms"
|
|
67
|
-
=end
|
|
68
|
-
|
|
69
|
-
puts "#{$statsd.dropped} messages dropped"
|
|
1
|
+
require 'afstatsd'
|
|
2
|
+
|
|
3
|
+
#$statsd = Statsd.new 'statsd_server.my_company.com', 8125, 20
|
|
4
|
+
|
|
5
|
+
$statsd = Statsd.new # use defaults
|
|
6
|
+
$statsd.namespace = 'test.ruby'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
$statsd.increment 'counter1'
|
|
10
|
+
$statsd.increment 'counter1'
|
|
11
|
+
$statsd.decrement 'counter1' #counters accumulate
|
|
12
|
+
|
|
13
|
+
$statsd.gauge 'gauge1', 1024
|
|
14
|
+
$statsd.gauge 'gauge1', 1025
|
|
15
|
+
$statsd.gauge 'gauge1', 1026
|
|
16
|
+
$statsd.gauge 'gauge1', 1027
|
|
17
|
+
$statsd.gauge 'gauge1', 1028 # gauges get averaged when aggregated
|
|
18
|
+
|
|
19
|
+
$statsd.time('timing1' ){sleep 0.01}
|
|
20
|
+
$statsd.time('timing1' ){sleep 0.02}
|
|
21
|
+
$statsd.time('timing1' ){sleep 0.03}
|
|
22
|
+
$statsd.time('timing1' ){sleep 0.04} # timings get averaged when aggregated
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
=begin
|
|
26
|
+
|
|
27
|
+
100.times do
|
|
28
|
+
#$statsd.increment 'sampled', 0.1, 'sampled'
|
|
29
|
+
$statsd.increment 'sampled'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
$statsd.set 'set1', 1099, "ez"
|
|
33
|
+
|
|
34
|
+
for i in 10..19 do
|
|
35
|
+
$statsd.increment "counter#{i}" # create a group of counters
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
1000.times do
|
|
39
|
+
$statsd.increment 'fast' # don't do this if aggregation is off
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# In this test program, this will give the aggregator time to run.
|
|
43
|
+
15.times do
|
|
44
|
+
sleep 2
|
|
45
|
+
$statsd.increment 'slow'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
=end
|
|
49
|
+
|
|
50
|
+
=begin
|
|
51
|
+
# test for thread safety
|
|
52
|
+
threads = []
|
|
53
|
+
start = Time.now
|
|
54
|
+
for i in 0..9 do
|
|
55
|
+
threads << Thread.new(i) do |j|
|
|
56
|
+
start = Time.now
|
|
57
|
+
1000000.times do
|
|
58
|
+
$statsd.increment 'inthethread'
|
|
59
|
+
# sleep(0.01)
|
|
60
|
+
end
|
|
61
|
+
puts "thread #{j} says: I took #{((Time.now - start)*1000).round} ms"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
threads.each { |t| t.join }
|
|
65
|
+
|
|
66
|
+
puts "total time: #{((Time.now - start)*1000).round} ms"
|
|
67
|
+
=end
|
|
68
|
+
|
|
69
|
+
puts "#{$statsd.dropped} messages dropped"
|
|
@@ -44,18 +44,15 @@ class GMetric < Metric
|
|
|
44
44
|
@name = name
|
|
45
45
|
@value = value
|
|
46
46
|
@message = msg
|
|
47
|
-
@count = 1
|
|
48
47
|
end
|
|
49
48
|
|
|
50
49
|
def aggregate(value)
|
|
51
|
-
@value
|
|
52
|
-
@count += 1
|
|
50
|
+
@value = value #overwrite
|
|
53
51
|
end
|
|
54
52
|
|
|
55
53
|
def to_s
|
|
56
|
-
avg = @value / @count
|
|
57
54
|
if @message == "" then m = "" else m = "|#{@message}" end
|
|
58
|
-
"#{name}:#{
|
|
55
|
+
"#{name}:#{@value}|g#{m}"
|
|
59
56
|
end
|
|
60
57
|
|
|
61
58
|
end
|
data/lib/afstatsd.rb
CHANGED
|
@@ -1,281 +1,281 @@
|
|
|
1
|
-
require 'socket'
|
|
2
|
-
require 'forwardable'
|
|
3
|
-
require 'rubygems'
|
|
4
|
-
require 'posix_mq'
|
|
5
|
-
require 'afstatsd/statsd_metrics'
|
|
6
|
-
require 'afstatsd/statsd_aggregator'
|
|
7
|
-
require 'monitor'
|
|
8
|
-
require 'fcntl'
|
|
9
|
-
|
|
10
|
-
# = Statsd: A Statsd client (https://github.com/etsy/statsd)
|
|
11
|
-
#
|
|
12
|
-
# @example Set up a global Statsd client for a server on localhost:9125,
|
|
13
|
-
# aggregate 20 seconds worth of metrics
|
|
14
|
-
# $statsd = Statsd.new 'localhost', 8125, 20
|
|
15
|
-
# @example Send some stats
|
|
16
|
-
# $statsd.increment 'garets'
|
|
17
|
-
# $statsd.timing 'glork', 320
|
|
18
|
-
# $statsd.gauge 'bork', 100
|
|
19
|
-
# @example Use {#time} to time the execution of a block
|
|
20
|
-
# $statsd.time('account.activate') { @account.activate! }
|
|
21
|
-
# @example Create a namespaced statsd client and increment 'account.activate'
|
|
22
|
-
# statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}
|
|
23
|
-
# statsd.increment 'activate'
|
|
24
|
-
#
|
|
25
|
-
# Statsd instances are thread safe for general usage, by using a thread local
|
|
26
|
-
# UDPSocket and carrying no state. The attributes are stateful, and are not
|
|
27
|
-
# mutexed, it is expected that users will not change these at runtime in
|
|
28
|
-
# threaded environments. If users require such use cases, it is recommend that
|
|
29
|
-
# users either mutex around their Statsd object, or create separate objects for
|
|
30
|
-
# each namespace / host+port combination.
|
|
31
|
-
class Statsd
|
|
32
|
-
|
|
33
|
-
# A namespace to prepend to all statsd calls.
|
|
34
|
-
attr_reader :namespace
|
|
35
|
-
|
|
36
|
-
# StatsD host. Defaults to 127.0.0.1. Only used with UDP transport
|
|
37
|
-
attr_reader :host
|
|
38
|
-
|
|
39
|
-
# StatsD port. Defaults to 8125. Only used with UDP transport
|
|
40
|
-
attr_reader :port
|
|
41
|
-
|
|
42
|
-
# StatsD namespace prefix, generated from #namespace
|
|
43
|
-
attr_reader :prefix
|
|
44
|
-
|
|
45
|
-
# a postfix to append to all metrics
|
|
46
|
-
attr_reader :postfix
|
|
47
|
-
|
|
48
|
-
# count of messages that were dropped due to transmit error
|
|
49
|
-
attr_reader :dropped
|
|
50
|
-
|
|
51
|
-
class << self
|
|
52
|
-
# Set to a standard logger instance to enable debug logging.
|
|
53
|
-
attr_accessor :logger
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# @param [String] host your statsd host
|
|
57
|
-
# @param [Integer] port your statsd port
|
|
58
|
-
# @param [Integer] interval for aggregatore
|
|
59
|
-
def initialize(host = '127.0.0.1', port = 8125, interval = 20)
|
|
60
|
-
self.host, self.port = host, port
|
|
61
|
-
@prefix = nil
|
|
62
|
-
@postfix = nil
|
|
63
|
-
@aggregator = StatsdAggregator.new(interval)
|
|
64
|
-
set_transport :mq_transport
|
|
65
|
-
self.aggregating = true unless interval == 0
|
|
66
|
-
@dropped = 0
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# @param [method] The ruby symbol for the method that gets called to send
|
|
70
|
-
# one metric to the server. eg: set_transport :udp_transport
|
|
71
|
-
def set_transport(transport)
|
|
72
|
-
@transport = method(transport)
|
|
73
|
-
@aggregator.transport = @transport # aggregator needs to know
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# @param [Boolean] Turn aggregation on or off
|
|
77
|
-
def aggregating= (should_aggregate)
|
|
78
|
-
if should_aggregate
|
|
79
|
-
@aggregator.start(@transport)
|
|
80
|
-
else
|
|
81
|
-
@aggregator.stop
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# is the aggregator running?
|
|
86
|
-
def aggregating
|
|
87
|
-
@aggregator.running
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# @attribute [w] namespace
|
|
91
|
-
# Writes are not thread safe.
|
|
92
|
-
def namespace=(namespace)
|
|
93
|
-
@namespace = namespace
|
|
94
|
-
@prefix = "#{namespace}."
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# @attribute [w] postfix
|
|
98
|
-
# A value to be appended to the stat name after a '.'. If the value is
|
|
99
|
-
# blank then the postfix will be reset to nil (rather than to '.').
|
|
100
|
-
def postfix=(pf)
|
|
101
|
-
case pf
|
|
102
|
-
when nil, false, '' then @postfix = nil
|
|
103
|
-
else @postfix = ".#{pf}"
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# @attribute [w] host
|
|
108
|
-
# Writes are not thread safe.
|
|
109
|
-
def host=(host)
|
|
110
|
-
@host = host || '127.0.0.1'
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# @attribute [w] port
|
|
114
|
-
# Writes are not thread safe.
|
|
115
|
-
def port=(port)
|
|
116
|
-
@port = port || 8125
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Sends an increment (count = 1) for the given stat to the statsd server.
|
|
120
|
-
#
|
|
121
|
-
# @param [String] stat stat name
|
|
122
|
-
# @param [Numeric] sample_rate sample rate, 1 for always
|
|
123
|
-
# @param [String] optional note (AppFirst extension to StatsD)
|
|
124
|
-
# @see #count
|
|
125
|
-
def increment(stat, sample_rate=1, note="")
|
|
126
|
-
count stat, 1, sample_rate, note
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# Sends a decrement (count = -1) for the given stat to the statsd server.
|
|
130
|
-
#
|
|
131
|
-
# @param [String] stat stat name
|
|
132
|
-
# @param [Numeric] sample_rate sample rate, 1 for always
|
|
133
|
-
# @param [String] optional note (AppFirst extension to StatsD)
|
|
134
|
-
# @see #count
|
|
135
|
-
def decrement(stat, sample_rate=1, note="")
|
|
136
|
-
count stat, -1, sample_rate, note
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Sends an arbitrary count for the given stat to the statsd server.
|
|
140
|
-
#
|
|
141
|
-
# @param [String] stat stat name
|
|
142
|
-
# @param [Integer] count count
|
|
143
|
-
# @param [Numeric] sample_rate sample rate, 1 for always
|
|
144
|
-
# @param [String] optional note (AppFirst extension to StatsD)
|
|
145
|
-
def count(stat, count, sample_rate=1, note="")
|
|
146
|
-
if sample_rate == 1 or rand < sample_rate
|
|
147
|
-
send_metric StatsdMetrics::CMetric.new(expand_name(stat), count, sample_rate, note)
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# Sends an arbitary gauge value for the given stat to the statsd server.
|
|
152
|
-
#
|
|
153
|
-
# This is useful for recording things like available disk space,
|
|
154
|
-
# memory usage, and the like, which have different semantics than
|
|
155
|
-
# counters.
|
|
156
|
-
#
|
|
157
|
-
# @param [String] stat stat name.
|
|
158
|
-
# @param [Numeric] value gauge value.
|
|
159
|
-
# @param [String] optional note (AppFirst extension to StatsD)
|
|
160
|
-
# @example Report the current user count:
|
|
161
|
-
# $statsd.gauge('user.count', User.count)
|
|
162
|
-
def gauge(stat, value, note="")
|
|
163
|
-
send_metric StatsdMetrics::GMetric.new(expand_name(stat), value, note)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
# Sends an arbitary set value for the given stat to the statsd server.
|
|
167
|
-
#
|
|
168
|
-
# This is for recording counts of unique events, which are useful to
|
|
169
|
-
# see on graphs to correlate to other values. For example, a deployment
|
|
170
|
-
# might get recorded as a set, and be drawn as annotations on a CPU history
|
|
171
|
-
# graph.
|
|
172
|
-
#
|
|
173
|
-
# @param [String] stat stat name.
|
|
174
|
-
# @param [Numeric] value event value.
|
|
175
|
-
# @param [String] optional note (AppFirst extension to StatsD)
|
|
176
|
-
# @example Report a deployment happening:
|
|
177
|
-
# $statsd.set('deployment', DEPLOYMENT_EVENT_CODE)
|
|
178
|
-
def set(stat, value, note="")
|
|
179
|
-
send_metric StatsdMetrics::SMetric.new(expand_name(stat), value, note)
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# Sends a timing (in ms) for the given stat to the statsd server. The
|
|
183
|
-
# sample_rate determines what percentage of the time this report is sent. The
|
|
184
|
-
# statsd server then uses the sample_rate to correctly track the average
|
|
185
|
-
# timing for the stat.
|
|
186
|
-
#
|
|
187
|
-
# @param [String] stat stat name
|
|
188
|
-
# @param [Integer] ms timing in milliseconds
|
|
189
|
-
# @param [Numeric] sample_rate sample rate, 1 for always
|
|
190
|
-
# @param [String] optional note (AppFirst extension to StatsD)
|
|
191
|
-
def timing(stat, ms, sample_rate=1, note="")
|
|
192
|
-
if sample_rate == 1 or rand < sample_rate
|
|
193
|
-
send_metric StatsdMetrics::TMetric.new(expand_name(stat), ms, sample_rate, note)
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
# Reports execution time of the provided block using {#timing}.
|
|
198
|
-
#
|
|
199
|
-
# @param [String] stat stat name
|
|
200
|
-
# @param [Numeric] sample_rate sample rate, 1 for always
|
|
201
|
-
# @param [String] optional note (AppFirst extension to StatsD)
|
|
202
|
-
# @yield The operation to be timed
|
|
203
|
-
# @see #timing
|
|
204
|
-
# @example Report the time (in ms) taken to activate an account
|
|
205
|
-
# $statsd.time('account.activate') { @account.activate! }
|
|
206
|
-
def time(stat, sample_rate=1, note="")
|
|
207
|
-
start = Time.now
|
|
208
|
-
result = yield
|
|
209
|
-
timing(stat, ((Time.now - start) * 1000).round, sample_rate, note)
|
|
210
|
-
result
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
protected
|
|
214
|
-
|
|
215
|
-
def send_metric(metric)
|
|
216
|
-
# All the metric types above funnel to here. We will send or aggregate.
|
|
217
|
-
if aggregating
|
|
218
|
-
@aggregator.add metric
|
|
219
|
-
else
|
|
220
|
-
@transport.call(metric)
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def expand_name(name)
|
|
225
|
-
# Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
|
|
226
|
-
name = name.to_s.gsub('::', '.').tr(':|@', '_')
|
|
227
|
-
"#{prefix}#{name}#{postfix}"
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def udp_transport(metric)
|
|
231
|
-
#puts "socket < #{metric}\n"
|
|
232
|
-
self.class.logger.debug { "Statsd: #{metric}" } if self.class.logger
|
|
233
|
-
socket.send(metric.to_s, 0, @host, @port)
|
|
234
|
-
rescue => boom
|
|
235
|
-
#puts "socket send error"
|
|
236
|
-
@dropped +=1
|
|
237
|
-
self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
|
|
238
|
-
nil
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
STATSD_SEVERITY = 3
|
|
242
|
-
def mq_transport(metric)
|
|
243
|
-
#puts "MQ < #{metric}\n" #debug
|
|
244
|
-
self.class.logger.debug { "Statsd: #{metric}" } if self.class.logger
|
|
245
|
-
if not @mq
|
|
246
|
-
begin
|
|
247
|
-
@mq = POSIX_MQ.new("/afcollectorapi", Fcntl::O_WRONLY | Fcntl::O_NONBLOCK)
|
|
248
|
-
rescue => boom
|
|
249
|
-
self.class.logger.debug { "Statsd: MQ open error #{boom.class} #{boom}" } if self.class.logger
|
|
250
|
-
# failed to open MQ. Fall back to UPD transport. Note: Current message will be lost.
|
|
251
|
-
@dropped += 1
|
|
252
|
-
# puts "fallback to udp"
|
|
253
|
-
set_transport :udp_transport
|
|
254
|
-
return nil
|
|
255
|
-
end
|
|
256
|
-
end
|
|
257
|
-
begin
|
|
258
|
-
@mq.send(metric.to_s, STATSD_SEVERITY)
|
|
259
|
-
rescue => boom
|
|
260
|
-
# just drop it on the floor
|
|
261
|
-
@dropped += 1
|
|
262
|
-
#puts "MQ send error: #{boom.class} #{boom}"
|
|
263
|
-
self.class.logger.error { "Statsd: MQ Send Error#{boom.class} #{boom}" } if self.class.logger
|
|
264
|
-
nil
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
def both_transport(metric)
|
|
269
|
-
mq_transport(metric)
|
|
270
|
-
udp_transport(metric)
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
private
|
|
274
|
-
|
|
275
|
-
def socket
|
|
276
|
-
Thread.current[:statsd_socket] ||= UDPSocket.new
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
end # class Statsd
|
|
280
|
-
|
|
281
|
-
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'forwardable'
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
require 'posix_mq'
|
|
5
|
+
require 'afstatsd/statsd_metrics'
|
|
6
|
+
require 'afstatsd/statsd_aggregator'
|
|
7
|
+
require 'monitor'
|
|
8
|
+
require 'fcntl'
|
|
9
|
+
|
|
10
|
+
# = Statsd: A Statsd client (https://github.com/etsy/statsd)
|
|
11
|
+
#
|
|
12
|
+
# @example Set up a global Statsd client for a server on localhost:9125,
|
|
13
|
+
# aggregate 20 seconds worth of metrics
|
|
14
|
+
# $statsd = Statsd.new 'localhost', 8125, 20
|
|
15
|
+
# @example Send some stats
|
|
16
|
+
# $statsd.increment 'garets'
|
|
17
|
+
# $statsd.timing 'glork', 320
|
|
18
|
+
# $statsd.gauge 'bork', 100
|
|
19
|
+
# @example Use {#time} to time the execution of a block
|
|
20
|
+
# $statsd.time('account.activate') { @account.activate! }
|
|
21
|
+
# @example Create a namespaced statsd client and increment 'account.activate'
|
|
22
|
+
# statsd = Statsd.new('localhost').tap{|sd| sd.namespace = 'account'}
|
|
23
|
+
# statsd.increment 'activate'
|
|
24
|
+
#
|
|
25
|
+
# Statsd instances are thread safe for general usage, by using a thread local
|
|
26
|
+
# UDPSocket and carrying no state. The attributes are stateful, and are not
|
|
27
|
+
# mutexed, it is expected that users will not change these at runtime in
|
|
28
|
+
# threaded environments. If users require such use cases, it is recommend that
|
|
29
|
+
# users either mutex around their Statsd object, or create separate objects for
|
|
30
|
+
# each namespace / host+port combination.
|
|
31
|
+
class Statsd
|
|
32
|
+
|
|
33
|
+
# A namespace to prepend to all statsd calls.
|
|
34
|
+
attr_reader :namespace
|
|
35
|
+
|
|
36
|
+
# StatsD host. Defaults to 127.0.0.1. Only used with UDP transport
|
|
37
|
+
attr_reader :host
|
|
38
|
+
|
|
39
|
+
# StatsD port. Defaults to 8125. Only used with UDP transport
|
|
40
|
+
attr_reader :port
|
|
41
|
+
|
|
42
|
+
# StatsD namespace prefix, generated from #namespace
|
|
43
|
+
attr_reader :prefix
|
|
44
|
+
|
|
45
|
+
# a postfix to append to all metrics
|
|
46
|
+
attr_reader :postfix
|
|
47
|
+
|
|
48
|
+
# count of messages that were dropped due to transmit error
|
|
49
|
+
attr_reader :dropped
|
|
50
|
+
|
|
51
|
+
class << self
|
|
52
|
+
# Set to a standard logger instance to enable debug logging.
|
|
53
|
+
attr_accessor :logger
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @param [String] host your statsd host
|
|
57
|
+
# @param [Integer] port your statsd port
|
|
58
|
+
# @param [Integer] interval for aggregatore
|
|
59
|
+
def initialize(host = '127.0.0.1', port = 8125, interval = 20)
|
|
60
|
+
self.host, self.port = host, port
|
|
61
|
+
@prefix = nil
|
|
62
|
+
@postfix = nil
|
|
63
|
+
@aggregator = StatsdAggregator.new(interval)
|
|
64
|
+
set_transport :mq_transport
|
|
65
|
+
self.aggregating = true unless interval == 0
|
|
66
|
+
@dropped = 0
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @param [method] The ruby symbol for the method that gets called to send
|
|
70
|
+
# one metric to the server. eg: set_transport :udp_transport
|
|
71
|
+
def set_transport(transport)
|
|
72
|
+
@transport = method(transport)
|
|
73
|
+
@aggregator.transport = @transport # aggregator needs to know
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @param [Boolean] Turn aggregation on or off
|
|
77
|
+
def aggregating= (should_aggregate)
|
|
78
|
+
if should_aggregate
|
|
79
|
+
@aggregator.start(@transport)
|
|
80
|
+
else
|
|
81
|
+
@aggregator.stop
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# is the aggregator running?
|
|
86
|
+
def aggregating
|
|
87
|
+
@aggregator.running
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @attribute [w] namespace
|
|
91
|
+
# Writes are not thread safe.
|
|
92
|
+
def namespace=(namespace)
|
|
93
|
+
@namespace = namespace
|
|
94
|
+
@prefix = "#{namespace}."
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @attribute [w] postfix
|
|
98
|
+
# A value to be appended to the stat name after a '.'. If the value is
|
|
99
|
+
# blank then the postfix will be reset to nil (rather than to '.').
|
|
100
|
+
def postfix=(pf)
|
|
101
|
+
case pf
|
|
102
|
+
when nil, false, '' then @postfix = nil
|
|
103
|
+
else @postfix = ".#{pf}"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @attribute [w] host
|
|
108
|
+
# Writes are not thread safe.
|
|
109
|
+
def host=(host)
|
|
110
|
+
@host = host || '127.0.0.1'
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @attribute [w] port
|
|
114
|
+
# Writes are not thread safe.
|
|
115
|
+
def port=(port)
|
|
116
|
+
@port = port || 8125
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Sends an increment (count = 1) for the given stat to the statsd server.
|
|
120
|
+
#
|
|
121
|
+
# @param [String] stat stat name
|
|
122
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
|
123
|
+
# @param [String] optional note (AppFirst extension to StatsD)
|
|
124
|
+
# @see #count
|
|
125
|
+
def increment(stat, sample_rate=1, note="")
|
|
126
|
+
count stat, 1, sample_rate, note
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Sends a decrement (count = -1) for the given stat to the statsd server.
|
|
130
|
+
#
|
|
131
|
+
# @param [String] stat stat name
|
|
132
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
|
133
|
+
# @param [String] optional note (AppFirst extension to StatsD)
|
|
134
|
+
# @see #count
|
|
135
|
+
def decrement(stat, sample_rate=1, note="")
|
|
136
|
+
count stat, -1, sample_rate, note
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Sends an arbitrary count for the given stat to the statsd server.
|
|
140
|
+
#
|
|
141
|
+
# @param [String] stat stat name
|
|
142
|
+
# @param [Integer] count count
|
|
143
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
|
144
|
+
# @param [String] optional note (AppFirst extension to StatsD)
|
|
145
|
+
def count(stat, count, sample_rate=1, note="")
|
|
146
|
+
if sample_rate == 1 or rand < sample_rate
|
|
147
|
+
send_metric StatsdMetrics::CMetric.new(expand_name(stat), count, sample_rate, note)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Sends an arbitary gauge value for the given stat to the statsd server.
|
|
152
|
+
#
|
|
153
|
+
# This is useful for recording things like available disk space,
|
|
154
|
+
# memory usage, and the like, which have different semantics than
|
|
155
|
+
# counters.
|
|
156
|
+
#
|
|
157
|
+
# @param [String] stat stat name.
|
|
158
|
+
# @param [Numeric] value gauge value.
|
|
159
|
+
# @param [String] optional note (AppFirst extension to StatsD)
|
|
160
|
+
# @example Report the current user count:
|
|
161
|
+
# $statsd.gauge('user.count', User.count)
|
|
162
|
+
def gauge(stat, value, note="")
|
|
163
|
+
send_metric StatsdMetrics::GMetric.new(expand_name(stat), value, note)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Sends an arbitary set value for the given stat to the statsd server.
|
|
167
|
+
#
|
|
168
|
+
# This is for recording counts of unique events, which are useful to
|
|
169
|
+
# see on graphs to correlate to other values. For example, a deployment
|
|
170
|
+
# might get recorded as a set, and be drawn as annotations on a CPU history
|
|
171
|
+
# graph.
|
|
172
|
+
#
|
|
173
|
+
# @param [String] stat stat name.
|
|
174
|
+
# @param [Numeric] value event value.
|
|
175
|
+
# @param [String] optional note (AppFirst extension to StatsD)
|
|
176
|
+
# @example Report a deployment happening:
|
|
177
|
+
# $statsd.set('deployment', DEPLOYMENT_EVENT_CODE)
|
|
178
|
+
def set(stat, value, note="")
|
|
179
|
+
send_metric StatsdMetrics::SMetric.new(expand_name(stat), value, note)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Sends a timing (in ms) for the given stat to the statsd server. The
|
|
183
|
+
# sample_rate determines what percentage of the time this report is sent. The
|
|
184
|
+
# statsd server then uses the sample_rate to correctly track the average
|
|
185
|
+
# timing for the stat.
|
|
186
|
+
#
|
|
187
|
+
# @param [String] stat stat name
|
|
188
|
+
# @param [Integer] ms timing in milliseconds
|
|
189
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
|
190
|
+
# @param [String] optional note (AppFirst extension to StatsD)
|
|
191
|
+
def timing(stat, ms, sample_rate=1, note="")
|
|
192
|
+
if sample_rate == 1 or rand < sample_rate
|
|
193
|
+
send_metric StatsdMetrics::TMetric.new(expand_name(stat), ms, sample_rate, note)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Reports execution time of the provided block using {#timing}.
|
|
198
|
+
#
|
|
199
|
+
# @param [String] stat stat name
|
|
200
|
+
# @param [Numeric] sample_rate sample rate, 1 for always
|
|
201
|
+
# @param [String] optional note (AppFirst extension to StatsD)
|
|
202
|
+
# @yield The operation to be timed
|
|
203
|
+
# @see #timing
|
|
204
|
+
# @example Report the time (in ms) taken to activate an account
|
|
205
|
+
# $statsd.time('account.activate') { @account.activate! }
|
|
206
|
+
def time(stat, sample_rate=1, note="")
|
|
207
|
+
start = Time.now
|
|
208
|
+
result = yield
|
|
209
|
+
timing(stat, ((Time.now - start) * 1000).round, sample_rate, note)
|
|
210
|
+
result
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
protected
|
|
214
|
+
|
|
215
|
+
def send_metric(metric)
|
|
216
|
+
# All the metric types above funnel to here. We will send or aggregate.
|
|
217
|
+
if aggregating
|
|
218
|
+
@aggregator.add metric
|
|
219
|
+
else
|
|
220
|
+
@transport.call(metric)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def expand_name(name)
|
|
225
|
+
# Replace Ruby module scoping with '.' and reserved chars (: | @) with underscores.
|
|
226
|
+
name = name.to_s.gsub('::', '.').tr(':|@', '_')
|
|
227
|
+
"#{prefix}#{name}#{postfix}"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def udp_transport(metric)
|
|
231
|
+
#puts "socket < #{metric}\n"
|
|
232
|
+
self.class.logger.debug { "Statsd: #{metric}" } if self.class.logger
|
|
233
|
+
socket.send(metric.to_s, 0, @host, @port)
|
|
234
|
+
rescue => boom
|
|
235
|
+
#puts "socket send error"
|
|
236
|
+
@dropped +=1
|
|
237
|
+
self.class.logger.error { "Statsd: #{boom.class} #{boom}" } if self.class.logger
|
|
238
|
+
nil
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
STATSD_SEVERITY = 3
|
|
242
|
+
def mq_transport(metric)
|
|
243
|
+
#puts "MQ < #{metric}\n" #debug
|
|
244
|
+
self.class.logger.debug { "Statsd: #{metric}" } if self.class.logger
|
|
245
|
+
if not @mq
|
|
246
|
+
begin
|
|
247
|
+
@mq = POSIX_MQ.new("/afcollectorapi", Fcntl::O_WRONLY | Fcntl::O_NONBLOCK)
|
|
248
|
+
rescue => boom
|
|
249
|
+
self.class.logger.debug { "Statsd: MQ open error #{boom.class} #{boom}" } if self.class.logger
|
|
250
|
+
# failed to open MQ. Fall back to UPD transport. Note: Current message will be lost.
|
|
251
|
+
@dropped += 1
|
|
252
|
+
# puts "fallback to udp"
|
|
253
|
+
set_transport :udp_transport
|
|
254
|
+
return nil
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
begin
|
|
258
|
+
@mq.send(metric.to_s, STATSD_SEVERITY)
|
|
259
|
+
rescue => boom
|
|
260
|
+
# just drop it on the floor
|
|
261
|
+
@dropped += 1
|
|
262
|
+
#puts "MQ send error: #{boom.class} #{boom}"
|
|
263
|
+
self.class.logger.error { "Statsd: MQ Send Error#{boom.class} #{boom}" } if self.class.logger
|
|
264
|
+
nil
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def both_transport(metric)
|
|
269
|
+
mq_transport(metric)
|
|
270
|
+
udp_transport(metric)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
private
|
|
274
|
+
|
|
275
|
+
def socket
|
|
276
|
+
Thread.current[:statsd_socket] ||= UDPSocket.new
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
end # class Statsd
|
|
280
|
+
|
|
281
|
+
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: afstatsd
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.2
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2013-
|
|
12
|
+
date: 2013-03-06 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: posix_mq
|