wavefront-sdk 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a02cb5dea9d85e5aedad5b0e90797ffd903e040599791e52797c65cf76915c23
4
- data.tar.gz: 14e6c880b1c010955bf11bb878835d957d7c751c63ba6b64214181dbb17ab39d
3
+ metadata.gz: ae4a49d72cb51beb4506cf0ea7049cc992e59d7699c37e7e59be1312c7b8d3b0
4
+ data.tar.gz: 8f0c3227f635bf86bdb1eca4e0091d9fb31abf0a99ada3cfcacd57e20a90655c
5
5
  SHA512:
6
- metadata.gz: 7fcc872018be55b82240eb3df41eab15c4439894bd657e50d7d11d4be7f8a9b3d663aca4a0007921b269b78c1b1a64cf842bb9a4ef07268012c861cb4a5b1949
7
- data.tar.gz: 0ad071bda2318a32541b7d97dc98314c8e47efe0100ba7d1eed868519c66bc3b07b228ee80a5888b9bd29d9dc33debb11367171afd5a5dbead6768789b6905fd
6
+ metadata.gz: dd4638fdafec9d0bc5afc327451f81a5d1dd8ef36409f4249e868e948ceb836528bed3bfa369f568dc0b959817a44c83aa5340eee20145d4347ac0a61a1d7904
7
+ data.tar.gz: c1b87ee6385563a046e6d76c9e914ff5f4b635c4cdcf9179f84f44c1db6f87967028df4fdfb40ba25a0dca745f83a1bd43c229e91ae6893e170eba7c66b34dbc
data/HISTORY.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.4.0 (28/01/2019)
4
+ * New `Wavefront::MetricHelper` class creates and in-memory buffer
5
+ to which you can instantaneously add metrics, flushing it to
6
+ Wavefront when appropriate. All `Writer` types are supported.
7
+ * Add `noauto` option to `Write` class. This lets you manage the
8
+ connection yourself without having to pass `false` as the second
9
+ argument to every single `Write#write` call. (Though this still
10
+ works.)
11
+ * Improve error handling when proxy socket is not available.
12
+ * Raise an exception if an interval is not given when writing a
13
+ histogram.
14
+ * Improve `README` instructions on writing metrics.
15
+ * Support Ruby 2.6.
16
+
3
17
  ## 2.3.0 (06/01/2019)
4
18
  * When sending points via the API, send bundles of up to 100 points
5
19
  with each `POST`, rather than a call per point.
data/README.md CHANGED
@@ -47,6 +47,7 @@ object. Map objects can be interrogated in various ways. For
47
47
  instance `map['items']`, `map[:items]` and `map.items` will all get
48
48
  you to the same place.
49
49
 
50
+ ### Standard API Calls
50
51
 
51
52
  ```ruby
52
53
  # Define our API endpoint. (This is not a valid token!)
@@ -120,6 +121,32 @@ last 10 minutes, with one minute bucket granularity. We will
120
121
  describe the time as a Ruby object, but could also use an epoch
121
122
  timestamp. The SDK happily converts between the two.
122
123
 
124
+ ### Credentials
125
+
126
+ The SDK provides a helper class for extracting credentials from a
127
+ configuration file. If you don't supply a file, defaults will be
128
+ used. You can even override things with environment variables.
129
+
130
+ ```ruby
131
+ require 'wavefront-sdk/credentials'
132
+
133
+ c = Wavefront::Credentials.new
134
+
135
+ # Now use that to list the alerts in our account
136
+
137
+ require 'wavefront-sdk/alert'
138
+
139
+ p Wavefront::Alert.new(c.creds).list
140
+
141
+ # To get proxy configuration, use the `proxy` method. This is
142
+ # required by the Write class. You can also use c.all, which
143
+ # includes proxy and API configuration.
144
+
145
+ wf = Wavefront::Write.new(c.proxy)
146
+ wf = Wavefront::Write.new(c.all)
147
+ ```
148
+
149
+ ### Queries
123
150
 
124
151
  ```ruby
125
152
  require 'wavefront-sdk/query'
@@ -131,68 +158,194 @@ Wavefront::Query.new(CREDS).query(
131
158
  )
