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

Aerospike client instrumentation #9836

Open
wants to merge 28 commits into
base: main
Choose a base branch
from

Conversation

Abhishekkr3003
Copy link

Resolves #6578

@Abhishekkr3003 Abhishekkr3003 requested a review from a team November 9, 2023 19:54
Copy link

linux-foundation-easycla bot commented Nov 9, 2023

CLA Signed

The committers listed above are authorized under a signed CLA.

@Abhishekkr3003 Abhishekkr3003 force-pushed the aerospike-client-instrumentation branch from a39cf52 to a03903c Compare November 9, 2023 20:20
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

public final class AerospikeMetrics implements OperationListener {
Copy link
Contributor

Choose a reason for hiding this comment

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

Usually we place framework specific instrumentation classes with the framework instrumentation not here

Copy link
Author

Choose a reason for hiding this comment

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

Moved it to the library in framework instrumentation.


compileOnly("com.google.auto.value:auto-value-annotations")
annotationProcessor("com.google.auto.value:auto-value")
testInstrumentation(project(":instrumentation:aerospike-client:aerospike-client-7.1:javaagent"))
Copy link
Contributor

Choose a reason for hiding this comment

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

should be added automatically

Copy link
Author

Choose a reason for hiding this comment

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

Yes, changed it to use the framework instrumentation library.

pass {
group.set("com.aerospike")
module.set("aerospike-client")
versions.set("[7.1.0,)")
Copy link
Contributor

Choose a reason for hiding this comment

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

usually we also add assertInverse.set(true) to verify that the instrumentation does not pass on older versions

Copy link
Author

Choose a reason for hiding this comment

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

Added, assertInverse

import org.testcontainers.containers.wait.strategy.Wait;

@SuppressWarnings("deprecation") // until old http semconv are dropped in 2.0
class AerospikeClientTest {
Copy link
Contributor

Choose a reason for hiding this comment

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

could you also add tests for the metrics

Copy link
Author

Choose a reason for hiding this comment

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

Done

request.setStatus(Status.FAILURE);
}
requestContext.endSpan(AersopikeSingletons.instrumenter(), context, request, throwable);
Scope scope = context.makeCurrent();
Copy link
Contributor

Choose a reason for hiding this comment

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

activating scope to just close it isn't useful, you can delete this

Copy link
Author

Choose a reason for hiding this comment

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

Yes, cleaned this

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(
@Advice.This Command command,
@Advice.Local("otelAerospikeRequest") AerospikeRequest request,
Copy link
Contributor

Choose a reason for hiding this comment

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

@Advice.Local is usually used to pass variables from enter advice to exit advice. Just using it on exit advice doesn't make sense, you could just as well just have a local inside the advice method.

Copy link
Author

Choose a reason for hiding this comment

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

Refactored it as well.

@@ -0,0 +1,11 @@
plugins {
id("otel.library-instrumentation")
Copy link
Contributor

Choose a reason for hiding this comment

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

By library instrumentation we mean an instrumentation that can be used without javaagent which this module is not. In my opinion you should move the classes from this module to the javaagent module.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for clarifying! I have moved the metrics to javaagent module.

final class AerospikeMetricsAdvice {
private AerospikeMetricsAdvice() {}

@SuppressWarnings("deprecation") // until old http semconv are dropped in 2.0
Copy link
Contributor

Choose a reason for hiding this comment

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

old http semconv has been removed from the agent, you can also remove it

Copy link
Author

Choose a reason for hiding this comment

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

Alright, removed it.

@SuppressWarnings("deprecation")
// until old http semconv are dropped in 2.0
void collectsMetrics() {
InMemoryMetricReader metricReader = InMemoryMetricReader.create();
Copy link
Contributor

Choose a reason for hiding this comment

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

Testing metrics this way is a bit unusual, it would be easier to combine traces test with metrics like in

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, indeed. Merged metrics tests with traces tests.

@Abhishekkr3003 Abhishekkr3003 force-pushed the aerospike-client-instrumentation branch from bd4a7f8 to 5059c8e Compare December 31, 2023 13:56
@Abhishekkr3003 Abhishekkr3003 force-pushed the aerospike-client-instrumentation branch from f99bcdb to 93da28f Compare December 31, 2023 15:41
contextThreadLocal.remove();
}

@SuppressWarnings("unchecked")
Copy link
Contributor

Choose a reason for hiding this comment

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

is the suppress warnings really necessary?

pass {
group.set("com.aerospike")
module.set("aerospike-client")
versions.set("[4.4.9,)")
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 you want this instrumentation to work starting from 7.1.0 and don't care about earlier versions? The idea here is to have the muzzle version the same as the earliest supported version to avoid the situation where instrumentation applies to some earlier version but does not work correctly. You could bring the minimum version down to 5.0.0 where tests seem to pass with a small modification or I guess you could artificially restrict the version where the instrumentation works with something like

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return hasClassesNamed("redis.clients.jedis.CommandArguments");
}
where you enable the instrumentation only when a class that was added in 7.1.0 is present (of course this only works when such a class exists).

Comment on lines 39 to 42
@Override
public boolean isIndyModule() {
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

isIndyModule is an experimental feature you don't need to add this method unless you know that what is done in this instrumentation fails with indy for some reason

}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static AerospikeRequestContext stopSpan(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you should return void instead of AerospikeRequestContext, the return value isn't used

}
context = AersopikeSingletons.instrumenter().start(parentContext, request);
scope = context.makeCurrent();
return AerospikeRequestContext.attach(request, context);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would make sense to skip the ThreadLocal when you don't really need it. Perhaps have a method that just creates the request context? Or just use instrumenter().end(...) or have a static version of requestContext.endSpan(...) so you could call it without the request context.

.addAttributesExtractor(NetworkAttributesExtractor.create(netAttributesGetter))
.addOperationMetrics(AerospikeMetrics.get());
if (InstrumentationConfig.get()
.getBoolean("otel.instrumentation.aerospike.experimental-span-attributes", false)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

VirtualField<Command, AerospikeRequestContext> virtualField =
VirtualField.find(Command.class, AerospikeRequestContext.class);
AerospikeRequestContext requestContext = virtualField.get(command);
if (requestContext != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

if it is really possible that the command under construction already has a request attached then it would be better to add a comment here how this could happen

if (!instrumenter().shouldStart(parentContext, request)) {
return;
}
Context context = instrumenter().start(parentContext, request);
Copy link
Contributor

Choose a reason for hiding this comment

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

I get the feeling that constructor of the command object isn't a good instrumentation point. Perhaps EventLoop.execute(...) would be better? When you need to use thread local to pass context then ideally you should be able set it at the start of the method and clear it at then end.

AerospikeRequest request = requestContext.getRequest();
Context context = requestContext.getContext();
if (throwable == null) {
request.setStatus(Status.SUCCESS);
Copy link
Contributor

Choose a reason for hiding this comment

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

my understanding is that otel generally favors leaving status unset when operation was successful.

eventPolicy.commandsPerEventLoop = 2;
EventLoops eventLoops = new NioEventLoops(eventPolicy, eventLoopSize);
clientPolicy.eventLoops = eventLoops;
clientPolicy.maxConnsPerNode = 11;
Copy link
Contributor

Choose a reason for hiding this comment

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

For me the test fails to start with

AerospikeClientTest > initializationError FAILED
    com.aerospike.client.AerospikeException: Error -1: asyncMaxConnsPerNode 11 must be >= event loop count 16
        at app//com.aerospike.client.cluster.Cluster.<init>(Cluster.java:340)
        at app//com.aerospike.client.AerospikeClient.<init>(AerospikeClient.java:294)
        at app//com.aerospike.client.AerospikeClient.<init>(AerospikeClient.java:243)
        at app//io.opentelemetry.javaagent.instrumentation.aerospike.v7_1.AerospikeClientTest.setupSpec(AerospikeClientTest.java:71)

@github-actions github-actions bot requested a review from theletterf February 11, 2024 16:50
@Abhishekkr3003 Abhishekkr3003 requested a review from a team as a code owner November 26, 2024 12:56
@Abhishekkr3003 Abhishekkr3003 force-pushed the aerospike-client-instrumentation branch from 7db1a8e to 4bb06f2 Compare November 26, 2024 15:06
@github-actions github-actions bot added the test native This label can be applied to PRs to trigger them to run native tests label Nov 26, 2024
@Abhishekkr3003 Abhishekkr3003 force-pushed the aerospike-client-instrumentation branch from 4bb06f2 to cfc0227 Compare November 26, 2024 15:14
@Abhishekkr3003
Copy link
Author

@laurit, I have updated this PR based on your previous comments.

@@ -0,0 +1,4 @@
kotlin version: 2.0.20
Copy link
Contributor

Choose a reason for hiding this comment

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

remove this file

| System property | Type | Default | Description |
|---------------------------------------------------------------|---------|---------|----------------------------------------------------------------------|
| `otel.instrumentation.aerospike.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental Aerospike client span attributes. |
| `otel.instrumentation.aerospike.experimental-metrics` | Boolean | `false` | Enable the recording of experimental Aerospike client metrics |
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
| `otel.instrumentation.aerospike.experimental-metrics` | Boolean | `false` | Enable the recording of experimental Aerospike client metrics |
| `otel.instrumentation.aerospike.experimental-metrics` | Boolean | `false` | Enable the recording of experimental Aerospike client metrics. |

}

dependencies {
library("com.aerospike:aerospike-client:8.0.0")
Copy link
Contributor

Choose a reason for hiding this comment

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

What version do you want to support? Here you use 8, but in package name you use 7. Muzzle passes for 4, would that work?
Since the latest version seems to require java 21 you need to do something like

if (latestDepTest) {
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_17)
}
}


@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return hasClassesNamed("com.aerospike.client.IAerospikeClient")
Copy link
Contributor

Choose a reason for hiding this comment

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

hasClassesNamed accepts an array of strings so you could have one call to hasClassesNamed that lists all the classes. Usually you'd use hasClassesNamed here to avoid running the instrumentation on old version of library and here you'd test that a class that was added in the new version is present. Looking at the list of classes makes me wonder why you are doing this and whether this is needed at all.

@laurit
Copy link
Contributor

laurit commented Nov 27, 2024

import io.opentelemetry.javaagent.instrumentation.aerospike.v7_0.internal.AerospikeRequest;
import io.opentelemetry.javaagent.instrumentation.aerospike.v7_0.metrics.AerospikeMetrics;

public final class AersopikeSingletons {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
public final class AersopikeSingletons {
public final class AerospikeSingletons {

Comment on lines 17 to 21
if (latestDepTest) {
library("com.aerospike:aerospike-client:+")
} else {
library("com.aerospike:aerospike-client:7.1.0")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

just library("com.aerospike:aerospike-client:7.1.0") would be enough, when latest dep tests are run version for library dependencies is automatically changed to latest

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
test native This label can be applied to PRs to trigger them to run native tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Aerospike Instrumentation
3 participants