Поделиться через


Отладка высокой загрузки ЦП в .NET Core

Эта статья относится к пакету ✔️ SDK для .NET Core 3.1 и более поздних версий.

В этом руководстве вы узнаете, как отлаживать чрезмерный сценарий использования ЦП. Используя приведенный пример репозитория исходного кода веб-приложения ASP.NET Core, можно намеренно вызвать дедлок. Конечная точка перестанет отвечать на запросы и накапливать потоки. Вы узнаете, как использовать различные средства для диагностики этого сценария с помощью нескольких ключевых элементов диагностических данных.

При работе с этим руководством вы сделаете следующее:

  • Изучение высокого использования ЦП
  • Определение использования ЦП с помощью dotnet-counters
  • Использование dotnet-trace для создания трассировки
  • Производительность профиля в PerfView
  • Диагностика и устранение чрезмерного использования ЦП

Предпосылки

В этом руководстве используется:

Счетчики ЦП

Прежде чем пытаться выполнить эту инструкцию, установите последнюю версию dotnet-counters:

dotnet tool install --global dotnet-counters

Если приложение работает с версией .NET старше, чем .NET 9, интерфейс вывода dotnet-counters будет выглядеть несколько иначе. Подробнее см. в документации dotnet-counters.

Прежде чем пытаться собирать диагностические данные, необходимо наблюдать за высоким состоянием ЦП. Запустите пример приложения с помощью следующей команды из корневого каталога проекта.

dotnet run

Чтобы проверить текущее использование ЦП, используйте команду dotnet-counters :

dotnet-counters monitor -n DiagnosticScenarios --showDeltas

Выходные данные должны быть похожи на следующие:

Press p to pause, r to resume, q to quit.
    Status: Running

Name                                                            Current Value      Last Delta
[System.Runtime]
    dotnet.assembly.count ({assembly})                               111               0
    dotnet.gc.collections ({collection})
        gc.heap.generation
        ------------------
        gen0                                                           8               0
        gen1                                                           1               0
        gen2                                                           0               0
    dotnet.gc.heap.total_allocated (By)                        4,042,656          24,512
    dotnet.gc.last_collection.heap.fragmentation.size (By)
        gc.heap.generation
        ------------------
        gen0                                                     801,728               0
        gen1                                                       6,048               0
        gen2                                                           0               0
        loh                                                            0               0
        poh                                                            0               0
    dotnet.gc.last_collection.heap.size (By)
        gc.heap.generation
        ------------------
        gen0                                                     811,512               0
        gen1                                                     562,024               0
        gen2                                                   1,095,056               0
        loh                                                       98,384               0
        poh                                                       24,528               0
    dotnet.gc.last_collection.memory.committed_size (By)       5,623,808               0
    dotnet.gc.pause.time (s)                                           0.019           0
    dotnet.jit.compilation.time (s)                                    0.582           0
    dotnet.jit.compiled_il.size (By)                             138,895               0
    dotnet.jit.compiled_methods ({method})                         1,470               0
    dotnet.monitor.lock_contentions ({contention})                     4               0
    dotnet.process.cpu.count ({cpu})                                  22               0
    dotnet.process.cpu.time (s)
        cpu.mode
        --------
        system                                                         0.109           0
        user                                                           0.453           0
    dotnet.process.memory.working_set (By)                    65,515,520               0
    dotnet.thread_pool.queue.length ({work_item})                      0               0
    dotnet.thread_pool.thread.count ({thread})                         0               0
    dotnet.thread_pool.work_item.count ({work_item})                   6               0
    dotnet.timer.count ({timer})                                       0               0

Рассматривая значения Last Deltadotnet.process.cpu.time, можно определить, сколько секунд в период обновления (который в настоящее время по умолчанию составляет 1 с) процессор был активен. При запуске веб-приложения сразу после запуска ЦП не используется вообще, и эти разностные значения являются обоими 0. Перейдите к маршруту api/diagscenario/highcpu с 60000 параметром маршрута:

https://localhost:5001/api/diagscenario/highcpu/60000

Теперь повторно выполните команду dotnet-counters .

dotnet-counters monitor -n DiagnosticScenarios --showDeltas

Вы должны увидеть увеличение использования ЦП, как показано ниже (в зависимости от хост-компьютера, ожидается изменение использования ЦП):

Press p to pause, r to resume, q to quit.
    Status: Running

Name                                                            Current Value      Last Delta
[System.Runtime]
    dotnet.assembly.count ({assembly})                               111               0
    dotnet.gc.collections ({collection})
        gc.heap.generation
        ------------------
        gen0                                                           8               0
        gen1                                                           1               0
        gen2                                                           0               0
    dotnet.gc.heap.total_allocated (By)                        4,042,656          24,512
    dotnet.gc.last_collection.heap.fragmentation.size (By)
        gc.heap.generation
        ------------------
        gen0                                                     801,728               0
        gen1                                                       6,048               0
        gen2                                                           0               0
        loh                                                            0               0
        poh                                                            0               0
    dotnet.gc.last_collection.heap.size (By)
        gc.heap.generation
        ------------------
        gen0                                                     811,512               0
        gen1                                                     562,024               0
        gen2                                                   1,095,056               0
        loh                                                       98,384               0
        poh                                                       24,528               0
    dotnet.gc.last_collection.memory.committed_size (By)       5,623,808               0
    dotnet.gc.pause.time (s)                                           0.019           0
    dotnet.jit.compilation.time (s)                                    0.582           0
    dotnet.jit.compiled_il.size (By)                             138,895               0
    dotnet.jit.compiled_methods ({method})                         1,470               0
    dotnet.monitor.lock_contentions ({contention})                     4               0
    dotnet.process.cpu.count ({cpu})                                  22               0
    dotnet.process.cpu.time (s)
        cpu.mode
        --------
        system                                                         0.344           0.013
        user                                                          14.203           0.963
    dotnet.process.memory.working_set (By)                    65,515,520               0
    dotnet.thread_pool.queue.length ({work_item})                      0               0
    dotnet.thread_pool.thread.count ({thread})                         0               0
    dotnet.thread_pool.work_item.count ({work_item})                   6               0
    dotnet.timer.count ({timer})                                       0               0

