欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用dotnet-dump 查找 .net core 3.0 占用CPU 100%的原因

程序员文章站 2022-12-09 23:38:39
公司的产品一直紧跟 .net core 3.0 preview 不断升级, 部署到 Linux 服务器后, 偶尔会出现某个进程CPU占用100%. 由于服务部署在云上, 不能使用远程调试; 在局域网内的Linux 服务器 或 Windows开发机上又不能重现这个问题, 联想到Java的jstack, ......

  公司的产品一直紧跟 .net core 3.0 preview 不断升级, 部署到 linux 服务器后, 偶尔会出现某个进程cpu占用100%.
  由于服务部署在云上, 不能使用远程调试; 在局域网内的linux 服务器 或 windows开发机上又不能重现这个问题, 联想到java的jstack, 很是羡慕啊. 想到.net core 已经出来这么久了, 还是试着找找看吧, 结果还真找到一篇博客introducing diagnostics improvements in .net core 3.0

 

  这篇文章介绍了3个工具

  • dotnet-counters: 实时统计runtime的状况, 包括 cpu、内存、gc、异常等
  • dotnet-trace: 类似性能探测器
  • dotnet-dump: 程序崩溃时使用该工具

  这次使用的是dotnet-dump, 即使程序没有崩溃, 也可以dump程序快照, 用于分析

 

实验环境

ubuntu-16.04.5-desktop-amd64
sdk 3.0.100-preview6-012264

 

1. 新建一个简单console程序(只能是 .net core 3.0的程序, 不支持 .net core 2.2), 模拟cpu占用100%的情况

mkdir netcoredumptest && cd netcoredumptest
dotnet new console

编辑program.cs

namespace netcoredumptest
{
    using system;
    using system.threading.tasks;
    class program
    {
        static void main(string[] args)
        {
            task.factory.startnew(() => printnumber("print", 5));
            console.writeline("press any key to exit.");
            console.readkey();
        }

        static void printnumber(string message, int startnumber)
        {
            var number = startnumber;
            while (true)
                console.writeline($"{message} {number++}");
        }
    }
}

 

2. 安装dotnet-dump

dotnet tool install --global dotnet-dump --version 1.0.4-preview6.19311.1

提示

if you are using bash, you can add it to your profile by running the following command:

cat << \eof >> ~/.bash_profile
# add .net core sdk tools
export path="$path:/home/****/.dotnet/tools"
eof

you can add it to the current session by running the following command:

export path="$path:/home/****/.dotnet/tools"

you can invoke the tool using the following command: dotnet-dump
tool 'dotnet-dump' (version '1.0.4-preview6.19311.1') was successfully installed.

建议将 $home/.dotnet/tools加入到path, 好吧, 照着做吧, 记得使用下面的命令使设置立即生效

source ~/.bash_profile

 

3. 使用 dotnet netcoredumptest.dll 启动我们的问题程序, 然后使用  ps -ef | grep dotnet  查看程序的进程id, 可以看到进程id是 3411

ps -ef | grep dotnet
z*****e 3411 1464 22 07:51 pts/8 00:00:59 dotnet netcoredumptest.dll z*****e 3431 2935 0 07:55 pts/9 00:00:00 grep --color=auto dotnet

针对进程3411, 我们还需要知道是哪个线程占cpu, 使用 top -hp 3411 可以列出所有线程, 由于top每隔3秒刷新一次, 所以可能需要多观察几秒才能看到具体是哪个线程占用cpu比较高, 这里我们可以看到是pid=3418的线程(linux的进程id和线程id请自行了解一下).

