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.
Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +13 -4
  4. data/CHANGELOG.md +119 -5
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +27 -24
  7. data/README.md +2 -0
  8. data/bin/rspecs +6 -0
  9. data/certs/cert_chain.pem +21 -21
  10. data/docker-compose.yml +22 -0
  11. data/karafka-web.gemspec +3 -3
  12. data/lib/karafka/web/app.rb +6 -2
  13. data/lib/karafka/web/cli.rb +51 -47
  14. data/lib/karafka/web/config.rb +33 -9
  15. data/lib/karafka/web/contracts/base.rb +32 -0
  16. data/lib/karafka/web/contracts/config.rb +63 -0
  17. data/lib/karafka/web/deserializer.rb +10 -1
  18. data/lib/karafka/web/errors.rb +29 -7
  19. data/lib/karafka/web/installer.rb +58 -148
  20. data/lib/karafka/web/management/base.rb +34 -0
  21. data/lib/karafka/web/management/clean_boot_file.rb +31 -0
  22. data/lib/karafka/web/management/create_initial_states.rb +101 -0
  23. data/lib/karafka/web/management/create_topics.rb +127 -0
  24. data/lib/karafka/web/management/delete_topics.rb +28 -0
  25. data/lib/karafka/web/management/enable.rb +82 -0
  26. data/lib/karafka/web/management/extend_boot_file.rb +37 -0
  27. data/lib/karafka/web/processing/consumer.rb +73 -17
  28. data/lib/karafka/web/processing/consumers/aggregators/base.rb +56 -0
  29. data/lib/karafka/web/processing/consumers/aggregators/metrics.rb +154 -0
  30. data/lib/karafka/web/processing/consumers/aggregators/state.rb +180 -0
  31. data/lib/karafka/web/processing/consumers/contracts/aggregated_stats.rb +32 -0
  32. data/lib/karafka/web/processing/consumers/contracts/metrics.rb +53 -0
  33. data/lib/karafka/web/processing/consumers/contracts/process.rb +19 -0
  34. data/lib/karafka/web/processing/consumers/contracts/state.rb +49 -0
  35. data/lib/karafka/web/processing/consumers/contracts/topic_stats.rb +21 -0
  36. data/lib/karafka/web/processing/consumers/metrics.rb +29 -0
  37. data/lib/karafka/web/processing/consumers/schema_manager.rb +56 -0
  38. data/lib/karafka/web/processing/consumers/state.rb +6 -9
  39. data/lib/karafka/web/processing/time_series_tracker.rb +130 -0
  40. data/lib/karafka/web/tracking/consumers/contracts/consumer_group.rb +2 -2
  41. data/lib/karafka/web/tracking/consumers/contracts/job.rb +2 -1
  42. data/lib/karafka/web/tracking/consumers/contracts/partition.rb +14 -1
  43. data/lib/karafka/web/tracking/consumers/contracts/report.rb +10 -8
  44. data/lib/karafka/web/tracking/consumers/contracts/subscription_group.rb +2 -2
  45. data/lib/karafka/web/tracking/consumers/contracts/topic.rb +2 -2
  46. data/lib/karafka/web/tracking/consumers/listeners/processing.rb +6 -2
  47. data/lib/karafka/web/tracking/consumers/listeners/statistics.rb +15 -1
  48. data/lib/karafka/web/tracking/consumers/reporter.rb +14 -6
  49. data/lib/karafka/web/tracking/consumers/sampler.rb +80 -39
  50. data/lib/karafka/web/tracking/contracts/error.rb +2 -1
  51. data/lib/karafka/web/ui/app.rb +20 -10
  52. data/lib/karafka/web/ui/base.rb +56 -6
  53. data/lib/karafka/web/ui/controllers/base.rb +28 -0
  54. data/lib/karafka/web/ui/controllers/become_pro.rb +1 -1
  55. data/lib/karafka/web/ui/controllers/cluster.rb +12 -6
  56. data/lib/karafka/web/ui/controllers/consumers.rb +4 -2
  57. data/lib/karafka/web/ui/controllers/dashboard.rb +32 -0
  58. data/lib/karafka/web/ui/controllers/errors.rb +19 -6
  59. data/lib/karafka/web/ui/controllers/jobs.rb +4 -2
  60. data/lib/karafka/web/ui/controllers/requests/params.rb +28 -0
  61. data/lib/karafka/web/ui/controllers/responses/redirect.rb +29 -0
  62. data/lib/karafka/web/ui/helpers/application_helper.rb +57 -14
  63. data/lib/karafka/web/ui/helpers/paths_helper.rb +48 -0
  64. data/lib/karafka/web/ui/lib/hash_proxy.rb +18 -6
  65. data/lib/karafka/web/ui/lib/paginations/base.rb +61 -0
  66. data/lib/karafka/web/ui/lib/paginations/offset_based.rb +96 -0
  67. data/lib/karafka/web/ui/lib/paginations/page_based.rb +70 -0
  68. data/lib/karafka/web/ui/lib/paginations/paginators/arrays.rb +33 -0
  69. data/lib/karafka/web/ui/lib/paginations/paginators/base.rb +23 -0
  70. data/lib/karafka/web/ui/lib/paginations/paginators/partitions.rb +52 -0
  71. data/lib/karafka/web/ui/lib/paginations/paginators/sets.rb +85 -0
  72. data/lib/karafka/web/ui/lib/paginations/watermark_offsets_based.rb +75 -0
  73. data/lib/karafka/web/ui/lib/ttl_cache.rb +82 -0
  74. data/lib/karafka/web/ui/models/cluster_info.rb +59 -0
  75. data/lib/karafka/web/ui/models/consumers_metrics.rb +46 -0
  76. data/lib/karafka/web/ui/models/{state.rb → consumers_state.rb} +6 -2
  77. data/lib/karafka/web/ui/models/health.rb +37 -7
  78. data/lib/karafka/web/ui/models/message.rb +123 -39
  79. data/lib/karafka/web/ui/models/metrics/aggregated.rb +196 -0
  80. data/lib/karafka/web/ui/models/metrics/charts/aggregated.rb +50 -0
  81. data/lib/karafka/web/ui/models/metrics/charts/topics.rb +109 -0
  82. data/lib/karafka/web/ui/models/metrics/topics.rb +101 -0
  83. data/lib/karafka/web/ui/models/partition.rb +27 -0
  84. data/lib/karafka/web/ui/models/process.rb +12 -1
  85. data/lib/karafka/web/ui/models/status.rb +110 -22
  86. data/lib/karafka/web/ui/models/visibility_filter.rb +33 -0
  87. data/lib/karafka/web/ui/pro/app.rb +87 -19
  88. data/lib/karafka/web/ui/pro/controllers/cluster.rb +11 -0
  89. data/lib/karafka/web/ui/pro/controllers/consumers.rb +13 -7
  90. data/lib/karafka/web/ui/pro/controllers/dashboard.rb +54 -0
  91. data/lib/karafka/web/ui/pro/controllers/dlq.rb +1 -2
  92. data/lib/karafka/web/ui/pro/controllers/errors.rb +46 -10
  93. data/lib/karafka/web/ui/pro/controllers/explorer.rb +145 -15
  94. data/lib/karafka/web/ui/pro/controllers/health.rb +10 -2
  95. data/lib/karafka/web/ui/pro/controllers/messages.rb +62 -0
  96. data/lib/karafka/web/ui/pro/controllers/routing.rb +44 -0
  97. data/lib/karafka/web/ui/pro/views/consumers/_breadcrumbs.erb +7 -1
  98. data/lib/karafka/web/ui/pro/views/consumers/_consumer.erb +1 -1
  99. data/lib/karafka/web/ui/pro/views/consumers/_counters.erb +7 -5
  100. data/lib/karafka/web/ui/pro/views/consumers/consumer/_job.erb +3 -3
  101. data/lib/karafka/web/ui/pro/views/consumers/consumer/_metrics.erb +5 -4
  102. data/lib/karafka/web/ui/pro/views/consumers/consumer/_partition.erb +13 -4
  103. data/lib/karafka/web/ui/pro/views/consumers/consumer/_subscription_group.erb +3 -2
  104. data/lib/karafka/web/ui/pro/views/consumers/consumer/_tabs.erb +7 -0
  105. data/lib/karafka/web/ui/pro/views/consumers/details.erb +21 -0
  106. data/lib/karafka/web/ui/pro/views/consumers/index.erb +4 -2
  107. data/lib/karafka/web/ui/pro/views/dashboard/_ranges_selector.erb +39 -0
  108. data/lib/karafka/web/ui/pro/views/dashboard/index.erb +82 -0
  109. data/lib/karafka/web/ui/pro/views/dlq/_topic.erb +1 -1
  110. data/lib/karafka/web/ui/pro/views/errors/_breadcrumbs.erb +8 -6
  111. data/lib/karafka/web/ui/pro/views/errors/_error.erb +2 -2
  112. data/lib/karafka/web/ui/pro/views/errors/_partition_option.erb +1 -1
  113. data/lib/karafka/web/ui/pro/views/errors/_table.erb +21 -0
  114. data/lib/karafka/web/ui/pro/views/errors/_title_with_select.erb +31 -0
  115. data/lib/karafka/web/ui/pro/views/errors/index.erb +9 -56
  116. data/lib/karafka/web/ui/pro/views/errors/partition.erb +17 -0
  117. data/lib/karafka/web/ui/pro/views/errors/show.erb +1 -1
  118. data/lib/karafka/web/ui/pro/views/explorer/_breadcrumbs.erb +6 -4
  119. data/lib/karafka/web/ui/pro/views/explorer/_filtered.erb +16 -0
  120. data/lib/karafka/web/ui/pro/views/explorer/_message.erb +14 -4
  121. data/lib/karafka/web/ui/pro/views/explorer/_no_topics.erb +7 -0
  122. data/lib/karafka/web/ui/pro/views/explorer/_partition_option.erb +3 -3
  123. data/lib/karafka/web/ui/pro/views/explorer/_topic.erb +1 -1
  124. data/lib/karafka/web/ui/pro/views/explorer/index.erb +12 -8
  125. data/lib/karafka/web/ui/pro/views/explorer/messages/_headers.erb +15 -0
  126. data/lib/karafka/web/ui/pro/views/explorer/messages/_key.erb +12 -0
  127. data/lib/karafka/web/ui/pro/views/explorer/partition/_details.erb +35 -0
  128. data/lib/karafka/web/ui/pro/views/explorer/partition/_messages.erb +1 -0
  129. data/lib/karafka/web/ui/pro/views/explorer/partition.erb +6 -4
  130. data/lib/karafka/web/ui/pro/views/explorer/show.erb +48 -5
  131. data/lib/karafka/web/ui/pro/views/explorer/topic/_details.erb +23 -0
  132. data/lib/karafka/web/ui/pro/views/explorer/topic/_empty.erb +3 -0
  133. data/lib/karafka/web/ui/pro/views/explorer/topic/_limited.erb +4 -0
  134. data/lib/karafka/web/ui/pro/views/explorer/topic.erb +51 -0
  135. data/lib/karafka/web/ui/pro/views/health/_breadcrumbs.erb +16 -0
  136. data/lib/karafka/web/ui/pro/views/health/_no_data.erb +9 -0
  137. data/lib/karafka/web/ui/pro/views/health/_partition.erb +17 -15
  138. data/lib/karafka/web/ui/pro/views/health/_partition_offset.erb +40 -0
  139. data/lib/karafka/web/ui/pro/views/health/_tabs.erb +27 -0
  140. data/lib/karafka/web/ui/pro/views/health/offsets.erb +71 -0
  141. data/lib/karafka/web/ui/pro/views/health/overview.erb +68 -0
  142. data/lib/karafka/web/ui/pro/views/jobs/_job.erb +6 -3
  143. data/lib/karafka/web/ui/pro/views/jobs/index.erb +4 -1
  144. data/lib/karafka/web/ui/pro/views/routing/_consumer_group.erb +37 -0
  145. data/lib/karafka/web/ui/pro/views/routing/_detail.erb +25 -0
  146. data/lib/karafka/web/ui/pro/views/routing/_topic.erb +23 -0
  147. data/lib/karafka/web/ui/pro/views/routing/index.erb +10 -0
  148. data/lib/karafka/web/ui/pro/views/routing/show.erb +26 -0
  149. data/lib/karafka/web/ui/pro/views/shared/_navigation.erb +7 -10
  150. data/lib/karafka/web/ui/public/images/logo-gray.svg +28 -0
  151. data/lib/karafka/web/ui/public/javascripts/application.js +30 -0
  152. data/lib/karafka/web/ui/public/javascripts/chart.min.js +14 -0
  153. data/lib/karafka/web/ui/public/javascripts/charts.js +330 -0
  154. data/lib/karafka/web/ui/public/javascripts/datepicker.js +6 -0
  155. data/lib/karafka/web/ui/public/javascripts/live_poll.js +39 -12
  156. data/lib/karafka/web/ui/public/javascripts/offset_datetime.js +74 -0
  157. data/lib/karafka/web/ui/public/javascripts/tabs.js +59 -0
  158. data/lib/karafka/web/ui/public/stylesheets/application.css +11 -0
  159. data/lib/karafka/web/ui/public/stylesheets/datepicker.min.css +12 -0
  160. data/lib/karafka/web/ui/views/cluster/_no_partitions.erb +3 -0
  161. data/lib/karafka/web/ui/views/cluster/_partition.erb +20 -22
  162. data/lib/karafka/web/ui/views/cluster/index.erb +6 -1
  163. data/lib/karafka/web/ui/views/consumers/_consumer.erb +1 -1
  164. data/lib/karafka/web/ui/views/consumers/_counters.erb +6 -4
  165. data/lib/karafka/web/ui/views/consumers/_summary.erb +3 -3
  166. data/lib/karafka/web/ui/views/consumers/index.erb +3 -1
  167. data/lib/karafka/web/ui/views/dashboard/_feature_pro.erb +3 -0
  168. data/lib/karafka/web/ui/views/dashboard/_not_enough_data.erb +15 -0
  169. data/lib/karafka/web/ui/views/dashboard/_ranges_selector.erb +23 -0
  170. data/lib/karafka/web/ui/views/dashboard/index.erb +95 -0
  171. data/lib/karafka/web/ui/views/errors/_detail.erb +12 -0
  172. data/lib/karafka/web/ui/views/errors/_error.erb +2 -2
  173. data/lib/karafka/web/ui/views/errors/show.erb +1 -1
  174. data/lib/karafka/web/ui/views/jobs/index.erb +3 -1
  175. data/lib/karafka/web/ui/views/layout.erb +10 -3
  176. data/lib/karafka/web/ui/views/routing/_consumer_group.erb +8 -6
  177. data/lib/karafka/web/ui/views/routing/_detail.erb +2 -2
  178. data/lib/karafka/web/ui/views/routing/_topic.erb +1 -1
  179. data/lib/karafka/web/ui/views/routing/show.erb +1 -1
  180. data/lib/karafka/web/ui/views/shared/_brand.erb +2 -2
  181. data/lib/karafka/web/ui/views/shared/_chart.erb +14 -0
  182. data/lib/karafka/web/ui/views/shared/_content.erb +2 -2
  183. data/lib/karafka/web/ui/views/shared/_feature_pro.erb +1 -1
  184. data/lib/karafka/web/ui/views/shared/_flashes.erb +9 -0
  185. data/lib/karafka/web/ui/views/shared/_footer.erb +22 -0
  186. data/lib/karafka/web/ui/views/shared/_header.erb +15 -9
  187. data/lib/karafka/web/ui/views/shared/_live_poll.erb +7 -0
  188. data/lib/karafka/web/ui/views/shared/_navigation.erb +5 -8
  189. data/lib/karafka/web/ui/views/shared/_no_paginated_data.erb +9 -0
  190. data/lib/karafka/web/ui/views/shared/_pagination.erb +17 -13
  191. data/lib/karafka/web/ui/views/shared/_tab_nav.erb +7 -0
  192. data/lib/karafka/web/ui/views/shared/exceptions/not_found.erb +34 -32
  193. data/lib/karafka/web/ui/views/shared/exceptions/pro_only.erb +45 -43
  194. data/lib/karafka/web/ui/views/status/failures/_consumers_reports_schema_state.erb +15 -0
  195. data/lib/karafka/web/ui/views/status/failures/_enabled.erb +8 -0
  196. data/lib/karafka/web/ui/views/status/failures/_initial_consumers_metrics.erb +11 -0
  197. data/lib/karafka/web/ui/views/status/failures/{_initial_state.erb → _initial_consumers_state.erb} +3 -3
  198. data/lib/karafka/web/ui/views/status/failures/_partitions.erb +14 -6
  199. data/lib/karafka/web/ui/views/status/info/_components.erb +21 -1
  200. data/lib/karafka/web/ui/views/status/show.erb +62 -5
  201. data/lib/karafka/web/ui/views/status/successes/_enabled.erb +1 -0
  202. data/lib/karafka/web/ui/views/status/warnings/_replication.erb +19 -0
  203. data/lib/karafka/web/version.rb +1 -1
  204. data/lib/karafka/web.rb +11 -0
  205. data.tar.gz.sig +0 -0
  206. metadata +124 -39
  207. metadata.gz.sig +0 -0
  208. data/lib/karafka/web/processing/consumers/aggregator.rb +0 -130
  209. data/lib/karafka/web/tracking/contracts/base.rb +0 -34
  210. data/lib/karafka/web/ui/lib/paginate_array.rb +0 -38
  211. data/lib/karafka/web/ui/pro/views/explorer/_encryption_enabled.erb +0 -18
  212. data/lib/karafka/web/ui/pro/views/explorer/partition/_watermark_offsets.erb +0 -10
  213. data/lib/karafka/web/ui/pro/views/health/index.erb +0 -60
  214. /data/lib/karafka/web/ui/pro/views/explorer/{_detail.erb → messages/_detail.erb} +0 -0
