From eb5b9ad2da59da77a062669ff00b8957420e1a0a Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 14 Oct 2024 07:36:52 +0200 Subject: [PATCH 01/12] use otel autoconfig Signed-off-by: Gregor Zeitlinger --- benchmarks/pom.xml | 15 +- pom.xml | 10 +- .../ExporterOpenTelemetryProperties.java | 73 ++-- .../io/prometheus/metrics/config/Util.java | 10 +- .../pom.xml | 40 ++- .../opentelemetry/OpenTelemetryExporter.java | 337 +----------------- .../opentelemetry/OtelAutoConfig.java | 86 +++++ .../PropertiesResourceProvider.java | 35 ++ .../opentelemetry/PropertyMapper.java | 80 +++++ .../opentelemetry/ResourceAttributes.java | 36 -- .../ResourceAttributesDefaults.java | 14 - .../ResourceAttributesFromJarFileName.java | 60 ---- .../ResourceAttributesFromOtelAgent.java | 16 +- .../src/main/resources/lib/.gitignore | 1 - prometheus-metrics-tracer/pom.xml | 18 +- 15 files changed, 324 insertions(+), 507 deletions(-) create mode 100644 prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java create mode 100644 prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertiesResourceProvider.java create mode 100644 prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java delete mode 100644 prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributes.java delete mode 100644 prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesDefaults.java delete mode 100644 prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesFromJarFileName.java delete mode 100644 prometheus-metrics-exporter-opentelemetry/src/main/resources/lib/.gitignore diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 4b7d53b79..99c4ecee8 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -22,6 +22,18 @@ 3.0.2 + + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-bom-alpha + ${otel.instrumentation.version} + pom + import + + + + org.openjdk.jmh @@ -58,17 +70,14 @@ io.opentelemetry opentelemetry-api - ${otel.version} io.opentelemetry opentelemetry-sdk - ${otel.version} io.opentelemetry opentelemetry-sdk-testing - ${otel.version} diff --git a/pom.xml b/pom.xml index 4b62d968f..6ab7cbb04 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ UTF-8 --module-name-need-to-be-overriden-- 5.11.2 - 1.42.1 + 2.8.0-alpha 8 @@ -100,6 +100,12 @@ 3.26.3 test + + org.slf4j + slf4j-simple + 1.7.36 + test + @@ -236,7 +242,7 @@ ${java.version} true - -Xlint:all + -Xlint:all,-serial,-processing -Werror -XDcompilePolicy=simple diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java index b8498d6c5..bf584a505 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java @@ -8,22 +8,23 @@ public class ExporterOpenTelemetryProperties { // See // https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md - private static String PROTOCOL = "protocol"; // otel.exporter.otlp.protocol - private static String ENDPOINT = "endpoint"; // otel.exporter.otlp.endpoint - private static String HEADERS = "headers"; // otel.exporter.otlp.headers - private static String INTERVAL_SECONDS = "intervalSeconds"; // otel.metric.export.interval - private static String TIMEOUT_SECONDS = "timeoutSeconds"; // otel.exporter.otlp.timeout - private static String SERVICE_NAME = "serviceName"; // otel.service.name - private static String SERVICE_NAMESPACE = "serviceNamespace"; - private static String SERVICE_INSTANCE_ID = "serviceInstanceId"; - private static String SERVICE_VERSION = "serviceVersion"; - private static String RESOURCE_ATTRIBUTES = "resourceAttributes"; // otel.resource.attributes + private static final String PROTOCOL = "protocol"; // otel.exporter.otlp.protocol + private static final String ENDPOINT = "endpoint"; // otel.exporter.otlp.endpoint + private static final String HEADERS = "headers"; // otel.exporter.otlp.headers + private static final String INTERVAL_SECONDS = "intervalSeconds"; // otel.metric.export.interval + private static final String TIMEOUT_SECONDS = "timeoutSeconds"; // otel.exporter.otlp.timeout + private static final String SERVICE_NAME = "serviceName"; // otel.service.name + private static final String SERVICE_NAMESPACE = "serviceNamespace"; + private static final String SERVICE_INSTANCE_ID = "serviceInstanceId"; + private static final String SERVICE_VERSION = "serviceVersion"; + private static final String RESOURCE_ATTRIBUTES = + "resourceAttributes"; // otel.resource.attributes private final String protocol; private final String endpoint; private final Map headers; - private final Integer intervalSeconds; - private final Integer timeoutSeconds; + private final String interval; + private final String timeout; private final String serviceName; private final String serviceNamespace; private final String serviceInstanceId; @@ -34,8 +35,8 @@ private ExporterOpenTelemetryProperties( String protocol, String endpoint, Map headers, - Integer intervalSeconds, - Integer timeoutSeconds, + String interval, + String timeout, String serviceName, String serviceNamespace, String serviceInstanceId, @@ -44,8 +45,8 @@ private ExporterOpenTelemetryProperties( this.protocol = protocol; this.endpoint = endpoint; this.headers = headers; - this.intervalSeconds = intervalSeconds; - this.timeoutSeconds = timeoutSeconds; + this.interval = interval; + this.timeout = timeout; this.serviceName = serviceName; this.serviceNamespace = serviceNamespace; this.serviceInstanceId = serviceInstanceId; @@ -65,12 +66,12 @@ public Map getHeaders() { return headers; } - public Integer getIntervalSeconds() { - return intervalSeconds; + public String getInterval() { + return interval; } - public Integer getTimeoutSeconds() { - return timeoutSeconds; + public String getTimeout() { + return timeout; } public String getServiceName() { @@ -102,27 +103,20 @@ static ExporterOpenTelemetryProperties load(String prefix, Map p String protocol = Util.loadString(prefix + "." + PROTOCOL, properties); String endpoint = Util.loadString(prefix + "." + ENDPOINT, properties); Map headers = Util.loadMap(prefix + "." + HEADERS, properties); - Integer intervalSeconds = Util.loadInteger(prefix + "." + INTERVAL_SECONDS, properties); - Integer timeoutSeconds = Util.loadInteger(prefix + "." + TIMEOUT_SECONDS, properties); + String interval = Util.loadStringAddSuffix(prefix + "." + INTERVAL_SECONDS, properties, "s"); + String timeout = Util.loadStringAddSuffix(prefix + "." + TIMEOUT_SECONDS, properties, "s"); String serviceName = Util.loadString(prefix + "." + SERVICE_NAME, properties); String serviceNamespace = Util.loadString(prefix + "." + SERVICE_NAMESPACE, properties); String serviceInstanceId = Util.loadString(prefix + "." + SERVICE_INSTANCE_ID, properties); String serviceVersion = Util.loadString(prefix + "." + SERVICE_VERSION, properties); Map resourceAttributes = Util.loadMap(prefix + "." + RESOURCE_ATTRIBUTES, properties); - Util.assertValue(intervalSeconds, t -> t > 0, "Expecting value > 0", prefix, INTERVAL_SECONDS); - Util.assertValue(timeoutSeconds, t -> t > 0, "Expecting value > 0", prefix, TIMEOUT_SECONDS); - if (protocol != null && !protocol.equals("grpc") && !protocol.equals("http/protobuf")) { - throw new PrometheusPropertiesException( - protocol - + ": Unsupported OpenTelemetry exporter protocol. Expecting grpc or http/protobuf"); - } return new ExporterOpenTelemetryProperties( protocol, endpoint, headers, - intervalSeconds, - timeoutSeconds, + interval, + timeout, serviceName, serviceNamespace, serviceInstanceId, @@ -138,14 +132,14 @@ public static class Builder { private String protocol; private String endpoint; - private Map headers = new HashMap<>(); - private Integer intervalSeconds; - private Integer timeoutSeconds; + private final Map headers = new HashMap<>(); + private String interval; + private String timeout; private String serviceName; private String serviceNamespace; private String serviceInstanceId; private String serviceVersion; - private Map resourceAttributes = new HashMap<>(); + private final Map resourceAttributes = new HashMap<>(); private Builder() {} @@ -173,7 +167,7 @@ public Builder intervalSeconds(int intervalSeconds) { if (intervalSeconds <= 0) { throw new IllegalArgumentException(intervalSeconds + ": Expecting intervalSeconds > 0"); } - this.intervalSeconds = intervalSeconds; + this.interval = intervalSeconds + "s"; return this; } @@ -181,7 +175,7 @@ public Builder timeoutSeconds(int timeoutSeconds) { if (timeoutSeconds <= 0) { throw new IllegalArgumentException(timeoutSeconds + ": Expecting timeoutSeconds > 0"); } - this.timeoutSeconds = timeoutSeconds; + this.timeout = timeoutSeconds + "s"; return this; } @@ -205,6 +199,7 @@ public Builder serviceVersion(String serviceVersion) { return this; } + // todo add test public Builder resourceAttribute(String name, String value) { this.resourceAttributes.put(name, value); return this; @@ -215,8 +210,8 @@ public ExporterOpenTelemetryProperties build() { protocol, endpoint, headers, - intervalSeconds, - timeoutSeconds, + interval, + timeout, serviceName, serviceNamespace, serviceInstanceId, diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java index 8adcd0af7..c152d2c75 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java @@ -9,7 +9,7 @@ class Util { - private static String getProperty(String name, Map properties) { + static String getProperty(String name, Map properties) { Object object = properties.remove(name); if (object != null) { return object.toString(); @@ -46,6 +46,14 @@ static String loadString(String name, Map properties) return getProperty(name, properties); } + static String loadStringAddSuffix(String name, Map properties, String suffix) { + Object object = properties.remove(name); + if (object != null) { + return object + suffix; + } + return null; + } + static List loadStringList(String name, Map properties) throws PrometheusPropertiesException { String property = getProperty(name, properties); diff --git a/prometheus-metrics-exporter-opentelemetry/pom.xml b/prometheus-metrics-exporter-opentelemetry/pom.xml index 3e293ad39..44ed9879d 100644 --- a/prometheus-metrics-exporter-opentelemetry/pom.xml +++ b/prometheus-metrics-exporter-opentelemetry/pom.xml @@ -21,6 +21,18 @@ io.prometheus.metrics.exporter.opentelemetry + + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-bom-alpha + ${otel.instrumentation.version} + pom + import + + + + io.prometheus @@ -30,17 +42,22 @@ io.opentelemetry opentelemetry-api - ${otel.version} io.opentelemetry opentelemetry-sdk - ${otel.version} io.opentelemetry opentelemetry-exporter-otlp - ${otel.version} + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure + + + io.opentelemetry.instrumentation + opentelemetry-resources @@ -71,7 +88,6 @@ io.opentelemetry opentelemetry-sdk-trace - ${otel.version} test @@ -98,8 +114,8 @@ regex-property - otel.string-version - ${otel.version} + otel.instrumentation.string-version + ${otel.instrumentation.version} \. _ true @@ -129,37 +145,37 @@ io.opentelemetry - io.prometheus.metrics.shaded.io_opentelemetry_${otel.string-version} + io.prometheus.metrics.shaded.io_opentelemetry_${otel.instrumentation.string-version} okhttp3 - io.prometheus.metrics.shaded.io_opentelemetry_${otel.string-version}.okhttp3 + io.prometheus.metrics.shaded.io_opentelemetry_${otel.instrumentation.string-version}.okhttp3 kotlin - io.prometheus.metrics.shaded.io_opentelemetry_${otel.string-version}.kotlin + io.prometheus.metrics.shaded.io_opentelemetry_${otel.instrumentation.string-version}.kotlin org.intellij - io.prometheus.metrics.shaded.io_opentelemetry_${otel.string-version}.org.intellij + io.prometheus.metrics.shaded.io_opentelemetry_${otel.instrumentation.string-version}.org.intellij org.jetbrains - io.prometheus.metrics.shaded.io_opentelemetry_${otel.string-version}.org.jetbrains + io.prometheus.metrics.shaded.io_opentelemetry_${otel.instrumentation.string-version}.org.jetbrains okio - io.prometheus.metrics.shaded.io_opentelemetry_${otel.string-version}.okio + io.prometheus.metrics.shaded.io_opentelemetry_${otel.instrumentation.string-version}.okio diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OpenTelemetryExporter.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OpenTelemetryExporter.java index 82f503ac9..a00b2ef85 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OpenTelemetryExporter.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OpenTelemetryExporter.java @@ -1,62 +1,16 @@ package io.prometheus.metrics.exporter.opentelemetry; -import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; -import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; -import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; -import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; -import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.metrics.export.MetricExporter; -import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.resources.ResourceBuilder; -import io.prometheus.metrics.config.ExporterOpenTelemetryProperties; +import io.opentelemetry.sdk.metrics.export.MetricReader; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.model.registry.PrometheusRegistry; -import java.time.Duration; import java.util.HashMap; -import java.util.Locale; import java.util.Map; -import java.util.concurrent.TimeUnit; public class OpenTelemetryExporter implements AutoCloseable { - private final PeriodicMetricReader reader; + private final MetricReader reader; - private OpenTelemetryExporter( - Builder builder, PrometheusProperties config, PrometheusRegistry registry) { - InstrumentationScopeInfo instrumentationScopeInfo = - PrometheusInstrumentationScope.loadInstrumentationScopeInfo(); - ExporterOpenTelemetryProperties properties = config.getExporterOpenTelemetryProperties(); - Resource resource = initResourceAttributes(builder, properties, instrumentationScopeInfo); - MetricExporter exporter; - if (ConfigHelper.getProtocol(builder, properties).equals("grpc")) { - OtlpGrpcMetricExporterBuilder exporterBuilder = - OtlpGrpcMetricExporter.builder() - .setTimeout(Duration.ofSeconds(ConfigHelper.getTimeoutSeconds(builder, properties))) - .setEndpoint(ConfigHelper.getEndpoint(builder, properties)); - for (Map.Entry header : - ConfigHelper.getHeaders(builder, properties).entrySet()) { - exporterBuilder.addHeader(header.getKey(), header.getValue()); - } - exporter = exporterBuilder.build(); - } else { - OtlpHttpMetricExporterBuilder exporterBuilder = - OtlpHttpMetricExporter.builder() - .setTimeout(Duration.ofSeconds(ConfigHelper.getTimeoutSeconds(builder, properties))) - .setEndpoint(ConfigHelper.getEndpoint(builder, properties)); - for (Map.Entry header : - ConfigHelper.getHeaders(builder, properties).entrySet()) { - exporterBuilder.addHeader(header.getKey(), header.getValue()); - } - exporter = exporterBuilder.build(); - } - reader = - PeriodicMetricReader.builder(exporter) - .setInterval(Duration.ofSeconds(ConfigHelper.getIntervalSeconds(builder, properties))) - .build(); - - PrometheusMetricProducer prometheusMetricProducer = - new PrometheusMetricProducer(registry, instrumentationScopeInfo, resource); - reader.register(prometheusMetricProducer); + public OpenTelemetryExporter(MetricReader reader) { + this.reader = reader; } @Override @@ -64,29 +18,6 @@ public void close() { reader.shutdown(); } - private Resource initResourceAttributes( - Builder builder, - ExporterOpenTelemetryProperties properties, - InstrumentationScopeInfo instrumentationScopeInfo) { - String serviceName = ConfigHelper.getServiceName(builder, properties); - String serviceNamespace = ConfigHelper.getServiceNamespace(builder, properties); - String serviceInstanceId = ConfigHelper.getServiceInstanceId(builder, properties); - String serviceVersion = ConfigHelper.getServiceVersion(builder, properties); - Map resourceAttributes = - ResourceAttributes.get( - instrumentationScopeInfo.getName(), - serviceName, - serviceNamespace, - serviceInstanceId, - serviceVersion, - ConfigHelper.getResourceAttributes(builder, properties)); - ResourceBuilder resourceBuilder = Resource.builder(); - for (Map.Entry entry : resourceAttributes.entrySet()) { - resourceBuilder.put(entry.getKey(), entry.getValue()); - } - return resourceBuilder.build(); - } - public static Builder builder() { return new Builder(PrometheusProperties.get()); } @@ -99,16 +30,16 @@ public static class Builder { private final PrometheusProperties config; private PrometheusRegistry registry = null; - private String protocol; - private String endpoint; - private final Map headers = new HashMap<>(); - private Integer intervalSeconds; - private Integer timeoutSeconds; - private String serviceName; - private String serviceNamespace; - private String serviceInstanceId; - private String serviceVersion; - private final Map resourceAttributes = new HashMap<>(); + String protocol; + String endpoint; + final Map headers = new HashMap<>(); + String interval; + String timeout; + String serviceName; + String serviceNamespace; + String serviceInstanceId; + String serviceVersion; + final Map resourceAttributes = new HashMap<>(); private Builder(PrometheusProperties config) { this.config = config; @@ -181,7 +112,7 @@ public Builder intervalSeconds(int intervalSeconds) { if (intervalSeconds <= 0) { throw new IllegalStateException(intervalSeconds + ": expecting a push interval > 0s"); } - this.intervalSeconds = intervalSeconds; + this.interval = intervalSeconds + "s"; return this; } @@ -196,7 +127,7 @@ public Builder timeoutSeconds(int timeoutSeconds) { if (timeoutSeconds <= 0) { throw new IllegalStateException(timeoutSeconds + ": expecting a push interval > 0s"); } - this.timeoutSeconds = timeoutSeconds; + this.timeout = timeoutSeconds + "s"; return this; } @@ -266,241 +197,7 @@ public OpenTelemetryExporter buildAndStart() { if (registry == null) { registry = PrometheusRegistry.defaultRegistry; } - return new OpenTelemetryExporter(this, config, registry); - } - } - - private static class ConfigHelper { - - private static String getProtocol( - OpenTelemetryExporter.Builder builder, ExporterOpenTelemetryProperties config) { - String protocol = config.getProtocol(); - if (protocol != null) { - return protocol; - } - protocol = getString("otel.exporter.otlp.protocol"); - if (protocol != null) { - if (!protocol.equals("grpc") && !protocol.equals("http/protobuf")) { - throw new IllegalStateException( - protocol - + ": Unsupported OpenTelemetry exporter protocol. Expecting grpc or http/protobuf."); - } - return protocol; - } - if (builder.protocol != null) { - return builder.protocol; - } - return "grpc"; - } - - private static String getEndpoint( - OpenTelemetryExporter.Builder builder, ExporterOpenTelemetryProperties config) { - String endpoint = config.getEndpoint(); - if (endpoint == null) { - endpoint = getString("otel.exporter.otlp.metrics.endpoint"); - } - if (endpoint == null) { - endpoint = getString("otel.exporter.otlp.endpoint"); - } - if (endpoint == null) { - endpoint = builder.endpoint; - } - if (endpoint == null) { - if (getProtocol(builder, config).equals("grpc")) { - endpoint = "http://localhost:4317"; - } else { // http/protobuf - endpoint = "http://localhost:4318/v1/metrics"; - } - } - if (getProtocol(builder, config).equals("grpc")) { - return endpoint; - } else { // http/protobuf - if (!endpoint.endsWith("v1/metrics")) { - if (!endpoint.endsWith("/")) { - return endpoint + "/v1/metrics"; - } else { - return endpoint + "v1/metrics"; - } - } else { - return endpoint; - } - } - } - - private static Map getHeaders( - OpenTelemetryExporter.Builder builder, ExporterOpenTelemetryProperties config) { - Map headers = config.getHeaders(); - if (!headers.isEmpty()) { - return headers; - } - headers = getMap("otel.exporter.otlp.headers"); - if (!headers.isEmpty()) { - return headers; - } - if (!builder.headers.isEmpty()) { - return builder.headers; - } - return new HashMap<>(); - } - - private static int getIntervalSeconds( - OpenTelemetryExporter.Builder builder, ExporterOpenTelemetryProperties config) { - Integer intervalSeconds = config.getIntervalSeconds(); - if (intervalSeconds != null) { - return intervalSeconds; - } - intervalSeconds = getPositiveInteger("otel.metric.export.interval"); - if (intervalSeconds != null) { - return (int) TimeUnit.MILLISECONDS.toSeconds(intervalSeconds); - } - if (builder.intervalSeconds != null) { - return builder.intervalSeconds; - } - return 60; - } - - private static int getTimeoutSeconds( - OpenTelemetryExporter.Builder builder, ExporterOpenTelemetryProperties config) { - Integer timeoutSeconds = config.getTimeoutSeconds(); - if (timeoutSeconds != null) { - return timeoutSeconds; - } - Integer timeoutMilliseconds = getPositiveInteger("otel.exporter.otlp.metrics.timeout"); - if (timeoutMilliseconds == null) { - timeoutMilliseconds = getPositiveInteger("otel.exporter.otlp.timeout"); - } - if (timeoutMilliseconds != null) { - return (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMilliseconds); - } - if (builder.timeoutSeconds != null) { - return builder.timeoutSeconds; - } - return 10; - } - - private static String getServiceName( - OpenTelemetryExporter.Builder builder, ExporterOpenTelemetryProperties config) { - String serviceName = config.getServiceName(); - if (serviceName != null) { - return serviceName; - } - serviceName = getString("otel.service.name"); - if (serviceName != null) { - return serviceName; - } - if (builder.serviceName != null) { - return builder.serviceName; - } - return null; - } - - private static String getServiceNamespace( - OpenTelemetryExporter.Builder builder, ExporterOpenTelemetryProperties config) { - String serviceNamespace = config.getServiceNamespace(); - if (serviceNamespace != null) { - return serviceNamespace; - } - if (builder.serviceNamespace != null) { - return builder.serviceNamespace; - } - return null; - } - - private static String getServiceInstanceId( - OpenTelemetryExporter.Builder builder, ExporterOpenTelemetryProperties config) { - String serviceInstanceId = config.getServiceInstanceId(); - if (serviceInstanceId != null) { - return serviceInstanceId; - } - if (builder.serviceInstanceId != null) { - return builder.serviceInstanceId; - } - return null; - } - - private static String getServiceVersion( - OpenTelemetryExporter.Builder builder, ExporterOpenTelemetryProperties config) { - String serviceVersion = config.getServiceVersion(); - if (serviceVersion != null) { - return serviceVersion; - } - if (builder.serviceVersion != null) { - return builder.serviceVersion; - } - return null; - } - - private static Map getResourceAttributes( - OpenTelemetryExporter.Builder builder, ExporterOpenTelemetryProperties config) { - Map resourceAttributes = config.getResourceAttributes(); - if (!resourceAttributes.isEmpty()) { - return resourceAttributes; - } - resourceAttributes = getMap("otel.resource.attributes"); - if (!resourceAttributes.isEmpty()) { - return resourceAttributes; - } - if (!builder.resourceAttributes.isEmpty()) { - return builder.resourceAttributes; - } - return new HashMap<>(); - } - - private static String getString(String otelPropertyName) { - String otelEnvVarName = - otelPropertyName.replace(".", "_").replace("-", "_").toUpperCase(Locale.ROOT); - if (System.getenv(otelEnvVarName) != null) { - return System.getenv(otelEnvVarName); - } - if (System.getProperty(otelPropertyName) != null) { - return System.getProperty(otelPropertyName); - } - return null; - } - - private static Integer getInteger(String otelPropertyName) { - String result = getString(otelPropertyName); - if (result == null) { - return null; - } else { - try { - return Integer.parseInt(result); - } catch (NumberFormatException e) { - throw new IllegalStateException(otelPropertyName + "=" + result + " - illegal value."); - } - } - } - - private static Integer getPositiveInteger(String otelPropertyName) { - Integer result = getInteger(otelPropertyName); - if (result == null) { - return null; - } - if (result <= 0) { - throw new IllegalStateException(otelPropertyName + "=" + result + ": Expecting value > 0."); - } - return result; - } - - private static Map getMap(String otelPropertyName) { - Map result = new HashMap<>(); - String property = getString(otelPropertyName); - if (property != null) { - String[] pairs = property.split(","); - for (String pair : pairs) { - if (pair.contains("=")) { - String[] keyValue = pair.split("=", 1); - if (keyValue.length == 2) { - String key = keyValue[0].trim(); - String value = keyValue[1].trim(); - if (key.length() > 0 && value.length() > 0) { - result.putIfAbsent(key, value); - } - } - } - } - } - return result; + return new OpenTelemetryExporter(OtelAutoConfig.createReader(this, config, registry)); } } } diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java new file mode 100644 index 000000000..761c4586d --- /dev/null +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java @@ -0,0 +1,86 @@ +package io.prometheus.metrics.exporter.opentelemetry; + +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import io.opentelemetry.sdk.metrics.export.MetricReader; +import io.opentelemetry.sdk.resources.Resource; +import io.prometheus.metrics.config.ExporterOpenTelemetryProperties; +import io.prometheus.metrics.config.PrometheusProperties; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicReference; + +public class OtelAutoConfig { + static MetricReader createReader( + OpenTelemetryExporter.Builder builder, + PrometheusProperties config, + PrometheusRegistry registry) { + AtomicReference readerRef = new AtomicReference<>(); + InstrumentationScopeInfo instrumentationScopeInfo = + PrometheusInstrumentationScope.loadInstrumentationScopeInfo(); + + AutoConfiguredOpenTelemetrySdk sdk = + createAutoConfiguredOpenTelemetrySdk(builder, config, readerRef, instrumentationScopeInfo); + + MetricReader reader = readerRef.get(); + reader.register( + new PrometheusMetricProducer(registry, instrumentationScopeInfo, getResourceField(sdk))); + return reader; + } + + static AutoConfiguredOpenTelemetrySdk createAutoConfiguredOpenTelemetrySdk( + OpenTelemetryExporter.Builder builder, + PrometheusProperties config, + AtomicReference readerRef, + InstrumentationScopeInfo instrumentationScopeInfo) { + PropertyMapper propertyMapper = + PropertyMapper.create(config.getExporterOpenTelemetryProperties(), builder); + + return AutoConfiguredOpenTelemetrySdk.builder() + .addPropertiesSupplier(() -> propertyMapper.configLowPriority) + .addPropertiesCustomizer( + c -> PropertyMapper.customizeProperties(propertyMapper.configHighPriority, c)) + .addMetricReaderCustomizer( + (reader, unused) -> { + readerRef.set(reader); + return reader; + }) + .addResourceCustomizer( + (resource, unused) -> getResource(builder, config, resource, instrumentationScopeInfo)) + .build(); + } + + private static Resource getResource( + OpenTelemetryExporter.Builder builder, + PrometheusProperties config, + Resource resource, + InstrumentationScopeInfo instrumentationScopeInfo) { + ExporterOpenTelemetryProperties properties = config.getExporterOpenTelemetryProperties(); + return resource + .merge( + PropertiesResourceProvider.mergeResource( + builder.resourceAttributes, + builder.serviceName, + builder.serviceNamespace, + builder.serviceInstanceId, + builder.serviceVersion)) + .merge( + PropertiesResourceProvider.mergeResource( + properties.getResourceAttributes(), + properties.getServiceName(), + properties.getServiceNamespace(), + properties.getServiceInstanceId(), + properties.getServiceVersion())) + .merge(ResourceAttributesFromOtelAgent.get(instrumentationScopeInfo.getName())); + } + + private static Resource getResourceField(AutoConfiguredOpenTelemetrySdk sdk) { + try { + Method method = AutoConfiguredOpenTelemetrySdk.class.getDeclaredMethod("getResource"); + method.setAccessible(true); + return (Resource) method.invoke(sdk); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertiesResourceProvider.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertiesResourceProvider.java new file mode 100644 index 000000000..1bb6b19bf --- /dev/null +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertiesResourceProvider.java @@ -0,0 +1,35 @@ +package io.prometheus.metrics.exporter.opentelemetry; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.resources.Resource; +import java.util.HashMap; +import java.util.Map; + +final class PropertiesResourceProvider { + + static Resource mergeResource( + Map resourceAttributes, + String serviceName, + String serviceNamespace, + String serviceInstanceId, + String serviceVersion) { + Map resource = new HashMap<>(resourceAttributes); + if (serviceName != null) { + resource.put("service.name", serviceName); + } + if (serviceNamespace != null) { + resource.put("service.namespace", serviceNamespace); + } + if (serviceInstanceId != null) { + resource.put("service.instance.id", serviceInstanceId); + } + if (serviceVersion != null) { + resource.put("service.version", serviceVersion); + } + + AttributesBuilder builder = Attributes.builder(); + resource.forEach(builder::put); + return Resource.create(builder.build()); + } +} diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java new file mode 100644 index 000000000..153405072 --- /dev/null +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java @@ -0,0 +1,80 @@ +package io.prometheus.metrics.exporter.opentelemetry; + +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.prometheus.metrics.config.ExporterOpenTelemetryProperties; +import io.prometheus.metrics.config.PrometheusPropertiesException; +import java.util.HashMap; +import java.util.Map; + +class PropertyMapper { + + private static final String METRICS_ENDPOINT = "otel.exporter.otlp.metrics.endpoint"; + Map configLowPriority = new HashMap<>(); + Map configHighPriority = new HashMap<>(); + + static PropertyMapper create( + ExporterOpenTelemetryProperties properties, OpenTelemetryExporter.Builder builder) + throws PrometheusPropertiesException { + String protocol = properties.getProtocol(); + return new PropertyMapper() + .addString(builder.protocol, protocol, "otel.exporter.otlp.metrics.protocol") + .addString(builder.endpoint, properties.getEndpoint(), METRICS_ENDPOINT) + .addString( + mapToOtelString(builder.headers), + mapToOtelString(properties.getHeaders()), + "otel.exporter.otlp.metric.headers") + .addString(builder.interval, properties.getInterval(), "otel.metric.export.interval") + .addString(builder.timeout, properties.getTimeout(), "otel.exporter.otlp.metrics.timeout") + .addString(builder.serviceName, properties.getServiceName(), "otel.service.name"); + } + + PropertyMapper addString(String builderValue, String propertyValue, String otelKey) { + if (builderValue != null) { + configLowPriority.put(otelKey, builderValue); + } + if (propertyValue != null) { + configHighPriority.put(otelKey, propertyValue); + } + return this; + } + + private static String mapToOtelString(Map map) { + if (map.isEmpty()) { + return null; + } + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : map.entrySet()) { + sb.append(entry.getKey()).append("=").append(entry.getValue()).append(","); + } + return sb.substring(0, sb.length() - 1); + } + + static Map customizeProperties(Map result, ConfigProperties c) { + Map map = addEndpointPath(result, c); + map.put("otel.logs.exporter", "none"); + map.put("otel.traces.exporter", "none"); + return map; + } + + static Map addEndpointPath(Map result, ConfigProperties c) { + String endpoint = c.getString(METRICS_ENDPOINT); + if (endpoint == null) { + return result; + } + String protocol = c.getString("otel.exporter.otlp.metrics.protocol"); + if (protocol == null) { + protocol = c.getString("otel.exporter.otlp.protocol"); + } + + if (!"grpc".equals(protocol)) { // http/protobuf + if (!endpoint.endsWith("v1/metrics")) { + if (!endpoint.endsWith("/")) { + result.put(METRICS_ENDPOINT, endpoint + "/v1/metrics"); + } else { + result.put(METRICS_ENDPOINT, endpoint + "v1/metrics"); + } + } + } + return result; + } +} diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributes.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributes.java deleted file mode 100644 index 2c88badfc..000000000 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributes.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.prometheus.metrics.exporter.opentelemetry; - -import java.util.HashMap; -import java.util.Map; - -public class ResourceAttributes { - - // TODO: The OTel Java instrumentation also has a SpringBootServiceNameDetector, we should port - // this over. - public static Map get( - String instrumentationScopeName, - String serviceName, - String serviceNamespace, - String serviceInstanceId, - String serviceVersion, - Map configuredResourceAttributes) { - Map result = new HashMap<>(); - ResourceAttributesFromOtelAgent.addIfAbsent(result, instrumentationScopeName); - putIfAbsent(result, "service.name", serviceName); - putIfAbsent(result, "service.namespace", serviceNamespace); - putIfAbsent(result, "service.instance.id", serviceInstanceId); - putIfAbsent(result, "service.version", serviceVersion); - for (Map.Entry attribute : configuredResourceAttributes.entrySet()) { - putIfAbsent(result, attribute.getKey(), attribute.getValue()); - } - ResourceAttributesFromJarFileName.addIfAbsent(result); - ResourceAttributesDefaults.addIfAbsent(result); - return result; - } - - private static void putIfAbsent(Map result, String key, String value) { - if (value != null) { - result.putIfAbsent(key, value); - } - } -} diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesDefaults.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesDefaults.java deleted file mode 100644 index 19328fd73..000000000 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesDefaults.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.prometheus.metrics.exporter.opentelemetry; - -import java.util.Map; -import java.util.UUID; - -public class ResourceAttributesDefaults { - - private static final String instanceId = UUID.randomUUID().toString(); - - public static void addIfAbsent(Map result) { - result.putIfAbsent("service.instance.id", instanceId); - result.putIfAbsent("service.name", "unknown_service:java"); - } -} diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesFromJarFileName.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesFromJarFileName.java deleted file mode 100644 index 7cf7a51aa..000000000 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesFromJarFileName.java +++ /dev/null @@ -1,60 +0,0 @@ -package io.prometheus.metrics.exporter.opentelemetry; - -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Map; - -// See io.opentelemetry.instrumentation.resources.JarServiceNameDetector -public class ResourceAttributesFromJarFileName { - - public static void addIfAbsent(Map result) { - if (result.containsKey("service.name")) { - return; - } - Path jarPath = getJarPathFromSunCommandLine(); - if (jarPath == null) { - return; - } - String serviceName = getServiceName(jarPath); - result.putIfAbsent("service.name", serviceName); - } - - private static Path getJarPathFromSunCommandLine() { - String programArguments = System.getProperty("sun.java.command"); - if (programArguments == null) { - return null; - } - // Take the path until the first space. If the path doesn't exist extend it up to the next - // space. Repeat until a path that exists is found or input runs out. - int next = 0; - while (true) { - int nextSpace = programArguments.indexOf(' ', next); - if (nextSpace == -1) { - return pathIfExists(programArguments); - } - Path path = pathIfExists(programArguments.substring(0, nextSpace)); - next = nextSpace + 1; - if (path != null) { - return path; - } - } - } - - private static Path pathIfExists(String programArguments) { - Path candidate; - try { - candidate = Paths.get(programArguments); - } catch (InvalidPathException e) { - return null; - } - return Files.isRegularFile(candidate) ? candidate : null; - } - - private static String getServiceName(Path jarPath) { - String jarName = jarPath.getFileName().toString(); - int dotIndex = jarName.lastIndexOf("."); - return dotIndex == -1 ? jarName : jarName.substring(0, dotIndex); - } -} diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesFromOtelAgent.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesFromOtelAgent.java index ce6baafe2..caf0246f5 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesFromOtelAgent.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesFromOtelAgent.java @@ -2,6 +2,7 @@ import static java.nio.file.Files.createTempDirectory; +import io.opentelemetry.sdk.resources.Resource; import java.io.File; import java.io.InputStream; import java.lang.reflect.Field; @@ -11,7 +12,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.Map; public class ResourceAttributesFromOtelAgent { @@ -32,7 +32,7 @@ public class ResourceAttributesFromOtelAgent { *

After that we discard the class loader so that all OTel specific classes are unloaded. No * runtime dependency on any OTel version remains. */ - public static void addIfAbsent(Map result, String instrumentationScopeName) { + public static Resource get(String instrumentationScopeName) { try { Path tmpDir = createTempDirectory(instrumentationScopeName + "-"); try { @@ -43,20 +43,13 @@ public static void addIfAbsent(Map result, String instrumentatio classLoader.loadClass("io.opentelemetry.api.GlobalOpenTelemetry"); Object globalOpenTelemetry = globalOpenTelemetryClass.getMethod("get").invoke(null); if (globalOpenTelemetry.getClass().getSimpleName().contains("ApplicationOpenTelemetry")) { - // GlobalOpenTelemetry is injected by the OTel Java aqent + // GlobalOpenTelemetry is injected by the OTel Java agent Object applicationMeterProvider = callMethod("getMeterProvider", globalOpenTelemetry); Object agentMeterProvider = getField("agentMeterProvider", applicationMeterProvider); Object sdkMeterProvider = getField("delegate", agentMeterProvider); Object sharedState = getField("sharedState", sdkMeterProvider); Object resource = callMethod("getResource", sharedState); - Object attributes = callMethod("getAttributes", resource); - Map attributeMap = (Map) callMethod("asMap", attributes); - - for (Map.Entry entry : attributeMap.entrySet()) { - if (entry.getKey() != null && entry.getValue() != null) { - result.putIfAbsent(entry.getKey().toString(), entry.getValue().toString()); - } - } + return (Resource) resource; } } } finally { @@ -65,6 +58,7 @@ public static void addIfAbsent(Map result, String instrumentatio } catch (Exception ignored) { // ignore } + return Resource.empty(); } private static Object getField(String name, Object obj) throws Exception { diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/resources/lib/.gitignore b/prometheus-metrics-exporter-opentelemetry/src/main/resources/lib/.gitignore deleted file mode 100644 index d392f0e82..000000000 --- a/prometheus-metrics-exporter-opentelemetry/src/main/resources/lib/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.jar diff --git a/prometheus-metrics-tracer/pom.xml b/prometheus-metrics-tracer/pom.xml index 84938cff4..953b09dc5 100644 --- a/prometheus-metrics-tracer/pom.xml +++ b/prometheus-metrics-tracer/pom.xml @@ -17,14 +17,16 @@ - - - io.opentelemetry - opentelemetry-api - ${otel.version} - - - + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-bom-alpha + ${otel.instrumentation.version} + pom + import + + + prometheus-metrics-tracer-common From 1d081884adf117bdcea85922833bcca7d104d981 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 14 Oct 2024 10:53:52 +0200 Subject: [PATCH 02/12] add oats test Signed-off-by: Gregor Zeitlinger --- .github/workflows/acceptance-tests.yml | 41 +++++++++++++++++++ .../example-exporter-opentelemetry/Dockerfile | 7 ++++ .../docker-compose.oats.yml | 11 +++++ .../example-exporter-opentelemetry/oats.yaml | 12 ++++++ scripts/run-acceptance-tests.sh | 10 +++++ 5 files changed, 81 insertions(+) create mode 100644 .github/workflows/acceptance-tests.yml create mode 100644 examples/example-exporter-opentelemetry/Dockerfile create mode 100644 examples/example-exporter-opentelemetry/docker-compose.oats.yml create mode 100644 examples/example-exporter-opentelemetry/oats.yaml create mode 100755 scripts/run-acceptance-tests.sh diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml new file mode 100644 index 000000000..70a8c0130 --- /dev/null +++ b/.github/workflows/acceptance-tests.yml @@ -0,0 +1,41 @@ +name: OpenTelemetry Acceptance Tests + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + acceptance-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check out oats + uses: actions/checkout@v4 + with: + repository: grafana/oats + ref: 9a79819efcde37f025613914708dd1ba721e5ddc + path: oats + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + cache: 'maven' + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache-dependency-path: oats/go.sum + - name: Run the Maven verify phase + run: | + ./mvnw clean install -DskipTests + - name: Run acceptance tests + run: ./scripts/run-acceptance-tests.sh + - name: upload log file + uses: actions/upload-artifact@v4 + if: failure() + with: + name: docker-compose.log + path: oats/yaml/build/**/output.log diff --git a/examples/example-exporter-opentelemetry/Dockerfile b/examples/example-exporter-opentelemetry/Dockerfile new file mode 100644 index 000000000..01d779f28 --- /dev/null +++ b/examples/example-exporter-opentelemetry/Dockerfile @@ -0,0 +1,7 @@ +FROM eclipse-temurin:21-jre + +WORKDIR /usr/src/app/ + +COPY target/example-exporter-opentelemetry.jar ./app.jar + +ENTRYPOINT [ "java", "-jar", "./app.jar" ] diff --git a/examples/example-exporter-opentelemetry/docker-compose.oats.yml b/examples/example-exporter-opentelemetry/docker-compose.oats.yml new file mode 100644 index 000000000..afe372727 --- /dev/null +++ b/examples/example-exporter-opentelemetry/docker-compose.oats.yml @@ -0,0 +1,11 @@ +# OATS is an acceptance testing framework for OpenTelemetry - https://github.com/grafana/oats/tree/main/yaml +version: '3.4' + +services: + java: + build: + dockerfile: Dockerfile + environment: + OTEL_SERVICE_NAME: "rolldice" + OTEL_EXPORTER_OTLP_ENDPOINT: http://lgtm:4317 + OTEL_METRIC_EXPORT_INTERVAL: "5000" # so we don't have to wait 60s for metrics diff --git a/examples/example-exporter-opentelemetry/oats.yaml b/examples/example-exporter-opentelemetry/oats.yaml new file mode 100644 index 000000000..351d7c2e5 --- /dev/null +++ b/examples/example-exporter-opentelemetry/oats.yaml @@ -0,0 +1,12 @@ +# OATS is an acceptance testing framework for OpenTelemetry - https://github.com/grafana/oats/tree/main/yaml +docker-compose: + generator: docker-lgtm + files: + - ./docker-compose.oats.yml +input: + - path: /invalid +expected: + metrics: + - promql: 'uptime_seconds_total{}' + value: '>= 0' + diff --git a/scripts/run-acceptance-tests.sh b/scripts/run-acceptance-tests.sh new file mode 100755 index 000000000..151783f77 --- /dev/null +++ b/scripts/run-acceptance-tests.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd oats/yaml +go install github.com/onsi/ginkgo/v2/ginkgo +export TESTCASE_SKIP_BUILD=true +export TESTCASE_TIMEOUT=5m +export TESTCASE_BASE_PATH=../../examples +ginkgo -r # is parallel causing problems? -p From f2a01c5a9e6f2a3198ac304fde9fdd7796d3aabb Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 14 Oct 2024 14:54:37 +0200 Subject: [PATCH 03/12] add oats test Signed-off-by: Gregor Zeitlinger --- .../example-exporter-opentelemetry/Dockerfile | 7 -- .../docker-compose.oats.yml | 3 +- .../oats.Dockerfile | 8 ++ .../example-exporter-opentelemetry/oats.yaml | 2 - .../metrics/examples/opentelemetry/Main.java | 2 + .../pom.xml | 53 +++++++++++-- .../opentelemetry/OtelAutoConfig.java | 23 ++++-- .../src/main/resources/lib/.gitignore | 1 + .../opentelemetry/OtelAutoConfigTest.java | 74 +++++++++++++++++++ 9 files changed, 149 insertions(+), 24 deletions(-) delete mode 100644 examples/example-exporter-opentelemetry/Dockerfile create mode 100644 examples/example-exporter-opentelemetry/oats.Dockerfile create mode 100644 prometheus-metrics-exporter-opentelemetry/src/main/resources/lib/.gitignore create mode 100644 prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfigTest.java diff --git a/examples/example-exporter-opentelemetry/Dockerfile b/examples/example-exporter-opentelemetry/Dockerfile deleted file mode 100644 index 01d779f28..000000000 --- a/examples/example-exporter-opentelemetry/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM eclipse-temurin:21-jre - -WORKDIR /usr/src/app/ - -COPY target/example-exporter-opentelemetry.jar ./app.jar - -ENTRYPOINT [ "java", "-jar", "./app.jar" ] diff --git a/examples/example-exporter-opentelemetry/docker-compose.oats.yml b/examples/example-exporter-opentelemetry/docker-compose.oats.yml index afe372727..389566b38 100644 --- a/examples/example-exporter-opentelemetry/docker-compose.oats.yml +++ b/examples/example-exporter-opentelemetry/docker-compose.oats.yml @@ -4,8 +4,9 @@ version: '3.4' services: java: build: - dockerfile: Dockerfile + dockerfile: oats.Dockerfile environment: OTEL_SERVICE_NAME: "rolldice" OTEL_EXPORTER_OTLP_ENDPOINT: http://lgtm:4317 + OTEL_EXPORTER_OTLP_PROTOCOL: grpc OTEL_METRIC_EXPORT_INTERVAL: "5000" # so we don't have to wait 60s for metrics diff --git a/examples/example-exporter-opentelemetry/oats.Dockerfile b/examples/example-exporter-opentelemetry/oats.Dockerfile new file mode 100644 index 000000000..bd34a9266 --- /dev/null +++ b/examples/example-exporter-opentelemetry/oats.Dockerfile @@ -0,0 +1,8 @@ +FROM eclipse-temurin:21-jre + +COPY target/example-exporter-opentelemetry.jar ./app.jar +# check that the resource attributs from the agent are used, epsecially the service.instance.id should be the same +ADD --chmod=644 https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.8.0/opentelemetry-javaagent.jar /usr/src/app/opentelemetry-javaagent.jar +ENV JAVA_TOOL_OPTIONS=-javaagent:/usr/src/app/opentelemetry-javaagent.jar + +ENTRYPOINT [ "java", "-jar", "./app.jar" ] diff --git a/examples/example-exporter-opentelemetry/oats.yaml b/examples/example-exporter-opentelemetry/oats.yaml index 351d7c2e5..b616fe52b 100644 --- a/examples/example-exporter-opentelemetry/oats.yaml +++ b/examples/example-exporter-opentelemetry/oats.yaml @@ -3,8 +3,6 @@ docker-compose: generator: docker-lgtm files: - ./docker-compose.oats.yml -input: - - path: /invalid expected: metrics: - promql: 'uptime_seconds_total{}' diff --git a/examples/example-exporter-opentelemetry/src/main/java/io/prometheus/metrics/examples/opentelemetry/Main.java b/examples/example-exporter-opentelemetry/src/main/java/io/prometheus/metrics/examples/opentelemetry/Main.java index defe85074..b1aa440b1 100644 --- a/examples/example-exporter-opentelemetry/src/main/java/io/prometheus/metrics/examples/opentelemetry/Main.java +++ b/examples/example-exporter-opentelemetry/src/main/java/io/prometheus/metrics/examples/opentelemetry/Main.java @@ -9,6 +9,7 @@ public class Main { public static void main(String[] args) throws Exception { + System.out.println("Starting example application"); // Note: Some JVM metrics are also defined as OpenTelemetry's semantic conventions. // We have plans to implement a configuration option for JvmMetrics to use OpenTelemetry @@ -34,6 +35,7 @@ public static void main(String[] args) throws Exception { while (true) { Thread.sleep(1000); + System.out.println("Incrementing counter"); counter.inc(); } } diff --git a/prometheus-metrics-exporter-opentelemetry/pom.xml b/prometheus-metrics-exporter-opentelemetry/pom.xml index 44ed9879d..2b7197b2f 100644 --- a/prometheus-metrics-exporter-opentelemetry/pom.xml +++ b/prometheus-metrics-exporter-opentelemetry/pom.xml @@ -55,6 +55,10 @@ io.opentelemetry opentelemetry-sdk-extension-autoconfigure + + io.opentelemetry + opentelemetry-sdk-extension-incubator + io.opentelemetry.instrumentation opentelemetry-resources @@ -85,11 +89,6 @@ 1.7.1-alpha test - - io.opentelemetry - opentelemetry-sdk-trace - test - @@ -116,13 +115,41 @@ otel.instrumentation.string-version ${otel.instrumentation.version} - \. + [\.-] _ true + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + package + + copy + + + + + + + io.opentelemetry + opentelemetry-api + ${project.basedir}/src/main/resources/lib/ + + + io.opentelemetry + opentelemetry-context + ${project.basedir}/src/main/resources/lib/ + + + + + org.apache.maven.plugins maven-shade-plugin @@ -136,6 +163,8 @@ io.opentelemetry:* + io.opentelemetry.semconv:* + io.opentelemetry.instrumentation:* com.squareup.*:* org.jetbrains:* org.jetbrains.*:* @@ -148,6 +177,18 @@ io.prometheus.metrics.shaded.io_opentelemetry_${otel.instrumentation.string-version} + + io.opentelemetry.instrumentation + + io.prometheus.metrics.shaded.io_opentelemetry_${otel.instrumentation.string-version}.instrumentation + + + + io.opentelemetry.semconv + + io.prometheus.metrics.shaded.io_opentelemetry_${otel.instrumentation.string-version}.semconv + + okhttp3 diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java index 761c4586d..5732cec9b 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java @@ -1,6 +1,8 @@ package io.prometheus.metrics.exporter.opentelemetry; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.ResourceConfiguration; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.metrics.export.MetricReader; import io.opentelemetry.sdk.resources.Resource; @@ -20,7 +22,11 @@ static MetricReader createReader( PrometheusInstrumentationScope.loadInstrumentationScopeInfo(); AutoConfiguredOpenTelemetrySdk sdk = - createAutoConfiguredOpenTelemetrySdk(builder, config, readerRef, instrumentationScopeInfo); + createAutoConfiguredOpenTelemetrySdk( + builder, + readerRef, + config.getExporterOpenTelemetryProperties(), + instrumentationScopeInfo); MetricReader reader = readerRef.get(); reader.register( @@ -30,11 +36,10 @@ static MetricReader createReader( static AutoConfiguredOpenTelemetrySdk createAutoConfiguredOpenTelemetrySdk( OpenTelemetryExporter.Builder builder, - PrometheusProperties config, AtomicReference readerRef, + ExporterOpenTelemetryProperties properties, InstrumentationScopeInfo instrumentationScopeInfo) { - PropertyMapper propertyMapper = - PropertyMapper.create(config.getExporterOpenTelemetryProperties(), builder); + PropertyMapper propertyMapper = PropertyMapper.create(properties, builder); return AutoConfiguredOpenTelemetrySdk.builder() .addPropertiesSupplier(() -> propertyMapper.configLowPriority) @@ -46,16 +51,17 @@ static AutoConfiguredOpenTelemetrySdk createAutoConfiguredOpenTelemetrySdk( return reader; }) .addResourceCustomizer( - (resource, unused) -> getResource(builder, config, resource, instrumentationScopeInfo)) + (resource, c) -> + getResource(builder, resource, instrumentationScopeInfo, c, properties)) .build(); } private static Resource getResource( OpenTelemetryExporter.Builder builder, - PrometheusProperties config, Resource resource, - InstrumentationScopeInfo instrumentationScopeInfo) { - ExporterOpenTelemetryProperties properties = config.getExporterOpenTelemetryProperties(); + InstrumentationScopeInfo instrumentationScopeInfo, + ConfigProperties configProperties, + ExporterOpenTelemetryProperties properties) { return resource .merge( PropertiesResourceProvider.mergeResource( @@ -64,6 +70,7 @@ private static Resource getResource( builder.serviceNamespace, builder.serviceInstanceId, builder.serviceVersion)) + .merge(ResourceConfiguration.createEnvironmentResource(configProperties)) .merge( PropertiesResourceProvider.mergeResource( properties.getResourceAttributes(), diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/resources/lib/.gitignore b/prometheus-metrics-exporter-opentelemetry/src/main/resources/lib/.gitignore new file mode 100644 index 000000000..d392f0e82 --- /dev/null +++ b/prometheus-metrics-exporter-opentelemetry/src/main/resources/lib/.gitignore @@ -0,0 +1 @@ +*.jar diff --git a/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfigTest.java b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfigTest.java new file mode 100644 index 000000000..1cf92d45f --- /dev/null +++ b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfigTest.java @@ -0,0 +1,74 @@ +package io.prometheus.metrics.exporter.opentelemetry; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.prometheus.metrics.config.ExporterOpenTelemetryProperties; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class OtelAutoConfigTest { + + static class TestCase { + Map systemProperties = Collections.emptyMap(); + Map> expected; + + public TestCase(Map> expected) { + this.expected = expected; + } + + public TestCase setSystemProperties(Map systemProperties) { + this.systemProperties = systemProperties; + return this; + } + } + + public static Stream testCases() { + return Stream.of( + Arguments.of( + "endpoint from system property", + new TestCase( + ImmutableMap.of( + "otel.exporter.otlp.endpoint", Optional.of("http://lgtm:4317"), + "otel.exporter.otlp.metrics.endpoint", Optional.empty(), + "otel.exporter.otlp.metrics.protocol", Optional.empty())) + .setSystemProperties( + Collections.singletonMap("otel.exporter.otlp.endpoint", "http://lgtm:4317")))); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("testCases") + void properties(String name, TestCase testCase) { + testCase.systemProperties.forEach(System::setProperty); + + try { + AutoConfiguredOpenTelemetrySdk sdk = + OtelAutoConfig.createAutoConfiguredOpenTelemetrySdk( + OpenTelemetryExporter.builder(), + new AtomicReference<>(), + ExporterOpenTelemetryProperties.builder().build(), + PrometheusInstrumentationScope.loadInstrumentationScopeInfo()); + + ConfigProperties config = AutoConfigureUtil.getConfig(sdk); + testCase.expected.forEach( + (key, value) -> { + if (value.isPresent()) { + assertThat(config.getString(key)).isEqualTo(value.get()); + } else { + assertThat(config.getString(key)).isNull(); + } + }); + } finally { + testCase.systemProperties.keySet().forEach(System::clearProperty); + } + } +} From 347127f81b9d1ede76b0f1d318c76d7e7a506e81 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 14 Oct 2024 16:57:14 +0200 Subject: [PATCH 04/12] add oats test Signed-off-by: Gregor Zeitlinger --- .../{oats.Dockerfile => oats-tests/agent/Dockerfile} | 2 +- .../agent/docker-compose.yml} | 3 ++- .../{ => oats-tests/agent}/oats.yaml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) rename examples/example-exporter-opentelemetry/{oats.Dockerfile => oats-tests/agent/Dockerfile} (86%) rename examples/example-exporter-opentelemetry/{docker-compose.oats.yml => oats-tests/agent/docker-compose.yml} (85%) rename examples/example-exporter-opentelemetry/{ => oats-tests/agent}/oats.yaml (88%) diff --git a/examples/example-exporter-opentelemetry/oats.Dockerfile b/examples/example-exporter-opentelemetry/oats-tests/agent/Dockerfile similarity index 86% rename from examples/example-exporter-opentelemetry/oats.Dockerfile rename to examples/example-exporter-opentelemetry/oats-tests/agent/Dockerfile index bd34a9266..35f35d46f 100644 --- a/examples/example-exporter-opentelemetry/oats.Dockerfile +++ b/examples/example-exporter-opentelemetry/oats-tests/agent/Dockerfile @@ -5,4 +5,4 @@ COPY target/example-exporter-opentelemetry.jar ./app.jar ADD --chmod=644 https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.8.0/opentelemetry-javaagent.jar /usr/src/app/opentelemetry-javaagent.jar ENV JAVA_TOOL_OPTIONS=-javaagent:/usr/src/app/opentelemetry-javaagent.jar -ENTRYPOINT [ "java", "-jar", "./app.jar" ] +ENTRYPOINT [ "java", "-Dotel.javaagent.debug=true","-jar", "./app.jar" ] diff --git a/examples/example-exporter-opentelemetry/docker-compose.oats.yml b/examples/example-exporter-opentelemetry/oats-tests/agent/docker-compose.yml similarity index 85% rename from examples/example-exporter-opentelemetry/docker-compose.oats.yml rename to examples/example-exporter-opentelemetry/oats-tests/agent/docker-compose.yml index 389566b38..9c0fc66fb 100644 --- a/examples/example-exporter-opentelemetry/docker-compose.oats.yml +++ b/examples/example-exporter-opentelemetry/oats-tests/agent/docker-compose.yml @@ -4,7 +4,8 @@ version: '3.4' services: java: build: - dockerfile: oats.Dockerfile + context: ../.. + dockerfile: oats-tests/agent/Dockerfile environment: OTEL_SERVICE_NAME: "rolldice" OTEL_EXPORTER_OTLP_ENDPOINT: http://lgtm:4317 diff --git a/examples/example-exporter-opentelemetry/oats.yaml b/examples/example-exporter-opentelemetry/oats-tests/agent/oats.yaml similarity index 88% rename from examples/example-exporter-opentelemetry/oats.yaml rename to examples/example-exporter-opentelemetry/oats-tests/agent/oats.yaml index b616fe52b..a3af9ffc2 100644 --- a/examples/example-exporter-opentelemetry/oats.yaml +++ b/examples/example-exporter-opentelemetry/oats-tests/agent/oats.yaml @@ -2,7 +2,7 @@ docker-compose: generator: docker-lgtm files: - - ./docker-compose.oats.yml + - ./docker-compose.yml expected: metrics: - promql: 'uptime_seconds_total{}' From e6af1242c189239e5bae6dee324969a271f11e68 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 14 Oct 2024 17:20:36 +0200 Subject: [PATCH 05/12] add oats test Signed-off-by: Gregor Zeitlinger --- .github/workflows/acceptance-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 70a8c0130..5dff1c62e 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v4 with: repository: grafana/oats - ref: 9a79819efcde37f025613914708dd1ba721e5ddc + ref: 3883730d6cd310cc91bfdc5d5955405fac7a421c path: oats - name: Set up JDK uses: actions/setup-java@v4 From dccc62e117ed3e938e11f806607ba0467a1ef040 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 14 Oct 2024 17:27:46 +0200 Subject: [PATCH 06/12] add oats test Signed-off-by: Gregor Zeitlinger --- .github/workflows/acceptance-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 5dff1c62e..82551cfa6 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -38,4 +38,4 @@ jobs: if: failure() with: name: docker-compose.log - path: oats/yaml/build/**/output.log + path: oats/yaml/build/**/*.log From 1df6a7784335db7c1ed33b879077460c09ba0227 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 14 Oct 2024 18:23:15 +0200 Subject: [PATCH 07/12] add oats test Signed-off-by: Gregor Zeitlinger --- .../oats-tests/agent/Dockerfile | 3 ++- .../oats-tests/http/Dockerfile | 5 +++++ .../oats-tests/http/docker-compose.yml | 13 +++++++++++++ .../oats-tests/http/oats.yaml | 10 ++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 examples/example-exporter-opentelemetry/oats-tests/http/Dockerfile create mode 100644 examples/example-exporter-opentelemetry/oats-tests/http/docker-compose.yml create mode 100644 examples/example-exporter-opentelemetry/oats-tests/http/oats.yaml diff --git a/examples/example-exporter-opentelemetry/oats-tests/agent/Dockerfile b/examples/example-exporter-opentelemetry/oats-tests/agent/Dockerfile index 35f35d46f..23f980005 100644 --- a/examples/example-exporter-opentelemetry/oats-tests/agent/Dockerfile +++ b/examples/example-exporter-opentelemetry/oats-tests/agent/Dockerfile @@ -5,4 +5,5 @@ COPY target/example-exporter-opentelemetry.jar ./app.jar ADD --chmod=644 https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.8.0/opentelemetry-javaagent.jar /usr/src/app/opentelemetry-javaagent.jar ENV JAVA_TOOL_OPTIONS=-javaagent:/usr/src/app/opentelemetry-javaagent.jar -ENTRYPOINT [ "java", "-Dotel.javaagent.debug=true","-jar", "./app.jar" ] +#ENTRYPOINT [ "java", "-Dotel.javaagent.debug=true","-jar", "./app.jar" ] # for debugging +ENTRYPOINT [ "java", "-jar", "./app.jar" ] diff --git a/examples/example-exporter-opentelemetry/oats-tests/http/Dockerfile b/examples/example-exporter-opentelemetry/oats-tests/http/Dockerfile new file mode 100644 index 000000000..22191f07e --- /dev/null +++ b/examples/example-exporter-opentelemetry/oats-tests/http/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre + +COPY target/example-exporter-opentelemetry.jar ./app.jar + +ENTRYPOINT [ "java", "-jar", "./app.jar" ] diff --git a/examples/example-exporter-opentelemetry/oats-tests/http/docker-compose.yml b/examples/example-exporter-opentelemetry/oats-tests/http/docker-compose.yml new file mode 100644 index 000000000..cec15bf19 --- /dev/null +++ b/examples/example-exporter-opentelemetry/oats-tests/http/docker-compose.yml @@ -0,0 +1,13 @@ +# OATS is an acceptance testing framework for OpenTelemetry - https://github.com/grafana/oats/tree/main/yaml +version: '3.4' + +services: + java: + build: + context: ../.. + dockerfile: oats-tests/http/Dockerfile + environment: + OTEL_SERVICE_NAME: "rolldice" + OTEL_EXPORTER_OTLP_ENDPOINT: http://lgtm:4318 + OTEL_EXPORTER_OTLP_PROTOCOL: http/protobuf + OTEL_METRIC_EXPORT_INTERVAL: "5000" # so we don't have to wait 60s for metrics diff --git a/examples/example-exporter-opentelemetry/oats-tests/http/oats.yaml b/examples/example-exporter-opentelemetry/oats-tests/http/oats.yaml new file mode 100644 index 000000000..a3af9ffc2 --- /dev/null +++ b/examples/example-exporter-opentelemetry/oats-tests/http/oats.yaml @@ -0,0 +1,10 @@ +# OATS is an acceptance testing framework for OpenTelemetry - https://github.com/grafana/oats/tree/main/yaml +docker-compose: + generator: docker-lgtm + files: + - ./docker-compose.yml +expected: + metrics: + - promql: 'uptime_seconds_total{}' + value: '>= 0' + From cf3d9a399a3f0278f3541bf905ab757443b49590 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 15 Oct 2024 08:25:27 +0200 Subject: [PATCH 08/12] extra project for otel resource attributes Signed-off-by: Gregor Zeitlinger --- otel-agent-resources/pom.xml | 67 +++++++++++++++++++ .../ResourceAttributesFromOtelAgent.java | 21 ++++-- .../src/main/resources/lib/.gitignore | 0 pom.xml | 1 + .../pom.xml | 36 ++-------- .../opentelemetry/OtelAutoConfig.java | 8 ++- 6 files changed, 96 insertions(+), 37 deletions(-) create mode 100644 otel-agent-resources/pom.xml rename {prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry => otel-agent-resources/src/main/java/io/prometheus/otelagent}/ResourceAttributesFromOtelAgent.java (84%) rename {prometheus-metrics-exporter-opentelemetry => otel-agent-resources}/src/main/resources/lib/.gitignore (100%) diff --git a/otel-agent-resources/pom.xml b/otel-agent-resources/pom.xml new file mode 100644 index 000000000..f868cc6a5 --- /dev/null +++ b/otel-agent-resources/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + + io.prometheus + client_java + 1.4.0-SNAPSHOT + + + otel-agent-resources + bundle + + OpenTelemetry Agent Resource Extractor + + Reads the OpenTelemetry Agent resources the GlobalOpenTelemetry instance + + + + io.prometheus.otel.resource.attributes + + 1.29.0 + + + + + + + + + src/main/resources + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + validate + + copy + + + + + + + io.opentelemetry + opentelemetry-api + ${otel-dynamic-load.version} + ${project.basedir}/src/main/resources/lib/ + + + io.opentelemetry + opentelemetry-context + ${otel-dynamic-load.version} + ${project.basedir}/src/main/resources/lib/ + + + + + + + diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesFromOtelAgent.java b/otel-agent-resources/src/main/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgent.java similarity index 84% rename from prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesFromOtelAgent.java rename to otel-agent-resources/src/main/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgent.java index caf0246f5..6019268ed 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/ResourceAttributesFromOtelAgent.java +++ b/otel-agent-resources/src/main/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgent.java @@ -1,8 +1,7 @@ -package io.prometheus.metrics.exporter.opentelemetry; +package io.prometheus.otelagent; import static java.nio.file.Files.createTempDirectory; -import io.opentelemetry.sdk.resources.Resource; import java.io.File; import java.io.InputStream; import java.lang.reflect.Field; @@ -12,6 +11,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; public class ResourceAttributesFromOtelAgent { @@ -32,7 +34,7 @@ public class ResourceAttributesFromOtelAgent { *

After that we discard the class loader so that all OTel specific classes are unloaded. No * runtime dependency on any OTel version remains. */ - public static Resource get(String instrumentationScopeName) { + public static Map getResourceAttributes(String instrumentationScopeName) { try { Path tmpDir = createTempDirectory(instrumentationScopeName + "-"); try { @@ -49,7 +51,16 @@ public static Resource get(String instrumentationScopeName) { Object sdkMeterProvider = getField("delegate", agentMeterProvider); Object sharedState = getField("sharedState", sdkMeterProvider); Object resource = callMethod("getResource", sharedState); - return (Resource) resource; + Object attributes = callMethod("getAttributes", resource); + Map attributeMap = (Map) callMethod("asMap", attributes); + + Map result = new HashMap<>(); + for (Map.Entry entry : attributeMap.entrySet()) { + if (entry.getKey() != null && entry.getValue() != null) { + result.put(entry.getKey().toString(), entry.getValue().toString()); + } + } + return result; } } } finally { @@ -58,7 +69,7 @@ public static Resource get(String instrumentationScopeName) { } catch (Exception ignored) { // ignore } - return Resource.empty(); + return Collections.emptyMap(); } private static Object getField(String name, Object obj) throws Exception { diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/resources/lib/.gitignore b/otel-agent-resources/src/main/resources/lib/.gitignore similarity index 100% rename from prometheus-metrics-exporter-opentelemetry/src/main/resources/lib/.gitignore rename to otel-agent-resources/src/main/resources/lib/.gitignore diff --git a/pom.xml b/pom.xml index 6ab7cbb04..273f97881 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,7 @@ prometheus-metrics-instrumentation-dropwizard5 prometheus-metrics-instrumentation-guava prometheus-metrics-simpleclient-bridge + otel-agent-resources diff --git a/prometheus-metrics-exporter-opentelemetry/pom.xml b/prometheus-metrics-exporter-opentelemetry/pom.xml index 2b7197b2f..d9ecaaa1c 100644 --- a/prometheus-metrics-exporter-opentelemetry/pom.xml +++ b/prometheus-metrics-exporter-opentelemetry/pom.xml @@ -39,6 +39,11 @@ prometheus-metrics-core ${project.version} + + io.prometheus + otel-agent-resources + ${project.version} + io.opentelemetry opentelemetry-api @@ -93,9 +98,6 @@ - - src/main/resources - src/main/resources-filtered true @@ -122,34 +124,6 @@ - - org.apache.maven.plugins - maven-dependency-plugin - - - copy - package - - copy - - - - - - - io.opentelemetry - opentelemetry-api - ${project.basedir}/src/main/resources/lib/ - - - io.opentelemetry - opentelemetry-context - ${project.basedir}/src/main/resources/lib/ - - - - - org.apache.maven.plugins maven-shade-plugin diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java index 5732cec9b..52a8e2ead 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java @@ -1,5 +1,7 @@ package io.prometheus.metrics.exporter.opentelemetry; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.ResourceConfiguration; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -9,6 +11,7 @@ import io.prometheus.metrics.config.ExporterOpenTelemetryProperties; import io.prometheus.metrics.config.PrometheusProperties; import io.prometheus.metrics.model.registry.PrometheusRegistry; +import io.prometheus.otelagent.ResourceAttributesFromOtelAgent; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicReference; @@ -62,6 +65,9 @@ private static Resource getResource( InstrumentationScopeInfo instrumentationScopeInfo, ConfigProperties configProperties, ExporterOpenTelemetryProperties properties) { + AttributesBuilder agentAttributesBuilder = Attributes.builder(); + ResourceAttributesFromOtelAgent.getResourceAttributes(instrumentationScopeInfo.getName()) + .forEach(agentAttributesBuilder::put); return resource .merge( PropertiesResourceProvider.mergeResource( @@ -78,7 +84,7 @@ private static Resource getResource( properties.getServiceNamespace(), properties.getServiceInstanceId(), properties.getServiceVersion())) - .merge(ResourceAttributesFromOtelAgent.get(instrumentationScopeInfo.getName())); + .merge(Resource.create(agentAttributesBuilder.build())); } private static Resource getResourceField(AutoConfiguredOpenTelemetrySdk sdk) { From 11b32628e05e990e62f85e03d9d441cde641db35 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 15 Oct 2024 08:28:43 +0200 Subject: [PATCH 09/12] extra project for otel resource attributes Signed-off-by: Gregor Zeitlinger --- .../prometheus/otelagent/ResourceAttributesFromOtelAgent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/otel-agent-resources/src/main/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgent.java b/otel-agent-resources/src/main/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgent.java index 6019268ed..98829c86a 100644 --- a/otel-agent-resources/src/main/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgent.java +++ b/otel-agent-resources/src/main/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgent.java @@ -60,7 +60,7 @@ public static Map getResourceAttributes(String instrumentationSc result.put(entry.getKey().toString(), entry.getValue().toString()); } } - return result; + return Collections.unmodifiableMap(result); } } } finally { From 8f7412549d5db304c3831d23c87b63a7af605866 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 15 Oct 2024 10:22:36 +0200 Subject: [PATCH 10/12] extra project for otel resource attributes Signed-off-by: Gregor Zeitlinger --- .github/workflows/acceptance-tests.yml | 2 +- .../oats-tests/agent/oats.yaml | 2 ++ .../agent/service-instance-id-check.py | 33 +++++++++++++++++++ .../ResourceAttributesFromOtelAgent.java | 3 ++ .../opentelemetry/OtelAutoConfig.java | 26 ++++++++++++--- 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100755 examples/example-exporter-opentelemetry/oats-tests/agent/service-instance-id-check.py diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 82551cfa6..590c12946 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v4 with: repository: grafana/oats - ref: 3883730d6cd310cc91bfdc5d5955405fac7a421c + ref: 7cd5ca42fff009fd67ea986d42c79134ac99ae48 path: oats - name: Set up JDK uses: actions/setup-java@v4 diff --git a/examples/example-exporter-opentelemetry/oats-tests/agent/oats.yaml b/examples/example-exporter-opentelemetry/oats-tests/agent/oats.yaml index a3af9ffc2..08e4d8d3f 100644 --- a/examples/example-exporter-opentelemetry/oats-tests/agent/oats.yaml +++ b/examples/example-exporter-opentelemetry/oats-tests/agent/oats.yaml @@ -4,6 +4,8 @@ docker-compose: files: - ./docker-compose.yml expected: + custom-checks: + - script: ./service-instance-id-check.py metrics: - promql: 'uptime_seconds_total{}' value: '>= 0' diff --git a/examples/example-exporter-opentelemetry/oats-tests/agent/service-instance-id-check.py b/examples/example-exporter-opentelemetry/oats-tests/agent/service-instance-id-check.py new file mode 100755 index 000000000..196c629c9 --- /dev/null +++ b/examples/example-exporter-opentelemetry/oats-tests/agent/service-instance-id-check.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +# This script is used to check if the service instance id is present in the exported data +# The script will return 0 if the service instance id is present in the exported data + +from urllib.request import urlopen +import urllib.parse +import json + +url = ' http://localhost:9090/api/v1/label/instance/values' +res = json.loads(urlopen(url).read().decode('utf-8')) + +values = list(res['data']) +print(values) + +if "localhost:8888" in values: + values.remove("localhost:8888") + +# both the agent and the exporter should report the same instance id +assert len(values) == 1 + +path = 'target_info{instance="%s"}' % values[0] +path = urllib.parse.quote_plus(path) +url = 'http://localhost:9090/api/v1/query?query=%s' % path +res = json.loads(urlopen(url).read().decode('utf-8')) + +infos = res['data']['result'] +print(infos) + +# they should not have the same target info +# e.g. only the agent has telemetry_distro_name +assert len(infos) == 2 + diff --git a/otel-agent-resources/src/main/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgent.java b/otel-agent-resources/src/main/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgent.java index 98829c86a..215b79de5 100644 --- a/otel-agent-resources/src/main/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgent.java +++ b/otel-agent-resources/src/main/java/io/prometheus/otelagent/ResourceAttributesFromOtelAgent.java @@ -33,6 +33,9 @@ public class ResourceAttributesFromOtelAgent { * *

After that we discard the class loader so that all OTel specific classes are unloaded. No * runtime dependency on any OTel version remains. + * + *

The test for this class is in + * examples/example-exporter-opentelemetry/oats-tests/agent/service-instance-id-check.py */ public static Map getResourceAttributes(String instrumentationScopeName) { try { diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java index 52a8e2ead..f66d720a9 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java @@ -13,9 +13,13 @@ import io.prometheus.metrics.model.registry.PrometheusRegistry; import io.prometheus.otelagent.ResourceAttributesFromOtelAgent; import java.lang.reflect.Method; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; public class OtelAutoConfig { + + private static final String SERVICE_INSTANCE_ID = "service.instance.id"; + static MetricReader createReader( OpenTelemetryExporter.Builder builder, PrometheusProperties config, @@ -65,9 +69,6 @@ private static Resource getResource( InstrumentationScopeInfo instrumentationScopeInfo, ConfigProperties configProperties, ExporterOpenTelemetryProperties properties) { - AttributesBuilder agentAttributesBuilder = Attributes.builder(); - ResourceAttributesFromOtelAgent.getResourceAttributes(instrumentationScopeInfo.getName()) - .forEach(agentAttributesBuilder::put); return resource .merge( PropertiesResourceProvider.mergeResource( @@ -84,7 +85,24 @@ private static Resource getResource( properties.getServiceNamespace(), properties.getServiceInstanceId(), properties.getServiceVersion())) - .merge(Resource.create(agentAttributesBuilder.build())); + .merge(Resource.create(otelResourceAttributes(instrumentationScopeInfo))); + } + + /** + * Only copy the service instance id from the Otel agent resource attributes. + * + *

All other attributes are calculated from the configuration using OTel SDK AutoConfig. + */ + private static Attributes otelResourceAttributes( + InstrumentationScopeInfo instrumentationScopeInfo) { + AttributesBuilder builder = Attributes.builder(); + Map attributes = + ResourceAttributesFromOtelAgent.getResourceAttributes(instrumentationScopeInfo.getName()); + String id = attributes.get(SERVICE_INSTANCE_ID); + if (id != null) { + builder.put(SERVICE_INSTANCE_ID, id); + } + return builder.build(); } private static Resource getResourceField(AutoConfiguredOpenTelemetrySdk sdk) { From 8254bc1c085f38f6b2884144c1ed7980b80f78d2 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 15 Oct 2024 12:10:18 +0200 Subject: [PATCH 11/12] add otel tests Signed-off-by: Gregor Zeitlinger --- CONTRIBUTING.md | 2 + .../ExporterOpenTelemetryProperties.java | 1 - .../io/prometheus/metrics/config/Util.java | 2 +- .../opentelemetry/OtelAutoConfig.java | 2 +- .../opentelemetry/PropertyMapper.java | 64 +++-- .../opentelemetry/OtelAutoConfigTest.java | 258 ++++++++++++++++-- 6 files changed, 292 insertions(+), 37 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9e3d5779..79cd4b6ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,8 @@ This repository uses [Google Java Format](https://github.com/google/google-java- Run `./mvnw spotless:apply` to format the code (only changed files) before committing. +Use `-Dspotless.check.skip=true` to skip the formatting check during development. + ## Running Tests If you're getting errors when running tests: diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java index bf584a505..d009d2603 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterOpenTelemetryProperties.java @@ -199,7 +199,6 @@ public Builder serviceVersion(String serviceVersion) { return this; } - // todo add test public Builder resourceAttribute(String name, String value) { this.resourceAttributes.put(name, value); return this; diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java index c152d2c75..15a61f7fe 100644 --- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java +++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java @@ -95,7 +95,7 @@ static Map loadMap(String name, Map properties) String[] pairs = property.split(","); for (String pair : pairs) { if (pair.contains("=")) { - String[] keyValue = pair.split("=", 1); + String[] keyValue = pair.split("=", 2); if (keyValue.length == 2) { String key = keyValue[0].trim(); String value = keyValue[1].trim(); diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java index f66d720a9..fb6ebff7e 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfig.java @@ -105,7 +105,7 @@ private static Attributes otelResourceAttributes( return builder.build(); } - private static Resource getResourceField(AutoConfiguredOpenTelemetrySdk sdk) { + static Resource getResourceField(AutoConfiguredOpenTelemetrySdk sdk) { try { Method method = AutoConfiguredOpenTelemetrySdk.class.getDeclaredMethod("getResource"); method.setAccessible(true); diff --git a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java index 153405072..be30197fa 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java +++ b/prometheus-metrics-exporter-opentelemetry/src/main/java/io/prometheus/metrics/exporter/opentelemetry/PropertyMapper.java @@ -5,6 +5,7 @@ import io.prometheus.metrics.config.PrometheusPropertiesException; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; class PropertyMapper { @@ -15,14 +16,14 @@ class PropertyMapper { static PropertyMapper create( ExporterOpenTelemetryProperties properties, OpenTelemetryExporter.Builder builder) throws PrometheusPropertiesException { - String protocol = properties.getProtocol(); return new PropertyMapper() - .addString(builder.protocol, protocol, "otel.exporter.otlp.metrics.protocol") + .addString( + builder.protocol, properties.getProtocol(), "otel.exporter.otlp.metrics.protocol") .addString(builder.endpoint, properties.getEndpoint(), METRICS_ENDPOINT) .addString( mapToOtelString(builder.headers), mapToOtelString(properties.getHeaders()), - "otel.exporter.otlp.metric.headers") + "otel.exporter.otlp.metrics.headers") .addString(builder.interval, properties.getInterval(), "otel.metric.export.interval") .addString(builder.timeout, properties.getTimeout(), "otel.exporter.otlp.metrics.timeout") .addString(builder.serviceName, properties.getServiceName(), "otel.service.name"); @@ -30,7 +31,10 @@ static PropertyMapper create( PropertyMapper addString(String builderValue, String propertyValue, String otelKey) { if (builderValue != null) { - configLowPriority.put(otelKey, builderValue); + // the low priority config should not be used for the metrics settings, so that both general + // and metrics settings + // can be used to override the values + configLowPriority.put(otelKey.replace("otlp.metrics", "otlp"), builderValue); } if (propertyValue != null) { configHighPriority.put(otelKey, propertyValue); @@ -50,16 +54,50 @@ private static String mapToOtelString(Map map) { } static Map customizeProperties(Map result, ConfigProperties c) { - Map map = addEndpointPath(result, c); + Map map = fixEndpointPaths(result, c); map.put("otel.logs.exporter", "none"); map.put("otel.traces.exporter", "none"); return map; } - static Map addEndpointPath(Map result, ConfigProperties c) { - String endpoint = c.getString(METRICS_ENDPOINT); + static Map fixEndpointPaths(Map result, ConfigProperties c) { + transformEndpointPath( + result, + c, + METRICS_ENDPOINT, + endpoint -> { + if (!endpoint.endsWith("v1/metrics")) { + if (!endpoint.endsWith("/")) { + return endpoint + "/v1/metrics"; + } else { + return endpoint + "v1/metrics"; + } + } + return endpoint; + }); + + transformEndpointPath( + result, + c, + "otel.exporter.otlp.endpoint", + endpoint -> { + if (endpoint.endsWith("v1/metrics")) { + return endpoint.substring(0, endpoint.length() - "v1/metrics".length()); + } + return endpoint; + }); + + return result; + } + + static void transformEndpointPath( + Map result, + ConfigProperties c, + String key, + Function valueMapper) { + String endpoint = c.getString(key); if (endpoint == null) { - return result; + return; } String protocol = c.getString("otel.exporter.otlp.metrics.protocol"); if (protocol == null) { @@ -67,14 +105,8 @@ static Map addEndpointPath(Map result, ConfigPro } if (!"grpc".equals(protocol)) { // http/protobuf - if (!endpoint.endsWith("v1/metrics")) { - if (!endpoint.endsWith("/")) { - result.put(METRICS_ENDPOINT, endpoint + "/v1/metrics"); - } else { - result.put(METRICS_ENDPOINT, endpoint + "v1/metrics"); - } - } + endpoint = valueMapper.apply(endpoint); + result.put(key, endpoint); } - return result; } } diff --git a/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfigTest.java b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfigTest.java index 1cf92d45f..9b9b12420 100644 --- a/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfigTest.java +++ b/prometheus-metrics-exporter-opentelemetry/src/test/java/io/prometheus/metrics/exporter/opentelemetry/OtelAutoConfigTest.java @@ -3,15 +3,20 @@ import static org.assertj.core.api.Assertions.assertThat; import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.prometheus.metrics.config.ExporterOpenTelemetryProperties; +import io.prometheus.metrics.config.PrometheusPropertiesLoader; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import java.util.stream.Stream; +import org.assertj.core.api.AbstractStringAssert; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -19,15 +24,37 @@ class OtelAutoConfigTest { static class TestCase { - Map systemProperties = Collections.emptyMap(); - Map> expected; + Map systemProperties = new HashMap<>(); + Map> expectedProperties = Collections.emptyMap(); + Map expectedResourceAttributes = Collections.emptyMap(); + Consumer exporterBuilder; + Consumer propertiesBuilder; - public TestCase(Map> expected) { - this.expected = expected; + public TestCase() {} + + public TestCase expectedProperties(Map> expectedProperties) { + this.expectedProperties = expectedProperties; + return this; } - public TestCase setSystemProperties(Map systemProperties) { - this.systemProperties = systemProperties; + public TestCase expectedResourceAttributes(Map expectedResourceAttributes) { + this.expectedResourceAttributes = expectedResourceAttributes; + return this; + } + + public TestCase systemProperties(Map systemProperties) { + this.systemProperties.putAll(systemProperties); + return this; + } + + public TestCase exporterBuilder(Consumer exporterBuilder) { + this.exporterBuilder = exporterBuilder; + return this; + } + + public TestCase propertiesBuilder( + Consumer propertiesBuilder) { + this.propertiesBuilder = propertiesBuilder; return this; } } @@ -35,14 +62,187 @@ public TestCase setSystemProperties(Map systemProperties) { public static Stream testCases() { return Stream.of( Arguments.of( - "endpoint from system property", - new TestCase( + "values from builder", + new TestCase() + .expectedProperties( + ImmutableMap.>builder() + .put("otel.exporter.otlp.protocol", Optional.of("http/protobuf")) + .put("otel.exporter.otlp.endpoint", Optional.of("http://builder:4318")) + .put("otel.exporter.otlp.headers", Optional.of("h=builder-v")) + .put("otel.metric.export.interval", Optional.of("2s")) + .put("otel.exporter.otlp.timeout", Optional.of("3s")) + .put("otel.service.name", Optional.of("builder-service")) + .build()) + .expectedResourceAttributes( + ImmutableMap.of( + "key", + "builder-value", + "service.name", + "builder-service", + "service.namespace", + "builder-namespace", + "service.instance.id", + "builder-instance", + "service.version", + "builder-version")) + .exporterBuilder(OtelAutoConfigTest::setBuilderValues)), + Arguments.of( + "builder endpoint with path", + new TestCase() + .expectedProperties( ImmutableMap.of( - "otel.exporter.otlp.endpoint", Optional.of("http://lgtm:4317"), - "otel.exporter.otlp.metrics.endpoint", Optional.empty(), - "otel.exporter.otlp.metrics.protocol", Optional.empty())) - .setSystemProperties( - Collections.singletonMap("otel.exporter.otlp.endpoint", "http://lgtm:4317")))); + "otel.exporter.otlp.endpoint", Optional.of("http://builder:4318/"))) + .exporterBuilder(builder -> builder.endpoint("http://builder:4318/v1/metrics"))), + Arguments.of( + "values from otel have precedence over builder", + new TestCase() + .expectedProperties( + ImmutableMap.>builder() + .put("otel.exporter.otlp.protocol", Optional.of("grpc")) + .put("otel.exporter.otlp.metrics.protocol", Optional.empty()) + .put("otel.exporter.otlp.endpoint", Optional.of("http://otel:4317")) + .put("otel.exporter.otlp.metrics.endpoint", Optional.empty()) + .put("otel.exporter.otlp.headers", Optional.of("h=otel-v")) + .put("otel.exporter.otlp.metrics.headers", Optional.empty()) + .put("otel.metric.export.interval", Optional.of("12s")) + .put("otel.exporter.otlp.timeout", Optional.of("13s")) + .put("otel.exporter.otlp.metrics.timeout", Optional.empty()) + .put("otel.service.name", Optional.of("otel-service")) + .build()) + .expectedResourceAttributes( + ImmutableMap.of( + "key", + "otel-value", + "service.name", + "otel-service", + "service.namespace", + "otel-namespace", + "service.instance.id", + "otel-instance", + "service.version", + "otel-version")) + .exporterBuilder(OtelAutoConfigTest::setBuilderValues) + .systemProperties(otelOverrides())), + Arguments.of( + "values from prom properties have precedence over builder and otel", + new TestCase() + .expectedProperties( + ImmutableMap.>builder() + .put("otel.exporter.otlp.metrics.protocol", Optional.of("http/protobuf")) + .put("otel.exporter.otlp.protocol", Optional.of("grpc")) + .put("otel.exporter.otlp.metrics.endpoint", Optional.of("http://prom:4317")) + .put("otel.exporter.otlp.endpoint", Optional.of("http://otel:4317")) + .put("otel.exporter.otlp.metrics.headers", Optional.of("h=prom-v")) + .put("otel.exporter.otlp.headers", Optional.of("h=otel-v")) + .put("otel.metric.export.interval", Optional.of("22s")) + .put("otel.exporter.otlp.metrics.timeout", Optional.of("23s")) + .put("otel.exporter.otlp.timeout", Optional.of("13s")) + .put("otel.service.name", Optional.of("prom-service")) + .build()) + .expectedResourceAttributes( + ImmutableMap.of( + "key", + "prom-value", + "service.name", + "prom-service", + "service.namespace", + "prom-namespace", + "service.instance.id", + "prom-instance", + "service.version", + "prom-version")) + .exporterBuilder(OtelAutoConfigTest::setBuilderValues) + .systemProperties(otelOverrides()) + .systemProperties( + ImmutableMap.builder() + .put("io.prometheus.exporter.opentelemetry.protocol", "http/protobuf") + .put("io.prometheus.exporter.opentelemetry.endpoint", "http://prom:4317") + .put("io.prometheus.exporter.opentelemetry.headers", "h=prom-v") + .put("io.prometheus.exporter.opentelemetry.intervalSeconds", "22") + .put("io.prometheus.exporter.opentelemetry.timeoutSeconds", "23") + .put("io.prometheus.exporter.opentelemetry.serviceName", "prom-service") + .put( + "io.prometheus.exporter.opentelemetry.serviceNamespace", + "prom-namespace") + .put( + "io.prometheus.exporter.opentelemetry.serviceInstanceId", + "prom-instance") + .put("io.prometheus.exporter.opentelemetry.serviceVersion", "prom-version") + .put( + "io.prometheus.exporter.opentelemetry.resourceAttributes", + "key=prom-value") + .build())), + Arguments.of( + "values from prom properties builder have precedence over builder and otel", + new TestCase() + .expectedProperties( + ImmutableMap.>builder() + .put("otel.exporter.otlp.metrics.protocol", Optional.of("http/protobuf")) + .put("otel.exporter.otlp.protocol", Optional.of("grpc")) + .put("otel.exporter.otlp.metrics.endpoint", Optional.of("http://prom:4317")) + .put("otel.exporter.otlp.endpoint", Optional.of("http://otel:4317")) + .put("otel.exporter.otlp.metrics.headers", Optional.of("h=prom-v")) + .put("otel.exporter.otlp.headers", Optional.of("h=otel-v")) + .put("otel.metric.export.interval", Optional.of("22s")) + .put("otel.exporter.otlp.metrics.timeout", Optional.of("23s")) + .put("otel.exporter.otlp.timeout", Optional.of("13s")) + .put("otel.service.name", Optional.of("prom-service")) + .build()) + .expectedResourceAttributes( + ImmutableMap.of( + "key", + "prom-value", + "service.name", + "prom-service", + "service.namespace", + "prom-namespace", + "service.instance.id", + "prom-instance", + "service.version", + "prom-version")) + .exporterBuilder(OtelAutoConfigTest::setBuilderValues) + .systemProperties(otelOverrides()) + .propertiesBuilder( + builder -> + builder + .protocol("http/protobuf") + .endpoint("http://prom:4317") + .header("h", "prom-v") + .intervalSeconds(22) + .timeoutSeconds(23) + .serviceName("prom-service") + .serviceNamespace("prom-namespace") + .serviceInstanceId("prom-instance") + .serviceVersion("prom-version") + .resourceAttribute("key", "prom-value")))); + } + + private static ImmutableMap otelOverrides() { + return ImmutableMap.builder() + .put("otel.exporter.otlp.protocol", "grpc") + .put("otel.exporter.otlp.endpoint", "http://otel:4317") + .put("otel.exporter.otlp.headers", "h=otel-v") + .put("otel.metric.export.interval", "12s") + .put("otel.exporter.otlp.timeout", "13s") + .put("otel.service.name", "otel-service") + .put( + "otel.resource.attributes", + "key=otel-value,service.namespace=otel-namespace,service.instance.id=otel-instance,service.version=otel-version") + .build(); + } + + private static void setBuilderValues(OpenTelemetryExporter.Builder builder) { + builder + .protocol("http/protobuf") + .endpoint("http://builder:4318") + .header("h", "builder-v") + .intervalSeconds(2) + .timeoutSeconds(3) + .serviceName("builder-service") + .serviceNamespace("builder-namespace") + .serviceInstanceId("builder-instance") + .serviceVersion("builder-version") + .resourceAttribute("key", "builder-value"); } @ParameterizedTest(name = "{0}") @@ -51,24 +251,46 @@ void properties(String name, TestCase testCase) { testCase.systemProperties.forEach(System::setProperty); try { + OpenTelemetryExporter.Builder builder = OpenTelemetryExporter.builder(); + if (testCase.exporterBuilder != null) { + testCase.exporterBuilder.accept(builder); + } AutoConfiguredOpenTelemetrySdk sdk = OtelAutoConfig.createAutoConfiguredOpenTelemetrySdk( - OpenTelemetryExporter.builder(), + builder, new AtomicReference<>(), - ExporterOpenTelemetryProperties.builder().build(), + getExporterOpenTelemetryProperties(testCase), PrometheusInstrumentationScope.loadInstrumentationScopeInfo()); ConfigProperties config = AutoConfigureUtil.getConfig(sdk); - testCase.expected.forEach( + Map, Object> map = + OtelAutoConfig.getResourceField(sdk).getAttributes().asMap(); + testCase.expectedProperties.forEach( (key, value) -> { + AbstractStringAssert o = assertThat(config.getString(key)).describedAs("key=" + key); if (value.isPresent()) { - assertThat(config.getString(key)).isEqualTo(value.get()); + o.isEqualTo(value.get()); } else { - assertThat(config.getString(key)).isNull(); + o.isNull(); } }); + testCase.expectedResourceAttributes.forEach( + (key, value) -> + assertThat(map.get(AttributeKey.stringKey(key))) + .describedAs("key=" + key) + .hasToString(value)); } finally { testCase.systemProperties.keySet().forEach(System::clearProperty); } } + + private static ExporterOpenTelemetryProperties getExporterOpenTelemetryProperties( + TestCase testCase) { + if (testCase.propertiesBuilder == null) { + return PrometheusPropertiesLoader.load().getExporterOpenTelemetryProperties(); + } + ExporterOpenTelemetryProperties.Builder builder = ExporterOpenTelemetryProperties.builder(); + testCase.propertiesBuilder.accept(builder); + return builder.build(); + } } From f9094ed503b46968da67e35c427e57db34ddab1d Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 17 Oct 2024 12:16:10 +0200 Subject: [PATCH 12/12] release automation Signed-off-by: Gregor Zeitlinger --- otel-agent-resources/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/otel-agent-resources/pom.xml b/otel-agent-resources/pom.xml index f868cc6a5..176c2c6eb 100644 --- a/otel-agent-resources/pom.xml +++ b/otel-agent-resources/pom.xml @@ -6,7 +6,7 @@ io.prometheus client_java - 1.4.0-SNAPSHOT + 0.0.1-releasetest1 otel-agent-resources