karafka-core 2.5.10 → 2.5.12

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +8 -8
  3. data/.github/workflows/push.yml +2 -2
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +16 -0
  6. data/Gemfile.lock +1 -1
  7. data/karafka-core.gemspec +1 -1
  8. data/lib/karafka/core/configurable/node.rb +8 -6
  9. data/lib/karafka/core/contractable/contract.rb +35 -9
  10. data/lib/karafka/core/contractable/result.rb +9 -0
  11. data/lib/karafka/core/instrumentation/callbacks_manager.rb +6 -1
  12. data/lib/karafka/core/monitoring/event.rb +21 -4
  13. data/lib/karafka/core/monitoring/notifications.rb +5 -16
  14. data/lib/karafka/core/monitoring/statistics_decorator.rb +192 -39
  15. data/lib/karafka/core/version.rb +1 -1
  16. metadata +2 -26
  17. data/test/lib/karafka/core/configurable/leaf_test.rb +0 -3
  18. data/test/lib/karafka/core/configurable/node_test.rb +0 -3
  19. data/test/lib/karafka/core/configurable_test.rb +0 -504
  20. data/test/lib/karafka/core/contractable/contract_test.rb +0 -241
  21. data/test/lib/karafka/core/contractable/result_test.rb +0 -106
  22. data/test/lib/karafka/core/contractable/rule_test.rb +0 -5
  23. data/test/lib/karafka/core/contractable_test.rb +0 -3
  24. data/test/lib/karafka/core/helpers/time_test.rb +0 -29
  25. data/test/lib/karafka/core/instrumentation/callbacks_manager_test.rb +0 -81
  26. data/test/lib/karafka/core/instrumentation_test.rb +0 -35
  27. data/test/lib/karafka/core/monitoring/event_test.rb +0 -25
  28. data/test/lib/karafka/core/monitoring/monitor_test.rb +0 -237
  29. data/test/lib/karafka/core/monitoring/notifications_test.rb +0 -275
  30. data/test/lib/karafka/core/monitoring/statistics_decorator_test.rb +0 -284
  31. data/test/lib/karafka/core/monitoring_test.rb +0 -3
  32. data/test/lib/karafka/core/patches/rdkafka/bindings_test.rb +0 -25
  33. data/test/lib/karafka/core/taggable/tags_test.rb +0 -66
  34. data/test/lib/karafka/core/taggable_test.rb +0 -36
  35. data/test/lib/karafka/core/version_test.rb +0 -5
  36. data/test/lib/karafka/core_test.rb +0 -13
  37. data/test/lib/karafka-core_test.rb +0 -3
  38. data/test/support/class_builder.rb +0 -24
  39. data/test/support/describe_current_helper.rb +0 -41
  40. data/test/test_helper.rb +0 -55
