From b9def6ea87c3effe480aa138c946872a1dcb9ed3 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 14 Feb 2024 12:48:26 +0100 Subject: [PATCH 01/12] Added profiler integration auto config --- custom/build.gradle.kts | 1 + .../otel/UniversalProfilingProcessor.java | 2 +- ...UniversalProfilingProcessorAutoConfig.java | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java diff --git a/custom/build.gradle.kts b/custom/build.gradle.kts index 308ac6bc2..4ed711b54 100644 --- a/custom/build.gradle.kts +++ b/custom/build.gradle.kts @@ -5,6 +5,7 @@ plugins { dependencies { implementation(project(":common")) implementation(project(":inferred-spans")) + implementation(project(":universal-profiling-integration")) compileOnly(project(":bootstrap")) implementation(project(":resources")) compileOnly("io.opentelemetry:opentelemetry-sdk") diff --git a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessor.java b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessor.java index 4280d3e6a..196dff191 100644 --- a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessor.java +++ b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessor.java @@ -154,7 +154,7 @@ private String openProfilerSocket(String socketDir) { } while (Files.exists(socketFile)); String absolutePath = socketFile.toAbsolutePath().toString(); - log.log(Level.FINE, "Opening profiler correlation socket '{0}'", absolutePath); + log.log(Level.FINE, "Opening profiler correlation socket {0}", new Object[] {absolutePath}); UniversalProfilingCorrelation.startProfilerReturnChannel(absolutePath); return absolutePath; } diff --git a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java new file mode 100644 index 000000000..c2b03de2d --- /dev/null +++ b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java @@ -0,0 +1,20 @@ +package co.elastic.otel; + +import co.elastic.otel.common.ChainingSpanProcessorAutoConfiguration; +import co.elastic.otel.common.ChainingSpanProcessorRegisterer; +import com.google.auto.service.AutoService; +import io.opentelemetry.sdk.autoconfigure.ResourceConfiguration; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.resources.Resource; + +@AutoService(ChainingSpanProcessorAutoConfiguration.class) +public class UniversalProfilingProcessorAutoConfig implements + ChainingSpanProcessorAutoConfiguration { + @Override + public void registerSpanProcessors(ConfigProperties properties, + ChainingSpanProcessorRegisterer registerer) { + Resource resource = ResourceConfiguration.createEnvironmentResource(properties); + registerer.register(next -> UniversalProfilingProcessor.builder(next, resource).build()); + + } +} From 4ce5635d656b0e47b44e2d3e35d39afc8836e8d7 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 16 Apr 2024 14:43:37 +0200 Subject: [PATCH 02/12] Move PropertiesApplier to common project --- .../otel/common/config/PropertiesApplier.java | 54 +++++++++++++++++++ .../otel/common}/config/WildcardMatcher.java | 10 ++-- .../profiler/InferredSpansAutoConfig.java | 52 +----------------- .../profiler/InferredSpansConfiguration.java | 2 +- .../InferredSpansProcessorBuilder.java | 2 +- .../otel/profiler/SamplingProfiler.java | 2 +- .../profiler/asyncprofiler/JfrParser.java | 2 +- .../profiler/InferredSpansAutoConfigTest.java | 2 +- .../profiler/asyncprofiler/JfrParserTest.java | 2 +- 9 files changed, 66 insertions(+), 62 deletions(-) create mode 100644 common/src/main/java/co/elastic/otel/common/config/PropertiesApplier.java rename {inferred-spans/src/main/java/co/elastic/otel/profiler => common/src/main/java/co/elastic/otel/common}/config/WildcardMatcher.java (98%) diff --git a/common/src/main/java/co/elastic/otel/common/config/PropertiesApplier.java b/common/src/main/java/co/elastic/otel/common/config/PropertiesApplier.java new file mode 100644 index 000000000..a57320926 --- /dev/null +++ b/common/src/main/java/co/elastic/otel/common/config/PropertiesApplier.java @@ -0,0 +1,54 @@ +package co.elastic.otel.common.config; + +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class PropertiesApplier { + + private final ConfigProperties properties; + + public PropertiesApplier(ConfigProperties properties) { + this.properties = properties; + } + + public void applyBool(String configKey, Consumer funcToApply) { + applyValue(properties.getBoolean(configKey), funcToApply); + } + + public void applyInt(String configKey, Consumer funcToApply) { + applyValue(properties.getInt(configKey), funcToApply); + } + + public void applyDuration(String configKey, Consumer funcToApply) { + applyValue(properties.getDuration(configKey), funcToApply); + } + + public void applyString(String configKey, Consumer funcToApply) { + applyValue(properties.getString(configKey), funcToApply); + } + + public void applyWildcards(String configKey, + Consumer> funcToApply) { + String wildcardListString = properties.getString(configKey); + if (wildcardListString != null && !wildcardListString.isEmpty()) { + List values = + Arrays.stream(wildcardListString.split(",")) + .filter(str -> !str.isEmpty()) + .map(WildcardMatcher::valueOf) + .collect(Collectors.toList()); + if (!values.isEmpty()) { + funcToApply.accept(values); + } + } + } + + private static void applyValue(T value, Consumer funcToApply) { + if (value != null) { + funcToApply.accept(value); + } + } +} diff --git a/inferred-spans/src/main/java/co/elastic/otel/profiler/config/WildcardMatcher.java b/common/src/main/java/co/elastic/otel/common/config/WildcardMatcher.java similarity index 98% rename from inferred-spans/src/main/java/co/elastic/otel/profiler/config/WildcardMatcher.java rename to common/src/main/java/co/elastic/otel/common/config/WildcardMatcher.java index a024bf74f..e825a03ad 100644 --- a/inferred-spans/src/main/java/co/elastic/otel/profiler/config/WildcardMatcher.java +++ b/common/src/main/java/co/elastic/otel/common/config/WildcardMatcher.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package co.elastic.otel.profiler.config; +package co.elastic.otel.common.config; import java.util.ArrayList; import java.util.Collections; @@ -334,10 +334,10 @@ static class SimpleWildcardMatcher extends WildcardMatcher { this.ignoreCase = ignoreCase; this.stringRepresentation = new StringBuilder( - matcher.length() - + CASE_SENSITIVE_PREFIX.length() - + WILDCARD.length() - + WILDCARD.length()) + matcher.length() + + CASE_SENSITIVE_PREFIX.length() + + WILDCARD.length() + + WILDCARD.length()) .append(ignoreCase ? "" : CASE_SENSITIVE_PREFIX) .append(wildcardAtBeginning ? WILDCARD : "") .append(matcher) diff --git a/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansAutoConfig.java b/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansAutoConfig.java index 9a3cbf6d6..56c5a10c2 100644 --- a/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansAutoConfig.java +++ b/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansAutoConfig.java @@ -18,17 +18,11 @@ */ package co.elastic.otel.profiler; -import co.elastic.otel.profiler.config.WildcardMatcher; +import co.elastic.otel.common.config.PropertiesApplier; import com.google.auto.service.AutoService; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import java.time.Duration; -import java.util.Arrays; -import java.util.List; -import java.util.function.Consumer; import java.util.logging.Logger; -import java.util.stream.Collectors; @AutoService(AutoConfigurationCustomizerProvider.class) public class InferredSpansAutoConfig implements AutoConfigurationCustomizerProvider { @@ -79,48 +73,4 @@ public void customize(AutoConfigurationCustomizer config) { }); } - private static class PropertiesApplier { - - private final ConfigProperties properties; - - private PropertiesApplier(ConfigProperties properties) { - this.properties = properties; - } - - void applyBool(String configKey, Consumer funcToApply) { - applyValue(properties.getBoolean(configKey), funcToApply); - } - - void applyInt(String configKey, Consumer funcToApply) { - applyValue(properties.getInt(configKey), funcToApply); - } - - void applyDuration(String configKey, Consumer funcToApply) { - applyValue(properties.getDuration(configKey), funcToApply); - } - - void applyString(String configKey, Consumer funcToApply) { - applyValue(properties.getString(configKey), funcToApply); - } - - void applyWildcards(String configKey, Consumer> funcToApply) { - String wildcardListString = properties.getString(configKey); - if (wildcardListString != null && !wildcardListString.isEmpty()) { - List values = - Arrays.stream(wildcardListString.split(",")) - .filter(str -> !str.isEmpty()) - .map(WildcardMatcher::valueOf) - .collect(Collectors.toList()); - if (!values.isEmpty()) { - funcToApply.accept(values); - } - } - } - - private static void applyValue(T value, Consumer funcToApply) { - if (value != null) { - funcToApply.accept(value); - } - } - } } diff --git a/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansConfiguration.java b/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansConfiguration.java index 3577b8999..a7775d8e0 100644 --- a/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansConfiguration.java +++ b/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansConfiguration.java @@ -18,7 +18,7 @@ */ package co.elastic.otel.profiler; -import co.elastic.otel.profiler.config.WildcardMatcher; +import co.elastic.otel.common.config.WildcardMatcher; import java.time.Duration; import java.util.List; diff --git a/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansProcessorBuilder.java b/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansProcessorBuilder.java index 51d1e7526..528eccef5 100644 --- a/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansProcessorBuilder.java +++ b/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansProcessorBuilder.java @@ -18,7 +18,7 @@ */ package co.elastic.otel.profiler; -import co.elastic.otel.profiler.config.WildcardMatcher; +import co.elastic.otel.common.config.WildcardMatcher; import java.io.File; import java.time.Duration; import java.util.Arrays; diff --git a/inferred-spans/src/main/java/co/elastic/otel/profiler/SamplingProfiler.java b/inferred-spans/src/main/java/co/elastic/otel/profiler/SamplingProfiler.java index 89dea3716..9b4eb7266 100644 --- a/inferred-spans/src/main/java/co/elastic/otel/profiler/SamplingProfiler.java +++ b/inferred-spans/src/main/java/co/elastic/otel/profiler/SamplingProfiler.java @@ -21,11 +21,11 @@ import static java.nio.file.StandardOpenOption.READ; import static java.nio.file.StandardOpenOption.WRITE; +import co.elastic.otel.common.config.WildcardMatcher; import co.elastic.otel.common.util.ExecutorUtils; import co.elastic.otel.profiler.asyncprofiler.AsyncProfiler; import co.elastic.otel.profiler.asyncprofiler.JfrParser; import co.elastic.otel.profiler.collections.Long2ObjectHashMap; -import co.elastic.otel.profiler.config.WildcardMatcher; import co.elastic.otel.profiler.pooling.Allocator; import co.elastic.otel.profiler.pooling.ObjectPool; import com.lmax.disruptor.EventFactory; diff --git a/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/JfrParser.java b/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/JfrParser.java index 19247c0ee..ad7656c03 100644 --- a/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/JfrParser.java +++ b/inferred-spans/src/main/java/co/elastic/otel/profiler/asyncprofiler/JfrParser.java @@ -18,12 +18,12 @@ */ package co.elastic.otel.profiler.asyncprofiler; +import co.elastic.otel.common.config.WildcardMatcher; import co.elastic.otel.profiler.StackFrame; import co.elastic.otel.profiler.collections.Int2IntHashMap; import co.elastic.otel.profiler.collections.Int2ObjectHashMap; import co.elastic.otel.profiler.collections.Long2LongHashMap; import co.elastic.otel.profiler.collections.Long2ObjectHashMap; -import co.elastic.otel.profiler.config.WildcardMatcher; import co.elastic.otel.profiler.pooling.Recyclable; import java.io.File; import java.io.IOException; diff --git a/inferred-spans/src/test/java/co/elastic/otel/profiler/InferredSpansAutoConfigTest.java b/inferred-spans/src/test/java/co/elastic/otel/profiler/InferredSpansAutoConfigTest.java index 3c3c61eb4..e52bcba53 100644 --- a/inferred-spans/src/test/java/co/elastic/otel/profiler/InferredSpansAutoConfigTest.java +++ b/inferred-spans/src/test/java/co/elastic/otel/profiler/InferredSpansAutoConfigTest.java @@ -21,7 +21,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.awaitility.Awaitility.await; -import co.elastic.otel.profiler.config.WildcardMatcher; +import co.elastic.otel.common.config.WildcardMatcher; import co.elastic.otel.testing.AutoConfigTestProperties; import co.elastic.otel.testing.AutoConfiguredDataCapture; import co.elastic.otel.testing.DisabledOnAppleSilicon; diff --git a/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/JfrParserTest.java b/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/JfrParserTest.java index 4925212ea..0302ae4fa 100644 --- a/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/JfrParserTest.java +++ b/inferred-spans/src/test/java/co/elastic/otel/profiler/asyncprofiler/JfrParserTest.java @@ -18,7 +18,7 @@ */ package co.elastic.otel.profiler.asyncprofiler; -import static co.elastic.otel.profiler.config.WildcardMatcher.caseSensitiveMatcher; +import static co.elastic.otel.common.config.WildcardMatcher.caseSensitiveMatcher; import static org.assertj.core.api.Assertions.assertThat; import co.elastic.otel.profiler.StackFrame; From 0e1f49b12c5e334b18fbbfea5b34f2dd1d18ea8f Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 17 Apr 2024 09:35:23 +0200 Subject: [PATCH 03/12] Implemented autoconfig options, fixed span value issue if used as agentextension --- .../otel/common/SpanValueStorageProvider.java | 16 +++ jvmti-access/build.gradle.kts | 4 + licenses/more-licences.md | 35 +++++- settings.gradle.kts | 1 + .../universal-profiling-test/build.gradle.kts | 26 +++++ .../UniversalProfilingIntegrationTest.java | 26 +++++ .../build.gradle.kts | 8 ++ .../otel/SpanProfilingSamplesCorrelator.java | 3 +- .../otel/UniversalProfilingProcessor.java | 6 +- ...UniversalProfilingProcessorAutoConfig.java | 60 +++++++++- ...ersalProfilingProcessorAutoConfigTest.java | 104 ++++++++++++++++++ 11 files changed, 276 insertions(+), 13 deletions(-) create mode 100644 testing/integration-tests/universal-profiling-test/build.gradle.kts create mode 100644 testing/integration-tests/universal-profiling-test/src/test/java/UniversalProfilingIntegrationTest.java create mode 100644 universal-profiling-integration/src/test/java/co/elastic/otel/UniversalProfilingProcessorAutoConfigTest.java diff --git a/common/src/main/java/co/elastic/otel/common/SpanValueStorageProvider.java b/common/src/main/java/co/elastic/otel/common/SpanValueStorageProvider.java index 8c87e1021..02b7fb6d0 100644 --- a/common/src/main/java/co/elastic/otel/common/SpanValueStorageProvider.java +++ b/common/src/main/java/co/elastic/otel/common/SpanValueStorageProvider.java @@ -21,11 +21,27 @@ import com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap; import io.opentelemetry.api.trace.Span; import io.opentelemetry.sdk.trace.FieldBackedSpanValueStorageProvider; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Nullable; public interface SpanValueStorageProvider { + Logger logger = Logger.getLogger(SpanValueStorageProvider.class.getName()); + static SpanValueStorageProvider get() { + try { + Class sdkSpan = Class.forName("io.opentelemetry.sdk.trace.SdkSpan"); + if (sdkSpan.getClassLoader() != SpanValueStorage.class.getClassLoader()) { + // If we are running in a different classloader, this means we aren't running in our distro + logger.log(Level.FINE, + "Using map-backed storage for SpanValues because SdkSpan lives in a different classloader and therefore is inaccessible"); + return MapBacked.getInstance(); + + } + } catch (ClassNotFoundException e) { + throw new RuntimeException("Expected SdkSpan class to exist", e); + } return FieldBackedSpanValueStorageProvider.INSTANCE != null ? FieldBackedSpanValueStorageProvider.INSTANCE : MapBacked.getInstance(); diff --git a/jvmti-access/build.gradle.kts b/jvmti-access/build.gradle.kts index 5b8743996..267f71e0b 100644 --- a/jvmti-access/build.gradle.kts +++ b/jvmti-access/build.gradle.kts @@ -9,6 +9,7 @@ import java.util.* plugins { id("elastic-otel.library-packaging-conventions") + id("elastic-otel.sign-and-publish-conventions") alias(catalog.plugins.dockerJavaApplication) } @@ -18,6 +19,9 @@ dependencies { implementation(libs.findbugs.jsr305) } +description = "Library for exposing JVMTI and JNI functionality to Java" + + // we use Java 7 for this project so that it can be reused in the old elastic-apm-agent // Subsequently, the newest Java compiler we can use is java 17 java { diff --git a/licenses/more-licences.md b/licenses/more-licences.md index 46266566d..4e124dd86 100644 --- a/licenses/more-licences.md +++ b/licenses/more-licences.md @@ -16,7 +16,7 @@ **3** **Group:** `com.google.code.findbugs` **Name:** `jsr305` **Version:** `3.0.2` > - **Manifest License**: Apache License, Version 2.0 (Not Packaged) > - **POM Project URL**: [http://findbugs.sourceforge.net/](http://findbugs.sourceforge.net/) -> - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) **4** **Group:** `com.lmax` **Name:** `disruptor` **Version:** `3.4.4` > - **Project URL**: [http://lmax-exchange.github.com/disruptor](http://lmax-exchange.github.com/disruptor) @@ -80,7 +80,7 @@ **19** **Group:** `io.opentelemetry.semconv` **Name:** `opentelemetry-semconv` **Version:** `1.23.1-alpha` > - **POM Project URL**: [https://github.com/open-telemetry/semantic-conventions-java](https://github.com/open-telemetry/semantic-conventions-java) -> - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) +> - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) **20** **Group:** `net.bytebuddy` **Name:** `byte-buddy-dep` **Version:** `1.14.12` > - **POM License**: Apache License, Version 2.0 - [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0) @@ -106,16 +106,43 @@ > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) +## Creative Commons Legal Code + +**24** **Group:** `org.hdrhistogram` **Name:** `HdrHistogram` **Version:** `2.1.12` +> - **Manifest License**: The 2-Clause BSD License (Not Packaged) +> - **POM Project URL**: [http://hdrhistogram.github.io/HdrHistogram/](http://hdrhistogram.github.io/HdrHistogram/) +> - **POM License**: Creative Commons Legal Code - [https://creativecommons.org/publicdomain/zero/1.0/legalcode](https://creativecommons.org/publicdomain/zero/1.0/legalcode) +> - **POM License**: PUBLIC DOMAIN - [http://creativecommons.org/publicdomain/zero/1.0/](http://creativecommons.org/publicdomain/zero/1.0/) +> - **POM License**: The 2-Clause BSD License - [https://opensource.org/licenses/BSD-2-Clause](https://opensource.org/licenses/BSD-2-Clause) + +## PUBLIC DOMAIN + +**25** **Group:** `org.hdrhistogram` **Name:** `HdrHistogram` **Version:** `2.1.12` +> - **Manifest License**: The 2-Clause BSD License (Not Packaged) +> - **POM Project URL**: [http://hdrhistogram.github.io/HdrHistogram/](http://hdrhistogram.github.io/HdrHistogram/) +> - **POM License**: Creative Commons Legal Code - [https://creativecommons.org/publicdomain/zero/1.0/legalcode](https://creativecommons.org/publicdomain/zero/1.0/legalcode) +> - **POM License**: PUBLIC DOMAIN - [http://creativecommons.org/publicdomain/zero/1.0/](http://creativecommons.org/publicdomain/zero/1.0/) +> - **POM License**: The 2-Clause BSD License - [https://opensource.org/licenses/BSD-2-Clause](https://opensource.org/licenses/BSD-2-Clause) + +## The 2-Clause BSD License + +**26** **Group:** `org.hdrhistogram` **Name:** `HdrHistogram` **Version:** `2.1.12` +> - **Manifest License**: The 2-Clause BSD License (Not Packaged) +> - **POM Project URL**: [http://hdrhistogram.github.io/HdrHistogram/](http://hdrhistogram.github.io/HdrHistogram/) +> - **POM License**: Creative Commons Legal Code - [https://creativecommons.org/publicdomain/zero/1.0/legalcode](https://creativecommons.org/publicdomain/zero/1.0/legalcode) +> - **POM License**: PUBLIC DOMAIN - [http://creativecommons.org/publicdomain/zero/1.0/](http://creativecommons.org/publicdomain/zero/1.0/) +> - **POM License**: The 2-Clause BSD License - [https://opensource.org/licenses/BSD-2-Clause](https://opensource.org/licenses/BSD-2-Clause) + ## The 3-Clause BSD License -**24** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.6` +**27** **Group:** `org.ow2.asm` **Name:** `asm` **Version:** `9.6` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) > - **POM License**: Apache License, Version 2.0 - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) > - **POM License**: The 3-Clause BSD License - [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause) -**25** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.6` +**28** **Group:** `org.ow2.asm` **Name:** `asm-commons` **Version:** `9.6` > - **Manifest Project URL**: [http://asm.ow2.org](http://asm.ow2.org) > - **Manifest License**: The 3-Clause BSD License (Not Packaged) > - **POM Project URL**: [http://asm.ow2.io/](http://asm.ow2.io/) diff --git a/settings.gradle.kts b/settings.gradle.kts index d23219a5e..7a4b82a92 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,6 +29,7 @@ include("testing-common") include("universal-profiling-integration") include("testing:integration-tests:inferred-spans-test") include("testing:integration-tests:agent-internals") +include("testing:integration-tests:universal-profiling-test") dependencyResolutionManagement { versionCatalogs { diff --git a/testing/integration-tests/universal-profiling-test/build.gradle.kts b/testing/integration-tests/universal-profiling-test/build.gradle.kts new file mode 100644 index 000000000..2720665a4 --- /dev/null +++ b/testing/integration-tests/universal-profiling-test/build.gradle.kts @@ -0,0 +1,26 @@ +import java.nio.file.Paths + +plugins { + id("elastic-otel.test-with-agent-conventions") +} + +dependencies { + testImplementation(project(":testing-common")) +} + + +tasks.withType() { + + // We need a short temporary path, because the socket file path length is limited to only ~100 chars + // therefore we can't use this.temporaryDir reliably + + val tmpDir=Paths.get(System.getProperty("java.io.tmpdir")) + .resolve("proftest"+System.nanoTime()) + .toAbsolutePath().toString() + + jvmArgs( + //"-Dotel.javaagent.debug=true", + "-Dotel.service.name=testing", + "-Delastic.otel.universal.profiling.integration.socket.dir=${tmpDir}" + ) +} diff --git a/testing/integration-tests/universal-profiling-test/src/test/java/UniversalProfilingIntegrationTest.java b/testing/integration-tests/universal-profiling-test/src/test/java/UniversalProfilingIntegrationTest.java new file mode 100644 index 000000000..d4847e6b4 --- /dev/null +++ b/testing/integration-tests/universal-profiling-test/src/test/java/UniversalProfilingIntegrationTest.java @@ -0,0 +1,26 @@ +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public class UniversalProfilingIntegrationTest { + + @Test + public void checkIntegrationActive() throws IOException { + // We verify that the integration is running by checking for the presence of the socket + String socketDir = System.getProperty( + "elastic.otel.universal.profiling.integration.socket.dir"); + List files = Files.list(Paths.get(socketDir)) + .map(Path::getFileName) + .map(Path::toString) + .collect(Collectors.toList()); + assertThat(files).hasSize(1); + assertThat(files.get(0)).startsWith("essock"); + } + +} diff --git a/universal-profiling-integration/build.gradle.kts b/universal-profiling-integration/build.gradle.kts index e0ec54177..8e9cd7616 100644 --- a/universal-profiling-integration/build.gradle.kts +++ b/universal-profiling-integration/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("elastic-otel.library-packaging-conventions") + id("elastic-otel.sign-and-publish-conventions") alias(libs.plugins.jmh) } @@ -10,10 +11,16 @@ jmh { //profilers.add("jfr") } +description = "OpenTelemetry SDK extension to enable correlation of traces with elastic universal profiling" + dependencies { + annotationProcessor(libs.autoservice.processor) + compileOnly(libs.autoservice.annotations) implementation(project(":jvmti-access")) implementation(project(":common")) implementation("io.opentelemetry.semconv:opentelemetry-semconv") + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") implementation(libs.lmax.disruptor) implementation(libs.hdrhistogram) //only used for the WriterReaderPhaser @@ -24,6 +31,7 @@ dependencies { testImplementation(project(":testing-common")) testImplementation("io.opentelemetry:opentelemetry-sdk") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") testImplementation(libs.assertj.core) testImplementation(libs.awaitility) } diff --git a/universal-profiling-integration/src/main/java/co/elastic/otel/SpanProfilingSamplesCorrelator.java b/universal-profiling-integration/src/main/java/co/elastic/otel/SpanProfilingSamplesCorrelator.java index 3d445446c..311d2e8f5 100644 --- a/universal-profiling-integration/src/main/java/co/elastic/otel/SpanProfilingSamplesCorrelator.java +++ b/universal-profiling-integration/src/main/java/co/elastic/otel/SpanProfilingSamplesCorrelator.java @@ -50,7 +50,8 @@ public class SpanProfilingSamplesCorrelator { private final LongSupplier nanoClock; - private final RingBuffer delayedSpans; + // Visible for testing + final RingBuffer delayedSpans; private final PeekingPoller delayedSpansPoller; private volatile long spanBufferDurationNanos; diff --git a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessor.java b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessor.java index 196dff191..4c77fa5ac 100644 --- a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessor.java +++ b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessor.java @@ -78,13 +78,15 @@ public class UniversalProfilingProcessor extends AbstractChainingSpanProcessor { private static boolean anyInstanceActive = false; - private final SpanProfilingSamplesCorrelator correlator; + // Visible for testing + final SpanProfilingSamplesCorrelator correlator; private final ScheduledExecutorService messagePollAndSpanFlushExecutor; // Visible for testing String socketPath; - private volatile boolean tlsPropagationActive = false; + // Visible for testing + volatile boolean tlsPropagationActive = false; public static UniversalProfilingProcessorBuilder builder(SpanProcessor next, Resource resource) { return new UniversalProfilingProcessorBuilder(next, resource); diff --git a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java index c2b03de2d..ba850c53c 100644 --- a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java +++ b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java @@ -1,20 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel; import co.elastic.otel.common.ChainingSpanProcessorAutoConfiguration; import co.elastic.otel.common.ChainingSpanProcessorRegisterer; +import co.elastic.otel.common.config.PropertiesApplier; import com.google.auto.service.AutoService; import io.opentelemetry.sdk.autoconfigure.ResourceConfiguration; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.resources.Resource; @AutoService(ChainingSpanProcessorAutoConfiguration.class) -public class UniversalProfilingProcessorAutoConfig implements - ChainingSpanProcessorAutoConfiguration { +public class UniversalProfilingProcessorAutoConfig + implements ChainingSpanProcessorAutoConfiguration { + + static final String ENABLED_OPTION = "elastic.otel.universal.profiling.integration.enabled"; + static final String BUFFER_SIZE_OPTION = + "elastic.otel.universal.profiling.integration.buffer.size"; + static final String SOCKET_DIR_OPTION = "elastic.otel.universal.profiling.integration.socket.dir"; + + private enum EnabledOptions { + TRUE, + FALSE, + AUTO + } + @Override - public void registerSpanProcessors(ConfigProperties properties, - ChainingSpanProcessorRegisterer registerer) { - Resource resource = ResourceConfiguration.createEnvironmentResource(properties); - registerer.register(next -> UniversalProfilingProcessor.builder(next, resource).build()); + public void registerSpanProcessors( + ConfigProperties properties, ChainingSpanProcessorRegisterer registerer) { + + String enabledString = + properties.getString(ENABLED_OPTION, EnabledOptions.AUTO.toString()).toUpperCase(); + EnabledOptions enabled = EnabledOptions.valueOf(enabledString); + if (enabled == EnabledOptions.FALSE) { + return; + } + + PropertiesApplier props = new PropertiesApplier(properties); + Resource resource = ResourceConfiguration.createEnvironmentResource(properties); + registerer.register( + next -> { + UniversalProfilingProcessorBuilder builder = + UniversalProfilingProcessor.builder(next, resource); + builder.delayActivationAfterProfilerRegistration(enabled == EnabledOptions.AUTO); + props.applyInt(BUFFER_SIZE_OPTION, builder::bufferSize); + props.applyString(SOCKET_DIR_OPTION, builder::socketDir); + return builder.build(); + }); } } diff --git a/universal-profiling-integration/src/test/java/co/elastic/otel/UniversalProfilingProcessorAutoConfigTest.java b/universal-profiling-integration/src/test/java/co/elastic/otel/UniversalProfilingProcessorAutoConfigTest.java new file mode 100644 index 000000000..ef78b475d --- /dev/null +++ b/universal-profiling-integration/src/test/java/co/elastic/otel/UniversalProfilingProcessorAutoConfigTest.java @@ -0,0 +1,104 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package co.elastic.otel; + +import static co.elastic.otel.UniversalProfilingProcessorAutoConfig.BUFFER_SIZE_OPTION; +import static co.elastic.otel.UniversalProfilingProcessorAutoConfig.ENABLED_OPTION; +import static co.elastic.otel.UniversalProfilingProcessorAutoConfig.SOCKET_DIR_OPTION; +import static org.assertj.core.api.Assertions.assertThat; + +import co.elastic.otel.testing.AutoConfigTestProperties; +import co.elastic.otel.testing.OtelReflectionUtils; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.trace.SpanProcessor; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class UniversalProfilingProcessorAutoConfigTest { + + @BeforeEach + @AfterEach + public void resetGlobalOtel() { + OtelReflectionUtils.shutdownAndResetGlobalOtel(); + } + + @Test + public void testDisabling() { + try (AutoConfigTestProperties props = + new AutoConfigTestProperties().put(ENABLED_OPTION, "false")) { + OpenTelemetry otel = GlobalOpenTelemetry.get(); + List processors = OtelReflectionUtils.getSpanProcessors(otel); + assertThat(processors).noneMatch(proc -> proc instanceof UniversalProfilingProcessor); + } + } + + @Test + public void checkEnabledButInactiveByDefault() { + try (AutoConfigTestProperties props = + new AutoConfigTestProperties().put("otel.service.name", "myservice")) { + OpenTelemetry otel = GlobalOpenTelemetry.get(); + List processors = OtelReflectionUtils.getSpanProcessors(otel); + assertThat(processors) + .filteredOn(proc -> proc instanceof UniversalProfilingProcessor) + .hasSize(1); + UniversalProfilingProcessor processor = + (UniversalProfilingProcessor) + processors.stream() + .filter(proc -> proc instanceof UniversalProfilingProcessor) + .findFirst() + .get(); + + assertThat(processor.tlsPropagationActive).isFalse(); + } + } + + @Test + public void testAllSettings(@TempDir Path tempDir) { + + String tempDirAbs = tempDir.toAbsolutePath().toString(); + + try (AutoConfigTestProperties props = + new AutoConfigTestProperties() + .put("otel.service.name", "myservice") + .put(ENABLED_OPTION, "true") + .put(BUFFER_SIZE_OPTION, "256") + .put(SOCKET_DIR_OPTION, tempDirAbs)) { + OpenTelemetry otel = GlobalOpenTelemetry.get(); + List processors = OtelReflectionUtils.getSpanProcessors(otel); + assertThat(processors) + .filteredOn(proc -> proc instanceof UniversalProfilingProcessor) + .hasSize(1); + UniversalProfilingProcessor processor = + (UniversalProfilingProcessor) + processors.stream() + .filter(proc -> proc instanceof UniversalProfilingProcessor) + .findFirst() + .get(); + + assertThat(processor.tlsPropagationActive).isTrue(); + assertThat(processor.socketPath).startsWith(tempDirAbs); + assertThat(processor.correlator.delayedSpans.getBufferSize()).isEqualTo(256); + } + } +} From a471d0f315d85bf776ac0111ead570a4a34f2ee0 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 17 Apr 2024 09:54:03 +0200 Subject: [PATCH 04/12] spotless --- .../otel/common/SpanValueStorageProvider.java | 4 +-- .../otel/common/config/PropertiesApplier.java | 22 +++++++++++-- .../otel/common/config/WildcardMatcher.java | 8 ++--- .../profiler/InferredSpansAutoConfig.java | 1 - .../UniversalProfilingIntegrationTest.java | 32 +++++++++++++++---- 5 files changed, 51 insertions(+), 16 deletions(-) diff --git a/common/src/main/java/co/elastic/otel/common/SpanValueStorageProvider.java b/common/src/main/java/co/elastic/otel/common/SpanValueStorageProvider.java index 02b7fb6d0..0b66304f1 100644 --- a/common/src/main/java/co/elastic/otel/common/SpanValueStorageProvider.java +++ b/common/src/main/java/co/elastic/otel/common/SpanValueStorageProvider.java @@ -34,10 +34,10 @@ static SpanValueStorageProvider get() { Class sdkSpan = Class.forName("io.opentelemetry.sdk.trace.SdkSpan"); if (sdkSpan.getClassLoader() != SpanValueStorage.class.getClassLoader()) { // If we are running in a different classloader, this means we aren't running in our distro - logger.log(Level.FINE, + logger.log( + Level.FINE, "Using map-backed storage for SpanValues because SdkSpan lives in a different classloader and therefore is inaccessible"); return MapBacked.getInstance(); - } } catch (ClassNotFoundException e) { throw new RuntimeException("Expected SdkSpan class to exist", e); diff --git a/common/src/main/java/co/elastic/otel/common/config/PropertiesApplier.java b/common/src/main/java/co/elastic/otel/common/config/PropertiesApplier.java index a57320926..3956e6624 100644 --- a/common/src/main/java/co/elastic/otel/common/config/PropertiesApplier.java +++ b/common/src/main/java/co/elastic/otel/common/config/PropertiesApplier.java @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ package co.elastic.otel.common.config; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -31,8 +49,8 @@ public void applyString(String configKey, Consumer funcToApply) { applyValue(properties.getString(configKey), funcToApply); } - public void applyWildcards(String configKey, - Consumer> funcToApply) { + public void applyWildcards( + String configKey, Consumer> funcToApply) { String wildcardListString = properties.getString(configKey); if (wildcardListString != null && !wildcardListString.isEmpty()) { List values = diff --git a/common/src/main/java/co/elastic/otel/common/config/WildcardMatcher.java b/common/src/main/java/co/elastic/otel/common/config/WildcardMatcher.java index e825a03ad..fdd5a3336 100644 --- a/common/src/main/java/co/elastic/otel/common/config/WildcardMatcher.java +++ b/common/src/main/java/co/elastic/otel/common/config/WildcardMatcher.java @@ -334,10 +334,10 @@ static class SimpleWildcardMatcher extends WildcardMatcher { this.ignoreCase = ignoreCase; this.stringRepresentation = new StringBuilder( - matcher.length() - + CASE_SENSITIVE_PREFIX.length() - + WILDCARD.length() - + WILDCARD.length()) + matcher.length() + + CASE_SENSITIVE_PREFIX.length() + + WILDCARD.length() + + WILDCARD.length()) .append(ignoreCase ? "" : CASE_SENSITIVE_PREFIX) .append(wildcardAtBeginning ? WILDCARD : "") .append(matcher) diff --git a/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansAutoConfig.java b/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansAutoConfig.java index 56c5a10c2..fa821f7db 100644 --- a/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansAutoConfig.java +++ b/inferred-spans/src/main/java/co/elastic/otel/profiler/InferredSpansAutoConfig.java @@ -72,5 +72,4 @@ public void customize(AutoConfigurationCustomizer config) { return providerBuilder; }); } - } diff --git a/testing/integration-tests/universal-profiling-test/src/test/java/UniversalProfilingIntegrationTest.java b/testing/integration-tests/universal-profiling-test/src/test/java/UniversalProfilingIntegrationTest.java index d4847e6b4..f2a07776b 100644 --- a/testing/integration-tests/universal-profiling-test/src/test/java/UniversalProfilingIntegrationTest.java +++ b/testing/integration-tests/universal-profiling-test/src/test/java/UniversalProfilingIntegrationTest.java @@ -1,3 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; @@ -13,14 +31,14 @@ public class UniversalProfilingIntegrationTest { @Test public void checkIntegrationActive() throws IOException { // We verify that the integration is running by checking for the presence of the socket - String socketDir = System.getProperty( - "elastic.otel.universal.profiling.integration.socket.dir"); - List files = Files.list(Paths.get(socketDir)) - .map(Path::getFileName) - .map(Path::toString) - .collect(Collectors.toList()); + String socketDir = + System.getProperty("elastic.otel.universal.profiling.integration.socket.dir"); + List files = + Files.list(Paths.get(socketDir)) + .map(Path::getFileName) + .map(Path::toString) + .collect(Collectors.toList()); assertThat(files).hasSize(1); assertThat(files.get(0)).startsWith("essock"); } - } From ff673e5c99dc4c94aec9b0028a06bf3e5cb33493 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 17 Apr 2024 10:11:24 +0200 Subject: [PATCH 05/12] Do not crash agent if no service name is configured --- .../UniversalProfilingProcessorAutoConfig.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java index ba850c53c..65d9ddd1a 100644 --- a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java +++ b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java @@ -25,11 +25,16 @@ import io.opentelemetry.sdk.autoconfigure.ResourceConfiguration; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.semconv.ResourceAttributes; +import java.util.logging.Logger; @AutoService(ChainingSpanProcessorAutoConfiguration.class) public class UniversalProfilingProcessorAutoConfig implements ChainingSpanProcessorAutoConfiguration { + private static final Logger logger = Logger.getLogger( + UniversalProfilingProcessorAutoConfig.class.getName()); + static final String ENABLED_OPTION = "elastic.otel.universal.profiling.integration.enabled"; static final String BUFFER_SIZE_OPTION = "elastic.otel.universal.profiling.integration.buffer.size"; @@ -52,9 +57,16 @@ public void registerSpanProcessors( if (enabled == EnabledOptions.FALSE) { return; } + Resource resource = ResourceConfiguration.createEnvironmentResource(properties); + + String serviceName = resource.getAttribute(ResourceAttributes.SERVICE_NAME); + if (serviceName == null || serviceName.isEmpty()) { + logger.warning( + "Cannot start universal profiling integration because no service name was configured"); + return; + } PropertiesApplier props = new PropertiesApplier(properties); - Resource resource = ResourceConfiguration.createEnvironmentResource(properties); registerer.register( next -> { UniversalProfilingProcessorBuilder builder = From 4269b7cd0f098a093b293a56eca5562dac06269b Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 17 Apr 2024 10:36:51 +0200 Subject: [PATCH 06/12] Fixed LocalRootSpan to not break when invoked on `MutableSpan` --- .../co/elastic/otel/common/LocalRootSpan.java | 39 +++++++++---------- .../otel/ProfilerSharedMemoryWriter.java | 3 +- ...UniversalProfilingProcessorAutoConfig.java | 4 +- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/common/src/main/java/co/elastic/otel/common/LocalRootSpan.java b/common/src/main/java/co/elastic/otel/common/LocalRootSpan.java index 9ac5ebffb..5af37477a 100644 --- a/common/src/main/java/co/elastic/otel/common/LocalRootSpan.java +++ b/common/src/main/java/co/elastic/otel/common/LocalRootSpan.java @@ -79,6 +79,20 @@ public static void onSpanStart(ReadableSpan startedSpan, Context parentContext) } } + /** See {@link LocalRootSpan#getFor(Span)}. */ + @Nullable + public static ReadableSpan getFor(ReadableSpan span) { + Object rootSpanVal = localRoot.get(span); + if (rootSpanVal == LOCAL_ROOT_MARKER) { + return span; + } + if (rootSpanVal == INFERRED_SPAN_UNKNOWN_ROOT_MARKER) { + return null; + } + return (ReadableSpan) rootSpanVal; + } + + /** * If the provided span is a local root span, itself is returned. Otherwise, returns the * (transitive) parent which is a local root. @@ -93,31 +107,14 @@ public static void onSpanStart(ReadableSpan startedSpan, Context parentContext) * */ @Nullable - public static Span getFor(Span span) { - return resolveRoot(span, localRoot.get(span)); - } - - /** See {@link LocalRootSpan#getFor(Span)}. */ - @Nullable - public static Span getFor(ReadableSpan span) { - return resolveRoot(span, localRoot.get(span)); + public static ReadableSpan getFor(Span span) { + return getFor((ReadableSpan) span); } /** See {@link LocalRootSpan#getFor(Span)}. */ @Nullable - public static Span getFor(ReadWriteSpan span) { - return resolveRoot(span, localRoot.get(span)); - } - - @Nullable - private static Span resolveRoot(Object span, Object rootSpanVal) { - if (rootSpanVal == LOCAL_ROOT_MARKER) { - return (Span) span; - } - if (rootSpanVal == INFERRED_SPAN_UNKNOWN_ROOT_MARKER) { - return null; - } - return (Span) rootSpanVal; + public static ReadableSpan getFor(ReadWriteSpan span) { + return getFor((ReadableSpan) span); } private LocalRootSpan() {} diff --git a/universal-profiling-integration/src/main/java/co/elastic/otel/ProfilerSharedMemoryWriter.java b/universal-profiling-integration/src/main/java/co/elastic/otel/ProfilerSharedMemoryWriter.java index 33780de12..caaf06c9d 100644 --- a/universal-profiling-integration/src/main/java/co/elastic/otel/ProfilerSharedMemoryWriter.java +++ b/universal-profiling-integration/src/main/java/co/elastic/otel/ProfilerSharedMemoryWriter.java @@ -23,6 +23,7 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.ReadableSpan; import io.opentelemetry.semconv.ResourceAttributes; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -99,7 +100,7 @@ static void updateThreadCorrelationStorage(Span newSpan) { SpanContext spanCtx = newSpan.getSpanContext(); if (spanCtx.isValid() && !spanCtx.isRemote()) { - Span localRoot = LocalRootSpan.getFor(newSpan); + ReadableSpan localRoot = LocalRootSpan.getFor(newSpan); if (localRoot != null) { String localRootSpanId = localRoot.getSpanContext().getSpanId(); tls.put(TLS_TRACE_PRESENT_OFFSET, (byte) 1); diff --git a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java index 65d9ddd1a..aebb63004 100644 --- a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java +++ b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java @@ -32,8 +32,8 @@ public class UniversalProfilingProcessorAutoConfig implements ChainingSpanProcessorAutoConfiguration { - private static final Logger logger = Logger.getLogger( - UniversalProfilingProcessorAutoConfig.class.getName()); + private static final Logger logger = + Logger.getLogger(UniversalProfilingProcessorAutoConfig.class.getName()); static final String ENABLED_OPTION = "elastic.otel.universal.profiling.integration.enabled"; static final String BUFFER_SIZE_OPTION = From b3ccc31d54ab263cddb23bf1d2cd68ca15050512 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 17 Apr 2024 13:17:00 +0200 Subject: [PATCH 07/12] spotless --- common/src/main/java/co/elastic/otel/common/LocalRootSpan.java | 1 - 1 file changed, 1 deletion(-) diff --git a/common/src/main/java/co/elastic/otel/common/LocalRootSpan.java b/common/src/main/java/co/elastic/otel/common/LocalRootSpan.java index 5af37477a..e036096c8 100644 --- a/common/src/main/java/co/elastic/otel/common/LocalRootSpan.java +++ b/common/src/main/java/co/elastic/otel/common/LocalRootSpan.java @@ -92,7 +92,6 @@ public static ReadableSpan getFor(ReadableSpan span) { return (ReadableSpan) rootSpanVal; } - /** * If the provided span is a local root span, itself is returned. Otherwise, returns the * (transitive) parent which is a local root. From 66eb708084a41cf2981732bea458c95f9e6a2da3 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 17 Apr 2024 13:59:30 +0200 Subject: [PATCH 08/12] Added javadoc and readme --- universal-profiling-integration/README.md | 58 +++++++++++++++++++ .../UniversalProfilingProcessorBuilder.java | 14 +++++ 2 files changed, 72 insertions(+) create mode 100644 universal-profiling-integration/README.md diff --git a/universal-profiling-integration/README.md b/universal-profiling-integration/README.md new file mode 100644 index 000000000..0536f951b --- /dev/null +++ b/universal-profiling-integration/README.md @@ -0,0 +1,58 @@ +OpenTelemetry extension for correlating traces with profiling data from the elastic universal profiler. + +Only platform threads are supported at the moment. Virtual threads are not supported yet and will not be correlated. + +## Usage + +This section describes the usage of this extension outside of an agent. +Add the following dependency to your project: + +``` + + co.elastic.otel + universal-profiling-integration + {latest version} + +``` + +### Autoconfiguration + +This extension supports [autoconfiguration](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure). + +So if you are using an autoconfigured OpenTelemetry SDK, you'll only need to add this extension to your class path and configure it via system properties or environment variables: + +| Property Name / Environment Variable Name | Default | Description | +|-------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| elastic.otel.universal.profiling.integration.enabled
ELASTIC_OTEL_UNIVERSAL_PROFILING_INTEGRATION_ENABLED | `auto` | Enables or disables the feature. Possible values are `true`, `false` or `auto`. On `auto` the profiling integration will be installed but remain inactive until the presence of a profiler is detected (Requires a profiling host agent 8.15 or later). This reduces the overhead in the case no profiler is there. When using `auto`, there might be a slight delay until the correlation is activated. So if your application creates spans during startup which you want correlated, you should use `true` instead. | +| elastic.otel.universal.profiling.integration.socket.dir
ELASTIC_OTEL_UNIVERSAL_PROFILING_INTEGRATION_SOCKET_DIR | the value of the `java.io.tmpdir` JVM-property | The extension needs to bind a socket to a file for communicating with the universal profiling host agent. By default, this socket will be placed in the java.io.tmpdir. This configuration option can be used to change the location. Note that the total path name (including the socket) must not exceed 100 characters due to OS restrictions. | +| elastic.otel.universal.profiling.integration.buffer.size
ELASTIC_OTEL_UNIVERSAL_PROFILING_INTEGRATION_BUFFER_SIZE | 8096 | The extension needs to buffer ended local-root spans for a short duration to ensure that all of its profiling data has been received. This configuration options configures the buffer size in number of spans. The higher the number of local root spans per second, the higher this buffer size should be set. The extension will log a warning if it is not capable of buffering a span due to insufficient buffer size. This will cause the span to be exported immediately instead with possibly incomplete profiling correlation data. | + + +### Manual SDK setup + +If you manually set-up your `OpenTelemetrySDK`, you need to create and register an `InferredSpansProcessor` with your `TracerProvider`: + +```java + +Resource resource = Resource.builder() + .put(ResourceAttributes.SERVICE_NAME, "my-service") + .build(); + +SpanExporter exporter = OtlpGrpcSpanExporter.builder() + .setEndpoint("https://.apm.europe-west3.gcp.cloud.es.io:443") + .addHeader("Authorization", "Bearer >") + .build(); +// Wrap exporter to ensure the correct host.id is used +exporter = new ProfilerHostIdApplyingSpanExporter(exporter); +SpanProcessor exportingProcessor = BatchSpanProcessor.builder(exporter); + +UniversalProfilingProcessor processor = + UniversalProfilingProcessor.builder(exportingProcessor, resource) + .build(); + +SdkTracerProvider tracerProvider = SdkTracerProvider.builder() + .addSpanProcessor(processor) + .build(); +``` + +The `setTracerProvider(..)` call shown at the end may be omitted, in that case `GlobalOpenTelemetry` will be used for generating the inferred spans. diff --git a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorBuilder.java b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorBuilder.java index 17aa0db16..aaec79247 100644 --- a/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorBuilder.java +++ b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorBuilder.java @@ -70,11 +70,25 @@ public UniversalProfilingProcessorBuilder delayActivationAfterProfilerRegistrati return this; } + /** + * The extension needs to buffer ended local-root spans for a short duration to ensure that all of + * its profiling data has been received. This configuration options configures the buffer size in + * number of spans. The higher the number of local root spans per second, the higher this buffer + * size should be set. The extension will log a warning if it is not capable of buffering a span + * due to insufficient buffer size. This will cause the span to be exported immediately instead + * with possibly incomplete profiling correlation data. + */ public UniversalProfilingProcessorBuilder bufferSize(int bufferSize) { this.bufferSize = bufferSize; return this; } + /** + * The extension needs to bind a socket to a file for communicating with the universal profiling + * host agent. By default, this socket will be placed in the java.io.tmpdir. This configuration + * option can be used to change the location. Note that the total path name (including the socket) + * must not exceed 100 characters due to OS restrictions. + */ public UniversalProfilingProcessorBuilder socketDir(String path) { this.socketDir = path; return this; From 4437e0a7f91087f740880e86f286e90c2231eb23 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 23 Apr 2024 14:06:12 +0200 Subject: [PATCH 09/12] Added libs as buildkite artifacts --- .buildkite/release.yml | 2 ++ .buildkite/snapshot.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.buildkite/release.yml b/.buildkite/release.yml index 58e8a2ed4..745846543 100644 --- a/.buildkite/release.yml +++ b/.buildkite/release.yml @@ -9,6 +9,8 @@ steps: artifact_paths: - "release.txt" - "agent/build/libs/elastic-otel-javaagent-*.jar" + - "jvmti-access/build/libs/jvmti-access-.*.jar" + - "universal-profiling-integration/build/libs/universal-profiling-integration-.*.jar" - "build/dry-run-maven-repo.tgz" notify: diff --git a/.buildkite/snapshot.yml b/.buildkite/snapshot.yml index a66cf20de..059d80117 100644 --- a/.buildkite/snapshot.yml +++ b/.buildkite/snapshot.yml @@ -9,6 +9,8 @@ steps: artifact_paths: - "snapshot.txt" - "agent/build/libs/elastic-otel-javaagent-*.jar" + - "jvmti-access/build/libs/jvmti-access-.*.jar" + - "universal-profiling-integration/build/libs/universal-profiling-integration-.*.jar" - "build/dry-run-maven-repo.tgz" notify: From 0c1b766ad99e3619f239644e313e35f1b6715383 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 23 Apr 2024 17:19:12 +0200 Subject: [PATCH 10/12] add hdrhistogram license --- NOTICE | 3 ++ licenses/LICENSE_hdrhistogram_bsd-2-clause | 41 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 licenses/LICENSE_hdrhistogram_bsd-2-clause diff --git a/NOTICE b/NOTICE index 9ab44ca65..e27d154b3 100644 --- a/NOTICE +++ b/NOTICE @@ -10,5 +10,8 @@ A copy of the ASM license (3-Clause BSD License) is provided in the 'licenses/LI This project depends on okhttp, which contains 'publicsuffixes.gz' that is licensed under the Mozilla Public License, v. 2.0 - https://mozilla.org/MPL/2.0/ A copy of the publicsuffixes.gz license (Mozilla Public License, v. 2.0) is provided in the 'licenses/LICENSE_mpl-2' file. +This project depends on HdrHistogram which is licensed under the 2-Clause BSD License - https://opensource.org/licenses/BSD-2-Clause +A copy of the HdrHistogram license (2-Clause BSD License) is provided in the 'licenses/LICENSE_hdrhistogram_bsd-2-clause' file. + Details on the individual dependencies notices and licenses can be found in the 'licenses' folder. diff --git a/licenses/LICENSE_hdrhistogram_bsd-2-clause b/licenses/LICENSE_hdrhistogram_bsd-2-clause new file mode 100644 index 000000000..401ccfb0e --- /dev/null +++ b/licenses/LICENSE_hdrhistogram_bsd-2-clause @@ -0,0 +1,41 @@ +The code in this repository code was Written by Gil Tene, Michael Barker, +and Matt Warren, and released to the public domain, as explained at +http://creativecommons.org/publicdomain/zero/1.0/ + +For users of this code who wish to consume it under the "BSD" license +rather than under the public domain or CC0 contribution text mentioned +above, the code found under this directory is *also* provided under the +following license (commonly referred to as the BSD 2-Clause License). This +license does not detract from the above stated release of the code into +the public domain, and simply represents an additional license granted by +the Author. + +----------------------------------------------------------------------------- +** Beginning of "BSD 2-Clause License" text. ** + + Copyright (c) 2012, 2013, 2014, 2015, 2016 Gil Tene + Copyright (c) 2014 Michael Barker + Copyright (c) 2014 Matt Warren + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. From 325bc9ecc089efb943c91c83d4fece2171472b7c Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 23 Apr 2024 17:49:15 +0200 Subject: [PATCH 11/12] add cc0 for hdrhistogram --- NOTICE | 6 ++++-- ...m_bsd-2-clause => LICENSE_hdrhistogram_bsd-2-clause_cc0} | 0 2 files changed, 4 insertions(+), 2 deletions(-) rename licenses/{LICENSE_hdrhistogram_bsd-2-clause => LICENSE_hdrhistogram_bsd-2-clause_cc0} (100%) diff --git a/NOTICE b/NOTICE index e27d154b3..348956b84 100644 --- a/NOTICE +++ b/NOTICE @@ -10,8 +10,10 @@ A copy of the ASM license (3-Clause BSD License) is provided in the 'licenses/LI This project depends on okhttp, which contains 'publicsuffixes.gz' that is licensed under the Mozilla Public License, v. 2.0 - https://mozilla.org/MPL/2.0/ A copy of the publicsuffixes.gz license (Mozilla Public License, v. 2.0) is provided in the 'licenses/LICENSE_mpl-2' file. -This project depends on HdrHistogram which is licensed under the 2-Clause BSD License - https://opensource.org/licenses/BSD-2-Clause -A copy of the HdrHistogram license (2-Clause BSD License) is provided in the 'licenses/LICENSE_hdrhistogram_bsd-2-clause' file. +This project depends on HdrHistogram which is dual-licensed under the following licenses: +- 2-Clause BSD License - https://opensource.org/licenses/BSD-2-Clause +- Creative Commons Public Domain License - https://creativecommons.org/publicdomain/zero/1.0/ +A copy of the HdrHistogram license is provided in the 'licenses/LICENSE_hdrhistogram_bsd-2-clause_cc0' file. Details on the individual dependencies notices and licenses can be found in the 'licenses' folder. diff --git a/licenses/LICENSE_hdrhistogram_bsd-2-clause b/licenses/LICENSE_hdrhistogram_bsd-2-clause_cc0 similarity index 100% rename from licenses/LICENSE_hdrhistogram_bsd-2-clause rename to licenses/LICENSE_hdrhistogram_bsd-2-clause_cc0 From 0c657df1231fb925ec351c1b4524c1d65197f933 Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Wed, 24 Apr 2024 09:25:23 +0200 Subject: [PATCH 12/12] Add comment linking information on socket path length requirements. --- jvmti-access/src/main/java/co/elastic/otel/JvmtiAccess.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jvmti-access/src/main/java/co/elastic/otel/JvmtiAccess.java b/jvmti-access/src/main/java/co/elastic/otel/JvmtiAccess.java index c2144cda5..afb9c421d 100644 --- a/jvmti-access/src/main/java/co/elastic/otel/JvmtiAccess.java +++ b/jvmti-access/src/main/java/co/elastic/otel/JvmtiAccess.java @@ -50,6 +50,12 @@ static void setProfilingCorrelationCurrentThreadStorage(@Nullable ByteBuffer sto JvmtiAccessImpl.setThreadProfilingCorrelationBuffer0(storage); } + /** + * Starts the socket for receiving universal profiler messages on the given filepath. Note that + * the path has a limitation of about 100 characters, see this + * discussion for details. + */ static void startProfilerReturnChannelSocket(String filepath) { ensureInitialized(); checkError(JvmtiAccessImpl.startProfilerReturnChannelSocket0(filepath));