perf-map-agent 使用步骤

使用 asyncProfiler 能捕获 Java 的栈, 使用 perf 能捕获操作系统栈, 由于 JVM 使用自己单独的虚拟机, 所以不能同时访问 2 部分栈. Netflix 的教程(https://netflixtechblog.com/java-in-flames-e763b3d32166), 能捕获 2 个在一起的栈. 需要做 2 件事情:

  1. 对 Java 进程添加 -XX:+PreserveFramePointer flag
  2. 生成当前正在运行的 Java 进程的 符号表文件. asyncProfiler 和 bpftrace 都需要这个文件在 /tmp/ 目录下, 并且以 perf-<pid>.map 的形式存在.

ls -lah /tmp/perf-2868448.map

假如不使用 Java 运行时符号表

你将看到如下的栈信息, 毫无用处:

  0x7f72beee3337
  0x7f72be72e399
  0x7f72be72ed3e
  0x7f72be1a7d98
  0x7f72be1aacb1

下面就是关于如何产生符号表文件的教程.

  1. clone 或者下载最新版本 https://github.com/jvm-profiling-tools/perf-map-agent

    curl -vvv 'https://github.com/jvm-profiling-tools/perf-map-agent/archive/refs/heads/master.zip' --output perf-map-agent.zip
    
    # or
    git clone https://github.com/jvm-profiling-tools/perf-map-agent.git
    
  2. 设置 JAVA_HOME 环境变量

    export JAVA_HOME=/home/supra/work/tools/myjdk/jdk11/jdk
  3. 编译

    # 解压并进入该文件夹
    unzip perf-map-agent.git && cd perf-map-agent
    cmake .
    make
  4. 产生 perf-.map

    ./bin/create-java-perf-map.sh $(pgrep java)
    # 到 /tmp 目录查看对应的 perf-<pid>.map 是不是存在了
    ls /tmp/ | grep .map
    -rw-rw-r--  1 root  root   29K Nov 13 06:29 perf-2868448.map

使用 bpftrace 的例子

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_pread /comm=="java"/{ @[ustack(20)] = count(); }'

# or
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_pread /pid==2868448/{ @[ustack(20)] = count(); }'

可能遇到问题:

  1. 如果遇到下面:

    -- The C compiler identification is GNU 7.5.0
    -- The CXX compiler identification is unknown
    -- Check for working C compiler: /usr/bin/cc
    -- Check for working C compiler: /usr/bin/cc -- broken
    CMake Error at /usr/share/cmake-3.10/Modules/CMakeTestCCompiler.cmake:52 (message):
     The C compiler
    
     "/usr/bin/cc"
    
     is not able to compile a simple test program.
    
     It fails with the following output:

    则安装编译工具

    sudo apt-get cmake
    sudo apt-get install build-essential
  2. 如果遇到下面:

    Sorry, user xxx is not allowed to execute '/yyyy/bin/java -cp /home/xxx/perf-map-agent-master/bin/../out/attach-main.jar:/yyyy/lib/tools.jar net.virtualvoid.perf.AttachOnce 23545 ' as xxx on hostzzzz.txh.com.

    那么我们可以到 perf-map-agent-master/out/ 目录下去执行这个命令就好了, 不是执行 create-java-perf-map.sh.

  3. 关于要如何使用 root 的, 查看 Netflix 的那个文档
  4. 创建 perf-.map 要使用 java 应用启动相同的用户名, 因为 java 应用的 agent 要使用同样的用户才能访问对应的 java 程序.
  5. 如果你发现即便使用了运行时 Java 进程的符号表, 可是还是很多翻译的栈, 如下:

    __GI___recv+110
     Java_java_net_SocketInputStream_socketRead0+434
     Interpreter+28336
     Interpreter+4352
     Interpreter+4352
     Interpreter+4352
     Interpreter+4352
     Interpreter+5875
     Interpreter+4352
     Interpreter+4352
     Interpreter+3728
     Interpreter+3728
     Interpreter+5184
     Interpreter+5184
     Interpreter+4352
     call_stub+138
     JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, Thread*)+883

    因为只有编译的代码才有符号表, 翻译的是没有的, 所以要让它尽快尽早JIT编译, 因此, 要通过下面2个参数:
    -XX:-TieredCompilation -XX:CompileThreshold=1
    2个参数分别是: 禁止分层编译, 然后只要运行1次就JIT编译. 所以完整的命令是:
    java -XX:+PreserveFramePointer -XX:-TieredCompilation -XX:CompileThreshold=1 myJavaApp.
    同时要确保你的编译cache 足够大.
    这样, 你就得到了如下的代码栈:

     __GI___recv+110
     Java_java_net_SocketInputStream_socketRead0+434
     Ljava/net/SocketInputStream;::socketRead0+244
     Ljava/net/SocketInputStream;::read+224
     Ljava/io/BufferedInputStream;::fill+784
     Ljava/io/BufferedInputStream;::read1+176
     Ljava/io/BufferedInputStream;::read+252
     Lsun/net/www/http/HttpClient;::parseHTTPHeader+444
     Lsun/net/www/http/HttpClient;::parseHTTP+1004
     Lsun/net/www/protocol/http/HttpURLConnection;::getInputStream0+1420
     Lsun/net/www/protocol/http/HttpURLConnection;::getInputStream+196
     Ljava/net/HttpURLConnection;::getResponseCode+96

标签: none

添加新评论