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.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +18 -5
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +63 -0
  6. data/Gemfile.lock +22 -22
  7. data/docker-compose.yml +3 -1
  8. data/karafka-web.gemspec +2 -2
  9. data/lib/karafka/web/config.rb +16 -3
  10. data/lib/karafka/web/contracts/config.rb +7 -2
  11. data/lib/karafka/web/errors.rb +12 -0
  12. data/lib/karafka/web/inflector.rb +33 -0
  13. data/lib/karafka/web/installer.rb +20 -11
  14. data/lib/karafka/web/management/actions/base.rb +36 -0
  15. data/lib/karafka/web/management/actions/clean_boot_file.rb +33 -0
  16. data/lib/karafka/web/management/actions/create_initial_states.rb +77 -0
  17. data/lib/karafka/web/management/actions/create_topics.rb +139 -0
  18. data/lib/karafka/web/management/actions/delete_topics.rb +30 -0
  19. data/lib/karafka/web/management/actions/enable.rb +117 -0
  20. data/lib/karafka/web/management/actions/extend_boot_file.rb +39 -0
  21. data/lib/karafka/web/management/actions/migrate_states_data.rb +18 -0
  22. data/lib/karafka/web/management/migrations/0_base.rb +58 -0
  23. data/lib/karafka/web/management/migrations/0_set_initial_consumers_metrics.rb +36 -0
  24. data/lib/karafka/web/management/migrations/0_set_initial_consumers_state.rb +43 -0
  25. data/lib/karafka/web/management/migrations/1699543515_fill_missing_received_and_sent_bytes_in_consumers_metrics.rb +26 -0
  26. data/lib/karafka/web/management/migrations/1699543515_fill_missing_received_and_sent_bytes_in_consumers_state.rb +23 -0
  27. data/lib/karafka/web/management/migrations/1700234522_introduce_waiting_in_consumers_metrics.rb +24 -0
  28. data/lib/karafka/web/management/migrations/1700234522_introduce_waiting_in_consumers_state.rb +20 -0
  29. data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_metrics.rb +24 -0
  30. data/lib/karafka/web/management/migrations/1700234522_remove_processing_from_consumers_state.rb +20 -0
  31. data/lib/karafka/web/management/migrations/1704722380_split_listeners_into_active_and_paused_in_metrics.rb +36 -0
  32. data/lib/karafka/web/management/migrations/1704722380_split_listeners_into_active_and_paused_in_states.rb +32 -0
  33. data/lib/karafka/web/management/migrator.rb +117 -0
  34. data/lib/karafka/web/processing/consumer.rb +39 -38
  35. data/lib/karafka/web/processing/consumers/aggregators/metrics.rb +2 -3
  36. data/lib/karafka/web/processing/consumers/aggregators/state.rb +8 -3
  37. data/lib/karafka/web/processing/consumers/contracts/aggregated_stats.rb +5 -1
  38. data/lib/karafka/web/processing/publisher.rb +59 -0
  39. data/lib/karafka/web/tracking/consumers/contracts/job.rb +3 -2
  40. data/lib/karafka/web/tracking/consumers/contracts/partition.rb +1 -0
  41. data/lib/karafka/web/tracking/consumers/contracts/report.rb +6 -1
  42. data/lib/karafka/web/tracking/consumers/contracts/subscription_group.rb +10 -1
  43. data/lib/karafka/web/tracking/consumers/listeners/connections.rb +49 -0
  44. data/lib/karafka/web/tracking/consumers/listeners/pausing.rb +7 -4
  45. data/lib/karafka/web/tracking/consumers/listeners/processing.rb +78 -70
  46. data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +40 -13
  47. data/lib/karafka/web/tracking/consumers/sampler.rb +82 -25
  48. data/lib/karafka/web/tracking/helpers/ttls/array.rb +72 -0
  49. data/lib/karafka/web/tracking/helpers/ttls/hash.rb +34 -0
  50. data/lib/karafka/web/tracking/helpers/ttls/stats.rb +49 -0
  51. data/lib/karafka/web/tracking/helpers/ttls/windows.rb +32 -0
  52. data/lib/karafka/web/tracking/reporter.rb +1 -0
  53. data/lib/karafka/web/ui/app.rb +22 -4
  54. data/lib/karafka/web/ui/base.rb +18 -2
  55. data/lib/karafka/web/ui/controllers/base.rb +34 -4
  56. data/lib/karafka/web/ui/controllers/become_pro.rb +1 -1
  57. data/lib/karafka/web/ui/controllers/cluster.rb +33 -9
  58. data/lib/karafka/web/ui/controllers/consumers.rb +8 -2
  59. data/lib/karafka/web/ui/controllers/dashboard.rb +2 -2
  60. data/lib/karafka/web/ui/controllers/errors.rb +2 -2
  61. data/lib/karafka/web/ui/controllers/jobs.rb +55 -5
  62. data/lib/karafka/web/ui/controllers/requests/params.rb +5 -0
  63. data/lib/karafka/web/ui/controllers/responses/deny.rb +15 -0
  64. data/lib/karafka/web/ui/controllers/responses/file.rb +23 -0
  65. data/lib/karafka/web/ui/controllers/responses/{data.rb → render.rb} +3 -3
  66. data/lib/karafka/web/ui/controllers/routing.rb +11 -2
  67. data/lib/karafka/web/ui/controllers/status.rb +1 -1
  68. data/lib/karafka/web/ui/helpers/application_helper.rb +70 -0
  69. data/lib/karafka/web/ui/lib/hash_proxy.rb +29 -14
  70. data/lib/karafka/web/ui/lib/sorter.rb +170 -0
  71. data/lib/karafka/web/ui/models/counters.rb +6 -0
  72. data/lib/karafka/web/ui/models/health.rb +23 -2
  73. data/lib/karafka/web/ui/models/jobs.rb +48 -0
  74. data/lib/karafka/web/ui/models/metrics/charts/aggregated.rb +33 -0
  75. data/lib/karafka/web/ui/models/metrics/charts/topics.rb +1 -1
  76. data/lib/karafka/web/ui/models/process.rb +2 -1
  77. data/lib/karafka/web/ui/models/status.rb +23 -7
  78. data/lib/karafka/web/ui/models/topic.rb +3 -1
  79. data/lib/karafka/web/ui/models/visibility_filter.rb +16 -0
  80. data/lib/karafka/web/ui/pro/app.rb +44 -6
  81. data/lib/karafka/web/ui/pro/controllers/cluster.rb +1 -0
  82. data/lib/karafka/web/ui/pro/controllers/consumers.rb +52 -6
  83. data/lib/karafka/web/ui/pro/controllers/dashboard.rb +1 -1
  84. data/lib/karafka/web/ui/pro/controllers/dlq.rb +1 -1
  85. data/lib/karafka/web/ui/pro/controllers/errors.rb +3 -3
  86. data/lib/karafka/web/ui/pro/controllers/explorer.rb +8 -8
  87. data/lib/karafka/web/ui/pro/controllers/health.rb +34 -2
  88. data/lib/karafka/web/ui/pro/controllers/jobs.rb +11 -0
  89. data/lib/karafka/web/ui/pro/controllers/messages.rb +42 -0
  90. data/lib/karafka/web/ui/pro/controllers/routing.rb +11 -2
  91. data/lib/karafka/web/ui/pro/views/consumers/_breadcrumbs.erb +8 -2
  92. data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +14 -8
  93. data/lib/karafka/web/ui/pro/views/consumers/_counters.erb +8 -6
  94. data/lib/karafka/web/ui/pro/views/consumers/consumer/_job.erb +4 -1
  95. data/lib/karafka/web/ui/pro/views/consumers/consumer/_no_jobs.erb +1 -1
  96. data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +1 -3
  97. data/lib/karafka/web/ui/pro/views/consumers/consumer/_subscription_group.erb +28 -11
  98. data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +10 -3
  99. data/lib/karafka/web/ui/pro/views/consumers/index.erb +3 -3
  100. data/lib/karafka/web/ui/pro/views/consumers/pending_jobs.erb +43 -0
  101. data/lib/karafka/web/ui/pro/views/consumers/{jobs.erb → running_jobs.erb} +11 -10
  102. data/lib/karafka/web/ui/pro/views/dashboard/index.erb +7 -1
  103. data/lib/karafka/web/ui/pro/views/explorer/message/_message_actions.erb +18 -0
  104. data/lib/karafka/web/ui/pro/views/explorer/message/_metadata.erb +43 -0
  105. data/lib/karafka/web/ui/pro/views/explorer/message/_payload.erb +21 -0
  106. data/lib/karafka/web/ui/pro/views/explorer/message/_payload_actions.erb +19 -0
  107. data/lib/karafka/web/ui/pro/views/explorer/show.erb +9 -84
  108. data/lib/karafka/web/ui/pro/views/health/_breadcrumbs.erb +8 -0
  109. data/lib/karafka/web/ui/pro/views/health/_partition.erb +1 -3
  110. data/lib/karafka/web/ui/pro/views/health/_partition_offset.erb +4 -4
  111. data/lib/karafka/web/ui/pro/views/health/_partition_times.erb +32 -0
  112. data/lib/karafka/web/ui/pro/views/health/_tabs.erb +9 -0
  113. data/lib/karafka/web/ui/pro/views/health/changes.erb +66 -0
  114. data/lib/karafka/web/ui/pro/views/health/offsets.erb +14 -14
  115. data/lib/karafka/web/ui/pro/views/health/overview.erb +11 -11
  116. data/lib/karafka/web/ui/pro/views/jobs/_job.erb +1 -1
  117. data/lib/karafka/web/ui/pro/views/jobs/_no_jobs.erb +1 -1
  118. data/lib/karafka/web/ui/pro/views/jobs/pending.erb +39 -0
  119. data/lib/karafka/web/ui/pro/views/jobs/running.erb +39 -0
  120. data/lib/karafka/web/ui/pro/views/routing/_consumer_group.erb +2 -2
  121. data/lib/karafka/web/ui/pro/views/routing/_topic.erb +9 -0
  122. data/lib/karafka/web/ui/pro/views/routing/show.erb +12 -0
  123. data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +1 -1
  124. data/lib/karafka/web/ui/public/javascripts/application.js +10 -0
  125. data/lib/karafka/web/ui/public/stylesheets/application.css +4 -0
  126. data/lib/karafka/web/ui/views/cluster/_breadcrumbs.erb +16 -0
  127. data/lib/karafka/web/ui/views/cluster/_tabs.erb +27 -0
  128. data/lib/karafka/web/ui/views/cluster/brokers.erb +27 -0
  129. data/lib/karafka/web/ui/views/cluster/topics.erb +35 -0
  130. data/lib/karafka/web/ui/views/consumers/_counters.erb +8 -6
  131. data/lib/karafka/web/ui/views/consumers/_summary.erb +2 -2
  132. data/lib/karafka/web/ui/views/consumers/index.erb +3 -3
  133. data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +23 -7
  134. data/lib/karafka/web/ui/views/dashboard/index.erb +19 -8
  135. data/lib/karafka/web/ui/views/errors/show.erb +2 -23
  136. data/lib/karafka/web/ui/views/jobs/_breadcrumbs.erb +17 -1
  137. data/lib/karafka/web/ui/views/jobs/_job.erb +1 -1
  138. data/lib/karafka/web/ui/views/jobs/_no_jobs.erb +1 -1
  139. data/lib/karafka/web/ui/views/jobs/_tabs.erb +27 -0
  140. data/lib/karafka/web/ui/views/jobs/{index.erb → pending.erb} +9 -7
  141. data/lib/karafka/web/ui/{pro/views/jobs/index.erb → views/jobs/running.erb} +9 -11
  142. data/lib/karafka/web/ui/views/routing/_consumer_group.erb +14 -12
  143. data/lib/karafka/web/ui/views/shared/_navigation.erb +1 -1
  144. data/lib/karafka/web/ui/views/shared/_pagination.erb +1 -1
  145. data/lib/karafka/web/ui/views/shared/exceptions/not_allowed.erb +37 -0
  146. data/lib/karafka/web/ui/views/status/show.erb +17 -2
  147. data/lib/karafka/web/ui/views/status/warnings/_routing_topics_presence.erb +15 -0
  148. data/lib/karafka/web/version.rb +1 -1
  149. data/lib/karafka/web.rb +6 -2
  150. data.tar.gz.sig +0 -0
  151. metadata +61 -26
  152. metadata.gz.sig +0 -0
  153. data/lib/karafka/web/management/base.rb +0 -34
  154. data/lib/karafka/web/management/clean_boot_file.rb +0 -31
  155. data/lib/karafka/web/management/create_initial_states.rb +0 -101
  156. data/lib/karafka/web/management/create_topics.rb +0 -133
  157. data/lib/karafka/web/management/delete_topics.rb +0 -28
  158. data/lib/karafka/web/management/enable.rb +0 -102
  159. data/lib/karafka/web/management/extend_boot_file.rb +0 -37
  160. data/lib/karafka/web/tracking/ttl_array.rb +0 -59
  161. data/lib/karafka/web/tracking/ttl_hash.rb +0 -16
  162. data/lib/karafka/web/ui/pro/views/dashboard/_ranges_selector.erb +0 -39
  163. 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
@@ -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
@@ -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
@@ -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