top -hp 3411
   pid user      pr  ni    virt    res    shr s %cpu %mem     time+ command
  3418 z*****e    20   0 2997700  29060  22400 r 10.3  1.4   0:20.68 dotnet
  3411 z*****e    20   0 2997700  29060  22400 s  0.0  1.4   0:00.11 dotnet
  3412 z*****e    20   0 2997700  29060  22400 s  0.0  1.4   0:00.02 dotnet
  3413 z*****e    20   0 2997700  29060  22400 s  0.0  1.4   0:00.00 dotnet
  3414 z*****e    20   0 2997700  29060  22400 s  0.0  1.4   0:00.00 dotnet
  3415 z*****e    20   0 2997700  29060  22400 s  0.0  1.4   0:00.01 dotnet
  3416 z*****e    20   0 2997700  29060  22400 s  0.0  1.4   0:00.00 dotnet
  3417 z*****e    20   0 2997700  29060  22400 s  0.0  1.4   0:00.00 dotnet
  3421 z*****e    20   0 2997700  29060  22400 s  0.0  1.4   0:00.00 dotnet

 

获取dump, 只能正对进程进行dump, 所以我们输入的是 3411

dotnet-dump collect -p 3411
writing minidump with heap to /tmp/core_20190623_075649
complete

 

4. 分析

dotnet-dump analyze core_20190623_075649

使用clrthreads 查看所有线程

>clrthreads
threadcount:      4
unstartedthread:  0
backgroundthread: 3
pendingthread:    0
deadthread:       0
hosted runtime:   no
                                                                                                        lock
 dbg   id osid threadobj           state gc mode     gc alloc context                  domain           count apt exception
   0    1  d53 0000000001307d80    20020 preemptive  0000000000000000:0000000000000000 0000000001306450 1     ukn
   4    2  d57 000000000135bbd0    21220 preemptive  0000000000000000:0000000000000000 0000000001306450 0     ukn (finalizer)
   6    3  d59 00007f666c0009f0  1020220 preemptive  0000000000000000:0000000000000000 0000000001306450 0     ukn (threadpool worker)
   7    4  d5a 000000000130da40  1021220 preemptive  00007f6678106860:00007f6678106f20 0000000001306450 1     ukn (threadpool worker)

我们关心的线程3418的16进制是d5a, 也就是最后一行, 它的dbg是7, 我们需要使用 setthread 7, 将其设置为  当前操作的线程

然后使用 clrstack 获取线程调用信息

> setthread 7
> clrstack
os thread id: 0xd5a (7)
        child sp               ip call site
00007f6715561558 00007f671a2bd4bd [inlinedcallframe: 00007f6715561558] interop+sys.write(system.runtime.interopservices.safehandle, byte*, int32)
00007f6715561558 00007f669f669a9e [inlinedcallframe: 00007f6715561558] interop+sys.write(system.runtime.interopservices.safehandle, byte*, int32)
00007f6715561540 00007f669f669a9e ilstubclass.il_stub_pinvoke
00007f67155615e0 00007f669f67333e system.consolepal.write(microsoft.win32.safehandles.safefilehandle, byte*, int32, boolean)
00007f67155616a0 00007f669f67360c system.consolepal.write(microsoft.win32.safehandles.safefilehandle, byte[], int32, int32, boolean) [/_/src/system.console/src/system/consolepal.unix.cs @ 1236]
00007f67155616c0 00007f669f672b2a system.io.streamwriter.flush(boolean, boolean) [/_/src/system.private.corelib/shared/system/io/streamwriter.cs @ 261]
00007f6715561710 00007f669f6729f3 system.io.streamwriter.writeline(system.string) [/_/src/system.private.corelib/shared/system/io/streamwriter.cs @ 474]
00007f6715561760 00007f669f6727d3 system.io.textwriter+synctextwriter.writeline(system.string) [/_/src/system.private.corelib/shared/system/io/textwriter.cs @ 891]
00007f67155617a0 00007f669f672770 system.console.writeline(system.string) [/_/src/system.console/src/system/console.cs @ 550]
00007f67155617c0 00007f669f663791 netcoredumptest.program.printnumber(system.string, int32) [/home/zhouke/netcoredumptest/program.cs @ 18]
00007f6715561800 00007f669f6636d9 netcoredumptest.program+<>c.<main>b__0_0()
00007f6715561820 00007f669f1872a1 system.threading.tasks.task.innerinvoke() [/_/src/system.private.corelib/shared/system/threading/tasks/task.cs @ 2466]
00007f6715561840 00007f669f18cbc2 system.threading.tasks.task+<>c.<.cctor>b__274_0(system.object) [/_/src/system.private.corelib/shared/system/threading/tasks/task.cs @ 2445]
00007f6715561850 00007f669f171af2 system.threading.executioncontext.runfromthreadpooldispatchloop(system.threading.thread, system.threading.executioncontext, system.threading.contextcallback, system.object) [/_/src/system.private.corelib/shared/system/threading/executioncontext.cs @ 289]
00007f6715561890 00007f669f187111 system.threading.tasks.task.executewiththreadlocal(system.threading.tasks.task byref, system.threading.thread) [/_/src/system.private.corelib/shared/system/threading/tasks/task.cs @ 2406]
00007f6715561910 00007f669f186f28 system.threading.tasks.task.executeentryunsafe(system.threading.thread) [/_/src/system.private.corelib/shared/system/threading/tasks/task.cs @ 2344]
00007f6715561930 00007f669f186ebb system.threading.tasks.task.executefromthreadpool(system.threading.thread)
00007f6715561940 00007f669f17b754 system.threading.threadpoolworkqueue.dispatch() [/_/src/system.private.corelib/shared/system/threading/threadpool.cs @ 663]
00007f67155619c0 00007f669f169a5b system.threading._threadpoolwaitcallback.performwaitcallback() [/_/src/system.private.corelib/src/system/threading/threadpool.coreclr.cs @ 29]
00007f6715561d50 00007f6718a1ccaf [debuggeru2mcatchhandlerframe: 00007f6715561d50]

 

