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


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

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

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

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

  • Изучение высокого использования ЦП
  • Определение использования ЦП с помощью счетчиков dotnet-counter
  • Использование 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 Delta значениях dotnet.process.cpu.time, они сообщают нам, сколько секунд в течение периода обновления (в настоящее время установлено значение по умолчанию 1 s) ЦП активен. При запуске веб-приложения сразу после запуска ЦП не используется вообще, и эти разностные значения являются обоими 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 можно использовать во всех операционных системах, однако его ограничения с предвзятостью безопасной точки и управляемыми вызовами приводят к более общей информации по сравнению с профилировщиком, поддерживающего ядро, например perf для Linux или ETW для Windows. Если исследование производительности включает только управляемый код, обычно dotnet-trace будет достаточно.

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

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. Дополнительные сведения см. в разделе "Анализ данных об использовании ЦП".

См. также

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