waterdrop 2.3.2 → 2.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +8 -48
- data/README.md +46 -7
- data/config/errors.yml +28 -5
- data/lib/waterdrop/config.rb +2 -2
- data/lib/waterdrop/contracts/config.rb +25 -15
- data/lib/waterdrop/contracts/message.rb +40 -20
- data/lib/waterdrop/instrumentation/callbacks/statistics.rb +1 -1
- data/lib/waterdrop/instrumentation/monitor.rb +12 -35
- data/lib/waterdrop/instrumentation/notifications.rb +38 -0
- data/lib/waterdrop/instrumentation/vendors/datadog/listener.rb +44 -31
- data/lib/waterdrop/version.rb +1 -1
- data/lib/waterdrop.rb +2 -3
- data/waterdrop.gemspec +1 -3
- data.tar.gz.sig +0 -0
- metadata +11 -37
- metadata.gz.sig +4 -1
- data/lib/waterdrop/configurable/leaf.rb +0 -8
- data/lib/waterdrop/configurable/node.rb +0 -100
- data/lib/waterdrop/configurable.rb +0 -71
- data/lib/waterdrop/contracts/base.rb +0 -23
- data/lib/waterdrop/instrumentation/callbacks/statistics_decorator.rb +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70b84fa09ce807d6d72679917d6c9b98a4dd15b9eb50f0ae9c556acc6f084632
|
4
|
+
data.tar.gz: 8ccffe9e5331865dc93ab4c8b4b8ace2e0c3297f164c378b7919a14e307edf70
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66f25845bada1a7de0f6bd100b7966f246604701f0394891bc959817f222b739a73fc674b49d288ebf3674c85e9872db37069771f347fad98eeade561373610c
|
7
|
+
data.tar.gz: 308eafaec616bf936517086de4bcee0efa03ed555ad91a061588b781d9214198fa67258a0d7044eae523fbba4da57fa447ddafdcf3169a234cdc4b0785151c6e
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# WaterDrop changelog
|
2
2
|
|
3
|
+
## 2.4.1 (2022-08-01)
|
4
|
+
- Replace local statistics decorator with the one extracted to `karafka-core`.
|
5
|
+
|
6
|
+
## 2.4.0 (2022-07-28)
|
7
|
+
- Small refactor of the DataDog/Statsd listener to align for future extraction to `karafka-common`.
|
8
|
+
- Replace `dry-monitor` with home-brew notification layer (API compatible) and allow for usage with `ActiveSupport::Notifications`.
|
9
|
+
- Remove all the common code into `karafka-core` and add it as a dependency.
|
10
|
+
|
11
|
+
## 2.3.3 (2022-07-18)
|
12
|
+
- Replace `dry-validation` with home-brew validation layer and drop direct dependency on `dry-validation`.
|
13
|
+
- Remove indirect dependency on dry-configurable from DataDog listener (no changes required).
|
14
|
+
|
3
15
|
## 2.3.2 (2022-07-17)
|
4
16
|
- Replace `dry-configurable` with home-brew config and drop direct dependency on `dry-configurable`.
|
5
17
|
|
data/Gemfile.lock
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
waterdrop (2.
|
5
|
-
|
6
|
-
dry-monitor (~> 0.5)
|
7
|
-
dry-validation (~> 1.7)
|
4
|
+
waterdrop (2.4.1)
|
5
|
+
karafka-core (>= 2.0.2, < 3.0.0)
|
8
6
|
rdkafka (>= 0.10)
|
9
7
|
zeitwerk (~> 2.3)
|
10
8
|
|
11
9
|
GEM
|
12
10
|
remote: https://rubygems.org/
|
13
11
|
specs:
|
14
|
-
activesupport (7.0.3)
|
12
|
+
activesupport (7.0.3.1)
|
15
13
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
16
14
|
i18n (>= 1.6, < 2)
|
17
15
|
minitest (>= 5.1)
|
@@ -20,52 +18,15 @@ GEM
|
|
20
18
|
concurrent-ruby (1.1.10)
|
21
19
|
diff-lcs (1.5.0)
|
22
20
|
docile (1.4.0)
|
23
|
-
dry-configurable (0.15.0)
|
24
|
-
concurrent-ruby (~> 1.0)
|
25
|
-
dry-core (~> 0.6)
|
26
|
-
dry-container (0.10.0)
|
27
|
-
concurrent-ruby (~> 1.0)
|
28
|
-
dry-core (0.8.0)
|
29
|
-
concurrent-ruby (~> 1.0)
|
30
|
-
dry-events (0.3.0)
|
31
|
-
concurrent-ruby (~> 1.0)
|
32
|
-
dry-core (~> 0.5, >= 0.5)
|
33
|
-
dry-inflector (0.3.0)
|
34
|
-
dry-initializer (3.1.1)
|
35
|
-
dry-logic (1.2.0)
|
36
|
-
concurrent-ruby (~> 1.0)
|
37
|
-
dry-core (~> 0.5, >= 0.5)
|
38
|
-
dry-monitor (0.6.1)
|
39
|
-
dry-configurable (~> 0.13, >= 0.13.0)
|
40
|
-
dry-core (~> 0.5, >= 0.5)
|
41
|
-
dry-events (~> 0.2)
|
42
|
-
zeitwerk (~> 2.5)
|
43
|
-
dry-schema (1.9.3)
|
44
|
-
concurrent-ruby (~> 1.0)
|
45
|
-
dry-configurable (~> 0.13, >= 0.13.0)
|
46
|
-
dry-core (~> 0.5, >= 0.5)
|
47
|
-
dry-initializer (~> 3.0)
|
48
|
-
dry-logic (~> 1.0)
|
49
|
-
dry-types (~> 1.5)
|
50
|
-
dry-types (1.5.1)
|
51
|
-
concurrent-ruby (~> 1.0)
|
52
|
-
dry-container (~> 0.3)
|
53
|
-
dry-core (~> 0.5, >= 0.5)
|
54
|
-
dry-inflector (~> 0.1, >= 0.1.2)
|
55
|
-
dry-logic (~> 1.0, >= 1.0.2)
|
56
|
-
dry-validation (1.8.1)
|
57
|
-
concurrent-ruby (~> 1.0)
|
58
|
-
dry-container (~> 0.7, >= 0.7.1)
|
59
|
-
dry-core (~> 0.5, >= 0.5)
|
60
|
-
dry-initializer (~> 3.0)
|
61
|
-
dry-schema (~> 1.8, >= 1.8.0)
|
62
21
|
factory_bot (6.2.1)
|
63
22
|
activesupport (>= 5.0.0)
|
64
23
|
ffi (1.15.5)
|
65
|
-
i18n (1.
|
24
|
+
i18n (1.12.0)
|
66
25
|
concurrent-ruby (~> 1.0)
|
26
|
+
karafka-core (2.0.2)
|
27
|
+
concurrent-ruby (>= 1.1)
|
67
28
|
mini_portile2 (2.8.0)
|
68
|
-
minitest (5.16.
|
29
|
+
minitest (5.16.2)
|
69
30
|
rake (13.0.6)
|
70
31
|
rdkafka (0.12.0)
|
71
32
|
ffi (~> 1.15)
|
@@ -90,12 +51,11 @@ GEM
|
|
90
51
|
simplecov_json_formatter (~> 0.1)
|
91
52
|
simplecov-html (0.12.3)
|
92
53
|
simplecov_json_formatter (0.1.4)
|
93
|
-
tzinfo (2.0.
|
54
|
+
tzinfo (2.0.5)
|
94
55
|
concurrent-ruby (~> 1.0)
|
95
56
|
zeitwerk (2.6.0)
|
96
57
|
|
97
58
|
PLATFORMS
|
98
|
-
arm64-darwin-21
|
99
59
|
x86_64-linux
|
100
60
|
|
101
61
|
DEPENDENCIES
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
**Note**: Documentation presented here refers to WaterDrop `2.x`.
|
4
4
|
|
5
|
-
WaterDrop `2.x`
|
5
|
+
WaterDrop `2.x` works with Karafka `2.*` and aims to either work as a standalone producer or as a part of the Karafka `2.*`.
|
6
6
|
|
7
7
|
Please refer to [this](https://github.com/karafka/waterdrop/tree/1.4) branch and its documentation for details about WaterDrop `1.*` usage.
|
8
8
|
|
@@ -10,17 +10,17 @@ Please refer to [this](https://github.com/karafka/waterdrop/tree/1.4) branch and
|
|
10
10
|
[![Gem Version](https://badge.fury.io/rb/waterdrop.svg)](http://badge.fury.io/rb/waterdrop)
|
11
11
|
[![Join the chat at https://slack.karafka.io](https://raw.githubusercontent.com/karafka/misc/master/slack.svg)](https://slack.karafka.io)
|
12
12
|
|
13
|
-
|
13
|
+
A gem to send messages to Kafka easily with an extra validation layer. It is a part of the [Karafka](https://github.com/karafka/karafka) ecosystem.
|
14
14
|
|
15
15
|
It:
|
16
16
|
|
17
|
-
- Is thread
|
17
|
+
- Is thread-safe
|
18
18
|
- Supports sync producing
|
19
19
|
- Supports async producing
|
20
20
|
- Supports buffering
|
21
21
|
- Supports producing messages to multiple clusters
|
22
22
|
- Supports multiple delivery policies
|
23
|
-
- Works with Kafka 1.0
|
23
|
+
- Works with Kafka `1.0+` and Ruby `2.7+`
|
24
24
|
|
25
25
|
## Table of contents
|
26
26
|
|
@@ -30,6 +30,8 @@ It:
|
|
30
30
|
* [Kafka configuration options](#kafka-configuration-options)
|
31
31
|
- [Usage](#usage)
|
32
32
|
* [Basic usage](#basic-usage)
|
33
|
+
* [Using WaterDrop across the application and with Ruby on Rails](#using-waterdrop-across-the-application-and-with-ruby-on-rails)
|
34
|
+
* [Using WaterDrop with a connection-pool](#using-waterdrop-with-a-connection-pool)
|
33
35
|
* [Buffering](#buffering)
|
34
36
|
+ [Using WaterDrop to buffer messages based on the application logic](#using-waterdrop-to-buffer-messages-based-on-the-application-logic)
|
35
37
|
+ [Using WaterDrop with rdkafka buffers to achieve periodic auto-flushing](#using-waterdrop-with-rdkafka-buffers-to-achieve-periodic-auto-flushing)
|
@@ -42,6 +44,8 @@ It:
|
|
42
44
|
|
43
45
|
## Installation
|
44
46
|
|
47
|
+
**Note**: If you want to both produce and consume messages, please use [Karafka](https://github.com/karafka/karafka/). It integrates WaterDrop automatically.
|
48
|
+
|
45
49
|
Add this to your Gemfile:
|
46
50
|
|
47
51
|
```ruby
|
@@ -56,7 +60,7 @@ bundle install
|
|
56
60
|
|
57
61
|
## Setup
|
58
62
|
|
59
|
-
WaterDrop is a complex tool
|
63
|
+
WaterDrop is a complex tool that contains multiple configuration options. To keep everything organized, all the configuration options were divided into two groups:
|
60
64
|
|
61
65
|
- WaterDrop options - options directly related to WaterDrop and its components
|
62
66
|
- Kafka driver options - options related to `rdkafka`
|
@@ -103,8 +107,6 @@ You can create producers with different `kafka` settings. Documentation of the a
|
|
103
107
|
|
104
108
|
## Usage
|
105
109
|
|
106
|
-
Please refer to the [documentation](https://www.rubydoc.info/gems/waterdrop) in case you're interested in the more advanced API.
|
107
|
-
|
108
110
|
### Basic usage
|
109
111
|
|
110
112
|
To send Kafka messages, just create a producer and use it:
|
@@ -157,6 +159,41 @@ Here are all the things you can provide in the message hash:
|
|
157
159
|
|
158
160
|
Keep in mind, that message you want to send should be either binary or stringified (to_s, to_json, etc).
|
159
161
|
|
162
|
+
### Using WaterDrop across the application and with Ruby on Rails
|
163
|
+
|
164
|
+
If you plan to both produce and consume messages using Kafka, you should install and use [Karafka](https://github.com/karafka/karafka). It integrates automatically with Ruby on Rails applications and auto-configures WaterDrop producer to make it accessible via `Karafka#producer` method.
|
165
|
+
|
166
|
+
If you want to only produce messages from within your application, since WaterDrop is thread-safe you can create a single instance in an initializer like so:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
KAFKA_PRODUCER = WaterDrop::Producer.new
|
170
|
+
|
171
|
+
KAFKA_PRODUCER.setup do |config|
|
172
|
+
config.kafka = { 'bootstrap.servers': 'localhost:9092' }
|
173
|
+
end
|
174
|
+
|
175
|
+
# And just dispatch messages
|
176
|
+
KAFKA_PRODUCER.produce_sync(topic: 'my-topic', payload: 'my message')
|
177
|
+
```
|
178
|
+
|
179
|
+
### Using WaterDrop with a connection-pool
|
180
|
+
|
181
|
+
While WaterDrop is thread-safe, there is no problem in using it with a connection pool inside high-intensity applications. The only thing worth keeping in mind, is that WaterDrop instances should be shutdown before the application is closed.
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
KAFKA_PRODUCERS_CP = ConnectionPool.new do
|
185
|
+
WaterDrop::Producer.new do |config|
|
186
|
+
config.kafka = { 'bootstrap.servers': 'localhost:9092' }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
KAFKA_PRODUCERS_CP.with do |producer|
|
191
|
+
producer.produce_async(topic: 'my-topic', payload: 'my message')
|
192
|
+
end
|
193
|
+
|
194
|
+
KAFKA_PRODUCERS_CP.shutdown { |producer| producer.close }
|
195
|
+
```
|
196
|
+
|
160
197
|
### Buffering
|
161
198
|
|
162
199
|
WaterDrop producers support buffering messages in their internal buffers and on the `rdkafka` level via `queue.buffering.*` set of settings.
|
@@ -316,6 +353,8 @@ producer.monitor.subscribe(listener)
|
|
316
353
|
|
317
354
|
You can also find [here](https://github.com/karafka/waterdrop/blob/master/lib/waterdrop/instrumentation/vendors/datadog/dashboard.json) a ready to import DataDog dashboard configuration file that you can use to monitor all of your producers.
|
318
355
|
|
356
|
+
![Example WaterDrop DD dashboard](https://raw.githubusercontent.com/karafka/misc/master/printscreens/waterdrop_dd_dashboard_example.png)
|
357
|
+
|
319
358
|
### Error notifications
|
320
359
|
|
321
360
|
WaterDrop allows you to listen to all errors that occur while producing messages and in its internal background threads. Things like reconnecting to Kafka upon network errors and others unrelated to publishing messages are all available under `error.occurred` notification key. You can subscribe to this event to ensure your setup is healthy and without any problems that would otherwise go unnoticed as long as messages are delivered.
|
data/config/errors.yml
CHANGED
@@ -1,7 +1,30 @@
|
|
1
1
|
en:
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
validations:
|
3
|
+
config:
|
4
|
+
missing: must be present
|
5
|
+
logger_format: must be present
|
6
|
+
deliver_format: must be boolean
|
7
|
+
id_format: must be a non-empty string
|
8
|
+
max_payload_size_format: must be an integer that is equal or bigger than 1
|
9
|
+
wait_timeout_format: must be a numeric that is bigger than 0
|
10
|
+
max_wait_timeout_format: must be an integer that is equal or bigger than 0
|
11
|
+
kafka_format: must be a hash with symbol based keys
|
7
12
|
kafka_key_must_be_a_symbol: All keys under the kafka settings scope need to be symbols
|
13
|
+
|
14
|
+
message:
|
15
|
+
missing: must be present
|
16
|
+
partition_format: must be an integer greater or equal to -1
|
17
|
+
topic_format: 'does not match the topic allowed format'
|
18
|
+
partition_key_format: must be a non-empty string
|
19
|
+
timestamp_format: must be either time or integer
|
20
|
+
payload_format: must be string
|
21
|
+
headers_format: must be a hash
|
22
|
+
key_format: must be a non-empty string
|
23
|
+
payload_max_size: is more than `max_payload_size` config value
|
24
|
+
headers_invalid_key_type: all headers keys need to be of type String
|
25
|
+
headers_invalid_value_type: all headers values need to be of type String
|
26
|
+
|
27
|
+
test:
|
28
|
+
missing: must be present
|
29
|
+
nested.id_format: 'is invalid'
|
30
|
+
nested.id2_format: 'is invalid'
|
data/lib/waterdrop/config.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
module WaterDrop
|
6
6
|
# Configuration object for setting up all options required by WaterDrop
|
7
7
|
class Config
|
8
|
-
include Configurable
|
8
|
+
include ::Karafka::Core::Configurable
|
9
9
|
|
10
10
|
# Defaults for kafka settings, that will be overwritten only if not present already
|
11
11
|
KAFKA_DEFAULTS = {
|
@@ -77,7 +77,7 @@ module WaterDrop
|
|
77
77
|
# Propagates the kafka setting defaults unless they are already present
|
78
78
|
# This makes it easier to set some values that users usually don't change but still allows them
|
79
79
|
# to overwrite the whole hash if they want to
|
80
|
-
# @param config [
|
80
|
+
# @param config [WaterDrop::Configurable::Node] config of this producer
|
81
81
|
def merge_kafka_defaults!(config)
|
82
82
|
KAFKA_DEFAULTS.each do |key, value|
|
83
83
|
next if config.kafka.key?(key)
|
@@ -3,27 +3,37 @@
|
|
3
3
|
module WaterDrop
|
4
4
|
module Contracts
|
5
5
|
# Contract with validation rules for WaterDrop configuration details
|
6
|
-
class Config <
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
required(:wait_timeout).filled(:number?, gt?: 0)
|
14
|
-
required(:kafka).filled(:hash?)
|
6
|
+
class Config < ::Karafka::Core::Contractable::Contract
|
7
|
+
configure do |config|
|
8
|
+
config.error_messages = YAML.safe_load(
|
9
|
+
File.read(
|
10
|
+
File.join(WaterDrop.gem_root, 'config', 'errors.yml')
|
11
|
+
)
|
12
|
+
).fetch('en').fetch('validations').fetch('config')
|
15
13
|
end
|
16
14
|
|
15
|
+
required(:id) { |val| val.is_a?(String) && !val.empty? }
|
16
|
+
required(:logger) { |val| !val.nil? }
|
17
|
+
required(:deliver) { |val| [true, false].include?(val) }
|
18
|
+
required(:max_payload_size) { |val| val.is_a?(Integer) && val >= 1 }
|
19
|
+
required(:max_wait_timeout) { |val| val.is_a?(Numeric) && val >= 0 }
|
20
|
+
required(:wait_timeout) { |val| val.is_a?(Numeric) && val.positive? }
|
21
|
+
required(:kafka) { |val| val.is_a?(Hash) && !val.empty? }
|
22
|
+
|
17
23
|
# rdkafka allows both symbols and strings as keys for config but then casts them to strings
|
18
24
|
# This can be confusing, so we expect all keys to be symbolized
|
19
|
-
|
20
|
-
next unless
|
25
|
+
virtual do |config, errors|
|
26
|
+
next true unless errors.empty?
|
27
|
+
|
28
|
+
errors = []
|
21
29
|
|
22
|
-
|
23
|
-
|
30
|
+
config
|
31
|
+
.fetch(:kafka)
|
32
|
+
.keys
|
33
|
+
.reject { |key| key.is_a?(Symbol) }
|
34
|
+
.each { |key| errors << [[:kafka, key], :kafka_key_must_be_a_symbol] }
|
24
35
|
|
25
|
-
|
26
|
-
end
|
36
|
+
errors
|
27
37
|
end
|
28
38
|
end
|
29
39
|
end
|
@@ -4,36 +4,56 @@ module WaterDrop
|
|
4
4
|
module Contracts
|
5
5
|
# Contract with validation rules for validating that all the message options that
|
6
6
|
# we provide to producer ale valid and usable
|
7
|
-
class Message <
|
7
|
+
class Message < ::Karafka::Core::Contractable::Contract
|
8
|
+
configure do |config|
|
9
|
+
config.error_messages = YAML.safe_load(
|
10
|
+
File.read(
|
11
|
+
File.join(WaterDrop.gem_root, 'config', 'errors.yml')
|
12
|
+
)
|
13
|
+
).fetch('en').fetch('validations').fetch('message')
|
14
|
+
end
|
15
|
+
|
8
16
|
# Regex to check that topic has a valid format
|
9
17
|
TOPIC_REGEXP = /\A(\w|-|\.)+\z/
|
10
18
|
|
11
|
-
|
12
|
-
STRING_ASSERTION = ->(value) { value.is_a?(String) }.to_proc
|
19
|
+
private_constant :TOPIC_REGEXP
|
13
20
|
|
14
|
-
|
21
|
+
attr_reader :max_payload_size
|
15
22
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
required(:payload).filled(:str?)
|
21
|
-
optional(:key).maybe(:str?, :filled?)
|
22
|
-
optional(:partition).filled(:int?, gteq?: -1)
|
23
|
-
optional(:partition_key).maybe(:str?, :filled?)
|
24
|
-
optional(:timestamp).maybe { time? | int? }
|
25
|
-
optional(:headers).maybe(:hash?)
|
23
|
+
# @param max_payload_size [Integer] max payload size
|
24
|
+
def initialize(max_payload_size:)
|
25
|
+
super()
|
26
|
+
@max_payload_size = max_payload_size
|
26
27
|
end
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
required(:topic) { |val| val.is_a?(String) && TOPIC_REGEXP.match?(val) }
|
30
|
+
required(:payload) { |val| val.is_a?(String) }
|
31
|
+
optional(:key) { |val| val.nil? || (val.is_a?(String) && !val.empty?) }
|
32
|
+
optional(:partition) { |val| val.is_a?(Integer) && val >= -1 }
|
33
|
+
optional(:partition_key) { |val| val.nil? || (val.is_a?(String) && !val.empty?) }
|
34
|
+
optional(:timestamp) { |val| val.nil? || (val.is_a?(Time) || val.is_a?(Integer)) }
|
35
|
+
optional(:headers) { |val| val.nil? || val.is_a?(Hash) }
|
36
|
+
|
37
|
+
virtual do |config, errors|
|
38
|
+
next true unless errors.empty?
|
39
|
+
next true unless config.key?(:headers)
|
40
|
+
next true if config[:headers].nil?
|
30
41
|
|
31
|
-
|
32
|
-
|
42
|
+
errors = []
|
43
|
+
|
44
|
+
config.fetch(:headers).each do |key, value|
|
45
|
+
errors << [%i[headers], :invalid_key_type] unless key.is_a?(String)
|
46
|
+
errors << [%i[headers], :invalid_value_type] unless value.is_a?(String)
|
47
|
+
end
|
48
|
+
|
49
|
+
errors
|
33
50
|
end
|
34
51
|
|
35
|
-
|
36
|
-
|
52
|
+
virtual do |config, errors, validator|
|
53
|
+
next true unless errors.empty?
|
54
|
+
next true if config[:payload].bytesize <= validator.max_payload_size
|
55
|
+
|
56
|
+
[[%i[payload], :max_size]]
|
37
57
|
end
|
38
58
|
end
|
39
59
|
end
|
@@ -17,7 +17,7 @@ module WaterDrop
|
|
17
17
|
@producer_id = producer_id
|
18
18
|
@client_name = client_name
|
19
19
|
@monitor = monitor
|
20
|
-
@statistics_decorator = StatisticsDecorator.new
|
20
|
+
@statistics_decorator = ::Karafka::Core::Monitoring::StatisticsDecorator.new
|
21
21
|
end
|
22
22
|
|
23
23
|
# Emits decorated statistics to the monitor
|
@@ -2,41 +2,18 @@
|
|
2
2
|
|
3
3
|
module WaterDrop
|
4
4
|
module Instrumentation
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
message.produced_async
|
18
|
-
message.produced_sync
|
19
|
-
message.acknowledged
|
20
|
-
message.buffered
|
21
|
-
|
22
|
-
messages.produced_async
|
23
|
-
messages.produced_sync
|
24
|
-
messages.buffered
|
25
|
-
|
26
|
-
buffer.flushed_async
|
27
|
-
buffer.flushed_sync
|
28
|
-
|
29
|
-
statistics.emitted
|
30
|
-
|
31
|
-
error.occurred
|
32
|
-
].freeze
|
33
|
-
|
34
|
-
private_constant :EVENTS
|
35
|
-
|
36
|
-
# @return [WaterDrop::Instrumentation::Monitor] monitor instance for system instrumentation
|
37
|
-
def initialize
|
38
|
-
super(:waterdrop)
|
39
|
-
EVENTS.each(&method(:register_event))
|
5
|
+
# WaterDrop instrumentation monitor that we use to publish events
|
6
|
+
# By default uses our internal notifications bus but can be used with
|
7
|
+
# `ActiveSupport::Notifications` as well
|
8
|
+
class Monitor < ::Karafka::Core::Monitoring::Monitor
|
9
|
+
# @param notifications_bus [Object] either our internal notifications bus or
|
10
|
+
# `ActiveSupport::Notifications`
|
11
|
+
# @param namespace [String, nil] namespace for events or nil if no namespace
|
12
|
+
def initialize(
|
13
|
+
notifications_bus = WaterDrop::Instrumentation::Notifications.new,
|
14
|
+
namespace = nil
|
15
|
+
)
|
16
|
+
super(notifications_bus, namespace)
|
40
17
|
end
|
41
18
|
end
|
42
19
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WaterDrop
|
4
|
+
module Instrumentation
|
5
|
+
# Instrumented is used to hookup external monitoring services to monitor how WaterDrop works
|
6
|
+
class Notifications < ::Karafka::Core::Monitoring::Notifications
|
7
|
+
# List of events that we support in the system and to which a monitor client can hook up
|
8
|
+
# @note The non-error once support timestamp benchmarking
|
9
|
+
EVENTS = %w[
|
10
|
+
producer.closed
|
11
|
+
|
12
|
+
message.produced_async
|
13
|
+
message.produced_sync
|
14
|
+
message.acknowledged
|
15
|
+
message.buffered
|
16
|
+
|
17
|
+
messages.produced_async
|
18
|
+
messages.produced_sync
|
19
|
+
messages.buffered
|
20
|
+
|
21
|
+
buffer.flushed_async
|
22
|
+
buffer.flushed_sync
|
23
|
+
|
24
|
+
statistics.emitted
|
25
|
+
|
26
|
+
error.occurred
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
private_constant :EVENTS
|
30
|
+
|
31
|
+
# @return [WaterDrop::Instrumentation::Monitor] monitor instance for system instrumentation
|
32
|
+
def initialize
|
33
|
+
super
|
34
|
+
EVENTS.each { |event| register_event(event) }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -11,26 +11,29 @@ module WaterDrop
|
|
11
11
|
#
|
12
12
|
# @note You need to setup the `dogstatsd-ruby` client and assign it
|
13
13
|
class Listener
|
14
|
-
include
|
14
|
+
include ::Karafka::Core::Configurable
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
def_delegators :config, :client, :rd_kafka_metrics, :namespace, :default_tags
|
15
18
|
|
16
19
|
# Value object for storing a single rdkafka metric publishing details
|
17
20
|
RdKafkaMetric = Struct.new(:type, :scope, :name, :key_location)
|
18
21
|
|
19
22
|
# Namespace under which the DD metrics should be published
|
20
|
-
setting :namespace, default: 'waterdrop'
|
23
|
+
setting :namespace, default: 'waterdrop'
|
21
24
|
|
22
25
|
# Datadog client that we should use to publish the metrics
|
23
|
-
setting :client
|
26
|
+
setting :client
|
24
27
|
|
25
28
|
# Default tags we want to publish (for example hostname)
|
26
29
|
# Format as followed (example for hostname): `["host:#{Socket.gethostname}"]`
|
27
|
-
setting :default_tags, default: []
|
30
|
+
setting :default_tags, default: []
|
28
31
|
|
29
32
|
# All the rdkafka metrics we want to publish
|
30
33
|
#
|
31
34
|
# By default we publish quite a lot so this can be tuned
|
32
35
|
# Note, that the once with `_d` come from WaterDrop, not rdkafka or Kafka
|
33
|
-
setting :rd_kafka_metrics,
|
36
|
+
setting :rd_kafka_metrics, default: [
|
34
37
|
# Client metrics
|
35
38
|
RdKafkaMetric.new(:count, :root, 'calls', 'tx_d'),
|
36
39
|
RdKafkaMetric.new(:histogram, :root, 'queue.size', 'msg_cnt_d'),
|
@@ -47,8 +50,11 @@ module WaterDrop
|
|
47
50
|
RdKafkaMetric.new(:gauge, :brokers, 'network.latency.p99', %w[rtt p99])
|
48
51
|
].freeze
|
49
52
|
|
53
|
+
configure
|
54
|
+
|
50
55
|
# @param block [Proc] configuration block
|
51
56
|
def initialize(&block)
|
57
|
+
configure
|
52
58
|
setup(&block) if block
|
53
59
|
end
|
54
60
|
|
@@ -60,7 +66,7 @@ module WaterDrop
|
|
60
66
|
|
61
67
|
# Hooks up to WaterDrop instrumentation for emitted statistics
|
62
68
|
#
|
63
|
-
# @param event [
|
69
|
+
# @param event [WaterDrop::Monitor::Event]
|
64
70
|
def on_statistics_emitted(event)
|
65
71
|
statistics = event[:statistics]
|
66
72
|
|
@@ -71,22 +77,15 @@ module WaterDrop
|
|
71
77
|
|
72
78
|
# Increases the errors count by 1
|
73
79
|
#
|
74
|
-
# @param _event [
|
80
|
+
# @param _event [WaterDrop::Monitor::Event]
|
75
81
|
def on_error_occurred(_event)
|
76
|
-
|
77
|
-
namespaced_metric('error_occurred'),
|
78
|
-
1,
|
79
|
-
tags: default_tags
|
80
|
-
)
|
82
|
+
count('error_occurred', 1, tags: default_tags)
|
81
83
|
end
|
82
84
|
|
83
85
|
# Increases acknowledged messages counter
|
84
|
-
# @param _event [
|
86
|
+
# @param _event [WaterDrop::Monitor::Event]
|
85
87
|
def on_message_acknowledged(_event)
|
86
|
-
|
87
|
-
namespaced_metric('acknowledged'),
|
88
|
-
tags: default_tags
|
89
|
-
)
|
88
|
+
increment('acknowledged', tags: default_tags)
|
90
89
|
end
|
91
90
|
|
92
91
|
%i[
|
@@ -94,12 +93,12 @@ module WaterDrop
|
|
94
93
|
produced_async
|
95
94
|
].each do |event_scope|
|
96
95
|
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
97
|
-
# @param event [
|
96
|
+
# @param event [WaterDrop::Monitor::Event]
|
98
97
|
def on_message_#{event_scope}(event)
|
99
98
|
report_message(event[:message][:topic], :#{event_scope})
|
100
99
|
end
|
101
100
|
|
102
|
-
# @param event [
|
101
|
+
# @param event [WaterDrop::Monitor::Event]
|
103
102
|
def on_messages_#{event_scope}(event)
|
104
103
|
event[:messages].each do |message|
|
105
104
|
report_message(message[:topic], :#{event_scope})
|
@@ -114,10 +113,10 @@ module WaterDrop
|
|
114
113
|
messages_buffered
|
115
114
|
].each do |event_scope|
|
116
115
|
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
117
|
-
# @param event [
|
116
|
+
# @param event [WaterDrop::Monitor::Event]
|
118
117
|
def on_#{event_scope}(event)
|
119
|
-
|
120
|
-
|
118
|
+
histogram(
|
119
|
+
'buffer.size',
|
121
120
|
event[:buffer].size,
|
122
121
|
tags: default_tags
|
123
122
|
)
|
@@ -132,7 +131,7 @@ module WaterDrop
|
|
132
131
|
flushed_async
|
133
132
|
].each do |event_scope|
|
134
133
|
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
135
|
-
# @param event [
|
134
|
+
# @param event [WaterDrop::Monitor::Event]
|
136
135
|
def on_buffer_#{event_scope}(event)
|
137
136
|
event[:messages].each do |message|
|
138
137
|
report_message(message[:topic], :#{event_scope})
|
@@ -143,14 +142,28 @@ module WaterDrop
|
|
143
142
|
|
144
143
|
private
|
145
144
|
|
145
|
+
%i[
|
146
|
+
count
|
147
|
+
gauge
|
148
|
+
histogram
|
149
|
+
increment
|
150
|
+
decrement
|
151
|
+
].each do |metric_type|
|
152
|
+
class_eval <<~METHODS, __FILE__, __LINE__ + 1
|
153
|
+
def #{metric_type}(key, *args)
|
154
|
+
client.#{metric_type}(
|
155
|
+
namespaced_metric(key),
|
156
|
+
*args
|
157
|
+
)
|
158
|
+
end
|
159
|
+
METHODS
|
160
|
+
end
|
161
|
+
|
146
162
|
# Report that a message has been produced to a topic.
|
147
163
|
# @param topic [String] Kafka topic
|
148
164
|
# @param method_name [Symbol] method from which this message operation comes
|
149
165
|
def report_message(topic, method_name)
|
150
|
-
|
151
|
-
namespaced_metric(method_name),
|
152
|
-
tags: default_tags + ["topic:#{topic}"]
|
153
|
-
)
|
166
|
+
increment(method_name, tags: default_tags + ["topic:#{topic}"])
|
154
167
|
end
|
155
168
|
|
156
169
|
# Wraps metric name in listener's namespace
|
@@ -166,9 +179,9 @@ module WaterDrop
|
|
166
179
|
def report_metric(metric, statistics)
|
167
180
|
case metric.scope
|
168
181
|
when :root
|
169
|
-
|
182
|
+
public_send(
|
170
183
|
metric.type,
|
171
|
-
|
184
|
+
metric.name,
|
172
185
|
statistics.fetch(*metric.key_location),
|
173
186
|
tags: default_tags
|
174
187
|
)
|
@@ -179,9 +192,9 @@ module WaterDrop
|
|
179
192
|
# node ids
|
180
193
|
next if broker_statistics['nodeid'] == -1
|
181
194
|
|
182
|
-
|
195
|
+
public_send(
|
183
196
|
metric.type,
|
184
|
-
|
197
|
+
metric.name,
|
185
198
|
broker_statistics.dig(*metric.key_location),
|
186
199
|
tags: default_tags + ["broker:#{broker_statistics['nodename']}"]
|
187
200
|
)
|
data/lib/waterdrop/version.rb
CHANGED
data/lib/waterdrop.rb
CHANGED
data/waterdrop.gemspec
CHANGED
@@ -16,9 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.description = spec.summary
|
17
17
|
spec.license = 'MIT'
|
18
18
|
|
19
|
-
spec.add_dependency '
|
20
|
-
spec.add_dependency 'dry-monitor', '~> 0.5'
|
21
|
-
spec.add_dependency 'dry-validation', '~> 1.7'
|
19
|
+
spec.add_dependency 'karafka-core', '>= 2.0.2', '< 3.0.0'
|
22
20
|
spec.add_dependency 'rdkafka', '>= 0.10'
|
23
21
|
spec.add_dependency 'zeitwerk', '~> 2.3'
|
24
22
|
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: waterdrop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maciej Mensfeld
|
@@ -34,50 +34,28 @@ cert_chain:
|
|
34
34
|
R2P11bWoCtr70BsccVrN8jEhzwXngMyI2gVt750Y+dbTu1KgRqZKp/ECe7ZzPzXj
|
35
35
|
pIy9vHxTANKYVyI4qj8OrFdEM5BQNu8oQpL0iQ==
|
36
36
|
-----END CERTIFICATE-----
|
37
|
-
date: 2022-
|
37
|
+
date: 2022-08-01 00:00:00.000000000 Z
|
38
38
|
dependencies:
|
39
39
|
- !ruby/object:Gem::Dependency
|
40
|
-
name:
|
40
|
+
name: karafka-core
|
41
41
|
requirement: !ruby/object:Gem::Requirement
|
42
42
|
requirements:
|
43
43
|
- - ">="
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version:
|
46
|
-
|
47
|
-
prerelease: false
|
48
|
-
version_requirements: !ruby/object:Gem::Requirement
|
49
|
-
requirements:
|
50
|
-
- - ">="
|
45
|
+
version: 2.0.2
|
46
|
+
- - "<"
|
51
47
|
- !ruby/object:Gem::Version
|
52
|
-
version:
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: dry-monitor
|
55
|
-
requirement: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - "~>"
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: '0.5'
|
48
|
+
version: 3.0.0
|
60
49
|
type: :runtime
|
61
50
|
prerelease: false
|
62
51
|
version_requirements: !ruby/object:Gem::Requirement
|
63
52
|
requirements:
|
64
|
-
- - "
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: '0.5'
|
67
|
-
- !ruby/object:Gem::Dependency
|
68
|
-
name: dry-validation
|
69
|
-
requirement: !ruby/object:Gem::Requirement
|
70
|
-
requirements:
|
71
|
-
- - "~>"
|
53
|
+
- - ">="
|
72
54
|
- !ruby/object:Gem::Version
|
73
|
-
version:
|
74
|
-
|
75
|
-
prerelease: false
|
76
|
-
version_requirements: !ruby/object:Gem::Requirement
|
77
|
-
requirements:
|
78
|
-
- - "~>"
|
55
|
+
version: 2.0.2
|
56
|
+
- - "<"
|
79
57
|
- !ruby/object:Gem::Version
|
80
|
-
version:
|
58
|
+
version: 3.0.0
|
81
59
|
- !ruby/object:Gem::Dependency
|
82
60
|
name: rdkafka
|
83
61
|
requirement: !ruby/object:Gem::Requirement
|
@@ -130,11 +108,7 @@ files:
|
|
130
108
|
- docker-compose.yml
|
131
109
|
- lib/waterdrop.rb
|
132
110
|
- lib/waterdrop/config.rb
|
133
|
-
- lib/waterdrop/configurable.rb
|
134
|
-
- lib/waterdrop/configurable/leaf.rb
|
135
|
-
- lib/waterdrop/configurable/node.rb
|
136
111
|
- lib/waterdrop/contracts.rb
|
137
|
-
- lib/waterdrop/contracts/base.rb
|
138
112
|
- lib/waterdrop/contracts/config.rb
|
139
113
|
- lib/waterdrop/contracts/message.rb
|
140
114
|
- lib/waterdrop/errors.rb
|
@@ -142,10 +116,10 @@ files:
|
|
142
116
|
- lib/waterdrop/instrumentation/callbacks/delivery.rb
|
143
117
|
- lib/waterdrop/instrumentation/callbacks/error.rb
|
144
118
|
- lib/waterdrop/instrumentation/callbacks/statistics.rb
|
145
|
-
- lib/waterdrop/instrumentation/callbacks/statistics_decorator.rb
|
146
119
|
- lib/waterdrop/instrumentation/callbacks_manager.rb
|
147
120
|
- lib/waterdrop/instrumentation/logger_listener.rb
|
148
121
|
- lib/waterdrop/instrumentation/monitor.rb
|
122
|
+
- lib/waterdrop/instrumentation/notifications.rb
|
149
123
|
- lib/waterdrop/instrumentation/vendors/datadog/dashboard.json
|
150
124
|
- lib/waterdrop/instrumentation/vendors/datadog/listener.rb
|
151
125
|
- lib/waterdrop/patches/rdkafka/bindings.rb
|
metadata.gz.sig
CHANGED
@@ -1 +1,4 @@
|
|
1
|
-
|
1
|
+
A��ˣ�v=�o�*>�.C{Ys�bA,��aB��?#Ϻ��8\����wC�Ƈ��]%�S��Om�ȏY�u�v�Rm�l�a/����L��ӥ�J�����&����Ŷ{��a�&&Be�>b����}�}H�iݞL��h�����IE��
|
2
|
+
%�Z#�i��ޭ��P��%���A��%D"��۩u0lJ)��Yͦ��
|
3
|
+
}"�"�9�� B�.��?��)�)yLY3{q��7�x��-�E��fE�xmg�c��b�7Ԗ&��kt�Dx�7f����AY�X�\R����#L�_�
|
4
|
+
�*8/I���ȖskMo��?�_���A��\�V��&*>���ؼ�ң����_=�ry*�:�D�CD��\�������[0���I �6
|
@@ -1,100 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WaterDrop
|
4
|
-
module Configurable
|
5
|
-
# Single non-leaf node
|
6
|
-
# This is a core component for the configurable settings
|
7
|
-
#
|
8
|
-
# The idea here is simple: we collect settings (leafs) and children (nodes) information and we
|
9
|
-
# only compile/initialize the values prior to user running the `#configure` API. This API needs
|
10
|
-
# to run prior to using the result stuff even if there is nothing to configure
|
11
|
-
class Node
|
12
|
-
attr_reader :name, :nestings
|
13
|
-
|
14
|
-
# We need to be able to redefine children for deep copy
|
15
|
-
attr_accessor :children
|
16
|
-
|
17
|
-
# @param name [Symbol] node name
|
18
|
-
# @param nestings [Proc] block for nested settings
|
19
|
-
def initialize(name, nestings = ->(_) {})
|
20
|
-
@name = name
|
21
|
-
@children = []
|
22
|
-
@nestings = nestings
|
23
|
-
instance_eval(&nestings)
|
24
|
-
end
|
25
|
-
|
26
|
-
# Allows for a single leaf or nested node definition
|
27
|
-
#
|
28
|
-
# @param name [Symbol] setting or nested node name
|
29
|
-
# @param default [Object] default value
|
30
|
-
# @param constructor [#call, nil] callable or nil
|
31
|
-
# @param block [Proc] block for nested settings
|
32
|
-
def setting(name, default: nil, constructor: nil, &block)
|
33
|
-
@children << if block
|
34
|
-
Node.new(name, block)
|
35
|
-
else
|
36
|
-
Leaf.new(name, default, constructor)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# Allows for the configuration and setup of the settings
|
41
|
-
#
|
42
|
-
# Compile settings, allow for overrides via yielding
|
43
|
-
# @return [Node] returns self after configuration
|
44
|
-
def configure
|
45
|
-
compile
|
46
|
-
yield(self) if block_given?
|
47
|
-
self
|
48
|
-
end
|
49
|
-
|
50
|
-
# @return [Hash] frozen config hash representation
|
51
|
-
def to_h
|
52
|
-
config = {}
|
53
|
-
|
54
|
-
@children.each do |value|
|
55
|
-
config[value.name] = if value.is_a?(Leaf)
|
56
|
-
public_send(value.name)
|
57
|
-
else
|
58
|
-
value.to_h
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
config.freeze
|
63
|
-
end
|
64
|
-
|
65
|
-
# Deep copies all the children nodes to allow us for templates building on a class level and
|
66
|
-
# non-side-effect usage on an instance/inherited.
|
67
|
-
# @return [Node] duplicated node
|
68
|
-
def deep_dup
|
69
|
-
dupped = Node.new(name, nestings)
|
70
|
-
|
71
|
-
dupped.children += children.map do |value|
|
72
|
-
value.is_a?(Leaf) ? value.dup : value.deep_dup
|
73
|
-
end
|
74
|
-
|
75
|
-
dupped
|
76
|
-
end
|
77
|
-
|
78
|
-
# Converts the settings definitions into end children
|
79
|
-
# @note It runs once, after things are compiled, they will not be recompiled again
|
80
|
-
def compile
|
81
|
-
@children.each do |value|
|
82
|
-
# Do not redefine something that was already set during compilation
|
83
|
-
# This will allow us to reconfigure things and skip override with defaults
|
84
|
-
next if respond_to?(value.name)
|
85
|
-
|
86
|
-
singleton_class.attr_accessor value.name
|
87
|
-
|
88
|
-
initialized = if value.is_a?(Leaf)
|
89
|
-
value.constructor ? value.constructor.call(value.default) : value.default
|
90
|
-
else
|
91
|
-
value.compile
|
92
|
-
value
|
93
|
-
end
|
94
|
-
|
95
|
-
public_send("#{value.name}=", initialized)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
@@ -1,71 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WaterDrop
|
4
|
-
# A simple dry-configuration API compatible module for defining settings with defaults and a
|
5
|
-
# constructor.
|
6
|
-
module Configurable
|
7
|
-
# A simple settings layer that works similar to dry-configurable
|
8
|
-
# It allows us to define settings on a class and per instance level with templating on a class
|
9
|
-
# level. It handles inheritance and allows for nested settings.
|
10
|
-
#
|
11
|
-
# @note The core settings template needs to be defined on a class level
|
12
|
-
class << self
|
13
|
-
# Sets up all the class methods and inits the core root node.
|
14
|
-
# Useful when only per class settings are needed as does not include instance methods
|
15
|
-
# @param base [Class] class that we extend
|
16
|
-
def extended(base)
|
17
|
-
base.extend ClassMethods
|
18
|
-
end
|
19
|
-
|
20
|
-
# Sets up all the class and instance methods and inits the core root node
|
21
|
-
#
|
22
|
-
# @param base [Class] class to which we want to add configuration
|
23
|
-
#
|
24
|
-
# Needs to be used when per instance configuration is needed
|
25
|
-
def included(base)
|
26
|
-
base.include InstanceMethods
|
27
|
-
base.extend self
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Instance related methods
|
32
|
-
module InstanceMethods
|
33
|
-
# @return [Node] config root node
|
34
|
-
def config
|
35
|
-
@config ||= self.class.config.deep_dup
|
36
|
-
end
|
37
|
-
|
38
|
-
# Allows for a per instance configuration (if needed)
|
39
|
-
# @param block [Proc] block for configuration
|
40
|
-
def configure(&block)
|
41
|
-
config.configure(&block)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Class related methods
|
46
|
-
module ClassMethods
|
47
|
-
# @return [Node] root node for the settings
|
48
|
-
def config
|
49
|
-
return @config if @config
|
50
|
-
|
51
|
-
# This will handle inheritance
|
52
|
-
@config = if superclass.respond_to?(:config)
|
53
|
-
superclass.config.deep_dup
|
54
|
-
else
|
55
|
-
Node.new(:root)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# Allows for a per class configuration (if needed)
|
60
|
-
# @param block [Proc] block for configuration
|
61
|
-
def configure(&block)
|
62
|
-
config.configure(&block)
|
63
|
-
end
|
64
|
-
|
65
|
-
# Pipes the settings setup to the config root node
|
66
|
-
def setting(...)
|
67
|
-
config.setting(...)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WaterDrop
|
4
|
-
module Contracts
|
5
|
-
# Base for all the contracts in WaterDrop
|
6
|
-
class Base < Dry::Validation::Contract
|
7
|
-
config.messages.load_paths << File.join(WaterDrop.gem_root, 'config', 'errors.yml')
|
8
|
-
|
9
|
-
# @param data [Hash] data for validation
|
10
|
-
# @param error_class [Class] error class that should be used when validation fails
|
11
|
-
# @return [Boolean] true
|
12
|
-
# @raise [StandardError] any error provided in the error_class that inherits from the
|
13
|
-
# standard error
|
14
|
-
def validate!(data, error_class)
|
15
|
-
result = call(data)
|
16
|
-
|
17
|
-
return true if result.success?
|
18
|
-
|
19
|
-
raise error_class, result.errors.to_h
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,77 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WaterDrop
|
4
|
-
module Instrumentation
|
5
|
-
module Callbacks
|
6
|
-
# Many of the librdkafka statistics are absolute values instead of a gauge.
|
7
|
-
# This means, that for example number of messages sent is an absolute growing value
|
8
|
-
# instead of being a value of messages sent from the last statistics report.
|
9
|
-
# This decorator calculates the diff against previously emited stats, so we get also
|
10
|
-
# the diff together with the original values
|
11
|
-
class StatisticsDecorator
|
12
|
-
def initialize
|
13
|
-
@previous = {}.freeze
|
14
|
-
end
|
15
|
-
|
16
|
-
# @param emited_stats [Hash] original emited statistics
|
17
|
-
# @return [Hash] emited statistics extended with the diff data
|
18
|
-
# @note We modify the emited statistics, instead of creating new. Since we don't expose
|
19
|
-
# any API to get raw data, users can just assume that the result of this decoration is
|
20
|
-
# the proper raw stats that they can use
|
21
|
-
def call(emited_stats)
|
22
|
-
diff(
|
23
|
-
@previous,
|
24
|
-
emited_stats
|
25
|
-
)
|
26
|
-
|
27
|
-
@previous = emited_stats
|
28
|
-
|
29
|
-
emited_stats.freeze
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
# Calculates the diff of the provided values and modifies in place the emited statistics
|
35
|
-
#
|
36
|
-
# @param previous [Object] previous value from the given scope in which
|
37
|
-
# we are
|
38
|
-
# @param current [Object] current scope from emitted statistics
|
39
|
-
# @return [Object] the diff if the values were numerics or the current scope
|
40
|
-
def diff(previous, current)
|
41
|
-
if current.is_a?(Hash)
|
42
|
-
# @note We cannot use #each_key as we modify the content of the current scope
|
43
|
-
# in place (in case it's a hash)
|
44
|
-
current.keys.each do |key|
|
45
|
-
append(
|
46
|
-
current,
|
47
|
-
key,
|
48
|
-
diff((previous || {})[key], (current || {})[key])
|
49
|
-
)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Diff can be computed only for numerics
|
54
|
-
return current unless current.is_a?(Numeric)
|
55
|
-
# If there was no previous value, delta is always zero
|
56
|
-
return 0 unless previous
|
57
|
-
# Should never happen but just in case, a type changed in between stats
|
58
|
-
return current unless previous.is_a?(Numeric)
|
59
|
-
|
60
|
-
current - previous
|
61
|
-
end
|
62
|
-
|
63
|
-
# Appends the result of the diff to a given key as long as the result is numeric
|
64
|
-
#
|
65
|
-
# @param current [Hash] current scope
|
66
|
-
# @param key [Symbol] key based on which we were diffing
|
67
|
-
# @param result [Object] diff result
|
68
|
-
def append(current, key, result)
|
69
|
-
return unless result.is_a?(Numeric)
|
70
|
-
return if current.frozen?
|
71
|
-
|
72
|
-
current["#{key}_d"] = result
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|