Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
cabecf0
boostrap app in docker image
SylvainJuge Nov 27, 2025
a545981
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Nov 27, 2025
dda3c85
start creating instrumentation
SylvainJuge Nov 27, 2025
45a5d60
wip
SylvainJuge Nov 27, 2025
678a6fb
wip
SylvainJuge Nov 27, 2025
7c907ce
add missing dependencies
SylvainJuge Nov 28, 2025
9c9d3de
add missing task dependency for tests
SylvainJuge Nov 28, 2025
9766b9f
fix instrumentation
SylvainJuge Nov 28, 2025
352431d
it's working !!
SylvainJuge Nov 28, 2025
b0a8052
further simplify and modify arguments
SylvainJuge Nov 28, 2025
a157d64
wip: test with virtual fields
SylvainJuge Dec 1, 2025
00e8b59
add wip changes
SylvainJuge Dec 1, 2025
04d5252
simplify things without custom docker image
SylvainJuge Dec 1, 2025
27c951c
fix virtual fields and make it work again
SylvainJuge Dec 1, 2025
31ecfc2
fix test assertions + impl local variable
SylvainJuge Dec 1, 2025
4fe6a4b
local value support working
SylvainJuge Dec 1, 2025
6d3508f
spotless
SylvainJuge Dec 1, 2025
f280d72
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Dec 1, 2025
936f40c
update readme
SylvainJuge Dec 2, 2025
cf39a5a
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Dec 2, 2025
4f01f70
rename and simplify
SylvainJuge Dec 3, 2025
2a1fae5
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Dec 3, 2025
94916f4
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Dec 4, 2025
c254070
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ include(":smoke-tests:images:servlet")
include(":smoke-tests:images:servlet:servlet-3.0")
include(":smoke-tests:images:servlet:servlet-5.0")
include(":smoke-tests:images:spring-boot")
include(":smoke-tests:extensions:testapp")
include(":smoke-tests:extensions:extension")

include(":smoke-tests-otel-starter:spring-smoke-testing")
include(":smoke-tests-otel-starter:spring-boot-2")
Expand Down
5 changes: 4 additions & 1 deletion smoke-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@

Assert that various applications will start up with the JavaAgent without any obvious ill effects.

Each subproject underneath `smoke-tests` produces one or more docker images containing some application
Each subproject underneath `smoke-tests/images` produces one or more docker images containing some application
under the test. Various tests in the main module then use them to run the appropriate tests.

The `smoke-tests/extensions` folder contains a test application and packaged instrumentation(s) extension to
test compatibility with existing user-built extensions.
14 changes: 13 additions & 1 deletion smoke-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,20 @@ tasks {
.withPropertyName("javaagent")
.withNormalizer(ClasspathNormalizer::class)

val extensionTask = project(":smoke-tests:extensions:extension").tasks.named<Jar>("jar")
val extensionJarPath = extensionTask.flatMap { it.archiveFile }

val extensionTestAppTask = project(":smoke-tests:extensions:testapp").tasks.named<Jar>("jar")
val extensionTestAppJarPath = extensionTestAppTask.flatMap { it.archiveFile }

dependsOn(shadowTask, extensionTestAppTask, extensionTask)

doFirst {
jvmArgs("-Dio.opentelemetry.smoketest.agent.shadowJar.path=${agentJarPath.get()}")
jvmArgs(
"-Dio.opentelemetry.smoketest.agent.shadowJar.path=${agentJarPath.get()}",
"-Dio.opentelemetry.smoketest.extension.path=${extensionJarPath.get()}",
"-Dio.opentelemetry.smoketest.extension.testapp.path=${extensionTestAppJarPath.get()}"
)
}
}
}
19 changes: 19 additions & 0 deletions smoke-tests/extensions/extension/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
plugins {
id("otel.java-conventions")
id("io.opentelemetry.instrumentation.javaagent-instrumentation")
}

dependencies {
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api")
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator")
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")

compileOnly("com.google.auto.service:auto-service")
compileOnly("com.google.auto.service:auto-service-annotations")

annotationProcessor("com.google.auto.service:auto-service")

// Used by byte-buddy but not brought in as a transitive dependency
compileOnly("com.google.code.findbugs:annotations")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.smoketest.extensions.inlined;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import io.opentelemetry.instrumentation.api.util.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.util.Arrays;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public class SmokeInlinedInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("io.opentelemetry.smoketest.extensions.app.AppMain");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("returnValue").and(takesArgument(0, int.class)),
this.getClass().getName() + "$ModifyReturnValueAdvice");
transformer.applyAdviceToMethod(
named("methodArguments").and(takesArgument(0, int.class)),
this.getClass().getName() + "$ModifyArgumentsAdvice");
transformer.applyAdviceToMethod(
named("setVirtualFieldValue")
.and(takesArgument(0, Object.class))
.and(takesArgument(1, Integer.class)),
this.getClass().getName() + "$VirtualFieldSetAdvice");
transformer.applyAdviceToMethod(
named("getVirtualFieldValue")
.and(takesArgument(0, Object.class))
.and(returns(Integer.class)),
this.getClass().getName() + "$VirtualFieldGetAdvice");
transformer.applyAdviceToMethod(
named("localValue").and(takesArgument(0, int[].class)).and(returns(int[].class)),
this.getClass().getName() + "$LocalVariableAdvice");
}

@SuppressWarnings("unused")
public static class ModifyReturnValueAdvice {

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(@Advice.Return(readOnly = false) int returnValue) {
returnValue = returnValue + 1;
}
}

@SuppressWarnings("unused")
public static class ModifyArgumentsAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(@Advice.Argument(value = 0, readOnly = false) int argument) {
argument = argument - 1;
}
}

public static class VirtualFieldSetAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) Object target, @Advice.Argument(1) Integer value) {
VirtualField<Object, Integer> field = VirtualField.find(Object.class, Integer.class);
field.set(target, value);
}
}

@SuppressWarnings("unused")
public static class VirtualFieldGetAdvice {
@SuppressWarnings("UnusedVariable")
@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(
@Advice.Argument(0) Object target, @Advice.Return(readOnly = false) Integer returnValue) {
VirtualField<Object, Integer> field = VirtualField.find(Object.class, Integer.class);
returnValue = field.get(target);
}
}

@SuppressWarnings("unused")
public static class LocalVariableAdvice {

@SuppressWarnings("UnusedVariable")
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(
@Advice.Argument(0) int[] array, @Advice.Local("backup") int[] backupArray) {
backupArray = Arrays.copyOf(array, array.length);
}

@SuppressWarnings("UnusedVariable")
@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(
@Advice.Return(readOnly = false) int[] array, @Advice.Local("backup") int[] backupArray) {
array = Arrays.copyOf(backupArray, backupArray.length);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.smoketest.extensions.inlined;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.Collections;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class SmokeInlinedInstrumentationModule extends InstrumentationModule {

public SmokeInlinedInstrumentationModule() {
super("smoke-test-extension-inlined");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return Collections.singletonList(new SmokeInlinedInstrumentation());
}
}
19 changes: 19 additions & 0 deletions smoke-tests/extensions/testapp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
plugins {
id("otel.java-conventions")
}

dependencies {
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

tasks {
jar {
manifest {
attributes("Main-Class" to "io.opentelemetry.smoketest.extensions.app.AppMain")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.smoketest.extensions.app;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.PrintStream;
import java.util.Arrays;

public class AppMain {

private AppMain() {}

public static void main(String[] args) {
testReturnValue();
testMethodArguments();
testVirtualFields();
testLocalValue();
}

private static void msg(String msg) {
// avoid checkstyle to complain
PrintStream out = System.out;
out.println(msg);
}

private static void testReturnValue() {
int returnValue = returnValue(42);
if (returnValue != 42) {
msg("return value has been modified");
} else {
msg("return value not modified");
}
}

private static int returnValue(int value) {
// method return value should be modified by instrumentation
return value;
}

private static void testMethodArguments() {
methodArguments(42, 42);
}

private static void methodArguments(int argument, int originalArgument) {
// method first argument should be modified by instrumentation
if (argument != originalArgument) {
msg("argument has been modified");
} else {
msg("argument not modified");
}
}

private static void testVirtualFields() {
Object target = new Object();
setVirtualFieldValue(target, 42);
Integer fieldValue = getVirtualFieldValue(target);
if (fieldValue == null || fieldValue != 42) {
msg("virtual field not supported");
} else {
msg("virtual field supported");
}
}

public static void setVirtualFieldValue(Object target, Integer value) {
// implementation should be provided by instrumentation
}

public static Integer getVirtualFieldValue(Object target) {
// implementation should be provided by instrumentation
return null;
}

private static void testLocalValue() {
int[] input = new int[] {1, 2, 3};
int[] result = localValue(input);
if (result.length != 3) {
throw new IllegalStateException();
}
// assumption on the instrumentation implementation to use a local value to preserve original
// array
boolean preserved = result[0] == 1 && result[1] == 2 && result[2] == 3;
if (!preserved) {
msg("local advice variable not supported");
} else {
msg("local advice variable supported");
}
}

@CanIgnoreReturnValue
private static int[] localValue(int[] array) {
Arrays.fill(array, 0);
return array;
}
}
Loading
Loading