riemann-client 1.0.0 → 1.1.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.
@@ -0,0 +1,531 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'riemann'
4
+ require 'riemann/client'
5
+ require 'set'
6
+ require 'timecop'
7
+
8
+ INACTIVITY_TIME = 5
9
+
10
+ class Sequence
11
+ include Singleton
12
+
13
+ def initialize
14
+ @nextval = 0
15
+ end
16
+
17
+ def nextval
18
+ @nextval += 1
19
+ end
20
+
21
+ def current
22
+ @nextval
23
+ end
24
+ end
25
+
26
+ def next_message_id
27
+ Sequence.instance.nextval
28
+ "#{Process.pid}-#{Sequence.instance.current}"
29
+ end
30
+
31
+ def wait_for_message_with_id(message_id)
32
+ wait_for { client[%(message_id = "#{message_id}")].first }
33
+ end
34
+
35
+ def wait_for(&block)
36
+ tries = 0
37
+ while tries < 30
38
+ tries += 1
39
+ begin
40
+ res = block.call
41
+ return res if res
42
+ rescue NoMethodError
43
+ # If a query returns no result (#query retruns nil or #[] returns []),
44
+ # calling #first on it will raise a NoMethodError. We can ignore it for
45
+ # these tests.
46
+ end
47
+ sleep(0.1)
48
+ end
49
+
50
+ raise 'wait_for condition never realized'
51
+ end
52
+
53
+ def roundtrip_metric(metric)
54
+ message_id = next_message_id
55
+
56
+ client_with_transport << {
57
+ service: 'metric-test',
58
+ metric: metric,
59
+ message_id: message_id
60
+ }
61
+
62
+ e = wait_for_message_with_id(message_id)
63
+ expect(e.metric).to eq(metric)
64
+ end
65
+
66
+ RSpec.shared_examples 'a riemann client' do
67
+ it 'is not connected before sending' do
68
+ expect(client).not_to be_connected
69
+ end
70
+
71
+ context 'when given a block that raises' do
72
+ let(:client) do
73
+ res = nil
74
+ begin
75
+ Riemann::Client.new(host: 'localhost', port: 5555) do |c|
76
+ res = c
77
+ raise 'The Boom'
78
+ end
79
+ rescue StandardError
80
+ # swallow the exception
81
+ end
82
+ res
83
+ end
84
+
85
+ it 'in not connected' do
86
+ expect(client).not_to be_connected
87
+ end
88
+ end
89
+
90
+ it 'is connected after sending' do
91
+ client_with_transport << { state: 'ok', service: 'connected check' }
92
+ expect(client_with_transport).to be_connected
93
+ # NOTE: only single transport connected at this point, client.connected? is still false until all transports used
94
+ end
95
+
96
+ describe '#<<' do
97
+ subject { wait_for_message_with_id(message_id) }
98
+
99
+ let(:message_id) { next_message_id }
100
+
101
+ before do
102
+ client_with_transport << {
103
+ state: 'ok',
104
+ service: 'test',
105
+ description: 'desc',
106
+ metric_f: 1.0,
107
+ message_id: message_id
108
+ }
109
+ end
110
+
111
+ it 'finds the send message' do
112
+ expect(subject.state).to eq('ok')
113
+ end
114
+ end
115
+
116
+ it 'send longs' do
117
+ roundtrip_metric(0)
118
+ roundtrip_metric(-3)
119
+ roundtrip_metric(5)
120
+ roundtrip_metric(-(2**63))
121
+ roundtrip_metric(2**63 - 1)
122
+ end
123
+
124
+ it 'send doubles' do
125
+ roundtrip_metric 0.0
126
+ roundtrip_metric 12.0
127
+ roundtrip_metric 1.2300000190734863
128
+ end
129
+
130
+ context 'when sending custom attributes' do
131
+ subject { wait_for_message_with_id(message_id) }
132
+
133
+ before do
134
+ event = Riemann::Event.new(
135
+ service: 'custom',
136
+ state: 'ok',
137
+ cats: 'meow',
138
+ env: 'prod',
139
+ message_id: message_id
140
+ )
141
+ event[:sneak] = 'attack'
142
+ client_with_transport << event
143
+ end
144
+
145
+ let(:message_id) { next_message_id }
146
+
147
+ it 'has the expected service' do
148
+ expect(subject.service).to eq('custom')
149
+ end
150
+
151
+ it 'has the expected state' do
152
+ expect(subject.state).to eq('ok')
153
+ end
154
+
155
+ it 'has the expected cats' do
156
+ expect(subject[:cats]).to eq('meow')
157
+ end
158
+
159
+ it 'has the expected env' do
160
+ expect(subject[:env]).to eq('prod')
161
+ end
162
+
163
+ it 'has the expected sneak' do
164
+ expect(subject[:sneak]).to eq('attack')
165
+ end
166
+ end
167
+
168
+ context 'when passing time' do
169
+ subject { wait_for_message_with_id(message_id) }
170
+
171
+ before do
172
+ Timecop.freeze
173
+ client_with_transport << {
174
+ state: 'ok',
175
+ service: 'test',
176
+ time: t,
177
+ message_id: message_id
178
+ }
179
+ end
180
+
181
+ after do
182
+ Timecop.return
183
+ end
184
+
185
+ let(:message_id) { next_message_id }
186
+ let(:t) { (Time.now - 10).to_i }
187
+
188
+ it 'has the expected time' do
189
+ expect(subject.time).to eq(t)
190
+ end
191
+
192
+ it 'has the expected time_micros' do
193
+ expect(subject.time_micros).to eq(t * 1_000_000)
194
+ end
195
+ end
196
+
197
+ context 'when passing time_micros' do
198
+ subject { wait_for_message_with_id(message_id) }
199
+
200
+ before do
201
+ Timecop.freeze
202
+ client_with_transport << {
203
+ state: 'ok',
204
+ service: 'test',
205
+ time_micros: t,
206
+ message_id: message_id
207
+ }
208
+ end
209
+
210
+ after do
211
+ Timecop.return
212
+ end
213
+
214
+ let(:message_id) { next_message_id }
215
+ let(:t) { ((Time.now - 10).to_f * 1_000_000).to_i }
216
+
217
+ it 'has the expected time' do
218
+ expect(subject.time).to eq((Time.now - 10).to_i)
219
+ end
220
+
221
+ it 'has the expected time_micros' do
222
+ expect(subject.time_micros).to eq(t)
223
+ end
224
+ end
225
+
226
+ context 'when passing no time nor time_micros' do
227
+ let(:message_id) { next_message_id }
228
+
229
+ let(:time_before) { (Time.now.to_f * 1_000_000).to_i }
230
+ let(:event) do
231
+ client_with_transport << {
232
+ state: 'ok',
233
+ service: 'timeless test',
234
+ message_id: message_id
235
+ }
236
+ end
237
+ let(:time_after) { (Time.now.to_f * 1_000_000).to_i }
238
+
239
+ it 'has the expected time_micros' do
240
+ time_before
241
+ event
242
+ time_after
243
+
244
+ e = wait_for_message_with_id(message_id)
245
+
246
+ expect([time_before, e.time_micros, time_after].sort).to eq([time_before, e.time_micros, time_after])
247
+ end
248
+ end
249
+
250
+ describe '#query' do
251
+ before do
252
+ message_id1 = next_message_id
253
+ message_id2 = next_message_id
254
+ message_id3 = next_message_id
255
+
256
+ client_with_transport << { state: 'critical', service: '1', message_id: message_id1 }
257
+ client_with_transport << { state: 'warning', service: '2', message_id: message_id2 }
258
+ client_with_transport << { state: 'critical', service: '3', message_id: message_id3 }
259
+
260
+ wait_for_message_with_id(message_id3)
261
+ end
262
+
263
+ let(:rate) do
264
+ t1 = Time.now
265
+ total = 1000
266
+ total.times do |_i|
267
+ client.query('state = "critical"')
268
+ end
269
+ t2 = Time.now
270
+
271
+ total / (t2 - t1)
272
+ end
273
+
274
+ it 'returns all events without parameters' do
275
+ expect(client.query.events
276
+ .map(&:service).to_set).to include(%w[1 2 3].to_set)
277
+ end
278
+
279
+ it 'returns matched events with parameters' do
280
+ expect(client.query('state = "critical" and (service = "1" or service = "2" or service = "3")').events
281
+ .map(&:service).to_set).to eq(%w[1 3].to_set)
282
+ end
283
+
284
+ it 'query quickly' do
285
+ puts "\n #{format('%.2f', rate)} queries/sec (#{format('%.2f', (1000 / rate))}ms per query)"
286
+ expect(rate).to be > 100
287
+ end
288
+ end
289
+
290
+ it '[]' do
291
+ message_id = next_message_id
292
+
293
+ # expect(client['state = "critical"']).to be_empty
294
+ client_with_transport << { state: 'critical', message_id: message_id }
295
+ e = wait_for_message_with_id(message_id)
296
+ expect(e.state).to eq('critical')
297
+ end
298
+
299
+ describe '#bulk_send' do
300
+ let(:message_id1) { next_message_id }
301
+ let(:message_id2) { next_message_id }
302
+ let(:event1) { wait_for_message_with_id(message_id1) }
303
+ let(:event2) { wait_for_message_with_id(message_id2) }
304
+
305
+ before do
306
+ client_with_transport.bulk_send(
307
+ [
308
+ {
309
+ state: 'ok',
310
+ service: 'foo',
311
+ message_id: message_id1
312
+ },
313
+ {
314
+ state: 'warning',
315
+ service: 'bar',
316
+ message_id: message_id2
317
+ }
318
+ ]
319
+ )
320
+ end
321
+
322
+ it 'has send the first event' do
323
+ expect(event2.state).to eq('warning')
324
+ end
325
+
326
+ it 'has send the second event' do
327
+ expect(event1.state).to eq('ok')
328
+ end
329
+ end
330
+
331
+ context 'when using multiple threads' do
332
+ let!(:rate) do
333
+ concurrency = 10
334
+ per_thread = 200
335
+ total = concurrency * per_thread
336
+
337
+ t1 = Time.now
338
+ concurrency.times.map do
339
+ Thread.new do
340
+ per_thread.times do
341
+ client_with_transport << {
342
+ state: 'ok',
343
+ service: 'test',
344
+ description: 'desc',
345
+ metric_f: 1.0,
346
+ message_id: next_message_id
347
+ }
348
+ end
349
+ end
350
+ end.each(&:join)
351
+ t2 = Time.now
352
+
353
+ total / (t2 - t1)
354
+ end
355
+
356
+ it 'is threadsafe' do
357
+ puts "\n #{format('%.2f', rate)} inserts/sec (#{format('%.2f', (1000 / rate))}ms per insert)"
358
+ expect(rate).to be > expected_rate
359
+ end
360
+ end
361
+ end
362
+
363
+ RSpec.shared_examples 'a riemann client that acknowledge messages' do
364
+ describe '#<<' do
365
+ subject do
366
+ client_with_transport << {
367
+ state: 'ok',
368
+ service: 'test',
369
+ description: 'desc',
370
+ metric_f: 1.0
371
+ }
372
+ end
373
+
374
+ it 'acknowledge the message' do
375
+ expect(subject.ok).to be_truthy
376
+ end
377
+ end
378
+
379
+ context 'when inactive' do
380
+ let(:message_id1) { next_message_id }
381
+ let(:message1) do
382
+ {
383
+ state: 'warning',
384
+ service: 'survive TCP inactivity',
385
+ message_id: message_id1
386
+ }
387
+ end
388
+
389
+ let(:message_id2) { next_message_id }
390
+ let(:message2) do
391
+ {
392
+ state: 'ok',
393
+ service: 'survive TCP inactivity',
394
+ message_id: message_id2
395
+ }
396
+ end
397
+
398
+ before do
399
+ client_with_transport << message1
400
+ wait_for_message_with_id(message_id1)
401
+ end
402
+
403
+ it 'survive inactivity' do
404
+ sleep INACTIVITY_TIME
405
+
406
+ expect((client_with_transport << message2).ok).to be_truthy
407
+ wait_for_message_with_id(message_id2)
408
+ end
409
+ end
410
+
411
+ context 'when the connection is closed' do
412
+ let(:message_id1) { next_message_id }
413
+ let(:message1) do
414
+ {
415
+ state: 'warning',
416
+ service: 'survive TCP local close',
417
+ message_id: message_id1
418
+ }
419
+ end
420
+
421
+ let(:message_id2) { next_message_id }
422
+ let(:message2) do
423
+ {
424
+ state: 'ok',
425
+ service: 'survive TCP local close',
426
+ message_id: message_id2
427
+ }
428
+ end
429
+
430
+ before do
431
+ client_with_transport << message1
432
+ wait_for_message_with_id(message_id1)
433
+ end
434
+
435
+ it 'survive local close' do
436
+ client.close
437
+
438
+ expect((client_with_transport << message2).ok).to be_truthy
439
+ wait_for_message_with_id(message_id2)
440
+ end
441
+ end
442
+ end
443
+
444
+ RSpec.shared_examples 'a riemann client that does not acknowledge messages' do
445
+ describe '#<<' do
446
+ subject do
447
+ client_with_transport << {
448
+ state: 'ok',
449
+ service: 'test',
450
+ description: 'desc',
451
+ metric_f: 1.0
452
+ }
453
+ end
454
+
455
+ it 'does not acknowledge the message' do
456
+ expect(subject).to be_nil
457
+ end
458
+ end
459
+
460
+ context 'when inactive' do
461
+ let(:message_id1) { next_message_id }
462
+ let(:message1) do
463
+ {
464
+ state: 'warning',
465
+ service: 'survive UDP inactivity',
466
+ message_id: message_id1
467
+ }
468
+ end
469
+
470
+ let(:message_id2) { next_message_id }
471
+ let(:message2) do
472
+ {
473
+ state: 'ok',
474
+ service: 'survive UDP inactivity',
475
+ message_id: message_id2
476
+ }
477
+ end
478
+
479
+ before do
480
+ client_with_transport << message1
481
+ wait_for_message_with_id(message_id1)
482
+ end
483
+
484
+ it 'survive inactivity' do
485
+ sleep INACTIVITY_TIME
486
+
487
+ client_with_transport << message2
488
+ wait_for_message_with_id(message_id2)
489
+ end
490
+ end
491
+
492
+ context 'when the connection is closed' do
493
+ let(:message_id1) { next_message_id }
494
+ let(:message1) do
495
+ {
496
+ state: 'warning',
497
+ service: 'survive UDP local close',
498
+ message_id: message_id1
499
+ }
500
+ end
501
+
502
+ let(:message_id2) { next_message_id }
503
+ let(:message2) do
504
+ {
505
+ state: 'ok',
506
+ service: 'survive UDP local close',
507
+ message_id: message_id2
508
+ }
509
+ end
510
+
511
+ before do
512
+ client_with_transport << message1
513
+ wait_for_message_with_id(message_id1)
514
+ end
515
+
516
+ it 'survive local close' do
517
+ client.close
518
+
519
+ client_with_transport << message2
520
+ wait_for_message_with_id(message_id2)
521
+ end
522
+ end
523
+
524
+ it 'raise Riemann::Client::Unsupported exception on #[]' do
525
+ expect { client_with_transport['service = "test"'] }.to raise_error(Riemann::Client::Unsupported)
526
+ end
527
+
528
+ it 'raise Riemann::Client::Unsupported exception on #query' do
529
+ expect { client_with_transport.query('service = "test"') }.to raise_error(Riemann::Client::Unsupported)
530
+ end
531
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.configure do |config|
4
+ # Limits the available syntax to the non-monkey patched syntax that is
5
+ # recommended. For more details, see:
6
+ # https://relishapp.com/rspec/rspec-core/docs/configuration/zero-monkey-patching-mode
7
+ # config.disable_monkey_patching!
8
+
9
+ # This setting enables warnings. It's recommended, but in some cases may
10
+ # be too noisy due to issues in dependencies.
11
+ config.warnings = true
12
+
13
+ # Run specs in random order to surface order dependencies. If you find an
14
+ # order dependency and want to debug it, you can fix the order by providing
15
+ # the seed, which is printed after each run.
16
+ # --seed 1234
17
+ config.order = :random
18
+
19
+ # Seed global randomization in this process using the `--seed` CLI option.
20
+ # Setting this allows you to use `--seed` to deterministically reproduce
21
+ # test failures related to randomization by passing the same `--seed` value
22
+ # as the one that triggered the failure.
23
+ Kernel.srand config.seed
24
+
25
+ # RSpec tries to be friendly to us by detecting deadlocks but this breaks CI
26
+ # :-(
27
+ #
28
+ # Some tests want to start multiple thread in a let block, and this
29
+ # thread-safety mechanism makes it impossible and raise an exception while
30
+ # our code is working correctly.
31
+ #
32
+ # This issue seems the same as:
33
+ # https://github.com/rspec/rspec-core/issues/2064
34
+ #
35
+ # The feature we disable was introduced in:
36
+ # https://github.com/rspec/rspec-core/commit/ffe00a1d4e369e312881e6b2c091c8b6fb7e6087
37
+ config.threadsafe = false
38
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riemann-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle Kingsbury
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-16 00:00:00.000000000 Z
11
+ date: 2023-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -25,7 +25,35 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.3'
27
27
  - !ruby/object:Gem::Dependency
