Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add zipkin compatibility mode to open-tracing #18313

Merged
merged 1 commit into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion .github/native-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
{
"category": "Misc3",
"timeout": 65,
"test-modules": "kubernetes-client, openshift-client, smallrye-config, smallrye-graphql, smallrye-graphql-client, smallrye-metrics",
"test-modules": "kubernetes-client, openshift-client, smallrye-config, smallrye-graphql, smallrye-graphql-client, smallrye-metrics, smallrye-opentracing",
"os-name": "ubuntu-latest"
},
{
Expand Down
15 changes: 15 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2864,6 +2864,21 @@
<artifactId>jaeger-thrift</artifactId>
<version>${jaeger.version}</version>
</dependency>
<dependency>
<groupId>io.jaegertracing</groupId>
<artifactId>jaeger-zipkin</artifactId>
<version>${jaeger.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
Expand Down
21 changes: 21 additions & 0 deletions docs/src/main/asciidoc/opentracing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,27 @@ Following the link:mongodb[MongoDB guide], the command listener will be register
quarkus.mongodb.tracing.enabled=true
----

=== Zipkin compatibility mode

To enable it, add the following dependency to your pom.xml:

[source, xml]
----
<dependency>
<groupId>io.jaegertracing</groupId>
<artifactId>jaeger-zipkin</artifactId>
</dependency>
----

It contains the dependencies to convert the request to zipkin format.
The zipkin compatibility mode will be activated after defining the config property as follows:

[source, properties]
----
# Enable zipkin compatibility mode
quarkus.jaeger.zipkin.compatibility-mode=true
----

[[configuration-reference]]
== Jaeger Configuration Reference

Expand Down
11 changes: 10 additions & 1 deletion extensions/jaeger/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,22 @@
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-metrics-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jaegertracing</groupId>
<artifactId>jaeger-zipkin</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io.quarkus.jaeger.runtime.JaegerBuildTimeConfig;
import io.quarkus.jaeger.runtime.JaegerConfig;
import io.quarkus.jaeger.runtime.JaegerDeploymentRecorder;
import io.quarkus.jaeger.runtime.ZipkinConfig;
import io.quarkus.runtime.ApplicationConfig;
import io.quarkus.runtime.metrics.MetricsFactory;

Expand All @@ -29,18 +30,18 @@ void setVersion(JaegerDeploymentRecorder jdr) {
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
ExtensionSslNativeSupportBuildItem setupTracer(JaegerDeploymentRecorder jdr, JaegerBuildTimeConfig buildTimeConfig,
JaegerConfig jaeger,
ApplicationConfig appConfig, Optional<MetricsCapabilityBuildItem> metricsCapability) {
JaegerConfig jaeger, ApplicationConfig appConfig, Optional<MetricsCapabilityBuildItem> metricsCapability,
ZipkinConfig zipkinConfig) {

if (buildTimeConfig.enabled) {
if (buildTimeConfig.metricsEnabled && metricsCapability.isPresent()) {
if (metricsCapability.get().metricsSupported(MetricsFactory.MICROMETER)) {
jdr.registerTracerWithMicrometerMetrics(jaeger, appConfig);
jdr.registerTracerWithMicrometerMetrics(jaeger, appConfig, zipkinConfig);
} else {
jdr.registerTracerWithMpMetrics(jaeger, appConfig);
jdr.registerTracerWithMpMetrics(jaeger, appConfig, zipkinConfig);
}
} else {
jdr.registerTracerWithoutMetrics(jaeger, appConfig);
jdr.registerTracerWithoutMetrics(jaeger, appConfig, zipkinConfig);
}
}

Expand All @@ -58,7 +59,6 @@ public ReflectiveClassBuildItem reflectiveClasses() {
return ReflectiveClassBuildItem
.builder("io.jaegertracing.internal.samplers.http.SamplingStrategyResponse",
"io.jaegertracing.internal.samplers.http.ProbabilisticSamplingStrategy")
.finalFieldsWritable(true)
.build();
.finalFieldsWritable(true).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.quarkus.jaeger.deployment;

import java.util.function.BooleanSupplier;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.jaeger.runtime.JaegerDeploymentRecorder;
import io.quarkus.jaeger.runtime.ZipkinConfig;
import io.quarkus.jaeger.runtime.ZipkinReporterProvider;

public class ZipkinProcessor {

static final String REGISTRY_CLASS_NAME = "zipkin2.reporter.urlconnection.URLConnectionSender";
static final Class<?> REGISTRY_CLASS = JaegerDeploymentRecorder.getClassForName(REGISTRY_CLASS_NAME);

public static class ZipkinEnabled implements BooleanSupplier {
ZipkinConfig config;

public boolean getAsBoolean() {
return REGISTRY_CLASS != null && config.compatibilityMode;
}
}

@BuildStep(onlyIf = ZipkinEnabled.class)
void addZipkinClasses(BuildProducer<AdditionalBeanBuildItem> additionalBeans) {

// Add Zipkin classes
additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(ZipkinReporterProvider.class)
.setUnremovable().build());

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package io.quarkus.jaeger.test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import javax.enterprise.inject.Default;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.CDI;

import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import io.jaegertracing.Configuration;
import io.jaegertracing.internal.JaegerTracer;
import io.jaegertracing.internal.JaegerTracer.Builder;
import io.jaegertracing.spi.Reporter;
import io.jaegertracing.zipkin.ZipkinV2Reporter;
import io.opentracing.Tracer;
import io.quarkus.jaeger.runtime.QuarkusJaegerTracer;
import io.quarkus.jaeger.runtime.ReporterFactory;
import io.quarkus.jaeger.runtime.ZipkinReporterFactoryImpl;

public class QuarkusJaegerTracerTest {

@Test
@SuppressWarnings("unchecked")
public void withzipkinCompatibilityMode() {

try (MockedStatic<Configuration> mockedStaticConfiguration = Mockito.mockStatic(Configuration.class);
MockedStatic<CDI> mockedStaticCDI = Mockito.mockStatic(CDI.class)) {

CDI<Object> mockedCDI = (CDI<Object>) Mockito.mock(CDI.class);

mockedStaticCDI.when(() -> CDI.current()).thenReturn(mockedCDI);

Instance instanceCDI = Mockito.mock(Instance.class);
Mockito.when(instanceCDI.isAmbiguous()).thenReturn(false);
Mockito.when(instanceCDI.isUnsatisfied()).thenReturn(false);
Mockito.when(instanceCDI.get()).thenReturn(new ZipkinReporterFactoryImpl());
Mockito.when(mockedCDI.select(ReporterFactory.class, Default.Literal.INSTANCE)).thenReturn(instanceCDI);

Configuration mockedInstanceConfiguration = Mockito.mock(Configuration.class);
Builder mockedBuilder = Mockito.mock(Builder.class);
Tracer mockedTracer = Mockito.mock(JaegerTracer.class);

mockedStaticConfiguration.when(() -> Configuration.fromEnv()).thenReturn(mockedInstanceConfiguration);
mockedStaticConfiguration.when(() -> mockedInstanceConfiguration.withMetricsFactory(Mockito.any()))
.thenReturn(mockedInstanceConfiguration);
mockedStaticConfiguration.when(() -> mockedInstanceConfiguration.getTracerBuilder())
.thenReturn(mockedBuilder);
mockedStaticConfiguration.when(() -> mockedBuilder.withScopeManager(Mockito.any()))
.thenReturn(mockedBuilder);
mockedStaticConfiguration.when(() -> mockedBuilder.withReporter(Mockito.any())).thenReturn(mockedBuilder);
mockedStaticConfiguration.when(() -> mockedBuilder.build()).thenReturn(mockedTracer);

QuarkusJaegerTracer tracer = new QuarkusJaegerTracer();
tracer.setZipkinCompatibilityMode(true);
tracer.setEndpoint("http://localhost");
tracer.toString();
tracer.close();

ArgumentCaptor<Reporter> argument = ArgumentCaptor.forClass(Reporter.class);
Mockito.verify(mockedBuilder).withReporter(argument.capture());
assertEquals(ZipkinV2Reporter.class, argument.getValue().getClass());
}

}

@Test
public void withoutZipkinCompatibilityMode() {
try (MockedStatic<Configuration> mockedStaticConfiguration = Mockito.mockStatic(Configuration.class)) {
Configuration mockedInstanceConfiguration = Mockito.mock(Configuration.class);
Builder mockedBuilder = Mockito.mock(Builder.class);
Tracer mockedTracer = Mockito.mock(JaegerTracer.class);

mockedStaticConfiguration.when(() -> Configuration.fromEnv()).thenReturn(mockedInstanceConfiguration);
mockedStaticConfiguration.when(() -> mockedInstanceConfiguration.withMetricsFactory(Mockito.any()))
.thenReturn(mockedInstanceConfiguration);
mockedStaticConfiguration.when(() -> mockedInstanceConfiguration.getTracerBuilder())
.thenReturn(mockedBuilder);
mockedStaticConfiguration.when(() -> mockedBuilder.withScopeManager(Mockito.any()))
.thenReturn(mockedBuilder);
mockedStaticConfiguration.when(() -> mockedBuilder.withReporter(Mockito.any())).thenReturn(mockedBuilder);
mockedStaticConfiguration.when(() -> mockedBuilder.build()).thenReturn(mockedTracer);

QuarkusJaegerTracer tracer = new QuarkusJaegerTracer();
tracer.toString();
tracer.close();

ArgumentCaptor<Reporter> argument = ArgumentCaptor.forClass(Reporter.class);
Mockito.verify(mockedBuilder).withReporter(argument.capture());
assertNull(argument.getValue());
}
}

}
9 changes: 9 additions & 0 deletions extensions/jaeger/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.jaegertracing</groupId>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you measure the amount of additional RSS is present by introducing this dependency when running a native executable?

I'm concerned this approach to supporting Zipkin will add reasonable RSS weight to an application even when they're not using Zipkin compatibility

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made what you requested and the results are:
without my changes:

  • executable size: 43M
  • RSS on startup: 18908
  • RSS after first request: 22144

With my changes:

  • executable size: 44M
  • RSS on startup: 18960
  • RSS after first request: 24796

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @raulvaldoleiros!

@n1hility, @ebullient what do you think of these numbers? Sufficiently small? I'm inclined to say yes, as adding tracing adds weight anyway, but would appreciate your thoughts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have add the dependency as optional, explain in the guide that to have zipkin capability you need to add the dependency to your pom.xml, and make a check at runtime that the class is present before enabling Zipkin.

OK, it's a small RSS increase, but still, if we have more and more of those addition we will ends up with a much bigger RSS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had thought about suggesting that, but wasn't sure if it could be done without causing an issue when it wasn't there.

If it can be done, that would be better.

@raulvaldoleiros can you try?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to change the dependency scope to test or provided and it works fine in the quarkus build, but when I'm compiling an application it breaks without the dependency.

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 24.495 s
[INFO] Finished at: 2021-07-09T18:24:08+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal io.quarkus:quarkus-maven-plugin:999-SNAPSHOT:build (default) on project opentracing-quickstart: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[ERROR] [error]: Build step io.quarkus.jaeger.deployment.JaegerProcessor#setupTracer threw an exception: java.lang.NoClassDefFoundError: zipkin2/reporter/Sender

To make the dependency optional the only way is to use java reflection or do you have an example that I can follow?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make the dependency optional the only way is to use java reflection or do you have an example that I can follow?

Yes, you need to guard the usage of the Zipkin classes by a check that the class exist (Class.forName("") and if an exception occurs, do nothing).

The only other solution is to create a separate extension but it can be a lot of work so maybe it's not worth it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the code for zipkin be optional, and also I have updated the documentation. Native mode needs extra configuration but it works, I tried to put that configuration in the jaeger extension but I wasn't successful.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is extra configuration for native mode still necessary? If so, what configuration?

<artifactId>jaeger-zipkin</artifactId>
<optional>true</optional>
</dependency>
<!-- transitive dependency from jaeger-thrift, consider removing once we move to jaeger 1.x -->
<dependency>
<groupId>jakarta.activation</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,33 @@ public void setJaegerVersion(String version) {
}

/* RUNTIME_INIT */
public void registerTracerWithoutMetrics(JaegerConfig jaeger, ApplicationConfig appConfig) {
registerTracer(jaeger, appConfig, new NoopMetricsFactory());
public void registerTracerWithoutMetrics(JaegerConfig jaeger, ApplicationConfig appConfig,
ZipkinConfig zipkinConfig) {
registerTracer(jaeger, appConfig, new NoopMetricsFactory(), zipkinConfig);
}

/* RUNTIME_INIT */
public void registerTracerWithMpMetrics(JaegerConfig jaeger, ApplicationConfig appConfig) {
registerTracer(jaeger, appConfig, new QuarkusJaegerMpMetricsFactory());
public void registerTracerWithMpMetrics(JaegerConfig jaeger, ApplicationConfig appConfig,
ZipkinConfig zipkinConfig) {
registerTracer(jaeger, appConfig, new QuarkusJaegerMpMetricsFactory(), zipkinConfig);
}

/* RUNTIME_INIT */
public void registerTracerWithMicrometerMetrics(JaegerConfig jaeger, ApplicationConfig appConfig) {
registerTracer(jaeger, appConfig, new QuarkusJaegerMicrometerFactory());
public void registerTracerWithMicrometerMetrics(JaegerConfig jaeger, ApplicationConfig appConfig,
ZipkinConfig zipkinConfig) {
registerTracer(jaeger, appConfig, new QuarkusJaegerMicrometerFactory(), zipkinConfig);
}

private synchronized void registerTracer(JaegerConfig jaeger, ApplicationConfig appConfig, MetricsFactory metricsFactory) {
private synchronized void registerTracer(JaegerConfig jaeger, ApplicationConfig appConfig,
MetricsFactory metricsFactory, ZipkinConfig zipkinConfig) {
if (!jaeger.serviceName.isPresent()) {
if (appConfig.name.isPresent()) {
jaeger.serviceName = appConfig.name;
} else {
jaeger.serviceName = UNKNOWN_SERVICE_NAME;
}
}
initTracerConfig(jaeger);
initTracerConfig(jaeger, zipkinConfig);
quarkusTracer.setMetricsFactory(metricsFactory);
quarkusTracer.reset();
// register Quarkus tracer to GlobalTracer.
Expand All @@ -60,8 +64,11 @@ private synchronized void registerTracer(JaegerConfig jaeger, ApplicationConfig
}
}

private void initTracerConfig(JaegerConfig jaeger) {
private void initTracerConfig(JaegerConfig jaeger, ZipkinConfig zipkinConfig) {
initTracerProperty("JAEGER_ENDPOINT", jaeger.endpoint, uri -> uri.toString());
if (jaeger.endpoint.isPresent()) {
quarkusTracer.setEndpoint(jaeger.endpoint.get().toString());
}
initTracerProperty("JAEGER_AUTH_TOKEN", jaeger.authToken, token -> token);
initTracerProperty("JAEGER_USER", jaeger.user, user -> user);
initTracerProperty("JAEGER_PASSWORD", jaeger.password, pw -> pw);
Expand All @@ -79,6 +86,7 @@ private void initTracerConfig(JaegerConfig jaeger) {
initTracerProperty("JAEGER_PROPAGATION", jaeger.propagation, format -> format.toString());
initTracerProperty("JAEGER_SENDER_FACTORY", jaeger.senderFactory, sender -> sender);
quarkusTracer.setLogTraceContext(jaeger.logTraceContext);
quarkusTracer.setZipkinCompatibilityMode(zipkinConfig.compatibilityMode);
}

private <T> void initTracerProperty(String property, Optional<T> value, Function<T, String> accessor) {
Expand All @@ -92,4 +100,15 @@ private void initTracerProperty(String property, OptionalInt value, Function<Int
System.setProperty(property, accessor.apply(Integer.valueOf(value.getAsInt())));
}
}

public static Class<?> getClassForName(String className) {
Class<?> clazz = null;
try {
clazz = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
// Ignore exception
}
log.debugf("getClass: TCCL: %s ## %s : %s", Thread.currentThread().getContextClassLoader(), className, (clazz != null));
return clazz;
}
}
Loading