
【进阶篇】五、Java Agent实现系统数据采集
APM系统用到了Java Agent的静态代理模式 + 字节码增强,从而采集到方法的耗时、数据库的查询时长、SQL信息等Arthas用到了Java Agent的动态代理模式,用到了JMX获取到一些信息,以及字节码增强打印方法耗时、参数等。
·
文章目录
根据Java Agent静态和动态模式的特点,动态模式更适配Arthas这类随时连接Java程序的工具,动态模式则更适配Java程序启动之后就需要持续地进行信息的采集的场景,如Application performance monitor (APM) 应用程序性能监控系统(Zipkin、Sky walking)采集数据
1、APM系统数据采集
实现需求:
- 无侵入式采集springboot应用中,controller层方法的执行耗时
- 将采集到的数据写入到文件中(以后写入到ES、Sky Walking等)
使用Byte Buddy字节码增强框架,搭配Java Agent静态代理:
public class AgentMain {
public static void premain(String agentArgs, Instrumentation inst) {
//使用byte buddy增强类
new AgentBuilder.Default()
//禁止byte buddy处理时修改类名
.disableClassFormatChanges()
//处理时使用retransform增强
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
//打印出增强过程中的错误日志(官网固定写法)
.with(new AgentBuilder.Listener.WithTransformationsOnly(AgentBuilder.Listener.StreamWriting
.toSystemOut()))
//匹配哪些类,这里要处理Controller类的方法,所以匹配有@RestController或者@Controller注解的
.type(ElementMatchers.isAnnotatedWith(
ElementMatchers.named("org.springframework.web.bind.annotation.RestController")
.or(ElementMatchers.named("org.springframework.web.bind.annotation.Controller"))))
//增强,使用MyAdvice通知,ElementMatchers.any()即对所有方法都进行增强
.transform((builder, classLoader, module, protectionDomain) ->
builder.visit(Advice.withCustomMapping() //代表需要传递参数到Byte Buddy
.bind(AgentParam.class, agentArgs) //将静态代理传入的参数通过注解绑定
.to(TimeAdvice.class).on(ElementMatchers.any())))
.installOn(inst);
}
}
自定义通知类,描述如何增强:
import net.bytebuddy.asm.Advice;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class TimeAdvice {
//方法进入时,返回开始时间
@Advice.OnMethodEnter
static long enter(@Advice.AllArguments Object[] ary) {
//返回毫秒值
return System.nanoTime();
}
/**
* 方法退出时候,统计方法执行耗时
* Advice.Origin传入#t代表获取增强的类的类名,#m即方法名
*/
@Advice.OnMethodExit
static void exit(@Advice.Enter long value,
@Advice.Origin("#t") String className,
@Advice.Origin("#m") String methodName,
//让用户能指定信息写入文件的文件名,默认agent.log
@AgentParam("agent.log")String fileName) {
String str = methodName + "@" + className + "耗时为: " + (System.nanoTime() - value) + "纳秒\n";
try {
FileUtils.writeStringToFile(new File(fileName), str, StandardCharsets.UTF_8, true);
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面写入文件用apache的common-ios,相关依赖:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
此外,为了获取到用户运行Java Agent的Jar包时传入的参数,定义了@AgentParam注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface AgentParam {
String value();
}
最后,Java Agent的说明文件:
Manifest-Version: 1.0
Premain-Class: com.llg.AgentMain
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
maven-assembly-plugin插件打出Java Agent的Jar包
2、效果
运行普通的Java应用,指定Java Agent的Jar包:
调用接口:
增强成功,数据采集:
3、注意点
Java Agent中,需要传递参数到Byte Buddy,可绑定Key Value,Key是一个自定义注解,Value是参数的值
自定义注解:
通过注解注入参数:
4、总结
- APM系统用到了Java Agent的静态代理模式 + 字节码增强,从而采集到方法的耗时、数据库的查询时长、SQL信息等
- Arthas用到了Java Agent的动态代理模式,用到了JMX获取到一些信息,以及字节码增强打印方法耗时、参数等
更多推荐
所有评论(0)