diff --git a/.buildkite/release.yml b/.buildkite/release.yml index 3c657e735..02662af05 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" - "agentextension/build/libs/elastic-otel-agentextension-.*.jar" - "common/build/libs/common-.*.jar" - "inferred-spans/build/libs/inferred-spans-.*.jar" diff --git a/.buildkite/snapshot.yml b/.buildkite/snapshot.yml index 2709c6fcf..5d86bb9f3 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" - "agentextension/build/libs/elastic-otel-agentextension-.*.jar" - "common/build/libs/common-.*.jar" - "inferred-spans/build/libs/inferred-spans-.*.jar" diff --git a/NOTICE b/NOTICE index 9ab44ca65..348956b84 100644 --- a/NOTICE +++ b/NOTICE @@ -10,5 +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 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/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..3956e6624 --- /dev/null +++ b/common/src/main/java/co/elastic/otel/common/config/PropertiesApplier.java @@ -0,0 +1,72 @@ +/* + * 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; +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 99% 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..fdd5a3336 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; 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/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..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 @@ -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 { @@ -78,49 +72,4 @@ public void customize(AutoConfigurationCustomizer config) { return providerBuilder; }); } - - 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; diff --git a/jvmti-access/build.gradle.kts b/jvmti-access/build.gradle.kts index 0a6a82bb0..006e74334 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/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)); diff --git a/licenses/LICENSE_hdrhistogram_bsd-2-clause_cc0 b/licenses/LICENSE_hdrhistogram_bsd-2-clause_cc0 new file mode 100644 index 000000000..401ccfb0e --- /dev/null +++ b/licenses/LICENSE_hdrhistogram_bsd-2-clause_cc0 @@ -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. 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..f2a07776b --- /dev/null +++ b/testing/integration-tests/universal-profiling-test/src/test/java/UniversalProfilingIntegrationTest.java @@ -0,0 +1,44 @@ +/* + * 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; +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/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/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 4280d3e6a..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); @@ -154,7 +156,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..aebb63004 --- /dev/null +++ b/universal-profiling-integration/src/main/java/co/elastic/otel/UniversalProfilingProcessorAutoConfig.java @@ -0,0 +1,80 @@ +/* + * 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; +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"; + 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) { + + String enabledString = + properties.getString(ENABLED_OPTION, EnabledOptions.AUTO.toString()).toUpperCase(); + EnabledOptions enabled = EnabledOptions.valueOf(enabledString); + + 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); + 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/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; 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); + } + } +}