@@ -23,6 +23,9 @@ module Karafka
23
23
 
24
24
  # Topic for storing states aggregated info
25
25
  setting :states, default: 'karafka_consumers_states'
26
+
27
+ # Topic for storing consumers historical metrics info
28
+ setting :metrics, default: 'karafka_consumers_metrics'
26
29
  end
27
30
  end
28
31
 
@@ -71,21 +74,42 @@ module Karafka
71
74
  # What should be the consumer group name for web consumer
72
75
  setting :consumer_group, default: 'karafka_web'
73
76
 
74
- # How often should we report the aggregated state
75
- setting :interval, default: 1_000
76
-
77
- setting :consumers do
78
- setting :aggregator, default: Processing::Consumers::Aggregator.new
79
- end
77
+ # How often should we report the aggregated state and metrics
78
+ # By default we flush the states twice as often as the data reporting.
79
+ # This will allow us to have closer to real-time reporting.
80
+ setting :interval, default: 2_500
80
81
  end
81
82
 
82
83
  setting :ui do
83
- # Should the payload be decrypted for the Pro Web UI. Default to `false` due to security
84
- # reasons
85
- setting :decrypt, default: false
84
+ # UI session settings
85
+ # Should be set per ENV.
86
+ setting :sessions do
87
+ # Cookie key name
88
+ setting :key, default: '_karafka_session'
89
+
90
+ # Secret for the session cookie
91
+ setting :secret, default: SecureRandom.hex(32)
92
+ end
93
+
94
+ # UI cache to improve performance of views that reuse states that are not often changed
95
+ setting :cache, default: Ui::Lib::TtlCache.new(60_000 * 5)
96
+
97
+ # Should we display internal topics of Kafka. The once starting with `__`
98
+ # By default we do not display them as they are not usable from regular users perspective
99
+ setting :show_internal_topics, default: false
86
100
 
