statful-client 1.0.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.
- checksums.yaml +7 -0
- data/lib/client.rb +316 -0
- data/lib/statful-client.rb +1 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2082d2f2574057de832307766202d6a492f4817c
|
4
|
+
data.tar.gz: b9326d3830adb06a4ab25123ca489a1238408a30
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5a09ee6befcecc28200828770b37a1cec711316121b691c617635ade92af7e75b8c0af4ff20d7a2f8bb4dec05d1bd67802e98f883f54664d5867023c647d3dd6
|
7
|
+
data.tar.gz: 0f9a337a23504ad89aac1a4b9ac0d9d5b8f9d731a297d42c3b836f4e527d75c23c14abd55029791675e7a959006f5d04e4badec6b2493016888db03c8bd28d04
|
data/lib/client.rb
ADDED
@@ -0,0 +1,316 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'delegate'
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
# Statful Client Instance
|
6
|
+
#
|
7
|
+
# @attr_reader config [Hash] Current client config
|
8
|
+
class StatfulClient
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
def new
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# Initialize the client
|
16
|
+
#
|
17
|
+
# @param [Hash] config Client bootstrap configuration
|
18
|
+
# @option config [String] :host Destination host *required*
|
19
|
+
# @option config [String] :port Destination port *required*
|
20
|
+
# @option config [String] :transport Transport protocol, one of (udp or http) *required*
|
21
|
+
# @option config [Integer] :timeout Timeout for http transport
|
22
|
+
# @option config [String] :token Authentication account token for http transport
|
23
|
+
# @option config [String] :app Global metric app tag
|
24
|
+
# @option config [TrueClass/FalseClass] :dryrun Enable dry-run mode
|
25
|
+
# @option config [Object] :logger Logger instance that supports debug (if dryrun is enabled) and error methods
|
26
|
+
# @option config [Hash] :tags Global list of metric tags
|
27
|
+
# @option config [Integer] :sample_rate Global sample rate (as a percentage), between: (1-100)
|
28
|
+
# @option config [String] :namespace Global default namespace
|
29
|
+
# @option config [Integer] :flush_size Buffer flush upper size limit
|
30
|
+
# @return [Object] The Statful client
|
31
|
+
def initialize(config = {})
|
32
|
+
user_config = MyHash[config].symbolize_keys
|
33
|
+
|
34
|
+
if !user_config.has_key?(:transport) || !%w(udp http).include?(user_config[:transport])
|
35
|
+
raise ArgumentError.new('Transport is missing or invalid')
|
36
|
+
end
|
37
|
+
|
38
|
+
if user_config[:transport] == 'http'
|
39
|
+
raise ArgumentError.new('Token is missing') if user_config[:token].nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
if user_config.has_key?(:sample_rate) && !user_config[:sample_rate].between?(1, 100)
|
43
|
+
raise ArgumentError.new('Sample rate is not within range (1-100)')
|
44
|
+
end
|
45
|
+
|
46
|
+
default_config = {
|
47
|
+
:host => 'api.statful.com',
|
48
|
+
:port => 443,
|
49
|
+
:transport => 'http',
|
50
|
+
:tags => {},
|
51
|
+
:sample_rate => 100,
|
52
|
+
:namespace => 'application',
|
53
|
+
:flush_size => 5
|
54
|
+
}
|
55
|
+
|
56
|
+
@config = default_config.merge(user_config)
|
57
|
+
@logger = @config[:logger]
|
58
|
+
@buffer = []
|
59
|
+
@http = Net::HTTP.new(@config[:host], @config[:port])
|
60
|
+
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
# Sends a timer
|
65
|
+
#
|
66
|
+
# @param name [String] Name of the timer
|
67
|
+
# @param value [Numeric] Value of the metric
|
68
|
+
# @param [Hash] options The options to apply to the metric
|
69
|
+
# @option options [Hash] :tags Tags to associate to the metric
|
70
|
+
# @option options [Array<String>] :agg List of aggregations to be applied by Statful
|
71
|
+
# @option options [Integer] :agg_freq Aggregation frequency in seconds
|
72
|
+
# @option options [String] :namespace Namespace of the metric
|
73
|
+
def timer(name, value, options = {})
|
74
|
+
tags = @config[:tags].merge({:unit => 'ms'})
|
75
|
+
tags = tags.merge(options[:tags]) unless options[:tags].nil?
|
76
|
+
|
77
|
+
aggregations = %w(avg p90 count)
|
78
|
+
aggregations.concat(options[:agg]) unless options[:agg].nil?
|
79
|
+
|
80
|
+
opts = {
|
81
|
+
:agg_freq => 10,
|
82
|
+
:namespace => 'application'
|
83
|
+
}.merge(MyHash[options].symbolize_keys)
|
84
|
+
|
85
|
+
opts[:tags] = tags
|
86
|
+
opts[:agg] = aggregations
|
87
|
+
|
88
|
+
_put("timer.#{name}", opts[:tags], value, opts[:agg], opts[:agg_freq], @config[:sample_rate], opts[:namespace])
|
89
|
+
end
|
90
|
+
|
91
|
+
# Sends a counter
|
92
|
+
#
|
93
|
+
# @param name [String] Name of the counter
|
94
|
+
# @param value [Numeric] Increment/Decrement value, this will be truncated with `to_int`
|
95
|
+
# @param [Hash] options The options to apply to the metric
|
96
|
+
# @option options [Hash] :tags Tags to associate to the metric
|
97
|
+
# @option options [Array<String>] :agg List of aggregations to be applied by the Statful
|
98
|
+
# @option options [Integer] :agg_freq Aggregation frequency in seconds
|
99
|
+
# @option options [String] :namespace Namespace of the metric
|
100
|
+
def counter(name, value, options = {})
|
101
|
+
tags = @config[:tags]
|
102
|
+
tags = tags.merge(options[:tags]) unless options[:tags].nil?
|
103
|
+
|
104
|
+
aggregations = %w(sum count)
|
105
|
+
aggregations.concat(options[:agg]) unless options[:agg].nil?
|
106
|
+
|
107
|
+
opts = {
|
108
|
+
:agg_freq => 10,
|
109
|
+
:namespace => 'application'
|
110
|
+
}.merge(MyHash[options].symbolize_keys)
|
111
|
+
|
112
|
+
opts[:tags] = tags
|
113
|
+
opts[:agg] = aggregations
|
114
|
+
|
115
|
+
_put("counter.#{name}", opts[:tags], value.to_i, opts[:agg], opts[:agg_freq], @config[:sample_rate], opts[:namespace])
|
116
|
+
end
|
117
|
+
|
118
|
+
# Sends a gauge
|
119
|
+
#
|
120
|
+
# @param name [String] Name of the gauge
|
121
|
+
# @param value [Numeric] Value of the metric
|
122
|
+
# @param [Hash] options The options to apply to the metric
|
123
|
+
# @option options [Hash] :tags Tags to associate to the metric
|
124
|
+
# @option options [Array<String>] :agg List of aggregations to be applied by Statful
|
125
|
+
# @option options [Integer] :agg_freq Aggregation frequency in seconds
|
126
|
+
# @option options [String] :namespace Namespace of the metric
|
127
|
+
def gauge(name, value, options = {})
|
128
|
+
tags = @config[:tags]
|
129
|
+
tags = tags.merge(options[:tags]) unless options[:tags].nil?
|
130
|
+
|
131
|
+
aggregations = %w(last)
|
132
|
+
aggregations.concat(options[:agg]) unless options[:agg].nil?
|
133
|
+
|
134
|
+
opts = {
|
135
|
+
:agg_freq => 10,
|
136
|
+
:namespace => 'application'
|
137
|
+
}.merge(MyHash[options].symbolize_keys)
|
138
|
+
|
139
|
+
opts[:tags] = tags
|
140
|
+
opts[:agg] = aggregations
|
141
|
+
|
142
|
+
_put("gauge.#{name}", opts[:tags], value, opts[:agg], opts[:agg_freq], @config[:sample_rate], opts[:namespace])
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
# Flush metrics buffer
|
147
|
+
def flush_metrics
|
148
|
+
flush
|
149
|
+
end
|
150
|
+
|
151
|
+
# Adds a new metric to the buffer
|
152
|
+
#
|
153
|
+
# @private
|
154
|
+
# @param metric [String] Name of the metric, ex: `response_time`
|
155
|
+
# @param value [Numeric] Value of the metric
|
156
|
+
# @param tags [Hash] Tags to associate to the metric
|
157
|
+
# @param agg [Array<String>] List of aggregations to be applied by Statful
|
158
|
+
# @param agg_freq [Integer] Aggregation frequency in seconds
|
159
|
+
# @param sample_rate [Integer] Sampling rate, between: (1-100)
|
160
|
+
# @param namespace [String] Namespace of the metric
|
161
|
+
# @param timestamp [Integer] Timestamp of the metric
|
162
|
+
def put(metric, tags, value, agg = [], agg_freq = 10, sample_rate = nil, namespace = 'application', timestamp = nil)
|
163
|
+
_tags = @config[:tags]
|
164
|
+
_tags = _tags.merge(tags) unless tags.nil?
|
165
|
+
|
166
|
+
_put(metric, _tags, value, agg, agg_freq, sample_rate, namespace, timestamp)
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
attr_accessor :buffer
|
172
|
+
attr_accessor :logger
|
173
|
+
|
174
|
+
# Adds a new metric to the buffer
|
175
|
+
#
|
176
|
+
# @private
|
177
|
+
# @param metric [String] Name of the metric, ex: `response_time`
|
178
|
+
# @param value [Numeric] Value of the metric
|
179
|
+
# @param tags [Hash] Tags to associate to the metric
|
180
|
+
# @param agg [Array<String>] List of aggregations to be applied by Statful
|
181
|
+
# @param agg_freq [Integer] Aggregation frequency in seconds
|
182
|
+
# @param sample_rate [Integer] Sampling rate, between: (1-100)
|
183
|
+
# @param namespace [String] Namespace of the metric
|
184
|
+
# @param timestamp [Integer] Timestamp of the metric
|
185
|
+
def _put(metric, tags, value, agg = [], agg_freq = 10, sample_rate = nil, namespace = 'application', timestamp = nil)
|
186
|
+
metric_name = "#{namespace}.#{metric}"
|
187
|
+
sample_rate_normalized = sample_rate / 100
|
188
|
+
timestamp = Time.now.to_i if timestamp.nil?
|
189
|
+
|
190
|
+
@config.has_key?(:app) ? merged_tags = tags.merge({:app => @config[:app]}) : merged_tags = tags
|
191
|
+
|
192
|
+
if Random.new.rand(1..100)*0.01 <= sample_rate_normalized
|
193
|
+
flush_line = merged_tags.keys.inject(metric_name) { |previous, tag|
|
194
|
+
"#{previous},#{tag.to_s}=#{merged_tags[tag]}"
|
195
|
+
}
|
196
|
+
|
197
|
+
flush_line += " #{value} #{timestamp}"
|
198
|
+
|
199
|
+
unless agg.empty?
|
200
|
+
agg.push(agg_freq)
|
201
|
+
flush_line += " #{agg.join(',')}"
|
202
|
+
end
|
203
|
+
|
204
|
+
flush_line += sample_rate ? " #{sample_rate}" : ''
|
205
|
+
|
206
|
+
put_raw(flush_line)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Add raw metrics directly into the flush buffer
|
211
|
+
#
|
212
|
+
# @private
|
213
|
+
# @param metric_lines
|
214
|
+
def put_raw(metric_lines)
|
215
|
+
@buffer.push(metric_lines)
|
216
|
+
if @buffer.size >= @config[:flush_size]
|
217
|
+
flush
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Flushed the metrics to the Statful via UDP
|
222
|
+
#
|
223
|
+
# @private
|
224
|
+
def flush
|
225
|
+
unless @buffer.empty?
|
226
|
+
message = @buffer.join('\n')
|
227
|
+
|
228
|
+
# Handle socket errors by just logging if we have a logger instantiated
|
229
|
+
# We could eventually save the buffer state but that would require us to manage the buffer size etc.
|
230
|
+
begin
|
231
|
+
@logger.debug("Flushing metrics: #{message}") unless @logger.nil?
|
232
|
+
|
233
|
+
if !@config.has_key?(:dryrun) || !@config[:dryrun]
|
234
|
+
transport_send(message)
|
235
|
+
end
|
236
|
+
rescue SocketError => ex
|
237
|
+
@logger.debug("Statful: #{ex} on #{@config[:host]}:#{@config[:port]}") unless @logger.nil?
|
238
|
+
false
|
239
|
+
ensure
|
240
|
+
@buffer.clear
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Delegate flushing messages to the proper transport
|
246
|
+
#
|
247
|
+
# @private
|
248
|
+
# @param message
|
249
|
+
def transport_send(message)
|
250
|
+
case @config[:transport]
|
251
|
+
when 'http'
|
252
|
+
http_transport(message)
|
253
|
+
when 'udp'
|
254
|
+
udp_transport(message)
|
255
|
+
else
|
256
|
+
@logger.debug("Failed to flush message due to invalid transport: #{@config[:transport]}") unless @logger.nil?
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
# Sends message via http transport
|
262
|
+
#
|
263
|
+
# @private
|
264
|
+
# @param message
|
265
|
+
# :nocov:
|
266
|
+
def http_transport(message)
|
267
|
+
headers = {
|
268
|
+
'Content-Type' => 'application/json',
|
269
|
+
'M-Api-Token' => @config[:token]
|
270
|
+
}
|
271
|
+
response = @http.send_request('PUT', '/tel/v2.0/metrics', message, headers)
|
272
|
+
|
273
|
+
if response.code != '201'
|
274
|
+
@logger.debug("Failed to flush message via http with: #{response.code} - #{response.msg}") unless @logger.nil?
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Sends message via udp transport
|
279
|
+
#
|
280
|
+
# @private
|
281
|
+
# @param message
|
282
|
+
# :nocov:
|
283
|
+
def udp_transport(message)
|
284
|
+
udp_socket.send(message)
|
285
|
+
end
|
286
|
+
|
287
|
+
# Return a new or existing udp socket
|
288
|
+
#
|
289
|
+
# @private
|
290
|
+
# :nocov:
|
291
|
+
def udp_socket
|
292
|
+
Thread.current[:statful_socket] ||= UDPSocket.new(Addrinfo.udp(@config[:host], @config[:port]).afamily)
|
293
|
+
end
|
294
|
+
|
295
|
+
# Custom Hash implementation to add a symbolize_keys method
|
296
|
+
#
|
297
|
+
# @private
|
298
|
+
class MyHash < Hash
|
299
|
+
# Recursively symbolize an Hash
|
300
|
+
#
|
301
|
+
# @return [Hash] the symbolized hash
|
302
|
+
def symbolize_keys
|
303
|
+
symbolize = lambda do |h|
|
304
|
+
Hash === h ?
|
305
|
+
Hash[
|
306
|
+
h.map do |k, v|
|
307
|
+
[k.respond_to?(:to_sym) ? k.to_sym : k, symbolize[v]]
|
308
|
+
end
|
309
|
+
] : h
|
310
|
+
end
|
311
|
+
|
312
|
+
symbolize[self]
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'client'
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: statful-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Miguel Fonseca
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest-reporters
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Statful Ruby Client (https://www.statful.com)
|
98
|
+
email: miguel.fonseca@mindera.com
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- lib/client.rb
|
104
|
+
- lib/statful-client.rb
|
105
|
+
homepage: https://github.com/statful/statful-client-ruby
|
106
|
+
licenses:
|
107
|
+
- MIT
|
108
|
+
metadata: {}
|
109
|
+
post_install_message:
|
110
|
+
rdoc_options: []
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 2.5.1
|
126
|
+
signing_key:
|
127
|
+
specification_version: 4
|
128
|
+
summary: Statful Ruby Client
|
129
|
+
test_files: []
|