riemann-client 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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