87
101
  # How many elements should we display on pages that support pagination
88
102
  setting :per_page, default: 25
103
+
104
+ # Time beyond which the last stable offset freeze is considered a risk
105
+ # (unless same as high). This is used to show on the UI that there may be a hanging
106
+ # transaction that will cause given consumer group to halt processing and wait
107
+ setting :lso_threshold, default: 5 * 60 * 1_000
108
+
109
+ # Allows to manage visibility of payload, headers and message key in the UI
110
+ # In some cases you may want to limit what is being displayed due to the type of data you
111
+ # are dealing with
112
+ setting :visibility_filter, default: Ui::Models::VisibilityFilter.new
89
113
  end
90
114
  end
91
115
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ # Namespace for contracts across the web
6
+ module Contracts
7
+ # Base for all the contracts
8
+ class Base < ::Karafka::Core::Contractable::Contract
9
+ class << self
10
+ # This layer is not for users extensive feedback, thus we can easily use the minimum
11
+ # error messaging there is.
12
+ def configure
13
+ super do |config|
14
+ config.error_messages = YAML.safe_load(
15
+ File.read(
16
+ File.join(Karafka::Web.gem_root, 'config', 'locales', 'errors.yml')
17
+ )
18
+ ).fetch('en').fetch('validations').fetch('web')
19
+ end
20
+ end
21
+ end
22
+
23
+ # @param data [Hash] data for validation
24
+ # @return [Boolean] true if all good
25
+ # @raise [Errors::ContractError] invalid report
26
+ def validate!(data)
27
+ super(data, Errors::ContractError)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Contracts
6
+ # Contract to validate Web-UI configuration
7
+ class Config < Web::Contracts::Base
8
+ configure
9
+
10
+ # Use the same regexp as Karafka for topics validation
11
+ TOPIC_REGEXP = ::Karafka::Contracts::TOPIC_REGEXP
12
+
13
+ required(:ttl) { |val| val.is_a?(Numeric) && val.positive? }
14
+
15
+ nested(:topics) do
16
+ required(:errors) { |val| val.is_a?(String) && TOPIC_REGEXP.match?(val) }
17
+
18
+ nested(:consumers) do
19
+ required(:reports) { |val| val.is_a?(String) && TOPIC_REGEXP.match?(val) }
20
+ required(:states) { |val| val.is_a?(String) && TOPIC_REGEXP.match?(val) }
21
+ required(:metrics) { |val| val.is_a?(String) && TOPIC_REGEXP.match?(val) }
22
+ end
23
+ end
24
+
25
+ nested(:tracking) do
26
+ # Do not report more often then every second, this could overload the system
27
+ required(:interval) { |val| val.is_a?(Integer) && val >= 1_000 }
28
+
29
+ nested(:consumers) do
30
+ required(:reporter) { |val| !val.nil? }
31
+ required(:sampler) { |val| !val.nil? }
32
+ required(:listeners) { |val| val.is_a?(Array) }
33
+ end
34
+
35
+ nested(:producers) do
36
+ required(:reporter) { |val| !val.nil? }
37
+ required(:sampler) { |val| !val.nil? }
38
+ required(:listeners) { |val| val.is_a?(Array) }
39
+ end
40
+ end
41
+
42
+ nested(:processing) do
43
+ required(:active) { |val| [true, false].include?(val) }
44
+ required(:consumer_group) { |val| val.is_a?(String) && TOPIC_REGEXP.match?(val) }
45
+ # Do not update data more often not to overload and not to generate too much data
46
+ required(:interval) { |val| val.is_a?(Integer) && val >= 1_000 }
47
+ end
48
+
49
+ nested(:ui) do
50
+ nested(:sessions) do
51
+ required(:key) { |val| val.is_a?(String) && !val.empty? }
52
+ required(:secret) { |val| val.is_a?(String) && val.length >= 64 }
53
+ end
54
+
55
+ required(:show_internal_topics) { |val| [true, false].include?(val) }
56
+ required(:cache) { |val| !val.nil? }
57
+ required(:per_page) { |val| val.is_a?(Integer) && val >= 1 && val <= 100 }
58
+ required(:visibility_filter) { |val| !val.nil? }
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -6,12 +6,21 @@ module Karafka
6
6
  #
