async-profiler源码(二)如何在业务中落地

139 阅读5分钟

前言

本章分析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

4-2)SkyWalking8源码(四)性能剖析

一、应用直接接入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分析结果。

image.png

选择Memory Allocations可以查看alloc分析结果。

image.png

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包里了。

image.png

最终调用jdk.internal.loader.NativeLibraries#load加载动态库。

image.png

在openjdk中的实现如下:

1)根据入参name找到libasyncProfiler.so,返回fd;

2)从库中找到JNI_OnLoad方法;

3)调用库中的JNI_OnLoad方法;

image.png

asyncProfiler.so中JNI_OnLoad实现如下:

1)执行初始化,上一章分析过,包括找AsyncGetCallTrace方法,挂载jvmti钩子,触发jmethodId分配等等;

2)注册native方法实现;

image.png

javaApi.cpp:registerNatives的实现比较搞笑,他这边为了兼容AsyncProfiler被shaded或改名,通过调用栈(GetStackTrace)反向定位AsyncProfiler类,最后循环注册n个内置的native方法。

image.png

native方法包含如下几个。

image.png

2-2、execute

one.profiler.AsyncProfiler#execute:调用execute0方法。

image.png

javaApi.cpp:Java_one_profiler_AsyncProfiler_execute0,execute0的native实现。

解析command入参到Arguments,开始profiling,后面逻辑上一章都分析过了。

对于cpu分析,主要是安装信号处理方法,不同的cpu引擎定时触发SIGPROF信号。

image.png

二、SkyWalking接入async-profiler

1、案例

可多选服务实例和分析事件,String任务扩展就是传入libasyncProfiler的入参,可以传入cstack=no表示不收集native方法栈。

注:目前SkyWalking Agent对于String任务扩展仅用于libasyncProfiler的start指令,不会传入stop指令,所以控制stop指令的扩展参数无法生效(如传入simple,sig本意是调整方法输出格式,取classSimpleName+方法参数列表,在这里并不会生效)。

image.png

等待几分钟后可以查看分析结果。async-profiler本身只能分析单个进程的情况,SkyWalking的优势在于可以多选实例聚合数据返回

image.png

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,从内存查询后,记录通知成功,返回任务。

image.png

区别主要在于后面执行任务和完成阶段。

2-2、执行任务

AsyncProfilerTaskExecutionService#processAsyncProfilerTask:grpc线程提交任务到AsyncProfiler线程(单线程),执行AsyncProfilerTask#start,延迟持续时间duration后,执行AsyncProfilerTask#stop。

image.png

AsyncProfilerTask#start:开始任务

1)创建临时文件/tmp/{taskId}.jfr,用于记录profiling数据;

2)组装asyncprofiler命令参数,即start,event=事件类型,{用户传入扩展参数},file=临时文件;

3)调用AsyncProfiler#execute0开始profiling。

image.png

AsyncProfilerTaskExecutionService#stopWhenSuccess:经过duration时长,自动触发stop。

image.png

AsyncProfilerTask#stop:stop仅传入file参数,返回jfr文件。

image.png

2-3、执行完成

image.png

AsyncProfilerDataSender#sendData:Agent侧打开Grpc双向流,先发送一个AsyncProfilerMetaData给服务端,包括是否成功,还有jfr文件大小。

image.png

AsyncProfilerServiceHandler#collect:因为涉及文件传输,服务端根据SW_RECEIVER_ASYNC_PROFILER_MEMORY_PARSER_ENABLED=true/false有两种处理方式,默认true,使用堆内存接收jfr文件,可选false,使用临时文件接收jfr文件。

image.png

AsyncProfilerByteBufCollectionObserver#onNext:

如果profiling失败,直接记录执行失败日志,结束流程;

如果成功,收到metadata校验文件大小,服务端约束不能超过30MB,否则返回TERMINATED_BY_OVERSIZE,记录失败日志,结束流程;正常情况下,分配内存buffer,回复agent可以继续上传jfr文件,

image.png

AsyncProfilerDataSender#sendData:agent收到服务端响应,读取jfr文件,以1MB一个包的速度发送给OAP,最终调用onCompleted告知OAP文件传输完毕。

image.png

AsyncProfilerByteBufCollectionObserver#onNext:oap将数据缓存到内存buffer。

注:如果SW_RECEIVER_ASYNC_PROFILER_MEMORY_PARSER_ENABLED=false,这里就是写本地文件。

image.png

AsyncProfilerByteBufCollectionObserver#onCompleted:文件传输完毕,oap持久化数据。

image.png

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。

image.png

FrameTree是调用栈树,每个节点记录:frame-当前方法名,total-当前方法执行次数。

image.png

AsyncProfilerQueryService#queryJFRData:用户点击分析结果,需要做一次聚合,因为会多选instanceId,将不同进程的分析结果合并。

image.png

总结

本文分析了如何让async profiler在业务中落地。

方式一:应用直接引入async-profiler。

通过调用AsyncProfiler类中的execute0方法能直接在当前jvm进程内运行profiling。

其原理是async-profiler.jar中打入了libasyncProfiler.so动态库。

方式二:SkyWalking接入async-profiler。

SkyWalking接入async-profiling的大致流程和trace profiling类似。

OAP写入任务,agent主动拉取任务执行。

image.png

agent执行(start/stop)任务调用AsyncProfiler类中的execute0方法。

agent执行完成后上传jfr文件到oap,oap将数据写入jfr_profiling_data。

image.png