Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений .NET Framework Добавьте счетчики производительности в свое MVC-приложение RSS

Добавьте счетчики производительности в свое MVC-приложение

Текущий рейтинг: 4 (проголосовало 2)
 Посетителей: 1429 | Просмотров: 3192 (сегодня 0)  Шрифт: - +

Работа над корпоративными веб-приложениями обычно требует написания массы дополнительного кода, помогающего вести мониторинг этих приложений. В этой статье я поясню, как я использовал фильтры Model-View-Controller (MVC) для расчистки и замены повторяющегося, запутанного кода, который был разбросан по бесчисленным методам в одном приложении.

Менеджеры по эксплуатации часто устанавливают Microsoft Operations Manager (MOM) для мониторинга работоспособности веб-сайта (или сервиса) и используют счетчики производительности для оповещения о достижении пороговых значений. Эти оповещения помогают быстро обнаруживать деградацию производительности различных частей веб-сайта.

Проблемный код

Я работаю над проектом (с применением ASP.NET MVC Framework), где требуется добавлять счетчики производительности к веб-страницам и веб-сервисам, которые должны помогать группе эксплуатации. Этой группе на каждой странице нужны счетчики Request Latency (задержка обработки запроса), Total Requests per Second (общее количество запросов в секунду) и Failure Ratio (частота отказов).

При реализации таких требований возникает впечатление, что проблемы неизбежны. Я начал с изучения текущей реализации этих счетчиков от более прилежных разработчиков. И был разочарован. Уверен, вы все бывали в таких ситуациях: вы смотрите на код и ощущаете досаду. Что я увидел? Повторяющийся код, вкрапленный в каждый метод, с небольшими изменения в именах переменных там и сям. Радости от текущей реализации я не испытал.

Код, вызвавший у меня досаду, показан на рис. 1.

Рис. 1. Код, заставивший меня покривиться

public ActionResult AccountProfileInformation()
{
  try
  {
    totalRequestsAccountInfoCounter.Increment();
    // Запуск счетчика задержек
    long startTime = Stopwatch.GetTimestamp();

    // Здесь выполняем какие-то операции
    long stopTime = Stopwatch.GetTimestamp();
    latencyAccountInfoCounter.IncrementBy((stopTime - startTime) /
      Stopwatch.Frequency);
    latencyAccountInfoBaseCounter.Increment();
  }
  catch (Exception e)
  {
    failureAccountInfoCounterCounter.Increment();
  }
  return View();
}

Глядя на этот код, я захотел удалить его из каждого метода операции в проекте. Шаблон такого типа крайне затрудняет понимание того, где находится код самого метода, поскольку он напрочь закрывается кодом мониторинга производительности. Я искал более разумный способ рефакторинга этого кода, чтобы он не замусоривал все методы операций. И выбрал для этого MVC-фильтры.

MVC-фильтры

Это собственные атрибуты, которые вы помещаете в методы операций (или в контроллеры) для добавления общей функциональности. MVC-фильтры позволяют добавлять пред- и постобработку. Список встроенных MVC-фильтров см. по ссылке bit.ly/jSaD5N. Я использовал некоторые встроенные фильтры, такие как OutputCache, но знал, что в MVC-фильтрах скрыта колоссальная мощь, которую я никогда не смог бы задействовать в полной мере (подробнее о классе FilterAttribute см. по ссылке bit.ly/kMPBYB).

Поэтому я начал размышлять: а что если мне удастся инкапсулировать всю логику этих счетчиков производительности в атрибуте фильтра MVC? И идея родилась! Я смог соблюсти требования всех ранее перечисленных счетчиков производительности со следующими операциями.

  1. Total Requests per Second (общее количество запросов в секунду):
    1. реализуем IActionFilter с двумя методами: OnActionExecuting и OnActionExecuted;
    2. увеличиваем счетчик в OnActionExecuting.
  2. Request Latency (задержка в обработке запроса):
    1. реализуем IResultFilter с двумя методами: OnResultExecuting и OnResultExecuted;
    2. запускаем таймер в OnActionExecuting и фиксируем задержку в ходе выполнения OnResultExecuted.
  3. Failure Ratio (частота отказов):
    1. Реализуем IExceptionFilter с методом OnException.

Этот процесс показан на рис. 2.