7
7
  # @note We use `symbolize_names` because we want to use the same convention of hash building
8
8
  # for producing, consuming and displaying metrics related data
9
+ #
10
+ # @note We have to check if we compress the data, because older Web-UI versions were not
11
+ # compressing the payload.
9
12
  class Deserializer
10
13
  # @param message [::Karafka::Messages::Message]
11
14
  # @return [Object] deserialized data
12
15
  def call(message)
16
+ raw_payload = if message.headers.key?('zlib')
17
+ Zlib::Inflate.inflate(message.raw_payload)
18
+ else
19
+ message.raw_payload
20
+ end
21
+
13
22
  ::JSON.parse(
14
- message.raw_payload,
23
+ raw_payload,
15
24
  symbolize_names: true
16
25
  )
17
26
  end
@@ -4,21 +4,43 @@ module Karafka
4
4
  module Web
5
5
  # Karafka::Web related errors
6
6
  module Errors
7
- # Tracking related errors
8
- module Tracking
9
- # Raised when the a report is not valid for any reason
10
- # This should never happen and if you see this, please open an issue.
11
- ContractError = Class.new(::Karafka::Errors::BaseError)
7
+ # Base class for all errors related to the web ui
8
+ BaseError = Class.new(::Karafka::Errors::BaseError)
9
+
10
+ # Raised when the a report is not valid for any reason
11
+ # This should never happen and if you see this, please open an issue.
12
+ ContractError = Class.new(BaseError)
13
+
14
+ # Processing related errors namespace
15
+ module Processing
16
+ # Raised when we try to process reports but we do not have the current state bootstrapped
17
+ # If you see this error, it probably means, that you did not bootstrap Web-UI correctly
18
+ MissingConsumersStateError = Class.new(BaseError)
19
+
20
+ # Similar to the above. It should be created during install
21
+ MissingConsumersMetricsError = Class.new(BaseError)
22
+
23
+ # This error occurs when consumer running older version of the web-ui tries to materialize
24
+ # states from newer versions. Karafka Web-UI provides only backwards compatibility, so
25
+ # you need to have an up-to-date consumer materializing reported states.
26
+ #
27
+ # If you see this error, please make sure that the consumer process that is materializing
28
+ # your states is running at least the same version as the consumers that are reporting
29
+ # the states
30
+ #
31
+ # If you see this error do not worry. When you get a consumer with up-to-date version,
32
+ # all the historical metrics will catch up.
33
+ IncompatibleSchemaError = Class.new(BaseError)
12
34
  end
