前言
本章分析async profiler如何在业务中落地:
1)应用直接接入async-profiler;
2)SkyWalking如何接入async-profiler;
注:
1)async-profiler4.0.0;
2)SkyWalking OAP服务端10.2,JavaAgent客户端9.4.0;
3)openjdk17;
4)相关文章
4-1)async-profiler源码(一)cpu profiling
一、应用直接接入async-profiler
1、案例
Step1,引入async-profiler依赖。
<dependency>
<groupId>tools.profiler</groupId>
<artifactId>async-profiler</artifactId>
<version>4.0</version>
</dependency>
Step2,调用AsyncProfiler发起profiling。
@RestController
public class AsyncProfilerController {
private static final Logger log = LoggerFactory.getLogger(AsyncProfilerController.class);
private static final AsyncProfiler asprof;
static {
// 通过-D指定使用外置的libasyncProfiler.so实现
// String lib = System.getProperty("async-profiler.lib");
// asprof = AsyncProfiler.getInstance(lib);
// 使用async-profiler.jar中内嵌的libasyncProfiler.so
asprof = AsyncProfiler.getInstance();
}
@GetMapping("/async-profiler")
public String execute(@RequestParam String cmd) throws IOException {
return asprof.execute(cmd);
}
}
Step3,使用。
开始profiling,这里分析cpu和内存分配事件,结果用jfr格式存储。
curl "localhost:8080/async-profiler?cmd=start,file=/tmp/asprof.jfr,event=alloc,cpu"
等待一段时间,结束profiling。
curl "localhost:8080/async-profiler?cmd=stop,file=/tmp/asprof.jfr"
Step4,效果如下:
这里用IDEA直接打开(也可以用JDK Mission Control)。
选择CPU Samples可以查看cpu分析结果。
选择Memory Allocations可以查看alloc分析结果。
2、原理
2-1、创建AsyncProfiler
AsyncProfiler单例对象创建,需要加载asyncProfiler动态库。
动态库可以传入(libPath),也可以直接使用jar包里的(extractEmbeddedLib)。
public class AsyncProfiler implements AsyncProfilerMXBean {
private static AsyncProfiler instance;
private AsyncProfiler() {
}
public static AsyncProfiler getInstance() {
return getInstance((String)null);
}
public static synchronized AsyncProfiler getInstance(String libPath) {
if (instance != null) {
return instance;
} else {
AsyncProfiler profiler = new AsyncProfiler();
if (libPath != null) {
// 可以指定libasyncProfiler.so的位置
System.load(libPath);
} else {
try {
profiler.getVersion();
} catch (UnsatisfiedLinkError var8) {
File file = extractEmbeddedLib();
if (file != null) {
// 默认取jar包里的libasyncProfiler.so
try {
System.load(file.getPath());
} finally {
file.delete();
}
} else {
// 否则从java.library.path系统变量获取
System.loadLibrary("asyncProfiler");
}
}
}
instance = profiler;
return profiler;
}
}
}
async-profiler将不同平台的动态库都打到jar包里了。
最终调用jdk.internal.loader.NativeLibraries#load加载动态库。
在openjdk中的实现如下:
1)根据入参name找到libasyncProfiler.so,返回fd;
2)从库中找到JNI_OnLoad方法;
3)调用库中的JNI_OnLoad方法;
asyncProfiler.so中JNI_OnLoad实现如下:
1)执行初始化,上一章分析过,包括找AsyncGetCallTrace方法,挂载jvmti钩子,触发jmethodId分配等等;
2)注册native方法实现;
javaApi.cpp:registerNatives的实现比较搞笑,他这边为了兼容AsyncProfiler被shaded或改名,通过调用栈(GetStackTrace)反向定位AsyncProfiler类,最后循环注册n个内置的native方法。
native方法包含如下几个。
2-2、execute
one.profiler.AsyncProfiler#execute:调用execute0方法。
javaApi.cpp:Java_one_profiler_AsyncProfiler_execute0,execute0的native实现。
解析command入参到Arguments,开始profiling,后面逻辑上一章都分析过了。
对于cpu分析,主要是安装信号处理方法,不同的cpu引擎定时触发SIGPROF信号。
二、SkyWalking接入async-profiler
1、案例
可多选服务实例和分析事件,String任务扩展就是传入libasyncProfiler的入参,可以传入cstack=no表示不收集native方法栈。
注:目前SkyWalking Agent对于String任务扩展仅用于libasyncProfiler的start指令,不会传入stop指令,所以控制stop指令的扩展参数无法生效(如传入simple,sig本意是调整方法输出格式,取classSimpleName+方法参数列表,在这里并不会生效)。
等待几分钟后可以查看分析结果。async-profiler本身只能分析单个进程的情况,SkyWalking的优势在于可以多选实例聚合数据返回。
2、原理
SkyWalking接入AsyncProfiler的整体流程和TraceProfiling差不多。
2-1、创建和获取任务
创建任务:
OAP,AsyncProfilerMutationService#createTask,创建任务,持久化到async_profiler_task。
OAP,CacheUpdateTimer#updateProfileTask,每10s从ES加载5分钟内的任务到内存缓存。
获取任务:
Agent,AsyncProfilerTaskChannelService#run,每20s调用OAP获取自己的任务。
OAP,AsyncProfilerServiceHandler#getAsyncProfilerTaskCommands,从内存查询后,记录通知成功,返回任务。
区别主要在于后面执行任务和完成阶段。
2-2、执行任务
AsyncProfilerTaskExecutionService#processAsyncProfilerTask:grpc线程提交任务到AsyncProfiler线程(单线程),执行AsyncProfilerTask#start,延迟持续时间duration后,执行AsyncProfilerTask#stop。
AsyncProfilerTask#start:开始任务
1)创建临时文件/tmp/{taskId}.jfr,用于记录profiling数据;
2)组装asyncprofiler命令参数,即start,event=事件类型,{用户传入扩展参数},file=临时文件;
3)调用AsyncProfiler#execute0开始profiling。
AsyncProfilerTaskExecutionService#stopWhenSuccess:经过duration时长,自动触发stop。
AsyncProfilerTask#stop:stop仅传入file参数,返回jfr文件。
2-3、执行完成
AsyncProfilerDataSender#sendData:Agent侧打开Grpc双向流,先发送一个AsyncProfilerMetaData给服务端,包括是否成功,还有jfr文件大小。
AsyncProfilerServiceHandler#collect:因为涉及文件传输,服务端根据SW_RECEIVER_ASYNC_PROFILER_MEMORY_PARSER_ENABLED=true/false有两种处理方式,默认true,使用堆内存接收jfr文件,可选false,使用临时文件接收jfr文件。
AsyncProfilerByteBufCollectionObserver#onNext:
如果profiling失败,直接记录执行失败日志,结束流程;
如果成功,收到metadata校验文件大小,服务端约束不能超过30MB,否则返回TERMINATED_BY_OVERSIZE,记录失败日志,结束流程;正常情况下,分配内存buffer,回复agent可以继续上传jfr文件,
AsyncProfilerDataSender#sendData:agent收到服务端响应,读取jfr文件,以1MB一个包的速度发送给OAP,最终调用onCompleted告知OAP文件传输完毕。
AsyncProfilerByteBufCollectionObserver#onNext:oap将数据缓存到内存buffer。
注:如果SW_RECEIVER_ASYNC_PROFILER_MEMORY_PARSER_ENABLED=false,这里就是写本地文件。
AsyncProfilerByteBufCollectionObserver#onCompleted:文件传输完毕,oap持久化数据。
AsyncProfilerByteBufCollectionObserver#parseJFRAndStorage:
oap引入async-profiler提供的async-profiler-converter工具,解析jfr文件。
每类JFR事件(JFREventType)对应一条jfr_profiling_data记录(FrameTree)。
如cpu+alloc最后生成三条记录:EXECUTION_SAMPLE+OBJECT_ALLOCATION_IN_NEW_TLAB+OBJECT_ALLOCATION_OUTSIDE_TLAB。
FrameTree是调用栈树,每个节点记录:frame-当前方法名,total-当前方法执行次数。
AsyncProfilerQueryService#queryJFRData:用户点击分析结果,需要做一次聚合,因为会多选instanceId,将不同进程的分析结果合并。
总结
本文分析了如何让async profiler在业务中落地。
方式一:应用直接引入async-profiler。
通过调用AsyncProfiler类中的execute0方法能直接在当前jvm进程内运行profiling。
其原理是async-profiler.jar中打入了libasyncProfiler.so动态库。
方式二:SkyWalking接入async-profiler。
SkyWalking接入async-profiling的大致流程和trace profiling类似。
OAP写入任务,agent主动拉取任务执行。
agent执行(start/stop)任务调用AsyncProfiler类中的execute0方法。
agent执行完成后上传jfr文件到oap,oap将数据写入jfr_profiling_data。