*
Увеличить

Рис. 2. Конвейер обработки MVC-фильтров

Давайте вкратце обсудим применение каждого фильтра, показанного на рис. 2.

IActionFilter OnActionExecuting (2a) выполняется до метода операции, а OnActionExecuted (2b) — после метода операции, но до обработки результата.

IResultFilter OnResultExecuting (4a) выполняется до результата операции (например, рендеринга представления), а OnResultExecuted (4b) — после результата операции.

IExceptionFilter OnException (не показан на рис. 2, чтобы не засорять общую картину) выполняется всякий раз, когда генерируется исключение (необработанное).

IAuthorizationFilter OnAuthorization (отсутствует на рис. 2и не используется в этой статье) вызывается, когда требуется авторизация.

Управление счетчиками

Однако, если я задействую эти фильтры счетчика производительности, я столкнусь с проблемой: как получать счетчик производительности (для каждой операции) в каждом из фильтров в период выполнения? Я не хотел создавать отдельный класс атрибута фильтра для каждой операции. В таком случае мне пришлось бы «зашить» имя счетчика производительности в этот атрибут. А это привело бы к взрывному росту имен классов, необходимых для реализации решения. Я решил вернуться к технологии, которую использовал в первой работе с Microsoft .NET Framework: к отражению. Механизм отражения интенсивно используется самой инфраструктурой MVC. Вы можете узнать больше об отражении по ссылке bit.ly/iPHdHz.

Моя идея заключалась в создании двух классов.

  1. WebCounterAttribute:
    1. реализует интерфейсы MVC-фильтров (IExceptionFilter, IActionFilter и IResultFilter);
    2. увеличивает счетчики, хранящиеся в WebCounterManager.
  2. WebCounterManager:
    1. реализует код отражения для загрузки атрибутов WebCounterAttribute из MVC-операции;
    2. хранит карту для ускорения поиска объектов счетчиков производительности;
    3. предоставляет методы для увеличения счетчиков, хранящихся в этой карте.

Механизм отражения интенсивно используется самой инфраструктурой MVC.

Реализация проекта

Располагая этими классами, я могу дополнять WebCounterAttribute в методах каждой операции, в которой нужно реализовать счетчики производительности, как показано ниже:

public sealed class WebCounterAttribute : FilterAttribute, IActionFilter, IExceptionFilter, IResultFilter
{
  /// Реализации интерфейсов не показаны
}

Вот пример метода операции:

[WebCounter("Contoso Site", "AccountProfileInformation")]
public ActionResult AccountProfileInformation()
{
  // Загрузка какой-либо модели
  return View();
}

Затем я могу считывать эти атрибуты в методе Application_Start, используя отражения, и создавать счетчик для каждой из операций, как показано на рис. 3. (Заметьте, что счетчики регистрируются в системе программой установки, а экземпляры счетчиков создаются в коде.)

Рис. 3. Отражение сборок

/// <summary>
/// Этот метод отражает указанную сборку (сборки) по заданному
/// пути и создает базовые операции, необходимые счетчикам
/// </summary>
/// <param name="assemblyPath"></param>
/// <param name="assemblyFilter"></param>
public void Create(string assemblyPath, string assemblyFilter)
{
  counterMap = new Dictionary<string, PerformanceCounter>();

  foreach (string assemblyName in Directory.EnumerateFileSystemEntries(
    assemblyPath, assemblyFilter))
  {
    Type[] allTypes = Assembly.LoadFrom(assemblyName).GetTypes();

    foreach (Type t in allTypes)
    {
      if (typeof(IController).IsAssignableFrom(t))
      {
        MemberInfo[] infos = Type.GetType(t.AssemblyQualifiedName).GetMembers();

        foreach (MemberInfo memberInfo in infos)
        {
          foreach (object info in memberInfo.GetCustomAttributes(
            typeof(WebCounterAttribute), true))
          {
            WebCounterAttribute webPerfCounter = info as WebCounterAttribute;
            string category = webPerfCounter.Category;
            string instance = webPerfCounter.Instance;
            // Создаем агрегированные экземпляры, если их нет
            foreach (string type in CounterTypeNames)
            {
              if (!counterMap.ContainsKey(KeyBuilder(Total, type)))
              {
                counterMap.Add(KeyBuilder(Total, type),
                  CreateInstance(category, type, Total));
              }
            }
            // Создаем счетчики производительности
            foreach (string type in CounterTypeNames)
            {
              counterMap.Add(KeyBuilder(instance, type),
                CreateInstance(category, type, instance));
            }
          }
        }
      }
    }
  }
}