哗啦啦一大片, 有点java调用堆栈的味道, 不过我们还是找到了我们的问题代码

netcoredumptest.program.printnumber(system.string, int32)

 

有时候我们想知道传入的什么参数导致cpu占用高, 可以给clrstack加上参数 -a

> clrstack -a
..............
00007f0dd6ffc7c0 00007f0d6eef3791 netcoredumptest.program.printnumber(system.string, int32) [/home/zhouke/netcoredumptest/program.cs @ 18]
    parameters:
        message (0x00007f0dd6ffc7e8) = 0x00007f0d4800b8b0
        startnumber (0x00007f0dd6ffc7e4) = 0x0000000000000005
    locals:
        0x00007f0dd6ffc7e0 = 0x000000000014e42b
        0x00007f0dd6ffc7dc = 0x0000000000000001
...............

可以看到parameters里, startnumber作为值类型, 可以直接看到数值为5, 而message是引用类型, 指向0x00007f0d4800b8b0, 这时候需要用到 dumpobj 命令

> dumpobj 0x00007f0d4800b8b0
name:        system.string
methodtable: 00007f0d6ef70f90
eeclass:     00007f0d6eede1c0
size:        32(0x20) bytes
file:        /home/zhouke/dotnet/shared/microsoft.netcore.app/3.0.0-preview6-27804-01/system.private.corelib.dll
string:      print
fields:
              mt    field   offset                 type vt     attr            value name
00007f0d6ef6a138  400022b        8         system.int32  1 instance                5 _stringlength
00007f0d6ef66f38  400022c        c          system.char  1 instance               50 _firstchar
00007f0d6ef70f90  400022d      108        system.string  0   static 00007f0d47fff360 empty

好了, 可以看到它是一个字符串, 内容为 "print"

假如message是一个复杂类型, 可以查看fields下面的信息进一步查看

clrstack 还有一个实验性质的参数 -i, 协助查看各种变量信息, 需要用到lldb, 按照官方教程, 我暂时没有实验成功.

 

查看进程id和线程id, 更方便的方法是 htop(需要安装), 然后按 f4 进行过滤, 输入dotnet 即可

使用dotnet-dump 查找 .net core 3.0 占用CPU 100%的原因

这张图是重新运行问题程序的结果, 进程id和线程id与前面不一样

第二行白色的是进程id=1650, 第一行cpu占用高, 是问题线程id=1658

 

 

 

end