karafka 2.0.0.rc1 → 2.0.0.rc4

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 (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +24 -0
  4. data/CONTRIBUTING.md +4 -8
  5. data/Gemfile.lock +14 -56
  6. data/LICENSE-COMM +1 -1
  7. data/README.md +46 -10
  8. data/config/errors.yml +52 -5
  9. data/docker-compose.yml +3 -0
  10. data/karafka.gemspec +4 -6
  11. data/lib/karafka/active_job/consumer.rb +2 -0
  12. data/lib/karafka/active_job/job_options_contract.rb +8 -2
  13. data/lib/karafka/cli/install.rb +15 -2
  14. data/lib/karafka/cli/server.rb +4 -2
  15. data/lib/karafka/connection/client.rb +4 -4
  16. data/lib/karafka/contracts/base.rb +2 -8
  17. data/lib/karafka/contracts/config.rb +71 -52
  18. data/lib/karafka/contracts/consumer_group.rb +25 -18
  19. data/lib/karafka/contracts/consumer_group_topic.rb +30 -16
  20. data/lib/karafka/contracts/server_cli_options.rb +18 -7
  21. data/lib/karafka/errors.rb +0 -3
  22. data/lib/karafka/helpers/colorize.rb +20 -0
  23. data/lib/karafka/instrumentation/logger_listener.rb +8 -2
  24. data/lib/karafka/instrumentation/monitor.rb +14 -59
  25. data/lib/karafka/instrumentation/notifications.rb +52 -0
  26. data/lib/karafka/instrumentation/vendors/datadog/dashboard.json +1 -0
  27. data/lib/karafka/instrumentation/vendors/datadog/listener.rb +232 -0
  28. data/lib/karafka/pro/active_job/dispatcher.rb +5 -2
  29. data/lib/karafka/pro/active_job/job_options_contract.rb +11 -6
  30. data/lib/karafka/pro/contracts/base.rb +21 -0
  31. data/lib/karafka/pro/contracts/consumer_group.rb +34 -0
  32. data/lib/karafka/pro/contracts/consumer_group_topic.rb +33 -0
  33. data/lib/karafka/pro/loader.rb +21 -3
  34. data/lib/karafka/pro/processing/partitioner.rb +22 -3
  35. data/lib/karafka/pro/routing/builder_extensions.rb +30 -0
  36. data/lib/karafka/pro/routing/{extensions.rb → topic_extensions.rb} +1 -1
  37. data/lib/karafka/processing/jobs_queue.rb +11 -0
  38. data/lib/karafka/processing/worker.rb +4 -2
  39. data/lib/karafka/setup/config.rb +7 -3
  40. data/lib/karafka/templates/example_consumer.rb.erb +2 -2
  41. data/lib/karafka/version.rb +1 -1
  42. data/lib/karafka.rb +3 -4
  43. data.tar.gz.sig +0 -0
  44. metadata +24 -38
  45. metadata.gz.sig +0 -0
@@ -9,71 +9,90 @@ module Karafka
9
9
  # validated per each route (topic + consumer_group) because they can be overwritten,
10
10
  # so we validate all of that once all the routes are defined and ready.
11
11
  class Config < Base
12
- params do
13
- # License validity happens in the licenser. Here we do only the simple consistency checks
14
- required(:license).schema do
15
- required(:token) { bool? | str? }
16
- required(:entity) { str? }
17
- required(:expires_on) { date? }
12
+ configure do |config|
13
+ config.error_messages = YAML.safe_load(
14
+ File.read(
15
+ File.join(Karafka.gem_root, 'config', 'errors.yml')
16
+ )
17
+ ).fetch('en').fetch('validations').fetch('config')
18
+ end
19
+
20
+ # License validity happens in the licenser. Here we do only the simple consistency checks
21
+ nested(:license) do
22
+ required(:token) { |val| [true, false].include?(val) || val.is_a?(String) }
23
+ required(:entity) { |val| val.is_a?(String) }
24
+ required(:expires_on) { |val| val.is_a?(Date) }
25
+ end
26
+
27
+ required(:client_id) { |val| val.is_a?(String) && Contracts::TOPIC_REGEXP.match?(val) }
28
+ required(:concurrency) { |val| val.is_a?(Integer) && val.positive? }
29
+ required(:consumer_mapper) { |val| !val.nil? }
30
+ required(:consumer_persistence) { |val| [true, false].include?(val) }
31
+ required(:pause_timeout) { |val| val.is_a?(Integer) && val.positive? }
32
+ required(:pause_max_timeout) { |val| val.is_a?(Integer) && val.positive? }
33
+ required(:pause_with_exponential_backoff) { |val| [true, false].include?(val) }
34
+ required(:shutdown_timeout) { |val| val.is_a?(Integer) && val.positive? }
35
+ required(:max_wait_time) { |val| val.is_a?(Integer) && val.positive? }
36
+ required(:kafka) { |val| val.is_a?(Hash) && !val.empty? }
37
+
38
+ # We validate internals just to be sure, that they are present and working
39
+ nested(:internal) do
40
+ required(:status) { |val| !val.nil? }
41
+ required(:process) { |val| !val.nil? }
42
+
43
+ nested(:routing) do
44
+ required(:builder) { |val| !val.nil? }
45
+ required(:subscription_groups_builder) { |val| !val.nil? }
46
+ end
47
+
48
+ nested(:processing) do
49
+ required(:jobs_builder) { |val| !val.nil? }
50
+ required(:scheduler) { |val| !val.nil? }
51
+ required(:coordinator_class) { |val| !val.nil? }
52
+ required(:partitioner_class) { |val| !val.nil? }
18
53
  end
19
54
 
20
- required(:client_id).filled(:str?, format?: Karafka::Contracts::TOPIC_REGEXP)
21
- required(:concurrency) { int? & gt?(0) }
22
- required(:consumer_mapper).filled
23
- required(:consumer_persistence).filled(:bool?)
24
- required(:pause_timeout) { int? & gt?(0) }
25
- required(:pause_max_timeout) { int? & gt?(0) }
26
- required(:pause_with_exponential_backoff).filled(:bool?)
27
- required(:shutdown_timeout) { int? & gt?(0) }
28
- required(:max_wait_time) { int? & gt?(0) }
29
- required(:kafka).filled(:hash)
30
-
31
- # We validate internals just to be sure, that they are present and working
32
- required(:internal).schema do
33
- required(:status)
34
- required(:process)
35
-
36
- required(:routing).schema do
37
- required(:builder)
38
- required(:subscription_groups_builder)
39
- end
40
-
41
- required(:processing).schema do
42
- required(:jobs_builder)
43
- required(:scheduler)
44
- required(:coordinator_class)
45
- required(:partitioner_class)
46
- end
47
-
48
- required(:active_job).schema do
49
- required(:dispatcher)
50
- required(:job_options_contract)
51
- required(:consumer_class)
52
- end
55
+ nested(:active_job) do
56
+ required(:dispatcher) { |val| !val.nil? }
57
+ required(:job_options_contract) { |val| !val.nil? }
58
+ required(:consumer_class) { |val| !val.nil? }
53
59
  end
54
60
  end
55
61
 
56
- # rdkafka requires all the keys to be strings, so we ensure that
57
- rule(:kafka) do
58
- next unless value.is_a?(Hash)
62
+ virtual do |data, errors|
63
+ next unless errors.empty?
64
+
65
+ detected_errors = []
59
66
 
60
- value.each_key do |key|
67
+ data.fetch(:kafka).each_key do |key|
61
68
  next if key.is_a?(Symbol)
62
69
 
63
- key(:"kafka.#{key}").failure(:kafka_key_must_be_a_symbol)
70
+ detected_errors << [[:kafka, key], :key_must_be_a_symbol]
64
71
  end
72
+
73
+ detected_errors
65
74
  end
66
75
 
67
- rule(:pause_timeout, :pause_max_timeout) do
68
- if values[:pause_timeout].to_i > values[:pause_max_timeout].to_i
69
- key(:pause_timeout).failure(:max_timeout_vs_pause_max_timeout)
70
- end
76
+ virtual do |data, errors|
77
+ next unless errors.empty?
78
+
79
+ pause_timeout = data.fetch(:pause_timeout)
80
+ pause_max_timeout = data.fetch(:pause_max_timeout)
81
+
82
+ next if pause_timeout <= pause_max_timeout
83
+
84
+ [[%i[pause_timeout], :max_timeout_vs_pause_max_timeout]]
71
85
  end
72
86
 
73
- rule(:shutdown_timeout, :max_wait_time) do
74
- if values[:max_wait_time].to_i >= values[:shutdown_timeout].to_i
75
- key(:shutdown_timeout).failure(:shutdown_timeout_vs_max_wait_time)
76
- end
87
+ virtual do |data, errors|
88
+ next unless errors.empty?
89
+
90
+ shutdown_timeout = data.fetch(:shutdown_timeout)
91
+ max_wait_time = data.fetch(:max_wait_time)
92
+
93
+ next if max_wait_time < shutdown_timeout
94
+
95
+ [[%i[shutdown_timeout], :shutdown_timeout_vs_max_wait_time]]
77
96
  end
78
97
  end
79
98
  end
@@ -4,32 +4,39 @@ module Karafka
4
4
  module Contracts
5
5
  # Contract for single full route (consumer group + topics) validation.
6
6
  class ConsumerGroup < Base
7
- # Internal contract for sub-validating topics schema
8
- TOPIC_CONTRACT = ConsumerGroupTopic.new.freeze
7
+ configure do |config|
8
+ config.error_messages = YAML.safe_load(
9
+ File.read(
10
+ File.join(Karafka.gem_root, 'config', 'errors.yml')
11
+ )
12
+ ).fetch('en').fetch('validations').fetch('consumer_group')
13
+ end
9
14
 
10
- private_constant :TOPIC_CONTRACT
15
+ required(:id) { |id| id.is_a?(String) && Contracts::TOPIC_REGEXP.match?(id) }
16
+ required(:topics) { |topics| topics.is_a?(Array) && !topics.empty? }
11
17
 
12
- params do
13
- required(:id).filled(:str?, format?: Karafka::Contracts::TOPIC_REGEXP)
14
- required(:topics).value(:array, :filled?)
15
- end
18
+ virtual do |data, errors|
19
+ next unless errors.empty?
16
20
 
17
- rule(:topics) do
18
- if value.is_a?(Array)
19
- names = value.map { |topic| topic[:name] }
21
+ names = data.fetch(:topics).map { |topic| topic[:name] }
20
22
 
21
- key.failure(:topics_names_not_unique) if names.size != names.uniq.size
22
- end
23
+ next if names.size == names.uniq.size
24
+
25
+ [[%i[topics], :names_not_unique]]
23
26
  end
24
27
 
25
- rule(:topics) do
26
- if value.is_a?(Array)
27
- value.each_with_index do |topic, index|
28
- TOPIC_CONTRACT.call(topic).errors.each do |error|
29
- key([:topics, index, error.path[0]]).failure(error.text)
30
- end
28
+ virtual do |data, errors|
29
+ next unless errors.empty?
30
+
31
+ fetched_errors = []
32
+
33
+ data.fetch(:topics).each do |topic|
34
+ ConsumerGroupTopic.new.call(topic).errors.each do |key, value|
35
+ fetched_errors << [[topic, key].flatten, value]
31
36
  end
32
37
  end
38
+
39
+ fetched_errors
33
40
  end
34
41
  end
35
42
  end
@@ -4,24 +4,38 @@ module Karafka
4
4
  module Contracts
5
5
  # Consumer group topic validation rules.
6
6
  class ConsumerGroupTopic < Base
7
- params do
8
- required(:consumer).filled
9
- required(:deserializer).filled
10
- required(:id).filled(:str?, format?: Karafka::Contracts::TOPIC_REGEXP)
11
- required(:kafka).filled
12
- required(:max_messages) { int? & gteq?(1) }
13
- required(:initial_offset).filled(included_in?: %w[earliest latest])
14
- required(:max_wait_time).filled { int? & gteq?(10) }
15
- required(:manual_offset_management).filled(:bool?)
16
- required(:name).filled(:str?, format?: Karafka::Contracts::TOPIC_REGEXP)
7
+ configure do |config|
8
+ config.error_messages = YAML.safe_load(
9
+ File.read(
10
+ File.join(Karafka.gem_root, 'config', 'errors.yml')
11
+ )
12
+ ).fetch('en').fetch('validations').fetch('consumer_group_topic')
17
13
  end
18
14
 
19
- rule(:kafka) do
20
- # This will trigger rdkafka validations that we catch and re-map the info and use dry
21
- # compatible format
22
- Rdkafka::Config.new(value).send(:native_config)
23
- rescue Rdkafka::Config::ConfigError => e
24
- key(:kafka).failure(e.message)
15
+ required(:consumer) { |consumer_group| !consumer_group.nil? }
16
+ required(:deserializer) { |deserializer| !deserializer.nil? }
17
+ required(:id) { |id| id.is_a?(String) && Contracts::TOPIC_REGEXP.match?(id) }
18
+ required(:kafka) { |kafka| kafka.is_a?(Hash) && !kafka.empty? }
19
+ required(:max_messages) { |mm| mm.is_a?(Integer) && mm >= 1 }
20
+ required(:initial_offset) { |io| %w[earliest latest].include?(io) }
21
+ required(:max_wait_time) { |mwt| mwt.is_a?(Integer) && mwt >= 10 }
22
+ required(:manual_offset_management) { |mmm| [true, false].include?(mmm) }
23
+ required(:name) { |name| name.is_a?(String) && Contracts::TOPIC_REGEXP.match?(name) }
24
+
25
+ virtual do |data, errors|
26
+ next unless errors.empty?
27
+
28
+ value = data.fetch(:kafka)
29
+
30
+ begin
31
+ # This will trigger rdkafka validations that we catch and re-map the info and use dry
32
+ # compatible format
33
+ Rdkafka::Config.new(value).send(:native_config)
34
+
35
+ nil
36
+ rescue Rdkafka::Config::ConfigError => e
37
+ [[%w[kafka], e.message]]
38
+ end
25
39
  end
26
40
  end
27
41
  end
@@ -4,17 +4,28 @@ module Karafka
4
4
  module Contracts
5
5
  # Contract for validating correctness of the server cli command options.
6
6
  class ServerCliOptions < Base
7
- params do
8
- optional(:consumer_groups).value(:array, :filled?)
7
+ configure do |config|
8
+ config.error_messages = YAML.safe_load(
9
+ File.read(
10
+ File.join(Karafka.gem_root, 'config', 'errors.yml')
11
+ )
12
+ ).fetch('en').fetch('validations').fetch('server_cli_options')
9
13
  end
10
14
 
11
- rule(:consumer_groups) do
15
+ optional(:consumer_groups) { |cg| cg.is_a?(Array) && !cg.empty? }
16
+
17
+ virtual do |data, errors|
18
+ next unless errors.empty?
19
+ next unless data.key?(:consumer_groups)
20
+
21
+ value = data.fetch(:consumer_groups)
22
+
12
23
  # If there were no consumer_groups declared in the server cli, it means that we will
13
24
  # run all of them and no need to validate them here at all
14
- if !value.nil? &&
15
- !(value - Karafka::App.config.internal.routing.builder.map(&:name)).empty?
16
- key(:consumer_groups).failure(:consumer_groups_inclusion)
17
- end
25
+ next if value.nil?
26
+ next if (value - Karafka::App.config.internal.routing.builder.map(&:name)).empty?
27
+
28
+ [[%i[consumer_groups], :consumer_groups_inclusion]]
18
29
  end
19
30
  end
20
31
  end
@@ -24,9 +24,6 @@ module Karafka
24
24
  # Raised when we try to use Karafka CLI commands (except install) without a boot file
25
25
  MissingBootFileError = Class.new(BaseError)
26
26
 
27
- # Raised when want to hook up to an event that is not registered and supported
28
- UnregisteredMonitorEventError = Class.new(BaseError)
29
-
30
27
  # Raised when we've waited enough for shutting down a non-responsive process
31
28
  ForcefulShutdownError = Class.new(BaseError)
32
29
 
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Helpers
5
+ # Simple wrapper for adding colors to strings
6
+ module Colorize
7
+ # @param string [String] string we want to have in green
8
+ # @return [String] green string
9
+ def green(string)
10
+ "\033[0;32m#{string}\033[0m"
11
+ end
12
+
13
+ # @param string [String] string we want to have in red
14
+ # @return [String] red string
15
+ def red(string)
16
+ "\033[0;31m#{string}\033[0m"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -18,7 +18,7 @@ module Karafka
18
18
  # @param event [Dry::Events::Event] event details including payload
19
19
  def on_connection_listener_fetch_loop(event)
20
20
  listener = event[:caller]
21
- info "[#{listener.id}] Polling messages..."
21
+ debug "[#{listener.id}] Polling messages..."
22
22
  end
23
23
 
24
24
  # Logs about messages that we've received from Kafka
@@ -28,7 +28,13 @@ module Karafka
28
28
  listener = event[:caller]
29
29
  time = event[:time]
30
30
  messages_count = event[:messages_buffer].size
31
- info "[#{listener.id}] Polled #{messages_count} messages in #{time}ms"
31
+
32
+ message = "[#{listener.id}] Polled #{messages_count} messages in #{time}ms"
33
+
34
+ # We don't want the "polled 0" in dev as it would spam the log
35
+ # Instead we publish only info when there was anything we could poll and fail over to the
36
+ # zero notifications when in debug mode
37
+ messages_count.zero? ? debug(message) : info(message)
32
38
  end
33
39
 
34
40
  # Prints info about the fact that a given job has started
@@ -1,66 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Karafka
4
- # Namespace for all the things related with Karafka instrumentation process
5
4
  module Instrumentation
6
- # Monitor is used to hookup external monitoring services to monitor how Karafka works
7
- # It provides a standardized API for checking incoming messages/enqueueing etc
8
- # Since it is a pub-sub based on dry-monitor, you can use as many subscribers/loggers at the
9
- # same time, which means that you might have for example file logging and NewRelic at the same
10
- # time
11
- # @note This class acts as a singleton because we are only permitted to have single monitor
12
- # per running process (just as logger)
13
- class Monitor < Dry::Monitor::Notifications
14
- # List of events that we support in the system and to which a monitor client can hook up
15
- # @note The non-error once support timestamp benchmarking
16
- # @note Depending on Karafka extensions and additional engines, this might not be the
17
- # complete list of all the events. Please use the #available_events on fully loaded
18
- # Karafka system to determine all of the events you can use.
19
- BASE_EVENTS = %w[
20
- app.initialized
21
- app.running
22
- app.stopping
23
- app.stopped
24
-
25
- consumer.consumed
26
- consumer.revoked
27
- consumer.shutdown
28
-
29
- process.notice_signal
30
-
31
- connection.listener.before_fetch_loop
32
- connection.listener.fetch_loop
33
- connection.listener.fetch_loop.received
34
-
35
- worker.process
36
- worker.processed
37
-
38
- statistics.emitted
39
-
40
- error.occurred
41
- ].freeze
42
-
43
- private_constant :BASE_EVENTS
44
-
45
- # @return [Karafka::Instrumentation::Monitor] monitor instance for system instrumentation
46
- def initialize
47
- super(:karafka)
48
- BASE_EVENTS.each(&method(:register_event))
49
- end
50
-
51
- # Allows us to subscribe to events with a code that will be yielded upon events
52
- # @param event_name_or_listener [String, Object] name of the event we want to subscribe to
53
- # or a listener if we decide to go with object listener
54
- def subscribe(event_name_or_listener)
55
- return super unless event_name_or_listener.is_a?(String)
56
- return super if available_events.include?(event_name_or_listener)
57
-
58
- raise Errors::UnregisteredMonitorEventError, event_name_or_listener
59
- end
60
-
61
- # @return [Array<String>] names of available events to which we can subscribe
62
- def available_events
63
- __bus__.events.keys
5
+ # Karafka 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
+ attr_reader :notifications_bus
10
+
11
+ # @param notifications_bus [Object] either our internal notifications bus or
12
+ # `ActiveSupport::Notifications`
13
+ # @param namespace [String, nil] namespace for events or nil if no namespace
14
+ def initialize(
15
+ notifications_bus = ::Karafka::Instrumentation::Notifications.new,
16
+ namespace = nil
17
+ )
18
+ super(notifications_bus, namespace)
64
19
  end
65
20
  end
66
21
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ # Namespace for all the things related with Karafka instrumentation process
5
+ module Instrumentation
6
+ # Monitor is used to hookup external monitoring services to monitor how Karafka works
7
+ # It provides a standardized API for checking incoming messages/enqueueing etc
8
+ # Since it is a pub-sub based on dry-monitor, you can use as many subscribers/loggers at the
9
+ # same time, which means that you might have for example file logging and NewRelic at the same
10
+ # time
11
+ # @note This class acts as a singleton because we are only permitted to have single monitor
12
+ # per running process (just as logger)
13
+ class Notifications < Karafka::Core::Monitoring::Notifications
14
+ # List of events that we support in the system and to which a monitor client can hook up
15
+ # @note The non-error once support timestamp benchmarking
16
+ # @note Depending on Karafka extensions and additional engines, this might not be the
17
+ # complete list of all the events. Please use the #available_events on fully loaded
18
+ # Karafka system to determine all of the events you can use.
19
+ EVENTS = %w[
20
+ app.initialized
21
+ app.running
22
+ app.stopping
23
+ app.stopped
24
+
25
+ consumer.consumed
26
+ consumer.revoked
27
+ consumer.shutdown
28
+
29
+ process.notice_signal
30
+
31
+ connection.listener.before_fetch_loop
32
+ connection.listener.fetch_loop
33
+ connection.listener.fetch_loop.received
34
+
35
+ worker.process
36
+ worker.processed
37
+
38
+ statistics.emitted
39
+
40
+ error.occurred
41
+ ].freeze
42
+
43
+ private_constant :EVENTS
44
+
45
+ # @return [Karafka::Instrumentation::Monitor] monitor instance for system instrumentation
46
+ def initialize
47
+ super
48
+ EVENTS.each { |event| register_event(event) }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1 @@
1
+ {"title":"Karafka monitoring dashboard","description":"","widgets":[{"id":7444969424381053,"definition":{"title":"Stability & errors","type":"group","show_title":true,"layout_type":"ordered","widgets":[{"id":8304008422587936,"definition":{"title":"Client connects and disconnects","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Connects","formula":"query1"},{"alias":"Disconnects","formula":"query2"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.connection.connects{*} by {host}.as_count()","data_source":"metrics","name":"query1"},{"query":"sum:karafka.connection.disconnects{*} by {host}.as_count()","data_source":"metrics","name":"query2"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"bars"}]},"layout":{"x":0,"y":0,"width":4,"height":2}},{"id":3722865443336921,"definition":{"title":"Errors encountered (any)","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"in-karafka errors","formula":"query1"},{"alias":"librdkafka consume errors","formula":"query2"},{"alias":"librdkafka receive errors","formula":"query3"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.error_occurred{*} by {type}.as_count()","data_source":"metrics","name":"query1"},{"query":"sum:karafka.consume.errors{*}.as_count()","data_source":"metrics","name":"query2"},{"query":"sum:karafka.receive.errors{*}.as_count()","data_source":"metrics","name":"query3"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":4,"y":0,"width":4,"height":2}},{"id":5477381252952760,"definition":{"title":"Processing errors","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"formula":"query1"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.error_occurred{type:consumer.consume.error} by {partition,topic}.as_count()","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":8,"y":0,"width":4,"height":2}},{"id":2357301680769076,"definition":{"title":"Processing errors rate per topic","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"% error rate per topic","formula":"(query1 / (query1 + query2)) * 100"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.error_occurred{type:consumer.consume.error} by {topic,partition}.as_count()","data_source":"metrics","name":"query1"},{"query":"sum:karafka.consumer.batches{*} by {topic,partition}.as_count()","data_source":"metrics","name":"query2"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"bars"}]},"layout":{"x":0,"y":2,"width":4,"height":2}},{"id":3902930069982135,"definition":{"title":"Batches successful vs failures","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Successfully processed batch","formula":"query1"},{"alias":"Batch processing with error","formula":"query2"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.consumer.batches{*} by {partition,topic}.as_count()","data_source":"metrics","name":"query1"},{"query":"avg:karafka.error_occurred{type:consumer.consume.error} by {partition,topic}.as_count()","data_source":"metrics","name":"query2"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":4,"y":2,"width":4,"height":2}},{"id":718749162159145,"definition":{"title":"Consumer instances revocations and shutdowns","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Consumer instances revokations","formula":"query1"},{"alias":"Consumer instances shutdowns","formula":"query2"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.consumer.revoked{*}.as_count()","data_source":"metrics","name":"query1"},{"query":"sum:karafka.consumer.shutdown{*}.as_count()","data_source":"metrics","name":"query2"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":8,"y":2,"width":4,"height":2}}]},"layout":{"x":0,"y":0,"width":12,"height":5}},{"id":5988438511387100,"definition":{"title":"Workers poll","type":"group","show_title":true,"layout_type":"ordered","widgets":[{"id":8769294644934352,"definition":{"title":"Enqueued jobs","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Enqueued jobs","formula":"query1"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.worker.enqueued_jobs.avg{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":0,"y":0,"width":4,"height":2}},{"id":2714502141463873,"definition":{"title":"Workers usage","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Busy workers (p95)","formula":"query1"},{"alias":"Total workers","formula":"query2"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.worker.processing.95percentile{*}","data_source":"metrics","name":"query1"},{"query":"sum:karafka.worker.total_threads{*}","data_source":"metrics","name":"query2"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":4,"y":0,"width":4,"height":2}},{"id":5370086629441984,"definition":{"title":"Workers % utilization","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"% workers utilization","formula":"(query1 / query2) * 100"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.worker.processing.95percentile{*}","data_source":"metrics","name":"query1"},{"query":"sum:karafka.worker.total_threads{*}","data_source":"metrics","name":"query2"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":8,"y":0,"width":4,"height":2}}]},"layout":{"x":0,"y":5,"width":12,"height":3}},{"id":8544040083223278,"definition":{"title":"Throughput ","type":"group","show_title":true,"layout_type":"ordered","widgets":[{"id":3740207481939733,"definition":{"title":"Offset lag changes","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"formula":"derivative(query1)"}],"response_format":"timeseries","queries":[{"query":"max:karafka.consumer.offset{*} by {topic,partition}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":0,"y":0,"width":4,"height":2}},{"id":6319110548544878,"definition":{"title":"Batches processed per topic","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"formula":"query1"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.consumer.batches{*} by {partition,topic}.as_count()","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":4,"y":0,"width":4,"height":2}},{"id":6232784865331443,"definition":{"title":"Messages consumed per topic","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Messages consumed","formula":"query1"},{"alias":"Average batch size","formula":"query1 / query2"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.consumer.messages{*} by {partition,topic}.as_count()","data_source":"metrics","name":"query1"},{"query":"sum:karafka.consumer.batches{*} by {partition,topic}.as_count()","data_source":"metrics","name":"query2"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":8,"y":0,"width":4,"height":2}},{"id":2321394598982770,"definition":{"title":"Consumption lag (in seconds)","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Consumption lag in s (max)","formula":"query2 / 1000"},{"alias":"Consumption lag in s (avg)","formula":"query3 / 1000"},{"alias":"Consumption lag in s (p95)","formula":"query1 / 1000"}],"response_format":"timeseries","queries":[{"query":"max:karafka.consumer.consumption_lag.max{*}","data_source":"metrics","name":"query2"},{"query":"max:karafka.consumer.consumption_lag.avg{*}","data_source":"metrics","name":"query3"},{"query":"max:karafka.consumer.consumption_lag.95percentile{*}","data_source":"metrics","name":"query1"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":0,"y":2,"width":4,"height":2}},{"id":1062074781483741,"definition":{"title":"Processing lag (in ms)","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Processing lag in ms (p95)","formula":"query1"},{"alias":"Processing lag in ms (max)","formula":"query2"},{"alias":"Processing lag in ms (avg)","formula":"query3"}],"response_format":"timeseries","queries":[{"query":"max:karafka.consumer.processing_lag.95percentile{*}","data_source":"metrics","name":"query1"},{"query":"max:karafka.consumer.processing_lag.max{*}","data_source":"metrics","name":"query2"},{"query":"max:karafka.consumer.processing_lag.avg{*}","data_source":"metrics","name":"query3"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":4,"y":2,"width":4,"height":2}},{"id":7497794728674267,"definition":{"title":"Batch processing time","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"formula":"query1"},{"formula":"query2"},{"formula":"query3"}],"response_format":"timeseries","queries":[{"query":"max:karafka.consumer.consumed.time_taken.95percentile{*} by {topic,partition}","data_source":"metrics","name":"query1"},{"query":"max:karafka.consumer.consumed.time_taken.max{*} by {topic,partition}","data_source":"metrics","name":"query2"},{"query":"max:karafka.consumer.consumed.time_taken.avg{*} by {topic,partition}","data_source":"metrics","name":"query3"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":8,"y":2,"width":4,"height":2}},{"id":4192833027984161,"definition":{"title":"Batch size per topic","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Batch size p95","formula":"query1"},{"alias":"Batch size avg","formula":"query2"},{"alias":"Batch size max","formula":"query3"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.consumer.batch_size.95percentile{*} by {partition,topic}","data_source":"metrics","name":"query1"},{"query":"sum:karafka.consumer.batch_size.avg{*} by {partition,topic}","data_source":"metrics","name":"query2"},{"query":"sum:karafka.consumer.batch_size.max{*} by {partition,topic}","data_source":"metrics","name":"query3"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":0,"y":4,"width":4,"height":2}},{"id":4741598444771147,"definition":{"title":"Messages consumed overall","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"type":"timeseries","requests":[{"formulas":[{"alias":"Messages consumed","formula":"query1"},{"alias":"Average batch size","formula":"query1 / query2"}],"response_format":"timeseries","queries":[{"query":"sum:karafka.consumer.messages{*}.as_count()","data_source":"metrics","name":"query1"},{"query":"sum:karafka.consumer.batches{*}.as_count()","data_source":"metrics","name":"query2"}],"style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":4,"y":4,"width":4,"height":2}},{"id":4502534794102513,"definition":{"title":"Polling times (ms)","title_size":"16","title_align":"left","show_legend":true,"legend_layout":"auto","legend_columns":["avg","min","max","value","sum"],"time":{},"type":"timeseries","requests":[{"formulas":[{"alias":"p95 ms polling time","formula":"query1"},{"alias":"max ms polling time","formula":"query2"},{"alias":"average ms polling time","formula":"query3"}],"queries":[{"name":"query1","data_source":"metrics","query":"avg:karafka.listener.polling.time_taken.95percentile{*}"},{"name":"query2","data_source":"metrics","query":"avg:karafka.listener.polling.time_taken.max{*}"},{"name":"query3","data_source":"metrics","query":"avg:karafka.listener.polling.time_taken.avg{*}"}],"response_format":"timeseries","style":{"palette":"dog_classic","line_type":"solid","line_width":"normal"},"display_type":"line"}]},"layout":{"x":8,"y":4,"width":4,"height":2}}]},"layout":{"x":0,"y":0,"width":12,"height":7,"is_column_break":true}}],"template_variables":[],"layout_type":"ordered","is_read_only":false,"notify_list":[],"reflow_type":"fixed","id":"s3u-z47-i6u"}