karafka-web 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +3 -0
- data/.coditsu/ci.yml +3 -0
- data/.diffend.yml +3 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +50 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/ci.yml +49 -0
- data/.gitignore +69 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +9 -0
- data/CODE_OF_CONDUCT.md +46 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +52 -0
- data/LICENSE +17 -0
- data/README.md +29 -0
- data/bin/karafka-web +33 -0
- data/certs/cert_chain.pem +26 -0
- data/config/locales/errors.yml +9 -0
- data/karafka-web.gemspec +44 -0
- data/lib/karafka/web/app.rb +17 -0
- data/lib/karafka/web/config.rb +80 -0
- data/lib/karafka/web/deserializer.rb +20 -0
- data/lib/karafka/web/errors.rb +25 -0
- data/lib/karafka/web/installer.rb +124 -0
- data/lib/karafka/web/processing/consumer.rb +66 -0
- data/lib/karafka/web/processing/consumers/aggregator.rb +130 -0
- data/lib/karafka/web/processing/consumers/state.rb +32 -0
- data/lib/karafka/web/tracking/base_contract.rb +31 -0
- data/lib/karafka/web/tracking/consumers/contracts/consumer_group.rb +33 -0
- data/lib/karafka/web/tracking/consumers/contracts/job.rb +26 -0
- data/lib/karafka/web/tracking/consumers/contracts/partition.rb +22 -0
- data/lib/karafka/web/tracking/consumers/contracts/report.rb +95 -0
- data/lib/karafka/web/tracking/consumers/contracts/topic.rb +29 -0
- data/lib/karafka/web/tracking/consumers/listeners/base.rb +33 -0
- data/lib/karafka/web/tracking/consumers/listeners/errors.rb +107 -0
- data/lib/karafka/web/tracking/consumers/listeners/pausing.rb +45 -0
- data/lib/karafka/web/tracking/consumers/listeners/processing.rb +157 -0
- data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +123 -0
- data/lib/karafka/web/tracking/consumers/listeners/status.rb +58 -0
- data/lib/karafka/web/tracking/consumers/sampler.rb +216 -0
- data/lib/karafka/web/tracking/memoized_shell.rb +48 -0
- data/lib/karafka/web/tracking/reporter.rb +144 -0
- data/lib/karafka/web/tracking/ttl_array.rb +59 -0
- data/lib/karafka/web/tracking/ttl_hash.rb +16 -0
- data/lib/karafka/web/ui/app.rb +78 -0
- data/lib/karafka/web/ui/base.rb +77 -0
- data/lib/karafka/web/ui/controllers/base.rb +40 -0
- data/lib/karafka/web/ui/controllers/become_pro.rb +17 -0
- data/lib/karafka/web/ui/controllers/cluster.rb +24 -0
- data/lib/karafka/web/ui/controllers/consumers.rb +27 -0
- data/lib/karafka/web/ui/controllers/errors.rb +43 -0
- data/lib/karafka/web/ui/controllers/jobs.rb +33 -0
- data/lib/karafka/web/ui/controllers/requests/params.rb +30 -0
- data/lib/karafka/web/ui/controllers/responses/data.rb +26 -0
- data/lib/karafka/web/ui/controllers/routing.rb +30 -0
- data/lib/karafka/web/ui/helpers/application_helper.rb +144 -0
- data/lib/karafka/web/ui/lib/hash_proxy.rb +66 -0
- data/lib/karafka/web/ui/lib/paginate_array.rb +38 -0
- data/lib/karafka/web/ui/models/consumer_group.rb +20 -0
- data/lib/karafka/web/ui/models/health.rb +44 -0
- data/lib/karafka/web/ui/models/job.rb +13 -0
- data/lib/karafka/web/ui/models/message.rb +99 -0
- data/lib/karafka/web/ui/models/partition.rb +13 -0
- data/lib/karafka/web/ui/models/process.rb +56 -0
- data/lib/karafka/web/ui/models/processes.rb +86 -0
- data/lib/karafka/web/ui/models/state.rb +67 -0
- data/lib/karafka/web/ui/models/topic.rb +19 -0
- data/lib/karafka/web/ui/pro/app.rb +120 -0
- data/lib/karafka/web/ui/pro/controllers/cluster.rb +16 -0
- data/lib/karafka/web/ui/pro/controllers/consumers.rb +54 -0
- data/lib/karafka/web/ui/pro/controllers/dlq.rb +44 -0
- data/lib/karafka/web/ui/pro/controllers/errors.rb +57 -0
- data/lib/karafka/web/ui/pro/controllers/explorer.rb +79 -0
- data/lib/karafka/web/ui/pro/controllers/health.rb +33 -0
- data/lib/karafka/web/ui/pro/controllers/jobs.rb +26 -0
- data/lib/karafka/web/ui/pro/controllers/routing.rb +26 -0
- data/lib/karafka/web/ui/pro/views/consumers/_breadcrumbs.erb +27 -0
- data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +60 -0
- data/lib/karafka/web/ui/pro/views/consumers/_counters.erb +50 -0
- data/lib/karafka/web/ui/pro/views/consumers/_summary.erb +81 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_consumer_group.erb +109 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_job.erb +26 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_metrics.erb +126 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_no_jobs.erb +9 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_no_subscriptions.erb +9 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +32 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_stopped.erb +10 -0
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +20 -0
- data/lib/karafka/web/ui/pro/views/consumers/index.erb +30 -0
- data/lib/karafka/web/ui/pro/views/consumers/jobs.erb +42 -0
- data/lib/karafka/web/ui/pro/views/consumers/subscriptions.erb +23 -0
- data/lib/karafka/web/ui/pro/views/dlq/_breadcrumbs.erb +5 -0
- data/lib/karafka/web/ui/pro/views/dlq/_no_topics.erb +9 -0
- data/lib/karafka/web/ui/pro/views/dlq/_topic.erb +12 -0
- data/lib/karafka/web/ui/pro/views/dlq/index.erb +16 -0
- data/lib/karafka/web/ui/pro/views/errors/_breadcrumbs.erb +25 -0
- data/lib/karafka/web/ui/pro/views/errors/_detail.erb +29 -0
- data/lib/karafka/web/ui/pro/views/errors/_error.erb +26 -0
- data/lib/karafka/web/ui/pro/views/errors/_partition_option.erb +7 -0
- data/lib/karafka/web/ui/pro/views/errors/index.erb +58 -0
- data/lib/karafka/web/ui/pro/views/errors/show.erb +56 -0
- data/lib/karafka/web/ui/pro/views/explorer/_breadcrumbs.erb +29 -0
- data/lib/karafka/web/ui/pro/views/explorer/_detail.erb +21 -0
- data/lib/karafka/web/ui/pro/views/explorer/_encryption_enabled.erb +18 -0
- data/lib/karafka/web/ui/pro/views/explorer/_failed_deserialization.erb +4 -0
- data/lib/karafka/web/ui/pro/views/explorer/_message.erb +16 -0
- data/lib/karafka/web/ui/pro/views/explorer/_partition_option.erb +7 -0
- data/lib/karafka/web/ui/pro/views/explorer/_topic.erb +12 -0
- data/lib/karafka/web/ui/pro/views/explorer/index.erb +17 -0
- data/lib/karafka/web/ui/pro/views/explorer/partition.erb +56 -0
- data/lib/karafka/web/ui/pro/views/explorer/show.erb +65 -0
- data/lib/karafka/web/ui/pro/views/health/_breadcrumbs.erb +5 -0
- data/lib/karafka/web/ui/pro/views/health/_partition.erb +35 -0
- data/lib/karafka/web/ui/pro/views/health/index.erb +60 -0
- data/lib/karafka/web/ui/pro/views/jobs/_breadcrumbs.erb +5 -0
- data/lib/karafka/web/ui/pro/views/jobs/_job.erb +31 -0
- data/lib/karafka/web/ui/pro/views/jobs/_no_jobs.erb +9 -0
- data/lib/karafka/web/ui/pro/views/jobs/index.erb +34 -0
- data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +57 -0
- data/lib/karafka/web/ui/public/images/favicon.ico +0 -0
- data/lib/karafka/web/ui/public/images/logo.svg +28 -0
- data/lib/karafka/web/ui/public/javascripts/application.js +41 -0
- data/lib/karafka/web/ui/public/javascripts/bootstrap.min.js +7 -0
- data/lib/karafka/web/ui/public/javascripts/highlight.min.js +337 -0
- data/lib/karafka/web/ui/public/javascripts/live_poll.js +124 -0
- data/lib/karafka/web/ui/public/javascripts/timeago.min.js +1 -0
- data/lib/karafka/web/ui/public/stylesheets/application.css +106 -0
- data/lib/karafka/web/ui/public/stylesheets/bootstrap.min.css +7 -0
- data/lib/karafka/web/ui/public/stylesheets/bootstrap.min.css.map +1 -0
- data/lib/karafka/web/ui/public/stylesheets/highlight.min.css +10 -0
- data/lib/karafka/web/ui/views/cluster/_breadcrumbs.erb +5 -0
- data/lib/karafka/web/ui/views/cluster/_broker.erb +5 -0
- data/lib/karafka/web/ui/views/cluster/_partition.erb +22 -0
- data/lib/karafka/web/ui/views/cluster/index.erb +72 -0
- data/lib/karafka/web/ui/views/consumers/_breadcrumbs.erb +27 -0
- data/lib/karafka/web/ui/views/consumers/_consumer.erb +43 -0
- data/lib/karafka/web/ui/views/consumers/_counters.erb +44 -0
- data/lib/karafka/web/ui/views/consumers/_summary.erb +81 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_consumer_group.erb +109 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_job.erb +26 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_metrics.erb +126 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_no_jobs.erb +9 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_no_subscriptions.erb +9 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_partition.erb +32 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_stopped.erb +10 -0
- data/lib/karafka/web/ui/views/consumers/consumer/_tabs.erb +20 -0
- data/lib/karafka/web/ui/views/consumers/index.erb +29 -0
- data/lib/karafka/web/ui/views/errors/_breadcrumbs.erb +19 -0
- data/lib/karafka/web/ui/views/errors/_detail.erb +29 -0
- data/lib/karafka/web/ui/views/errors/_error.erb +26 -0
- data/lib/karafka/web/ui/views/errors/index.erb +38 -0
- data/lib/karafka/web/ui/views/errors/show.erb +30 -0
- data/lib/karafka/web/ui/views/jobs/_breadcrumbs.erb +5 -0
- data/lib/karafka/web/ui/views/jobs/_job.erb +22 -0
- data/lib/karafka/web/ui/views/jobs/_no_jobs.erb +9 -0
- data/lib/karafka/web/ui/views/jobs/index.erb +31 -0
- data/lib/karafka/web/ui/views/layout.erb +23 -0
- data/lib/karafka/web/ui/views/routing/_breadcrumbs.erb +15 -0
- data/lib/karafka/web/ui/views/routing/_consumer_group.erb +34 -0
- data/lib/karafka/web/ui/views/routing/_detail.erb +25 -0
- data/lib/karafka/web/ui/views/routing/_topic.erb +18 -0
- data/lib/karafka/web/ui/views/routing/index.erb +10 -0
- data/lib/karafka/web/ui/views/routing/show.erb +26 -0
- data/lib/karafka/web/ui/views/shared/_become_pro.erb +13 -0
- data/lib/karafka/web/ui/views/shared/_brand.erb +3 -0
- data/lib/karafka/web/ui/views/shared/_content.erb +31 -0
- data/lib/karafka/web/ui/views/shared/_header.erb +20 -0
- data/lib/karafka/web/ui/views/shared/_navigation.erb +57 -0
- data/lib/karafka/web/ui/views/shared/_pagination.erb +21 -0
- data/lib/karafka/web/ui/views/shared/exceptions/not_found.erb +39 -0
- data/lib/karafka/web/ui/views/shared/exceptions/pro_only.erb +52 -0
- data/lib/karafka/web/version.rb +8 -0
- data/lib/karafka/web.rb +60 -0
- data.tar.gz.sig +0 -0
- metadata +328 -0
- metadata.gz.sig +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Tracking
|
|
6
|
+
module Consumers
|
|
7
|
+
module Listeners
|
|
8
|
+
# Listener related to tracking errors, DLQs, and retries metrics for the Web UI
|
|
9
|
+
class Errors < Base
|
|
10
|
+
# Collects errors info and counts errors
|
|
11
|
+
#
|
|
12
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
13
|
+
def on_error_occurred(event)
|
|
14
|
+
track do |sampler|
|
|
15
|
+
# Collect extra info if it was a consumer related error.
|
|
16
|
+
# Those come from user code
|
|
17
|
+
details = if event[:caller].is_a?(Karafka::BaseConsumer)
|
|
18
|
+
extract_consumer_info(event[:caller])
|
|
19
|
+
else
|
|
20
|
+
{}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
error_class, error_message, backtrace = extract_error_info(event[:error])
|
|
24
|
+
|
|
25
|
+
sampler.errors << {
|
|
26
|
+
type: event[:type],
|
|
27
|
+
error_class: error_class,
|
|
28
|
+
error_message: error_message,
|
|
29
|
+
backtrace: backtrace,
|
|
30
|
+
details: details,
|
|
31
|
+
occurred_at: float_now
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
sampler.counters[:errors] += 1
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Count dead letter queue messages dispatches
|
|
39
|
+
#
|
|
40
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
|
41
|
+
def on_dead_letter_queue_dispatched(_event)
|
|
42
|
+
track do |sampler|
|
|
43
|
+
sampler.counters[:dead] += 1
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Count retries
|
|
48
|
+
#
|
|
49
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
|
50
|
+
def on_consumer_consuming_retry(_event)
|
|
51
|
+
track do |sampler|
|
|
52
|
+
sampler.counters[:retries] += 1
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# @param consumer [::Karafka::BaseConsumer]
|
|
59
|
+
# @return [Hash] hash with consumer specific info for details of error
|
|
60
|
+
def extract_consumer_info(consumer)
|
|
61
|
+
{
|
|
62
|
+
topic: consumer.topic.name,
|
|
63
|
+
consumer_group: consumer.topic.consumer_group.id,
|
|
64
|
+
partition: consumer.messages.metadata.partition,
|
|
65
|
+
first_offset: consumer.messages.first.offset,
|
|
66
|
+
last_offset: consumer.messages.last.offset,
|
|
67
|
+
comitted_offset: consumer.coordinator.seek_offset - 1,
|
|
68
|
+
consumer: consumer.class.to_s
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Extracts the basic error info
|
|
73
|
+
#
|
|
74
|
+
# @param error [StandardError] error that occurred
|
|
75
|
+
# @return [Array<String, String, String>] array with error name, message and backtrace
|
|
76
|
+
def extract_error_info(error)
|
|
77
|
+
error_message = error.message.to_s
|
|
78
|
+
error_message.force_encoding('utf-8')
|
|
79
|
+
error_message.scrub!
|
|
80
|
+
|
|
81
|
+
backtrace = (error.backtrace || [])
|
|
82
|
+
|
|
83
|
+
app_root = "#{::Karafka.root}/"
|
|
84
|
+
|
|
85
|
+
gem_home = if ENV.key?('GEM_HOME')
|
|
86
|
+
ENV['GEM_HOME']
|
|
87
|
+
else
|
|
88
|
+
File.expand_path(File.join(Karafka.gem_root.to_s, '../'))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
gem_home = "#{gem_home}/"
|
|
92
|
+
|
|
93
|
+
backtrace.map! { |line| line.gsub(app_root, '') }
|
|
94
|
+
backtrace.map! { |line| line.gsub(gem_home, '') }
|
|
95
|
+
|
|
96
|
+
[
|
|
97
|
+
error.class.name,
|
|
98
|
+
error_message,
|
|
99
|
+
backtrace.join("\n")
|
|
100
|
+
]
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Tracking
|
|
6
|
+
module Consumers
|
|
7
|
+
module Listeners
|
|
8
|
+
# Tracks pausing and un-pausing of topics partitions for both user requested and
|
|
9
|
+
# automatic events.
|
|
10
|
+
class Pausing < Base
|
|
11
|
+
# Indicate pause
|
|
12
|
+
#
|
|
13
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
14
|
+
def on_client_pause(event)
|
|
15
|
+
track do |sampler|
|
|
16
|
+
sampler.pauses << pause_id(event)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Indicate pause ended
|
|
21
|
+
#
|
|
22
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
23
|
+
def on_client_resume(event)
|
|
24
|
+
track do |sampler|
|
|
25
|
+
sampler.pauses.delete pause_id(event)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
32
|
+
# @return [String] pause id built from consumer group and topic details
|
|
33
|
+
def pause_id(event)
|
|
34
|
+
topic = event[:topic]
|
|
35
|
+
partition = event[:partition]
|
|
36
|
+
consumer_group_id = event[:subscription_group].consumer_group.id
|
|
37
|
+
|
|
38
|
+
[consumer_group_id, topic, partition].join('-')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Tracking
|
|
6
|
+
module Consumers
|
|
7
|
+
module Listeners
|
|
8
|
+
# Listener that is used to collect metrics related to work processing
|
|
9
|
+
class Processing < Base
|
|
10
|
+
# Collect time metrics about worker work execution time
|
|
11
|
+
#
|
|
12
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
13
|
+
def on_worker_processed(event)
|
|
14
|
+
track do |sampler|
|
|
15
|
+
sampler.times[:total] << event[:time]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Counts work execution and processing states in consumer instances
|
|
20
|
+
#
|
|
21
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
22
|
+
def on_consumer_consume(event)
|
|
23
|
+
consumer = event.payload[:caller]
|
|
24
|
+
messages_count = consumer.messages.count
|
|
25
|
+
jid = job_id(consumer, 'consume')
|
|
26
|
+
job_details = job_details(consumer, 'consume')
|
|
27
|
+
|
|
28
|
+
track do |sampler|
|
|
29
|
+
# We count batches and messages prior to the execution, so they are tracked even
|
|
30
|
+
# if error occurs, etc.
|
|
31
|
+
sampler.counters[:batches] += 1
|
|
32
|
+
sampler.counters[:messages] += messages_count
|
|
33
|
+
sampler.jobs[jid] = job_details
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Removes failed job from active jobs
|
|
38
|
+
#
|
|
39
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
40
|
+
def on_error_occurred(event)
|
|
41
|
+
track do |sampler|
|
|
42
|
+
type = case event[:type]
|
|
43
|
+
when 'consumer.consume.error'
|
|
44
|
+
'consume'
|
|
45
|
+
when 'consumer.revoked.error'
|
|
46
|
+
'revoked'
|
|
47
|
+
when 'consumer.shutdown.error'
|
|
48
|
+
'shutdown'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
sampler.jobs.delete(
|
|
52
|
+
job_id(event[:caller], type)
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Collect info about consumption event that occurred and its metrics
|
|
58
|
+
# Removes the job from running jobs
|
|
59
|
+
#
|
|
60
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
61
|
+
def on_consumer_consumed(event)
|
|
62
|
+
consumer = event.payload[:caller]
|
|
63
|
+
topic = consumer.topic
|
|
64
|
+
consumer_group_id = topic.consumer_group.id
|
|
65
|
+
messages_count = consumer.messages.count
|
|
66
|
+
time = event[:time]
|
|
67
|
+
jid = job_id(consumer, 'consume')
|
|
68
|
+
|
|
69
|
+
track do |sampler|
|
|
70
|
+
sampler.jobs.delete(jid)
|
|
71
|
+
sampler.times[consumer_group_id] << [topic.name, time, messages_count]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Stores this job details
|
|
76
|
+
#
|
|
77
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
78
|
+
def on_consumer_revoke(event)
|
|
79
|
+
consumer = event.payload[:caller]
|
|
80
|
+
jid = job_id(consumer, 'revoked')
|
|
81
|
+
job_details = job_details(consumer, 'revoked')
|
|
82
|
+
|
|
83
|
+
track do |sampler|
|
|
84
|
+
sampler.jobs[jid] = job_details
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Removes the job from running jobs
|
|
89
|
+
#
|
|
90
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
91
|
+
def on_consumer_revoked(event)
|
|
92
|
+
consumer = event.payload[:caller]
|
|
93
|
+
jid = job_id(consumer, 'revoked')
|
|
94
|
+
|
|
95
|
+
track do |sampler|
|
|
96
|
+
sampler.jobs.delete(jid)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Stores this job details
|
|
101
|
+
#
|
|
102
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
103
|
+
def on_consumer_shutting_down(event)
|
|
104
|
+
consumer = event.payload[:caller]
|
|
105
|
+
jid = job_id(consumer, 'shutdown')
|
|
106
|
+
job_details = job_details(consumer, 'shutdown')
|
|
107
|
+
|
|
108
|
+
track do |sampler|
|
|
109
|
+
sampler.jobs[jid] = job_details
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Removes the job from running jobs
|
|
114
|
+
#
|
|
115
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
116
|
+
def on_consumer_shutdown(event)
|
|
117
|
+
consumer = event.payload[:caller]
|
|
118
|
+
jid = job_id(consumer, 'shutdown')
|
|
119
|
+
|
|
120
|
+
track do |sampler|
|
|
121
|
+
sampler.jobs.delete(jid)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
# Generates a job id that we can use to track jobs in an unique way
|
|
128
|
+
#
|
|
129
|
+
# @param consumer [::Karafka::BaseConsumer] consumer instance
|
|
130
|
+
# @param type [String] job type
|
|
131
|
+
def job_id(consumer, type)
|
|
132
|
+
"#{consumer.id}-#{type}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Gets consumer details for job tracking
|
|
136
|
+
#
|
|
137
|
+
# @param consumer [::Karafka::BaseConsumer] consumer instance
|
|
138
|
+
# @param type [String] job type
|
|
139
|
+
def job_details(consumer, type)
|
|
140
|
+
{
|
|
141
|
+
started_at: float_now,
|
|
142
|
+
topic: consumer.topic.name,
|
|
143
|
+
partition: consumer.messages.metadata.partition,
|
|
144
|
+
first_offset: consumer.messages.first.offset,
|
|
145
|
+
last_offset: consumer.messages.last.offset,
|
|
146
|
+
comitted_offset: consumer.coordinator.seek_offset - 1,
|
|
147
|
+
consumer: consumer.class.to_s,
|
|
148
|
+
consumer_group: consumer.topic.consumer_group.id,
|
|
149
|
+
type: type
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Tracking
|
|
6
|
+
module Consumers
|
|
7
|
+
module Listeners
|
|
8
|
+
# Listener used to collect metrics published by librdkafka
|
|
9
|
+
class Statistics < Base
|
|
10
|
+
# Collect Kafka metrics
|
|
11
|
+
#
|
|
12
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
|
13
|
+
def on_statistics_emitted(event)
|
|
14
|
+
statistics = event[:statistics]
|
|
15
|
+
topics = statistics.fetch('topics')
|
|
16
|
+
cgrp = statistics.fetch('cgrp')
|
|
17
|
+
consumer_group_id = event[:consumer_group_id]
|
|
18
|
+
|
|
19
|
+
track do |sampler|
|
|
20
|
+
cg_details = extract_consumer_group_details(consumer_group_id, cgrp)
|
|
21
|
+
sampler.consumer_groups[consumer_group_id] = cg_details
|
|
22
|
+
|
|
23
|
+
topics.each do |topic_name, topic_values|
|
|
24
|
+
partitions = topic_values.fetch('partitions')
|
|
25
|
+
|
|
26
|
+
partitions.each do |partition_name, partition_statistics|
|
|
27
|
+
partition_id = partition_name.to_i
|
|
28
|
+
|
|
29
|
+
next unless partition_reportable?(partition_id, partition_statistics)
|
|
30
|
+
|
|
31
|
+
metrics = extract_partition_metrics(partition_statistics)
|
|
32
|
+
|
|
33
|
+
next if metrics.empty?
|
|
34
|
+
|
|
35
|
+
topics_details = cg_details[:topics]
|
|
36
|
+
|
|
37
|
+
topic_details = topics_details[topic_name] ||= {
|
|
38
|
+
name: topic_name,
|
|
39
|
+
partitions: {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
topic_details[:partitions][partition_id] = metrics.merge(
|
|
43
|
+
id: partition_id,
|
|
44
|
+
poll_state: poll_state(consumer_group_id, topic_name, partition_id)
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# Extracts basic consumer group related details
|
|
54
|
+
# @param consumer_group_id [String]
|
|
55
|
+
# @param consumer_group_statistics [Hash]
|
|
56
|
+
# @return [Hash] consumer group relevant details
|
|
57
|
+
def extract_consumer_group_details(consumer_group_id, consumer_group_statistics)
|
|
58
|
+
{
|
|
59
|
+
id: consumer_group_id,
|
|
60
|
+
state: consumer_group_statistics.slice(
|
|
61
|
+
'state',
|
|
62
|
+
'join_state',
|
|
63
|
+
'stateage',
|
|
64
|
+
'rebalance_age',
|
|
65
|
+
'rebalance_cnt',
|
|
66
|
+
'rebalance_reason'
|
|
67
|
+
),
|
|
68
|
+
topics: {}
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @param partition_id [Integer]
|
|
73
|
+
# @param partition_statistics [Hash]
|
|
74
|
+
# @return [Boolean] is this partition relevant to the current process, hence should we
|
|
75
|
+
# report about it in the context of the process.
|
|
76
|
+
def partition_reportable?(partition_id, partition_statistics)
|
|
77
|
+
return false if partition_id == -1
|
|
78
|
+
|
|
79
|
+
# Skip until lag info is available
|
|
80
|
+
return false if partition_statistics['consumer_lag'] == -1
|
|
81
|
+
|
|
82
|
+
# Collect information only about what we are subscribed to and what we fetch or
|
|
83
|
+
# work in any way. Stopped means, we no longer work with it
|
|
84
|
+
return false if partition_statistics['fetch_state'] == 'stopped'
|
|
85
|
+
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Extracts and formats partition relevant metrics
|
|
90
|
+
#
|
|
91
|
+
# @param partition_statistics [Hash]
|
|
92
|
+
# @return [Hash] extracted partition metrics
|
|
93
|
+
def extract_partition_metrics(partition_statistics)
|
|
94
|
+
metrics = partition_statistics.slice(
|
|
95
|
+
'consumer_lag_stored',
|
|
96
|
+
'consumer_lag_stored_d',
|
|
97
|
+
'committed_offset',
|
|
98
|
+
'stored_offset',
|
|
99
|
+
'fetch_state'
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Rename as we do not need `consumer_` prefix
|
|
103
|
+
metrics.transform_keys! { |key| key.gsub('consumer_', '') }
|
|
104
|
+
metrics.transform_keys!(&:to_sym)
|
|
105
|
+
|
|
106
|
+
metrics
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @param consumer_group_id [String]
|
|
110
|
+
# @param topic_name [String]
|
|
111
|
+
# @param partition_id [Integer]
|
|
112
|
+
# @return [String] poll state / is partition paused or not
|
|
113
|
+
def poll_state(consumer_group_id, topic_name, partition_id)
|
|
114
|
+
pause_id = [consumer_group_id, topic_name, partition_id].join('-')
|
|
115
|
+
|
|
116
|
+
sampler.pauses.include?(pause_id) ? 'paused' : 'active'
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Karafka
|
|
4
|
+
module Web
|
|
5
|
+
module Tracking
|
|
6
|
+
module Consumers
|
|
7
|
+
module Listeners
|
|
8
|
+
# Listener that triggers reporting on Karafka process status changes
|
|
9
|
+
# Whenever the whole process status changes, we do not want to wait and want to report
|
|
10
|
+
# as fast as possible, hence `report!`. This improves the user experience and since
|
|
11
|
+
# status changes do not happen that often, we can handle few extra reports dispatches.
|
|
12
|
+
class Status < Base
|
|
13
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
|
14
|
+
#
|
|
15
|
+
# @note We do not use `#report!` here because this kicks in for each listener loop and
|
|
16
|
+
# those run the same time.
|
|
17
|
+
def on_connection_listener_before_fetch_loop(_event)
|
|
18
|
+
report
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Indicate as fast as possible that we've started moving to the quiet mode
|
|
22
|
+
#
|
|
23
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
|
24
|
+
def on_app_quieting(_event)
|
|
25
|
+
report!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Indicate as fast as possible that we've reached the quiet mode
|
|
29
|
+
#
|
|
30
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
|
31
|
+
def on_app_quiet(_event)
|
|
32
|
+
report!
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Instrument on the fact that we're stopping
|
|
36
|
+
#
|
|
37
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
|
38
|
+
def on_app_stopping(_event)
|
|
39
|
+
# Make sure this is sent before shutdown
|
|
40
|
+
report!
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Instrument on the fact that Karafka has stopped
|
|
44
|
+
#
|
|
45
|
+
# We do this actually before the process ends but we need to do this so the UI does not
|
|
46
|
+
# have this "handling" stopping process.
|
|
47
|
+
#
|
|
48
|
+
# @param _event [Karafka::Core::Monitoring::Event]
|
|
49
|
+
def on_app_stopped(_event)
|
|
50
|
+
# Make sure this is sent before shutdown
|
|
51
|
+
report!
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|