karafka-web 0.7.10 → 0.8.0.rc1
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/.github/workflows/ci.yml +18 -5
- data/.ruby-version +1 -1
- data/CHANGELOG.md +63 -0
- data/Gemfile.lock +22 -22
- data/docker-compose.yml +3 -1
- data/karafka-web.gemspec +2 -2
- data/lib/karafka/web/config.rb +16 -3
- data/lib/karafka/web/contracts/config.rb +7 -2
- data/lib/karafka/web/errors.rb +12 -0
- data/lib/karafka/web/inflector.rb +33 -0
- data/lib/karafka/web/installer.rb +20 -11
- data/lib/karafka/web/management/actions/base.rb +36 -0
- data/lib/karafka/web/management/actions/clean_boot_file.rb +33 -0
- data/lib/karafka/web/management/actions/create_initial_states.rb +77 -0
- data/lib/karafka/web/management/actions/create_topics.rb +139 -0
- data/lib/karafka/web/management/actions/delete_topics.rb +30 -0
- data/lib/karafka/web/management/actions/enable.rb +117 -0
- data/lib/karafka/web/management/actions/extend_boot_file.rb +39 -0
- data/lib/karafka/web/management/actions/migrate_states_data.rb +18 -0
- data/lib/karafka/web/management/migrations/0_base.rb +58 -0
- data/lib/karafka/web/management/migrations/0_set_initial_consumers_metrics.rb +36 -0
- data/lib/karafka/web/management/migrations/0_set_initial_consumers_state.rb +43 -0
- data/lib/karafka/web/management/migrations/1699543515_fill_missing_received_and_sent_bytes_in_consumers_metrics.rb +26 -0
- data/lib/karafka/web/management/migrations/1699543515_fill_missing_received_and_sent_bytes_in_consumers_state.rb +23 -0
- data/lib/karafka/web/management/migrations/1700234522_introduce_waiting_in_consumers_metrics.rb +24 -0
- data/lib/karafka/web/management/migrations/1700234522_introduce_waiting_in_consumers_state.rb +20 -0
- data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_metrics.rb +24 -0
- data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_state.rb +20 -0
- data/lib/karafka/web/management/migrations/1704722380_split_listeners_into_active_and_paused_in_metrics.rb +36 -0
- data/lib/karafka/web/management/migrations/1704722380_split_listeners_into_active_and_paused_in_states.rb +32 -0
- data/lib/karafka/web/management/migrator.rb +117 -0
- data/lib/karafka/web/processing/consumer.rb +39 -38
- data/lib/karafka/web/processing/consumers/aggregators/metrics.rb +2 -3
- data/lib/karafka/web/processing/consumers/aggregators/state.rb +8 -3
- data/lib/karafka/web/processing/consumers/contracts/aggregated_stats.rb +5 -1
- data/lib/karafka/web/processing/publisher.rb +59 -0
- data/lib/karafka/web/tracking/consumers/contracts/job.rb +3 -2
- data/lib/karafka/web/tracking/consumers/contracts/partition.rb +1 -0
- data/lib/karafka/web/tracking/consumers/contracts/report.rb +6 -1
- data/lib/karafka/web/tracking/consumers/contracts/subscription_group.rb +10 -1
- data/lib/karafka/web/tracking/consumers/listeners/connections.rb +49 -0
- data/lib/karafka/web/tracking/consumers/listeners/pausing.rb +7 -4
- data/lib/karafka/web/tracking/consumers/listeners/processing.rb +78 -70
- data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +40 -13
- data/lib/karafka/web/tracking/consumers/sampler.rb +82 -25
- data/lib/karafka/web/tracking/helpers/ttls/array.rb +72 -0
- data/lib/karafka/web/tracking/helpers/ttls/hash.rb +34 -0
- data/lib/karafka/web/tracking/helpers/ttls/stats.rb +49 -0
- data/lib/karafka/web/tracking/helpers/ttls/windows.rb +32 -0
- data/lib/karafka/web/tracking/reporter.rb +1 -0
- data/lib/karafka/web/ui/app.rb +22 -4
- data/lib/karafka/web/ui/base.rb +18 -2
- data/lib/karafka/web/ui/controllers/base.rb +34 -4
- data/lib/karafka/web/ui/controllers/become_pro.rb +1 -1
- data/lib/karafka/web/ui/controllers/cluster.rb +33 -9
- data/lib/karafka/web/ui/controllers/consumers.rb +8 -2
- data/lib/karafka/web/ui/controllers/dashboard.rb +2 -2
- data/lib/karafka/web/ui/controllers/errors.rb +2 -2
- data/lib/karafka/web/ui/controllers/jobs.rb +55 -5
- data/lib/karafka/web/ui/controllers/requests/params.rb +5 -0
- data/lib/karafka/web/ui/controllers/responses/deny.rb +15 -0
- data/lib/karafka/web/ui/controllers/responses/file.rb +23 -0
- data/lib/karafka/web/ui/controllers/responses/{data.rb → render.rb} +3 -3
- data/lib/karafka/web/ui/controllers/routing.rb +11 -2
- data/lib/karafka/web/ui/controllers/status.rb +1 -1
- data/lib/karafka/web/ui/helpers/application_helper.rb +70 -0
- data/lib/karafka/web/ui/lib/hash_proxy.rb +29 -14
- data/lib/karafka/web/ui/lib/sorter.rb +170 -0
- data/lib/karafka/web/ui/models/counters.rb +6 -0
- data/lib/karafka/web/ui/models/health.rb +23 -2
- data/lib/karafka/web/ui/models/jobs.rb +48 -0
- data/lib/karafka/web/ui/models/metrics/charts/aggregated.rb +33 -0
- data/lib/karafka/web/ui/models/metrics/charts/topics.rb +1 -1
- data/lib/karafka/web/ui/models/process.rb +2 -1
- data/lib/karafka/web/ui/models/status.rb +23 -7
- data/lib/karafka/web/ui/models/topic.rb +3 -1
- data/lib/karafka/web/ui/models/visibility_filter.rb +16 -0
- data/lib/karafka/web/ui/pro/app.rb +44 -6
- data/lib/karafka/web/ui/pro/controllers/cluster.rb +1 -0
- data/lib/karafka/web/ui/pro/controllers/consumers.rb +52 -6
- data/lib/karafka/web/ui/pro/controllers/dashboard.rb +1 -1
- data/lib/karafka/web/ui/pro/controllers/dlq.rb +1 -1
- data/lib/karafka/web/ui/pro/controllers/errors.rb +3 -3
- data/lib/karafka/web/ui/pro/controllers/explorer.rb +8 -8
- data/lib/karafka/web/ui/pro/controllers/health.rb +34 -2
- data/lib/karafka/web/ui/pro/controllers/jobs.rb +11 -0
- data/lib/karafka/web/ui/pro/controllers/messages.rb +42 -0
- data/lib/karafka/web/ui/pro/controllers/routing.rb +11 -2
- data/lib/karafka/web/ui/pro/views/consumers/_breadcrumbs.erb +8 -2
- data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +14 -8
- data/lib/karafka/web/ui/pro/views/consumers/_counters.erb +8 -6
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_job.erb +4 -1
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_no_jobs.erb +1 -1
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +1 -3
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_subscription_group.erb +28 -11
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +10 -3
- data/lib/karafka/web/ui/pro/views/consumers/index.erb +3 -3
- data/lib/karafka/web/ui/pro/views/consumers/pending_jobs.erb +43 -0
- data/lib/karafka/web/ui/pro/views/consumers/{jobs.erb → running_jobs.erb} +11 -10
- data/lib/karafka/web/ui/pro/views/dashboard/index.erb +7 -1
- data/lib/karafka/web/ui/pro/views/explorer/message/_message_actions.erb +18 -0
- data/lib/karafka/web/ui/pro/views/explorer/message/_metadata.erb +43 -0
- data/lib/karafka/web/ui/pro/views/explorer/message/_payload.erb +21 -0
- data/lib/karafka/web/ui/pro/views/explorer/message/_payload_actions.erb +19 -0
- data/lib/karafka/web/ui/pro/views/explorer/show.erb +9 -84
- data/lib/karafka/web/ui/pro/views/health/_breadcrumbs.erb +8 -0
- data/lib/karafka/web/ui/pro/views/health/_partition.erb +1 -3
- data/lib/karafka/web/ui/pro/views/health/_partition_offset.erb +4 -4
- data/lib/karafka/web/ui/pro/views/health/_partition_times.erb +32 -0
- data/lib/karafka/web/ui/pro/views/health/_tabs.erb +9 -0
- data/lib/karafka/web/ui/pro/views/health/changes.erb +66 -0
- data/lib/karafka/web/ui/pro/views/health/offsets.erb +14 -14
- data/lib/karafka/web/ui/pro/views/health/overview.erb +11 -11
- data/lib/karafka/web/ui/pro/views/jobs/_job.erb +1 -1
- data/lib/karafka/web/ui/pro/views/jobs/_no_jobs.erb +1 -1
- data/lib/karafka/web/ui/pro/views/jobs/pending.erb +39 -0
- data/lib/karafka/web/ui/pro/views/jobs/running.erb +39 -0
- data/lib/karafka/web/ui/pro/views/routing/_consumer_group.erb +2 -2
- data/lib/karafka/web/ui/pro/views/routing/_topic.erb +9 -0
- data/lib/karafka/web/ui/pro/views/routing/show.erb +12 -0
- data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +1 -1
- data/lib/karafka/web/ui/public/javascripts/application.js +10 -0
- data/lib/karafka/web/ui/public/stylesheets/application.css +4 -0
- data/lib/karafka/web/ui/views/cluster/_breadcrumbs.erb +16 -0
- data/lib/karafka/web/ui/views/cluster/_tabs.erb +27 -0
- data/lib/karafka/web/ui/views/cluster/brokers.erb +27 -0
- data/lib/karafka/web/ui/views/cluster/topics.erb +35 -0
- data/lib/karafka/web/ui/views/consumers/_counters.erb +8 -6
- data/lib/karafka/web/ui/views/consumers/_summary.erb +2 -2
- data/lib/karafka/web/ui/views/consumers/index.erb +3 -3
- data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +23 -7
- data/lib/karafka/web/ui/views/dashboard/index.erb +19 -8
- data/lib/karafka/web/ui/views/errors/show.erb +2 -23
- data/lib/karafka/web/ui/views/jobs/_breadcrumbs.erb +17 -1
- data/lib/karafka/web/ui/views/jobs/_job.erb +1 -1
- data/lib/karafka/web/ui/views/jobs/_no_jobs.erb +1 -1
- data/lib/karafka/web/ui/views/jobs/_tabs.erb +27 -0
- data/lib/karafka/web/ui/views/jobs/{index.erb → pending.erb} +9 -7
- data/lib/karafka/web/ui/{pro/views/jobs/index.erb → views/jobs/running.erb} +9 -11
- data/lib/karafka/web/ui/views/routing/_consumer_group.erb +14 -12
- data/lib/karafka/web/ui/views/shared/_navigation.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_pagination.erb +1 -1
- data/lib/karafka/web/ui/views/shared/exceptions/not_allowed.erb +37 -0
- data/lib/karafka/web/ui/views/status/show.erb +17 -2
- data/lib/karafka/web/ui/views/status/warnings/_routing_topics_presence.erb +15 -0
- data/lib/karafka/web/version.rb +1 -1
- data/lib/karafka/web.rb +6 -2
- data.tar.gz.sig +0 -0
- metadata +61 -26
- metadata.gz.sig +0 -0
- data/lib/karafka/web/management/base.rb +0 -34
- data/lib/karafka/web/management/clean_boot_file.rb +0 -31
- data/lib/karafka/web/management/create_initial_states.rb +0 -101
- data/lib/karafka/web/management/create_topics.rb +0 -133
- data/lib/karafka/web/management/delete_topics.rb +0 -28
- data/lib/karafka/web/management/enable.rb +0 -102
- data/lib/karafka/web/management/extend_boot_file.rb +0 -37
- data/lib/karafka/web/tracking/ttl_array.rb +0 -59
- data/lib/karafka/web/tracking/ttl_hash.rb +0 -16
- data/lib/karafka/web/ui/pro/views/dashboard/_ranges_selector.erb +0 -39
- data/lib/karafka/web/ui/views/cluster/index.erb +0 -74
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Actions
|
7
|
+
# Creates all the needed topics (if they don't exist).
|
8
|
+
# It does **not** populate data.
|
9
|
+
class CreateTopics < Base
|
10
|
+
# Runs the creation process
|
11
|
+
#
|
12
|
+
# @param replication_factor [Integer] replication factor for Web-UI topics
|
13
|
+
#
|
14
|
+
# @note The order of creation of those topics is important. In order to support the
|
15
|
+
# zero-downtime bootstrap, we use the presence of the states topic and its initial
|
16
|
+
# state existence as an indicator that the setup went as expected. It the consumers
|
17
|
+
# states topic exists and contains needed data, it means all went as expected and that
|
18
|
+
# topics created before it also exist (as no error).
|
19
|
+
def call(replication_factor)
|
20
|
+
consumers_states_topic = ::Karafka::Web.config.topics.consumers.states
|
21
|
+
consumers_metrics_topic = ::Karafka::Web.config.topics.consumers.metrics
|
22
|
+
consumers_reports_topic = ::Karafka::Web.config.topics.consumers.reports
|
23
|
+
errors_topic = ::Karafka::Web.config.topics.errors
|
24
|
+
|
25
|
+
if existing_topics_names.include?(errors_topic)
|
26
|
+
exists(errors_topic)
|
27
|
+
else
|
28
|
+
creating(errors_topic)
|
29
|
+
# All the errors will be dispatched here
|
30
|
+
# This topic can have multiple partitions but we go with one by default. A single
|
31
|
+
# Ruby process should not crash that often and if there is an expectation of a higher
|
32
|
+
# volume of errors, this can be changed by the end user
|
33
|
+
::Karafka::Admin.create_topic(
|
34
|
+
errors_topic,
|
35
|
+
1,
|
36
|
+
replication_factor,
|
37
|
+
# Remove really old errors (older than 3 months just to preserve space)
|
38
|
+
{
|
39
|
+
'cleanup.policy': 'delete',
|
40
|
+
'retention.ms': 3 * 31 * 24 * 60 * 60 * 1_000 # 3 months
|
41
|
+
}
|
42
|
+
)
|
43
|
+
created(errors_topic)
|
44
|
+
end
|
45
|
+
|
46
|
+
if existing_topics_names.include?(consumers_reports_topic)
|
47
|
+
exists(consumers_reports_topic)
|
48
|
+
else
|
49
|
+
creating(consumers_reports_topic)
|
50
|
+
# This topic needs to have one partition
|
51
|
+
::Karafka::Admin.create_topic(
|
52
|
+
consumers_reports_topic,
|
53
|
+
1,
|
54
|
+
replication_factor,
|
55
|
+
# We do not need to to store this data for longer than 1 day as this data is only
|
56
|
+
# used to materialize the end states
|
57
|
+
# On the other hand we do not want to have it really short-living because in case
|
58
|
+
# of a consumer crash, we may want to use this info to catch up and backfill the
|
59
|
+
# state.
|
60
|
+
#
|
61
|
+
# In case its not consumed because no processes are running, it also usually means
|
62
|
+
# there's no data to consume because no karafka servers report
|
63
|
+
{
|
64
|
+
'cleanup.policy': 'delete',
|
65
|
+
'retention.ms': 24 * 60 * 60 * 1_000 # 1 day
|
66
|
+
}
|
67
|
+
)
|
68
|
+
created(consumers_reports_topic)
|
69
|
+
end
|
70
|
+
|
71
|
+
if existing_topics_names.include?(consumers_metrics_topic)
|
72
|
+
exists(consumers_metrics_topic)
|
73
|
+
else
|
74
|
+
creating(consumers_metrics_topic)
|
75
|
+
# This topic needs to have one partition
|
76
|
+
# Same as states - only most recent is relevant as it is a materialized state
|
77
|
+
::Karafka::Admin.create_topic(
|
78
|
+
consumers_metrics_topic,
|
79
|
+
1,
|
80
|
+
replication_factor,
|
81
|
+
{
|
82
|
+
'cleanup.policy': 'compact',
|
83
|
+
'retention.ms': 60 * 60 * 1_000, # 1h
|
84
|
+
'segment.ms': 24 * 60 * 60 * 1_000, # 1 day
|
85
|
+
'segment.bytes': 104_857_600 # 100MB
|
86
|
+
}
|
87
|
+
)
|
88
|
+
created(consumers_metrics_topic)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Create only if needed
|
92
|
+
if existing_topics_names.include?(consumers_states_topic)
|
93
|
+
exists(consumers_states_topic)
|
94
|
+
else
|
95
|
+
creating(consumers_states_topic)
|
96
|
+
# This topic needs to have one partition
|
97
|
+
::Karafka::Admin.create_topic(
|
98
|
+
consumers_states_topic,
|
99
|
+
1,
|
100
|
+
replication_factor,
|
101
|
+
# We care only about the most recent state, previous are irrelevant. So we can
|
102
|
+
# easily compact after one minute. We do not use this beyond the most recent
|
103
|
+
# collective state, hence it all can easily go away. We also limit the segment
|
104
|
+
# size to at most 100MB not to use more space ever.
|
105
|
+
{
|
106
|
+
'cleanup.policy': 'compact',
|
107
|
+
'retention.ms': 60 * 60 * 1_000,
|
108
|
+
'segment.ms': 24 * 60 * 60 * 1_000, # 1 day
|
109
|
+
'segment.bytes': 104_857_600 # 100MB
|
110
|
+
}
|
111
|
+
)
|
112
|
+
created(consumers_states_topic)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# @param topic_name [String] name of the topic that exists
|
119
|
+
# @return [String] formatted message
|
120
|
+
def exists(topic_name)
|
121
|
+
puts("Topic #{topic_name} #{already} exists.")
|
122
|
+
end
|
123
|
+
|
124
|
+
# @param topic_name [String] name of the topic that we are creating
|
125
|
+
# @return [String] formatted message
|
126
|
+
def creating(topic_name)
|
127
|
+
puts("Creating topic #{topic_name}...")
|
128
|
+
end
|
129
|
+
|
130
|
+
# @param topic_name [String] name of the topic that we created
|
131
|
+
# @return [String] formatted message
|
132
|
+
def created(topic_name)
|
133
|
+
puts("Topic #{topic_name} #{successfully} created.")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Actions
|
7
|
+
# Removes the Web-UI topics from Kafka
|
8
|
+
class DeleteTopics < Base
|
9
|
+
# Removes the Web-UI topics
|
10
|
+
def call
|
11
|
+
[
|
12
|
+
::Karafka::Web.config.topics.consumers.states,
|
13
|
+
::Karafka::Web.config.topics.consumers.reports,
|
14
|
+
::Karafka::Web.config.topics.consumers.metrics,
|
15
|
+
::Karafka::Web.config.topics.errors
|
16
|
+
].each do |topic_name|
|
17
|
+
if existing_topics_names.include?(topic_name.to_s)
|
18
|
+
puts "Removing #{topic_name}..."
|
19
|
+
::Karafka::Admin.delete_topic(topic_name)
|
20
|
+
puts "Topic #{topic_name} #{successfully} deleted."
|
21
|
+
else
|
22
|
+
puts "Topic #{topic_name} not found."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Actions
|
7
|
+
# @note This runs on each process start that has `karafka.rb`. It needs to be executed
|
8
|
+
# also in the context of other processes types and not only karafka server, because it
|
9
|
+
# installs producers instrumentation and routing as well.
|
10
|
+
class Enable < Base
|
11
|
+
# Enables routing consumer group and subscribes Web-UI listeners
|
12
|
+
def call
|
13
|
+
extend_routing
|
14
|
+
setup_tracking_activity
|
15
|
+
|
16
|
+
# Do not subscribe monitors or do anything else if tracking is disabled
|
17
|
+
return unless ::Karafka::Web.config.tracking.active
|
18
|
+
|
19
|
+
subscribe_to_monitor
|
20
|
+
subscribe_to_close_web_producer
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Enables tracking if it was not explicitly disabled by the user
|
26
|
+
def setup_tracking_activity
|
27
|
+
return unless ::Karafka::Web.config.tracking.active.nil?
|
28
|
+
|
29
|
+
::Karafka::Web.config.tracking.active = true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Enables all the needed routes
|
33
|
+
def extend_routing
|
34
|
+
::Karafka::App.routes.draw do
|
35
|
+
web_deserializer = ::Karafka::Web::Deserializer.new
|
36
|
+
|
37
|
+
consumer_group ::Karafka::Web.config.processing.consumer_group do
|
38
|
+
# Topic we listen on to materialize the states
|
39
|
+
topic ::Karafka::Web.config.topics.consumers.reports do
|
40
|
+
config(active: false)
|
41
|
+
active ::Karafka::Web.config.processing.active
|
42
|
+
# Since we materialize state in intervals, we can poll for half of this time
|
43
|
+
# without impacting the reporting responsiveness
|
44
|
+
max_wait_time ::Karafka::Web.config.processing.interval / 2
|
45
|
+
max_messages 1_000
|
46
|
+
consumer ::Karafka::Web::Processing::Consumer
|
47
|
+
# This needs to be true in order not to reload the consumer in dev. This consumer
|
48
|
+
# should not be affected by the end user development process
|
49
|
+
consumer_persistence true
|
50
|
+
deserializer web_deserializer
|
51
|
+
manual_offset_management true
|
52
|
+
# Start from the most recent data, do not materialize historical states
|
53
|
+
# This prevents us from dealing with cases, where client id would be changed and
|
54
|
+
# consumer group name would be renamed and we would start consuming all
|
55
|
+
# historical
|
56
|
+
initial_offset 'latest'
|
57
|
+
end
|
58
|
+
|
59
|
+
# We define those three here without consumption, so Web understands how to
|
60
|
+
# deserialize them when used / viewed
|
61
|
+
topic ::Karafka::Web.config.topics.consumers.states do
|
62
|
+
config(active: false)
|
63
|
+
active false
|
64
|
+
deserializer web_deserializer
|
65
|
+
end
|
66
|
+
|
67
|
+
topic ::Karafka::Web.config.topics.consumers.metrics do
|
68
|
+
config(active: false)
|
69
|
+
active false
|
70
|
+
deserializer web_deserializer
|
71
|
+
end
|
72
|
+
|
73
|
+
topic ::Karafka::Web.config.topics.errors do
|
74
|
+
config(active: false)
|
75
|
+
active false
|
76
|
+
deserializer web_deserializer
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Subscribes with all needed listeners
|
83
|
+
def subscribe_to_monitor
|
84
|
+
# Installs all the consumer related listeners
|
85
|
+
::Karafka::Web.config.tracking.consumers.listeners.each do |listener|
|
86
|
+
::Karafka.monitor.subscribe(listener)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Installs all the producer related listeners into Karafka default listener and
|
90
|
+
# into Karafka::Web listener in case it would be different than the Karafka one
|
91
|
+
::Karafka::Web.config.tracking.producers.listeners.each do |listener|
|
92
|
+
::Karafka.producer.monitor.subscribe(listener)
|
93
|
+
|
94
|
+
# Do not instrument twice in case only one default producer is used
|
95
|
+
next if ::Karafka.producer == ::Karafka::Web.producer
|
96
|
+
|
97
|
+
::Karafka::Web.producer.monitor.subscribe(listener)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# In most cases we want to close the producer if possible.
|
102
|
+
# While we cannot do it easily in user processes and we should rely on WaterDrop
|
103
|
+
# finalization logic, we can do it in `karafka server` on terminate
|
104
|
+
#
|
105
|
+
# In other places, this producer anyhow should not be used.
|
106
|
+
def subscribe_to_close_web_producer
|
107
|
+
::Karafka::App.monitor.subscribe('app.terminated') do
|
108
|
+
# If Web producer is the same as `Karafka.producer` it will do nothing as you can
|
109
|
+
# call `#close` multiple times without side effects
|
110
|
+
::Karafka::Web.producer.close
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Actions
|
7
|
+
# Extends the boot file with Web components
|
8
|
+
class ExtendBootFile < Base
|
9
|
+
# Code that is needed in the `karafka.rb` to connect Web UI to Karafka
|
10
|
+
ENABLER_CODE = 'Karafka::Web.enable!'
|
11
|
+
|
12
|
+
# Template with initial Web UI configuration
|
13
|
+
# Session secret needs to be set per user and per env
|
14
|
+
SETUP_TEMPLATE = <<~CONFIG.freeze
|
15
|
+
Karafka::Web.setup do |config|
|
16
|
+
# You may want to set it per ENV. This value was randomly generated.
|
17
|
+
config.ui.sessions.secret = '#{SecureRandom.hex(32)}'
|
18
|
+
end
|
19
|
+
|
20
|
+
#{ENABLER_CODE}
|
21
|
+
CONFIG
|
22
|
+
|
23
|
+
# Adds needed code
|
24
|
+
def call
|
25
|
+
if File.read(Karafka.boot_file).include?(ENABLER_CODE)
|
26
|
+
puts "Web UI #{already} installed."
|
27
|
+
else
|
28
|
+
puts 'Updating the Karafka boot file...'
|
29
|
+
File.open(Karafka.boot_file, 'a') do |f|
|
30
|
+
f << "\n#{SETUP_TEMPLATE}\n"
|
31
|
+
end
|
32
|
+
puts "Karafka boot file #{successfully} updated."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Actions
|
7
|
+
# Command to migrate states data
|
8
|
+
# Useful when we have older schema and need to move forward
|
9
|
+
class MigrateStatesData < Base
|
10
|
+
# Runs needed migrations (if any) on the states topics
|
11
|
+
def call
|
12
|
+
Migrator.new.call
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
# Namespace for storing migrations of our Web UI topics data
|
7
|
+
module Migrations
|
8
|
+
# Base for all our migrations
|
9
|
+
#
|
10
|
+
# Each migration **MUST** have a `#migrate` method defined
|
11
|
+
# Migrations are expected to modify the provided state **IN PLACE**
|
12
|
+
class Base
|
13
|
+
include Karafka::Core::Helpers::Time
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# First version that should **NOT** be affected by this migration
|
17
|
+
attr_accessor :versions_until
|
18
|
+
# What resource does it relate it
|
19
|
+
# One migration should modify only one resource type
|
20
|
+
attr_accessor :type
|
21
|
+
|
22
|
+
# @param version [String] sem-ver version
|
23
|
+
# @return [Boolean] is the given migration applicable
|
24
|
+
def applicable?(version)
|
25
|
+
version < versions_until
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param state [Hash] deserialized state to be modified
|
29
|
+
def migrate(state)
|
30
|
+
raise NotImplementedError, 'Implement in a subclass'
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Integer] index for sorting. Older migrations are always applied first
|
34
|
+
def index
|
35
|
+
instance_method(:migrate)
|
36
|
+
.source_location
|
37
|
+
.first
|
38
|
+
.split('/')
|
39
|
+
.last
|
40
|
+
.split('_')
|
41
|
+
.first
|
42
|
+
.to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Array<Class>] array with migrations sorted from oldest to latest. This is
|
46
|
+
# the order in which they need to be applied
|
47
|
+
def sorted_descendants
|
48
|
+
ObjectSpace
|
49
|
+
.each_object(Class)
|
50
|
+
.select { |klass| klass < self }
|
51
|
+
.sort_by(&:index)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Migrations
|
7
|
+
# Initial migration that sets the consumers metrics initial first state.
|
8
|
+
# This is the basic of metrics as they were when they were introduced.
|
9
|
+
class SetInitialConsumersMetrics < Base
|
10
|
+
# Always migrate from empty up
|
11
|
+
self.versions_until = '0.0.1'
|
12
|
+
self.type = :consumers_metrics
|
13
|
+
|
14
|
+
# @param state [Hash] initial empty state
|
15
|
+
def migrate(state)
|
16
|
+
state.merge!(
|
17
|
+
aggregated: {
|
18
|
+
days: [],
|
19
|
+
hours: [],
|
20
|
+
minutes: [],
|
21
|
+
seconds: []
|
22
|
+
},
|
23
|
+
consumer_groups: {
|
24
|
+
days: [],
|
25
|
+
hours: [],
|
26
|
+
minutes: [],
|
27
|
+
seconds: []
|
28
|
+
},
|
29
|
+
dispatched_at: float_now
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Migrations
|
7
|
+
# Initial migration that sets the consumers state initial first state.
|
8
|
+
# This is the basic of state as they were when they were introduced.
|
9
|
+
class SetInitialConsumersState < Base
|
10
|
+
# Run this only on the first setup
|
11
|
+
self.versions_until = '0.0.1'
|
12
|
+
self.type = :consumers_state
|
13
|
+
|
14
|
+
# @param state [Hash]
|
15
|
+
def migrate(state)
|
16
|
+
state.merge!(
|
17
|
+
processes: {},
|
18
|
+
stats: {
|
19
|
+
batches: 0,
|
20
|
+
messages: 0,
|
21
|
+
retries: 0,
|
22
|
+
dead: 0,
|
23
|
+
busy: 0,
|
24
|
+
enqueued: 0,
|
25
|
+
processing: 0,
|
26
|
+
workers: 0,
|
27
|
+
processes: 0,
|
28
|
+
rss: 0,
|
29
|
+
listeners: 0,
|
30
|
+
utilization: 0,
|
31
|
+
errors: 0,
|
32
|
+
lag_stored: 0,
|
33
|
+
lag: 0
|
34
|
+
},
|
35
|
+
schema_state: 'accepted',
|
36
|
+
dispatched_at: float_now
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Migrations
|
7
|
+
# Adds bytes_sent and bytes_received to all the aggregated metrics samples, so we have
|
8
|
+
# charts that do not have to fill gaps or check anything
|
9
|
+
class FillMissingReceivedAndSentBytesInConsumersMetrics < Base
|
10
|
+
self.versions_until = '1.1.0'
|
11
|
+
self.type = :consumers_metrics
|
12
|
+
|
13
|
+
# @param state [Hash] metrics state
|
14
|
+
def migrate(state)
|
15
|
+
state[:aggregated].each_value do |metrics|
|
16
|
+
metrics.each do |metric|
|
17
|
+
metric.last[:bytes_sent] = 0
|
18
|
+
metric.last[:bytes_received] = 0
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Migrations
|
7
|
+
# Similar to filling in consumers metrics, we initialize this with zeros so it is always
|
8
|
+
# present as expected
|
9
|
+
class FillMissingReceivedAndSentBytesInConsumersState < Base
|
10
|
+
# Network metrics were introduced with schema 1.2.0
|
11
|
+
self.versions_until = '1.2.0'
|
12
|
+
self.type = :consumers_state
|
13
|
+
|
14
|
+
# @param state [Hash]
|
15
|
+
def migrate(state)
|
16
|
+
state[:stats][:bytes_sent] = 0
|
17
|
+
state[:stats][:bytes_received] = 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/karafka/web/management/migrations/1700234522_introduce_waiting_in_consumers_metrics.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Migrations
|
7
|
+
# Introduce waiting in consumers metrics to complement busy and enqueued for jobs metrics
|
8
|
+
class IntroduceWaitingInConsumersMetrics < Base
|
9
|
+
self.versions_until = '1.1.1'
|
10
|
+
self.type = :consumers_metrics
|
11
|
+
|
12
|
+
# @param state [Hash]
|
13
|
+
def migrate(state)
|
14
|
+
state[:aggregated].each_value do |metrics|
|
15
|
+
metrics.each do |metric|
|
16
|
+
metric.last[:waiting] = 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Migrations
|
7
|
+
# Introduce waiting in consumers metrics to complement busy and enqueued for jobs stats
|
8
|
+
class IntroduceWaitingInConsumersState < Base
|
9
|
+
self.versions_until = '1.2.1'
|
10
|
+
self.type = :consumers_state
|
11
|
+
|
12
|
+
# @param state [Hash]
|
13
|
+
def migrate(state)
|
14
|
+
state[:stats][:waiting] = 0
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_metrics.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Migrations
|
7
|
+
# Moves unused "processing" that was used instead of "busy" in older versions
|
8
|
+
class RemoveProcessingFromConsumersMetrics < Base
|
9
|
+
self.versions_until = '1.1.1'
|
10
|
+
self.type = :consumers_metrics
|
11
|
+
|
12
|
+
# @param state [Hash]
|
13
|
+
def migrate(state)
|
14
|
+
state[:aggregated].each_value do |metrics|
|
15
|
+
metrics.each do |metric|
|
16
|
+
metric.last.delete(:processing)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_state.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Migrations
|
7
|
+
# Moves unused "processing" that was used instead of "busy" in older versions
|
8
|
+
class RemoveProcessingFromConsumersState < Base
|
9
|
+
self.versions_until = '1.2.1'
|
10
|
+
self.type = :consumers_state
|
11
|
+
|
12
|
+
# @param state [Hash]
|
13
|
+
def migrate(state)
|
14
|
+
state[:stats].delete(:processing)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Management
|
6
|
+
module Migrations
|
7
|
+
# Since we have introduced notion of pause listeners, we need to reflect this in the
|
8
|
+
# UI, so the scaling changes are visible
|
9
|
+
class SplitListenersIntoActiveAndPausedInMetrics < Base
|
10
|
+
self.versions_until = '1.1.2'
|
11
|
+
self.type = :consumers_metrics
|
12
|
+
|
13
|
+
# @param state [Hash]
|
14
|
+
def migrate(state)
|
15
|
+
state[:aggregated].each_value do |metrics|
|
16
|
+
metrics.each do |metric|
|
17
|
+
listeners = if metric.last.key?(:listeners)
|
18
|
+
metric.last[:listeners].to_i
|
19
|
+
elsif metric.last.key?(:listeners_count)
|
20
|
+
metric.last[:listeners_count].to_i
|
21
|
+
else
|
22
|
+
0
|
23
|
+
end
|
24
|
+
|
25
|
+
metric.last[:listeners] = {
|
26
|
+
active: listeners,
|
27
|
+
standby: 0
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|