13
35
 
14
36
  # Ui related errors
15
37
  module Ui
16
38
  # Raised when we cannot display a given view.
17
39
  # This may mean, that request topic was not present or partition or a message.
18
- NotFoundError = Class.new(::Karafka::Errors::BaseError)
40
+ NotFoundError = Class.new(BaseError)
19
41
 
20
42
  # Raised whe a given feature is available for Pro but not pro used
21
- ProOnlyError = Class.new(::Karafka::Errors::BaseError)
43
+ ProOnlyError = Class.new(BaseError)
22
44
  end
23
45
  end
24
46
  end
@@ -4,165 +4,75 @@ module Karafka
4
4
  module Web
5
5
  # Responsible for setup of the Web UI and Karafka Web-UI related components initialization.
6
6
  class Installer
7
+ include ::Karafka::Helpers::Colorize
8
+
7
9
  # Creates needed topics and the initial zero state, so even if no `karafka server` processes
8
- # are running, we can still display the empty UI
10
+ # are running, we can still display the empty UI. Also adds needed code to the `karafka.rb`
11
+ # file.
9
12
  #
10
13
  # @param replication_factor [Integer] replication factor we want to use (1 by default)
11
- def bootstrap!(replication_factor: 1)
12
- bootstrap_topics!(replication_factor)
13
- bootstrap_consumers_state!
14
- end
15
-
16
- # Removes all the Karafka topics and creates them again with the same replication factor
17
- def reset!
18
- states_topic = ::Karafka::Web.config.topics.consumers.states
19
- replication_factor = ::Karafka::Admin
20
- .cluster_info
21
- .topics
22
- .find { |topic| topic[:topic_name] == states_topic }
23
- .fetch(:partitions)
24
- .first
25
- .fetch(:replica_count)
26
-
27
- uninstall!
28
- bootstrap!(replication_factor: replication_factor)
14
+ def install(replication_factor: 1)
15
+ puts
16
+ puts 'Installing Karafka Web UI...'
17
+ puts
18
+ puts 'Creating necessary topics and populating state data...'
19
+ puts
20
+ Management::CreateTopics.new.call(replication_factor)
21
+ puts
22
+ Management::CreateInitialStates.new.call
23
+ puts
24
+ Management::ExtendBootFile.new.call
25
+ puts
26
+ puts("Installation #{green('completed')}. Have fun!")
27
+ puts
29
28
  end
