riemann-client 1.0.1 → 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,17 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riemann-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle Kingsbury
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-25 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
- name: bacon
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - ">="
@@ -25,21 +39,21 @@ dependencies:
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
- name: bundler
42
+ name: rubocop
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
32
46
  - !ruby/object:Gem::Version
33
- version: '1.3'
47
+ version: '0'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - ">="
39
53
  - !ruby/object:Gem::Version
40
- version: '1.3'
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
- name: rubocop
56
+ name: rubocop-rspec
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - ">="
@@ -100,14 +114,18 @@ executables: []
100
114
  extensions: []
101
115
  extra_rdoc_files: []
102
116
  files:
117
+ - ".github/dependabot.yml"
103
118
  - ".github/workflows/ci.yml"
119
+ - ".github/workflows/codeql-analysis.yml"
104
120
  - ".gitignore"
121
+ - ".rspec"
105
122
  - ".rubocop.yml"
106
123
  - CHANGELOG.md
107
124
  - Gemfile
108
125
  - LICENSE
109
126
  - README.markdown
110
127
  - Rakefile
128
+ - SECURITY.md
111
129
  - lib/riemann.rb
112
130
  - lib/riemann/attribute.rb
113
131
  - lib/riemann/auto_state.rb
@@ -123,8 +141,10 @@ files:
123
141
  - lib/riemann/state.rb
124
142
  - lib/riemann/version.rb
125
143
  - riemann-client.gemspec
126
- - spec/client.rb
144
+ - spec/client_spec.rb
127
145
  - spec/riemann.config
146
+ - spec/shared_examples.rb
147
+ - spec/spec_helper.rb
128
148
  homepage: https://github.com/aphyr/riemann-ruby-client
129
149
  licenses:
130
150
  - MIT
@@ -144,10 +164,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
164
  - !ruby/object:Gem::Version
145
165
  version: '0'
146
166
  requirements: []
147
- rubygems_version: 3.3.15
167
+ rubygems_version: 3.3.26
148
168
  signing_key:
149
169
  specification_version: 4
150
170
  summary: Client for the distributed event system Riemann.
151
171
  test_files:
152
- - spec/client.rb
172
+ - spec/client_spec.rb
153
173
  - spec/riemann.config
174
+ - spec/shared_examples.rb
175
+ - spec/spec_helper.rb