На протяжении всего времени выполнения запроса, использование ЦП будет оставаться на повышенном уровне.

Подсказка

Чтобы визуализировать еще большее использование ЦП, эту конечную точку можно использовать одновременно на нескольких вкладках браузера.

На этом этапе можно безопасно сказать, что ЦП работает выше, чем ожидалось. Определение последствий проблемы является ключом для поиска причины. Мы будем использовать эффект высокого потребления ЦП в дополнение к средствам диагностики, чтобы найти причину проблемы.

Анализ высокого ЦП с помощью Профилировщика

При анализе приложения с высоким уровнем использования ЦП используйте профилировщик, чтобы понять, что делает код. dotnet-trace collect работает во всех операционных системах, но смещение безопасных точек и управляемые только стековые вызовы ограничивают его более общими сведениями по сравнению с ядровым профилировщиком, таким как ETW для Windows или perf для Linux. В зависимости от версии вашей операционной системы и .NET на конкретной платформе могут быть доступны улучшенные возможности профилирования — см. вкладки, где содержатся подробные инструкции для каждой платформы.

Используйте dotnet-trace collect-linux (.NET 10+)

Начиная с .NET 10 и более поздних версий, dotnet-trace collect-linux является рекомендуемым подходом к профилированию на Linux. Он объединяет EventPipe с perf_events уровня ОС для создания единой трассировки, которая включает как управляемые, так и собственные вызовы, все без необходимости перезапуска процесса. Для этого требуются корневые разрешения и ядро Linux 6.4+ с CONFIG_USER_EVENTS=y. Прочитайте о collect-linux prerequisites для получения полной информации о требованиях.

Убедитесь, что тестовый отладочный объект настроен для .NET 10 или более поздней версии, затем запустите его и повторно протестируйте конечную точку с высокой загрузкой ЦП (https://localhost:5001/api/diagscenario/highcpu/60000). Поскольку она выполняется в течение запрашиваемой 1 минуты, выполните команду dotnet-trace collect-linux, чтобы записать трассировку на уровне компьютера.

sudo dotnet-trace collect-linux

Дайте ему проработать около 20–30 секунд, затем нажмите Ctrl+C или Enter, чтобы остановить сбор данных. Результатом является файл, включающий как управляемые, так и собственные стеки вызовов .nettrace.

Откройте .nettrace с помощью PerfView и используйте представление CPU стеков, чтобы определить методы, которые потребляют больше всего процессорного времени.

Сведения о разрешении собственных символов среды выполнения в трассировке см. в разделе "Получение символов для собственных кадров среды выполнения".

Используйте perf

Средство perf также можно использовать для создания профилей приложений .NET Core. Выход из предыдущего экземпляра примера целевого объекта отладки.

DOTNET_PerfMapEnabled Задайте переменную среды, чтобы приложение .NET создавало map файл в каталоге/tmp. Этот map файл используется perf для сопоставления адресов процессора с функциями, созданными JIT по имени. Для получения дополнительной информации см. раздел «Экспорт карт perf» и «дампы jit».

Запустите образец целевого объекта отладки в том же сеансе терминала.

export DOTNET_PerfMapEnabled=1
dotnet run

Повторно вызовите конечную точку API с высоким использованием ЦП (https://localhost:5001/api/diagscenario/highcpu/60000). Пока выполняется 1-минутный запрос, выполните команду perf с вашим идентификатором процесса:

sudo perf record -p 2266 -g

Команда perf запускает процесс сбора производительности. Дайте ему поработать около 20–30 секунд, затем нажмите CTRL+C, для завершения процесса сбора данных. Вы можете использовать ту же perf команду, чтобы просмотреть выходные данные трассировки.

sudo perf report -f

Вы также можете создать диаграмму пламени с помощью следующих команд:

git clone --depth=1 https://github.com/BrendanGregg/FlameGraph
sudo perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg

Эта команда создает flamegraph.svg представление, которое можно просмотреть в браузере, чтобы изучить проблему производительности:

Изображение SVG графа пламени

Анализ данных с высоким уровнем ЦП с помощью Visual Studio

Все файлы *.nettrace можно анализировать в Visual Studio. Чтобы проанализировать файл Linux *.nettrace в Visual Studio, передайте файл *.nettrace, помимо других необходимых документов, на компьютер Windows, а затем откройте файл *.nettrace в Visual Studio. Дополнительные сведения см. в разделе "Анализ данных об использовании ЦП".

См. также

Дальнейшие шаги