30
29
 
31
- # Removes all the Karafka Web topics
32
- def uninstall!
33
- [
34
- ::Karafka::Web.config.topics.consumers.states,
35
- ::Karafka::Web.config.topics.consumers.reports,
36
- ::Karafka::Web.config.topics.errors
37
- ].each { |topic_name| ::Karafka::Admin.delete_topic(topic_name) }
30
+ # Creates missing topics and missing zero states. Needs to run for each environment where we
31
+ # want to use Web-UI
32
+ #
33
+ # @param replication_factor [Integer] replication factor we want to use (1 by default)
34
+ def migrate(replication_factor: 1)
35
+ puts
36
+ puts 'Creating necessary topics and populating state data...'
37
+ puts
38
+ Management::CreateTopics.new.call(replication_factor)
39
+ Management::CreateInitialStates.new.call
40
+ puts
41
+ puts("Migration #{green('completed')}. Have fun!")
42
+ puts
38
43
  end
39
44
 
40
- # Adds the extra needed consumer group, topics and routes for Web UI to be able to operate
41
- def enable!
42
- ::Karafka::App.routes.draw do
43
- web_deserializer = ::Karafka::Web::Deserializer.new
44
-
45
- consumer_group ::Karafka::Web.config.processing.consumer_group do
46
- # Topic we listen on to materialize the states
47
- topic ::Karafka::Web.config.topics.consumers.reports do
48
- config(active: false)
49
- active ::Karafka::Web.config.processing.active
50
- # Since we materialize state in intervals, we can poll for half of this time without
51
- # impacting the reporting responsiveness
52
- max_wait_time ::Karafka::Web.config.processing.interval / 2
53
- max_messages 1_000
54
- consumer ::Karafka::Web::Processing::Consumer
55
- deserializer web_deserializer
56
- manual_offset_management true
57
- end
58
-
59
- # We define those two here without consumption, so Web understands how to deserialize
60
- # 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.errors do
68
- config(active: false)
69
- active false
70
- deserializer web_deserializer
71
- end
72
- end
73
- end
74
-
75
- # Installs all the consumer related listeners
76
- ::Karafka::Web.config.tracking.consumers.listeners.each do |listener|
77
- ::Karafka.monitor.subscribe(listener)
78
- end
79
-
80
- # Installs all the producer related listeners
81
- ::Karafka::Web.config.tracking.producers.listeners.each do |listener|
82
- ::Karafka.producer.monitor.subscribe(listener)
83
- end
45
+ # Removes all the Karafka topics and creates them again with the same replication factor
46
+ # @param replication_factor [Integer] replication factor we want to use (1 by default)
47
+ def reset(replication_factor: 1)
48
+ puts
49
+ puts 'Resetting Karafka Web UI...'
50
+ puts
51
+ Management::DeleteTopics.new.call
52
+ puts
53
+ Management::CreateTopics.new.call(replication_factor)
54
+ puts
55
+ Management::CreateInitialStates.new.call
56
+ puts
57
+ puts("Resetting #{green('completed')}. Have fun!")
58
+ puts
84
59
  end
