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

Install interceptors for Spring Component/Repository/Service #882

Merged
merged 5 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 36 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,41 +156,42 @@ If the target application runs under JDK 11 and above, the following arguments s

# Supported Components

| Component | Min Version | Max Version | Metrics | Tracing |
|-----------------------------|-------------|-------------|------------------------------------------------|---------|
| JVM | 1.8 | | ✓ | |
| JDK - Thread Pool | 1.8 | | ✓ | |
| JDK - HTTP Client | 1.8 | | [✓](doc/metrics/http-outgoing/README.md) | ✓ |
| Alibaba Druid | 1.0.28 | | ✓ | |
| Apache Druid | 0.16 | 24.0 | | ✓ |
| Apache Kafka(1) | 0.10 | | ✓ | ✓ |
| Apache OZone | 1.3.0 | | | ✓ |
| Eclipse Glassfish | 2.34 | | | ✓ |
| GRPC | 1.57.0 | | ✓ | |
| Google Guice | 4.1.0 | | | ✓ |
| HTTP Client - Apache | 4.5.2 | 5.x | [✓](doc/metrics/http-outgoing/README.md) | ✓ |
| HTTP Client - Jetty | 9.4.6 | | [✓](doc/metrics/http-outgoing/README.md) | ✓ |
| HTTP Client - Netty | 3.10.6 | < 4.0 | [&check;](doc/metrics/http-outgoing/README.md) | &check; |
| HTTP Client - okhttp3 | 3.2 | 4.9 | [&check;](doc/metrics/http-outgoing/README.md) | &check; |
| HTTP Client - reactor-netty | 1.0.11 | | [&check;](doc/metrics/http-outgoing/README.md) | &check; |
| Jersey | 1.19.4 | | | &check; |
| MongoDB | 3.4.2 | | &check; | |
| MySQL | 5.x | 8.x | &check; | |
| Open Feign | 10.8 | | | &check; |
| Quartz | 2.x | | &check; | &check; |
| Redis - Jedis | 2.9 | 5.x | &check; | &check; |
| Redis - Lettuce(2) | 5.1.2 | 6.x | &check; | &check; |
| Redis - Redisson | 3.19.0 | | &check; | &check; |
| Spring Boot | 1.5 | 3.0+ | | &check; |
| Spring Bean | 4.3.12 | | | &check; |
| Spring Rest Template | 4.3.12 | | | &check; |
| Spring Scheduling | 4.3.12 | | | &check; |
| Spring Gateway | 3.0.0 | | [&check;](doc/metrics/http-outgoing/README.md) | &check; |
| HTTP Server - Jetty | 9.4.41 | | &check; | &check; |
| HTTP Server - Netty | 2.0.0 | | | &check; |
| HTTP Server - Tomcat | 8.5.20 | | &check; | &check; |
| HTTP Server - Undertow | 1.4.12 | | &check; | &check; |
| xxl-job | 2.3.0 | | | &check; |
| Component | Min Version | Max Version | Metrics | Tracing |
|--------------------------------------------------------------|-------------|-------------|------------------------------------------------|---------|
| JVM | 1.8 | | &check; | |
| JDK - Thread Pool | 1.8 | | &check; | |
| JDK - HTTP Client | 1.8 | | [&check;](doc/metrics/http-outgoing/README.md) | &check; |
| Alibaba Druid | 1.0.28 | | &check; | |
| Apache Druid | 0.16 | 24.0 | | &check; |
| Apache Kafka(1) | 0.10 | | &check; | &check; |
| Apache OZone | 1.3.0 | | | &check; |
| Eclipse Glassfish | 2.34 | | | &check; |
| GRPC | 1.57.0 | | &check; | |
| Google Guice | 4.1.0 | | | &check; |
| HTTP Client - Apache | 4.5.2 | 5.x | [&check;](doc/metrics/http-outgoing/README.md) | &check; |
| HTTP Client - Jetty | 9.4.6 | | [&check;](doc/metrics/http-outgoing/README.md) | &check; |
| HTTP Client - Netty | 3.10.6 | < 4.0 | [&check;](doc/metrics/http-outgoing/README.md) | &check; |
| HTTP Client - okhttp3 | 3.2 | 4.9 | [&check;](doc/metrics/http-outgoing/README.md) | &check; |
| HTTP Client - reactor-netty | 1.0.11 | | [&check;](doc/metrics/http-outgoing/README.md) | &check; |
| Jersey | 1.19.4 | | | &check; |
| MongoDB | 3.4.2 | | &check; | |
| MySQL | 5.x | 8.x | &check; | |
| Open Feign | 10.8 | | | &check; |
| Quartz | 2.x | | &check; | &check; |
| Redis - Jedis | 2.9 | 5.x | &check; | &check; |
| Redis - Lettuce(2) | 5.1.2 | 6.x | &check; | &check; |
| Redis - Redisson | 3.19.0 | | &check; | &check; |
| Spring Boot | 1.5 | 3.0+ | | &check; |
| [Spring Bean](doc/configuration/agent/plugin/spring-bean.md) | 4.3.12 | | | &check; |
| Spring Open Feign | 10.8 | | | &check; |
| Spring Rest Template | 4.3.12 | | | &check; |
| Spring Scheduling | 4.3.12 | | | &check; |
| Spring Gateway | 3.0.0 | | [&check;](doc/metrics/http-outgoing/README.md) | &check; |
| HTTP Server - Jetty | 9.4.41 | | &check; | &check; |
| HTTP Server - Netty | 2.0.0 | | | &check; |
| HTTP Server - Tomcat | 8.5.20 | | &check; | &check; |
| HTTP Server - Undertow | 1.4.12 | | &check; | &check; |
| xxl-job | 2.3.0 | | | &check; |