Обратите внимание на важную строку, где заполняется карта:

(counterMap.Add(KeyBuilder(instance, type), CreateInstance(category, type, instance));),

Она создает сопоставление между конкретным экземпляром WebCounterAttribute операции, в том числе типом счетчика, и созданным экземпляром PerformanceCounter.

Тогда я получаю возможность написать код, который позволяет использовать это сопоставление для поиска экземпляра PerformanceCounter (и увеличивать его) для данного экземпляра WebCounterAttribute (рис. 4).

Рис. 4. WebCounterManager RecordLatency

/// <summary>
/// Записываем задержку для экземпляра с данным именем
/// </summary>
/// <param name="instance"></param>
/// <param name="latency"></param>
public void RecordLatency(string instance, long latency)
{
  if (counterMap.ContainsKey(KeyBuilder(instance,
    CounterTypeNames[(int)CounterTypes.AverageLatency]))
    && counterMap.ContainsKey(KeyBuilder(instance,
    CounterTypeNames[(int)CounterTypes.AverageLatencyBase])))
  {
    counterMap[KeyBuilder(instance,
      CounterTypeNames[(int)CounterTypes.AverageLatency])].IncrementBy(latency);
    counterMap[KeyBuilder(Total,
      CounterTypeNames[(int)CounterTypes.AverageLatency])].IncrementBy(latency);
    counterMap[KeyBuilder(instance,
      CounterTypeNames[(int)CounterTypes.AverageLatencyBase])].Increment();
    counterMap[KeyBuilder(Total,
      CounterTypeNames[(int)CounterTypes.AverageLatencyBase])].Increment();
  }
}

После этого я могу регистрировать данные счетчика производительности при выполнении этих фильтров. Например, на рис. 5 вы видите реализацию регистрации задержки.

Рис. 5. WebCounterAttribute, запускающий WebCounterManager RecordLatency

/// <summary>
/// Этот метод вызывается, когда результат был обработан
/// (это происходит перед возвратом ответа). Он регистрирует
/// задержку от начала запроса до возврата ответа.
/// </summary>
/// <param name="filterContext"></param>
public void  OnResultExecuted(ResultExecutedContext filterContext)
{
  // Stop counter for latency
  long time = Stopwatch.GetTimestamp() - startTime;
  WebCounterManager countManager = GetWebCounterManager(filterContext.HttpContext);
  if (countManager != null)
  {
    countManager.RecordLatency(Instance, time);
    ...
  }
}
private WebCounterManager GetWebCounterManager(HttpContextBase context)
{
  WebCounterManager manager =
    context.Application[WebCounterManager.WebCounterManagerApplicationKey]
    as WebCounterManager;
  return manager;
}

Вероятно, вы заметили, что в этом вызове я получаю WebCounterManager из состояния приложения (Application State). Чтобы это работало, вам нужно добавить следующий код в свой global.asax.cs:

WebCounterManager webCounterMgr = new WebCounterManager();
webCounterMgr.Create(Server.Map("~/bin"), "*.dll");
Application[WebCounterManager.WebCounterManagerApplicationKey] = webCounterMgr;

В заключение замечу, что MVC-фильтры предоставляют элегантное решение для устранения часто повторяемых шаблонов кода. Они позволяют переработать общий код, благодаря чему вам исходный код станет понятнее и легче в сопровождении. Очевидно, что нужно искать некий баланс между элегантностью и простотой реализации. В моем случае пришлось добавлять счетчики производительности примерно в 50 веб-страниц. Выигрыш явно стоит дополнительных усилий.

MVC-фильтры — отличный способ добавления поведений без вмешательства в существующий код, поэтому с чем бы вы ни имели дело — со счетчиками производительности, протоколированием или аудитом, вы найдете безграничные возможности для четкой реализации необходимой логики.

Автор: Бен Гроувер  •  Иcточник: MSDN Magazine  •  Опубликована: 24.11.2011
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:  


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.