From 866ba338fb4cfe7667b5d6d8d1104b7385a3cdb2 Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Thu, 6 Nov 2025 17:43:41 +0800 Subject: [PATCH 1/4] Add server address and port for Spymemcached --- .../SpymemcachedNetworkAttributesGetter.java | 50 +++++++ .../spymemcached/SpymemcachedSingletons.java | 3 + .../spymemcached/SpymemcachedTest.java | 132 ++++++++++++++---- 3 files changed, 158 insertions(+), 27 deletions(-) create mode 100644 instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java new file mode 100644 index 000000000000..09bc7121088e --- /dev/null +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java @@ -0,0 +1,50 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spymemcached; + +import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesGetter; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Collection; +import javax.annotation.Nullable; +import net.spy.memcached.MemcachedConnection; +import net.spy.memcached.MemcachedNode; + +final class SpymemcachedNetworkAttributesGetter + implements ServerAttributesGetter { + + @Nullable + @Override + public String getServerAddress(SpymemcachedRequest request) { + MemcachedConnection connection = request.getConnection(); + if (connection != null) { + Collection nodes = connection.getLocator().getAll(); + if (!nodes.isEmpty()) { + SocketAddress socketAddress = nodes.iterator().next().getSocketAddress(); + if (socketAddress instanceof InetSocketAddress) { + return ((InetSocketAddress) socketAddress).getHostString(); + } + } + } + return null; + } + + @Nullable + @Override + public Integer getServerPort(SpymemcachedRequest request) { + MemcachedConnection connection = request.getConnection(); + if (connection != null) { + Collection nodes = connection.getLocator().getAll(); + if (!nodes.isEmpty()) { + SocketAddress socketAddress = nodes.iterator().next().getSocketAddress(); + if (socketAddress instanceof InetSocketAddress) { + return ((InetSocketAddress) socketAddress).getPort(); + } + } + } + return null; + } +} \ No newline at end of file diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java index b896f5688ba4..1ab6adc16da9 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java @@ -11,6 +11,7 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; +import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesExtractor; public final class SpymemcachedSingletons { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spymemcached-2.12"; @@ -19,6 +20,7 @@ public final class SpymemcachedSingletons { static { SpymemcachedAttributesGetter dbAttributesGetter = new SpymemcachedAttributesGetter(); + SpymemcachedNetworkAttributesGetter netAttributesGetter = new SpymemcachedNetworkAttributesGetter(); INSTRUMENTER = Instrumenter.builder( @@ -26,6 +28,7 @@ public final class SpymemcachedSingletons { INSTRUMENTATION_NAME, DbClientSpanNameExtractor.create(dbAttributesGetter)) .addAttributesExtractor(DbClientAttributesExtractor.create(dbAttributesGetter)) + .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) .addOperationMetrics(DbClientMetrics.get()) .buildInstrumenter(SpanKindExtractor.alwaysClient()); } diff --git a/instrumentation/spymemcached-2.12/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedTest.java b/instrumentation/spymemcached-2.12/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedTest.java index 0408bbdd19af..237852248272 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedTest.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedTest.java @@ -16,6 +16,8 @@ import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static net.spy.memcached.ConnectionFactoryBuilder.Protocol.BINARY; @@ -152,6 +154,8 @@ void getHit() { maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), equalTo(maybeStable(DB_OPERATION), "get"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211)), equalTo(stringKey("spymemcached.result"), "hit")))); } @@ -174,6 +178,8 @@ void getMiss() { maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), equalTo(maybeStable(DB_OPERATION), "get"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211)), equalTo(stringKey("spymemcached.result"), "miss")))); } @@ -209,6 +215,8 @@ void getCancel() { maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), equalTo(maybeStable(DB_OPERATION), "get"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211)), equalTo(booleanKey("spymemcached.command.cancelled"), true)))); } @@ -268,7 +276,9 @@ void getTimeout() throws InterruptedException { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "get")))); + equalTo(maybeStable(DB_OPERATION), "get"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -296,7 +306,9 @@ void bulkGet() { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "getBulk")))); + equalTo(maybeStable(DB_OPERATION), "getBulk"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -320,7 +332,9 @@ void set() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "set")))); + equalTo(maybeStable(DB_OPERATION), "set"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -357,6 +371,8 @@ void setCancel() { maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), equalTo(maybeStable(DB_OPERATION), "set"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211)), equalTo(booleanKey("spymemcached.command.cancelled"), true)))); } @@ -382,7 +398,9 @@ void add() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "add")), + equalTo(maybeStable(DB_OPERATION), "add"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))), span -> span.hasName("get") .hasKind(SpanKind.CLIENT) @@ -392,6 +410,8 @@ void add() throws Exception { maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), equalTo(maybeStable(DB_OPERATION), "get"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211)), equalTo(stringKey("spymemcached.result"), "hit")))); } @@ -418,7 +438,9 @@ void secondAdd() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "add")), + equalTo(maybeStable(DB_OPERATION), "add"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))), span -> span.hasName("add") .hasKind(SpanKind.CLIENT) @@ -427,7 +449,9 @@ void secondAdd() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "add")))); + equalTo(maybeStable(DB_OPERATION), "add"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -452,7 +476,9 @@ void delete() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "delete")), + equalTo(maybeStable(DB_OPERATION), "delete"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))), span -> span.hasName("get") .hasKind(SpanKind.CLIENT) @@ -462,6 +488,8 @@ void delete() throws Exception { maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), equalTo(maybeStable(DB_OPERATION), "get"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211)), equalTo(stringKey("spymemcached.result"), "miss")))); } @@ -486,7 +514,9 @@ void deleteNonExistent() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "delete")))); + equalTo(maybeStable(DB_OPERATION), "delete"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -512,7 +542,9 @@ void replace() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "replace")), + equalTo(maybeStable(DB_OPERATION), "replace"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))), span -> span.hasName("get") .hasKind(SpanKind.CLIENT) @@ -522,6 +554,8 @@ void replace() throws Exception { maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), equalTo(maybeStable(DB_OPERATION), "get"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211)), equalTo(stringKey("spymemcached.result"), "hit")))); } @@ -550,7 +584,9 @@ void replaceNonExistent() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "replace")))); + equalTo(maybeStable(DB_OPERATION), "replace"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -577,7 +613,9 @@ void append() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "gets")), + equalTo(maybeStable(DB_OPERATION), "gets"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))), span -> span.hasName("append") .hasKind(SpanKind.CLIENT) @@ -586,7 +624,9 @@ void append() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "append")), + equalTo(maybeStable(DB_OPERATION), "append"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))), span -> span.hasName("get") .hasKind(SpanKind.CLIENT) @@ -596,6 +636,8 @@ void append() throws Exception { maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), equalTo(maybeStable(DB_OPERATION), "get"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211)), equalTo(stringKey("spymemcached.result"), "hit")))); } @@ -623,7 +665,9 @@ void prepend() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "gets")), + equalTo(maybeStable(DB_OPERATION), "gets"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))), span -> span.hasName("prepend") .hasKind(SpanKind.CLIENT) @@ -632,7 +676,9 @@ void prepend() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "prepend")), + equalTo(maybeStable(DB_OPERATION), "prepend"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))), span -> span.hasName("get") .hasKind(SpanKind.CLIENT) @@ -642,6 +688,8 @@ void prepend() throws Exception { maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), equalTo(maybeStable(DB_OPERATION), "get"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211)), equalTo(stringKey("spymemcached.result"), "hit")))); } @@ -669,7 +717,9 @@ void cas() { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "gets")), + equalTo(maybeStable(DB_OPERATION), "gets"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))), span -> span.hasName("cas") .hasKind(SpanKind.CLIENT) @@ -678,7 +728,9 @@ void cas() { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "cas")))); + equalTo(maybeStable(DB_OPERATION), "cas"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -704,7 +756,9 @@ void casNotFound() { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "cas")))); + equalTo(maybeStable(DB_OPERATION), "cas"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -728,7 +782,9 @@ void touch() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "touch")))); + equalTo(maybeStable(DB_OPERATION), "touch"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -753,7 +809,9 @@ void touchNonExistent() throws Exception { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "touch")))); + equalTo(maybeStable(DB_OPERATION), "touch"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -778,7 +836,9 @@ void getAndTouch() { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "getAndTouch")))); + equalTo(maybeStable(DB_OPERATION), "getAndTouch"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -803,7 +863,9 @@ void getAndTouchNonExistent() { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "getAndTouch")))); + equalTo(maybeStable(DB_OPERATION), "getAndTouch"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -832,7 +894,9 @@ it needs values to be strings (with digits in them) and it returns actual long f equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "decr")), + equalTo(maybeStable(DB_OPERATION), "decr"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))), span -> span.hasName("get") .hasKind(SpanKind.CLIENT) @@ -842,6 +906,8 @@ it needs values to be strings (with digits in them) and it returns actual long f maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), equalTo(maybeStable(DB_OPERATION), "get"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211)), equalTo(stringKey("spymemcached.result"), "hit")))); } @@ -866,7 +932,9 @@ void decrNonExistent() { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "decr")))); + equalTo(maybeStable(DB_OPERATION), "decr"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -894,7 +962,9 @@ void decrException() { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "decr")))); + equalTo(maybeStable(DB_OPERATION), "decr"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -923,7 +993,9 @@ it needs values to be strings (with digits in them) and it returns actual long f equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "incr")), + equalTo(maybeStable(DB_OPERATION), "incr"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))), span -> span.hasName("get") .hasKind(SpanKind.CLIENT) @@ -933,6 +1005,8 @@ it needs values to be strings (with digits in them) and it returns actual long f maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), equalTo(maybeStable(DB_OPERATION), "get"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211)), equalTo(stringKey("spymemcached.result"), "hit")))); } @@ -957,7 +1031,9 @@ void incrNonExistent() { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "incr")))); + equalTo(maybeStable(DB_OPERATION), "incr"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } @Test @@ -985,7 +1061,9 @@ void incrException() { equalTo( maybeStable(DB_SYSTEM), DbIncubatingAttributes.DbSystemIncubatingValues.MEMCACHED), - equalTo(maybeStable(DB_OPERATION), "incr")))); + equalTo(maybeStable(DB_OPERATION), "incr"), + equalTo(SERVER_ADDRESS, memcachedContainer.getHost()), + equalTo(SERVER_PORT, memcachedContainer.getMappedPort(11211))))); } private static String key(String k) { From 62d8ae3e4d5b4c1bc6737be7019b9d4fe8c68c74 Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Fri, 7 Nov 2025 16:07:39 +0800 Subject: [PATCH 2/4] Fix CI failing --- .../spymemcached/SpymemcachedNetworkAttributesGetter.java | 2 +- .../instrumentation/spymemcached/SpymemcachedSingletons.java | 3 ++- .../instrumentation/spymemcached/SpymemcachedTest.java | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java index 09bc7121088e..4287d75600c5 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java @@ -47,4 +47,4 @@ public Integer getServerPort(SpymemcachedRequest request) { } return null; } -} \ No newline at end of file +} diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java index 1ab6adc16da9..4caa9f5df7f9 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java @@ -20,7 +20,8 @@ public final class SpymemcachedSingletons { static { SpymemcachedAttributesGetter dbAttributesGetter = new SpymemcachedAttributesGetter(); - SpymemcachedNetworkAttributesGetter netAttributesGetter = new SpymemcachedNetworkAttributesGetter(); + SpymemcachedNetworkAttributesGetter netAttributesGetter = + new SpymemcachedNetworkAttributesGetter(); INSTRUMENTER = Instrumenter.builder( diff --git a/instrumentation/spymemcached-2.12/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedTest.java b/instrumentation/spymemcached-2.12/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedTest.java index 237852248272..9ac4fc3eb27e 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedTest.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedTest.java @@ -14,10 +14,10 @@ import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_MESSAGE; import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_STACKTRACE; import static io.opentelemetry.semconv.ExceptionAttributes.EXCEPTION_TYPE; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static net.spy.memcached.ConnectionFactoryBuilder.Protocol.BINARY; From 6b042dbacc6137dea3643abcf2500022c2dd65f5 Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Sat, 8 Nov 2025 22:05:12 +0800 Subject: [PATCH 3/4] Add server address and port for Spymemcached --- .../BulkGetCompletionListener.java | 33 +++++++++++- .../spymemcached/GetCompletionListener.java | 21 +++++++- .../MemcachedClientInstrumentation.java | 16 +++--- .../OperationCompletionListener.java | 21 +++++++- .../SetOperationInstrumentation.java | 52 +++++++++++++++++++ .../SpymemcachedInstrumentationModule.java | 4 +- .../SpymemcachedNetworkAttributesGetter.java | 32 +++++------- .../spymemcached/SpymemcachedRequest.java | 10 +++- .../spymemcached/SpymemcachedSingletons.java | 13 +++++ .../spymemcached/SyncCompletionListener.java | 18 ++++++- 10 files changed, 184 insertions(+), 36 deletions(-) create mode 100644 instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SetOperationInstrumentation.java diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/BulkGetCompletionListener.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/BulkGetCompletionListener.java index e6b3239c3c3a..aab5b3ce27f8 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/BulkGetCompletionListener.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/BulkGetCompletionListener.java @@ -12,6 +12,7 @@ import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; import net.spy.memcached.MemcachedConnection; +import net.spy.memcached.MemcachedNode; import net.spy.memcached.internal.BulkGetFuture; public class BulkGetCompletionListener extends CompletionListener> @@ -24,13 +25,43 @@ private BulkGetCompletionListener(Context parentContext, SpymemcachedRequest req @Nullable public static BulkGetCompletionListener create( Context parentContext, MemcachedConnection connection, String methodName) { - SpymemcachedRequest request = SpymemcachedRequest.create(connection, methodName); + MemcachedNode handlingNode = getRepresentativeNodeFromConnection(connection); + SpymemcachedRequest request = SpymemcachedRequest.create(connection, methodName, handlingNode); if (!instrumenter().shouldStart(parentContext, request)) { return null; } return new BulkGetCompletionListener(parentContext, request); } + @Nullable + private static MemcachedNode getRepresentativeNodeFromConnection(MemcachedConnection connection) { + try { + // Strategy: Get the "most representative" node for bulk operations + // We choose the last active node in the list, which often represents + // the most recently added or most stable node in the cluster + java.util.Collection allNodes = + connection.getLocator().getAll(); + + MemcachedNode lastActiveNode = null; + MemcachedNode fallbackNode = null; + + for (net.spy.memcached.MemcachedNode node : allNodes) { + if (fallbackNode == null) { + fallbackNode = node; + } + + if (node.isActive()) { + lastActiveNode = node; + } + } + + // Return the last active node, or fallback to the first node + return lastActiveNode != null ? lastActiveNode : fallbackNode; + } catch (RuntimeException e) { + return null; + } + } + @Override public void onComplete(BulkGetFuture future) { closeAsyncSpan(future); diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/GetCompletionListener.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/GetCompletionListener.java index 012677c76fa1..5f3a0dcbde79 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/GetCompletionListener.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/GetCompletionListener.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.spymemcached; +import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.GET_FUTURE_OPERATION; import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.instrumenter; import io.opentelemetry.api.trace.Span; @@ -12,7 +13,9 @@ import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; import net.spy.memcached.MemcachedConnection; +import net.spy.memcached.MemcachedNode; import net.spy.memcached.internal.GetFuture; +import net.spy.memcached.ops.Operation; public class GetCompletionListener extends CompletionListener> implements net.spy.memcached.internal.GetCompletionListener { @@ -23,14 +26,28 @@ private GetCompletionListener(Context parentContext, SpymemcachedRequest request @Nullable public static GetCompletionListener create( - Context parentContext, MemcachedConnection connection, String methodName) { - SpymemcachedRequest request = SpymemcachedRequest.create(connection, methodName); + Context parentContext, + MemcachedConnection connection, + String methodName, + GetFuture future) { + // Extract handling node from future before creating span + MemcachedNode handlingNode = extractHandlingNodeFromFuture(future); + SpymemcachedRequest request = SpymemcachedRequest.create(connection, methodName, handlingNode); if (!instrumenter().shouldStart(parentContext, request)) { return null; } return new GetCompletionListener(parentContext, request); } + @Nullable + private static MemcachedNode extractHandlingNodeFromFuture(GetFuture future) { + Operation operation = GET_FUTURE_OPERATION.get(future); + if (operation != null) { + return operation.getHandlingNode(); + } + return null; + } + @Override public void onComplete(GetFuture future) { closeAsyncSpan(future); diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/MemcachedClientInstrumentation.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/MemcachedClientInstrumentation.java index 645579db34f2..6a01e17a0863 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/MemcachedClientInstrumentation.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/MemcachedClientInstrumentation.java @@ -76,7 +76,7 @@ public static void methodExit( if (future != null) { OperationCompletionListener listener = OperationCompletionListener.create( - currentContext(), client.getConnection(), methodName); + currentContext(), client.getConnection(), methodName, future); if (listener != null) { future.addListener(listener); } @@ -106,7 +106,8 @@ public static void methodExit( if (future != null) { GetCompletionListener listener = - GetCompletionListener.create(currentContext(), client.getConnection(), methodName); + GetCompletionListener.create( + currentContext(), client.getConnection(), methodName, future); if (listener != null) { future.addListener(listener); } @@ -156,7 +157,7 @@ private AdviceScope(CallDepth callDepth, @Nullable SyncCompletionListener listen this.listener = listener; } - public static AdviceScope start(MemcachedClient client, String methodName) { + public static AdviceScope start(MemcachedClient client, String methodName, String key) { CallDepth callDepth = CallDepth.forClass(MemcachedClient.class); if (callDepth.getAndIncrement() > 0) { return new AdviceScope(callDepth, null); @@ -164,7 +165,8 @@ public static AdviceScope start(MemcachedClient client, String methodName) { return new AdviceScope( callDepth, - SyncCompletionListener.create(Context.current(), client.getConnection(), methodName)); + SyncCompletionListener.create( + Context.current(), client.getConnection(), methodName, key)); } public void end(@Nullable Throwable throwable) { @@ -178,8 +180,10 @@ public void end(@Nullable Throwable throwable) { @Advice.OnMethodEnter(suppress = Throwable.class) public static AdviceScope methodEnter( - @Advice.This MemcachedClient client, @Advice.Origin("#m") String methodName) { - return AdviceScope.start(client, methodName); + @Advice.This MemcachedClient client, + @Advice.Origin("#m") String methodName, + @Advice.Argument(0) String key) { + return AdviceScope.start(client, methodName, key); } @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/OperationCompletionListener.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/OperationCompletionListener.java index 0e0e4fd3a91b..6c6bacbd9e31 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/OperationCompletionListener.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/OperationCompletionListener.java @@ -5,6 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.spymemcached; +import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.OPERATION_FUTURE_OPERATION; import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.instrumenter; import io.opentelemetry.api.trace.Span; @@ -12,7 +13,9 @@ import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; import net.spy.memcached.MemcachedConnection; +import net.spy.memcached.MemcachedNode; import net.spy.memcached.internal.OperationFuture; +import net.spy.memcached.ops.Operation; public class OperationCompletionListener extends CompletionListener> implements net.spy.memcached.internal.OperationCompletionListener { @@ -23,14 +26,28 @@ private OperationCompletionListener(Context parentContext, SpymemcachedRequest r @Nullable public static OperationCompletionListener create( - Context parentContext, MemcachedConnection connection, String methodName) { - SpymemcachedRequest request = SpymemcachedRequest.create(connection, methodName); + Context parentContext, + MemcachedConnection connection, + String methodName, + OperationFuture future) { + // Extract handling node from future before creating span + MemcachedNode handlingNode = extractHandlingNodeFromFuture(future); + SpymemcachedRequest request = SpymemcachedRequest.create(connection, methodName, handlingNode); if (!instrumenter().shouldStart(parentContext, request)) { return null; } return new OperationCompletionListener(parentContext, request); } + @Nullable + private static MemcachedNode extractHandlingNodeFromFuture(OperationFuture future) { + Operation operation = OPERATION_FUTURE_OPERATION.get(future); + if (operation != null) { + return operation.getHandlingNode(); + } + return null; + } + @Override public void onComplete(OperationFuture future) { closeAsyncSpan(future); diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SetOperationInstrumentation.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SetOperationInstrumentation.java new file mode 100644 index 000000000000..96a16f3244ff --- /dev/null +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SetOperationInstrumentation.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.spymemcached; + +import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.GET_FUTURE_OPERATION; +import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.OPERATION_FUTURE_OPERATION; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import net.spy.memcached.internal.GetFuture; +import net.spy.memcached.internal.OperationFuture; +import net.spy.memcached.ops.Operation; + +public class SetOperationInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return namedOneOf( + "net.spy.memcached.internal.OperationFuture", "net.spy.memcached.internal.GetFuture"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(named("setOperation")).and(takesArguments(1)), + this.getClass().getName() + "$SetOperationAdvice"); + } + + @SuppressWarnings("unused") + public static class SetOperationAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.This Object future, @Advice.Argument(0) Operation operation) { + + if (future instanceof OperationFuture) { + OPERATION_FUTURE_OPERATION.set((OperationFuture) future, operation); + } else if (future instanceof GetFuture) { + GET_FUTURE_OPERATION.set((GetFuture) future, operation); + } + } + } +} diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedInstrumentationModule.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedInstrumentationModule.java index 6f150ef5ef6c..1267bb38957e 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedInstrumentationModule.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedInstrumentationModule.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.spymemcached; -import static java.util.Collections.singletonList; +import static java.util.Arrays.asList; import com.google.auto.service.AutoService; import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; @@ -23,7 +23,7 @@ public SpymemcachedInstrumentationModule() { @Override public List typeInstrumentations() { - return singletonList(new MemcachedClientInstrumentation()); + return asList(new MemcachedClientInstrumentation(), new SetOperationInstrumentation()); } @Override diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java index 4287d75600c5..8ffbdeaf2235 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedNetworkAttributesGetter.java @@ -5,28 +5,25 @@ package io.opentelemetry.javaagent.instrumentation.spymemcached; +import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesGetter; import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesGetter; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.util.Collection; import javax.annotation.Nullable; -import net.spy.memcached.MemcachedConnection; import net.spy.memcached.MemcachedNode; final class SpymemcachedNetworkAttributesGetter - implements ServerAttributesGetter { + implements ServerAttributesGetter, + NetworkAttributesGetter { @Nullable @Override public String getServerAddress(SpymemcachedRequest request) { - MemcachedConnection connection = request.getConnection(); - if (connection != null) { - Collection nodes = connection.getLocator().getAll(); - if (!nodes.isEmpty()) { - SocketAddress socketAddress = nodes.iterator().next().getSocketAddress(); - if (socketAddress instanceof InetSocketAddress) { - return ((InetSocketAddress) socketAddress).getHostString(); - } + MemcachedNode handlingNode = request.getHandlingNode(); + if (handlingNode != null) { + SocketAddress socketAddress = handlingNode.getSocketAddress(); + if (socketAddress instanceof InetSocketAddress) { + return ((InetSocketAddress) socketAddress).getHostString(); } } return null; @@ -35,14 +32,11 @@ public String getServerAddress(SpymemcachedRequest request) { @Nullable @Override public Integer getServerPort(SpymemcachedRequest request) { - MemcachedConnection connection = request.getConnection(); - if (connection != null) { - Collection nodes = connection.getLocator().getAll(); - if (!nodes.isEmpty()) { - SocketAddress socketAddress = nodes.iterator().next().getSocketAddress(); - if (socketAddress instanceof InetSocketAddress) { - return ((InetSocketAddress) socketAddress).getPort(); - } + MemcachedNode handlingNode = request.getHandlingNode(); + if (handlingNode != null) { + SocketAddress socketAddress = handlingNode.getSocketAddress(); + if (socketAddress instanceof InetSocketAddress) { + return ((InetSocketAddress) socketAddress).getPort(); } } return null; diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedRequest.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedRequest.java index e8c45837a5ae..56d905eba2d3 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedRequest.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedRequest.java @@ -6,19 +6,25 @@ package io.opentelemetry.javaagent.instrumentation.spymemcached; import com.google.auto.value.AutoValue; +import javax.annotation.Nullable; import net.spy.memcached.MemcachedConnection; +import net.spy.memcached.MemcachedNode; @AutoValue public abstract class SpymemcachedRequest { - public static SpymemcachedRequest create(MemcachedConnection connection, String statement) { - return new AutoValue_SpymemcachedRequest(connection, statement); + public static SpymemcachedRequest create( + MemcachedConnection connection, String statement, @Nullable MemcachedNode handlingNode) { + return new AutoValue_SpymemcachedRequest(connection, statement, handlingNode); } public abstract MemcachedConnection getConnection(); public abstract String getStatement(); + @Nullable + public abstract MemcachedNode getHandlingNode(); + public String dbOperation() { String statement = getStatement(); if (statement.startsWith("async")) { diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java index 4caa9f5df7f9..308a6885909f 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java @@ -11,13 +11,22 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; +import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesExtractor; import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesExtractor; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import net.spy.memcached.internal.GetFuture; +import net.spy.memcached.internal.OperationFuture; +import net.spy.memcached.ops.Operation; public final class SpymemcachedSingletons { private static final String INSTRUMENTATION_NAME = "io.opentelemetry.spymemcached-2.12"; private static final Instrumenter INSTRUMENTER; + public static final VirtualField, Operation> OPERATION_FUTURE_OPERATION; + + public static final VirtualField, Operation> GET_FUTURE_OPERATION; + static { SpymemcachedAttributesGetter dbAttributesGetter = new SpymemcachedAttributesGetter(); SpymemcachedNetworkAttributesGetter netAttributesGetter = @@ -30,8 +39,12 @@ public final class SpymemcachedSingletons { DbClientSpanNameExtractor.create(dbAttributesGetter)) .addAttributesExtractor(DbClientAttributesExtractor.create(dbAttributesGetter)) .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) + .addAttributesExtractor(NetworkAttributesExtractor.create(netAttributesGetter)) .addOperationMetrics(DbClientMetrics.get()) .buildInstrumenter(SpanKindExtractor.alwaysClient()); + + OPERATION_FUTURE_OPERATION = VirtualField.find(OperationFuture.class, Operation.class); + GET_FUTURE_OPERATION = VirtualField.find(GetFuture.class, Operation.class); } public static Instrumenter instrumenter() { diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SyncCompletionListener.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SyncCompletionListener.java index c24ef86e6c93..715249797961 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SyncCompletionListener.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SyncCompletionListener.java @@ -12,6 +12,7 @@ import java.util.logging.Logger; import javax.annotation.Nullable; import net.spy.memcached.MemcachedConnection; +import net.spy.memcached.MemcachedNode; public class SyncCompletionListener extends CompletionListener { @@ -23,14 +24,27 @@ private SyncCompletionListener(Context parentContext, SpymemcachedRequest reques @Nullable public static SyncCompletionListener create( - Context parentContext, MemcachedConnection connection, String methodName) { - SpymemcachedRequest request = SpymemcachedRequest.create(connection, methodName); + Context parentContext, MemcachedConnection connection, String methodName, String key) { + // For sync operations, determine the handling node based on the key + MemcachedNode handlingNode = getHandlingNodeForKey(connection, key); + SpymemcachedRequest request = SpymemcachedRequest.create(connection, methodName, handlingNode); if (!instrumenter().shouldStart(parentContext, request)) { return null; } return new SyncCompletionListener(parentContext, request); } + @Nullable + private static MemcachedNode getHandlingNodeForKey(MemcachedConnection connection, String key) { + try { + // Use the connection's locator to find the primary node for this key + return connection.getLocator().getPrimary(key); + } catch (RuntimeException e) { + // If we can't determine the node, return null + return null; + } + } + @Override protected void processResult(Span span, Void future) { logger.severe("processResult was called on SyncCompletionListener. This should never happen."); From 3464645be598afa35b4a3f27c3bbc7dab3bfb891 Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Sun, 9 Nov 2025 19:34:49 +0800 Subject: [PATCH 4/4] Unify future operation handling --- .../spymemcached/BulkGetCompletionListener.java | 6 +++--- .../spymemcached/GetCompletionListener.java | 5 ++--- .../spymemcached/OperationCompletionListener.java | 5 ++--- .../spymemcached/SetOperationInstrumentation.java | 10 ++++------ .../spymemcached/SpymemcachedSingletons.java | 12 +++--------- .../spymemcached/SyncCompletionListener.java | 1 - 6 files changed, 14 insertions(+), 25 deletions(-) diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/BulkGetCompletionListener.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/BulkGetCompletionListener.java index aab5b3ce27f8..e26b1c1b9a12 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/BulkGetCompletionListener.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/BulkGetCompletionListener.java @@ -9,6 +9,7 @@ import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; +import java.util.Collection; import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; import net.spy.memcached.MemcachedConnection; @@ -39,13 +40,12 @@ private static MemcachedNode getRepresentativeNodeFromConnection(MemcachedConnec // Strategy: Get the "most representative" node for bulk operations // We choose the last active node in the list, which often represents // the most recently added or most stable node in the cluster - java.util.Collection allNodes = - connection.getLocator().getAll(); + Collection allNodes = connection.getLocator().getAll(); MemcachedNode lastActiveNode = null; MemcachedNode fallbackNode = null; - for (net.spy.memcached.MemcachedNode node : allNodes) { + for (MemcachedNode node : allNodes) { if (fallbackNode == null) { fallbackNode = node; } diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/GetCompletionListener.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/GetCompletionListener.java index 5f3a0dcbde79..40112beea579 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/GetCompletionListener.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/GetCompletionListener.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.spymemcached; -import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.GET_FUTURE_OPERATION; +import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.FUTURE_OPERATION; import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.instrumenter; import io.opentelemetry.api.trace.Span; @@ -30,7 +30,6 @@ public static GetCompletionListener create( MemcachedConnection connection, String methodName, GetFuture future) { - // Extract handling node from future before creating span MemcachedNode handlingNode = extractHandlingNodeFromFuture(future); SpymemcachedRequest request = SpymemcachedRequest.create(connection, methodName, handlingNode); if (!instrumenter().shouldStart(parentContext, request)) { @@ -41,7 +40,7 @@ public static GetCompletionListener create( @Nullable private static MemcachedNode extractHandlingNodeFromFuture(GetFuture future) { - Operation operation = GET_FUTURE_OPERATION.get(future); + Operation operation = FUTURE_OPERATION.get(future); if (operation != null) { return operation.getHandlingNode(); } diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/OperationCompletionListener.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/OperationCompletionListener.java index 6c6bacbd9e31..12292ecfa146 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/OperationCompletionListener.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/OperationCompletionListener.java @@ -5,7 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.spymemcached; -import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.OPERATION_FUTURE_OPERATION; +import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.FUTURE_OPERATION; import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.instrumenter; import io.opentelemetry.api.trace.Span; @@ -30,7 +30,6 @@ public static OperationCompletionListener create( MemcachedConnection connection, String methodName, OperationFuture future) { - // Extract handling node from future before creating span MemcachedNode handlingNode = extractHandlingNodeFromFuture(future); SpymemcachedRequest request = SpymemcachedRequest.create(connection, methodName, handlingNode); if (!instrumenter().shouldStart(parentContext, request)) { @@ -41,7 +40,7 @@ public static OperationCompletionListener create( @Nullable private static MemcachedNode extractHandlingNodeFromFuture(OperationFuture future) { - Operation operation = OPERATION_FUTURE_OPERATION.get(future); + Operation operation = FUTURE_OPERATION.get(future); if (operation != null) { return operation.getHandlingNode(); } diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SetOperationInstrumentation.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SetOperationInstrumentation.java index 96a16f3244ff..3d5df01ca5da 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SetOperationInstrumentation.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SetOperationInstrumentation.java @@ -5,8 +5,7 @@ package io.opentelemetry.javaagent.instrumentation.spymemcached; -import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.GET_FUTURE_OPERATION; -import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.OPERATION_FUTURE_OPERATION; +import static io.opentelemetry.javaagent.instrumentation.spymemcached.SpymemcachedSingletons.FUTURE_OPERATION; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; @@ -14,6 +13,7 @@ import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.concurrent.Future; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -42,10 +42,8 @@ public static class SetOperationAdvice { @Advice.OnMethodExit(suppress = Throwable.class) public static void onExit(@Advice.This Object future, @Advice.Argument(0) Operation operation) { - if (future instanceof OperationFuture) { - OPERATION_FUTURE_OPERATION.set((OperationFuture) future, operation); - } else if (future instanceof GetFuture) { - GET_FUTURE_OPERATION.set((GetFuture) future, operation); + if (future instanceof OperationFuture || future instanceof GetFuture) { + FUTURE_OPERATION.set((Future) future, operation); } } } diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java index 308a6885909f..804de04ff006 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SpymemcachedSingletons.java @@ -11,11 +11,9 @@ import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; -import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesExtractor; import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesExtractor; import io.opentelemetry.instrumentation.api.util.VirtualField; -import net.spy.memcached.internal.GetFuture; -import net.spy.memcached.internal.OperationFuture; +import java.util.concurrent.Future; import net.spy.memcached.ops.Operation; public final class SpymemcachedSingletons { @@ -23,9 +21,7 @@ public final class SpymemcachedSingletons { private static final Instrumenter INSTRUMENTER; - public static final VirtualField, Operation> OPERATION_FUTURE_OPERATION; - - public static final VirtualField, Operation> GET_FUTURE_OPERATION; + public static final VirtualField, Operation> FUTURE_OPERATION; static { SpymemcachedAttributesGetter dbAttributesGetter = new SpymemcachedAttributesGetter(); @@ -39,12 +35,10 @@ public final class SpymemcachedSingletons { DbClientSpanNameExtractor.create(dbAttributesGetter)) .addAttributesExtractor(DbClientAttributesExtractor.create(dbAttributesGetter)) .addAttributesExtractor(ServerAttributesExtractor.create(netAttributesGetter)) - .addAttributesExtractor(NetworkAttributesExtractor.create(netAttributesGetter)) .addOperationMetrics(DbClientMetrics.get()) .buildInstrumenter(SpanKindExtractor.alwaysClient()); - OPERATION_FUTURE_OPERATION = VirtualField.find(OperationFuture.class, Operation.class); - GET_FUTURE_OPERATION = VirtualField.find(GetFuture.class, Operation.class); + FUTURE_OPERATION = VirtualField.find(Future.class, Operation.class); } public static Instrumenter instrumenter() { diff --git a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SyncCompletionListener.java b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SyncCompletionListener.java index 715249797961..ed1d372e4869 100644 --- a/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SyncCompletionListener.java +++ b/instrumentation/spymemcached-2.12/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spymemcached/SyncCompletionListener.java @@ -40,7 +40,6 @@ private static MemcachedNode getHandlingNodeForKey(MemcachedConnection connectio // Use the connection's locator to find the primary node for this key return connection.getLocator().getPrimary(key); } catch (RuntimeException e) { - // If we can't determine the node, return null return null; } }