## Restrictions
1. For Apache Kafka clients 3.7, the consumer metrics only works when the `group.protocol` is configured as `classic` which is the default configuration of the consumer client.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.bithon.agent.observability.utils.filter.StringEqualMatcher;
import org.bithon.shaded.net.bytebuddy.description.method.MethodDescription;
import org.bithon.shaded.net.bytebuddy.matcher.ElementMatcher;
import org.bithon.shaded.net.bytebuddy.matcher.ElementMatchers;

import java.lang.reflect.Method;
import java.util.ArrayList;
Expand Down Expand Up @@ -55,6 +56,10 @@ public class BeanMethodAopInstaller {
});
}

public static boolean isProcessed(Class<?> targetClass) {
return PROCESSED.contains(targetClass.getName());
}

public static void install(Class<?> targetClass,
String interceptor,
BeanTransformationConfig transformationConfig) {
Expand Down Expand Up @@ -230,6 +235,9 @@ public boolean matches(String name) {
}

static class BeanMethodMatcher extends ElementMatcher.Junction.AbstractBase<MethodDescription> {
private static final ElementMatcher<MethodDescription> IS_SETTER = ElementMatchers.isSetter();
private static final ElementMatcher<MethodDescription> IS_GETTER = ElementMatchers.isGetter();

private final MatcherList excludedMethods;

BeanMethodMatcher(MatcherList excludedMethods) {
Expand All @@ -239,35 +247,15 @@ static class BeanMethodMatcher extends ElementMatcher.Junction.AbstractBase<Meth
@Override
public boolean matches(MethodDescription target) {
boolean matched = target.isPublic()
&& !target.isConstructor()
&& !target.isStatic()
&& !target.isAbstract()
&& !target.isNative();
if (!matched) {
return false;
}

if (isPropertyMethod(target, "set")
|| isPropertyMethod(target, "get")
|| isPropertyMethod(target, "is")
|| isPropertyMethod(target, "can")) {
return false;
}

return !excludedMethods.matches(target.getName());
}

private boolean isPropertyMethod(MethodDescription method, String prefix) {
String name = method.getName();
if (name.startsWith(prefix)) {
int prefixLen = prefix.length();
if (name.length() > prefixLen) {
return Character.isUpperCase(name.charAt(prefixLen));
}
return true;
}
return false;
&& !target.isConstructor()
&& !target.isStatic()
&& !target.isAbstract()
&& !target.isNative()
&& !IS_GETTER.matches(target)
&& !IS_SETTER.matches(target);

// The exclude methods already include methods defined in Object.class and IBithonObject.class
return matched && !excludedMethods.matches(target.getName());
}
}

}
2 changes: 1 addition & 1 deletion agent/agent-plugins/spring-bean/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<artifactId>spring-beans</artifactId>
<version>4.3.12.RELEASE</version>
<scope>provided</scope>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,29 @@
import org.bithon.agent.configuration.ConfigurationManager;
import org.bithon.agent.observability.aop.BeanMethodAopInstaller;
import org.bithon.agent.plugin.spring.bean.interceptor.BeanMethod$Invoke;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;

/**
* @author [email protected]
* @date 2021/7/10 13:05
*/
public class BeanMethodAopInstallerHelper {
public class SpringBeanMethodAopInstaller {

public static void install(Class<?> targetClass) {
if (targetClass.getDeclaredAnnotation(RestController.class) != null) {
public static void installFor(Class<?> beanClass) {
if (BeanMethodAopInstaller.isProcessed(beanClass)) {
return;
}
if (targetClass.getDeclaredAnnotation(Controller.class) != null) {

SpringBeanPluginConfig pluginConfig = ConfigurationManager.getInstance()
.getConfig(SpringBeanPluginConfig.class);

String componentName = TracingComponentNameManager.computeComponentName(beanClass, pluginConfig.isEnableServiceComponentOnly());
if (componentName == null) {
return;
}
TracingComponentNameManager.setComponentName(beanClass, componentName);

BeanMethodAopInstaller.BeanTransformationConfig transformationConfig = ConfigurationManager.getInstance()
.getConfig("agent.plugin.spring.bean",
BeanMethodAopInstaller.BeanTransformationConfig.class,
true);
BeanMethodAopInstaller.install(targetClass,
BeanMethodAopInstaller.install(beanClass,
BeanMethod$Invoke.class.getName(),
transformationConfig);
pluginConfig);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2020 bithon.org
*
* 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 org.bithon.agent.plugin.spring.bean.installer;

import org.bithon.agent.configuration.ConfigurationProperties;
import org.bithon.agent.observability.aop.BeanMethodAopInstaller;

/**
* @author [email protected]
* @date 2024/11/3 11:33
*/
@ConfigurationProperties(path = "agent.plugin.spring.bean")
public class SpringBeanPluginConfig extends BeanMethodAopInstaller.BeanTransformationConfig {
private boolean enableServiceComponentOnly = false;

public boolean isEnableServiceComponentOnly() {
return enableServiceComponentOnly;
}

public void setEnableServiceComponentOnly(boolean enableServiceComponentOnly) {
this.enableServiceComponentOnly = enableServiceComponentOnly;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2020 bithon.org
*
* 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 org.bithon.agent.plugin.spring.bean.installer;

import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* @author [email protected]
* @date 2024/11/3 11:40
*/
public class TracingComponentNameManager {
private static final Map<String, String> COMPONENT_NAMES = new ConcurrentHashMap<>();

public static String getComponentName(Class<?> beanClass) {
return COMPONENT_NAMES.get(beanClass.getName());
}

public static void setComponentName(Class<?> beanClass, String name) {
COMPONENT_NAMES.put(beanClass.getName(), name);
}

public static String computeComponentName(Class<?> beanClass, boolean enableServiceComponentOnly) {
Annotation[] annotations = beanClass.getAnnotations();
for (Annotation annotation : annotations) {
String annotationType = annotation.annotationType().getName();
switch (annotationType) {
case "org.springframework.stereotype.Component":
return "spring-component";

case "org.springframework.stereotype.Repository":
return "spring-repository";

case "org.springframework.stereotype.Service":
return "spring-service";

case "org.springframework.context.annotation.Configuration":
// No need to instrument Configuration class
return null;

case "org.springframework.stereotype.Controller":
// Spring Controller is instrumented in the spring-webmvc plugin by the InvocableHandlerMethod$DoInvoke interceptor
return null;

case "org.springframework.web.bind.annotation.RestController":
// Rest Controller is instrumented in the spring-webmvc plugin by the InvocableHandlerMethod$DoInvoke interceptor
return null;
}
}

return enableServiceComponentOnly ? null : "spring-bean";
}
}
Loading
Loading