Implemented Apache Geode/Gemfire instrumentation
ADD
instrumentation/geode-1.4/geode-1.4.gradle
+// Geode only supports Java 8+
+ext {
+ minJavaVersionForTests = JavaVersion.VERSION_1_8
+}
+
+apply from: "${rootDir}/gradle/instrumentation.gradle"
+
+apply plugin: 'org.unbroken-dome.test-sets'
+
+muzzle {
+ pass {
+ group = "org.apache.geode"
+ module = "geode-core"
+ versions = "[1.4.0,)"
+ }
+}
+
+testSets {
+ latestDepTest {
+ dirName = 'test'
+ }
+}
+
+dependencies {
+ compileOnly group: 'org.apache.geode', name: 'geode-core', version: '1.4.0'
+
+ testCompile group: 'org.apache.geode', name: 'geode-core', version: '1.4.0'
+ latestDepTestCompile group: 'org.apache.geode', name: 'geode-core', version: '+'
+}

ADD
instrumentation/geode-1.4/src/main/java/io/opentelemetry/auto/instrumentation/geode/GeodeDecorator.java
+/*
+ * Copyright 2020, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.opentelemetry.auto.instrumentation.geode;
+
+import io.opentelemetry.OpenTelemetry;
+import io.opentelemetry.auto.decorator.DatabaseClientDecorator;
+import io.opentelemetry.auto.instrumentation.api.SpanTypes;
+import io.opentelemetry.trace.Tracer;
+import org.apache.geode.cache.Region;
+
+public class GeodeDecorator extends DatabaseClientDecorator<Region> {
+ public static GeodeDecorator DECORATE = new GeodeDecorator();
+
+ public static final Tracer TRACER =
+ OpenTelemetry.getTracerFactory().get("io.opentelemetry.auto.geode-1.7");
+
+ @Override
+ protected String dbType() {
+ return "geode";
+ }
+
+ @Override
+ protected String dbUser(final Region region) {
+ return null;
+ }
+
+ @Override
+ protected String dbInstance(final Region region) {
+ return region.getName();
+ }
+
+ @Override
+ protected String service() {
+ return "apache-geode";
+ }
+
+ @Override
+ protected String getSpanType() {
+ return SpanTypes.CACHE;
+ }
+
+ @Override
+ protected String getComponentName() {
+ return "apache-geode-client";
+ }
+}

ADD
instrumentation/geode-1.4/src/main/java/io/opentelemetry/auto/instrumentation/geode/GeodeInstrumentation.java
+/*
+ * Copyright 2020, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.opentelemetry.auto.instrumentation.geode;
+
+import static io.opentelemetry.auto.instrumentation.geode.GeodeDecorator.DECORATE;
+import static io.opentelemetry.auto.instrumentation.geode.GeodeDecorator.TRACER;
+import static io.opentelemetry.auto.tooling.bytebuddy.matcher.AgentElementMatchers.hasInterface;
+import static io.opentelemetry.trace.Span.Kind.CLIENT;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.auto.bootstrap.CallDepthThreadLocalMap;
+import io.opentelemetry.auto.instrumentation.api.SpanWithScope;
+import io.opentelemetry.auto.instrumentation.api.Tags;
+import io.opentelemetry.auto.tooling.Instrumenter;
+import io.opentelemetry.trace.Span;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.geode.cache.Region;
+
+@AutoService(Instrumenter.class)
+public class GeodeInstrumentation extends Instrumenter.Default {
+ public GeodeInstrumentation() {
+ super("geode", "geode-client");
+ }
+
+ @Override
+ public ElementMatcher<? super TypeDescription> typeMatcher() {
+ return hasInterface(named("org.apache.geode.cache.Region"));
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {
+ "io.opentelemetry.auto.decorator.BaseDecorator",
+ "io.opentelemetry.auto.decorator.ClientDecorator",
+ "io.opentelemetry.auto.decorator.DatabaseClientDecorator",
+ packageName + ".GeodeDecorator",
+ };
+ }
+
+ @Override
+ public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
+ final Map<ElementMatcher<? super MethodDescription>, String> map = new HashMap<>(2);
+ map.put(
+ isMethod()
+ .and(
+ named("clear")
+ .or(nameStartsWith("contains"))
+ .or(named("create"))
+ .or(named("destroy"))
+ .or(named("entrySet"))
+ .or(named("get"))
+ .or(named("getAll"))
+ .or(named("invalidate"))
+ .or(nameStartsWith("keySet"))
+ .or(nameStartsWith("put"))
+ .or(nameStartsWith("remove"))
+ .or(named("replace"))),
+ GeodeInstrumentation.class.getName() + "$SimpleAdvice");
+ map.put(
+ isMethod()
+ .and(named("existsValue").or(named("query")).or(named("selectValue")))
+ .and(takesArgument(0, named("java.lang.String"))),
+ GeodeInstrumentation.class.getName() + "$QueryAdvice");
+ return map;
+ }
+
+ public static class SimpleAdvice {
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static SpanWithScope onEnter(
+ @Advice.This final Region thiz, @Advice.Origin final Method method) {
+ if (CallDepthThreadLocalMap.incrementCallDepth(Region.class) > 0) {
+ return null;
+ }
+ final Span span = TRACER.spanBuilder(method.getName()).setSpanKind(CLIENT).startSpan();
+ DECORATE.afterStart(span);
+ span.setAttribute(Tags.DB_INSTANCE, thiz.getName());
+ return new SpanWithScope(span, TRACER.withSpan(span));
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void stopSpan(
+ @Advice.Enter final SpanWithScope spanWithScope, @Advice.Thrown final Throwable throwable) {
+ if (spanWithScope == null) {
+ return;
+ }
+ try {
+ final Span span = spanWithScope.getSpan();
+ DECORATE.onError(span, throwable);
+ DECORATE.beforeFinish(span);
+ span.end();
+ spanWithScope.closeScope();
+ } finally {
+ CallDepthThreadLocalMap.reset(Region.class);
+ }
+ }
+ }
+
+ public static class QueryAdvice {
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static SpanWithScope onEnter(
+ @Advice.This final Region thiz,
+ @Advice.Origin final Method method,
+ @Advice.Argument(0) final String query) {
+ if (CallDepthThreadLocalMap.incrementCallDepth(Region.class) > 0) {
+ return null;
+ }
+ final Span span = TRACER.spanBuilder(method.getName()).setSpanKind(CLIENT).startSpan();
+ DECORATE.afterStart(span);
+ span.setAttribute(Tags.DB_INSTANCE, thiz.getName());
+ span.setAttribute(Tags.DB_STATEMENT, query);
+ return new SpanWithScope(span, TRACER.withSpan(span));
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void stopSpan(
+ @Advice.Enter final SpanWithScope spanWithScope, @Advice.Thrown final Throwable throwable) {
+ if (spanWithScope == null) {
+ return;
+ }
+ try {
+ final Span span = spanWithScope.getSpan();
+ DECORATE.onError(span, throwable);
+ DECORATE.beforeFinish(span);
+ span.end();
+ spanWithScope.closeScope();
+ } finally {
+ CallDepthThreadLocalMap.reset(Region.class);
+ }
+ }
+ }
+}

ADD
instrumentation/geode-1.4/src/test/groovy/PutGetTest.groovy
+/*
+ * Copyright 2020, OpenTelemetry Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import io.opentelemetry.auto.instrumentation.api.MoreTags
+import io.opentelemetry.auto.instrumentation.api.SpanTypes
+import io.opentelemetry.auto.instrumentation.api.Tags
+import io.opentelemetry.auto.test.AgentTestRunner
+import org.apache.geode.cache.client.ClientCacheFactory
+import org.apache.geode.cache.client.ClientRegionShortcut
+import spock.lang.Shared
+import spock.lang.Unroll
+
+import static io.opentelemetry.auto.test.utils.TraceUtils.runUnderTrace
+import static io.opentelemetry.trace.Span.Kind.CLIENT
+import static io.opentelemetry.trace.Span.Kind.INTERNAL
+
+@Unroll
+class PutGetTest extends AgentTestRunner {
+ @Shared
+ def cache = new ClientCacheFactory().create()
+
+ @Shared
+ def regionFactory = cache.createClientRegionFactory(ClientRegionShortcut.LOCAL)
+
+ @Shared
+ def region = regionFactory.create("test-region")
+
+ def "test put and get"() {
+ when:
+ def cacheValue
+ runUnderTrace("someTrace") {
+ region.clear()
+ region.put(key, value)
+ cacheValue = region.get(key)
+ }
+
+ then:
+ cacheValue == value
+ assertGeodeTrace("get", null)
+
+ where:
+ key | value
+ 'Hello' | 'World'
+ 'Humpty' | 'Dumpty'
+ '1' | 'One'
+ 'One' | '1'
+ }
+
+ def "test put and remove"() {
+ when:
+ runUnderTrace("someTrace") {
+ region.clear()
+ region.put(key, value)
+ region.remove(key)
+ }
+
+ then:
+ region.size() == 0
+ assertGeodeTrace("remove", null)
+
+ where:
+ key | value
+ 'Hello' | 'World'
+ 'Humpty' | 'Dumpty'
+ '1' | 'One'
+ 'One' | '1'
+ }
+
+ def "test query"() {
+ when:
+ def cacheValue
+ runUnderTrace("someTrace") {
+ region.clear()
+ region.put(key, value)
+ cacheValue = region.query("SELECT * FROM /test-region")
+ }
+
+ then:
+ cacheValue.asList().size()
+ assertGeodeTrace("query", "SELECT * FROM /test-region")
+
+ where:
+ key | value
+ 'Hello' | 'World'
+ 'Humpty' | 'Dumpty'
+ '1' | 'One'
+ 'One' | '1'
+ }
+
+ def "test existsValue"() {
+ when:
+ def exists
+ runUnderTrace("someTrace") {
+ region.clear()
+ region.put(key, value)
+ exists = region.existsValue("SELECT * FROM /test-region")
+ }
+
+ then:
+ exists
+ assertGeodeTrace("existsValue", "SELECT * FROM /test-region")
+
+ where:
+ key | value
+ 'Hello' | 'World'
+ 'Humpty' | 'Dumpty'
+ '1' | 'One'
+ 'One' | '1'
+ }
+
+ def assertGeodeTrace(String verb, String query) {
+ assertTraces(1) {
+ trace(0, 4) {
+ span(0) {
+ operationName "someTrace"
+ spanKind INTERNAL
+ errored false
+ }
+ span(1) {
+ operationName "clear"
+ spanKind CLIENT
+ errored false
+ tags {
+ "$MoreTags.SPAN_TYPE" SpanTypes.CACHE
+ "$Tags.COMPONENT" "apache-geode-client"
+ "$MoreTags.SERVICE_NAME" "apache-geode"
+ "$Tags.DB_TYPE" "geode"
+ "$Tags.DB_INSTANCE" "test-region"
+ }
+ }
+ span(2) {
+ operationName "put"
+ spanKind CLIENT
+ errored false
+ tags {
+ "$MoreTags.SPAN_TYPE" SpanTypes.CACHE
+ "$Tags.COMPONENT" "apache-geode-client"
+ "$MoreTags.SERVICE_NAME" "apache-geode"
+ "$Tags.DB_TYPE" "geode"
+ "$Tags.DB_INSTANCE" "test-region"
+ }
+ }
+ span(3) {
+ operationName verb
+ spanKind CLIENT
+ errored false
+ tags {
+ "$MoreTags.SPAN_TYPE" SpanTypes.CACHE
+ "$Tags.COMPONENT" "apache-geode-client"
+ "$MoreTags.SERVICE_NAME" "apache-geode"
+ "$Tags.DB_TYPE" "geode"
+ "$Tags.DB_INSTANCE" "test-region"
+ if (query != null) {
+ "$Tags.DB_STATEMENT" query
+ }
+ }
+ }
+ }
+ }
+ return true
+ }
+}

MODIFY
settings.gradle
settings.gradle
@@ -64,6 +64,7 @@ include ':instrumentation:elasticsearch:transport-5.0'
include ':instrumentation:elasticsearch:transport-5.3'
include ':instrumentation:elasticsearch:transport-6.0'
include ':instrumentation:finatra-2.9'
+include ':instrumentation:geode-1.4'
include ':instrumentation:google-http-client-1.19'
include ':instrumentation:grizzly-2.0'
include ':instrumentation:grpc-1.5'

Implemented Apache Geode/Gemfire instrumentation (#215)