28
- name: bacon
28
+ name: rspec
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: rubocop
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: rubocop-rspec
29
57
  requirement: !ruby/object:Gem::Requirement
30
58
  requirements:
31
59
  - - ">="
@@ -86,13 +114,18 @@ executables: []
86
114
  extensions: []
87
115
  extra_rdoc_files: []
88
116
  files:
117
+ - ".github/dependabot.yml"
89
118
  - ".github/workflows/ci.yml"
119
+ - ".github/workflows/codeql-analysis.yml"
90
120
  - ".gitignore"
121
+ - ".rspec"
122
+ - ".rubocop.yml"
91
123
  - CHANGELOG.md
92
124
  - Gemfile
93
125
  - LICENSE
94
126
  - README.markdown
95
127
  - Rakefile
128
+ - SECURITY.md
96
129
  - lib/riemann.rb
97
130
  - lib/riemann/attribute.rb
98
131
  - lib/riemann/auto_state.rb
@@ -108,13 +141,15 @@ files:
108
141
  - lib/riemann/state.rb
109
142
  - lib/riemann/version.rb
110
143
  - riemann-client.gemspec
111
- - spec/client.rb
144
+ - spec/client_spec.rb
112
145
  - spec/riemann.config
146
+ - spec/shared_examples.rb
147
+ - spec/spec_helper.rb
113
148
  homepage: https://github.com/aphyr/riemann-ruby-client
114
149
  licenses:
115
150
  - MIT
116
151
  metadata: {}
117
- post_install_message:
152
+ post_install_message:
118
153
  rdoc_options: []
119
154
  require_paths:
120
155
  - lib
@@ -122,17 +157,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
157
  requirements:
123
158
  - - ">="
124
159
  - !ruby/object:Gem::Version
125
- version: 2.7.0
160
+ version: 2.6.0
126
161
  required_rubygems_version: !ruby/object:Gem::Requirement
127
162
  requirements:
128
163
  - - ">="
129
164
  - !ruby/object:Gem::Version
130
165
  version: '0'
131
166
  requirements: []
132
- rubygems_version: 3.2.5
133
- signing_key:
167
+ rubygems_version: 3.3.26
168
+ signing_key:
134
169
  specification_version: 4
135
170
  summary: Client for the distributed event system Riemann.
136
171
  test_files:
137
- - spec/client.rb
172
+ - spec/client_spec.rb
138
173
  - spec/riemann.config
174
+ - spec/shared_examples.rb
175
+ - spec/spec_helper.rb