karafka-web 0.6.3 → 0.7.0
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 +13 -4
- data/CHANGELOG.md +119 -5
- data/Gemfile +1 -0
- data/Gemfile.lock +27 -24
- data/README.md +2 -0
- data/bin/rspecs +6 -0
- data/certs/cert_chain.pem +21 -21
- data/docker-compose.yml +22 -0
- data/karafka-web.gemspec +3 -3
- data/lib/karafka/web/app.rb +6 -2
- data/lib/karafka/web/cli.rb +51 -47
- data/lib/karafka/web/config.rb +33 -9
- data/lib/karafka/web/contracts/base.rb +32 -0
- data/lib/karafka/web/contracts/config.rb +63 -0
- data/lib/karafka/web/deserializer.rb +10 -1
- data/lib/karafka/web/errors.rb +29 -7
- data/lib/karafka/web/installer.rb +58 -148
- data/lib/karafka/web/management/base.rb +34 -0
- data/lib/karafka/web/management/clean_boot_file.rb +31 -0
- data/lib/karafka/web/management/create_initial_states.rb +101 -0
- data/lib/karafka/web/management/create_topics.rb +127 -0
- data/lib/karafka/web/management/delete_topics.rb +28 -0
- data/lib/karafka/web/management/enable.rb +82 -0
- data/lib/karafka/web/management/extend_boot_file.rb +37 -0
- data/lib/karafka/web/processing/consumer.rb +73 -17
- data/lib/karafka/web/processing/consumers/aggregators/base.rb +56 -0
- data/lib/karafka/web/processing/consumers/aggregators/metrics.rb +154 -0
- data/lib/karafka/web/processing/consumers/aggregators/state.rb +180 -0
- data/lib/karafka/web/processing/consumers/contracts/aggregated_stats.rb +32 -0
- data/lib/karafka/web/processing/consumers/contracts/metrics.rb +53 -0
- data/lib/karafka/web/processing/consumers/contracts/process.rb +19 -0
- data/lib/karafka/web/processing/consumers/contracts/state.rb +49 -0
- data/lib/karafka/web/processing/consumers/contracts/topic_stats.rb +21 -0
- data/lib/karafka/web/processing/consumers/metrics.rb +29 -0
- data/lib/karafka/web/processing/consumers/schema_manager.rb +56 -0
- data/lib/karafka/web/processing/consumers/state.rb +6 -9
- data/lib/karafka/web/processing/time_series_tracker.rb +130 -0
- data/lib/karafka/web/tracking/consumers/contracts/consumer_group.rb +2 -2
- data/lib/karafka/web/tracking/consumers/contracts/job.rb +2 -1
- data/lib/karafka/web/tracking/consumers/contracts/partition.rb +14 -1
- data/lib/karafka/web/tracking/consumers/contracts/report.rb +10 -8
- data/lib/karafka/web/tracking/consumers/contracts/subscription_group.rb +2 -2
- data/lib/karafka/web/tracking/consumers/contracts/topic.rb +2 -2
- data/lib/karafka/web/tracking/consumers/listeners/processing.rb +6 -2
- data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +15 -1
- data/lib/karafka/web/tracking/consumers/reporter.rb +14 -6
- data/lib/karafka/web/tracking/consumers/sampler.rb +80 -39
- data/lib/karafka/web/tracking/contracts/error.rb +2 -1
- data/lib/karafka/web/ui/app.rb +20 -10
- data/lib/karafka/web/ui/base.rb +56 -6
- data/lib/karafka/web/ui/controllers/base.rb +28 -0
- data/lib/karafka/web/ui/controllers/become_pro.rb +1 -1
- data/lib/karafka/web/ui/controllers/cluster.rb +12 -6
- data/lib/karafka/web/ui/controllers/consumers.rb +4 -2
- data/lib/karafka/web/ui/controllers/dashboard.rb +32 -0
- data/lib/karafka/web/ui/controllers/errors.rb +19 -6
- data/lib/karafka/web/ui/controllers/jobs.rb +4 -2
- data/lib/karafka/web/ui/controllers/requests/params.rb +28 -0
- data/lib/karafka/web/ui/controllers/responses/redirect.rb +29 -0
- data/lib/karafka/web/ui/helpers/application_helper.rb +57 -14
- data/lib/karafka/web/ui/helpers/paths_helper.rb +48 -0
- data/lib/karafka/web/ui/lib/hash_proxy.rb +18 -6
- data/lib/karafka/web/ui/lib/paginations/base.rb +61 -0
- data/lib/karafka/web/ui/lib/paginations/offset_based.rb +96 -0
- data/lib/karafka/web/ui/lib/paginations/page_based.rb +70 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/arrays.rb +33 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/base.rb +23 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/partitions.rb +52 -0
- data/lib/karafka/web/ui/lib/paginations/paginators/sets.rb +85 -0
- data/lib/karafka/web/ui/lib/paginations/watermark_offsets_based.rb +75 -0
- data/lib/karafka/web/ui/lib/ttl_cache.rb +82 -0
- data/lib/karafka/web/ui/models/cluster_info.rb +59 -0
- data/lib/karafka/web/ui/models/consumers_metrics.rb +46 -0
- data/lib/karafka/web/ui/models/{state.rb → consumers_state.rb} +6 -2
- data/lib/karafka/web/ui/models/health.rb +37 -7
- data/lib/karafka/web/ui/models/message.rb +123 -39
- data/lib/karafka/web/ui/models/metrics/aggregated.rb +196 -0
- data/lib/karafka/web/ui/models/metrics/charts/aggregated.rb +50 -0
- data/lib/karafka/web/ui/models/metrics/charts/topics.rb +109 -0
- data/lib/karafka/web/ui/models/metrics/topics.rb +101 -0
- data/lib/karafka/web/ui/models/partition.rb +27 -0
- data/lib/karafka/web/ui/models/process.rb +12 -1
- data/lib/karafka/web/ui/models/status.rb +110 -22
- data/lib/karafka/web/ui/models/visibility_filter.rb +33 -0
- data/lib/karafka/web/ui/pro/app.rb +87 -19
- data/lib/karafka/web/ui/pro/controllers/cluster.rb +11 -0
- data/lib/karafka/web/ui/pro/controllers/consumers.rb +13 -7
- data/lib/karafka/web/ui/pro/controllers/dashboard.rb +54 -0
- data/lib/karafka/web/ui/pro/controllers/dlq.rb +1 -2
- data/lib/karafka/web/ui/pro/controllers/errors.rb +46 -10
- data/lib/karafka/web/ui/pro/controllers/explorer.rb +145 -15
- data/lib/karafka/web/ui/pro/controllers/health.rb +10 -2
- data/lib/karafka/web/ui/pro/controllers/messages.rb +62 -0
- data/lib/karafka/web/ui/pro/controllers/routing.rb +44 -0
- data/lib/karafka/web/ui/pro/views/consumers/_breadcrumbs.erb +7 -1
- data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +1 -1
- data/lib/karafka/web/ui/pro/views/consumers/_counters.erb +7 -5
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_job.erb +3 -3
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_metrics.erb +5 -4
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +13 -4
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_subscription_group.erb +3 -2
- data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +7 -0
- data/lib/karafka/web/ui/pro/views/consumers/details.erb +21 -0
- data/lib/karafka/web/ui/pro/views/consumers/index.erb +4 -2
- data/lib/karafka/web/ui/pro/views/dashboard/_ranges_selector.erb +39 -0
- data/lib/karafka/web/ui/pro/views/dashboard/index.erb +82 -0
- data/lib/karafka/web/ui/pro/views/dlq/_topic.erb +1 -1
- data/lib/karafka/web/ui/pro/views/errors/_breadcrumbs.erb +8 -6
- data/lib/karafka/web/ui/pro/views/errors/_error.erb +2 -2
- data/lib/karafka/web/ui/pro/views/errors/_partition_option.erb +1 -1
- data/lib/karafka/web/ui/pro/views/errors/_table.erb +21 -0
- data/lib/karafka/web/ui/pro/views/errors/_title_with_select.erb +31 -0
- data/lib/karafka/web/ui/pro/views/errors/index.erb +9 -56
- data/lib/karafka/web/ui/pro/views/errors/partition.erb +17 -0
- data/lib/karafka/web/ui/pro/views/errors/show.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/_breadcrumbs.erb +6 -4
- data/lib/karafka/web/ui/pro/views/explorer/_filtered.erb +16 -0
- data/lib/karafka/web/ui/pro/views/explorer/_message.erb +14 -4
- data/lib/karafka/web/ui/pro/views/explorer/_no_topics.erb +7 -0
- data/lib/karafka/web/ui/pro/views/explorer/_partition_option.erb +3 -3
- data/lib/karafka/web/ui/pro/views/explorer/_topic.erb +1 -1
- data/lib/karafka/web/ui/pro/views/explorer/index.erb +12 -8
- data/lib/karafka/web/ui/pro/views/explorer/messages/_headers.erb +15 -0
- data/lib/karafka/web/ui/pro/views/explorer/messages/_key.erb +12 -0
- data/lib/karafka/web/ui/pro/views/explorer/partition/_details.erb +35 -0
- data/lib/karafka/web/ui/pro/views/explorer/partition/_messages.erb +1 -0
- data/lib/karafka/web/ui/pro/views/explorer/partition.erb +6 -4
- data/lib/karafka/web/ui/pro/views/explorer/show.erb +48 -5
- data/lib/karafka/web/ui/pro/views/explorer/topic/_details.erb +23 -0
- data/lib/karafka/web/ui/pro/views/explorer/topic/_empty.erb +3 -0
- data/lib/karafka/web/ui/pro/views/explorer/topic/_limited.erb +4 -0
- data/lib/karafka/web/ui/pro/views/explorer/topic.erb +51 -0
- data/lib/karafka/web/ui/pro/views/health/_breadcrumbs.erb +16 -0
- data/lib/karafka/web/ui/pro/views/health/_no_data.erb +9 -0
- data/lib/karafka/web/ui/pro/views/health/_partition.erb +17 -15
- data/lib/karafka/web/ui/pro/views/health/_partition_offset.erb +40 -0
- data/lib/karafka/web/ui/pro/views/health/_tabs.erb +27 -0
- data/lib/karafka/web/ui/pro/views/health/offsets.erb +71 -0
- data/lib/karafka/web/ui/pro/views/health/overview.erb +68 -0
- data/lib/karafka/web/ui/pro/views/jobs/_job.erb +6 -3
- data/lib/karafka/web/ui/pro/views/jobs/index.erb +4 -1
- data/lib/karafka/web/ui/pro/views/routing/_consumer_group.erb +37 -0
- data/lib/karafka/web/ui/pro/views/routing/_detail.erb +25 -0
- data/lib/karafka/web/ui/pro/views/routing/_topic.erb +23 -0
- data/lib/karafka/web/ui/pro/views/routing/index.erb +10 -0
- data/lib/karafka/web/ui/pro/views/routing/show.erb +26 -0
- data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +7 -10
- data/lib/karafka/web/ui/public/images/logo-gray.svg +28 -0
- data/lib/karafka/web/ui/public/javascripts/application.js +30 -0
- data/lib/karafka/web/ui/public/javascripts/chart.min.js +14 -0
- data/lib/karafka/web/ui/public/javascripts/charts.js +330 -0
- data/lib/karafka/web/ui/public/javascripts/datepicker.js +6 -0
- data/lib/karafka/web/ui/public/javascripts/live_poll.js +39 -12
- data/lib/karafka/web/ui/public/javascripts/offset_datetime.js +74 -0
- data/lib/karafka/web/ui/public/javascripts/tabs.js +59 -0
- data/lib/karafka/web/ui/public/stylesheets/application.css +11 -0
- data/lib/karafka/web/ui/public/stylesheets/datepicker.min.css +12 -0
- data/lib/karafka/web/ui/views/cluster/_no_partitions.erb +3 -0
- data/lib/karafka/web/ui/views/cluster/_partition.erb +20 -22
- data/lib/karafka/web/ui/views/cluster/index.erb +6 -1
- data/lib/karafka/web/ui/views/consumers/_consumer.erb +1 -1
- data/lib/karafka/web/ui/views/consumers/_counters.erb +6 -4
- data/lib/karafka/web/ui/views/consumers/_summary.erb +3 -3
- data/lib/karafka/web/ui/views/consumers/index.erb +3 -1
- data/lib/karafka/web/ui/views/dashboard/_feature_pro.erb +3 -0
- data/lib/karafka/web/ui/views/dashboard/_not_enough_data.erb +15 -0
- data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +23 -0
- data/lib/karafka/web/ui/views/dashboard/index.erb +95 -0
- data/lib/karafka/web/ui/views/errors/_detail.erb +12 -0
- data/lib/karafka/web/ui/views/errors/_error.erb +2 -2
- data/lib/karafka/web/ui/views/errors/show.erb +1 -1
- data/lib/karafka/web/ui/views/jobs/index.erb +3 -1
- data/lib/karafka/web/ui/views/layout.erb +10 -3
- data/lib/karafka/web/ui/views/routing/_consumer_group.erb +8 -6
- data/lib/karafka/web/ui/views/routing/_detail.erb +2 -2
- data/lib/karafka/web/ui/views/routing/_topic.erb +1 -1
- data/lib/karafka/web/ui/views/routing/show.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_brand.erb +2 -2
- data/lib/karafka/web/ui/views/shared/_chart.erb +14 -0
- data/lib/karafka/web/ui/views/shared/_content.erb +2 -2
- data/lib/karafka/web/ui/views/shared/_feature_pro.erb +1 -1
- data/lib/karafka/web/ui/views/shared/_flashes.erb +9 -0
- data/lib/karafka/web/ui/views/shared/_footer.erb +22 -0
- data/lib/karafka/web/ui/views/shared/_header.erb +15 -9
- data/lib/karafka/web/ui/views/shared/_live_poll.erb +7 -0
- data/lib/karafka/web/ui/views/shared/_navigation.erb +5 -8
- data/lib/karafka/web/ui/views/shared/_no_paginated_data.erb +9 -0
- data/lib/karafka/web/ui/views/shared/_pagination.erb +17 -13
- data/lib/karafka/web/ui/views/shared/_tab_nav.erb +7 -0
- data/lib/karafka/web/ui/views/shared/exceptions/not_found.erb +34 -32
- data/lib/karafka/web/ui/views/shared/exceptions/pro_only.erb +45 -43
- data/lib/karafka/web/ui/views/status/failures/_consumers_reports_schema_state.erb +15 -0
- data/lib/karafka/web/ui/views/status/failures/_enabled.erb +8 -0
- data/lib/karafka/web/ui/views/status/failures/_initial_consumers_metrics.erb +11 -0
- data/lib/karafka/web/ui/views/status/failures/{_initial_state.erb → _initial_consumers_state.erb} +3 -3
- data/lib/karafka/web/ui/views/status/failures/_partitions.erb +14 -6
- data/lib/karafka/web/ui/views/status/info/_components.erb +21 -1
- data/lib/karafka/web/ui/views/status/show.erb +62 -5
- data/lib/karafka/web/ui/views/status/successes/_enabled.erb +1 -0
- data/lib/karafka/web/ui/views/status/warnings/_replication.erb +19 -0
- data/lib/karafka/web/version.rb +1 -1
- data/lib/karafka/web.rb +11 -0
- data.tar.gz.sig +0 -0
- metadata +124 -39
- metadata.gz.sig +0 -0
- data/lib/karafka/web/processing/consumers/aggregator.rb +0 -130
- data/lib/karafka/web/tracking/contracts/base.rb +0 -34
- data/lib/karafka/web/ui/lib/paginate_array.rb +0 -38
- data/lib/karafka/web/ui/pro/views/explorer/_encryption_enabled.erb +0 -18
- data/lib/karafka/web/ui/pro/views/explorer/partition/_watermark_offsets.erb +0 -10
- data/lib/karafka/web/ui/pro/views/health/index.erb +0 -60
- /data/lib/karafka/web/ui/pro/views/explorer/{_detail.erb → messages/_detail.erb} +0 -0
@@ -9,7 +9,7 @@ module Karafka
|
|
9
9
|
#
|
10
10
|
# Any outgoing reporting needs to match this format for it to work with the statuses
|
11
11
|
# consumer.
|
12
|
-
class Report <
|
12
|
+
class Report < Web::Contracts::Base
|
13
13
|
configure
|
14
14
|
|
15
15
|
required(:schema_version) { |val| val.is_a?(String) && !val.empty? }
|
@@ -21,12 +21,14 @@ module Karafka
|
|
21
21
|
nested(:process) do
|
22
22
|
required(:started_at) { |val| val.is_a?(Numeric) && val.positive? }
|
23
23
|
required(:name) { |val| val.is_a?(String) && val.count(':') >= 2 }
|
24
|
+
required(:cpus) { |val| val.is_a?(Integer) && val >= 1 }
|
24
25
|
required(:memory_usage) { |val| val.is_a?(Integer) && val >= 0 }
|
25
26
|
required(:memory_total_usage) { |val| val.is_a?(Integer) && val >= 0 }
|
26
27
|
required(:memory_size) { |val| val.is_a?(Integer) && val >= 0 }
|
27
28
|
required(:status) { |val| ::Karafka::Status::STATES.key?(val.to_s.to_sym) }
|
29
|
+
required(:threads) { |val| val.is_a?(Integer) && val >= 0 }
|
28
30
|
required(:listeners) { |val| val.is_a?(Integer) && val >= 0 }
|
29
|
-
required(:
|
31
|
+
required(:workers) { |val| val.is_a?(Integer) && val.positive? }
|
30
32
|
required(:tags) { |val| val.is_a?(Karafka::Core::Taggable::Tags) }
|
31
33
|
|
32
34
|
required(:cpu_usage) do |val|
|
@@ -53,11 +55,11 @@ module Karafka
|
|
53
55
|
required(:utilization) { |val| val.is_a?(Numeric) && val >= 0 }
|
54
56
|
|
55
57
|
nested(:total) do
|
56
|
-
required(:batches) { |val| val.is_a?(
|
57
|
-
required(:messages) { |val| val.is_a?(
|
58
|
-
required(:errors) { |val| val.is_a?(
|
59
|
-
required(:retries) { |val| val.is_a?(
|
60
|
-
required(:dead) { |val| val.is_a?(
|
58
|
+
required(:batches) { |val| val.is_a?(Integer) && val >= 0 }
|
59
|
+
required(:messages) { |val| val.is_a?(Integer) && val >= 0 }
|
60
|
+
required(:errors) { |val| val.is_a?(Integer) && val >= 0 }
|
61
|
+
required(:retries) { |val| val.is_a?(Integer) && val >= 0 }
|
62
|
+
required(:dead) { |val| val.is_a?(Integer) && val >= 0 }
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
@@ -73,7 +75,7 @@ module Karafka
|
|
73
75
|
cg_contract = ConsumerGroup.new
|
74
76
|
|
75
77
|
# Consumer group id (key) is irrelevant because it is also in the details
|
76
|
-
data.fetch(:consumer_groups).
|
78
|
+
data.fetch(:consumer_groups).each_value do |details|
|
77
79
|
cg_contract.validate!(details)
|
78
80
|
end
|
79
81
|
|
@@ -7,7 +7,7 @@ module Karafka
|
|
7
7
|
module Contracts
|
8
8
|
# Expected data for each subscription group
|
9
9
|
# It's mostly about topics details
|
10
|
-
class SubscriptionGroup <
|
10
|
+
class SubscriptionGroup < Web::Contracts::Base
|
11
11
|
configure
|
12
12
|
|
13
13
|
required(:id) { |val| val.is_a?(String) && !val.empty? }
|
@@ -19,7 +19,7 @@ module Karafka
|
|
19
19
|
|
20
20
|
topic_contract = Topic.new
|
21
21
|
|
22
|
-
data.fetch(:topics).
|
22
|
+
data.fetch(:topics).each_value do |details|
|
23
23
|
topic_contract.validate!(details)
|
24
24
|
end
|
25
25
|
|
@@ -6,7 +6,7 @@ module Karafka
|
|
6
6
|
module Consumers
|
7
7
|
module Contracts
|
8
8
|
# Expected topic information that needs to go out
|
9
|
-
class Topic <
|
9
|
+
class Topic < Web::Contracts::Base
|
10
10
|
configure
|
11
11
|
|
12
12
|
required(:name) { |val| val.is_a?(String) && !val.empty? }
|
@@ -17,7 +17,7 @@ module Karafka
|
|
17
17
|
|
18
18
|
partition_contract = Partition.new
|
19
19
|
|
20
|
-
data.fetch(:partitions).
|
20
|
+
data.fetch(:partitions).each_value do |details|
|
21
21
|
partition_contract.validate!(details)
|
22
22
|
end
|
23
23
|
|
@@ -21,7 +21,7 @@ module Karafka
|
|
21
21
|
# @param event [Karafka::Core::Monitoring::Event]
|
22
22
|
def on_consumer_consume(event)
|
23
23
|
consumer = event.payload[:caller]
|
24
|
-
messages_count = consumer.messages.
|
24
|
+
messages_count = consumer.messages.size
|
25
25
|
jid = job_id(consumer, 'consume')
|
26
26
|
job_details = job_details(consumer, 'consume')
|
27
27
|
|
@@ -73,7 +73,7 @@ module Karafka
|
|
73
73
|
consumer = event.payload[:caller]
|
74
74
|
topic = consumer.topic
|
75
75
|
consumer_group_id = topic.consumer_group.id
|
76
|
-
messages_count = consumer.messages.
|
76
|
+
messages_count = consumer.messages.size
|
77
77
|
time = event[:time]
|
78
78
|
jid = job_id(consumer, 'consume')
|
79
79
|
|
@@ -160,6 +160,10 @@ module Karafka
|
|
160
160
|
processing_lag: consumer.messages.metadata.processing_lag,
|
161
161
|
consumption_lag: consumer.messages.metadata.consumption_lag,
|
162
162
|
committed_offset: consumer.coordinator.seek_offset - 1,
|
163
|
+
# In theory this is redundant because we have first and last offset, but it is
|
164
|
+
# needed because VPs do not have linear count. For VPs first and last offset
|
165
|
+
# will be further away than the total messages count for a particular VP
|
166
|
+
messages: consumer.messages.size,
|
163
167
|
consumer: consumer.class.to_s,
|
164
168
|
consumer_group: consumer.topic.consumer_group.id,
|
165
169
|
type: type,
|
@@ -104,11 +104,25 @@ module Karafka
|
|
104
104
|
# @return [Hash] extracted partition metrics
|
105
105
|
def extract_partition_metrics(pt_stats)
|
106
106
|
metrics = pt_stats.slice(
|
107
|
+
'consumer_lag',
|
108
|
+
'consumer_lag_d',
|
107
109
|
'consumer_lag_stored',
|
108
110
|
'consumer_lag_stored_d',
|
109
111
|
'committed_offset',
|
112
|
+
# Can be useful to track the frequency of flushes when there is progress
|
113
|
+
'committed_offset_fd',
|
110
114
|
'stored_offset',
|
111
|
-
|
115
|
+
# Can be useful to track the frequency of flushes when there is progress
|
116
|
+
'stored_offset_fd',
|
117
|
+
'fetch_state',
|
118
|
+
'hi_offset',
|
119
|
+
'hi_offset_fd',
|
120
|
+
'lo_offset',
|
121
|
+
'eof_offset',
|
122
|
+
'ls_offset',
|
123
|
+
# Two below can be useful for detection of hanging transactions
|
124
|
+
'ls_offset_d',
|
125
|
+
'ls_offset_fd'
|
112
126
|
)
|
113
127
|
|
114
128
|
# Rename as we do not need `consumer_` prefix
|
@@ -32,6 +32,14 @@ module Karafka
|
|
32
32
|
# @param forced [Boolean] should we report bypassing the time frequency or should we
|
33
33
|
# report only in case we would not send the report for long enough time.
|
34
34
|
def report(forced: false)
|
35
|
+
# Do not even mutex if not needed
|
36
|
+
return unless report?(forced)
|
37
|
+
|
38
|
+
# We run this sampling before the mutex so sampling does not stop things in case
|
39
|
+
# other threads would need this mutex. This can take up to 25ms and we do not want to
|
40
|
+
# block during this time
|
41
|
+
sampler.sample
|
42
|
+
|
35
43
|
MUTEX.synchronize do
|
36
44
|
# Start background thread only when needed
|
37
45
|
# This prevents us from starting it too early or for non-consumer processes where
|
@@ -52,9 +60,10 @@ module Karafka
|
|
52
60
|
messages = [
|
53
61
|
{
|
54
62
|
topic: ::Karafka::Web.config.topics.consumers.reports,
|
55
|
-
payload: report.to_json,
|
63
|
+
payload: Zlib::Deflate.deflate(report.to_json),
|
56
64
|
key: process_name,
|
57
|
-
partition: 0
|
65
|
+
partition: 0,
|
66
|
+
headers: { 'zlib' => 'true' }
|
58
67
|
}
|
59
68
|
]
|
60
69
|
|
@@ -64,14 +73,13 @@ module Karafka
|
|
64
73
|
|
65
74
|
{
|
66
75
|
topic: Karafka::Web.config.topics.errors,
|
67
|
-
payload: error.to_json,
|
76
|
+
payload: Zlib::Deflate.deflate(error.to_json),
|
68
77
|
# Always dispatch errors from the same process to the same partition
|
69
|
-
key: process_name
|
78
|
+
key: process_name,
|
79
|
+
headers: { 'zlib' => 'true' }
|
70
80
|
}
|
71
81
|
end
|
72
82
|
|
73
|
-
return if messages.empty?
|
74
|
-
|
75
83
|
produce(messages)
|
76
84
|
|
77
85
|
# Clear the sampler so it tracks new state changes without previous once impacting
|
@@ -14,7 +14,7 @@ module Karafka
|
|
14
14
|
# Current schema version
|
15
15
|
# This can be used in the future for detecting incompatible changes and writing
|
16
16
|
# migrations
|
17
|
-
SCHEMA_VERSION = '1.2.
|
17
|
+
SCHEMA_VERSION = '1.2.3'
|
18
18
|
|
19
19
|
# 60 seconds window for time tracked window-based metrics
|
20
20
|
TIMES_TTL = 60
|
@@ -22,18 +22,26 @@ module Karafka
|
|
22
22
|
# Times ttl in ms
|
23
23
|
TIMES_TTL_MS = TIMES_TTL * 1_000
|
24
24
|
|
25
|
-
|
25
|
+
# Counters that count events occurrences during the given window
|
26
|
+
COUNTERS_BASE = {
|
27
|
+
# Number of processed batches
|
28
|
+
batches: 0,
|
29
|
+
# Number of processed messages
|
30
|
+
messages: 0,
|
31
|
+
# Number of errors that occurred
|
32
|
+
errors: 0,
|
33
|
+
# Number of retries that occurred
|
34
|
+
retries: 0,
|
35
|
+
# Number of messages considered dead
|
36
|
+
dead: 0
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
private_constant :TIMES_TTL, :TIMES_TTL_MS, :COUNTERS_BASE
|
26
40
|
|
27
41
|
def initialize
|
28
42
|
super
|
29
43
|
|
30
|
-
@counters =
|
31
|
-
batches: 0,
|
32
|
-
messages: 0,
|
33
|
-
errors: 0,
|
34
|
-
retries: 0,
|
35
|
-
dead: 0
|
36
|
-
}
|
44
|
+
@counters = COUNTERS_BASE.dup
|
37
45
|
@times = TtlHash.new(TIMES_TTL_MS)
|
38
46
|
@consumer_groups = {}
|
39
47
|
@errors = []
|
@@ -41,6 +49,9 @@ module Karafka
|
|
41
49
|
@pauses = Set.new
|
42
50
|
@jobs = {}
|
43
51
|
@shell = MemoizedShell.new
|
52
|
+
@memory_total_usage = 0
|
53
|
+
@memory_usage = 0
|
54
|
+
@cpu_usage = [-1, -1, -1]
|
44
55
|
end
|
45
56
|
|
46
57
|
# We cannot report and track the same time, that is why we use mutex here. To make sure
|
@@ -63,12 +74,13 @@ module Karafka
|
|
63
74
|
name: process_name,
|
64
75
|
status: ::Karafka::App.config.internal.status.to_s,
|
65
76
|
listeners: listeners,
|
66
|
-
|
67
|
-
memory_usage: memory_usage,
|
68
|
-
memory_total_usage: memory_total_usage,
|
77
|
+
workers: workers,
|
78
|
+
memory_usage: @memory_usage,
|
79
|
+
memory_total_usage: @memory_total_usage,
|
69
80
|
memory_size: memory_size,
|
70
|
-
|
71
|
-
|
81
|
+
cpus: cpus,
|
82
|
+
threads: threads,
|
83
|
+
cpu_usage: @cpu_usage,
|
72
84
|
tags: Karafka::Process.tags
|
73
85
|
},
|
74
86
|
|
@@ -101,6 +113,17 @@ module Karafka
|
|
101
113
|
@errors.clear
|
102
114
|
end
|
103
115
|
|
116
|
+
# @note This should run before any mutex, so other threads can continue as those
|
117
|
+
# operations may invoke shell commands
|
118
|
+
def sample
|
119
|
+
memory_threads_ps
|
120
|
+
|
121
|
+
@memory_usage = memory_usage
|
122
|
+
@memory_total_usage = memory_total_usage
|
123
|
+
@cpu_usage = cpu_usage
|
124
|
+
@threads = threads
|
125
|
+
end
|
126
|
+
|
104
127
|
private
|
105
128
|
|
106
129
|
# @return [Numeric] % utilization of all the threads. 100% means all the threads are
|
@@ -115,7 +138,7 @@ module Karafka
|
|
115
138
|
|
116
139
|
# We divide by 1_000 to convert from milliseconds
|
117
140
|
# We multiply by 100 to have it in % scale
|
118
|
-
times[:total].sum / 1_000 /
|
141
|
+
times[:total].sum / 1_000 / workers / timefactor * 100
|
119
142
|
end
|
120
143
|
|
121
144
|
# @return [Integer] number of listeners
|
@@ -152,20 +175,16 @@ module Karafka
|
|
152
175
|
# @return [Hash] job queue statistics
|
153
176
|
def jobs_queue_statistics
|
154
177
|
# We return empty stats in case jobs queue is not yet initialized
|
178
|
+
# busy - represents number of jobs that are being executed currently
|
179
|
+
# enqueued - represents number of jobs that are enqueued to be processed
|
155
180
|
Karafka::Server.jobs_queue&.statistics || { busy: 0, enqueued: 0 }
|
156
181
|
end
|
157
182
|
|
158
183
|
# Total memory used in the OS
|
159
184
|
def memory_total_usage
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
.call('ps -A -o rss=')
|
164
|
-
.split("\n")
|
165
|
-
.inject { |a, e| a.to_i + e.strip.to_i }
|
166
|
-
else
|
167
|
-
0
|
168
|
-
end
|
185
|
+
return 0 unless @memory_threads_ps
|
186
|
+
|
187
|
+
@memory_threads_ps.map(&:first).sum
|
169
188
|
end
|
170
189
|
|
171
190
|
# @return [Integer] total amount of memory
|
@@ -173,19 +192,19 @@ module Karafka
|
|
173
192
|
@memory_size ||= case RUBY_PLATFORM
|
174
193
|
when /linux/
|
175
194
|
@shell
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
195
|
+
.call('grep MemTotal /proc/meminfo')
|
196
|
+
.match(/(\d+)/)
|
197
|
+
.to_s
|
198
|
+
.to_i
|
180
199
|
when /darwin|bsd/
|
181
200
|
@shell
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
201
|
+
.call('sysctl -a')
|
202
|
+
.split("\n")
|
203
|
+
.find { |line| line.start_with?('hw.memsize:') }
|
204
|
+
.to_s
|
205
|
+
.split(' ')
|
206
|
+
.last
|
207
|
+
.to_i
|
189
208
|
else
|
190
209
|
0
|
191
210
|
end
|
@@ -206,14 +225,36 @@ module Karafka
|
|
206
225
|
end
|
207
226
|
end
|
208
227
|
|
228
|
+
# @return [Integer] number of process threads.
|
229
|
+
# @note This returns total number of threads from the OS perspective including native
|
230
|
+
# extensions threads, etc.
|
231
|
+
def threads
|
232
|
+
return 0 unless @memory_threads_ps
|
233
|
+
|
234
|
+
@memory_threads_ps.find { |row| row.last == ::Process.pid }[1]
|
235
|
+
end
|
236
|
+
|
209
237
|
# @return [Integer] CPU count
|
210
|
-
def
|
211
|
-
@
|
238
|
+
def cpus
|
239
|
+
@cpus ||= Etc.nprocessors
|
212
240
|
end
|
213
241
|
|
214
242
|
# @return [Integer] number of threads that process work
|
215
|
-
def
|
216
|
-
@
|
243
|
+
def workers
|
244
|
+
@workers ||= Karafka::App.config.concurrency
|
245
|
+
end
|
246
|
+
|
247
|
+
# Loads our ps results into memory so we can extract from them whatever we need
|
248
|
+
def memory_threads_ps
|
249
|
+
@memory_threads_ps = case RUBY_PLATFORM
|
250
|
+
when /darwin|bsd|linux/
|
251
|
+
@shell
|
252
|
+
.call('ps -A -o rss=,thcount,pid')
|
253
|
+
.split("\n")
|
254
|
+
.map { |row| row.strip.split(' ').map(&:to_i) }
|
255
|
+
else
|
256
|
+
@memory_threads_ps = false
|
257
|
+
end
|
217
258
|
end
|
218
259
|
end
|
219
260
|
end
|
@@ -3,11 +3,12 @@
|
|
3
3
|
module Karafka
|
4
4
|
module Web
|
5
5
|
module Tracking
|
6
|
+
# Namespace for all tracking related contracts
|
6
7
|
module Contracts
|
7
8
|
# Contract for error reporting
|
8
9
|
# Since producers and consumers report their errors to the same topic, we need to have
|
9
10
|
# a unified contract for both
|
10
|
-
class Error < Base
|
11
|
+
class Error < Web::Contracts::Base
|
11
12
|
configure
|
12
13
|
|
13
14
|
required(:schema_version) { |val| val.is_a?(String) }
|
data/lib/karafka/web/ui/app.rb
CHANGED
@@ -12,9 +12,19 @@ module Karafka
|
|
12
12
|
instance_exec(&CONTEXT_DETAILS)
|
13
13
|
|
14
14
|
route do |r|
|
15
|
-
r.root { r.redirect root_path('
|
15
|
+
r.root { r.redirect root_path('dashboard') }
|
16
16
|
|
17
|
-
|
17
|
+
# Serve current version specific assets to prevent users from fetching old assets
|
18
|
+
# after upgrade
|
19
|
+
r.on(:assets, Karafka::Web::VERSION) do
|
20
|
+
r.public
|
21
|
+
end
|
22
|
+
|
23
|
+
r.get 'dashboard' do
|
24
|
+
@breadcrumbs = false
|
25
|
+
controller = Controllers::Dashboard.new(params)
|
26
|
+
controller.index
|
27
|
+
end
|
18
28
|
|
19
29
|
r.on 'consumers' do
|
20
30
|
r.get String, 'subscriptions' do |_process_id|
|
@@ -24,7 +34,7 @@ module Karafka
|
|
24
34
|
r.get do
|
25
35
|
@breadcrumbs = false
|
26
36
|
controller = Controllers::Consumers.new(params)
|
27
|
-
|
37
|
+
controller.index
|
28
38
|
end
|
29
39
|
end
|
30
40
|
|
@@ -40,41 +50,41 @@ module Karafka
|
|
40
50
|
|
41
51
|
r.get 'jobs' do
|
42
52
|
controller = Controllers::Jobs.new(params)
|
43
|
-
|
53
|
+
controller.index
|
44
54
|
end
|
45
55
|
|
46
56
|
r.on 'routing' do
|
47
57
|
controller = Controllers::Routing.new(params)
|
48
58
|
|
49
59
|
r.get String do |topic_id|
|
50
|
-
|
60
|
+
controller.show(topic_id)
|
51
61
|
end
|
52
62
|
|
53
63
|
r.get do
|
54
|
-
|
64
|
+
controller.index
|
55
65
|
end
|
56
66
|
end
|
57
67
|
|
58
68
|
r.get 'cluster' do
|
59
69
|
controller = Controllers::Cluster.new(params)
|
60
|
-
|
70
|
+
controller.index
|
61
71
|
end
|
62
72
|
|
63
73
|
r.on 'errors' do
|
64
74
|
controller = Controllers::Errors.new(params)
|
65
75
|
|
66
76
|
r.get Integer do |offset|
|
67
|
-
|
77
|
+
controller.show(offset)
|
68
78
|
end
|
69
79
|
|
70
80
|
r.get do
|
71
|
-
|
81
|
+
controller.index
|
72
82
|
end
|
73
83
|
end
|
74
84
|
|
75
85
|
r.get 'status' do
|
76
86
|
controller = Controllers::Status.new(params)
|
77
|
-
|
87
|
+
controller.show
|
78
88
|
end
|
79
89
|
end
|
80
90
|
end
|
data/lib/karafka/web/ui/base.rb
CHANGED
@@ -5,24 +5,34 @@ module Karafka
|
|
5
5
|
module Ui
|
6
6
|
# Base Roda application
|
7
7
|
class Base < Roda
|
8
|
+
include Helpers::PathsHelper
|
8
9
|
include Helpers::ApplicationHelper
|
9
10
|
|
10
11
|
# Details that need to be evaluated in the context of OSS or Pro web UI.
|
11
12
|
# If those would be evaluated in the base, they would not be initialized as expected
|
12
13
|
CONTEXT_DETAILS = lambda do
|
13
14
|
plugin(
|
14
|
-
:
|
15
|
-
|
16
|
-
|
15
|
+
:public,
|
16
|
+
root: Karafka::Web.gem_root.join('lib/karafka/web/ui/public'),
|
17
|
+
# Cache all static files for the end user for as long as possible
|
18
|
+
# We can do it because we ship per version assets so they invalidate with gem bumps
|
19
|
+
headers: { 'Cache-Control' => 'max-age=604800' }
|
17
20
|
)
|
18
21
|
plugin :render_each
|
19
22
|
plugin :partials
|
23
|
+
# The secret here will be reconfigured after Web UI configuration setup
|
24
|
+
# This is why we assign here a random value as it will have to be changed by the end
|
25
|
+
# user to make the Web UI work.
|
26
|
+
plugin :sessions, key: '_karafka_session', secret: SecureRandom.hex(64)
|
27
|
+
plugin :route_csrf
|
20
28
|
end
|
21
29
|
|
22
30
|
plugin :render, escape: true, engine: 'erb'
|
23
31
|
plugin :run_append_slash
|
24
32
|
plugin :error_handler
|
25
33
|
plugin :not_found
|
34
|
+
plugin :hooks
|
35
|
+
plugin :flash
|
26
36
|
plugin :path
|
27
37
|
|
28
38
|
# Based on
|
@@ -43,15 +53,30 @@ module Karafka
|
|
43
53
|
csp.base_uri "'self'"
|
44
54
|
end
|
45
55
|
|
56
|
+
plugin :custom_block_results
|
57
|
+
|
58
|
+
handle_block_result Controllers::Responses::Data do |result|
|
59
|
+
render_response(result)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Redirect either to referer back or to the desired path
|
63
|
+
handle_block_result Controllers::Responses::Redirect do |result|
|
64
|
+
# Map redirect flashes (if any) to Roda flash messages
|
65
|
+
result.flashes.each { |key, value| flash[key] = value }
|
66
|
+
|
67
|
+
response.redirect result.back? ? request.referer : root_path(result.path)
|
68
|
+
end
|
69
|
+
|
46
70
|
# Display appropriate error specific to a given error type
|
47
71
|
plugin :error_handler, classes: [
|
48
|
-
::Karafka::Web::Errors::Ui::NotFoundError,
|
49
72
|
::Rdkafka::RdkafkaError,
|
73
|
+
Errors::Ui::NotFoundError,
|
50
74
|
Errors::Ui::ProOnlyError
|
51
75
|
] do |e|
|
52
76
|
@error = true
|
53
77
|
|
54
78
|
if e.is_a?(Errors::Ui::ProOnlyError)
|
79
|
+
response.status = 402
|
55
80
|
view 'shared/exceptions/pro_only'
|
56
81
|
else
|
57
82
|
response.status = 404
|
@@ -65,11 +90,36 @@ module Karafka
|
|
65
90
|
view 'shared/exceptions/not_found'
|
66
91
|
end
|
67
92
|
|
93
|
+
before do
|
94
|
+
check_csrf!
|
95
|
+
end
|
96
|
+
|
97
|
+
plugin :class_matchers
|
98
|
+
|
99
|
+
# Time matcher with optional hours, minutes and seconds
|
100
|
+
TIME_MATCHER = %r{(\d{4}-\d{2}-\d{2}/?(\d{2})?(:\d{2})?(:\d{2})?)}
|
101
|
+
|
102
|
+
private_constant :TIME_MATCHER
|
103
|
+
|
104
|
+
# Match a date-time. Useful for time-related routes
|
105
|
+
# @note In case the date-time is invalid, raise and render 404
|
106
|
+
# @note The time component is optional as `Time#parse` will fallback to lowest time
|
107
|
+
# available, so we can build only date based lookups
|
108
|
+
class_matcher(Time, TIME_MATCHER) do |datetime|
|
109
|
+
[Time.parse(datetime)]
|
110
|
+
rescue ArgumentError
|
111
|
+
raise Errors::Ui::NotFoundError
|
112
|
+
end
|
113
|
+
|
68
114
|
# Allows us to build current path with additional params
|
69
115
|
# @param query_data [Hash] query params we want to add to the current path
|
70
116
|
path :current do |query_data = {}|
|
71
|
-
q = query_data
|
72
|
-
|
117
|
+
q = query_data
|
118
|
+
.select { |_, v| v }
|
119
|
+
.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }
|
120
|
+
.join('&')
|
121
|
+
|
122
|
+
[request.path, q].compact.delete_if(&:empty?).join('?')
|
73
123
|
end
|
74
124
|
|
75
125
|
# Sets appropriate template variables based on the response object and renders the
|
@@ -12,6 +12,8 @@ module Karafka
|
|
12
12
|
@params = params
|
13
13
|
end
|
14
14
|
|
15
|
+
private
|
16
|
+
|
15
17
|
# Builds the respond data object with assigned attributes based on instance variables.
|
16
18
|
#
|
17
19
|
# @return [Responses::Data] data that should be used to render appropriate view
|
@@ -33,6 +35,32 @@ module Karafka
|
|
33
35
|
attributes
|
34
36
|
)
|
35
37
|
end
|
38
|
+
|
39
|
+
# Builds a redirect data object with assigned flashes (if any)
|
40
|
+
# @param path [String, Symbol] relative (without root path) path where we want to be
|
41
|
+
# redirected or `:back` to use referer back
|
42
|
+
# @param flashes [Hash] hash where key is the flash type and value is the message
|
43
|
+
# @return [Responses::Redirect] redirect result
|
44
|
+
def redirect(path = :back, flashes = {})
|
45
|
+
Responses::Redirect.new(path, flashes)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Initializes the expected pagination engine and assigns expected arguments
|
49
|
+
# @param args Any arguments accepted by the selected pagination engine
|
50
|
+
def paginate(*args)
|
51
|
+
engine = case args.count
|
52
|
+
when 2
|
53
|
+
Ui::Lib::Paginations::PageBased
|
54
|
+
when 3
|
55
|
+
Ui::Lib::Paginations::WatermarkOffsetsBased
|
56
|
+
when 4
|
57
|
+
Ui::Lib::Paginations::OffsetBased
|
58
|
+
else
|
59
|
+
raise ::Karafka::Errors::UnsupportedCaseError, args.count
|
60
|
+
end
|
61
|
+
|
62
|
+
@pagination = engine.new(*args)
|
63
|
+
end
|
36
64
|
end
|
37
65
|
end
|
38
66
|
end
|
@@ -6,7 +6,7 @@ module Karafka
|
|
6
6
|
module Controllers
|
7
7
|
# Pro message reporting info controller
|
8
8
|
class BecomePro < Base
|
9
|
-
# Display a message, that a
|
9
|
+
# Display a message, that a given feature is available only in Pro
|
10
10
|
def show
|
11
11
|
respond
|
12
12
|
end
|