85
60
 
86
- private
87
-
88
- # Creates all the needed topics for the admin UI to work
89
- #
90
- # @param replication_factor [Integer]
91
- def bootstrap_topics!(replication_factor = 1)
92
- existing_topics = ::Karafka::Admin.cluster_info.topics.map { |topic| topic[:topic_name] }
93
-
94
- consumers_states_topic = ::Karafka::Web.config.topics.consumers.states
95
- consumers_reports_topic = ::Karafka::Web.config.topics.consumers.reports
96
- errors_topic = ::Karafka::Web.config.topics.errors
97
-
98
- # Create only if needed
99
- unless existing_topics.include?(consumers_states_topic)
100
- # This topic needs to have one partition
101
- ::Karafka::Admin.create_topic(
102
- consumers_states_topic,
103
- 1,
104
- replication_factor,
105
- # We care only about the most recent state, previous are irrelevant. So we can easily
106
- # compact after one minute. We do not use this beyond the most recent collective
107
- # state, hence it all can easily go away.
108
- {
109
- 'cleanup.policy': 'compact',
110
- 'retention.ms': 60 * 60 * 1_000
111
- }
112
- )
113
- end
114
-
115
- unless existing_topics.include?(consumers_reports_topic)
116
- # This topic needs to have one partition
117
- ::Karafka::Admin.create_topic(
118
- consumers_reports_topic,
119
- 1,
120
- replication_factor,
121
- # We do not need to to store this data for longer than 7 days as this data is only used
122
- # to materialize the end states
123
- # On the other hand we do not want to have it really short-living because in case of a
124
- # consumer crash, we may want to use this info to catch up and backfill the state
125
- { 'retention.ms': 7 * 24 * 60 * 60 * 1_000 }
126
- )
127
- end
128
-
129
- unless existing_topics.include?(errors_topic)
130
- # All the errors will be dispatched here
131
- # This topic can have multiple partitions but we go with one by default. A single Ruby
132
- # process should not crash that often and if there is an expectation of a higher volume
133
- # of errors, this can be changed by the end user
134
- ::Karafka::Admin.create_topic(
135
- errors_topic,
136
- 1,
137
- replication_factor,
138
- # Remove really old errors (older than 3 months just to preserve space)
139
- { 'retention.ms': 3 * 31 * 24 * 60 * 60 * 1_000 }
140
- )
141
- end
61
+ # Removes all the Karafka Web topics and cleans after itself.
62
+ def uninstall
63
+ puts
64
+ puts 'Uninstalling Karafka Web UI...'
65
+ puts
66
+ Management::DeleteTopics.new.call
67
+ Management::CleanBootFile.new.call
68
+ puts
69
+ puts("Uninstalling #{green('completed')}. Goodbye!")
70
+ puts
142
71
  end
143
72
 
144
- # Creates the initial state record with all values being empty
145
- def bootstrap_consumers_state!
146
- ::Karafka.producer.produce_sync(
147
- topic: Karafka::Web.config.topics.consumers.states,
148
- key: Karafka::Web.config.topics.consumers.states,
149
- payload: {
150
- processes: {},
151
- stats: {
152
- batches: 0,
153
- messages: 0,
154
- retries: 0,
155
- dead: 0,
156
- busy: 0,
157
- enqueued: 0,
158
- threads_count: 0,
159
- processes: 0,
160
- rss: 0,
161
- listeners_count: 0,
162
- utilization: 0
163
- }
164
- }.to_json
165
- )
73
+ # Enables the Web-UI in the karafka app. Sets up needed routes and listeners.
74
+ def enable!
75
+ Management::Enable.new.call
166
76
  end
167
77
  end
