Skip to content

Commit 6a700c6

Browse files
committed
Improvements to the Grafana LGTM dashboards
1 parent 0742585 commit 6a700c6

File tree

23 files changed

+483
-26
lines changed

23 files changed

+483
-26
lines changed

extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/ContainerConstants.java

+8
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,12 @@ public final class ContainerConstants {
1515

1616
public static final String OTEL_GRPC_PROTOCOL = "grpc";
1717
public static final String OTEL_HTTP_PROTOCOL = "http/protobuf";
18+
19+
// Overrides
20+
21+
public static final int SCRAPING_INTERVAL = 10;
22+
public static final String OTEL_METRIC_EXPORT_INTERVAL = "10s";
23+
public static final String OTEL_BSP_SCHEDULE_DELAY = "3s";
24+
public static final String OTEL_BLRP_SCHEDULE_DELAY = "1s";
25+
1826
}

extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/ContainerConfigUtil.java

+36-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
import java.lang.reflect.Method;
44
import java.util.Arrays;
5+
import java.util.HashMap;
6+
import java.util.Map;
57
import java.util.Objects;
8+
import java.util.Optional;
69

710
public class ContainerConfigUtil {
811
/**
@@ -16,11 +19,7 @@ public static boolean isEqual(ContainerConfig cc1, ContainerConfig cc2) {
1619
return false;
1720
}
1821

19-
Class<?> i = Arrays.stream(c1.getInterfaces())
20-
.filter(ContainerConfig.class::isAssignableFrom)
21-
.findFirst()
22-
.orElseThrow(() -> new IllegalArgumentException("Missing ContainerConfig based interface"));
23-
Method[] methods = i.getMethods(); // should get all config methods
22+
Method[] methods = getMethods(c1);
2423
for (Method m : methods) {
2524
Object v1 = invoke(m, cc1);
2625
Object v2 = invoke(m, cc2);
@@ -31,6 +30,38 @@ public static boolean isEqual(ContainerConfig cc1, ContainerConfig cc2) {
3130
return true;
3231
}
3332

33+
/**
34+
* Get all properties to override from container config instance.
35+
*
36+
* @param config the container config
37+
* @return map of properties to override
38+
*/
39+
public static Map<String, Object> propertiesToOverride(ContainerConfig config) {
40+
Map<String, Object> map = new HashMap<>();
41+
for (Method m : getMethods(config.getClass())) {
42+
OverrideProperty override = m.getAnnotation(OverrideProperty.class);
43+
if (override != null) {
44+
String key = override.value();
45+
Object value = invoke(m, config);
46+
if (value instanceof Optional<?>) {
47+
Optional<?> optional = (Optional<?>) value;
48+
optional.ifPresent(o -> map.put(key, o));
49+
} else if (value != null) {
50+
map.put(key, value);
51+
}
52+
}
53+
}
54+
return map;
55+
}
56+
57+
private static Method[] getMethods(Class<?> c1) {
58+
Class<?> i = Arrays.stream(c1.getInterfaces())
59+
.filter(ContainerConfig.class::isAssignableFrom)
60+
.findFirst()
61+
.orElseThrow(() -> new IllegalArgumentException("Missing ContainerConfig based interface"));
62+
return i.getMethods();
63+
}
64+
3465
private static Object invoke(Method m, Object target) {
3566
try {
3667
return m.invoke(target);

extensions/observability-devservices/common/src/main/java/io/quarkus/observability/common/config/LgtmConfig.java

+36
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.Set;
55

66
import io.quarkus.observability.common.ContainerConstants;
7+
import io.quarkus.runtime.annotations.ConfigDocIgnore;
78
import io.quarkus.runtime.annotations.ConfigGroup;
89
import io.smallrye.config.WithDefault;
910

@@ -37,4 +38,39 @@ public interface LgtmConfig extends GrafanaConfig {
3738
*/
3839
@WithDefault(ContainerConstants.OTEL_HTTP_PROTOCOL)
3940
String otlpProtocol();
41+
42+
/**
43+
* The (Prometheus) scraping interval, in seconds.
44+
*/
45+
@WithDefault(ContainerConstants.SCRAPING_INTERVAL + "")
46+
int scrapingInterval();
47+
48+
/**
49+
* Do we force scraping.
50+
*/
51+
Optional<Boolean> forceScraping();
52+
53+
/**
54+
* A way to override `quarkus.otel.metric.export.interval` property's default value.
55+
*/
56+
@OverrideProperty("quarkus.otel.metric.export.interval")
57+
@WithDefault(ContainerConstants.OTEL_METRIC_EXPORT_INTERVAL)
58+
@ConfigDocIgnore
59+
String otelMetricExportInterval();
60+
61+
/**
62+
* A way to override `quarkus.otel.bsp.schedule.delay` property's default value.
63+
*/
64+
@OverrideProperty("quarkus.otel.bsp.schedule.delay")
65+
@WithDefault(ContainerConstants.OTEL_BSP_SCHEDULE_DELAY)
66+
@ConfigDocIgnore
67+
String otelBspScheduleDelay();
68+
69+
/**
70+
* A way to override `quarkus.otel.metric.export.interval` property's default value.
71+
*/
72+
@OverrideProperty("quarkus.otel.blrp.schedule.delay")
73+
@WithDefault(ContainerConstants.OTEL_BLRP_SCHEDULE_DELAY)
74+
@ConfigDocIgnore
75+
String otelBlrpScheduleDelay();
4076
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.quarkus.observability.common.config;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Override the property in the value,
10+
* with the value of the annotated method's return.
11+
*/
12+
@Retention(RetentionPolicy.RUNTIME)
13+
@Target({ ElementType.METHOD })
14+
public @interface OverrideProperty {
15+
/**
16+
* The property key to override.
17+
*
18+
* @return the property key
19+
*/
20+
String value();
21+
}

extensions/observability-devservices/deployment/src/main/java/io/quarkus/observability/deployment/ObservabilityDevServiceProcessor.java

+11
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import io.quarkus.deployment.builditem.DockerStatusBuildItem;
2929
import io.quarkus.deployment.builditem.FeatureBuildItem;
3030
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
31+
import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem;
3132
import io.quarkus.deployment.console.ConsoleInstalledBuildItem;
3233
import io.quarkus.deployment.console.StartupLogCompressor;
3334
import io.quarkus.deployment.dev.devservices.DevServicesConfig;
@@ -84,6 +85,7 @@ public void startContainers(LaunchModeBuildItem launchMode,
8485
LoggingSetupBuildItem loggingSetupBuildItem,
8586
DevServicesConfig devServicesConfig,
8687
BuildProducer<DevServicesResultBuildItem> services,
88+
BuildProducer<RunTimeConfigurationDefaultBuildItem> properties,
8789
Capabilities capabilities,
8890
Optional<MetricsCapabilityBuildItem> metricsConfiguration,
8991
BuildProducer<ObservabilityDevServicesConfigBuildItem> configBuildProducer) {
@@ -118,6 +120,8 @@ public void startContainers(LaunchModeBuildItem launchMode,
118120
ContainerConfig currentDevServicesConfiguration = dev.config(
119121
configuration,
120122
new ExtensionsCatalog(
123+
QuarkusClassLoader::isResourcePresentAtRuntime,
124+
QuarkusClassLoader::isClassPresentAtRuntime,
121125
capabilities.isPresent(Capability.OPENTELEMETRY_TRACER),
122126
hasMicrometerOtlp(metricsConfiguration)));
123127

@@ -140,6 +144,13 @@ public void startContainers(LaunchModeBuildItem launchMode,
140144
devServices.remove(devId); // clean-up
141145
capturedDevServicesConfigurations.put(devId, currentDevServicesConfiguration);
142146

147+
// override some OTel, etc defaults - rates, intervals, delays, ...
148+
Map<String, Object> propertiesToOverride = ContainerConfigUtil
149+
.propertiesToOverride(currentDevServicesConfiguration);
150+
propertiesToOverride
151+
.forEach((k, v) -> properties.produce(new RunTimeConfigurationDefaultBuildItem(k, v.toString())));
152+
log.infof("Dev Service %s properties override: %s", devId, propertiesToOverride);
153+
143154
StartupLogCompressor compressor = new StartupLogCompressor(
144155
(launchMode.isTest() ? "(test) " : "") + devId + " Dev Services Starting:",
145156
consoleInstalledBuildItem,

extensions/observability-devservices/testcontainers/src/main/java/io/quarkus/observability/testcontainers/LgtmContainer.java

+55-12
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
import io.quarkus.observability.common.ContainerConstants;
1616
import io.quarkus.observability.common.config.AbstractGrafanaConfig;
1717
import io.quarkus.observability.common.config.LgtmConfig;
18+
import io.quarkus.runtime.LaunchMode;
1819

1920
public class LgtmContainer extends GrafanaContainer<LgtmContainer, LgtmConfig> {
2021
protected static final String LGTM_NETWORK_ALIAS = "ltgm.testcontainer.docker";
2122

22-
protected static final String PROMETHEUS_CONFIG = """
23+
protected static final String PROMETHEUS_CONFIG_DEFAULT = """
2324
---
2425
otlp:
2526
# Recommended attributes to be promoted to labels.
@@ -47,12 +48,15 @@ public class LgtmContainer extends GrafanaContainer<LgtmContainer, LgtmConfig> {
4748
# A 10min time window is enough because it can easily absorb retries and network delays.
4849
out_of_order_time_window: 10m
4950
global:
50-
scrape_interval: 5s
51+
scrape_interval: %s
5152
evaluation_interval: 5s
53+
""";
54+
55+
protected static final String PROMETHEUS_CONFIG_SCRAPE = """
5256
scrape_configs:
5357
- job_name: '%s'
5458
metrics_path: '%s%s'
55-
scrape_interval: 5s
59+
scrape_interval: %s
5660
static_configs:
5761
- targets: ['%s:%d']
5862
""";
@@ -83,12 +87,16 @@ public class LgtmContainer extends GrafanaContainer<LgtmContainer, LgtmConfig> {
8387
foldersFromFilesStructure: false
8488
""";
8589

86-
public LgtmContainer() {
87-
this(new LgtmConfigImpl());
90+
private final boolean scrapingRequired;
91+
92+
public LgtmContainer(boolean scrapingRequired) {
93+
this(new LgtmConfigImpl(), scrapingRequired);
8894
}
8995

90-
public LgtmContainer(LgtmConfig config) {
96+
public LgtmContainer(LgtmConfig config, boolean scrapingRequired) {
9197
super(config);
98+
// do we require scraping
99+
this.scrapingRequired = scrapingRequired;
92100
// always expose both -- since the LGTM image already does that as well
93101
addExposedPorts(ContainerConstants.OTEL_GRPC_EXPORTER_PORT, ContainerConstants.OTEL_HTTP_EXPORTER_PORT);
94102

@@ -157,12 +165,22 @@ public static int getPrivateOtlpPort(String otlpProtocol) {
157165
}
158166

159167
private String getPrometheusConfig() {
160-
Config runtimeConfig = ConfigProvider.getConfig();
161-
String rootPath = runtimeConfig.getOptionalValue("quarkus.management.root-path", String.class).orElse("/q");
162-
String metricsPath = runtimeConfig.getOptionalValue("quarkus.management.metrics.path", String.class).orElse("/metrics");
163-
int httpPort = runtimeConfig.getOptionalValue("quarkus.http.port", Integer.class).orElse(8080); // when not set use default
164-
165-
return String.format(PROMETHEUS_CONFIG, config.serviceName(), rootPath, metricsPath, "host.docker.internal", httpPort);
168+
String scraping = config.scrapingInterval() + "s";
169+
String prometheusConfig = String.format(PROMETHEUS_CONFIG_DEFAULT, scraping);
170+
if (config.forceScraping().orElse(scrapingRequired)) {
171+
boolean isTest = LaunchMode.current() == LaunchMode.TEST;
172+
Config runtimeConfig = ConfigProvider.getConfig();
173+
String rootPath = runtimeConfig.getOptionalValue("quarkus.management.root-path", String.class).orElse("/q");
174+
String metricsPath = runtimeConfig.getOptionalValue("quarkus.management.metrics.path", String.class)
175+
.orElse("/metrics");
176+
String httpPortKey = isTest ? "quarkus.http.test-port" : "quarkus.http.port";
177+
Optional<Integer> optionalValue = runtimeConfig.getOptionalValue(httpPortKey, Integer.class);
178+
int httpPort = optionalValue.orElse(isTest ? 8081 : 8080); // when not set use default
179+
180+
prometheusConfig += String.format(PROMETHEUS_CONFIG_SCRAPE, config.serviceName(), rootPath, metricsPath, scraping,
181+
"host.docker.internal", httpPort);
182+
}
183+
return prometheusConfig;
166184
}
167185

168186
protected static class LgtmConfigImpl extends AbstractGrafanaConfig implements LgtmConfig {
@@ -183,6 +201,31 @@ public Optional<Set<String>> networkAliases() {
183201
public String otlpProtocol() {
184202
return ContainerConstants.OTEL_HTTP_PROTOCOL;
185203
}
204+
205+
@Override
206+
public int scrapingInterval() {
207+
return ContainerConstants.SCRAPING_INTERVAL;
208+
}
209+
210+
@Override
211+
public Optional<Boolean> forceScraping() {
212+
return Optional.empty();
213+
}
214+
215+
@Override
216+
public String otelMetricExportInterval() {
217+
return ContainerConstants.OTEL_METRIC_EXPORT_INTERVAL;
218+
}
219+
220+
@Override
221+
public String otelBspScheduleDelay() {
222+
return ContainerConstants.OTEL_BSP_SCHEDULE_DELAY;
223+
}
224+
225+
@Override
226+
public String otelBlrpScheduleDelay() {
227+
return ContainerConstants.OTEL_BLRP_SCHEDULE_DELAY;
228+
}
186229
}
187230

188231
protected static class LgtmLoggingFilter implements Predicate<OutputFrame> {
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package io.quarkus.observability.devresource;
22

3+
import java.util.function.Function;
4+
35
/**
46
* Relevant Observability extensions present.
57
*/
6-
public record ExtensionsCatalog(boolean hasOpenTelemetry,
8+
public record ExtensionsCatalog(
9+
Function<String, Boolean> resourceChecker,
10+
Function<String, Boolean> classChecker,
11+
boolean hasOpenTelemetry,
712
boolean hasMicrometerOtlp) {
813
}

extensions/observability-devservices/testlibs/devresource-lgtm/src/main/java/io/quarkus/observability/devresource/lgtm/LgtmResource.java

+41-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
import java.util.HashMap;
44
import java.util.Map;
5+
import java.util.Set;
6+
import java.util.function.Function;
7+
8+
import org.jboss.logging.Logger;
59

610
import io.quarkus.observability.common.ContainerConstants;
711
import io.quarkus.observability.common.config.LgtmConfig;
@@ -14,6 +18,22 @@
1418

1519
public class LgtmResource extends ContainerResource<LgtmContainer, LgtmConfig> {
1620

21+
private static final Logger log = Logger.getLogger(LgtmResource.class.getName());
22+
23+
protected static final Set<String> SCRAPING_REGISTRIES = Set.of(
24+
"io.micrometer.prometheus.PrometheusMeterRegistry");
25+
26+
protected static final Function<String, Boolean> TCCL_FN = s -> {
27+
ClassLoader cl = Thread.currentThread().getContextClassLoader();
28+
try {
29+
cl.loadClass(s);
30+
return true;
31+
} catch (Exception e) {
32+
// any exception
33+
return false;
34+
}
35+
};
36+
1737
private ExtensionsCatalog catalog;
1838
private LgtmConfig config;
1939

@@ -32,7 +52,26 @@ public LgtmConfig config(ModulesConfiguration configuration, ExtensionsCatalog c
3252

3353
@Override
3454
public Container<LgtmConfig> container(LgtmConfig config, ModulesConfiguration root) {
35-
return set(new LgtmContainer(config));
55+
return set(new LgtmContainer(config, isScrapingRequired(catalog.classChecker())));
56+
}
57+
58+
private boolean isScrapingRequired(Function<String, Boolean> checker) {
59+
boolean result = false;
60+
String foundRegistry = null;
61+
for (String clazz : SCRAPING_REGISTRIES) {
62+
if (checker.apply(clazz)) {
63+
foundRegistry = clazz;
64+
result = true;
65+
break;
66+
}
67+
}
68+
69+
if (result && (catalog != null && catalog.hasMicrometerOtlp())) {
70+
log.warnf("Multiple Micrometer registries found - OTLP and %s, no Prometheus scrapping required.", foundRegistry);
71+
return false;
72+
}
73+
74+
return result;
3675
}
3776

3877
private int getPrivateOtlpPort() {
@@ -86,7 +125,7 @@ public Map<String, String> config(int privatePort, String host, int publicPort)
86125

87126
@Override
88127
protected LgtmContainer defaultContainer() {
89-
return new LgtmContainer();
128+
return new LgtmContainer(isScrapingRequired(TCCL_FN)); // best we can do?
90129
}
91130

92131
@Override

0 commit comments

Comments
 (0)