-
Notifications
You must be signed in to change notification settings - Fork 878
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
base: main
Are you sure you want to change the base?
Aerospike client instrumentation #9836
Conversation
a39cf52
to
a03903c
Compare
import java.util.concurrent.TimeUnit; | ||
import java.util.logging.Logger; | ||
|
||
public final class AerospikeMetrics implements OperationListener { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be added automatically
There was a problem hiding this comment.
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,)") |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added, assertInverse
...pentelemetry/javaagent/instrumentation/aerospike/v7_1/AerospikeClientAttributeExtractor.java
Outdated
Show resolved
Hide resolved
import org.testcontainers.containers.wait.strategy.Wait; | ||
|
||
@SuppressWarnings("deprecation") // until old http semconv are dropped in 2.0 | ||
class AerospikeClientTest { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
...a/io/opentelemetry/javaagent/instrumentation/aerospike/v7_1/AsyncHandlerInstrumentation.java
Outdated
Show resolved
Hide resolved
request.setStatus(Status.FAILURE); | ||
} | ||
requestContext.endSpan(AersopikeSingletons.instrumenter(), context, request, throwable); | ||
Scope scope = context.makeCurrent(); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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
Line 709 in 30ddf6a
testing.waitAndAssertMetrics( |
There was a problem hiding this comment.
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.
bd4a7f8
to
5059c8e
Compare
f99bcdb
to
93da28f
Compare
contextThreadLocal.remove(); | ||
} | ||
|
||
@SuppressWarnings("unchecked") |
There was a problem hiding this comment.
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,)") |
There was a problem hiding this comment.
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
Lines 24 to 27 in 0337158
@Override | |
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() { | |
return hasClassesNamed("redis.clients.jedis.CommandArguments"); | |
} |
7.1.0
is present (of course this only works when such a class exists).
@Override | ||
public boolean isIndyModule() { | ||
return false; | ||
} |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this flag should be document in README.md
like in https://github.com/Abhishekkr3003/opentelemetry-java-instrumentation/blob/aerospike-client-instrumentation/instrumentation/grpc-1.6/README.md
VirtualField<Command, AerospikeRequestContext> virtualField = | ||
VirtualField.find(Command.class, AerospikeRequestContext.class); | ||
AerospikeRequestContext requestContext = virtualField.get(command); | ||
if (requestContext != null) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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)
7db1a8e
to
4bb06f2
Compare
4bb06f2
to
cfc0227
Compare
@laurit, I have updated this PR based on your previous comments. |
@@ -0,0 +1,4 @@ | |||
kotlin version: 2.0.20 |
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| `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") |
There was a problem hiding this comment.
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
Lines 83 to 87 in c6a6fa6
if (latestDepTest) { | |
otelJava { | |
minJavaVersionSupported.set(JavaVersion.VERSION_17) | |
} | |
} |
|
||
@Override | ||
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() { | ||
return hasClassesNamed("com.aerospike.client.IAerospikeClient") |
There was a problem hiding this comment.
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.
import io.opentelemetry.javaagent.instrumentation.aerospike.v7_0.internal.AerospikeRequest; | ||
import io.opentelemetry.javaagent.instrumentation.aerospike.v7_0.metrics.AerospikeMetrics; | ||
|
||
public final class AersopikeSingletons { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public final class AersopikeSingletons { | |
public final class AerospikeSingletons { |
if (latestDepTest) { | ||
library("com.aerospike:aerospike-client:+") | ||
} else { | ||
library("com.aerospike:aerospike-client:7.1.0") | ||
} |
There was a problem hiding this comment.
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
Resolves #6578