168
78
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ # Namespace for all the commands related to management of the Web-UI in the context of Karafka
6
+ # It includes things like installing, creating needed topics, etc.
7
+ module Management
8
+ # Base class for all the commands that we use to manage
9
+ class Base
10
+ include ::Karafka::Helpers::Colorize
11
+
12
+ private
13
+
14
+ # @return [String] green colored word "successfully"
15
+ def successfully
16
+ green('successfully')
17
+ end
18
+
19
+ # @return [String] green colored word "already"
20
+ def already
21
+ green('already')
22
+ end
23
+
24
+ # @return [Array<String>] topics available in the cluster
25
+ def existing_topics_names
26
+ @existing_topics_names ||= ::Karafka::Admin
27
+ .cluster_info
28
+ .topics
29
+ .map { |topic| topic[:topic_name] }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Management
6
+ # Cleans the boot file from Karafka Web-UI details.
7
+ class CleanBootFile < Base
8
+ # Web-UI enabled code
9
+ ENABLER_CODE = ExtendBootFile::ENABLER_CODE
10
+
11
+ private_constant :ENABLER_CODE
12
+
13
+ # Removes the Web-UI boot file data
14
+ def call
15
+ karafka_rb = File.readlines(Karafka.boot_file)
16
+
17
+ if karafka_rb.any? { |line| line.include?(ENABLER_CODE) }
18
+ puts 'Updating the Karafka boot file...'
19
+ karafka_rb.delete_if { |line| line.include?(ENABLER_CODE) }
20
+
21
+ File.write(Karafka.boot_file, karafka_rb.join)
22
+ puts "Karafka boot file #{successfully} updated."
23
+ puts 'Make sure to remove configuration and other customizations as well.'
24
+ else
25
+ puts 'Karafka Web UI components not found in the boot file.'
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ module Web
5
+ module Management
6
+ # Creates the records needed for the Web-UI to operate.
7
+ class CreateInitialStates < Base
8
+ # Defaults stats state that we create in Kafka
9
+ DEFAULT_STATS = {
10
+ batches: 0,
11
+ messages: 0,
12
+ retries: 0,
13
+ dead: 0,
14
+ busy: 0,
15
+ enqueued: 0,
16
+ processing: 0,
17
+ workers: 0,
18
+ processes: 0,
19
+ rss: 0,
20
+ listeners: 0,
21
+ utilization: 0,
22
+ lag_stored: 0,
23
+ errors: 0
24
+ }.freeze
25
+
26
+ # Default empty historicals for first record in Kafka
27
+ DEFAULT_AGGREGATED = Processing::TimeSeriesTracker::TIME_RANGES
28
+ .keys
29
+ .map { |range| [range, []] }
30
+ .to_h
31
+ .freeze
32
+
33
+ # WHole default empty state (aside from dispatch time)
34
+ DEFAULT_STATE = {
35
+ processes: {},
36
+ stats: DEFAULT_STATS,
37
+ schema_state: 'accepted',
38
+ schema_version: Processing::Consumers::Aggregators::State::SCHEMA_VERSION,
39
+ dispatched_at: Time.now.to_f
40
+ }.freeze
41
+
42
+ # Default metrics state
43
+ DEFAULT_METRICS = {
44
+ aggregated: DEFAULT_AGGREGATED,
45
+ consumer_groups: DEFAULT_AGGREGATED,
46
+ dispatched_at: Time.now.to_f,
47
+ schema_version: Processing::Consumers::Aggregators::Metrics::SCHEMA_VERSION
48
+ }.freeze
49
+
50
+ private_constant :DEFAULT_STATS, :DEFAULT_AGGREGATED
51
+
52
+ # Creates the initial states for the Web-UI if needed (if they don't exist)
53
+ def call
54
+ if Ui::Models::ConsumersState.current
55
+ exists('consumers state')
56
+ else
57
+ creating('consumers state')
58
+ ::Karafka.producer.produce_sync(
59
+ topic: Karafka::Web.config.topics.consumers.states,
60
+ key: Karafka::Web.config.topics.consumers.states,
61
+ payload: DEFAULT_STATE.to_json
62
+ )
63
+ created('consumers state')
64
+ end
65
+
66
+ if Ui::Models::ConsumersMetrics.current
67
+ exists('consumers metrics')
68
+ else
69
+ creating('consumers metrics')
70
+ ::Karafka.producer.produce_sync(
71
+ topic: Karafka::Web.config.topics.consumers.metrics,
72
+ key: Karafka::Web.config.topics.consumers.metrics,
73
+ payload: DEFAULT_METRICS.merge(dispatched_at: Time.now.to_f).to_json
74
+ )
75
+ created('consumers metrics')
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # @param type [String] type of state
82
+ # @return [String] exists message
83
+ def exists(type)
84
+ puts "Initial #{type} #{already} exists."
85
+ end
86
+
87
+ # @param type [String] type of state
88
+ # @return [String] message that the state is being created
89
+ def creating(type)
90
+ puts "Creating #{type} initial record..."
91
+ end
92
+
93
+ # @param type [String] type of state
94
+ # @return [String] message that the state was created
95
+ def created(type)
96
+ puts "Initial #{type} record #{successfully} created."
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end