Всем доброго времени суток. Сегодня я хочу рассказать про ASP.NET MVC MiniProfiler. А первое, что я хочу рассказать, это то, что, вопреки распространённому мнению и названию, этот профайлер вполне можно применять для отладки обычных (не MVC) ASP.NET приложений. Дальше я на примере покажу как это сделать. А пока обо всем по порядку.
Что такое MiniProfiler?
Это способ отладить ASP.NET-приложение на production (или, если вам больше нравится, “приложение, находящееся в промышленной эксплуатации”). И, что важно, не сильно влияя на производительность (то, что это работает на сайтах платформы StackExchange как бы намекает).
Вкратце процесс подключения выглядит так:
- Добавляется ссылка на dll.
- Подключается в “Master Page” или любое другое подходящее место одна строчка по инициализации UI.
- В Global.asax.cs прописываются вызовы по старту и остановке профайлера.
- Либо добавляются вызовы Step(string) там, где вам интересно, либо “сбоку” добавляются фильтры (вроде ProfilingActionFilter), чтобы не модифицировать код.
Опять же вкратце, на выходе получаем в виде небольшой разворачивающейся “закладки” на странице следующие возможности:
- Суммарное время.
- Время на дочерние действия (пункт 4) и запросы к БД.
- Показать/скрыть “тривиальные” запросы (с маленькими затратами времени).
- Переслать ссылку на отчет (данные хранятся в кэше).
Заинтересовались? Тогда продолжим…
Варианты установки и совместимость с Entity Framework
Есть два варианта установки – через NuGet или с Github. В первом варианте вы получаете более простую установку, во втором – потенциально более свежую версию. Правда, если вы хотите при этом отслеживать запросы в Entity Framework, да еще и с Sql Server Compact Edition, как пришлось сделать мне при подготовке доклада для встречи User Group, то жизнь становится намного интереснее.
Вероятно вы в курсе того, что есть, скажем так, некоторые проблемы с обратной совместимостью в Entity Framework 4.1 Update, касающиеся как раз возможности профилирования. А update этот, с другой стороны, полезен из-за проблем с производительностью в 4.1
Поэтому, если вы не используете Entity Framework 4.1 Update, для профилирования запросов Entity Framework достаточно в Package Manager Console выполнить:
PM> Install-Package MiniProfiler
PM> Install-Package MiniProfiler.EF
… и дальше эту главу можно не читать :)
В моем же случае пришлось установить BETA-версию EF 4.2, суммарно все действия по установке выглядели так:
PM> Install-Package SqlServerCompact
PM> Install-Package EntityFramework.SqlServerCompact
PM> Install-Package EntityFramework.Preview
PM> Install-Package MiniProfiler
PM> Install-Package MiniProfiler.EF
На странице проекта описаны обходные пути решения проблемы и для этого стоит скачать последнюю версию с Github и попробовать воспользоваться этими советами. Лично мне в моей конфигурации не помогло.
Еще один довод в пользу варианта с Github – большее количество исправленных багов (позже упомяну один). Также в этой версии есть и стандартные реализации для MVC-фильтров, в версии с NuGet не проверял, потому что в результате оставил с Github.
Подключение к проекту
Рассмотрим сначала вариант подключения к обычному ASP.NET проекту (в конце статьи я дам ссылку на архив с примером). После подключения (любым из указанным выше способов) нужных dll подключаем скрипты для UI в Master Page:
<%= MiniProfiler.RenderIncludes().ToString() %>
Затем в Global.asax.cs прописываются вызовы по старту и остановке профайлера а также подключение к Entity Framework, если это необходимо:
using MvcMiniProfiler;
...
protected void Application_BeginRequest()
{
if (AllowProfiling)
MiniProfiler.Start();
}
protected void Application_EndRequest()
{
if (AllowProfiling)
MiniProfiler.Stop();
}
private bool AllowProfiling
{
get { return Request.IsLocal; }
}
private void Application_Start(object sender, EventArgs e)
{
MiniProfilerEF.Initialize(false);
}
Естественно, это самый простой способ – отображать информацию только в случае запросов к localhost. Для отладки на production “по-живому” потребуется немного модифицировать код с учетом того, что выводить результаты профилирования нужно только для определённых пользователей:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if(!CurrentUserIsAllowedToSeeProfiler())
{
MvcMiniProfiler.MiniProfiler.Stop(discardResults: true);
}
}
Важно: сразу хочу сказать, что если вы воспользуетесь этим подходом, то вам на данный момент стоит однозначно установить версию с Github, из-за недавно исправленного бага.
После этого, как я уже говорил, можно добавить дополнительные фильтры для получения информации по вызовам методов контроллеров. Также может быть полезно увидеть время поиска представления (Find) и время, затраченное на его создание (Render).
Внешний вид
Давайте посмотрим, что у нас получилось в итоге. Сначала мы видим одну небольшую закладку. Забегая вперед скажу, что при отладке ajax-вызовов эти закладки начинают очень быстро плодиться, убегая вниз :)
По щелчку на ней можно увидеть краткую информацию по вызовам. Разумеется, если вы сейчас вплотную занимаетесь профилированием и добавили много вызовов вида MiniProfiler.Current.Step(“Проблема здесь быть может”); то список будет больше. Список также увеличится, если подключена информация по представлениям и используется много partial view.
Кстати, вы сразу можете увидеть, что в первый раз представление ищется значительно медленнее (причина в обработке исключений при поиске в нескольких папках).
Важно: иногда вы можете наблюдать некоторые расхождения в цифрах – кажется, что местами время “пропадает”. Чаще всего это лечится следующими способами или их комбинацией:
- Выводим и обращаем внимание на столбец “with children”.
- Вспоминаем, что время фиксируется не на каждую строчку кода, поэтому местами вполне естественно будут пробелы.
- Вспоминаем, что время могут “отбирать” параллельно запущенные процессы.
Обратите внимание – у нас есть пара запросов из Entity Framework, посмотрим?
Еще одна приятная возможность – выделение повторяющихся запросов. Вообще-то не очень нужная вещь – мы ведь не допускаем таких глупых ошибок? :) А если серьезно, то бывает полезно для обнаружения методов, данные которых полезно кэшировать (пример – запрос привилегий из БД). Показывать отдельные картинки не буду – поверьте мне на слово, вы заметите эти запросы пользуясь MiniProfiler’ом.
И, как я уже говорил, всегда можно открыть/переслать коллеге ссылку на отчет, который выглядит так:
Как я уже говорил, по умолчанию отчет хранится в кэше (один день). Это позволяет не мудрить с записью на диск или в БД, а если все это будет происходить на production и серверу не будет хватать ресурсов – то выкидывание из кэша этого отчета я считаю вполне оправданным. Далее я расскажу, как сохранять его в базу данных
Настройки
Настройки условно делятся на те, которые есть “из коробки” и те, которые иногда имеет смысл добавить самостоятельно.
Стандартные настройки можно использовать с помощью MiniProfiler.Settings, пример использования можно посмотреть на сайте проекта в Sample.Mvc/Global.asax.cs. Альтернативный вариант – параметры при вызове RenderIncludes.
Наиболее интересные настройки перечислю здесь:
- Storage – позволяет задавать хранилище для отчетов
- PopupRenderPosition – размещение закладок (слева/справа)
- PopupMaxTracesToShow – максимальное количество закладок на экране
- Exclude… – игнорирование сборок, классов и методов
- Results_Authorize – дополнительная проверка, чтобы снизить вероятность попадания отладочной информации не в те руки…
Теперь перейдем к тому, что может потребоваться сделать самостоятельно.
Во-первых, вы можете написать свои глобальные фильтры и/или обертки для профилирования критичный участков кода. Пример можно посмотреть в исходниках.
Во-вторых, вероятно, может потребоваться для пользователей с включенным режимом профилирования делать отступ слева/справа у основной части приложения, чтобы закладки не мешали что-либо видеть. Я попробовал добавить через jQuery прозрачности – результат не понравился, после чего просто сделал отступ. Уточню – в моём случае было достаточно много ajax-вызовов и, как следствие, много закладок (причём большинство из них интересны). Единичная же закладка мешает значительно меньше.
В-третьих, есть небольшие баги, которые в принципе легко обходятся (по крайней мере те, которые мне встречались). Например, у меня использовались всплывающие окна, пропадающие по клику на любое место страницы. А в MiniProfiler в тот же момент сидел баг в виде вызова метода jQuery .click. Наблюдалась весёлая картина, когда после щелчка по индикатору появлялись детали и тут же пропадали :) Если кому-то будет интересно, могу написать как сделать временный workaround на jQuery.
Профилирование первоначальной загрузки приложения
Как вы вероятно знаете, на Application_Start текущий HttpContext недоступен, а MiniProfiler его использует. Поэтому в реальном проекте для профилирования Bootstrapper’а (у нас так принято называть класс, отвечающий за инициализацию приложение, а в Application_Start он просто вызывается) пришлось немного допилить код.
Вообще-то решение получилось не совсем красивым, поэтому я добавил в web.config настройку по его включению/отключению. В Global.asax.cs я добавил следующий класс:
private static class BootstrapperOnFirstRequest
{
private static volatile bool _initializedAlready;
private static readonly Object _lock = new Object();
public static void Initialize()
{
lock (_lock)
{
if (_initializedAlready)
return;
Bootstrapper.Initialize();
_initializedAlready = true;
}
}
}
После этого осталось модифицировать Application_BeginRequest:
MiniProfiler.Start(ProfileLevel.Verbose);
if (AllowBootstrapperProfile)
BootstrapperOnFirstRequest.Initialize();
После этого можно после рестарта приложения точно также наглядно видеть на что тратится время при инициализации. Я просто “засеял” этот метод вызовами метода Step.
Резюме
Если у вас есть достаточно серьезное ASP.NET приложение, а вы до сих пор не пользуетесь MiniProfiler или другим его аналогом – я бы на вашем месте срочно исправлялся.
Как показывает практика, после первого профилирования в проекте сразу появляется пара-тройка багов или запросов на улучшение. А основное, конечно, это поиск проблемных мест с точки зрения производительности. И, еще раз подчеркну – решение очень хорошо приспособлено для работы на production.
По большому счёту, я согласен со словами автора на странице проекта:
Simple. Fast. Pragmatic. Useful.
P.S. Если кому-то интересен исходный код примера, который использовался при написании этой статьи, я выложил его в Google Docs. Единственное “но” – удалил папку packages, чтобы не раздувать размер. Думаю, её несложно будет восстановить, создав новый проект и скопировав эту папку оттуда, а потом установив пакеты, перечисленные в главе “варианты установки”.
Если у вас есть замечания, пожелания или новые темы – пишите в комментариях или на olegaxenow.reformal.ru. Постараюсь учесть.
Спасибо за статью, пока не пробовал, но полезно.
ОтветитьУдалитьСпасибо за статью. Информативно!
ОтветитьУдалитьДавно уже использую на проде. Весь код уже покрыт шагами профайлера. Перфоманс с тех пор улучшили кардинально.
ОтветитьУдалитьДописал чтобы еще показывал сколько памяти использует приложение. И чтобы работал в классическом режиме IIS под WebForms. Раньше не работало, не знаю как сейчас.
To @Анонимные: всегда пожалуйста!
ОтветитьУдалитьTo @Nikolay Sergeev:
Правильный success story :)
Классический режим c WebForms не использую, поэтому не интересовался.
P.S. А остальным хочу сказать, что даже без "покрытия всего кода шагами" (это уже fine-tuning) профайлера он очень полезен.