132
159
  ```
133
160
 
134
- We can write points too. The `Write` class lets you send points to a
135
- proxy, and the `Report` class sends them directly via the API.
136
- Unlike all other classes, `Write` requires the proxy address and
137
- port as its credential hash. `Report` has the same methods and works
138
- in the same way, but uses the same credentials as all the other
139
- classes.
161
+ ### Sending Metrics
162
+
163
+ The `Wavefront::Write` and `Wavefront::Distribution` classes lets
164
+ you send points to Wavefront in a number of ways.
165
+
166
+ #### Sending Points
167
+
168
+ Use `Wavefront::Write` to send points. Points are described as an
169
+ array of hashes. For example:
170
+
171
+ ```ruby
172
+ wf = Wavefront::Write.new(Wavefront::Credentials.new.proxy)
173
+ wf.write([{ path: 'dev.test.sdk', value: 10 }])
174
+ ```
175
+
176
+ The point hash also accepts optional `source`, `ts`, and `tag` keys.
177
+ `tag` is a hash describing point tags. For example.
178
+
179
+ ```ruby
180
+ wf.write({ path: 'dev.test.sdk',
181
+ value: 10,
182
+ ts: Time.now,
183
+ source: 'example',
184
+ tags: { language: 'ruby',
185
+ level: 'beginner'})
186
+ ```
187
+
188
+ As the example shows, if you are sending a single point, you can
189
+ send a naked hash, omitting the array syntax.
190
+
191
+ By default, `Wavefront::Write#write` will open a connection to
192
+ Wavefront on each call, closing it after use.
193
+
194
+ If you prefer to manage the connection yourself, supply `noauto:
195
+ true` in the options hash when instantiating the `Write` class.
140
196
 
141
197
  ```ruby
142
- require 'wavefront-sdk/write'
198
+ wf = Wavefront::Write.new(Wavefront::Credentials.new.proxy, noauto: true)
199
+ wf.open
200
+ wf.write(path: 'dev.test.sdk', value: 10)
201
+ wf.close
202
+ ```
143
203
 
144
- W_CREDS = { proxy: 'wavefront.localnet', port: 2878 }
204
+ Alternatively, pass `false` as the second argument to `Write#write`.
205
+ (This is the legacy method, kept in for backward compatability.)
206
+
207
+ ```ruby
208
+ wf = Wavefront::Write.new(Wavefront::Credentials.new.proxy)
209
+ wf.open
210
+ wf.write([{ path: 'dev.test.sdk', value: 10 }], false)
211
+ wf.close
212
+ ```
213
+
214
+ By default, `Write#write` speaks to a TCP socket on a nearby proxy,
215
+ but other methods are supported via the `writer` option.
216
+
217
+ ```ruby
218
+ # To send points via the API
219
+ wf = Wavefront::Write.new(Wavefront::Credentials.new.creds, writer: :api)
145
220
 
146
- wf = Wavefront::Write.new(W_CREDS, verbose:true)
221
+ # To send points via a local Unix socket
222
+ wf = Wavefront::Write.new(socket: '/tmp/wf_sock', { writer: :unix })
147
223
 
148
- task = wf.write( [{ path: 'dev.test.sdk', value: 10 }])
149
- # SDK DEBUG: Connecting to wavefront.localnet:2878.
150
- # SDK INFO: dev.test.sdk 10 source=box
224
+ # To send points over HTTP
225
+ wf = Wavefront::Write.new(Wavefront::Credentials.new.creds, writer: :http)
226
+
227
+ # Then call wf.write as before.
228
+ ```
229
+
230
+ `Write` can output verbose and debug info, and the response object
231
+ provides a `summary` object.
232
+
233
+ ```ruby
234
+ wf = Wavefront::Write.new(Wavefront::Credentials.new.proxy, verbose: true)
235
+ wf.write([{ path: 'dev.test.sdk', value: 11, tags: { tag1: 'mytag'} }])
236
+ # SDK INFO: dev.test.sdk 11 source=box tag1="mytag"
237
+
238
+ wf = Wavefront::Write.new(Wavefront::Credentials.new.proxy, debug: true)
239
+ wf.write([{ path: 'dev.test.sdk', value: 11, tags: { tag1: 'mytag'} }])
240
+ # SDK DEBUG: Connecting to wavefront:2878.
241
+ # SDK INFO: dev.test.sdk 11 source=box tag1="mytag"
151
242
  # SDK DEBUG: Closing connection to proxy.
152
- puts task.response
153
- # {"sent"=>1, "rejected"=>0, "unsent"=>0}
243
+
244
+ task = wf.write([{ path: 'dev.test.sdk_1', value: 1 },
245
+ { path: 'dev.test.sdk_2', value: 2 }])
246
+ p task.response
247
+ # {"sent"=>2, "rejected"=>0, "unsent"=>0}
154
248
  puts task.ok?
155
249
  # true
156
250
  ```
157
251
 
158
- You can send delta metrics either by manually prefixing your metric
159
- path with a delta symbol, or by using the `write_delta()` method.
160
- There is even a class to help you write Wavefront distributions.
252
+ You can send delta metrics my prefixing your `path` with a delta
253
+ symbol, or by using the `Write#write_delta()` method. This is called in
254
+ exactly the same way as `Write#write`, and supports all the same
255
+ options.
256
+
257
+ #### Sending Distributions
161
258
 
162
- You can also send points to a local proxy over HTTP. Just specify
163
- `:http` as the `writer` option when you create your write object.
259
+ Use the `Wavefront::Distribution` class to send distributions via a
260
+ proxy. This is an extension of `Wavefront::Write`, so usage is
261
+ almost the same. All you have to do differently is specify an
262
+ interval size (`m`, `h`, or `d`), and use a distribution as your
263
+ `value`. We give you methods to help with this. For instance:
164
264
 
165
265
  ```ruby
166
- wf = Wavefront::Write.new(W_CREDS, writer: :http, verbose: true)
266
+ wf = Wavefront::Distribution.new(CREDS.proxy)
167
267
 
168
- task = wf.write( [{ path: 'dev.test.sdk', value: 10 }])
169
- # SDK INFO: dev.test.sdk 10 source=box
170
- # SDK INFO: uri: POST http://wavefront.localnet:2878/
171
- # SDK INFO: body: dev.test.sdk 10 source=box
172
- p task.response
268
+ dist = wf.mk_distribution([7, 7, 7, 8, 8, 9, 10, 10])
269
+
270
+ p dist
271
+
272
+ # [[3, 7.0], [2, 8.0], [1, 9.0], [2, 10.0]]
273
+
274
+ p wf.write({ path: 'dev.test.dist', value: dist, interval: :m }).response
173
275
  # {"sent"=>1, "rejected"=>0, "unsent"=>0}
174
- puts task.ok?
175
- # true
176
276
  ```
177
277
 
178
- The SDK provides a helper class for extracting credentials from a
179
- configuration file. If you don't supply a file, defaults will be
180
- used. You can even override things with environment variables.
278
+ #### Metric Helpers
279
+
280
+ The `Wavefront::MetricHelper` class gives you simple ways to write
281
+ metrics to in-memory buffers, and flush those buffer whenever you see
282
+ fit. It aims to be a little bit like Dropwizard.
283
+
284
+ `MetricHelper` gives you less control over the metrics you send. For
285
+ instance, the source and timestamp are automatically sent. You can
286
+ view the buffer at any time with the `buf` `attr_accessor`.
181
287
 
182
288
  ```ruby
183
- require 'wavefront-sdk/credentials'
289
+ require 'wavefront-sdk/metric_helper'
184
290
 
185
- c = Wavefront::Credentials.new
291
+ wf = Wavefront::MetricHelper.new(CREDS.proxy, verbose: true)
186
292
 
187
- # Now use that to list the proxies in our account
293
+ wf.gauge('my.gauge', 1)
294
+ wf.gauge('my.gauge', 2, { tag1: 'val1' })
295
+ wf.gauge('my.gauge', 3, { tag1: 'val2' })
296
+ wf.counter('my.counter')
297
+ wf.counter('my.counter', 1, { tag1: 'val1' } )
298
+ wf.counter('my.counter')
188
299
 
189
- require 'wavefront-sdk/proxy'
300
+ pp wf.buf
190
301
 
191
- p Wavefront::Proxy.new(c.creds).list
302
+ # {:gauges=>
303
+ # [{:path=>"my.gauge", :ts=>1548633249, :value=>1},
304
+ # {:path=>"my.gauge", :ts=>1548633249, :value=>2, :tags=>{:tag1=>"val1"}},
305
+ # {:path=>"my.gauge", :ts=>1548633249, :value=>3, :tags=>{:tag1=>"val2"}}],
306
+ # :counters=>{["my.counter", nil]=>2, ["my.counter", {:tag1=>"val1"}]=>1}}
192
307
 
193
- # It works for proxies too:
308
+ wf.flush
194
309
 
195
- wf = Wavefront::Write.new(c.proxy)
310
+ # SDK INFO: my.gauge 1 1548633515 source=box
311
+ # SDK INFO: my.gauge 2 1548633515 source=box tag1="val1"
312
+ # SDK INFO: my.gauge 3 1548633515 source=box tag1="val2"
313
+ # SDK INFO: ∆my.counter 2 1548633515 source=box
314
+ # SDK INFO: ∆my.counter 1 1548633515 source=box tag1="val1"
315
+
316
+ pp wf.buf
317
+
318
+ # {:gauges=>[], :counters=>[]}
319
+ ```
320
+
321
+ Note that gauges are sent individually, timestamped at the time they
322
+ are created. All counters are aggregated as you go along, and when
323
+ flushed, they send their value at that moment as a *single delta
324
+ metric*, the timestamp being the time of the flush.
325
+
326
+ You can also work with distributions. To do this, you must add
327
+ `dist_port` to your options hash, giving the number of the proxy
328
+ port listening for Wavefront format distributions. Numbers can be added
329
+ to distributions individually, or in an array. You must specify the
330
+ distribution interval.
331
+
332
+ ```ruby
333
+ wf = Wavefront::MetricHelper.new(CREDS.proxy, { verbose: true, dist_port: 40000 })
334
+
335
+ wf.dist('my.dist', :m, 10)
336
+ wf.dist('my.dist', :m, 10)
337
+ wf.dist('my.dist', :m, [8, 8, 8, 9, 10, 10])
338
+ wf.dist('my.dist', :m, 8)
339
+
340
+ pp wf.buf
341
+
342
+ # {:gauges=>[],
343
+ # :counters=>{},
344
+ # :dists=>{["my.dist", :m, nil]=>[10, 10, 8, 8, 8, 9, 10, 10, 8]}}
345
+
346
+ wf.flush
347
+
348
+ # SDK INFO: !M 1548634226 #4 10.0 #4 8.0 #1 9.0 my.dist source=box
196
349
  ```
197
350
 
198
351
  ## Contributing
@@ -43,6 +43,7 @@ module Wavefront
43
43
  class InvalidVersion < RuntimeError; end
44
44
  class InvalidWebhookId < RuntimeError; end
45
45
  class NotImplemented < RuntimeError; end
46
+ class SocketError < RuntimeError; end
46
47
  class UnparseableResponse < RuntimeError; end
47
48
  class UnsupportedWriter < RuntimeError; end
48
49
  class ValueOutOfRange < RuntimeError; end
@@ -1 +1 @@
1
- WF_SDK_VERSION = '2.3.0'.freeze
1
+ WF_SDK_VERSION = '2.4.0'.freeze
@@ -51,8 +51,10 @@ module Wavefront
51
51
  # rubocop:disable Metrics/AbcSize
52
52
  def hash_to_wf(dist)
53
53
  logger.log("writer subclass #{writer}", :debug)
54
+ raise unless dist.key?(:interval)
55
+
54
56
  format('!%s %i %s %s source=%s %s %s',
55
- dist[:interval].to_s.upcase || raise,
57
+ dist[:interval].to_s.upcase,
56
58
  parse_time(dist.fetch(:ts, Time.now)),
57
59
  array2dist(dist[:value]),
58
60
  dist[:path] || raise,
@@ -0,0 +1,181 @@
1
+ require_relative 'write'
2
+
3
+ module Wavefront
4
+ #
5
+ # A helper class for quickly and efficiently sending metrics.
6
+ # This class creates an in-memory buffer to which you can write
7
+ # information using a number of methods. When the buffer is
8
+ # flushed, the points are send to Wavefront using any Writer
9
+ # class. You can currently write gauges, counters, and
10
+ # distributions. This list may grow in the future.
11
+ #
12
+ class MetricHelper
13
+ attr_reader :opts, :buf, :writer, :dist_writer
14
+
15
+ # See Wavefront::Write#initialize for parameters. Additionally,
16
+ # dist_port: proxy port to write distributions to. If this is
17
+ # unset, distributions will not be handled.
18
+ #
19
+ def initialize(creds, opts = {})
20
+ @opts = opts
21
+ @buf = { gauges: empty_gauges,
22
+ counters: empty_counters }
23
+ @writer = setup_writer(creds, opts)
24
+ @dist_writer = setup_dist_writer(creds, opts) if opts[:dist_port]
25
+ end
26
+
27
+ # Writes a simple path/metric point, with optional tags,
28
+ # to the buffer. The timestamp is automatically set to the
29
+ # current epoch second. For more control, use
30
+ # Wavefront::Write#write
31
+ # @param path [String] metric path
32
+ # @param value [Numeric] metric value
33
+ # @param tags [Hash] hash of point tags
34
+ #
35
+ def gauge(path, value, tags = nil)
36
+ gauge = { path: path, ts: Time.now.to_i, value: value }
37
+ gauge[:tags] = tags if tags
38
+ @buf[:gauges].<< gauge
39
+ end
40
+
41
+ # These counters are internal, and specific to the SDK. When
42
+ # the buffer is flushed, a single value is sent to Wavefront
43
+ # for each counter. The value sent is a Wavefront delta metric.
44
+ #
45
+ # @param path [String] metric path
46
+ # @param value [Numeric] value to add to counter
47
+ # @param tags [Hash] point tags
48
+ #
49
+ def counter(path, value = 1, tags = nil)
50
+ key = [path, tags]
51
+ @buf[:counters][key] += value
52
+ end
53
+
54
+ # These distributions are stored in memory, and sent to
55
+ # Wavefront as native distibutions when the buffer is flushed.
56
+ # @param path [String] metric path
57
+ # @param value [Array, Numeric] value(s) to add to distribution
58
+ # @param interval [Symbol, String] distribution interval, :m,
59
+ # :h, or :d
60
+ # @param tags [Hash] point tags
61
+ #
62
+ def dist(path, interval, value, tags = nil)
63
+ key = [path, interval, tags]
64
+ @buf[:dists][key] += [value].flatten
65
+ end
66
+
67
+ # Flush all stored metrics. Though you can flush by individual
68
+ # type, this is the preferred method
69
+ #
70
+ def flush
71
+ flush_gauges(buf[:gauges])
72
+ flush_counters(buf[:counters])
73
+ flush_dists(buf[:dists]) if opts.key?(:dist_port)
74
+ end
75
+
76
+ # When we are asked to flush the buffers, duplicate the current
77
+ # one, hand it off to the writer class, and clear. If writer
78
+ # tells us there was an error, dump the old buffer into the
79
+ # the new one for the next flush.
80
+ #
81
+ def flush_gauges(gauges)
82
+ return if gauges.empty?
83
+
84
+ to_flush = gauges.dup
85
+ @buf[:gauges] = empty_gauges
86
+
87
+ writer.write(gauges_to_wf(gauges)).tap do |resp|
88
+ @buf[:gauges] += to_flush unless resp.ok?
89
+ end
90
+ end
91
+
92
+ def flush_counters(counters)
93
+ return if counters.empty?
94
+
95
+ to_flush = counters.dup
96
+ @buf[:counters] = empty_counters
97
+
98
+ writer.write_delta(counters_to_wf(counters)).tap do |resp|
99
+ replay_counters(to_flush) unless resp.ok?
100
+ end
101
+ end
102
+
103
+ def flush_dists(dists)
104
+ return if dists.empty?
105
+
106
+ to_flush = dists.dup
107
+ @buf[:dists] = empty_dists
108
+
109
+ dist_writer.write(dists_to_wf(dists)).tap do |resp|
110
+ replay_dists(to_flush) unless resp.ok?
111
+ end
112
+ end
113
+
114
+ # Play a failed flush full of counters back into the system
115
+ #
116
+ def replay_counters(buffer)
117
+ buffer.each { |k, v| counter(k[0], v, k[1]) }
118
+ end
119
+
120
+ def replay_dists(buffer)
121
+ buffer.each { |k, v| dist(k[0], k[1], v, k[2]) }
122
+ end
123
+
124
+ # These are already Wavefront-format points
125
+ #
126
+ def gauges_to_wf(gauges)
127
+ gauges
128
+ end
129
+
130
+ def counters_to_wf(counters)
131
+ counters.map do |k, v|
132
+ path, tags = k
133
+ metric = { path: path, value: v, ts: Time.now.utc.to_i }
134
+ metric[:tags] = tags unless tags.nil?
135
+ metric
136
+ end
137
+ end
138
+
139
+ def dists_to_wf(dists)
140
+ dists.map do |k, v|
141
+ path, interval, tags = k
142
+ dist = { path: path,
143
+ value: dist_writer.mk_distribution(v),
144
+ ts: Time.now.utc.to_i,
145
+ interval: interval }
146
+ dist[:tags] = tags unless tags.nil?
147
+ dist
148
+ end
149
+ end
150
+
151
+ # @return [Hash] options hash, with :port replaced by :dist_port
152
+ #
153
+ def dist_creds(creds, opts)
154
+ creds.dup.tap { |o| o[:port] = opts[:dist_port] }
155
+ end
156
+
157
+ private
158
+
159
+ def empty_gauges
160
+ []
161
+ end
162
+
163
+ def empty_counters
164
+ Hash.new(0)
165
+ end
166
+
167
+ def empty_dists
168
+ Hash.new([])
169
+ end
170
+
171
+ def setup_writer(creds, opts)
172
+ Wavefront::Write.new(creds, opts)
173
+ end
174
+
175
+ def setup_dist_writer(creds, opts)
176
+ require_relative 'distribution'
177
+ @buf[:dists] = empty_dists
178
+ Wavefront::Distribution.new(dist_creds(creds, opts), opts)
179
+ end
180
+ end
181
+ end
@@ -21,11 +21,10 @@ module Wavefront
21
21
  # writing points to Wavefront. The actual writing is handled by
22
22
  # a Wavefront::Writer:: subclass.
23
23
  #
24
- # @param creds [Hash] credentials
25
- # signature.
26
- # @param options [Hash] can contain the following keys:
24
+ # @param creds [Hash] credentials: can contain keys:
27
25
  # proxy [String] the address of the Wavefront proxy. ('wavefront')
28
26
  # port [Integer] the port of the Wavefront proxy
27
+ # @param options [Hash] can contain the following keys:
29
28
  # tags [Hash] point tags which will be applied to every point
30
29
  # noop [Bool] if true, no proxy connection will be made, and
31
30
  # instead of sending the points, they will be printed in
@@ -38,12 +37,16 @@ module Wavefront
38
37
  # debug [Bool]
39
38
  # writer [Symbol, String] the name of the writer class to use.
40
39
  # Defaults to :socket
40
+ # noauto [Bool] if this is false, #write will automatically
41
+ # open a connection to Wavefront on each invocation. Set
42
+ # this to true to manually manage the connection.
41
43
  #
42
44
  def initialize(creds = {}, opts = {})
43
45
  defaults = { tags: nil,
44
46
  writer: :socket,
45
47
  noop: false,
46
48
  novalidate: false,
49
+ noauto: false,
47
50
  verbose: false,
48
51
  debug: false }
49
52
 
@@ -76,10 +79,20 @@ module Wavefront
76
79
  # appropriate class documentation for @return information etc.
77
80
  # The signature is always the same.
78
81
  #
79
- def write(points = [], openclose = true, prefix = nil)
82
+ def write(points = [], openclose = manage_conn, prefix = nil)
80
83
  writer.write(points, openclose, prefix)
81
84
  end
82
85
 
86
+ # Wrapper around writer class's #flush method
87
+ #
88
+ def flush
89
+ writer.flush
90
+ end
91
+
92
+ def manage_conn
93
+ opts[:noauto] ? false : true
94
+ end
95
+
83
96
  # A wrapper method around #write() which guarantees all points
84
97
  # will be sent as deltas. You can still manually prefix any
85
98
  # metric with a delta symbol and use #write(), but depending on
@@ -89,7 +102,7 @@ module Wavefront
89
102
  # @param points [Array[Hash]] see #write()
90
103
  # @param openclose [Bool] see #write()
91
104
  #
92
- def write_delta(points, openclose = true)
105
+ def write_delta(points, openclose = manage_conn)
93
106
  write(paths_to_deltas(points), openclose)
94
107
  end
95
108
 
@@ -129,7 +142,7 @@ module Wavefront
129
142
  # open a socket to the proxy before sending points, and
130
143
  # afterwards, close it.
131
144
  #
132
- def raw(points, openclose = true)
145
+ def raw(points, openclose = manage_conn)
133
146
  writer.open if openclose && writer.respond_to?(:open)
134
147
 
135
148
  begin
@@ -30,6 +30,7 @@ module Wavefront
30
30
  @creds = calling_class.creds
31
31
  @opts = calling_class.opts
32
32
  @logger = calling_class.logger
33
+ @manage_conn = calling_class.manage_conn
33
34
  @summary = Wavefront::Writer::Summary.new
34
35
 
35
36
  validate_credentials(creds) if respond_to?(:validate_credentials)
@@ -47,11 +48,14 @@ module Wavefront
47
48
  # @raise any unhandled point validation error is passed through
48
49
  # @return [Wavefront::Response]
49
50
  #
50
- def write(points = [], openclose = true, prefix = nil)
51
- open if openclose && respond_to?(:open)
52
-
51
+ def write(points = [], openclose = manage_conn, prefix = nil)
53
52
  points = screen_points(points)
54
53
  points = prefix_points(points, prefix)
54
+ do_write(points, openclose, prefix)
55
+ end
56
+
57
+ def do_write(points, openclose, _prefix)
58
+ open if openclose && respond_to?(:open)
55
59
 
56
60
  begin
57
61
  write_loop(points)
@@ -78,7 +82,7 @@ module Wavefront
78
82
  true
79
83
  rescue StandardError => e
80
84
  summary.unsent += 1
81
- logger.log('WARNING: failed to send point.')
85
+ logger.log('Failed to send point.', :warn)
82
86
  logger.log(e.to_s, :debug)
83
87
  false
84
88
  end
@@ -51,9 +51,12 @@ module Wavefront
51
51
 
52
52
  # @param point [String] point or points in native Wavefront
53
53
  # format.
54
+ # @raise [SocketError] if point cannot be written
54
55
  #
55
56
  def _send_point(point)
56
57
  conn.puts(point)
58
+ rescue StandardError
59
+ raise Wavefront::Exception::SocketError
57
60
  end
58
61
 
59
62
  # return [Integer] the port to connect to, if none is supplied
@@ -171,3 +171,23 @@ class Hash
171
171
  Marshal.load(Marshal.dump(self))
172
172
  end
173
173
  end
174
+
175
+ # A mock socket
176
+ #
177
+ class Mocket
178
+ def puts(socket); end
179
+
180
+ def close; end
181
+
182
+ def ok?
183
+ true
184
+ end
185
+ end
186
+
187
+ # A mock socket which says things went wrong.
188
+ #
189
+ class BadMocket < Mocket
190
+ def ok?
191
+ false
192
+ end
193
+ end
@@ -61,6 +61,13 @@ class WavefrontDistributionTest < MiniTest::Test
61
61
  '!M 1538865613 #5 11 #15 2.533 #8 -15 #12 1000000.0 ' \
62
62
  "test.distribution source=#{Socket.gethostname} " \
63
63
  'tag1="val1" tag2="val2"')
64
+
65
+ bad_dist = DIST.dup
66
+ bad_dist.delete(:interval)
67
+
68
+ assert_raises(Wavefront::Exception::InvalidDistribution) do
69
+ wf.hash_to_wf(bad_dist)
70
+ end
64
71
  end
65
72
 
66
73
  def test_array2dist
@@ -68,11 +75,3 @@ class WavefrontDistributionTest < MiniTest::Test
68
75
  assert_equal(wf.array2dist([[12, 4.235]]), '#12 4.235')
69
76
  end
70
77
  end
71
-
72
- # A mock socket
73
- #
74
- class Mocket
75
- def puts(socket); end
76
-
77
- def close; end
78
- end
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'minitest/autorun'
4
+ require 'spy'
5
+ require 'spy/integration'
6
+ require_relative '../spec_helper'
7
+ require_relative '../../lib/wavefront-sdk/metric_helper'
8
+
9
+ ND_CREDS = { proxy: 'wavefront' }.freeze
10
+ WH_TAGS = { t1: 'v1', t2: 'v2' }.freeze
11
+
12
+ # Tests for the MetricHelper class.
13
+ #
14
+ # rubocop:disable Style/NumericLiterals
15
+ class WavefrontMetricHelperTest < MiniTest::Test
16
+ attr_reader :wf, :wfd
17
+
18
+ def setup
19
+ @wf = Wavefront::MetricHelper.new(ND_CREDS, {})
20
+ @wfd = Wavefront::MetricHelper.new(ND_CREDS, dist_port: 40000)
21
+ end
22
+
23
+ def test_gauge_1
24
+ wf.gauge('test.gauge', 123)
25
+ b = wf.buf
26
+ refute_empty(b[:gauges])
27
+ assert_empty(b[:counters])
28
+ assert_instance_of(Array, b[:gauges])
29
+ assert_equal(1, b[:gauges].size)
30
+ assert_equal(123, b[:gauges][0][:value])
31
+ end
32
+
33
+ def test_gauge_tags
34
+ wf.gauge('test.gauge', 9.5, WH_TAGS)
35
+ b = wf.buf
36
+ refute_empty(b[:gauges])
37
+ assert_empty(b[:counters])
38
+ assert_instance_of(Array, b[:gauges])
39
+ assert_equal(1, b[:gauges].size)
40
+ assert_equal(9.5, b[:gauges][0][:value])
41
+ assert_equal(WH_TAGS, b[:gauges][0][:tags])
42
+ end
43
+
44
+ def test_counter
45
+ wf.counter('test.counter')
46
+ wf.counter('test.counter')
47
+ wf.counter('test.counter', 2)
48
+ b = wf.buf
49
+ assert_empty(b[:gauges])
50
+ refute_empty(b[:counters])
51
+ assert_instance_of(Hash, b[:counters])
52
+ assert_equal(4, b[:counters][['test.counter', nil]])
53
+ end
54
+
55
+ def test_counter_tags
56
+ wf.counter('test.counter')
57
+ wf.counter('test.counter', 1, WH_TAGS)
58
+ wf.counter('test.counter', 2)
59
+ wf.counter('test.counter', 3, WH_TAGS)
60
+ b = wf.buf
61
+ assert_empty(b[:gauges])
62
+ refute_empty(b[:counters])
63
+ assert_instance_of(Hash, b[:counters])
64
+ assert_equal(3, b[:counters][['test.counter', nil]])
65
+ assert_equal(4, b[:counters][['test.counter', WH_TAGS]])
66
+ end
67
+
68
+ def test_dist_nodist
69
+ refute wf.buf.key?(:dists)
70
+ end
71
+
72
+ def test_dist
73
+ wfd.dist('test.dist', :m, 10)
74
+ wfd.dist('test.dist', :h, 456)
75
+ wfd.dist('test.dist', :h, 123, WH_TAGS)
76
+ wfd.dist('test.dist', :m, [10, 12, 13, 14])
77
+ b = wfd.buf
78
+ assert_empty(b[:gauges])
79
+ assert_empty(b[:counters])
80
+ refute_empty(b[:dists])
81
+ assert_equal(3, b[:dists].size)
82
+ assert_equal([10, 10, 12, 13, 14], b[:dists][['test.dist', :m, nil]])
83
+ assert_equal([456], b[:dists][['test.dist', :h, nil]])
84
+ assert_equal([123], b[:dists][['test.dist', :h, WH_TAGS]])
85
+ end
86
+
87
+ def test_gauges_to_wf
88
+ input = [{ path: 'm1.p', ts: 1548636080, value: 0 },
89
+ { path: 'm1.p', ts: 1548636081, value: 1 },
90
+ { path: 'm2.p', ts: 1548636081, value: 9 }]
91
+
92
+ assert_equal(input, wf.gauges_to_wf(input))
93
+ end
94
+
95
+ def test_counters_to_wf
96
+ input = { ['test.counter1', nil] => 7,
97
+ ['test.counter1', WH_TAGS] => 8,
98
+ ['test.counter2', nil] => 9 }
99
+
100
+ out = wf.counters_to_wf(input)
101
+ assert_instance_of(Array, out)
102
+ assert_equal(3, out.size)
103
+ out.each { |o| assert_instance_of(Hash, o) }
104
+ assert_equal('test.counter1', out.first[:path])
105
+ assert_equal(9, out.last[:value])
106
+ refute(out.first[:tags])
107
+ assert_equal(WH_TAGS, out[1][:tags])
108
+ assert_kind_of(Numeric, out[2][:ts])
109
+ end
110
+
111
+ def test_dists_to_wf
112
+ input = { ['test.dist1', :m, nil] => [10, 10, 11, 12],
113
+ ['test.dist1', :m, WH_TAGS] => [123, 456, 789],
114
+ ['test.dist1', :h, nil] => [6, 6, 7, 4, 6, 4, 8] }
115
+
116
+ out = wfd.dists_to_wf(input)
117
+ assert_instance_of(Array, out)
118
+ assert_equal(3, out.size)
119
+ assert_equal(1, out.select do |o|
120
+ o[:value] == [[2, 10.0], [1, 11.0],
121
+ [1, 12.0]]
122
+ end .size)
123
+ assert_equal(1, out.select { |o| o[:tags] == WH_TAGS }.size)
124
+ assert_equal(3, out.select { |o| o[:path] == 'test.dist1' }.size)
125
+ end
126
+
127
+ def test_flush_gauges
128
+ assert_nil(wf.flush_gauges([]))
129
+
130
+ input = [{ path: 'm1.p', ts: 1548636080, value: 0 },
131
+ { path: 'm1.p', ts: 1548636081, value: 1, tags: WH_TAGS },
132
+ { path: 'm2.p', ts: 1548636081, value: 9 }]
133
+
134
+ mocket = Mocket.new
135
+ spy = Spy.on(wf.writer.writer, :write).and_return(mocket)
136
+
137
+ out = wf.flush_gauges(input)
138
+ args = spy.calls.first.args.first
139
+ assert_instance_of(Mocket, out)
140
+ assert spy.has_been_called?
141
+ assert_equal(input, args)
142
+ assert(args.any? { |a| a.key?(:tags) && a[:tags] == WH_TAGS })
143
+ refute(args.all? { |a| a.key?(:tags) })
144
+ assert_empty(wf.buf[:gauges])
145
+ end
146
+
147
+ def test_flush_gauges_fail
148
+ input = [{ path: 'm1.p', ts: 1548636080, value: 0 }]
149
+
150
+ mocket = BadMocket.new
151
+ spy = Spy.on(wf.writer.writer, :write).and_return(mocket)
152
+ out = wf.flush_gauges(input)
153
+ assert_instance_of(BadMocket, out)
154
+ wf.gauge('m2.p', 9)
155
+ wf.gauge('m3.p', 9)
156
+ assert spy.has_been_called?
157
+ assert_equal(input, spy.calls.first.args.first)
158
+ assert_equal(3, wf.buf[:gauges].size)
159
+ assert_includes(wf.buf[:gauges],
160
+ path: 'm1.p', ts: 1548636080, value: 0)
161
+ end
162
+
163
+ def test_flush_counters
164
+ assert_nil(wf.flush_counters([]))
165
+
166
+ input = { ['test.counter1', nil] => 7,
167
+ ['test.counter1', WH_TAGS] => 8,
168
+ ['test.counter2', nil] => 9 }
169
+
170
+ mocket = Mocket.new
171
+ spy = Spy.on(wf.writer.writer, :write).and_return(mocket)
172
+
173
+ out = wf.flush_counters(input)
174
+ args = spy.calls.first.args.first
175
+
176
+ assert_instance_of(Mocket, out)
177
+ assert spy.has_been_called?
178
+ assert_equal(3, args.size)
179
+
180
+ args.each do |a|
181
+ assert_instance_of(Hash, a)
182
+ assert_includes(a.keys, :path)
183
+ assert_includes(a.keys, :ts)
184
+ assert_includes(a.keys, :value)
185
+ assert(a[:path].start_with?(DELTA))
186
+ end
187
+
188
+ assert(args.any? { |a| a.key?(:tags) && a[:tags] == WH_TAGS })
189
+ refute(args.all? { |a| a.key?(:tags) })
190
+ assert_empty(wf.buf[:counters])
191
+ end
192
+
193
+ def test_flush_counters_fail
194
+ input = { ['test.counter1', nil] => 7,
195
+ ['test.counter1', WH_TAGS] => 8,
196
+ ['test.counter2', nil] => 9 }
197
+
198
+ mocket = BadMocket.new
199
+ spy = Spy.on(wf.writer.writer, :write).and_return(mocket)
200
+
201
+ out = wf.flush_counters(input)
202
+ args = spy.calls.first.args.first
203
+
204
+ assert_instance_of(BadMocket, out)
205
+ assert spy.has_been_called?
206
+ assert_equal(3, args.size)
207
+
208
+ wf.counter('test.counter1', 10)
209
+ wf.counter('test.counter1', 10)
210
+ wf.counter('test.counter2', 100)
211
+
212
+ assert(args.any? { |a| a.key?(:tags) && a[:tags] == WH_TAGS })
213
+ refute(args.all? { |a| a.key?(:tags) })
214
+ buf = wf.buf[:counters]
215
+ refute_empty(buf)
216
+ assert_equal(3, buf.size)
217
+ assert_equal(8, buf[['test.counter1', WH_TAGS]])
218
+ assert_equal(27, buf[['test.counter1', nil]])
219
+ assert_equal(109, buf[['test.counter2', nil]])
220
+ end
221
+
222
+ def test_flush_dists
223
+ assert_nil(wfd.flush_dists([]))
224
+
225
+ input = { ['test.dist1', :m, nil] => [10, 10, 11, 12],
226
+ ['test.dist1', :m, WH_TAGS] => [123, 456, 789],
227
+ ['test.dist1', :h, nil] => [6, 6, 7, 4, 6, 4, 8] }
228
+
229
+ mocket = Mocket.new
230
+ spy = Spy.on(wfd.dist_writer.writer, :write).and_return(mocket)
231
+
232
+ out = wfd.flush_dists(input)
233
+ args = spy.calls.first.args.first
234
+
235
+ assert_instance_of(Mocket, out)
236
+ assert spy.has_been_called?
237
+ assert_equal(3, args.size)
238
+
239
+ args.each do |a|
240
+ assert_instance_of(Hash, a)
241
+ assert_includes(a.keys, :path)
242
+ assert_includes(a.keys, :ts)
243
+ assert_includes(a.keys, :value)
244
+ assert_instance_of(Array, a[:value])
245
+ assert_kind_of(Numeric, a[:ts])
246
+ end
247
+
248
+ assert(args.any? { |a| a.key?(:tags) && a[:tags] == WH_TAGS })
249
+ refute(args.all? { |a| a.key?(:tags) })
250
+ assert_empty(wfd.buf[:dists])
251
+ end
252
+
253
+ def test_flush_dists_fail
254
+ input = { ['test.dist1', :m, nil] => [10, 10, 11, 12],
255
+ ['test.dist1', :m, WH_TAGS] => [123, 456, 789],
256
+ ['test.dist1', :h, nil] => [6, 6, 7, 4, 6, 4, 8] }
257
+
258
+ mocket = BadMocket.new
259
+ spy = Spy.on(wfd.dist_writer.writer, :write).and_return(mocket)
260
+
261
+ out = wfd.flush_dists(input)
262
+ args = spy.calls.first.args.first
263
+
264
+ assert_instance_of(BadMocket, out)
265
+ assert spy.has_been_called?
266
+ assert_equal(3, args.size)
267
+ refute_empty(wfd.buf[:dists])
268
+ assert_equal(input, wfd.buf[:dists])
269
+ end
270
+
271
+ def test_dist_creds
272
+ opts = { verbose: true, dist_port: 40000 }
273
+ o = wfd.dist_creds(ND_CREDS, opts)
274
+ assert_equal(40000, o[:port])
275
+ assert_equal({ verbose: true, dist_port: 40000 }, opts)
276
+ assert_equal('wavefront', o[:proxy])
277
+ end
278
+ end
279
+ # rubocop:enable Style/NumericLiterals
@@ -21,6 +21,10 @@ class TestClassNoTags
21
21
  def logger
22
22
  Logger.new(STDOUT)
23
23
  end
24
+
25
+ def manage_conn
26
+ true
27
+ end
24
28
  end
25
29
 
26
30
  # Test methods common to 'write' and 'report'
@@ -94,11 +94,3 @@ class WavefrontWriterSocketTest < MiniTest::Test
94
94
  refute tcp_spy.has_been_called?
95
95
  end
96
96
  end
97
-
98
- # A mock socket
99
- #
100
- class Mocket
101
- def puts(socket); end
102
-
103
- def close; end
104
- end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wavefront-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Fisher
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-07 00:00:00.000000000 Z
11
+ date: 2019-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -212,6 +212,7 @@ files:
212
212
  - lib/wavefront-sdk/maintenancewindow.rb
213
213
  - lib/wavefront-sdk/message.rb
214
214
  - lib/wavefront-sdk/metric.rb
215
+ - lib/wavefront-sdk/metric_helper.rb
215
216
  - lib/wavefront-sdk/notificant.rb
216
217
  - lib/wavefront-sdk/paginator/base.rb
217
218
  - lib/wavefront-sdk/paginator/delete.rb
@@ -258,6 +259,7 @@ files:
258
259
  - spec/wavefront-sdk/integration_spec.rb
259
260
  - spec/wavefront-sdk/maintenancewindow_spec.rb
260
261
  - spec/wavefront-sdk/message_spec.rb
262
+ - spec/wavefront-sdk/metric_helper_spec.rb
261
263
  - spec/wavefront-sdk/metric_spec.rb
262
264
  - spec/wavefront-sdk/notificant_spec.rb
263
265
  - spec/wavefront-sdk/paginator/base_spec.rb
@@ -327,6 +329,7 @@ test_files:
327
329
  - spec/wavefront-sdk/integration_spec.rb
328
330
  - spec/wavefront-sdk/maintenancewindow_spec.rb
329
331
  - spec/wavefront-sdk/message_spec.rb
332
+ - spec/wavefront-sdk/metric_helper_spec.rb
330
333
  - spec/wavefront-sdk/metric_spec.rb
331
334
  - spec/wavefront-sdk/notificant_spec.rb
332
335
  - spec/wavefront-sdk/paginator/base_spec.rb