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 support 4 Apache Dubbo #2108

Merged
merged 33 commits into from
Feb 7, 2021
Merged

add support 4 Apache Dubbo #2108

merged 33 commits into from
Feb 7, 2021

Conversation

tydhot
Copy link
Member

@tydhot tydhot commented Jan 25, 2021

support apache dubbo version: 2.7.0+
feature #1888

@tydhot
Copy link
Member Author

tydhot commented Jan 25, 2021

@anuraaga Is this what you said about start with library instrumentation?:)

Copy link
Contributor

@anuraaga anuraaga left a comment

Choose a reason for hiding this comment

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

Thanks a lot for the instrumentation!


public Span startSpan(String name) {
SpanBuilder spanBuilder = tracer.spanBuilder(name).setSpanKind(CLIENT);
spanBuilder.setAttribute(SemanticAttributes.RPC_SYSTEM, "dubbo");
Copy link
Contributor

Choose a reason for hiding this comment

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

Separate from this PR it will be nice to have abstract String getRpcSystem() on RpcClientTracer to abstract this away.

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe it would be more appropriate to do it in another pr.

import io.opentelemetry.context.propagation.TextMapPropagator;
import org.apache.dubbo.rpc.RpcInvocation;

public class DubboInjectAdapter implements TextMapPropagator.Setter<RpcInvocation> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public class DubboInjectAdapter implements TextMapPropagator.Setter<RpcInvocation> {
class DubboInjectAdapter implements TextMapPropagator.Setter<RpcInvocation> {

this.tracer = new DubboClientTracer();
}

public static TracingClientFilter newFilter() {
Copy link
Contributor

Choose a reason for hiding this comment

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

How about a factory that accepts DubboClientTracer as a parameter too


@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (invocation instanceof RpcInvocation) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's reduce indent

if (!(invocation instanceoc RpcInvocation)) {
  return invoker.invoke(invocation);
}

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
if (invocation instanceof RpcInvocation) {
String methodName = invocation.getMethodName();
Span span = tracer.startSpan(methodName);
Copy link
Contributor

Choose a reason for hiding this comment

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

Span name must be the full name

https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/rpc.md#span-name

So I guess it is invoker.getInterface().getName() + '/' + invocation.getMethodName()

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we can add a utility in the helper for it for use in server side too


public static void prepareSpan(Span span, String methodName, Invoker<?> invoker) {
span.setAttribute(
SemanticAttributes.RPC_SERVICE, invoker.getInterface().getSimpleName() + ":" + methodName);
Copy link
Contributor

Choose a reason for hiding this comment

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

public static void prepareSpan(Span span, String methodName, Invoker<?> invoker) {
span.setAttribute(
SemanticAttributes.RPC_SERVICE, invoker.getInterface().getSimpleName() + ":" + methodName);
if (methodName != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can methodname be null?

import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.RpcInvocation;

public class TracingServerFilter implements Filter {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar comments as client/side

.and(takesArgument(1, String.class))
.and(takesArgument(2, String.class))
.and(isPublic()),
DubboFilterInstrumentation.class.getName() + "$AddFilterAdvice");
Copy link
Contributor

Copy link
Member Author

Choose a reason for hiding this comment

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

In dubbo, I found that importing resources directly does not work.It should be declared or configured in advance.Maybe code here it's easier to understand now。

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you describe more what the issue is? This injection happens on classload, so it's as early as it needs to be I think. For the type matcher in DubboInitializationInstrumentation, I think you can just use ExtensionLoader.

Looking at this version of the instrumentation, I see we instrument one of these similarly named methods

https://github.com/apache/dubbo/blob/master/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java#L246

It's unclear whether this is complete, or if it will need maintenance in the future. We can be more confident if we use the official mechanism though, which I guess is the resource file?

Base automatically changed from master to main January 26, 2021 05:50
@tydhot tydhot requested a review from anuraaga January 27, 2021 02:47
.and(takesArgument(1, String.class))
.and(takesArgument(2, String.class))
.and(isPublic()),
DubboFilterInstrumentation.class.getName() + "$AddFilterAdvice");
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you describe more what the issue is? This injection happens on classload, so it's as early as it needs to be I think. For the type matcher in DubboInitializationInstrumentation, I think you can just use ExtensionLoader.

Looking at this version of the instrumentation, I see we instrument one of these similarly named methods

https://github.com/apache/dubbo/blob/master/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java#L246

It's unclear whether this is complete, or if it will need maintenance in the future. We can be more confident if we use the official mechanism though, which I guess is the resource file?

return span;
}

public Context withClient(Span span) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need this trivial helper, and the name is confusing I guess


private DubboHelper() {}

public static void prepareSpan(Span span, String methodName, String interfaceName) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Order it interfaceName, then methodName which is more common for class+method


class DubboTest extends AbstractDubboTest implements AgentTestTrait {
@Override
ServiceConfig configureServer() {
Copy link
Contributor

Choose a reason for hiding this comment

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

We generally have the tests differ only in the way they are instrumenting. Is it possible to have the registry / service definition happen in the base class, only difference is non-agent version calls addFilter if it's required?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes,I will fix it.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've made the unit test code cleaner

service.setInterface(HelloService)
service.setRef(new HelloServiceImpl())
service.setRegistry(registerConfig)
service.setFilter("OtelServerFilter")
Copy link
Contributor

Choose a reason for hiding this comment

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

Is setFilter required even though we have the resource files?

Copy link
Member Author

Choose a reason for hiding this comment

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

I found that if I add the Activate annotation at the beginning of a class it will be loaded automatically, and it is worked well in library.But maybe Dubbo's spi is more complicated than I thought, if I inject the resource file directly, it will be something wrong in javaagent's unit test.It will be the exception like failOnContextLeak I've spent a lot of time checking, but I don't have a clue.It seems that the process of Dubbo loading the server filter is different from that of the client filter.I have changed the code in javaagent in order to make it work by Activate annotation, and it can avoid the exception failOnContextLeak now in javaagent's unit test.

trace(0, 3) {
basicSpan(it, 0, "parent")
span(1) {
name "org.apache.dubbo.rpc.service.GenericService/\$invoke"
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 generally expect the client service / method to match the server one. In dubbo, is it not possible for the client to have the name HelloService/hello instead of this generic name? I see setInterface and the method name hello in the invocation, so it seems like the client has all the information.

Copy link
Member Author

Choose a reason for hiding this comment

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

Dubbo will use javaassist to set up a proxy for the interface to call directly instead of generic invoke, but I find that the proxy cannot work in groovy's unit test.So I use generic invoke here instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see - we'll need to add a smoke-test to verify the behavior in a future PR then. Can you add a comment here?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd love to do it

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah sorry I meant a comment in the code :P // Dubbo's codegen does not work properly from Groovy tests, so we need to verify in a smoke test instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you go ahead and add this code comment suggestion?

@tydhot
Copy link
Member Author

tydhot commented Jan 27, 2021

Hi @anuraaga !The library module I had refer to the implementation of Zipkin and rewrite it.Now the code is cleaner and additional support for asynchronous future is added.In library, the filter will be Autoload.
And I tried to inject the resource file in javaagent,the test case will be an exception like context leak.So I decided to remove the filter's autoload and add it when method back instead in javaagent module.It seemed work and the code is cleaner than before. Plz review it:)!

@tydhot tydhot requested a review from anuraaga January 27, 2021 11:39
@tydhot
Copy link
Member Author

tydhot commented Jan 28, 2021

@anuraaga I have changed the way and now the javaagent module is liked aws's javaagent module.I think It is more confident to do like it so.

@anuraaga
Copy link
Contributor

anuraaga commented Feb 3, 2021

@tydhot Sorry for the delay in checking this out - I found a problem with our tooling with a fix in #2179.

Now there is a different issue where the agent doesn't inject the references from the dubbo file since it's not a standard formatted SPI file. I think we can tweak our mechanism for resolving from SPI so instrumentation can customize the parsing technique. We basically need a way for

https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/2108/files#diff-92953523ffff3580ebb3ff4d0a1b1276d3aacfa375416f2a36e070117022bacfR1 to be parsed by

https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/muzzle/collector/ReferenceCollector.java#L60

@mateuszrzeszutek what do you think of this? Another simpler option, that loses the complete automation, is to have MuzzleCodeGenerator also pass additionalHelperClasses to the reference collector. Manually defining one helper class would be no problem here.

dependencies {
implementation project(':instrumentation:dubbo:dubbo-apache-2.7:library')

library group: 'org.apache.dubbo', name: 'dubbo', version: '2.7.0'
Copy link
Contributor

Choose a reason for hiding this comment

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

We target tests to oldest version supported

Copy link
Member Author

Choose a reason for hiding this comment

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

Before Dubbo 2.7.0, it is called alibaba Dubbo.And it is called Apache Dubbo since 2.7.0.

@Override
public String[] helperResourceNames() {
return new String[] {
"META-INF/services/org.apache.dubbo.rpc.Filter",
Copy link
Contributor

Choose a reason for hiding this comment

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

I found /services is handled by dubbo too, so this works with our muzzle

@@ -0,0 +1 @@
io.opentelemetry.javaagent.shaded.instrumentation.dubbo.apache.v2_7.OpenTelemetryFilter
Copy link
Contributor

Choose a reason for hiding this comment

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

I found it automatically transfers the classname to a name if not provided, which works well for us and ensures working with Muzzle

https://github.com/apache/dubbo/blob/master/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java#L1011

@@ -69,9 +69,6 @@ void reset() {

@Override
public CompletableResultCode export(Collection<MetricData> metrics) {
for (MetricData metric : metrics) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Really spammy - let's restore in some form if metrics gets more stable

@anuraaga
Copy link
Contributor

anuraaga commented Feb 4, 2021

Did some debugging and got something that seems to work, almost there

@tydhot
Copy link
Member Author

tydhot commented Feb 4, 2021

@anuraaga Yes, I founded that the classname in resource's name had to be renamed by shaded just before. It took me a lot of time to debug it...

Copy link
Contributor

@anuraaga anuraaga left a comment

Choose a reason for hiding this comment

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

Few hopefully easy comments left, thanks for the patience

@@ -0,0 +1,17 @@
apply from: "$rootDir/gradle/instrumentation.gradle"
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we name the folders either apache-dubbo/apache-dubbo-2.7 for consistency with other apache projects, or just dubbo/dubbo-2.7? I guess dubbo is a unique enough name that apache may not be needed but whatever seems more familiar to users I guess

Copy link
Member Author

Choose a reason for hiding this comment

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

In China, Alibaba's Dubbo still has a certain number of users

Copy link
Contributor

@anuraaga anuraaga Feb 4, 2021

Choose a reason for hiding this comment

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

Ah got it - then yeah let's use apache-dubbo/apache-dubbo-2.7 (currently it is dubbo-apache-2.7)

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe it would be more cautious to separate Alibaba Dubbo and Apache Dubbo

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok I got it


private DubboHelper() {}

public static void prepareSpan(Span span, String interfaceName, String methodName) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Methods can be package private

.spanBuilder(DubboHelper.getSpanName(interfaceName, methodName))
.setSpanKind(SERVER)
.setParent(
GlobalOpenTelemetry.getPropagators()
Copy link
Contributor

Choose a reason for hiding this comment

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

How about trying this again, it should work now. The issue previously was we applied the filter twice, once found in library instrumentation (hence needing #2179) and once in agent instrumentation.

context = tracer.startClientSpan(interfaceName, methodName);
GlobalOpenTelemetry.getPropagators()
.getTextMapPropagator()
.inject(context, (RpcInvocation) invocation, SETTER);
Copy link
Contributor

Choose a reason for hiding this comment

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

Not for this PR, nor necessarily you, but inject needs to be moved from HttpClientTracer to BaseTracer

trace(0, 3) {
basicSpan(it, 0, "parent")
span(1) {
name "org.apache.dubbo.rpc.service.GenericService/\$invoke"
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you go ahead and add this code comment suggestion?

Copy link
Contributor

@anuraaga anuraaga left a comment

Choose a reason for hiding this comment

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

Thanks!

@tydhot
Copy link
Member Author

tydhot commented Feb 4, 2021

@anuraaga hah, it was a challenging and interesting development experience.

@anuraaga
Copy link
Contributor

anuraaga commented Feb 4, 2021

@tydhot So sorry my comment said "Thanks?" that was a typo for "Thanks!". On phone's Japanese keyboard, ? is too close to ! :(

@tydhot
Copy link
Member Author

tydhot commented Feb 4, 2021

@anuraaga ah, I got it immediately. BTW, I have translated doc in this project 70 percent , it help me a lot :)! Espaclly the doc about class loader . open-telemetry/docs-cn#49

Copy link
Member

@trask trask left a comment

Choose a reason for hiding this comment

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

great work @tydhot!

@trask trask merged commit a6527c2 into open-telemetry:main Feb 7, 2021
@trask
Copy link
Member

trask commented Feb 7, 2021

thanks again @tydhot!

@tydhot
Copy link
Member Author

tydhot commented Feb 7, 2021

@trask Well, I hope this is a good start for the Chinese community :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants