Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Content improvements #70

Merged
merged 4 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Karafka Web changelog

## 0.6.1 (Unreleased)
- [Improvement] Include the karafka-web version in the status page tags.
- [Improvement] Report `karafka-web` version that is running in particular processes.
- [Improvement] Display `karafka-web` version in the per-process view.
- [Improvement] Report in the web-ui a scenario, where getting cluster info takes more than 500ms as a warning to make people realize, that operating with Kafka with extensive latencies is not recommended.
- [Improvement] Continue the status assessment flow on warnings.
- [Fix] Do not recommend running a server as a way to bootstrap the initial state.
- [Fix] Ensure in the report contract, that `karafka-core`, `karafka-web`, `rdkafka` and `librdkafka` are validated.

## 0.6.0 (2023-06-13)
- **[Feature]** Introduce producers errors tracking.
- [Improvement] Display the error origin as a badge to align with consumers view topic assignments.
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
karafka-web (0.6.0)
karafka-web (0.6.1)
erubi (~> 1.4)
karafka (>= 2.1.4, < 3.0.0)
karafka-core (>= 2.0.13, < 3.0.0)
Expand Down
10 changes: 7 additions & 3 deletions lib/karafka/web/tracking/consumers/contracts/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module Contracts
class Report < Tracking::Contracts::Base
configure

required(:schema_version) { |val| val.is_a?(String) }
required(:schema_version) { |val| val.is_a?(String) && !val.empty? }
required(:dispatched_at) { |val| val.is_a?(Numeric) && val.positive? }
# We have consumers and producer reports and need to ensure that each is handled
# in an expected fashion
Expand All @@ -24,7 +24,7 @@ class Report < Tracking::Contracts::Base
required(:memory_usage) { |val| val.is_a?(Integer) && val >= 0 }
required(:memory_total_usage) { |val| val.is_a?(Integer) && val >= 0 }
required(:memory_size) { |val| val.is_a?(Integer) && val >= 0 }
required(:status) { |val| ::Karafka::Status::STATES.key?(val.to_sym) }
required(:status) { |val| ::Karafka::Status::STATES.key?(val.to_s.to_sym) }
required(:listeners) { |val| val.is_a?(Integer) && val >= 0 }
required(:concurrency) { |val| val.is_a?(Integer) && val.positive? }
required(:tags) { |val| val.is_a?(Karafka::Core::Taggable::Tags) }
Expand All @@ -38,9 +38,13 @@ class Report < Tracking::Contracts::Base
end

nested(:versions) do
required(:ruby) { |val| val.is_a?(String) && !val.empty? }
required(:karafka) { |val| val.is_a?(String) && !val.empty? }
required(:karafka_core) { |val| val.is_a?(String) && !val.empty? }
required(:karafka_web) { |val| val.is_a?(String) && !val.empty? }
required(:waterdrop) { |val| val.is_a?(String) && !val.empty? }
required(:ruby) { |val| val.is_a?(String) && !val.empty? }
required(:rdkafka) { |val| val.is_a?(String) && !val.empty? }
required(:librdkafka) { |val| val.is_a?(String) && !val.empty? }
end

nested(:stats) do
Expand Down
3 changes: 2 additions & 1 deletion lib/karafka/web/tracking/consumers/sampler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ def to_report
versions: {
ruby: ruby_version,
karafka: karafka_version,
waterdrop: waterdrop_version,
karafka_core: karafka_core_version,
karafka_web: karafka_web_version,
waterdrop: waterdrop_version,
rdkafka: rdkafka_version,
librdkafka: librdkafka_version
},
Expand Down
5 changes: 5 additions & 0 deletions lib/karafka/web/tracking/sampler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ def karafka_version
::Karafka::VERSION
end

# @return [String] Karafka Web UI version
def karafka_web_version
::Karafka::Web::VERSION
end

# @return [String] Karafka::Core version
def karafka_core_version
::Karafka::Core::VERSION
Expand Down
38 changes: 31 additions & 7 deletions lib/karafka/web/ui/models/status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Web
module Ui
module Models
# Model that represents the general status of the Web UI.
#
# We use this data to display a status page that helps with debugging on what is missing
# in the overall setup of the Web UI.
#
Expand All @@ -15,7 +16,18 @@ class Status
Step = Struct.new(:status, :details) do
# @return [Boolean] is the given step successfully configured and working
def success?
status == :success
status == :success || status == :warning
end

# @return [String] local namespace for partial of a given type
def partial_namespace
case status
when :success then 'successes'
when :warning then 'warnings'
when :failure then 'failures'
else
raise ::Karafka::Errors::UnsupportedCaseError, status
end
end

# @return [String] stringified status
Expand All @@ -29,11 +41,21 @@ def initialize
connect
end

# @return [Status::Step] were we able to connect to Kafka or not
# @return [Status::Step] were we able to connect to Kafka or not and how fast.
# Some people try to work with Kafka over the internet with really high latency and this
# should be highlighted in the UI as often the connection just becomes unstable
def connection
level = if @connection_time < 1_000
:success
elsif @connection_time < 1_000_000
:warning
else
:failure
end

