Skip to content

Commit 4aad62c

Browse files
authored
Support for custom ports for MP Health and MP Metrics. (helidon-io#570)
* Support for custom ports for MP Health and MP Metrics. Signed-off-by: Tomas Langer <tomas.langer@oracle.com> * Copyright added. Signed-off-by: Tomas Langer <tomas.langer@oracle.com> * Test and copyright fix. Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
1 parent a8496b8 commit 4aad62c

19 files changed

Lines changed: 602 additions & 39 deletions

File tree

health/health/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,11 @@
6868
<artifactId>junit-jupiter-params</artifactId>
6969
<scope>test</scope>
7070
</dependency>
71+
<dependency>
72+
<groupId>io.helidon.config</groupId>
73+
<artifactId>helidon-config-yaml</artifactId>
74+
<version>${project.version}</version>
75+
<scope>test</scope>
76+
</dependency>
7177
</dependencies>
7278
</project>

health/health/src/main/java/io/helidon/health/HealthSupport.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ public static Builder builder() {
168168
*
169169
* @return health support configured with no health checks
170170
*/
171-
public static HealthSupport create(){
171+
public static HealthSupport create() {
172172
return builder().build();
173173
}
174174

@@ -177,7 +177,7 @@ public static HealthSupport create(){
177177
* The endpoint will always return {@code UP}.
178178
*
179179
* @param config configuration of this health check, used only to get {@code web-context} property to configure
180-
* {@link Builder#webContext(String)}
180+
* {@link Builder#webContext(String)}
181181
* @return health support configured with no health checks
182182
*/
183183
public static HealthSupport create(Config config) {
@@ -208,7 +208,11 @@ public HealthSupport build() {
208208
* @return updated builder instance
209209
*/
210210
public Builder webContext(String path) {
211-
this.webContext = path;
211+
if (path.startsWith("/")) {
212+
this.webContext = path;
213+
} else {
214+
this.webContext = "/" + path;
215+
}
212216
return this;
213217
}
214218

@@ -238,7 +242,6 @@ public Builder add(Collection<HealthCheck> healthChecks) {
238242
return this;
239243
}
240244

241-
242245
/**
243246
* Add a health check to a white list (in case {@link #includeAll} is set to {@code false}.
244247
*

metrics/metrics/src/main/java/io/helidon/metrics/BaseRegistry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
* {@code helidon.metrics.base.${metric_name}.enabled=false}
5151
*/
5252
final class BaseRegistry extends Registry {
53-
private static final String CONFIG_METRIC_ENABLED_BASE = "helidon.metrics.base.";
53+
private static final String CONFIG_METRIC_ENABLED_BASE = "base.";
5454
private static final Metadata MEMORY_USED_HEAP = new Metadata("memory.usedHeap",
5555
"Used Heap Memory",
5656
"Displays the amount of used heap memory in bytes.",

metrics/metrics/src/main/java/io/helidon/metrics/HelidonMetric.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,12 @@
2525
* All metrics should inherit from {@link MetricImpl}.
2626
*/
2727
interface HelidonMetric extends Metric {
28+
/**
29+
* Name of this metric.
30+
*
31+
* @return metric name
32+
*/
33+
String getName();
2834
/**
2935
* Add this metrics data to the JSON builder.
3036
*

metrics/metrics/src/main/java/io/helidon/metrics/MetricsSupport.java

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.helidon.metrics;
1818

1919
import java.util.Collections;
20+
import java.util.Comparator;
2021
import java.util.Objects;
2122
import java.util.Optional;
2223
import java.util.function.Function;
@@ -130,7 +131,7 @@ public static Builder builder() {
130131
*/
131132
static boolean requestsJsonData(RequestHeaders headers) {
132133
Optional<MediaType> mediaType = headers.bestAccepted(MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON);
133-
boolean requestsJson = mediaType.isPresent() && mediaType.get().equals(MediaType.APPLICATION_JSON);
134+
boolean requestsJson = mediaType.isPresent() && mediaType.get().equals(MediaType.APPLICATION_JSON);
134135

135136
if (LOGGER.isLoggable(Level.FINE)) {
136137
LOGGER.fine("Generating metrics for media type " + mediaType
@@ -184,6 +185,7 @@ static String toPrometheusData(Registry registry) {
184185
StringBuilder result = new StringBuilder();
185186

186187
registry.stream()
188+
.sorted(Comparator.comparing(HelidonMetric::getName))
187189
.forEach(mpMetric -> result.append(mpMetric.prometheusData()));
188190

189191
return result.toString();
@@ -209,7 +211,7 @@ private static String checkMetricTypeThenRun(Metric metric, Function<HelidonMetr
209211

210212
if (!(metric instanceof HelidonMetric)) {
211213
throw new IllegalArgumentException(String.format(
212-
"Metric of type %s is expected to implement %s but does not",
214+
"Metric of type %s is expected to implement %s but does not",
213215
metric.getClass().getName(),
214216
HelidonMetric.class.getName()));
215217
}
@@ -230,6 +232,7 @@ static JsonObject toJsonData(Registry... registries) {
230232
static JsonObject toJsonData(Registry registry) {
231233
JsonObjectBuilder builder = JSON.createObjectBuilder();
232234
registry.stream()
235+
.sorted(Comparator.comparing(HelidonMetric::getName))
233236
.forEach(mpMetric -> mpMetric.jsonData(builder));
234237
return builder.build();
235238
}
@@ -247,34 +250,61 @@ static JsonObject toJsonMeta(Registry... registries) {
247250
static JsonObject toJsonMeta(Registry registry) {
248251
JsonObjectBuilder builder = JSON.createObjectBuilder();
249252
registry.stream()
253+
.sorted(Comparator.comparing(HelidonMetric::getName))
250254
.forEach(mpMetric -> mpMetric.jsonMeta(builder));
251255
return builder.build();
252256
}
253257

254-
@Override
255-
public void update(Routing.Rules rules) {
256-
Registry base = rf.getARegistry(MetricRegistry.Type.BASE);
257-
Registry app = rf.getARegistry(MetricRegistry.Type.APPLICATION);
258-
Registry vendor = rf.getARegistry(MetricRegistry.Type.VENDOR);
258+
/**
259+
* Configure vendor metrics on the provided routing.
260+
* This method is exclusive to {@link #update(io.helidon.webserver.Routing.Rules)}
261+
* (e.g. you should not use both, as otherwise you would duplicate the metrics)
262+
*
263+
* @param routingName name of the routing (may be null)
264+
* @param rules routing builder or routing rules
265+
*/
266+
public void configureVendorMetrics(String routingName,
267+
Routing.Rules rules) {
268+
String metricPrefix = (null == routingName ? "" : routingName + ".") + "requests.";
259269

260-
Counter totalCount = vendor.counter(new Metadata("requests.count",
270+
Registry vendor = rf.getARegistry(MetricRegistry.Type.VENDOR);
271+
Counter totalCount = vendor.counter(new Metadata(metricPrefix + "count",
261272
"Total number of requests",
262273
"Each request (regardless of HTTP method) will increase this counter",
263274
MetricType.COUNTER,
264275
MetricUnits.NONE));
265276

266-
Meter totalMeter = vendor.meter(new Metadata("requests.meter",
277+
Meter totalMeter = vendor.meter(new Metadata(metricPrefix + "meter",
267278
"Meter for overall requests",
268279
"Each request will mark the meter to see overall throughput",
269280
MetricType.METERED,
270281
MetricUnits.NONE));
271282

283+
rules.any((req, res) -> {
284+
totalCount.inc();
285+
totalMeter.mark();
286+
req.next();
287+
});
288+
}
289+
290+
/**
291+
* Configure metrics endpoint on the provided routing rules.
292+
* This method just adds the endpoint {@code /metrics} (or appropriate
293+
* one as configured).
294+
* For simple routings, just register {@code MetricsSupport} instance.
295+
* This method is exclusive to {@link #update(io.helidon.webserver.Routing.Rules)}
296+
* (e.g. you should not use both, as otherwise you would register the endpoint twice)
297+
*
298+
* @param rules routing rules (also accepts {@link io.helidon.webserver.Routing.Builder}
299+
*/
300+
public void configureEndpoint(Routing.Rules rules) {
301+
Registry base = rf.getARegistry(MetricRegistry.Type.BASE);
302+
Registry vendor = rf.getARegistry(MetricRegistry.Type.VENDOR);
303+
Registry app = rf.getARegistry(MetricRegistry.Type.APPLICATION);
272304
// register the metric registry and factory to be available to all
273305
rules.any((req, res) -> {
274306
req.context().register(app);
275307
req.context().register(rf);
276-
totalCount.inc();
277-
totalMeter.mark();
278308
req.next();
279309
});
280310

@@ -297,6 +327,23 @@ public void update(Routing.Rules rules) {
297327
});
298328
}
299329

330+
/**
331+
* Method invoked by the web server to update routing rules.
332+
* Register this instance with webserver through
333+
* {@link io.helidon.webserver.Routing.Builder#register(io.helidon.webserver.Service...)}
334+
* rather than calling this method directly.
335+
* If multiple sockets (and routings) should be supported, you can use
336+
* the {@link #configureEndpoint(io.helidon.webserver.Routing.Rules)}, and
337+
* {@link #configureVendorMetrics(String, io.helidon.webserver.Routing.Rules)} methods.
338+
*
339+
* @param rules a routing rules to update
340+
*/
341+
@Override
342+
public void update(Routing.Rules rules) {
343+
configureVendorMetrics(null, rules);
344+
configureEndpoint(rules);
345+
}
346+
300347
private void getOne(ServerRequest req, ServerResponse res, Registry registry) {
301348
String metricName = req.path().param("metric");
302349

@@ -380,7 +427,11 @@ public MetricsSupport build() {
380427
*/
381428
public Builder config(Config config) {
382429
this.config = config;
383-
config.get("helidon.metrics.context").asString().ifPresent(this::context);
430+
// align with health checks
431+
config.get("web-context").asString().ifPresent(this::context);
432+
// backward compatibility
433+
config.get("context").asString().ifPresent(this::context);
434+
384435

385436
return this;
386437
}
@@ -407,9 +458,25 @@ public Builder registryFactory(RegistryFactory factory) {
407458
*
408459
* @param newContext context to use
409460
* @return updated builder instance
461+
* @deprecated use {@link #webContext(String)} instead, aligned with API of heatlh checks
410462
*/
463+
@Deprecated
411464
public Builder context(String newContext) {
412-
this.context = newContext;
465+
return webContext(newContext);
466+
}
467+
468+
/**
469+
* Set a new root context for REST API of metrics.
470+
*
471+
* @param path context to use
472+
* @return updated builder instance
473+
*/
474+
public Builder webContext(String path) {
475+
if (path.startsWith("/")) {
476+
this.context = path;
477+
} else {
478+
this.context = "/" + path;
479+
}
413480
return this;
414481
}
415482
}

metrics/metrics/src/test/java/io/helidon/metrics/RegistryFactoryTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ static void createInstance() {
5555
unconfigured = RegistryFactory.create();
5656
Config config = Config.builder()
5757
.sources(ConfigSources.create(CollectionsHelper.mapOf(
58-
"helidon.metrics.base." + METRIC_USED_HEAP + ".enabled",
58+
"base." + METRIC_USED_HEAP + ".enabled",
5959
"false")))
6060
.build();
6161
configured = RegistryFactory.create(config);

microprofile/health/src/main/java/io/helidon/microprofile/health/HealthMpService.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,7 +17,9 @@
1717
package io.helidon.microprofile.health;
1818

1919
import java.lang.annotation.Annotation;
20+
import java.util.Optional;
2021

22+
import io.helidon.config.Config;
2123
import io.helidon.health.HealthSupport;
2224
import io.helidon.microprofile.server.spi.MpService;
2325
import io.helidon.microprofile.server.spi.MpServiceContext;
@@ -31,9 +33,9 @@
3133
public class HealthMpService implements MpService {
3234
@Override
3335
public void configure(MpServiceContext mpServiceContext) {
34-
36+
Config healthConfig = mpServiceContext.helidonConfig().get("health");
3537
HealthSupport.Builder builder = HealthSupport.builder()
36-
.config(mpServiceContext.helidonConfig().get("helidon.health"));
38+
.config(healthConfig);
3739

3840
mpServiceContext.cdiContainer()
3941
.select(HealthCheck.class, new Health() {
@@ -45,7 +47,22 @@ public Class<? extends Annotation> annotationType() {
4547
.stream()
4648
.forEach(builder::add);
4749

48-
mpServiceContext.serverRoutingBuilder()
50+
healthConfig.get("routing")
51+
.asString()
52+
.flatMap(routeName -> {
53+
// support for overriding the routing back to default port using config
54+
if ("@default".equals(routeName)) {
55+
return Optional.empty();
56+
} else {
57+
return Optional.of(routeName);
58+
}
59+
})
60+
// use named routing
61+
.map(mpServiceContext::serverNamedRoutingBuilder)
62+
// use default server routing
63+
.orElseGet(mpServiceContext::serverRoutingBuilder)
64+
// register health support
4965
.register(builder.build());
66+
5067
}
5168
}

microprofile/metrics/src/main/java/io/helidon/microprofile/metrics/MetricsMpService.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,13 +16,17 @@
1616

1717
package io.helidon.microprofile.metrics;
1818

19+
import java.util.HashSet;
20+
import java.util.Set;
21+
22+
import io.helidon.common.CollectionsHelper;
23+
import io.helidon.config.Config;
24+
import io.helidon.config.ConfigValue;
1925
import io.helidon.metrics.MetricsSupport;
2026
import io.helidon.metrics.RegistryFactory;
21-
import io.helidon.microprofile.config.MpConfig;
2227
import io.helidon.microprofile.server.spi.MpService;
2328
import io.helidon.microprofile.server.spi.MpServiceContext;
24-
25-
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
29+
import io.helidon.webserver.Routing;
2630

2731
/**
2832
* Extension of microprofile {@link io.helidon.microprofile.server.Server} to enable support for metrics
@@ -36,9 +40,38 @@
3640
public class MetricsMpService implements MpService {
3741
@Override
3842
public void configure(MpServiceContext serviceContext) {
39-
MpConfig config = (MpConfig) ConfigProviderResolver.instance().getConfig();
43+
Set<String> vendorMetricsAdded = new HashSet<>();
44+
45+
Config metricsConfig = serviceContext.helidonConfig().get("metrics");
46+
47+
MetricsSupport metricsSupport = MetricsSupport.create(metricsConfig);
48+
49+
ConfigValue<String> routingNameConfig = metricsConfig.get("routing").asString();
50+
Routing.Builder defaultRouting = serviceContext.serverRoutingBuilder();
51+
52+
Routing.Builder endpointRouting = defaultRouting;
53+
54+
if (routingNameConfig.isPresent()) {
55+
String routingName = routingNameConfig.get();
56+
// support for overriding this back to default routing using config
57+
if (!"@default".equals(routingName)) {
58+
endpointRouting = serviceContext.serverNamedRoutingBuilder(routingName);
59+
}
60+
}
61+
62+
metricsSupport.configureVendorMetrics(null, defaultRouting);
63+
vendorMetricsAdded.add("@default");
64+
metricsSupport.configureEndpoint(endpointRouting);
4065

41-
MetricsSupport.create(config.helidonConfig())
42-
.update(serviceContext.serverRoutingBuilder());
66+
// now we may have additional sockets we want to add vendor metrics to
67+
metricsConfig.get("vendor-metrics-routings")
68+
.asList(String.class)
69+
.orElseGet(CollectionsHelper::listOf)
70+
.forEach(routeName -> {
71+
if (!vendorMetricsAdded.contains(routeName)) {
72+
metricsSupport.configureVendorMetrics(routeName, serviceContext.serverNamedRoutingBuilder(routeName));
73+
vendorMetricsAdded.add(routeName);
74+
}
75+
});
4376
}
4477
}

0 commit comments

Comments
 (0)