wavefront-sdk 2.3.0 → 2.4.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 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