Skip to content

Commit 894935f

Browse files
committed
Expose metrics per cluster & added graphs enabled feature
1 parent bc60ea5 commit 894935f

File tree

8 files changed

+92
-33
lines changed

8 files changed

+92
-33
lines changed

api/src/main/java/io/kafbat/ui/config/ClustersProperties.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public class ClustersProperties {
3737

3838
PollingProperties polling = new PollingProperties();
3939

40+
MetricsStorage defaultMetricsStorage = new MetricsStorage();
41+
4042
@Data
4143
public static class Cluster {
4244
@NotBlank(message = "field name for for cluster could not be blank")

api/src/main/java/io/kafbat/ui/controller/PrometheusExposeController.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import io.kafbat.ui.model.KafkaCluster;
55
import io.kafbat.ui.service.StatisticsCache;
66
import io.kafbat.ui.service.metrics.prometheus.PrometheusExpose;
7+
import java.util.Map;
8+
import java.util.Optional;
79
import java.util.stream.Collectors;
810
import lombok.RequiredArgsConstructor;
911
import org.springframework.http.ResponseEntity;
@@ -18,7 +20,7 @@ public class PrometheusExposeController extends AbstractController implements Pr
1820
private final StatisticsCache statisticsCache;
1921

2022
@Override
21-
public Mono<ResponseEntity<String>> getAllMetrics(ServerWebExchange exchange) {
23+
public Mono<ResponseEntity<String>> exposeAllMetrics(ServerWebExchange exchange) {
2224
return Mono.just(
2325
PrometheusExpose.exposeAllMetrics(
2426
clustersStorage.getKafkaClusters()
@@ -29,4 +31,17 @@ public Mono<ResponseEntity<String>> getAllMetrics(ServerWebExchange exchange) {
2931
);
3032
}
3133

34+
@Override
35+
public Mono<ResponseEntity<String>> exposeClusterMetrics(String clusterName,
36+
ServerWebExchange exchange) {
37+
Optional<KafkaCluster> cluster = clustersStorage.getClusterByName(clusterName);
38+
if (cluster.isPresent() && cluster.get().isExposeMetricsViaPrometheusEndpoint()) {
39+
return Mono.just(PrometheusExpose.exposeAllMetrics(
40+
Map.of(clusterName, statisticsCache.get(cluster.get()).getMetrics())
41+
));
42+
} else {
43+
return Mono.just(ResponseEntity.notFound().build());
44+
}
45+
}
46+
3247
}

api/src/main/java/io/kafbat/ui/model/ClusterFeature.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ public enum ClusterFeature {
77
TOPIC_DELETION,
88
KAFKA_ACL_VIEW,
99
KAFKA_ACL_EDIT,
10-
CLIENT_QUOTA_MANAGEMENT
10+
CLIENT_QUOTA_MANAGEMENT,
11+
GRAPHS_ENABLED
1112
}

api/src/main/java/io/kafbat/ui/service/FeatureService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ public Mono<List<ClusterFeature>> getAvailableFeatures(ReactiveAdminClient admin
3636
features.add(Mono.just(ClusterFeature.KSQL_DB));
3737
}
3838

39+
if (cluster.getPrometheusStorageClient() != null) {
40+
features.add(Mono.just(ClusterFeature.GRAPHS_ENABLED));
41+
}
42+
3943
if (cluster.getSchemaRegistryClient() != null) {
4044
features.add(Mono.just(ClusterFeature.SCHEMA_REGISTRY));
4145
}

api/src/main/java/io/kafbat/ui/service/KafkaClusterFactory.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,18 @@ public KafkaCluster create(ClustersProperties properties,
8787
if (ksqlConfigured(clusterProperties)) {
8888
builder.ksqlClient(ksqlClient(clusterProperties));
8989
}
90+
if (prometheusStorageConfigured(properties.getDefaultMetricsStorage())) {
91+
builder.prometheusStorageClient(
92+
prometheusStorageClient(properties.getDefaultMetricsStorage(), clusterProperties.getSsl())
93+
);
94+
}
9095
if (prometheusStorageConfigured(clusterProperties)) {
91-
builder.prometheusStorageClient(prometheusStorageClient(clusterProperties));
96+
builder.prometheusStorageClient(prometheusStorageClient(
97+
clusterProperties.getMetrics().getStore(),
98+
clusterProperties.getSsl())
99+
);
92100
}
101+
93102
builder.originalProperties(clusterProperties);
94103
return builder.build();
95104
}
@@ -129,7 +138,8 @@ public Mono<ClusterConfigValidationDTO> validate(ClustersProperties.Cluster clus
129138
: Mono.<Optional<Map<String, ApplicationPropertyValidationDTO>>>just(Optional.empty()),
130139

131140
prometheusStorageConfigured(clusterProperties)
132-
? validatePrometheusStore(() -> prometheusStorageClient(clusterProperties)).map(Optional::of)
141+
? validatePrometheusStore(() -> prometheusStorageClient(
142+
clusterProperties.getMetrics().getStore(), clusterProperties.getSsl())).map(Optional::of)
133143
: Mono.<Optional<ApplicationPropertyValidationDTO>>just(Optional.empty())
134144
).map(tuple -> {
135145
var validation = new ClusterConfigValidationDTO();
@@ -156,13 +166,14 @@ private Properties convertProperties(Map<String, Object> propertiesMap) {
156166
return properties;
157167
}
158168

159-
private ReactiveFailover<PrometheusClientApi> prometheusStorageClient(ClustersProperties.Cluster cluster) {
169+
private ReactiveFailover<PrometheusClientApi> prometheusStorageClient(
170+
ClustersProperties.MetricsStorage storage, ClustersProperties.TruststoreConfig ssl) {
160171
WebClient webClient = new WebClientConfigurator()
161-
.configureSsl(cluster.getSsl(), null)
172+
.configureSsl(ssl, null)
162173
.configureBufferSize(webClientMaxBuffSize)
163174
.build();
164175
return ReactiveFailover.create(
165-
parseUrlList(cluster.getMetrics().getStore().getPrometheus().getUrl()),
176+
parseUrlList(storage.getPrometheus().getUrl()),
166177
url -> new PrometheusClientApi(new io.kafbat.ui.prometheus.ApiClient(webClient).setBasePath(url)),
167178
ReactiveFailover.CONNECTION_REFUSED_EXCEPTION_FILTER,
168179
"No live schemaRegistry instances available",
@@ -173,6 +184,12 @@ private ReactiveFailover<PrometheusClientApi> prometheusStorageClient(ClustersPr
173184
private boolean prometheusStorageConfigured(ClustersProperties.Cluster cluster) {
174185
return Optional.ofNullable(cluster.getMetrics())
175186
.flatMap(m -> Optional.ofNullable(m.getStore()))
187+
.map(this::prometheusStorageConfigured)
188+
.orElse(false);
189+
}
190+
191+
private boolean prometheusStorageConfigured(ClustersProperties.MetricsStorage storage) {
192+
return Optional.ofNullable(storage)
176193
.flatMap(s -> Optional.ofNullable(s.getPrometheus()))
177194
.map(p -> StringUtils.hasText(p.getUrl()))
178195
.orElse(false);

api/src/main/java/io/kafbat/ui/service/metrics/scrape/jmx/JmxMetricsFormatter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
public class JmxMetricsFormatter {
1818

1919
// copied from https://github.com/prometheus/jmx_exporter/blob/b6b811b4aae994e812e902b26dd41f29364c0e2b/collector/src/main/java/io/prometheus/jmx/JmxMBeanPropertyCache.java#L15
20-
private static final Pattern PROPERTY_PATTERN = Pattern.compile( // NOSONAR
20+
private static final Pattern PROPERTY_PATTERN = Pattern.compile(// NOSONAR
2121
"([^,=:\\*\\?]+)=(\"(?:[^\\\\\"]*(?:\\\\.)?)*\"|[^,=:\"]*)" // NOSONAR
2222
);
2323

api/src/main/java/io/kafbat/ui/service/metrics/scrape/prometheus/PrometheusTextFormatParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class PrometheusTextFormatParser {
3838

3939
// Regex to capture metric name, optional labels, value, and optional timestamp.
4040
// Groups: 1=name, 2=labels (content), 3=value, 4=timestamp
41-
private static final Pattern METRIC_LINE_PATTERN = Pattern.compile( // NOSONAR
41+
private static final Pattern METRIC_LINE_PATTERN = Pattern.compile(// NOSONAR
4242
"^([a-zA-Z_:][a-zA-Z0-9_:]*)" // Metric name
4343
+ "(?:\\{([^}]*)})?" // Optional labels (content in group 2)
4444
+ "\\s+"

contract/src/main/resources/swagger/kafbat-ui-api.yaml

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,28 @@ paths:
205205
get:
206206
tags:
207207
- PrometheusExpose
208-
summary: getAllMetrics
209-
operationId: getAllMetrics
208+
summary: exposeAllMetrics
209+
operationId: exposeAllMetrics
210+
responses:
211+
200:
212+
description: OK
213+
content:
214+
application/text:
215+
schema:
216+
type: string
217+
218+
/metrics/{clusterName}:
219+
get:
220+
tags:
221+
- PrometheusExpose
222+
summary: exposeClusterMetrics
223+
operationId: exposeClusterMetrics
224+
parameters:
225+
- name: clusterName
226+
in: path
227+
required: true
228+
schema:
229+
type: string
210230
responses:
211231
200:
212232
description: OK
@@ -2540,6 +2560,7 @@ components:
25402560
- KAFKA_ACL_VIEW # get ACLs listing
25412561
- KAFKA_ACL_EDIT # create & delete ACLs
25422562
- CLIENT_QUOTA_MANAGEMENT
2563+
- GRAPHS_ENABLED
25432564
required:
25442565
- id
25452566
- name
@@ -4494,6 +4515,8 @@ components:
44944515
type: integer
44954516
internalTopicPrefix:
44964517
type: string
4518+
defaultMetricsStorage:
4519+
$ref: '#/components/schemas/ClusterMetricsStoreConfig'
44974520
clusters:
44984521
type: array
44994522
items:
@@ -4585,28 +4608,7 @@ components:
45854608
prometheusExpose:
45864609
type: boolean
45874610
store:
4588-
type: object
4589-
properties:
4590-
prometheus:
4591-
type: object
4592-
properties:
4593-
url:
4594-
type: string
4595-
remoteWrite:
4596-
type: boolean
4597-
pushGatewayUrl:
4598-
type: string
4599-
pushGatewayUsername:
4600-
type: string
4601-
pushGatewayPassword:
4602-
type: string
4603-
pushGatewayJobName:
4604-
type: string
4605-
kafka:
4606-
type: object
4607-
properties:
4608-
topic:
4609-
type: string
4611+
$ref: '#/components/schemas/ClusterMetricsStoreConfig'
46104612
properties:
46114613
type: object
46124614
additionalProperties: true
@@ -4688,3 +4690,21 @@ components:
46884690
type: object
46894691
additionalProperties:
46904692
type: string
4693+
ClusterMetricsStoreConfig:
4694+
type: object
4695+
properties:
4696+
prometheus:
4697+
type: object
4698+
properties:
4699+
url:
4700+
type: string
4701+
remoteWrite:
4702+
type: boolean
4703+
pushGatewayUrl:
4704+
type: string
4705+
pushGatewayUsername:
4706+
type: string
4707+
pushGatewayPassword:
4708+
type: string
4709+
pushGatewayJobName:
4710+
type: string

0 commit comments

Comments
 (0)