- PVSM.RU - https://www.pvsm.ru -
Вы уже наверняка знаете, что спустя несколько месяцев после конференций мы выкладываем видеозаписи всех докладов [1]. А для самых лучших, как в случае с кейноутом Саши goldshtn [2] Гольдштейна, мы готовим еще и расшифровки — чтобы приобщиться могли и те, кто не любит видеоформат.
Саша рассказывает о способах и инструментах анализа производительности приложений, в том числе разработанных им самим.
В основе статьи — выступление Саши на конференции DotNext 2017 Piter. Саша работает техническим директором израильской тренинговой и консалтинговой компании Sela и не понаслышке знает, как проводить анализ производительности. Как его лучше начинать, чем завершать, какие инструменты стоит использовать, а каких избегать, читайте под катом.
Начнем со структуры анализа производительности. Нижеописанный план используют разработчики, системные администраторы, любые технические специалисты:
Получить точное описание проблемы от клиента гораздо сложнее, чем кажется на первый взгляд.
Клиент может обратиться с проблемой вроде:
«Приложение работает слишком МЕДЛЕННО. Пользователи не могут точно сказать, в каких случаях это происходит, но это плохо. Сможете посмотреть?»
Или
«У нас есть бюджет на работы по повышению производительности, в течение двух дней сможете посмотреть на нашу рабочую среду и найти проблему?»
Конечно, попробовать можно, но это вряд ли будет очень хорошим использованием выделенного бюджета. Проблема, сформулированная более точно, может выглядеть так:
«Начиная с 4:25 утра, при обращении к сайту ASP.NET в 95% случаях наблюдается задержка в 1400 мс (обычное время отклика – 60 мс). Задержка наблюдается вне зависимости от географического расположения и не снижается. Мы включили автоматическое масштабирование, но это не помогло».
Такое описание проблемы уже гораздо точнее, потому что я вижу симптомы и понимаю, куда мне смотреть, и я знаю, что можно будет считать решением проблемы (снижение задержки до 60 мс).
Иногда узнать, что именно требуется клиенту, гораздо сложнее, чем кажется на первый взгляд.
У каждой компании свои требования к производительности, которые, к сожалению, не всегда формулируются. К примеру, они могут быть такими:
Когда такие требования есть, остается только протестировать систему на соответствие им и понять, как решать проблему. Но важно понимать, что требования не берутся из ниоткуда, они всегда должны соответствовать бизнес-целям. Имея четко сформулированные требования, всегда можно отслеживать статистику в APM-решении или другими способами и получать уведомления, когда что-то идет не так.
Прежде чем погрузиться в методы анализа, которые дают результат, я хочу немного поговорить о том, как не следует анализировать проблему. Итак, вам определенно не стоит:
Приведу пример одного неудачного анализа, который так и не был завершен. Моей задачей было понять, почему иногда при сохранении и загрузке документов в системе управления проектами клиенты сталкиваются со значительными задержками. Система была связана с NetApp через SMB в локальной сети, и моей задачей было выяснить время ожидания сети и задержки, которые могли возникать при работе с хранилищем данных.
У меня были инструменты для отслеживания производительности WCF и сервера приложений, у меня был сниффер для сетевого трафика, но у меня не было доступа к системе хранения данных NetApp. После серии тестов я выяснил, что средняя скорость отклика была равна 11 мс, однако в течение 24 часов наблюдались некоторые случаи задержки в 1200 мс. Мне не хватало информации о том, что происходит со стороны NetApp, и необходимо было получить данные тестирования производительности.
От клиента мне удалось получить лишь информацию о том, что скорость отклика системы хранения данных никак не может быть менее 5 мс. На мой вопрос о том, что это за цифра: средняя или пиковая задержка, я получил ответ: это максимальное среднее значение в течение 60 секунд. Я до сих пор не знаю, что это за значение, и я полагаю, что вы тоже не в курсе. Он мог брать среднее значение каждую секунду и затем — максимальное значение от всех средних или, возможно, брал максимальное значение каждую секунду и затем — среднее от максимума…
После этого я нашел в документации для NetApp счетчики производительности, которые считаются допустимыми для этой системы хранения данных. Это средние данные за секунду, а не за минуту. Я попросил у клиента предоставить мне эти данные, однако получил отказ. На этом попытки проведения анализа завершились.
Для меня это классический случай того, как нельзя проводить анализ производительности. Я всеми силами пытался получить как можно больше информации и не полагаться на предположения, однако это мне не удалось из-за отсутствия взаимопонимания с клиентом. И это хороший пример того, почему нельзя полагаться на предположения и абсурдные убеждения.
Теперь о неудачном использовании инструментов.
Иногда специалисты думают, что если они купили дорогой инструмент, то они просто обязаны его применять для всех вариантов анализа. Приведу классический пример использования неправильного инструмента для проведения анализа.
Запустим средства профилирования Visual Studio в режиме выборки CPU, чтобы проверить производительность поискового робота. Робот может делать некоторые вещи, которые не нагружают процессор, и если мы проведем такой тест, то можем получить примерно такие результаты:
Из этого следует, что необходимо улучшать производительность System.Console.WriteLine, поскольку этот метод замедляет приложение. Однако поисковый робот может просто ждать поступления сетевых данных, это никак не связано с процессором. Поэтому никогда нельзя выбирать инструмент для анализа по принципу «просто потому что мы его купили, и нам нужно отбить его стоимость».
Иногда вы просто не знаете, что нужно искать, и в этом случае я предлагаю методологию, которая часто используется инженерами во всем мире. Это – метод USE (Utilization, Saturation, Errors), работа с которым происходит в несколько этапов:
Вот как может выглядеть метод USE для аппаратных и программных ресурсов:
У вас должен быть чек-лист, согласно которому вы планомерно тестируете каждый из компонентов, чтобы получить общую картину.
Вот как выглядит чек-лист для систем Windows:
Компонент | Тип | Инструменты анализа или отслеживаемые параметры |
Процессор | Загрузка | Processor (_Total)%ProcessorTime, %User Time Process(My App)%ProcessorTime |
Процессор | Насыщение | SystemProcessor Queue Length |
Процессор | Ошибки | Intel Processor Diagnostic Tool (и другие) |
Память | Загрузка | MemoryAvailable Mbytes ProcessVirtual Size, Private Bytes, Working Set .NET CLR Memory#Bytes in all Heaps VMMap, RAMMap |
Память | Насыщение | MemoryPages/sec |
Память | Ошибки | Windows Memory Diagnostic Utility (и другие) |
Сеть | Загрузка | Network InterfaceBytes Received/sec, Bytes Sent/sec |
Сеть | Насыщение | Network InterfaceOutput Queue Length, Packets Outbound Discarded, Packets Received Discarded |
Сеть | Ошибки | Network InterfacePackets Outbound Errors, Packets Received Errors |
Диск | Загрузка | Physical Disc% Disc Time, % Idle Time, Disc Reds/sec, Disk Writes/sec |
Диск | Насыщение | Physical DiscCurrent Disk Queue Length |
Диск | Ошибки | Chkdisk (и другие инструменты) |
Приложение | Ошибки | .NET CLR Exceptions# of Excepts Thrown/sec ASP.NetError Events Raised |
Большинство этих данных можно получить при помощи встроенных счетчиков производительности Windows. Такую проверку можно выполнить очень быстро, но она позволяет сэкономить уйму времени, чтобы затем сосредоточиться на анализе обнаруженных проблем.
Для автоматизации этого процесса можно использовать самые разные решения:
Инструменты для анализа производительности можно разделить на три категории:
Как правило, инструменты первой категории дают небольшой оверхед, при использовании инструментов для определения времени ожидания он больше, а средства третьей категории приводят к значительному оверхеду. И это неудивительно, ведь последние предоставляют гораздо больше информации.
При выборе инструментов важно обращать внимание на пять моментов:
Любое наблюдение может повлиять на состояние системы, но некоторые инструменты влияют сильнее других. Поэтому перед использованием любого инструмента лучше всего обратиться к документации. Как правило, в ней указывается, чего можно ожидать от применения инструмента (например, повышение нагрузки на процессор на 5-10% при определенных обстоятельствах).
Если в документации к инструменту, который вы собираетесь использовать, ничего не сказано об оверхеде, значит, вам придется протестировать его самостоятельно. Это нужно делать на тестовой системе, замеряя, насколько падает производительность.
Возможно, для тех, кто не работает с Java, это будет новостью, но большинство Java-профайлеров CPU, которые используются разработчиками, выдают неправильные данные (VisualVM, jstack, YourKit, JProfiler…). Они используют GetAllStackTraces, задокументированный JVMTI API. Он выдает семпл того, что делает каждый поток в системе, когда вы вызываете функцию GetAllStackTraces.
Преимущество его использования – кроссплатформенность, однако есть и существенный недостаток. Вы получаете семпл потока только тогда, когда все потоки находятся в безопасных состояниях. То есть если вы запрашиваете трассировку стека, вы получаете ее не из текущего момента, а из какой-то точки позже, когда поток решает, что он хочет передать свою трассировку стека. В итоге вы получаете результаты, которые не имеют никакого отношения к реальному положению дел.
На скриншоте ниже вы можете увидеть данные научного доклада, посвященного точности Java-профайлеров.
На графике можно увидеть данные четырех профайлеров о том, какой из методов на определенном бенчмарке был самым «горячим». Два из четырех профайлеров (справа и слева) определили, что это был метод jj_scan_token, третий профайлер определил, что это был метод getPositionFromParent, а четвертый – DefaultNameStep.evaluate. То есть четыре профайлера дали совершенно разные показания и совершенно разные методы. И тут дело не в профайлерах, а в API, которые они используют для получения результатов из целевого процесса.
Именно поэтому, если вы используете новый инструмент, нужно обязательно протестировать его в разных условиях (когда процессор активно работает, находится в состоянии покоя или же происходит считывание данных с диска). И вы должны убедиться, что профайлер предоставляет корректные данные, а затем посмотреть на оверхед. Если данные некорректны, то этот профайлер, конечно, не стоит использовать.
Тут я хочу привести пример инструкции по профилированию .NET Core на Linux.
Мы не будем рассматривать ее подробно, обратимся лишь к некоторым моментам. Начинается она с необходимости настройки переменной окружения, с чем у меня, например, возникают проблемы. Ну ладно, допустим, вы это сделали. Инструкция заканчивается тем, что нужно взять ZIP-файл, сгенерированный в результате выполнения всех этих шагов, скопировать на Windows-машину и открыть его с использованием PerfView. И лишь тогда вы сможете проанализировать полученные данные. Звучит нелепо, не правда ли? Выполнить анализ на Linux, а затем открыть его в Windows…
Вот альтернативный вариант решения той же задачи. Эти скрипты работают не слишком хорошо, но они, по крайней мере, дают возможность получить результат на Linux.
$ ./dotnet-mapgen.py generate 4118
$ ./dotnet-mapgen.py merge 4118
# perf record -p 4118 -F 97 -g
# perf script | ./stackcollapse-perf.pl > stacks
$ ./flamegraph.pl stacks > stacks.svg
В итоге вы получаете визуализацию, которая называется флейм-граф. Я остановлюсь на ней подробнее, так как многие Windows и .NET-разработчики с ней еще не знакомы.
Этот метод позволяет визуализировать разные трассировки стека, например, где приложение часто обращается к диску, когда происходит сильная загрузка процессора и т.д. Когда у вас есть множество разных стеков, флейм-граф – это хороший способ наглядно визуализировать их вместо того, чтобы читать много текста. Флейм-граф превращает тысячи страниц трассировки стека в один интерактивный график.
Каждый прямоугольник на графике – это функция. Цвета подбираются в случайном порядке, поэтому их можно игнорировать. Ось Y – это глубина стека, то есть если одна функция вызвала другую, она будет расположена над ней, и на графике будет показана выше. Ось X – это отсортированные стеки (не время). Имея такой график, очень легко приблизить именно ту область, которая вам интересна.
Инвазивные профайлеры могут плохо влиять на производительность, надежность и отклик системы из-за того, что они слишком «тяжелые». К примеру, при использовании профайлера Visual Studio в instrumentation mode и IntelliTrace происходит перекомпиляция приложения и его запуск с дополнительными маркерами. Такой инструмент невозможно использовать в рабочей среде.
Другой пример – CLR Profiling API, который до сих пор используется в некоторых инструментах. Он основан на внедрении DLL в целевой процесс. Возможно, это приемлемо при разработке, но в рабочей среде внедрять библиотеку в запущенный процесс может быть проблематично.
Экстремальный пример на Linux – это фреймворки трассировки Linux SystemTap, LTTng и SysDig, требующие установки в системе кастомного модуля ядра. Да, вы можете доверять этим ребятам, но все равно это немного подозрительно, что для запуска инструмента для измерения производительности вам необходимо загружать что-то новое в ядро.
К счастью, в Windows есть достаточно легкий фреймворк трассировки Event Tracing (Windows), о котором вы, возможно, слышали. При помощи этого фреймворка можно выполнять профилирование процессора, определять, где находятся сборки мусора, к каким файлам приложение получает доступ, где оно обращается к диску и т.д.
Но несмотря на то, что ETW не слишком инвазивен, скорость получения результатов из него иногда может быть проблемой. Ниже я привожу пример из лог-файла, сгенерированного при помощи PerfView:
Как вы можете видеть, я собирал информацию об использовании процессора в течение 10 секунд, и в общей сложности получилось 15 МБ данных. Поэтому вряд ли вы сможете тестировать систему с помощью Event Tracing часами – объем данных будет слишком большим. Кроме того, на выполнение CLR Rundown ушло 12,7 секунд, затем еще понадобилось некоторое время на конвертирование и открытие данных (я выделил время красным). То есть, чтобы получить данные, собранные в течение 10 секунд, нужно потратить полминуты на их обработку и открытие.
Несмотря на то, что это считается достаточно неплохим результатом, мне он не очень нравится. Поэтому я лучше расскажу вам об инструментах, которые я написал сам и без которых я просто не могу жить.
Etrace (https://github.com/goldshtn/etrace) – это open source-интерфейс командной строки для ETW. Вы можете сказать ему, какие события вы хотите увидеть, и он выдаст информацию о них в реальном времени. Как только событие происходит, его можно увидеть в командной строке.
> etrace --help
…
Примеры:
etrace --clr GC --event GC/AllocationTick
etrace --kernel Process,Thread,FileIO,FileIOInit --event File/Create
etrace --file trace.etl --stats
etrace --clr GC --event GC/Start --field PID,TID,Reason[12],Type
etrace --kernel Process --event Process/start --where ImageFileName=myapp
etrace --clr GC --event GC/Start --duration 60
etrace --other Microsoft-Windows-Win32k --event QueuePostMessage
etrace --list CLR,Kernel
К примеру, вы запускаете etrace и говорите: я хочу события GC. Как только запускается такое событие, я хочу видеть его тип, причину, процесс и т.д.
Еще один инструмент, который я написал сам и хочу вам представить, – LiveStacks [3]. Он тоже имеет отношение к ETW. LiveStacks собирает трассировки стека для интересных событий (где находятся сборки мусора, где наблюдается нагрузка на процессор, к каким файлам приложение получает доступ, где оно обращается к диску и т.д.). Главное отличие LiveStacks от других подобных инструментов – вывод информации в реальном времени. Вам не нужно ждать, пока завершится обработка данных, чтобы узнать, какие процессы происходят.
Вот пример режима профилирования процессора, который используется по умолчанию. LiveStacks смотрит на процесс в Visual Studio и показывает стек вызовов в процессе, на который требуется больше всего процессорного времени.
Другой пример: на запрос «покажи мне, где запускается сборка мусора, какой стек вызовов привел к запуску сборки мусора в определенном процессе или во всей системе» LiveStacks в реальном времени выдает стек вызовов с информацией о том, где происходит сборка мусора:
Из полученных результатов LiveStacks может генерировать флейм-графы, визуализируя стеки вызовов, которые были выведены в консоли.
> LiveStacks -P JackCompiler -f > stacks.txt
ˆC
>perl flamegraph.pl stacks.txt > stacks.svg
Я использую эти инструменты, потому что они дают мне возможность получить результаты быстро, не дожидаясь обработки данных.
Когда вы строите систему, библиотеку, архитектуру для своего нового проекта, стоит заранее подумать о некоторых вещах, которые в будущем упростят проведение анализа производительности:
Пример проекта с очень хорошими средствами инструментирования – .NET на Windows, который используется многими людьми больше 10 лет. Тут есть события ETW, о которых я говорил выше, тут есть возможность захватить стеки вызовов интересных событий и преобразовать их в имена функций. И все это включено по умолчанию!
Сделать проект с такими средствами инструментирования непросто. Скажем, если посмотреть на .NET Core 2.0 для Linux, тут все не так радужно. И вовсе не потому, что в Linux нет хороших инструментов для анализа производительности, а потому что достаточно сложно построить платформу, которую было бы легко профилировать и отлаживать.
Вы хотите знать, что не так с .NET Core 1.0 для Linux? У платформы есть события, однако получить стеки вызовов невозможно, можно только узнать, что событие произошло (что гораздо менее информативно). Еще пример: чтобы преобразовать стеки вызовов для получения имен функций, нужно сделать очень много предварительных действий. Именно поэтому в документации предлагается взять ZIP-файл и открыть его в Windows (я приводил этот пример выше).
Тут все дело в приоритетах. Если вы считаете, что возможность проведения анализа производительности – важное требование к системе, которую вы разрабатываете, вы не будете выпускать что-то наподобие этого. Хотя, конечно, это лишь моя точка зрения.
Статистика и инструменты нас нередко обманывают. Вот о чем всегда нужно помнить в этом отношении:
К примеру, кто-то говорит вам, что «среднее время отклика системы равно 29 мс». Что это может означать? Например, то, что при среднем времени отклика 29 мс самое плохое значение – 50 мс или 60 мс, а самое лучшее – близко к нулю. Или это может означать, что для большинства случаев время отклика составляет 10 мс, но есть режим, в котором система работает гораздо медленнее (со временем отклика до 250 мс), и среднее значение также составляет 29 мс.
Вы видите, что два графика, демонстрирующие эти два случая с одинаковым средним временем отклика, совершенно разные. Для того чтобы понять реальную картину происходящего, недостаточно смотреть на числа, нужно смотреть на реальное распределение.
Есть отличное исследование [4], которое я нашел в Сети. Оно демонстрирует, почему никогда нельзя доверять лишь сводной статистике и всегда нужно визуализировать данные.
Авторы визуализировали 13 наборов данных с одинаковой сводной статистикой (одно и то же среднее значение x/y, одно стандартное отклонение x/y и одинаковая взаимная корреляция). Однако выглядят эти наборы данных совершенно по-разному. То есть когда вы смотрите только на числа, это не означает ничего. Вы не видите «форму» ваших данных, когда вы смотрите лишь на числа.
BenchmarkDotNet [5]– это библиотека, которую многие из вас используют. Она просто великолепна, но не показывает «форму» ваших данных (по крайней мере, по умолчанию). Когда вы запускаете ее в консоли, она выдает много чисел: средние значения, стандартные отклонения, доверительные интервалы, квартили, однако не «форму» данных. И это очень важно. Для некоторых типов анализа невозможность увидеть «форму» данных означает, что вы пропустите важные вещи.
Вот пример того, что вы можете пропустить, полагаясь на средние значения. На этом графике показано время задержки. В 99% случаев время отклика составляет чуть менее 200 мс, но можно наблюдать периодические заикания – слишком большие задержки (даже до 10 мс), возникающие в течение короткого периода времени.
И в большинстве случаев при выполнении анализа производительности просят обратить внимание именно на заикания – на то, что выше средних значений, на проблемы, с которыми иногда сталкиваются пользователи. Для того чтобы их выявить, необходимо визуализировать все точки данных, как на графике выше, или же построить распределения, как на графике ниже.
Еще одна распространенная ошибка, которую люди делают с перцентилями, – это совершение с ними математических операций. К примеру, нельзя усреднить перцентили, как это сделал мой клиент, письмо которого вы можете прочитать ниже.
Представьте, что у вас есть два сервера. Для сервера A в течение 90% времени время задержки составляет 92 мс, для сервера B в течение 90% времени время задержки – 22 мс.
Важно понимать, что вы не можете усреднить эти значения. Это неправда, что на 90% запросов ответ приходит менее чем за 57 мс. На самом деле на 90% запросов ответ приходит быстрее, чем за 68 мс.
Поэтому никогда нельзя усреднять проценты, квартили и т.д. Нужно всегда смотреть на данные и на их распространение.
Иногда можно услышать что-то наподобие: «Кому интересен 99-й перцентиль? Никто из моих пользователей даже не видит этого!» Я поясню, почему это важно, на примере страницы сайта Amazon.com. Она сделала 328 запросов. Если предположить, что все запросы независимы, какова вероятность того, что по крайней мере один из них был в 99-м перцентиле?
P = 1 – 0.99328 ~ 96%
Ответ – 96%. Поэтому очень вероятно, что при переходе на страницу Amazon.com вы получите по крайней мере один запрос в 99-м перцентиле. И если ваши пользователи получают доступ к системе, которая сравнительно сложна, то вероятность того, что с ними произойдет тот самый худший сценарий, очень высока.
Последнее, о чем я бы хотел упомянуть в рамках этой статьи, – необходимость использования специальных инструментов для систем, состоящих из большого числа машин. Вот что должны уметь такие инструменты:
Таких инструментов существует очень много. Один из примеров инструментов для мониторинга производительности приложений – Vector [6]от Netflix. На информационной панели вы можете видеть сводную статистику, но в то же время в любой момент можно кликнуть на определенный инстанс и, скажем, просмотреть процессорный флейм-граф для этого инстанса или использование ресурсов диска.
Другой пример – AMP-решение New Relic [7], которое работает в том числе и с .NET. Оно показывает вам запросы в системе и где вы проводите время, обслуживая эти запросы. И при желании можно переключиться к определенному запросу, к определенной сессии пользователя.
После того, как анализ производительности завершен, не пренебрегайте возможностью сесть и задокументировать, что было сделано. Что конкретно стоит сделать?
Из прочих докладов вот эти три вам наверняка также покажутся интересными:
Посмотреть всю программу конференции, виртуально познакомиться со спикерами и приобрести билеты можно на сайте мероприятия [13].
Автор: ARG89
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/265871
Ссылки в тексте:
[1] видеозаписи всех докладов: https://www.youtube.com/channel/UCNPwMPudMEw-gnAT4zh_UZg/playlists
[2] goldshtn: https://habrahabr.ru/users/goldshtn/
[3] LiveStacks: https://github.com/goldshtn/livestacks
[4] исследование : https://www.autodeskresearch.com/publications/samestats
[5] BenchmarkDotNet : https://github.com/dotnet/BenchmarkDotNet
[6] Vector : https://github.com/Netflix/vector
[7] New Relic: https://newrelic.com
[8] Debugging and Profiling .NET Core Apps on Linux: https://dotnext-moscow.ru/2017/msk/talks/2dfoltply8kscqeoa6444g/
[9] проведет отдельный тренинг: https://dotnext-moscow.ru/2017/msk/trainings/m4aetwwkioiwk4yaaouaq/
[10] High performance Networking in .NET Core: https://dotnext-moscow.ru/2017/msk/talks/3hcuoycrw4egcs0mkewoio/
[11] Поговорим про performance-тестирование): https://dotnext-moscow.ru/2017/msk/talks/6a4afxs2sqmwws0yuoc06s/
[12] Patterns for high-performance C#: from algorithm optimization to low-level techniques) : https://dotnext-moscow.ru/2017/msk/talks/3dzbwqxenm6eqkai4giygw/
[13] сайте мероприятия: https://dotnext-moscow.ru/
[14] Источник: https://habrahabr.ru/post/339926/
Нажмите здесь для печати.