karafka-web 0.7.9 → 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 +21 -6
- data/.ruby-version +1 -1
- data/CHANGELOG.md +66 -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 +15 -7
- 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 -10
- 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,32 @@
|
|
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 SplitListenersIntoActiveAndPausedInStates < Base
|
10
|
+
self.versions_until = '1.2.2'
|
11
|
+
self.type = :consumers_state
|
12
|
+
|
13
|
+
# @param state [Hash]
|
14
|
+
def migrate(state)
|
15
|
+
listeners = if state[:stats].key?(:listeners)
|
16
|
+
state[:stats][:listeners].to_i
|
17
|
+
elsif state[:stats].key?(:listeners_count)
|
18
|
+
state[:stats][:listeners_count].to_i
|
19
|
+
else
|
20
|
+
0
|
21
|
+
end
|
22
|
+
|
23
|
+
state[:stats][:listeners] = {
|
24
|
+
active: listeners,
|
25
|
+
standby: 0
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
# Namespace for all cross-context management operations that are needed to make sure everything
|
6
|
+
# operate as expected.
|
7
|
+
module Management
|
8
|
+
# Migrator used to run migrations on the states topics
|
9
|
+
# There are cases during upgrades, where extra fields may be added and other data, so in
|
10
|
+
# order not to deal with cases of some information missing, we can just migrate the data
|
11
|
+
# and ensure all the fields that we require after upgrade are present
|
12
|
+
#
|
13
|
+
# Migrations are similar to the once that are present in Ruby on Rails conceptually.
|
14
|
+
#
|
15
|
+
# We take our most recent state and we can alter it "in place". The altered result will be
|
16
|
+
# passed to the consecutive migrations and then republished back to Kafka. This allows us
|
17
|
+
# to manage Web UI aggregated data easily.
|
18
|
+
#
|
19
|
+
# @note We do not migrate the consumers reports for the following reasons:
|
20
|
+
# - if would be extremely hard to migrate them as they are being published and can be still
|
21
|
+
# published when the migrations are running
|
22
|
+
# - we would have to run migrations on each message
|
23
|
+
# - we already have a mechanism in the processing consumer that skips outdated records for
|
24
|
+
# rolling migrations
|
25
|
+
# - those records are short-lived and the expectation is for the user not to run old and
|
26
|
+
# new consumers together for an extensive period of time
|
27
|
+
#
|
28
|
+
# @note It will raise an error if we try to run migrations but the schemas we want to operate
|
29
|
+
# are newer. This will prevent us from damaging the data and ensures that we only move
|
30
|
+
# forward with the migrations. This can happen in case of a rolling upgrade, where old
|
31
|
+
# instance that is going to be terminated would get a temporary assignment with already
|
32
|
+
# migrated state.
|
33
|
+
class Migrator
|
34
|
+
# Include this so we can reference the schema versions easily
|
35
|
+
include Processing::Consumers::Aggregators
|
36
|
+
|
37
|
+
# Picks needed data from Kafka, alters it with migrations and puts the updated data
|
38
|
+
# back into Kafka. This ensures, that our Web UI topics that hold aggregated data are
|
39
|
+
# always aligned with the Web UI expectations
|
40
|
+
#
|
41
|
+
# @note To simplify things we always migrate and update all the topics data even if only
|
42
|
+
# part was migrated. That way we always ensure that all the elements are up to date
|
43
|
+
def call
|
44
|
+
ensure_migrable!
|
45
|
+
# If migrating returns `false` it means no migrations happened
|
46
|
+
migrate && publish
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Raise an exception if there would be an attempt to run migrations on a newer schema for
|
52
|
+
# any states we manage. We can only move forward, so attempt to migrate for example from
|
53
|
+
# 1.0.0 to 0.9.0 should be considered and error.
|
54
|
+
def ensure_migrable!
|
55
|
+
if consumers_state[:schema_version] > State::SCHEMA_VERSION
|
56
|
+
raise(
|
57
|
+
Errors::Management::IncompatibleSchemaError,
|
58
|
+
'consumers state newer than supported'
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
if consumers_metrics[:schema_version] > Metrics::SCHEMA_VERSION
|
63
|
+
raise(
|
64
|
+
Errors::Management::IncompatibleSchemaError,
|
65
|
+
'consumers metrics newer than supported'
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
# Applies migrations if needed and mutates the in-memory data
|
73
|
+
#
|
74
|
+
# @return [Boolean] were there any migrations applied
|
75
|
+
def migrate
|
76
|
+
any_migrations = false
|
77
|
+
|
78
|
+
Migrations::Base.sorted_descendants.each do |migration_class|
|
79
|
+
data = send(migration_class.type)
|
80
|
+
|
81
|
+
next unless migration_class.applicable?(data[:schema_version])
|
82
|
+
|
83
|
+
migration_class.new.migrate(data)
|
84
|
+
|
85
|
+
any_migrations = true
|
86
|
+
end
|
87
|
+
|
88
|
+
any_migrations
|
89
|
+
end
|
90
|
+
|
91
|
+
# Publishes all the states migrated records
|
92
|
+
def publish
|
93
|
+
consumers_state[:schema_version] = State::SCHEMA_VERSION
|
94
|
+
consumers_metrics[:schema_version] = Metrics::SCHEMA_VERSION
|
95
|
+
|
96
|
+
# Migrator may run in the context of the processing consumer prior to any states
|
97
|
+
# fetching related to processing. We use sync to make sure, that the following
|
98
|
+
# processing related states fetched fetch the new states
|
99
|
+
Processing::Publisher.publish!(
|
100
|
+
consumers_state,
|
101
|
+
consumers_metrics
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
# @return [Hash] current consumers states most recent state
|
106
|
+
def consumers_state
|
107
|
+
@consumers_state ||= Processing::Consumers::State.current!
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [Hash] current consumers metrics most recent state
|
111
|
+
def consumers_metrics
|
112
|
+
@consumers_metrics ||= Processing::Consumers::Metrics.current!
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -11,35 +11,19 @@ module Karafka
|
|
11
11
|
class Consumer < Karafka::BaseConsumer
|
12
12
|
include ::Karafka::Core::Helpers::Time
|
13
13
|
|
14
|
-
# @param args [Object] all the arguments `Karafka::BaseConsumer` accepts by default
|
15
|
-
def initialize(*args)
|
16
|
-
super
|
17
|
-
|
18
|
-
@flush_interval = ::Karafka::Web.config.processing.interval
|
19
|
-
|
20
|
-
@schema_manager = Consumers::SchemaManager.new
|
21
|
-
@state_aggregator = Consumers::Aggregators::State.new(@schema_manager)
|
22
|
-
@state_contract = Consumers::Contracts::State.new
|
23
|
-
|
24
|
-
@metrics_aggregator = Consumers::Aggregators::Metrics.new
|
25
|
-
@metrics_contract = Consumers::Contracts::Metrics.new
|
26
|
-
|
27
|
-
# We set this that way so we report with first batch and so we report as fast as possible
|
28
|
-
@flushed_at = monotonic_now - @flush_interval
|
29
|
-
@established = false
|
30
|
-
end
|
31
|
-
|
32
14
|
# Aggregates consumers state into a single current state representation
|
33
15
|
def consume
|
16
|
+
bootstrap!
|
17
|
+
|
34
18
|
consumers_messages = messages.select { |message| message.payload[:type] == 'consumer' }
|
35
19
|
|
36
20
|
# If there is even one incompatible message, we need to stop
|
37
21
|
consumers_messages.each do |message|
|
38
|
-
case @
|
22
|
+
case @reports_schema_manager.call(message)
|
39
23
|
when :current
|
40
24
|
true
|
41
25
|
when :newer
|
42
|
-
@
|
26
|
+
@reports_schema_manager.invalidate!
|
43
27
|
|
44
28
|
dispatch
|
45
29
|
|
@@ -49,6 +33,9 @@ module Karafka
|
|
49
33
|
# requests without significant or any impact on data quality but without having to
|
50
34
|
# worry about backwards compatibility. Errors are tracked independently, so it should
|
51
35
|
# not be a problem.
|
36
|
+
#
|
37
|
+
# In case user wants to do a rolling upgrade, the user docs state that this can happen
|
38
|
+
# and it is something user should be aware
|
52
39
|
when :older
|
53
40
|
next
|
54
41
|
else
|
@@ -83,6 +70,35 @@ module Karafka
|
|
83
70
|
|
84
71
|
private
|
85
72
|
|
73
|
+
# Prepares all the initial objects and ensures all the needed states are as expected
|
74
|
+
# @note We do not run it in the `#initialize` anymore as `#initialize` happens before
|
75
|
+
# the work starts so errors there are handled differently. We want this initial setup
|
76
|
+
# to operate and fail (if needed) during messages consumption phase
|
77
|
+
def bootstrap!
|
78
|
+
return if @bootstrapped
|
79
|
+
|
80
|
+
# Run the migrator on the assignment to make sure all our data is as expected
|
81
|
+
# While users may run the CLI command this is a fail-safe for zero downtime deployments
|
82
|
+
# It costs us two extra requests to Kafka topics as we migrate prior to fetching the
|
83
|
+
# states to the aggregators but this is done on purpose not to mix those two contexts.
|
84
|
+
Management::Migrator.new.call
|
85
|
+
|
86
|
+
@flush_interval = ::Karafka::Web.config.processing.interval
|
87
|
+
|
88
|
+
@reports_schema_manager = Consumers::SchemaManager.new
|
89
|
+
@state_aggregator = Consumers::Aggregators::State.new(@reports_schema_manager)
|
90
|
+
@state_contract = Consumers::Contracts::State.new
|
91
|
+
|
92
|
+
@metrics_aggregator = Consumers::Aggregators::Metrics.new
|
93
|
+
@metrics_contract = Consumers::Contracts::Metrics.new
|
94
|
+
|
95
|
+
# We set this that way so we report with first batch and so we report as fast as possible
|
96
|
+
@flushed_at = monotonic_now - @flush_interval
|
97
|
+
@established = false
|
98
|
+
|
99
|
+
@bootstrapped = true
|
100
|
+
end
|
101
|
+
|
86
102
|
# Flushes the state of the Web-UI to the DB
|
87
103
|
def dispatch
|
88
104
|
return unless @established
|
@@ -114,24 +130,9 @@ module Karafka
|
|
114
130
|
def flush
|
115
131
|
@flushed_at = monotonic_now
|
116
132
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
topic: Karafka::Web.config.topics.consumers.states,
|
121
|
-
payload: Zlib::Deflate.deflate(@state.to_json),
|
122
|
-
# This will ensure that the consumer states are compacted
|
123
|
-
key: Karafka::Web.config.topics.consumers.states,
|
124
|
-
partition: 0,
|
125
|
-
headers: { 'zlib' => 'true' }
|
126
|
-
},
|
127
|
-
{
|
128
|
-
topic: Karafka::Web.config.topics.consumers.metrics,
|
129
|
-
payload: Zlib::Deflate.deflate(@metrics.to_json),
|
130
|
-
key: Karafka::Web.config.topics.consumers.metrics,
|
131
|
-
partition: 0,
|
132
|
-
headers: { 'zlib' => 'true' }
|
133
|
-
}
|
134
|
-
]
|
133
|
+
Publisher.publish(
|
134
|
+
@state,
|
135
|
+
@metrics
|
135
136
|
)
|
136
137
|
end
|
137
138
|
end
|
@@ -10,9 +10,8 @@ module Karafka
|
|
10
10
|
# values for charts and metrics
|
11
11
|
class Metrics < Base
|
12
12
|
# Current schema version
|
13
|
-
# This
|
14
|
-
|
15
|
-
SCHEMA_VERSION = '1.0.0'
|
13
|
+
# This is used for detecting incompatible changes and writing migrations
|
14
|
+
SCHEMA_VERSION = '1.1.2'
|
16
15
|
|
17
16
|
def initialize
|
18
17
|
super
|
@@ -107,9 +106,18 @@ module Karafka
|
|
107
106
|
|
108
107
|
# Last stable offsets freeze durations - we pick the max freeze to indicate
|
109
108
|
# the longest open transaction that potentially may be hanging
|
110
|
-
|
111
|
-
|
112
|
-
|
109
|
+
# We select only those partitions for which LSO != HO as in any other case this
|
110
|
+
# just means we've reached the end of data and ls may freeze because there is no
|
111
|
+
# more data flowing. Such cases should not be reported as ls offset freezes because
|
112
|
+
# there is no more data to be processed and can grow until more data is present
|
113
|
+
# this does not indicate "bad" freezing that we are interested in
|
114
|
+
ls_offsets_fds = partitions_data.map do |p_details|
|
115
|
+
next if p_details.fetch(:ls_offset, 0) == p_details.fetch(:hi_offset, 0)
|
116
|
+
|
117
|
+
ls_offset_fd = p_details.fetch(:ls_offset_fd, 0)
|
118
|
+
|
119
|
+
ls_offset_fd.negative? ? nil : ls_offset_fd
|
120
|
+
end
|
113
121
|
|
114
122
|
cgs[group_name] ||= {}
|
115
123
|
cgs[group_name][topic_name] = {
|
@@ -119,7 +127,7 @@ module Karafka
|
|
119
127
|
# Take max last stable offset duration without any change. This can
|
120
128
|
# indicate a hanging transaction, because the offset will not move forward
|
121
129
|
# and will stay with a growing freeze duration when stuck
|
122
|
-
ls_offset_fd:
|
130
|
+
ls_offset_fd: ls_offsets_fds.compact.max || 0
|
123
131
|
}
|
124
132
|
end
|
125
133
|
|
@@ -20,7 +20,7 @@ module Karafka
|
|
20
20
|
# Current schema version
|
21
21
|
# This can be used in the future for detecting incompatible changes and writing
|
22
22
|
# migrations
|
23
|
-
SCHEMA_VERSION = '1.
|
23
|
+
SCHEMA_VERSION = '1.2.2'
|
24
24
|
|
25
25
|
# @param schema_manager [Karafka::Web::Processing::Consumers::SchemaManager] schema
|
26
26
|
# manager that tracks the compatibility of schemas.
|
@@ -126,9 +126,11 @@ module Karafka
|
|
126
126
|
stats[:workers] = 0
|
127
127
|
stats[:processes] = 0
|
128
128
|
stats[:rss] = 0
|
129
|
-
stats[:listeners] = 0
|
129
|
+
stats[:listeners] = { active: 0, standby: 0 }
|
130
130
|
stats[:lag] = 0
|
131
131
|
stats[:lag_stored] = 0
|
132
|
+
stats[:bytes_received] = 0
|
133
|
+
stats[:bytes_sent] = 0
|
132
134
|
utilization = 0
|
133
135
|
|
134
136
|
@active_reports
|
@@ -149,7 +151,10 @@ module Karafka
|
|
149
151
|
stats[:busy] += report_stats[:busy]
|
150
152
|
stats[:enqueued] += report_stats[:enqueued]
|
151
153
|
stats[:workers] += report_process[:workers] || 0
|
152
|
-
stats[:
|
154
|
+
stats[:bytes_received] += report_process[:bytes_received] || 0
|
155
|
+
stats[:bytes_sent] += report_process[:bytes_sent] || 0
|
156
|
+
stats[:listeners][:active] += report_process[:listeners][:active]
|
157
|
+
stats[:listeners][:standby] += report_process[:listeners][:standby]
|
153
158
|
stats[:processes] += 1
|
154
159
|
stats[:rss] += report_process[:memory_usage]
|
155
160
|
stats[:lag] += lags.compact.reject(&:negative?).sum
|
@@ -20,10 +20,14 @@ module Karafka
|
|
20
20
|
required(:workers) { |val| val.is_a?(Integer) && val >= 0 }
|
21
21
|
required(:processes) { |val| val.is_a?(Integer) && val >= 0 }
|
22
22
|
required(:rss) { |val| val.is_a?(Numeric) && val >= 0 }
|
23
|
-
required(:listeners) { |val| val.is_a?(Integer) && val >= 0 }
|
24
23
|
required(:utilization) { |val| val.is_a?(Numeric) && val >= 0 }
|
25
24
|
required(:lag_stored) { |val| val.is_a?(Integer) }
|
26
25
|
required(:lag) { |val| val.is_a?(Integer) }
|
26
|
+
|
27
|
+
nested(:listeners) do
|
28
|
+
required(:active) { |val| val.is_a?(Integer) && val >= 0 }
|
29
|
+
required(:standby) { |val| val.is_a?(Integer) && val >= 0 }
|
30
|
+
end
|
27
31
|
end
|
28
32
|
end
|
29
33
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Processing
|
6
|
+
# Object responsible for publishing states data back into Kafka so it can be used in the UI
|
7
|
+
class Publisher
|
8
|
+
class << self
|
9
|
+
# Publishes data back to Kafka in an async fashion
|
10
|
+
#
|
11
|
+
# @param consumers_state [Hash] consumers current state
|
12
|
+
# @param consumers_metrics [Hash] consumers current metrics
|
13
|
+
def publish(consumers_state, consumers_metrics)
|
14
|
+
::Karafka::Web.producer.produce_many_async(
|
15
|
+
prepare_data(consumers_state, consumers_metrics)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Publishes data back to Kafka in a sync fashion
|
20
|
+
#
|
21
|
+
# @param consumers_state [Hash] consumers current state
|
22
|
+
# @param consumers_metrics [Hash] consumers current metrics
|
23
|
+
def publish!(consumers_state, consumers_metrics)
|
24
|
+
::Karafka::Web.producer.produce_many_sync(
|
25
|
+
prepare_data(consumers_state, consumers_metrics)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Converts the states into format that we can dispatch to Kafka
|
32
|
+
#
|
33
|
+
# @param consumers_state [Hash] consumers current state
|
34
|
+
# @param consumers_metrics [Hash] consumers current metrics
|
35
|
+
# @return [Array<Hash>]
|
36
|
+
def prepare_data(consumers_state, consumers_metrics)
|
37
|
+
[
|
38
|
+
{
|
39
|
+
topic: Karafka::Web.config.topics.consumers.states,
|
40
|
+
payload: Zlib::Deflate.deflate(consumers_state.to_json),
|
41
|
+
# This will ensure that the consumer states are compacted
|
42
|
+
key: Karafka::Web.config.topics.consumers.states,
|
43
|
+
partition: 0,
|
44
|
+
headers: { 'zlib' => 'true' }
|
45
|
+
},
|
46
|
+
{
|
47
|
+
topic: Karafka::Web.config.topics.consumers.metrics,
|
48
|
+
payload: Zlib::Deflate.deflate(consumers_metrics.to_json),
|
49
|
+
key: Karafka::Web.config.topics.consumers.metrics,
|
50
|
+
partition: 0,
|
51
|
+
headers: { 'zlib' => 'true' }
|
52
|
+
}
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -11,18 +11,19 @@ module Karafka
|
|
11
11
|
|
12
12
|
required(:consumer) { |val| val.is_a?(String) }
|
13
13
|
required(:consumer_group) { |val| val.is_a?(String) }
|
14
|
-
required(:
|
14
|
+
required(:updated_at) { |val| val.is_a?(Float) && val >= 0 }
|
15
15
|
required(:topic) { |val| val.is_a?(String) }
|
16
16
|
required(:partition) { |val| val.is_a?(Integer) && val >= 0 }
|
17
17
|
required(:first_offset) { |val| val.is_a?(Integer) && (val >= 0 || val == -1001) }
|
18
18
|
required(:last_offset) { |val| val.is_a?(Integer) && (val >= 0 || val == -1001) }
|
19
19
|
required(:committed_offset) { |val| val.is_a?(Integer) }
|
20
20
|
required(:messages) { |val| val.is_a?(Integer) && val >= 0 }
|
21
|
-
required(:type) { |val| %w[consume revoked shutdown].include?(val) }
|
21
|
+
required(:type) { |val| %w[consume revoked shutdown tick].include?(val) }
|
22
22
|
required(:tags) { |val| val.is_a?(Karafka::Core::Taggable::Tags) }
|
23
23
|
# -1 can be here for workless flows
|
24
24
|
required(:consumption_lag) { |val| val.is_a?(Integer) && (val >= 0 || val == -1) }
|
25
25
|
required(:processing_lag) { |val| val.is_a?(Integer) && (val >= 0 || val == -1) }
|
26
|
+
required(:status) { |val| %w[running pending].include?(val) }
|
26
27
|
end
|
27
28
|
end
|
28
29
|
end
|
@@ -20,6 +20,7 @@ module Karafka
|
|
20
20
|
required(:stored_offset_fd) { |val| val.is_a?(Integer) && val >= 0 }
|
21
21
|
required(:fetch_state) { |val| val.is_a?(String) && !val.empty? }
|
22
22
|
required(:poll_state) { |val| val.is_a?(String) && !val.empty? }
|
23
|
+
required(:poll_state_ch) { |val| val.is_a?(Integer) && val >= 0 }
|
23
24
|
required(:hi_offset) { |val| val.is_a?(Integer) }
|
24
25
|
required(:hi_offset_fd) { |val| val.is_a?(Integer) && val >= 0 }
|
25
26
|
required(:lo_offset) { |val| val.is_a?(Integer) }
|
@@ -27,10 +27,14 @@ module Karafka
|
|
27
27
|
required(:memory_size) { |val| val.is_a?(Integer) && val >= 0 }
|
28
28
|
required(:status) { |val| ::Karafka::Status::STATES.key?(val.to_s.to_sym) }
|
29
29
|
required(:threads) { |val| val.is_a?(Integer) && val >= 0 }
|
30
|
-
required(:listeners) { |val| val.is_a?(Integer) && val >= 0 }
|
31
30
|
required(:workers) { |val| val.is_a?(Integer) && val.positive? }
|
32
31
|
required(:tags) { |val| val.is_a?(Karafka::Core::Taggable::Tags) }
|
33
32
|
|
33
|
+
nested(:listeners) do
|
34
|
+
required(:active) { |val| val.is_a?(Integer) && val >= 0 }
|
35
|
+
required(:standby) { |val| val.is_a?(Integer) && val >= 0 }
|
36
|
+
end
|
37
|
+
|
34
38
|
required(:cpu_usage) do |val|
|
35
39
|
val.is_a?(Array) &&
|
36
40
|
val.all? { |key| key.is_a?(Numeric) } &&
|
@@ -52,6 +56,7 @@ module Karafka
|
|
52
56
|
nested(:stats) do
|
53
57
|
required(:busy) { |val| val.is_a?(Integer) && val >= 0 }
|
54
58
|
required(:enqueued) { |val| val.is_a?(Integer) && val >= 0 }
|
59
|
+
required(:waiting) { |val| val.is_a?(Integer) && val >= 0 }
|
55
60
|
required(:utilization) { |val| val.is_a?(Numeric) && val >= 0 }
|
56
61
|
|
57
62
|
nested(:total) do
|
@@ -12,7 +12,16 @@ module Karafka
|
|
12
12
|
|
13
13
|
required(:id) { |val| val.is_a?(String) && !val.empty? }
|
14
14
|
required(:topics) { |val| val.is_a?(Hash) }
|
15
|
-
|
15
|
+
|
16
|
+
nested(:state) do
|
17
|
+
required(:state) { |val| val.is_a?(String) && !val.empty? }
|
18
|
+
required(:join_state) { |val| val.is_a?(String) && !val.empty? }
|
19
|
+
required(:stateage) { |val| val.is_a?(Integer) && val >= 0 }
|
20
|
+
required(:rebalance_age) { |val| val.is_a?(Integer) && val >= 0 }
|
21
|
+
required(:rebalance_cnt) { |val| val.is_a?(Integer) && val >= 0 }
|
22
|
+
required(:rebalance_reason) { |val| val.is_a?(String) && !val.empty? }
|
23
|
+
required(:poll_age) { |val| val.is_a?(Numeric) && val >= 0 }
|
24
|
+
end
|
16
25
|
|
17
26
|
virtual do |data, errors|
|
18
27
|
next unless errors.empty?
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Karafka
|
4
|
+
module Web
|
5
|
+
module Tracking
|
6
|
+
module Consumers
|
7
|
+
module Listeners
|
8
|
+
# Listener for listening on connections related events like polling, etc
|
9
|
+
class Connections < Base
|
10
|
+
# Set first poll time before we start fetching so we always have a poll time
|
11
|
+
# and we don't have to worry about it being always available
|
12
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
13
|
+
def on_connection_listener_before_fetch_loop(event)
|
14
|
+
on_connection_listener_fetch_loop_received(event)
|
15
|
+
end
|
16
|
+
|
17
|
+
# When fetch loop is done it means this subscription group is no longer active and we
|
18
|
+
# should stop reporting. The listener was stopped.
|
19
|
+
#
|
20
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
21
|
+
def on_connection_listener_after_fetch_loop(event)
|
22
|
+
subscription_group = event[:subscription_group]
|
23
|
+
sg_id = subscription_group.id
|
24
|
+
cg_id = subscription_group.consumer_group.id
|
25
|
+
|
26
|
+
track do |sampler|
|
27
|
+
sampler.consumer_groups[cg_id][:subscription_groups].delete(sg_id)
|
28
|
+
sampler.subscription_groups.delete(sg_id)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Tracks the moment a poll happened on a given subscription group
|
33
|
+
#
|
34
|
+
# @param event [Karafka::Core::Monitoring::Event]
|
35
|
+
def on_connection_listener_fetch_loop_received(event)
|
36
|
+
sg_id = event[:subscription_group].id
|
37
|
+
|
38
|
+
track do |sampler|
|
39
|
+
sampler.subscription_groups[sg_id] = {
|
40
|
+
polled_at: monotonic_now
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -11,9 +11,12 @@ module Karafka
|
|
11
11
|
# Indicate pause
|
12
12
|
#
|
13
13
|
# @param event [Karafka::Core::Monitoring::Event]
|
14
|
-
def
|
14
|
+
def on_consumer_consuming_pause(event)
|
15
15
|
track do |sampler|
|
16
|
-
sampler.pauses
|
16
|
+
sampler.pauses[pause_id(event)] = {
|
17
|
+
timeout: event[:timeout],
|
18
|
+
paused_till: monotonic_now + event[:timeout]
|
19
|
+
}
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
@@ -33,9 +36,9 @@ module Karafka
|
|
33
36
|
def pause_id(event)
|
34
37
|
topic = event[:topic]
|
35
38
|
partition = event[:partition]
|
36
|
-
|
39
|
+
subscription_group_id = event[:subscription_group].id
|
37
40
|
|
38
|
-
[
|
41
|
+
[subscription_group_id, topic, partition].join('-')
|
39
42
|
end
|
40
43
|
end
|
41
44
|
end
|