资讯详情

资讯详情

async-profiler 非 Java 应用性能剖析实战:LD_PRELOAD 注入与 C API 集成指南

async-profiler 非 Java 应用性能剖析实战LD_PRELOAD 注入与 C API 集成指南【免费下载链接】async-profilerSampling CPU and HEAP profiler for Java featuring AsyncGetCallTrace perf_events项目地址: https://gitcode.com/GitHub_Trending/as/async-profiler导读async-profiler 不仅能为 Java 应用提供 CPU/内存剖析还支持通过LD_PRELOAD注入或 C API 编程式调用的方式对原生Native应用进行 CPU、wall-clock、native 内存分配等剖析。本文基于 docs/ProfilingNonJavaApplications.md 展开结合仓库源码src/asprof.h、src/asprof.cpp、src/zInit.cpp、src/hooks.cpp讲解非 Java 场景下的两种接入方式、可用的剖析模式与输出格式以及不稳定扩展 API线程级采样计数、自定义 JFR 事件的使用方法。读完本文你将能够为任意原生可执行程序接入 async-profiler并在自己的 C/C 代码中直接驱动剖析器启停与数据采集。适用范围与重要边界首先明确 async-profiler 对非 Java 应用的剖析范围受支持的方式只有两种在进程内部编程式控制剖析器C API以及通过LD_PRELOAD预加载注入与 Java 场景不同动态 Attachdynamic attach不适用于非 Java 剖析。Java 下通过-agentpath/jattach附加的方式详见 IntegratingAsyncProfiler.md 中的 Launching as an Agent依赖 JVM 的 attach 机制非 Java 进程不存在该机制因此必须采用上述两种注入途径。这一边界也体现在源码中Hooks::init(bool attach)的attach参数区分了两种初始化路径src/hooks.cpp其中 attach 路径Profiler::instance()-updateSymbols(false)、patchLibraries()专为 JVM 场景设计。通过 LD_PRELOAD 注入原生应用基本用法async-profiler 的共享库libasyncProfiler.so可以通过LD_PRELOAD注入到任意原生应用中LD_PRELOAD/path/to/libasyncProfiler.so ASPROF_COMMANDstart,eventcpu,fileprofile.jfr NativeApp [args]其原理是注入后库的构造函数见 src/zInit.cpp 中的LateInitializer会在进程启动阶段执行。构造函数会先检查进程内是否已加载 JVMcheckJvmLoaded()若未加载 JVM则读取环境变量ASPROF_COMMAND当满足ASPROF_COMMAND已设置、且检测到进程确实通过预加载方式注入了该库OS::checkPreloaded()时便调用Hooks::init(false)初始化剖析器随后解析ASPROF_COMMAND中的命令并启动剖析startProfiler(command)。这一机制意味着ASPROF_COMMAND是 LD_PRELOAD 场景下传递剖析参数的标准入口。可用的剖析模式与输出格式注入后async-profiler 的全部基础功能保持可用剖析模式cpu、wall、nativemem以及其他基于 perf_events 的模式如cycles、instructions等硬件事件均可使用输出格式支持 Flame Graph.html与 JFR.jfr格式。需要注意的是非 Java 进程产出的 JFR 文件不会包含 Java 特有的事件如分配、锁竞争、Java 线程信息等 Java 专属事件。更完整的命令示例见 ProfilingModes.md。其中值得特别关注的是nativemem模式与 LD_PRELOAD 的组合可用于原生内存泄漏定位LD_PRELOAD/path/to/libasyncProfiler.so ASPROF_COMMANDstart,nativemem,total,loop10m,cstackdwarf,fileprofile-%t.jfr NativeApp [args]该命令以nativemem模式记录malloc、realloc、calloc、free调用loop10m表示每 10 分钟滚动生成一个带时间戳%t的 JFR 文件之后可用jfrconv --total --nativemem --leak profile.jfr profile-leak.html生成火焰图以分析泄漏详见 ProfilingModes.md 的 nativemem 章节。LD_PRELOAD 下的底层 Hook 机制从源码看LD_PRELOAD注入时 async-profiler 会以弱符号WEAK覆盖三个关键 libc/POSIX 接口src/hooks.cpppthread_create/pthread_exit拦截线程创建与退出从而为新线程注册剖析所需的线程本地数据dlopen拦截动态库加载以便在运行时加载的库上及时安装剖析 Hook如 malloc/native lock 追踪。在Hooks::init中还会调用Profiler::setupSignalHandlers()安装剖析信号处理器如 perf 事件、itimer 等采样引擎的信号。这意味着注入的剖析器与常规 Java 场景共用同一套信号采样与栈回溯框架。通过 C API 在原生应用内部控制剖析器除了LD_PRELOADasync-profiler 还提供了与 Java API 对等的C APIJava 侧用法参见 IntegratingAsyncProfiler.md 的 Using Java API允许原生程序在自身代码中按需启动、停止剖析。头文件与动态加载C API 的头文件随发布包一起提供位于仓库的 src/asprof.h。该头文件以extern C包裹声明同时兼容 C 与 C所有导出函数都以DLLEXPORT标记默认可见便于dlsym动态解析。完整示例下面是一个使用 C API 的完整示例原文档示例与 src/asprof.h 中的函数签名一一对应#include asprof.h #include dlfcn.h #include stdio.h #include stdlib.h void test_output_callback(const char* buffer, size_t size) { fwrite(buffer, sizeof(char), size, stderr); } int main() { void* lib dlopen(/path/to/libasyncProfiler.so, RTLD_NOW); if (lib NULL) { printf(%s\n, dlerror()); exit(1); } asprof_init_t asprof_init (asprof_init_t)dlsym(lib, asprof_init); asprof_execute_t asprof_execute (asprof_execute_t)dlsym(lib, asprof_execute); asprof_error_str_t asprof_error_str (asprof_error_str_t)dlsym(lib, asprof_error_str); if (asprof_init NULL || asprof_execute NULL || asprof_error_str NULL) { printf(%s\n, dlerror()); dlclose(lib); exit(1); } asprof_init(); printf(Starting profiler\n); char cmd[] start,eventcpu,logleveldebug,fileprofile.jfr; asprof_error_t err asprof_execute(cmd, test_output_callback); if (err ! NULL) { fprintf(stderr, %s\n, asprof_error_str(err)); exit(1); } // ... some meaningful work ... printf(Stopping profiler\n); err asprof_execute(stop, test_output_callback); if (err ! NULL) { fprintf(stderr, %s\n, asprof_error_str(err)); exit(1); } return 0; }C API 核心函数与底层行为对照头文件 src/asprof.h 与实现 src/asprof.cpp各函数语义如下函数签名语义与实现要点asprof_initvoid asprof_init()在任何其他 API 调用前仅需调用一次。实现为Hooks::init(true)即执行剖析器初始化安装信号处理器、patch 库等见 src/asprof.cpp。asprof_executeasprof_error_t asprof_execute(const char* command, asprof_writer_t output_callback)执行一条 async-profiler 命令如start,eventcpu,...、stopoutput_callback为可选的输出接收回调。返回NULL表示成功非NULL为错误码本质是const char*错误消息。asprof_error_strconst char* asprof_error_str(asprof_error_t err)将错误码转换为可读的错误消息字符串实现即直接返回该字符串。asprof_writer_tvoid (*)(const char* buf, size_t size)输出回调类型剖析器的文本输出会按缓冲区片段回调给该函数。在asprof_execute的实现中src/asprof.cpp命令会先经Arguments::parse(command)解析再根据输出目标选择不同的写入路径未指定输出文件时使用CallbackWriter将输出逐段回传给output_callback指定file时写入本地文件打开失败返回Could not open output file输出目标为 URLhttp/https时先写入内存缓冲区再由HttpClient::send推送到远程端点。这解释了示例中logleveldebug与fileprofile.jfr的配合剖析器日志经output_callback输出JFR 数据写入文件。不稳定扩展 APIUnstable APIs以下 API 在 src/asprof.h 中被明确标注为UNSTABLE可能在下一版本中变更或移除使用时需做好兼容性评估。高级采样检测asprof_get_thread_local_dataasprof_get_thread_local_data返回指向 async-profiler线程本地数据结构asprof_thread_local_data的指针该结构保证与线程同生命周期typedef struct { // 该线程最近一次剖析采样发生时刻的时间戳使用 async-profiler 内部时钟 // 与 JFR 事件时间戳同源。单调递增每次新采样记录时更新。 // 首次采样前为 0该字段可能被延迟初始化仅在首次调用 // asprof_get_thread_local_data 时创建。 volatile uint64_t sample_counter; } asprof_thread_local_data;结构体中的sample_counter字段会在每次采样事件发生时递增为原生代码提供了一种检测采样事件是否发生的简便手段——当程序被采样到时可在该线程的采样回调逻辑中记录此刻程序正在做什么的元数据从而把采样点与业务状态关联起来。从实现看src/threadLocalData.h、src/threadLocalData.cpp线程本地数据基于 POSIX TLSpthread_getspecific/pthread_setspecific实现数据按线程惰性分配首次调用时calloc分配并绑定到 pthread key分配失败时返回NULL不会中断宿主程序在剖析器尚未初始化pthread key 未创建时同样返回NULL该函数不是 async-signal-safe的但可与其他剖析器操作包括初始化并发调用。自定义 JFR 事件asprof_register_jfr_event与asprof_emit_jfr_event同一头文件中还提供了两个相关的扩展函数用于向 JFR 输出写入自定义事件asprof_register_jfr_event(const char* name)注册一个用户自定义事件名返回asprof_jfr_event_key标识失败返回-1。实现上通过线程安全的Dictionary::lookup将名称映射为整数键src/userEvents.cppasprof_emit_jfr_event(key, data, len)以指定键发射一个自定义 JFR 事件。数据为任意二进制长度上限ASPROF_MAX_JFR_EVENT_LENGTH2048 字节超限会返回错误见 src/asprof.cpp 中的长度校验与 src/asprof.h 的宏定义。事件发射后会以profiler.UserEvent类型写入 JFR包含startTime发射时刻的时钟 tick取自TSC::ticks()、eventThread、type常量池索引的事件类型名与data等字段。这使非 Java 应用也能在 JFR 时间线上标注业务关键点与采样数据对齐分析。如何选择LD_PRELOAD 与 C API两种方式服务于不同的集成场景维度LD_PRELOADC API集成成本无需改代码仅设置环境变量需要在源码中引入asprof.h并编写调用逻辑控制时机进程启动即按ASPROF_COMMAND自动开始可在程序任意时刻按需启停适用对象已编译、不便改动的原生可执行程序拥有源码、希望将剖析能力内置进产品的原生应用输出接收固定输出到文件可指定文件、URL 或自定义回调在实际工程中两者也常结合使用LD_PRELOAD适合快速复现与诊断线上问题尤其配合nativemem定位原生内存泄漏而 C API 适合将剖析能力作为可编程组件嵌入自身应用例如按请求/阶段动态开关剖析、用asprof_emit_jfr_event打点。小结非 Java 剖析仅支持LD_PRELOAD注入与进程内 C API 两种途径不支持 dynamic attachLD_PRELOAD场景通过ASPROF_COMMAND环境变量传递启动命令底层由 src/zInit.cpp 的构造逻辑在进程启动时完成初始化并通过 src/hooks.cpp 的pthread_create/dlopenHook 支撑多线程与动态加载场景C API 的头文件位于 src/asprof.h核心流程为dlopen加载 →dlsym解析 →asprof_init→asprof_execute错误通过asprof_error_str获取可读信息cpu、wall、nativemem等模式与 Flame Graph、JFR 输出在非 Java 场景下同样可用仅 JFR 中缺少 Java 专属事件需要精确感知采样时机时可借助不稳定的asprof_get_thread_local_data读取线程级sample_counter或用asprof_emit_jfr_event写入自定义 JFR 事件。进一步阅读ProfilingModes.md各模式与完整命令示例、IntegratingAsyncProfiler.mdJava API 与 agent 接入方式、OutputFormats.md输出格式细节。【免费下载链接】async-profilerSampling CPU and HEAP profiler for Java featuring AsyncGetCallTrace perf_events项目地址: https://gitcode.com/GitHub_Trending/as/async-profiler创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →