实验室 3 排查 Linux 中 dotnet-dump 的性能和 GC 问题

适用于: .NET Core 2.1、.NET Core 3.1、.NET 5

本文介绍如何使用 dotnet-dump 工具捕获和分析 .NET Core 转储,以及如何在 dotnet-gcdump Linux 中生成 GC 相关报告。

先决条件

遵循以下故障排除实验室的最低要求如下:

  • ASP.NET 核心应用程序,用于演示低 CPU 和高 CPU 性能问题和崩溃问题。
  • 打开核心转储文件时,lldb 调试器已安装并配置为加载 SOS 扩展。

如果已遵循本系列的上一部分,则应准备好进行以下设置:

  • Nginx 配置为托管两个网站:
    • 第一个使用 myfirstwebsite 主机标头 (http://myfirstwebsite) 侦听请求,并将请求路由到在端口 5000 上侦听的演示 ASP.NET Core 应用程序。
    • 第二个使用 buggyamb 主机标头 (http://buggyamb) 侦听请求,并将请求路由到侦听端口 5001 的第二个 ASP.NET 核心示例 buggy 应用程序。
  • ASP.NET Core 应用程序应作为服务运行,这些服务会在服务器重启或应用程序停止响应时自动重启。
  • Linux 本地防火墙已启用并配置为允许 SSH 和 HTTP 流量。

注意

如果设置尚未准备就绪,请转到“第 2 部分创建并运行 ASP.NET Core 应用”。

本实验室的目标

实验室 2.2 中,使用 ProcDump 捕获转储文件,并使用 ProcDump 对其进行测试,以手动捕获核心转储文件,或通过监视内存使用情况来捕获核心转储文件。

在本部分中,你将了解如何使用 dotnet-dump 捕获和分析核心转储文件,而无需任何本机调试器。 然后,你将了解如何使用 dotnet-gcdump 它生成可在基于 Windows 的计算机上打开的 GC 相关报表。

Dotnet-dump

dotnet-dump 是收集和分析核心转储文件的另一种方法,无需使用本机调试器(例如 Linux 上的 lldb)。 借助此工具,还可以运行 SOS 命令,以分析崩溃和垃圾回收器(GC)相关问题。 Dotnet-dump 仅在 .NET Core 3.0 SDK 及更高版本中可用。

注意

Dotnet-dump 不是本机调试器。 因此,某些功能(如显示本机堆栈帧)在此工具中不可用。

如果尚未安装 dotnet-dump,可以通过运行以下命令立即安装它:

dotnet tool install -g dotnet-dump

若要手动捕获核心转储文件,可以使用 dotnet-dump collect 该命令。 例如,dotnet-dump collect -p 11724。 该工具将收集使用 createdump 或 ProcDump 收集的相同核心转储文件。

dotnet 转储最重要的功能是,它可用于打开 .NET Core 转储文件并运行 SOS 命令,与 lldb 相同。 你仍必须设置符号并安装 SOS 扩展才能使用 dotnet-dump 分析转储文件。

注意

如果以前使用 lldb,则应已设置符号并安装了 SOS。 因此,你应该能够打开相同的 .NET Core 版本转储文件,而无需再次下载符号。 如果打开尚未下载符号的其他 .NET Core 版本转储文件,则必须在开始分析之前下载该版本的 .NET Core 的符号。

使用 dotnet-dump 收集核心转储文件

若要使用 dotnet-dump 收集核心转储文件,请使用 dotnet-dump collect <PID> 该命令。 此时,确定 PID 应该是一项简单的任务。 但是,如果需要帮助,此命令将包含一个 ps 参数。 该 dotnet-dump ps 命令列出了可从中收集转储文件的 dotnet 进程。

如果尝试运行 dotnet-dump ps 该命令,将遇到一些意外的结果。

ps 命令的屏幕截图。

此列表中显示两个进程。 但是,其中一个进程显示为提升的进程,其路径不能由 dotnet-dump ps 命令确定。 若要查找其路径,请运行 cat /proc/<PID>/cmdline 该命令来检查进程命令行信息。

注意

在此命令中,替换为 <PID> 目标进程的实际进程 ID,如以下示例输出中所示。 可以召回前面部分中的特殊 /proc/ 目录,Linux 中的进程可以被视为该目录下的另一个文件夹(具有进程 ID 的名称)。 可以通过检查 /proc/<PID>/ 目录结构来查找有关进程的所有详细信息。

cat cmdline 命令的屏幕截图。

如此屏幕截图所示,第一个命令(cat /proc/6164/cmdline)的输出会告诉你,这是第一个演示 ASP.NET Core 应用程序。 第二个命令 (cat /proc/15586/cmdline) 的输出告诉你,这是你的 bug 应用程序。

dotnet-dump ps尽管命令无法检索进程路径,但它仍应该能够生成必要的转储文件进行故障排除。 尝试使用 dotnet-dump collect 命令捕获核心转储文件。 首先,尝试该工具能够显示进程路径的演示应用程序。 运行 dotnet-dump collect -p 6164 (尝试练习时进程 PID 会有所不同)。

collect 命令的屏幕截图。

生成内存转储文件成功。 现在,尝试为该工具在使用命令时 dotnet-dump ps 无法列出进程路径的 buggy 应用程序收集核心转储文件。 运行 dotnet-dump collect -p 15586,并注意到这种情况意外失败。

dotnet collect 命令的屏幕截图。

返回以下错误消息:

(13): 权限被拒绝 /tmp/dotnet-diagnostic-15586-25444688-socket

当你注意到 dotnet-dump ps 命令无法获取进程路径时,你可能怀疑尝试生成核心转储文件会失败。

在前面的错误消息中,可以确定存在权限问题。 但是,这两个进程之间的区别是什么:第一个托管 .NET 5 示例应用程序,第二个托管 .NET Core 3 应用程序? 比较每个进程的服务文件。 应能够相对轻松地发现差异:运行每个进程的用户帐户是不同的。

两个命令的屏幕截图。

列表左侧对应于第一个演示应用程序,其中一切正常,应用程序以用户名>(或设置环境并登录时使用的用户帐户)运行<。 右侧对应于 buggy 应用程序,转储文件收集失败。 应用程序以 www-data 用户身份运行。

此问题的解决方案在官方 GitHub 页面上讨论。

总之,如果要以与用于登录的帐户不同的用户身份运行服务进程,则应按如下所示运行 dotnet-dump collect 命令。

sudo 命令的屏幕截图。

下面是命令的格式:

sudo -H -u <user name of service> bash -c "<full path to dotnet tools>/dotnet-dump collect -p <PID> -o <output path>"

可以使用命令确定 dotnet-dump 工具 whereis dotnet-dump 的完整路径。

whereis 命令的屏幕截图。

输出路径应是用户具有写入权限的目录。 通常,可以在 /tmp 目录下创建目录,然后将转储文件复制到主目录。

使用 dotnet-dump 打开和分析核心转储文件

在本练习中,无需捕获新的转储文件。 相反,可以打开使用 createdump 捕获的以前的转储文件之一。 如果需要,可以打开使用 dotnet-dump 捕获的核心转储文件。

若要使用 dotnet-dump 打开转储文件,请运行 dotnet-dump analyze ~/dumps/coredump.manual.2.11724 (计算机上内存转储文件的名称将有所不同)。 这是前面在 lldb 中使用的托管调试器引擎。 如果符号配置正确且 SOS 安装正确,则可以运行任何 SOS 命令,就像使用 lldb 一样。 在以下列表中,可以看到该 clrstack 命令在操作中。

clrstack 命令的屏幕截图。

请记住两点:

  • 由于这不是本机调试器,因此无法运行 lldb 本机调试器命令,例如之前指示在 lldb 中使用的内存读取命令。 只能运行 SOS 命令。
  • 自动完成不起作用。 在 lldb 中,可以开始键入命令,然后按 TAB 键自动完成命令,就像在命令行和 shell 中一样。 此功能不适用于 dotnet-dump。 但是,这不应影响你的训练。

打开核心转储文件时,请利用此机会练习一些新的 SOS 命令。

如何检查转储中的 CPU 使用率? 该 threadpool 命令可能有助于显示此信息。 它报告“服务器的总 CPU 使用率”(不仅针对正在调试的进程),但它仍然提供在计算机上生成内存转储文件时资源消耗的一般概念。 以下屏幕截图显示服务器的总 CPU 使用率约为 94%。

threadpool 命令的屏幕截图。

注意

用于创建这些列表的服务器配备了两个 CPU。 这就是为什么它生成此类报表的原因。

相同的输出还显示线程池配置,在上一个列表中,有 7 个工作线程可用,其中 6 个线程正在运行。 排查线程池配置问题时,此信息非常有用。

是否要将所有正在运行的线程的调用堆栈分组,并显示与 Visual Studio Parallel Stacks 面板类似的合并显示? 运行 pstacks 以获取类似的结果。

pstacks 命令的屏幕截图。

若要进一步了解可用的 SOS 命令,请打开 SOS 帮助并单独查看每个命令,以熟悉 SOS 调试的强大功能。

如果安装 lldb 不是一个选项,则 Dotnet-dump 非常有用。 培训继续引入另一种方法来分析 GC 堆。

Dotnet-gcdump

Dotnet-gcdump 是另一个有用的工具。 它在 .NET Core 3.1 或更高版本中可用。

此工具背后的想法是,不需要完整进程转储才能在主要想要查看托管堆的许多方案中完成调查。 那么,为什么不只是捕获堆信息并生成有关它的报表? 最重要的是,此工具生成的 GC 转储文件是可移植的,可在 Windows 计算机上进行分析。 由于这种类型的转储文件仅包含 GC 堆信息,因此无法在 lldb 中打开它来调查线程或线程调用堆栈。 但你可以在 PerfView 或 Visual Studio 中打开它。

这听起来可能很有希望,但... 必须注意一点: Dotnet-gcdump 在过程中触发第 2 代 GC 集合来收集数据。 在生产环境中仔细使用此工具。 除非你知道你绝对必须使用它,否则不要使用它。

何时可能需要使用此工具? 如果进程已处于非响应(挂起)状态,并且无法从情况中恢复,并且你打算重启应用程序。 然后,可以在重启前捕获 gcdump,以便你至少有一组信息可在稍后阶段进行分析。

此工具捕获的信息对于以下任务非常有用:

  • 按堆上的类型比较对象数。
  • 分析对象根。
  • 确定哪些对象具有对哪些类型的引用。
  • 有关堆上对象的其他统计分析。

如果尚未安装此工具,请立即执行此操作。 只需运行以下命令:

dotnet tool install --global dotnet-gcdump

使用 dotnet-gcdump 命令收集 gcdump

适用于应用于 dotnet-gcdump dotnet-dump 工具的相同规则:如果为其他用户运行进程,则必须使用以下格式运行命令:

sudo -H -u <user name of service> bash -c "<full path to dotnet tools>/dotnet-gcdump collect -p <PID> -o <output path>"

如果此命令看起来不熟悉,请参阅“使用 dotnet-dump”部分收集核心转储文件。

现在,你应该拥有使用该工具收集进程信息所需的所有内容。 目标是收集两组数据,就像使用核心转储文件一样。 目标进程再次为其他用户运行。 因此,必须对启动命令使用“bash”格式。

步骤如下:

  1. 通过使用 ASP.NET 核心网站中包含的负载生成器工具,通过运行六个请求来重现性能问题。

  2. 运行以下命令收集第一个 gcdump:

    sudo -H -u www-data bash -c "/home/<User Name>/.dotnet/tools/dotnet-gcdump collect -p 15586 -o /tmp/gcreport1.gcdump"

    注意:在此命令中,将 PID 替换为你自己的 PID。

  3. 等待 5-10 秒,然后运行以下命令收集第二个 gcdumpby:

    sudo -H -u www-data bash -c "/home/<User Name>/.dotnet/tools/dotnet-gcdump collect -p 15586 -o /tmp/gcreport2.gcdump

    注意:在此命令中,将 PID 替换为原始 PID。

请记住,你正在排查性能问题。 最好有多个数据集,以便比较托管堆随时间变化的方式。 这些命令的结果是生成两个 gcdump 文件。

sudo h 命令的屏幕截图。

从 gcdump 文件创建报表

将两个 gcdump 文件复制到基于 Windows 的计算机,并在 Visual Studio 2019 中打开它们。 以下屏幕截图显示了为此列表拍摄的两个 gcdump 文件的比较结果。 如你所看到的,String 对象类型值之间存在很大的差异。 还可以获取有关对象的一些根信息。

报表文件的屏幕截图。

这可以是有用的信息。

或者,可以在 PerfView打开 gcdump 报表。

报表文件的屏幕截图。

如果必须仅查看托管堆,则这可能是最快的选项。 只需记住在生产中使用时 dotnet-gcdump 要谨慎,因为如前所述,它会在进程中触发完整的 GC,这可能会导致长时间暂停。 在生产环境中仔细使用此工具,仅在排查内存问题时在必要时使用。