@@ -1,284 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- describe_current do
4
- subject(:decorator) { described_class.new }
5
-
6
- let(:emited_stats1) do
7
- {
8
- "string" => "value1",
9
- "float" => 10.4,
10
- "int" => 112,
11
- "nested" => {
12
- "brokers" => {
13
- "localhost:9092/2" => {
14
- "txbytes" => 123
15
- }
16
- }
17
- }
18
- }
19
- end
20
-
21
- let(:emited_stats2) do
22
- {
23
- "string" => "value2",
24
- "float" => 10.8,
25
- "int" => 130,
26
- "nested" => {
27
- "brokers" => {
28
- "localhost:9092/2" => {
29
- "txbytes" => 153
30
- }
31
- }
32
- }
33
- }
34
- end
35
-
36
- let(:emited_stats3) do
37
- {
38
- "string" => "value3",
39
- "float" => 11.8,
40
- "int" => 10,
41
- "nested" => {
42
- "brokers" => {
43
- "localhost:9092/2" => {
44
- "txbytes" => 2
45
- }
46
- }
47
- }
48
- }
49
- end
50
-
51
- let(:broker_scope) { %w[nested brokers localhost:9092/2] }
52
-
53
- context "when it is a first stats emit" do
54
- subject(:decorated) { decorator.call(emited_stats1) }
55
-
56
- it { assert_equal "value1", decorated["string"] }
57
- it { refute decorated.key?("string_d") }
58
- it { assert_equal 0, decorated["float_d"] }
59
- it { assert_equal 0, decorated["int_d"] }
60
- it { assert_equal 0, decorated.dig(*broker_scope)["txbytes_d"] }
61
- it { assert_predicate decorated, :frozen? }
62
- end
63
-
64
- context "when it is a second stats emit" do
65
- subject(:decorated) do
66
- decorator.call(emited_stats1)
67
- decorator.call(emited_stats2)
68
- end
69
-
70
- it { assert_equal "value2", decorated["string"] }
71
- it { refute decorated.key?("string_d") }
72
- it { assert_in_delta(0.4, decorated["float_d"].round(10)) }
73
- it { assert_equal 18, decorated["int_d"] }
74
- it { assert_equal 30, decorated.dig(*broker_scope)["txbytes_d"] }
75
- it { assert_predicate decorated, :frozen? }
76
- end
77
-
78
- context "when it is a third stats emit" do
79
- subject(:decorated) do
80
- decorator.call(emited_stats1)
81
- decorator.call(emited_stats2)
82
- decorator.call(emited_stats3)
83
- end
84
-
85
- it { assert_equal "value3", decorated["string"] }
86
- it { refute decorated.key?("string_d") }
87
- it { refute decorated.key?("string_fd") }
88
- it { assert_in_delta(1.0, decorated["float_d"].round(10)) }
89
- it { assert_in_delta 0, decorated["float_fd"], 5 }
90
- it { assert_equal(-120, decorated["int_d"]) }
91
- it { assert_in_delta 0, decorated["int_fd"], 5 }
92
- it { assert_equal(-151, decorated.dig(*broker_scope)["txbytes_d"]) }
93
- it { assert_in_delta 0, decorated.dig(*broker_scope)["txbytes_fd"], 5 }
94
- it { assert_predicate decorated, :frozen? }
95
- it { refute decorated.key?("float_d_d") }
96
- end
97
-
98
- context "when a broker is no longer present" do
99
- subject(:decorated) do
100
- decorator.call(emited_stats1)
101
- decorator.call(emited_stats2)
102
- end
103
-
104
- before { emited_stats2["nested"] = {} }
105
-
106
- it { assert_equal "value2", decorated["string"] }
107
- it { refute decorated.key?("string_d") }
108
- it { refute decorated.key?("string_fd") }
109
- it { assert_in_delta(0.4, decorated["float_d"].round(10)) }
110
- it { assert_in_delta 0, decorated["float_fd"], 5 }
111
- it { assert_equal 18, decorated["int_d"] }
112
- it { assert_in_delta 0, decorated["int_fd"], 5 }
113
- it { assert_equal({}, decorated["nested"]) }
114
- it { assert_predicate decorated, :frozen? }
115
- it { refute decorated.key?("float_d_d") }
116
- end
117
-
118
- context "when broker was introduced later on" do
119
- subject(:decorated) do
120
- decorator.call(emited_stats1)
121
- decorator.call(emited_stats2)
122
- end
123
-
124
- before { emited_stats1["nested"] = {} }
125
-
126
- it { assert_equal "value2", decorated["string"] }
127
- it { refute decorated.key?("string_d") }
128
- it { assert_in_delta(0.4, decorated["float_d"].round(10)) }
129
- it { assert_in_delta 0, decorated["float_fd"], 5 }
130
- it { assert_equal 18, decorated["int_d"] }
131
- it { assert_in_delta 0, decorated["int_fd"], 5 }
132
- it { assert_equal 0, decorated.dig(*broker_scope)["txbytes_d"] }
133
- it { assert_in_delta 0, decorated.dig(*broker_scope)["txbytes_fd"], 5 }
134
- it { assert_predicate decorated, :frozen? }
135
- it { refute decorated.key?("float_d_d") }
136
- end
137
-
138
- context "when value remains unchanged over time" do
139
- subject(:decorated) do
140
- # First one will set initial state
141
- decorator.call(deep_copy.call)
142
- # Second one will build first deltas with freeze duration of zero
143
- decorator.call(deep_copy.call)
144
- sleep(0.01)
145
- # Third one will allow for proper freeze duration computation
146
- decorator.call(deep_copy.call)
147
- end
148
-
149
- let(:deep_copy) { -> { Marshal.load(Marshal.dump(emited_stats1)) } }
150
-
151
- it { refute decorated.key?("string_d") }
152
- it { refute decorated.key?("string_fd") }
153
- it { assert_equal 0, decorated["float_d"] }
154
- it { assert_in_delta 10, decorated["float_fd"], 5 }
155
- it { assert_equal 0, decorated["int_d"] }
156
- it { assert_in_delta 10, decorated["int_fd"], 5 }
157
- it { assert_predicate decorated, :frozen? }
158
- it { refute decorated.key?("float_d_d") }
159
- end
160
-
161
- context "when value remains unchanged over multiple occurrences and time" do
162
- subject(:decorated) do
163
- # First one will set initial state
164
- decorator.call(deep_copy.call)
165
- # Second one will build first deltas with freeze duration of zero
166
- decorator.call(deep_copy.call)
167
- sleep(0.01)
168
- # Third one will allow for proper freeze duration computation
169
- decorator.call(deep_copy.call)
170
- sleep(0.01)
171
- decorator.call(deep_copy.call)
172
- end
173
-
174
- let(:deep_copy) { -> { Marshal.load(Marshal.dump(emited_stats1)) } }
175
-
176
- it { refute decorated.key?("string_d") }
177
- it { refute decorated.key?("string_fd") }
178
- it { assert_equal 0, decorated["float_d"] }
179
- it { assert_in_delta 20, decorated["float_fd"], 5 }
180
- it { assert_equal 0, decorated["int_d"] }
181
- # On slow CIs this value tends to grow and crash
182
- it { assert_in_delta 20, decorated["int_fd"], 15 }
183
- it { assert_predicate decorated, :frozen? }
184
- it { refute decorated.key?("float_d_d") }
185
- end
186
-
187
- context "when a value type changed from non-numeric to numeric between emissions" do
188
- subject(:decorated) do
189
- decorator.call(emited_stats1)
190
- decorator.call(emited_stats2)
191
- end
192
-
193
- before do
194
- # In the first emission, txbytes is a string (unusual but defensive)
195
- emited_stats1["nested"]["brokers"]["localhost:9092/2"]["txbytes"] = "not_a_number"
196
- end
197
-
198
- # When previous value was non-numeric but current is numeric, no delta should be computed
199
- it { refute decorated.dig(*broker_scope).key?("txbytes_d") }
200
- it { refute decorated.dig(*broker_scope).key?("txbytes_fd") }
201
- it { assert_equal 153, decorated.dig(*broker_scope)["txbytes"] }
202
- it { assert_predicate decorated, :frozen? }
203
- end
204
-
205
- context "when excluded_keys are configured" do
206
- subject(:decorator) { described_class.new(excluded_keys: %w[nested]) }
207
-
208
- context "when it is a first stats emit" do
209
- subject(:decorated) { decorator.call(emited_stats1) }
210
-
211
- it { assert_equal 0, decorated["float_d"] }
212
- it { assert_equal 0, decorated["int_d"] }
213
- it { assert_predicate decorated, :frozen? }
214
-
215
- it "does not decorate excluded subtrees" do
216
- expected = { "brokers" => { "localhost:9092/2" => { "txbytes" => 123 } } }
217
-
218
- assert_equal(expected, decorated["nested"])
219
- refute decorated["nested"]["brokers"]["localhost:9092/2"].key?("txbytes_d")
220
- end
221
- end
222
-
223
- context "when it is a second stats emit" do
224
- subject(:decorated) do
225
- decorator.call(emited_stats1)
226
- decorator.call(emited_stats2)
227
- end
228
-
229
- it { assert_in_delta(0.4, decorated["float_d"].round(10)) }
230
- it { assert_equal 18, decorated["int_d"] }
231
- it { assert_predicate decorated, :frozen? }
232
-
233
- it "does not decorate excluded subtrees" do
234
- refute decorated["nested"]["brokers"]["localhost:9092/2"].key?("txbytes_d")
235
- refute decorated["nested"]["brokers"]["localhost:9092/2"].key?("txbytes_fd")
236
- end
237
- end
238
- end
239
-
240
- context "when excluded_keys target a numeric key" do
241
- let(:decorator) { described_class.new(excluded_keys: %w[int]) }
242
-
243
- subject(:decorated) do
244
- decorator.call(emited_stats1)
245
- decorator.call(emited_stats2)
246
- end
247
-
248
- it { assert_in_delta(0.4, decorated["float_d"].round(10)) }
249
- it { assert_equal 130, decorated["int"] }
250
- it { refute decorated.key?("int_d") }
251
- it { refute decorated.key?("int_fd") }
252
- it { assert_predicate decorated, :frozen? }
253
- end
254
-
255
- context "when no excluded_keys are configured" do
256
- let(:decorator) { described_class.new }
257
-
258
- subject(:decorated) do
259
- decorator.call(emited_stats1)
260
- decorator.call(emited_stats2)
261
- end
262
-
263
- it { assert_equal 18, decorated["int_d"] }
264
- it { assert_equal 30, decorated.dig(*broker_scope)["txbytes_d"] }
265
- end
266
-
267
- context "when a value type changed from numeric to non-numeric between emissions" do
268
- subject(:decorated) do
269
- decorator.call(emited_stats1)
270
- decorator.call(emited_stats2)
271
- end
272
-
273
- before do
274
- # In the second emission, txbytes changed to a string
275
- emited_stats2["nested"]["brokers"]["localhost:9092/2"]["txbytes"] = "not_a_number"
276
- end
277
-
278
- # Non-numeric values are never decorated
279
- it { refute decorated.dig(*broker_scope).key?("txbytes_d") }
280
- it { refute decorated.dig(*broker_scope).key?("txbytes_fd") }
281
- it { assert_equal "not_a_number", decorated.dig(*broker_scope)["txbytes"] }
282
- it { assert_predicate decorated, :frozen? }
283
- end
284
- end
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Each component is tested in its own spec file.
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- describe_current do
4
- subject(:producer) do
5
- config = { "bootstrap.servers": "localhost:10092" }
6
- Rdkafka::Config.new(config).producer
7
- end
8
-
9
- describe "#build_error_callback" do
10
- let(:errors) { [] }
11
- let(:callback) { ->(*args) { errors << args } }
12
-
13
- before { Rdkafka::Config.error_callback.add("test", callback) }
14
-
15
- after { Rdkafka::Config.error_callback.delete("test") }
16
-
17
- it "expect to inject instance name to the error callback" do
18
- producer.produce(topic: "test", payload: "1")
19
- sleep(0.01) while errors.empty?
20
-
21
- assert_includes errors.first.first, "rdkafka#producer"
22
- assert_equal :transport, errors.first.last.code
23
- end
24
- end
25
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- describe_current do
4
- subject(:tags) { described_class.new }
5
-
6
- it { assert_empty tags.to_a }
7
-
8
- describe "#add" do
9
- context "when few under same name" do
10
- before { 3.times { |value| tags.add(:name, value) } }
11
-
12
- it { assert_equal %w[2], tags.to_a }
13
- end
14
-
15
- context "when with different names" do
16
- before do
17
- tags.add(:name1, 1)
18
- tags.add(:name2, 2)
19
- tags.add(:name3, 3)
20
- end
21
-
22
- it { assert_equal %w[1 2 3], tags.to_a }
23
- end
24
-
25
- context "when with different names but same value" do
26
- before do
27
- tags.add(:name1, 1)
28
- tags.add(:name2, 1)
29
- tags.add(:name3, 1)
30
- end
31
-
32
- it { assert_equal %w[1], tags.to_a }
33
- end
34
- end
35
-
36
- describe "#clear" do
37
- before do
38
- tags.add(:name, 1)
39
- tags.clear
40
- end
41
-
42
- it { assert_empty tags.to_a }
43
- end
44
-
45
- describe "#delete" do
46
- before do
47
- tags.add(:name1, 1)
48
- tags.delete(:name1)
49
- tags.add(:name3, 2)
50
- end
51
-
52
- it { assert_equal %w[2], tags.to_a }
53
- end
54
-
55
- describe "#to_json" do
56
- before { tags.add(:test, "abc") }
57
-
58
- it { assert_equal %w[abc].to_json, tags.to_json }
59
- end
60
-
61
- describe "#as_json" do
62
- before { tags.add(:test, "abc") }
63
-
64
- it { assert_equal %w[abc], tags.as_json }
65
- end
66
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- describe_current do
4
- context "when operating on an instance basis" do
5
- subject(:tagged) { tagged_class.new }
6
-
7
- let(:other_tagged) { tagged_class.new }
8
- let(:tagged_class) do
9
- Class.new do
10
- include Karafka::Core::Taggable
11
- end
12
- end
13
-
14
- it { assert_empty tagged.tags.to_a }
15
- it { refute_equal other_tagged.tags, tagged.tags }
16
- end
17
-
18
- context "when operating on a class basis" do
19
- subject(:tagged) { tagged_class }
20
-
21
- let(:other_tagged) { other_tagged_class }
22
- let(:tagged_class) do
23
- Class.new do
24
- extend Karafka::Core::Taggable
25
- end
26
- end
27
- let(:other_tagged_class) do
28
- Class.new do
29
- extend Karafka::Core::Taggable
30
- end
31
- end
32
-
33
- it { assert_empty tagged.tags.to_a }
34
- it { refute_equal other_tagged.tags, tagged.tags }
35
- end
36
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- describe Karafka::Core::VERSION do
4
- it { described_class }
5
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- describe_current do
4
- subject(:core) { described_class }
5
-
6
- describe "#gem_root" do
7
- context "when we want to get gem root path" do
8
- let(:path) { Dir.pwd }
9
-
10
- it { assert_equal path, core.gem_root.to_path }
11
- end
12
- end
13
- end
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Each component is tested in its own spec file.
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Class builder helps creating anonymous classes that we can use to spec our code
4
- # We need co create new class instances to have an "empty" and "clear" class for each spec
5
- # This module acts as an interface to create classes
6
- module ClassBuilder
7
- class << self
8
- # Creates an empty class without any predefined methods
9
- # @return [Class] created anonymous class
10
- def build(&)
11
- klass = Class.new
12
-
13
- klass.class_eval(&) if block_given?
14
- klass
15
- end
16
-
17
- # This method allows us to create a class that inherits from any other
18
- # @param klass [Class] any class from which we want to inherit in our anonymous class
19
- # @return [Class] new anonymous class
20
- def inherit(klass, &)
21
- Class.new(klass, &)
22
- end
23
- end
24
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Port of Karafka::Core::Helpers::RSpecLocator for minitest/spec
4
- # Provides `describe_current` that auto-discovers the class under test from file path,
5
- # `described_class` and `subject` DSL methods.
6
-
7
- require "karafka/core/helpers/minitest_locator"
8
-
9
- extend Karafka::Core::Helpers::MinitestLocator.new(
10
- File.expand_path("../test_helper.rb", __dir__)
11
- )
12
-
13
- # Provide `described_class` for minitest/spec — walks the desc hierarchy to find a Class/Module
14
- module MinitestDescribedClass
15
- def described_class
16
- # Walk up the describe hierarchy to find the Class/Module
17
- klass = self.class
18
- while klass
19
- return klass.desc if klass.respond_to?(:desc) && klass.desc.is_a?(Module)
20
-
21
- klass = klass.superclass
22
- end
23
- nil
24
- end
25
- end
26
-
27
- Minitest::Spec.include MinitestDescribedClass
28
-
29
- # Provide `subject` DSL for minitest/spec
30
- module MinitestSubjectDSL
31
- def subject(name = nil, &block)
32
- if name
33
- let(name, &block)
34
- define_method(:subject) { send(name) }
35
- else
36
- let(:subject, &block)
37
- end
38
- end
39
- end
40
-
41
- Minitest::Spec.extend MinitestSubjectDSL
data/test/test_helper.rb DELETED
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Warning[:performance] = true if RUBY_VERSION >= "3.3"
4
- Warning[:deprecated] = true
5
- $VERBOSE = true
6
-
7
- require "warning"
8
-
9
- Warning.process do |warning|
10
- next unless warning.include?(Dir.pwd)
11
-
12
- raise "Warning in your code: #{warning}"
13
- end
14
-
15
- ENV["KARAFKA_ENV"] = "test"
16
- $LOAD_PATH.unshift(File.dirname(__FILE__))
17
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
18
-
19
- %w[
20
- byebug
21
- simplecov
22
- tempfile
23
- securerandom
24
- ].each do |lib|
25
- require lib
26
- end
27
-
28
- # Don't include unnecessary stuff into rcov
29
- SimpleCov.start do
30
- add_filter "/vendor/"
31
- add_filter "/gems/"
32
- add_filter "/.bundle/"
33
- add_filter "/doc/"
34
- add_filter "/test/"
35
- add_filter "/config/"
36
- add_filter "/patches/"
37
- merge_timeout 600
38
- end
39
-
40
- SimpleCov.minimum_coverage(98.8)
41
-
42
- require "minitest/autorun"
43
- require "minitest/spec"
44
-
45
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"]
46
- .each { |f| require f }
47
-
48
- require "karafka-core"
49
-
50
- # Allow `context` as an alias for `describe` in minitest/spec
51
- Minitest::Spec.class_eval do
52
- class << self
53
- alias_method :context, :describe
54
- end
55
- end