Step.new(
@connected ? :success : :failure,
nil
level,
{ time: @connection_time }
)
end

Expand Down Expand Up @@ -171,12 +193,14 @@ def topics_details
topics
end

# Tries connecting with the cluster and sets the connection state
# Tries connecting with the cluster and saves the cluster info and the connection time
# @note If fails, `connection_time` will be 1_000_000
def connect
started = Time.now.to_f
@cluster_info = ::Karafka::Admin.cluster_info
@connected = true
@connection_time = (Time.now.to_f - started) * 1_000
rescue ::Rdkafka::RdkafkaError
@connected = false
@connection_time = 1_000_000
end
end
end
Expand Down
7 changes: 6 additions & 1 deletion lib/karafka/web/ui/pro/views/consumers/consumer/_metrics.erb
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@
</span>

<span class="badge bg-secondary">
karafka core
karafka-web
<%= @process.karafka_web %>
</span>

<span class="badge bg-secondary">
karafka-core
<%= @process.karafka_core %>
</span>

Expand Down
11 changes: 1 addition & 10 deletions lib/karafka/web/ui/views/status/failures/_initial_state.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,5 @@
</p>

<p>
To fix this, you can either:
</p>

<ul>
<li>Run <code>bundle exec karafka-web install</code></li>
<li>Run at least one Karafka consumer process</li>
</ul>

<p class="mb-0">
Any of the above, when successful, will bootstrap the initial state.
To fix this, you need to ensure that the <code>bundle exec karafka-web install</code> runs successfully.
</p>
7 changes: 6 additions & 1 deletion lib/karafka/web/ui/views/status/info/_components.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
</span>

<span class="badge bg-secondary">
karafka core
karafka-web
<%= @sampler.karafka_web_version %>
</span>

<span class="badge bg-secondary">
karafka-core
<%= @sampler.karafka_core_version %>
</span>

Expand Down
7 changes: 6 additions & 1 deletion lib/karafka/web/ui/views/status/show.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
"status/#{@status.connection.to_s}",
locals: {
title: 'Connection to Kafka',
description: partial('status/failures/connection')
description: partial(
"status/#{@status.connection.partial_namespace}/connection",
locals: {
details: @status.connection.details
}
)
}
)
%>
Expand Down
1 change: 1 addition & 0 deletions lib/karafka/web/ui/views/status/successes/_connection.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%# Expected to be empty. No content on success needed. %>
11 changes: 11 additions & 0 deletions lib/karafka/web/ui/views/status/warnings/_connection.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<p>
High latency detected when connecting to the Kafka cluster from the Web-UI.
</p>

<p>
Kafka is not designed to perform optimally under high network latencies; even when configurations seem appropriate, you may still encounter disconnects and other unexpected issues.
</p>

<p class="mb-0">
It took Web-UI <strong><%= details[:time].round(2) %> ms</strong> to get the cluster info.
</p>
2 changes: 1 addition & 1 deletion lib/karafka/web/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
module Karafka
module Web
# Current gem version
VERSION = '0.6.0'
VERSION = '0.6.1'
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# frozen_string_literal: true

RSpec.describe_current do
subject(:contract) { described_class.new }

let(:consumer_group) do
{
id: 'example_app_karafka_web',
subscription_groups: {
'c81e728d9d4c_1' => {
id: 'c81e728d9d4c_1',
state: {
state: 'up',
join_state: 'steady',
stateage: 90_002,
rebalance_age: 90_000,
rebalance_cnt: 1,
rebalance_reason: 'Metadata for subscribed topic(s) has changed'
},
topics: {
'karafka_consumers_reports' => {
name: 'karafka_consumers_reports',
partitions: {
0 => {
lag_stored: 0,
lag_stored_d: 0,
committed_offset: 18,
stored_offset: 18,
fetch_state: 'active',
id: 0,
poll_state: 'active'
}
}
}
}
}
}
}
end

context 'when config is valid' do
it { expect(contract.call(consumer_group)).to be_success }
end

context 'when id is missing' do
before { consumer_group.delete(:id) }

it { expect(contract.call(consumer_group)).not_to be_success }
end

context 'when id is empty' do
before { consumer_group[:id] = '' }

it { expect(contract.call(consumer_group)).not_to be_success }
end

context 'when id is not a string' do
before { consumer_group[:id] = 123 }

it { expect(contract.call(consumer_group)).not_to be_success }
end

context 'when subscription_groups is missing' do
before { consumer_group.delete(:subscription_groups) }

it { expect(contract.call(consumer_group)).not_to be_success }
end

context 'when subscription_groups is not a hash' do
before { consumer_group[:subscription_groups] = 'not a hash' }

it { expect(contract.call(consumer_group)).not_to be_success }
end

context 'when subscription_group does not have an id' do
let(:expected_error) { Karafka::Web::Errors::Tracking::ContractError }

before { consumer_group[:subscription_groups]['c81e728d9d4c_1'].delete(:id) }

it { expect { contract.call(consumer_group) }.to raise_error(expected_error) }
end
end
Loading