the mine universe

среда, 14 октября 2009 г.

SQL Server Profiler Library

Неделю назад я рассказывал как можно без побочных эффектов использовать SQL-сервер в true unit-tests. Теперь я расскажу где использовал предыдущий пост. Сегодня предлагаю интегрировать в свои продукты средства трассировки и профилирования встроенные в SQL-сервер. И в MSDE 2000, и в SQL Express 2005/2005 в том числе.

Когда речь идет о профилировании часто самое сложное - найти бутылочное горлышко. Хуже всего то, что как правило в среде разработки и тестирования система ведет себя совершенно не так в production environment. Мне в случае проблем с производительностью у какого нибудь заказчика приходится "отмазываться" простым вопросом, на который закзачик ответить толком не может. Я прошу описать в цифрах "профиль загрузки системы". Заказчик на какое-то время впадает в ступор, тем не менее проблема остается - а это не очень приятно.

Могу так же подтвердить из своего опыта, что проблема поиска бутылочного горлышка случается не только с тиражируемыми коробочными продуктами, но и с inhouse-системами (внутренняя самостоятельная разработка для одной компании)

Раньше я рассказывал как бороться с трафиком в на платформе WCF. Сегодня мой подопытный - IOPS. IOPS всегда были бутылочным горлышком в большинстве случаев. Понятно что диск намного медленнее работает чем память и CPU. Хуже другое:

  • Масштабирование диска существенно дороже масштабирования RAM и CPU. Для клиент-серверных системы CPU и память наращиваются совсем просто – добавлением в кластер сервера приложений еще одного узла.
  • Индустрия и технология производства дисков уверенно хранит показатель IOPS примерно на уровне 10-ти летней давности. Да, емкость и скорость последовательного чтения выросли на два порядка. А сколько вырос IOPS? В три раза? Всего-то :( Конечно последнее справедливо если закрыть глаза на новые SSD накопители с многоканальным контроллером памяти, например Intel X25 G2.

КПРР и структура журнала трассировки

  • Текст sql-команды и со значениями всех параметров. Если бы я сейчас рассказывал презентацию, а не щелкал по клавишам, сейчас бы сказал “WOW! Ух-ты!”. Значения параметров сюда попадают ав-то-ма-ти-чес-ки.
  • Идентификаторы подключения к SQL-серверу
  • Количественные показатели использованных ресурсов: CPU, Duration, Reads, Writes. Вокруг них проходят любые занятия профилированием.

Любые поля журнала можно отключать за ненадобностью, но в библиотеке не предусмотрено отключения “Количественных показателей использованных ресурсов”. Здесь и далее выражение в кавычках - КПРР. Любые поля журнала трассировки можно и нужно отключать в зависимости от сценария использования за ненадобностью.

Идентификация целевой области наблюдения

Сессия трассировки без фильтрации включает все команды всех приложений, исполненных sql сервером за время сеанса трассировки. Так устроен MS SQL Server. Это плохо, хотя иногда и хорошо. Но филтьтр по контексту нагрузки на SQL Server все же есть. И это безулословно хорошая фича. И "на стороне" SQL сервера и на клиенте есть хорошая возможность фильтровать команды на “свои” и “чужие”. Вот параметры фильтра:

  • Application Name (определяется строкой подключения к серверу)
  • Database (определяется по строке подключения к серверу, фактическая БД с которой работает запрос, к этому полю отношения имеет мало)
  • Client Host (обычно на клиенте эта строка равна Environment.MachineName)
  • Client Process (на клиенте доступно с помощью Process.GetCurrentProcess().Id)
  • Login (определяется строкой подключения к серверу)
  • Server Process (на клиенте доступно в виде функции @@spid)

Сценарии использования

  • Получить суммы КПРР, сгруппированные по типам удаленных вызовов? Одна сессия трассировки на всю систему. В ApplicationName включить имя action wcf-метода (или Request.Uri web-приложения).
  • Получить полную подробную трассировку на каждый экземпляр удаленного вызова? На каждый unit of work отдельная сессия трассировки.
  • Получить распределение загрузки ресурсов SQL сервера между своим продуктом и сторонними приложениями? Пожалуйста - единственная сессия трассирови на всю систему. В ApplicationName уникальное и хорошо известный идентификатор своего продукта|системы.
  • Получить трассировку на каждую SQL команду? Одна сессия трассировки на каждую команду.

Последний сценарий – самый простой. Последний сценарий дает самый подробный отчет. Почему же нужны еще другие сценарии? Ответ – производительность.

Отчет по журналу трассировки

Раз я заговорил об трасисровке на продакшене, то и в библиотеке реализовал три способа вычитывания отчета трассировки:

  • Детальный, как в SQL Server Profiler. Самый полный отчет - весь журнал вычитывается строчка за строчкой.
  • Суммы по группам. "КПРР" суммируются по интересным группам.
  • Тотальная сумма "КПРР" - количество запросов и четыре суммы использованных ресурсов

Производительность

Я взял в качестве подопытного кода самый легкий запрос: SELECT Null

  • Первая колонка – кол-во конкурентных потоков.
  • Commands – кол0во выполненных команд
  • Duration – время затраченное на выполнение. Оно включает старт и стоп сессий трассировки и не включает время на создание и уничтожение конкурентных потоков.
  • CPU – процессорное время использованное на “клиенте”.

Потребление ресурсов самого SQL-сервера здесь не интересно. Интересно другое - я накткулся на форуме на интересный термин профилирования: Оказывается можно различать серверную трассировку, она же real server side trace. И клиентскую трассировку. Под клиентской чувак Tibor (MVP) понимает сеанс трассировки выполняемый под SQL Server Profiler с записью журнала в файл или БД. Вот эта библиотека, о которой я сейчас рассказываю - это real server side tracing.

Два важных вывода:

Сама по себе трассировка  на производительность влияет незначительно. А вот сессия трассировки неприлично тормозит на два порядка в сценарии одна сессия трассировки на один запрос. Но это всего лишь пол беды. Хуже то что пуск и остановка сессии трассировки не масштабируется по ядрам процессора.

Использование RAM-диска драматически улучшает производительность инстанцирования сессии трассировки. Хотя масштабирование по ядрам процессора по прежнему отсутствует.

Дисклаймер юнит-тестов

Тесты проверяют только библиотеку, но не SQL сервер и не трассировку. Поэтому в тестах всего два ценных сценария:

  • Ловится вызов хранимки. в unit-тесте проверяется что захвачена именно sp_executesql с нашим кодом. и код И вызов хранимой процедуры и любая команда с параметрами попадают в журнал трассировки.
  • Ловится batch-код.

понедельник, 5 октября 2009 г.

The true about a true unit-testing of Data Access.

Что здесь подразумеваю под true unit testing? То же что и всегда:

  • В любом окружении результаты теста одинаковы. И в окружении любого из разработчиков, и на билдовом сервере.
  • Тесты, исполняемые конкурентно, не мешают друг другу.
  • Результат теста не зависит от параллельно исполняемых тестов.
  • Повторное исполнение теста возвращает такой же результат.

Любой [Test], не попадающий в это хорошо известное определение, true unit тестом не является. Я с этом полностью согласен. Какие следствия напрашиваются применительно к тестированию доступа к данным и бизнес-логики с зависимостью от MS SQL Сервера:

  • Каждый экземпляр теста исполняется в своей песочнице. В нашем случае у каждого экземпляра теста своя уникальная БД.
  • Вне зависимости от успешности прохождения тестов ресурсы должны быть освобождены, а изменения должны быть откатаны обратно. В нашем случае каждый экземпляр теста по окончании работы уничтожает свою рабочую БД.

В итоге имеет две проблемы:

  1. Выбрать подходящий экземпляр SQL Server. И у разработчиков, и на билдовом сервере имеет место быть зоопарк версий и имен экземпляров SQL серверов. Теоретически можно принудить каждого внутри команды «поднять» экземпляр SQL-сервера с одинаковым именем для работы unit-тестов. Но за пределами команды, в тех же открытых проектах, принуждение маловероятное.
  2. Уничтожить рабочую БД. Drop database было бы элементарным решением проблемы освобождения ресурсов, если бы не дефолтовый пулинг в SQL-сервере. Пулинг оставляет открытые соединения с БД – из-за этого Drop database без дополнительных приседаний падает.

Другие проблемы, мешающие воплотить в unit-тестах слоя доступа к данным true-принципы, мне неизвестны.

Voilà

Хочу поделится своей несложным окружением для unit-тестирования доступа к данным и любого кода работы с БД SQL сервер:

  • AnySqlServer – подходящий экземпляр SQL Server
  • DB – уникальное имя БД-песочницы
  • MasterConnectionString – строка подключения к экземпляру SQL-сервера
  • DbConnectionString – строка подключения к БД-песочнице
  • SetUp() – создать БД-песочницу
  • TearDown() – учичтожить БД-песочницу, если остаются незакрытые соедния к БД-песочнице, например соединения в пуле, то подключения обрываются.
  1: static class TestEnvironment
  2: {
  3:     static TestEnvironment()
  4:     {
  5:         var servers = SqlServiceInfo.Get(TimeSpan.FromSeconds(10));
  6:         foreach (var sqlInstance in servers.Instances)
  7:             if (sqlInstance.Status == ServiceControllerStatus.Running)
  8:                 if (sqlInstance.Description != null)
  9:                     if (!sqlInstance.IsExpress)
 10:                         if (SqlServerUtils.IsAdmin(sqlInstance.FullLocalName))
 11:                             AnySqlServer = sqlInstance.FullLocalName;
 12:     }
 13: 
 14:     public static readonly string AnySqlServer;
 15: 
 16:     public static readonly string DB = 
 17:         "UNITEST_" + Guid.NewGuid().ToString("N");
 18: 
 19:     public static string TracePath = 
 20:         Environment.SystemDirectory.Substring(0,2) 
 21:         + @"\\temp\\traces";
 22: 
 23:     public static readonly string WorkingAppicationName = 
 24:         "SqlTrace unit-test";
 25: 
 26:     public static string MasterConnectionString
 27:     {
 28:         get
 29:         {
 30:             return 
 31:                 "Application Name=SQL Unit Testing framework;" 
 32:                 + "Integrated Security=SSPI;" 
 33:                 + "Data Source=" + AnySqlServer + ";" 
 34:                 + "Pooling=false;";
 35:         }
 36:     }
 37: 
 38:     public static string DbConnectionString
 39:     {
 40:         get
 41:         {
 42:             return 
 43:                 "Application Name=" + WorkingAppicationName + ";" + 
 44:                 "Integrated Security=SSPI;" 
 45:                 + "Data Source=" + AnySqlServer + ";" 
 46:                 + "Pooling=true;"
 47:                 + "Initial Catalog=" + DB + ";"
 48:                 + "Max Pool Size=300";
 49:         }
 50:     }
 51: 
 52:     public static void SetUp()
 53:     {
 54:         Trace.WriteLine(
 55:             "Working SQL Server instance is " + AnySqlServer);
 56: 
 57:         using (var con = new SqlConnection(MasterConnectionString))
 58:         {
 59:             con.Open();
 60: 
 61:             var sql = "Create Database [" + DB + "]";
 62:             using (var cmd = new SqlCommand(sql, con))
 63:             {
 64:                 cmd.ExecuteNonQuery();
 65:             }
 66:         }
 67:     }
 68: 
 69:     public static void TearDown()
 70:     {
 71:         try
 72:         {
 73:             List<SqlServerUtils.ConnectionInfo> connections;
 74:             using (var con = new SqlConnection(MasterConnectionString))
 75:             {
 76:                 connections =
 77:                     SqlServerUtils.GetConnections(con)
 78:                         .FindAll(info => info.Database == DB && info.Spid > 50);
 79:             }
 80: 
 81:             SqlServerUtils.KillConnections(MasterConnectionString, connections);
 82: 
 83:             using (var con = new SqlConnection(MasterConnectionString))
 84:             using (var cmd = new SqlCommand("Drop Database " + DB, con))
 85:             {
 86:                 con.Open();
 87:                 cmd.ExecuteNonQuery();
 88:             }
 89:         }
 90:         catch(Exception ex)
 91:         {
 92:             Trace.WriteLine(
 93:                 "Failed to teardown unit test" + Environment.NewLine + ex);
 94:         }
 95:     }
 96: }

Подброр подходящего сервиса

Хочу отдельно, построчно, остановится на отборе подходящего экземпляра SQL-сервера:

  • строка 7: Экземпляр windows-сервиса сервера должен быть в состоянии запущен :)
  • строка 8: windows-аккаунт, под которым исполняется код (тест-кейс), должен иметь минимальные права на подключение к sql-серверу. Здесь Description - это значение функции @@version
  • строка 9: MSDE 2000, SQL Express 2005/2008 не подходят - такой вот тест
  • строка 10: windows-аккаунт, под которым исполняется код (тест-кейс), должен входить в группу sysadmin сервера

В вледующей серии - профилирование т.н. слоя доступа к данным в хранилище MS SQL Server.

Как работает SqlServiceInfo.Get(…)

  1. В реестре, в строгом соответствии с официальной документацией Books Online выискиваются все описания сервисов всех экземпляров SQL Server: File Locations for Default and Named Instances of SQL Server
  2. Далее тестируется каждый кандидат в своем рабочем потоке средствами пула потоков. Каждый кандидат проверяется на то что это зарегистрированный Win32-сервис Database Engine
  3. У каждого экземпляра Database Engine определяется версия исполнительного файла и статус win32-сервиса
  4. Каждый экземпляр тестируется на доступ с минимальными правами при подключении с помощью ADO.NET.

вторник, 4 августа 2009 г.

Будьте очень осторожны! Unity application block

Почти сразу после публикации своей заметки о сложностях с Unity, мне попался пост Ивана (Merle) об func – такая другая реализация IoC и DI концепции с поинтом на производительность и совместимость с Compact Framework. В нем – теже лямбды в регистрации. Помимо сложностей с добавлением новых реализаций «Lifetime Manager», позволю себе обратить внимание на менее очевидные трудности в Unity.

Не буду писать про недоразумение с RegisterInstance и PerThreadLifetimeManager. Очень кратко опишу концептуальные джоки (jokes), которые меня вводят в ступор после тсчательного знакомства с Unity:

  • Named Resolving - это самый верный способ запутать себя, коллег, и пользователей вашей библиотеки. Почему? Вспомним что такое Unity прежде всего - IoC и DI. Метод Resolve, во-первых, возвращает экземпляр определенного поведения. Этот экземпляр, прежде всего, исполняет некую роль в приложении, в системе, в библиотеке. Не побоюсь сделать на этом акцент - два вызова Resolve с одинаковыми аргументами должны возвращать две ссылки на экземпляры с одинаковым поведением. А что значит в таком свете Named Instances - это значит, что Resolve возвращает экземпляр как бы другого поведения!!! Т.е. Resolve<IService>("foo") и Resolve<IService>("bar") возвращают экземпляры IService разного поведения. Пока все хорошо? Ничуть! Если вы следите за моей мыслью, у вас должен возникнуть вопрос – «С какого перепуга параметризация поведения должна описываться единственной строкой?» Ни-с-ка-ко-го!!! Очень редко поведение можно параметризировать (ну и слово) строкой. Для пользователя вашей системы или библиотеки будет понятней, когда параметры будут явно описаны где-то в интерфейсе IService - будет видно и от каких именно параметров, и что именно зависит от параметров.

  • Injection на рефлексии. Ctrl-Shift-G в студии не покажет buildup-зависимости, спрятанные с помощью Unity. Unity придется подождать, пока Re# и VS научатся разглядывать Unity.

  • Circular references полностью на откупе гадалкам. Если вам повезло, и вы никогда-никогда не сталкивались с кошмаром StackOverflowException, боюсь мне будет трудно объяснить, почему же последний пункт не менее важен, чем все остальные выше.

пятница, 10 июля 2009 г.

Будьте осторожны: Unity Application Block

Столько шума и публикаций вокруг Unity не оставили меня равнодушным. Мне лично захотелось поближе познакомиться с Unity, когда я узнал что в Unity 1.2 появился Per Thread Storage Lifetime Manager. Подумать только! Для каждого потока возвращается отдельный экземпляр в методе Resolve. Не так давно коллега прислал ссылку на Unity без коментариев – дескать в продукте надобы использовать его вместо Dictionary<Type, object>, который выполняет роль контейнера зарегистрированных сервисов. Пришлось познакомиться с Unity поближе. И мое терпение лопнуло. Из моего поста вы узнаете, что Unity – это плохой, неудачный дизайн, его сложно использовать в реальных ситуациях, и сам Application Block “плохо пахнет”.

Для начала – почему Unity это over-design. Ответ - анонимный делегат. Да! Проще всего сконфигурировать контейнер и инвертировать зависимости с помощью анониного делегата в методе Register. Сразу отпадает необходимость в развесистой и неудобной иаерархии с красивым названием “Lifetime Managers”. А вместе с ней "в топку" отправим и другую избушку на курьих ножках (e.g. кривая, неудобная реализация) вокруг концепции "Injection of Contructor, Properties и Initializers".

Transient Lifetime Manager

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        return new SampleClass();
    });

Singleton (aka RegisterInstance)

SampleClass instance = new SampleClass();
ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        return instance;
    });

Injection of Contructors, Properties and Initialisers

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        SampleClass ret = new SampleClass(12, "Bye-bye, Unity!");
        ret.MyProperty = new object();
        ret.MyStringProperty = "Some Text";
        ret.InitializeMe(42.0m, container.Resolve<ILogger>());
        return ret;
    });

Для всех кто не читает справку приведу пример из справки Unity. Почувствуйте разницу:

IUnityContainer myContainer = new UnityContainer();
myContainer.Configure<InjectedMembers>()
  .ConfigureInjectionFor<MyObject>(
    new InjectionConstructor(12, "Hello Unity!"),
    new InjectionProperty("MyProperty"),
    new InjectionProperty("MyStringProperty", "SomeText"),
    new InjectionMethod("InitializeMe", 42.0, 
            new ResolvedParameter(typeof(ILogger), "SpecialLogger"))
  );

PerThreadLifetimeManager

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        const string key = "Sample Interface Implementation";
        LocalDataStoreSlot slot = Thread.GetNamedDataSlot(key);
        SampleClass ret = (SampleClass)Thread.GetData(slot);
        if (ret == null)
        {
            ret = new SampleClass();
            Thread.SetData(slot, ret);
        }
        return ret;
    });

TimeLimitedLifetimeManager

И в заключении – немного о том почему Unity сложен в использовании. Здесь я приведу очень древний пример реализации конецепции Lifetime Manager. Реализация древняя настолько, насколько древний сам .Net. Я пользуюсь реализацией ниже со времен появления .Net в 2002-м году. Для тех, кто не знает, напомню – Unity вышел в 2008 году. Сама концепция описывается одним простым предложением – Объекты “живут” в контейнере строго ограниченное время. Т.е. по истечении определенного времени объект из контейнера освобождается. Итак – сама реализация:

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        const string key = "Sample Interface Implementation";
        SampleClass ret = (SampleClass)HttpRuntime.Cache.Get(key);
        if (ret == null)
        {
            ret = new SampleClass();
            HttpRuntime.Cache.Insert(
                key,
                ret,
                null,
                DateTime.Now.AddMinutes(60),
                Cache.NoSlidingExpiration);
        }
        return ret;
    });

Дополнение: Lazy Singleton Lifetime Manager

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

 ConfigurationContainer container = new ConfigurationContainer();
 container.Register<ISampleInterface>(
     delegate
     {
         return Lazy.Get(
             "Sample Interface Implementation",
             () => new SampleClass(container.Resolve<IFoo>(),"2");
             );
     });

Продолжение

пятница, 15 мая 2009 г.

Продолжение Фиговины №10 старого голубятника

Почему именно провокацию, сейчас будет ответ. Старый голубятник, оценив хоть что нибудь по имени спикеры, на свой мессадж потратил все силы что бы удивиться бестолковой “платформе” Symbian OS 9 внутри топового из топовых телефонов от Nokia. Дескать какой дурилка будет в N96, якобы топовом тлефоне от производителя №1, набирать на 9-ти кнопках документы ms office, да еще платить за это деньги. Мысль могла бы быть вполне разумная, если бы не но. Неужели Nokia встраивает в свои топовые телефоны Symbian OS 9 только ради продажи редактора ms office документов? Этого, хотя бы намека, я так и не увидел. Напротив уважаемый Сергей источает непоколебимую уверенность и даже не оставляет намеков “читателю” погуглить, что же такого производитель телефонов №1 нашел в Symian OS 9.x и почему он именно эту платформу встраивает в свои топовые телефоны?

Email-клиент

  • Просмотр текстовых и html-писем
  • Просмотр вложенных документов office и PDF, бесплатно и без покупок и скачиваний чегобы то нибыло
  • Интунативный переход по ссылкам: и по телефонным и по интернет-адресам

Яндекс-карты

  • Пробки,
  • Поиск местоположения по базовым станциям,
  • Поиск адреса на русском языке,
  • Offline-карты

Nokia Maps

  • Карта всего мира
  • Интуитивная загрузка offline-карт

вторник, 28 апреля 2009 г.

Никогда не планируйте Unit-тестов

Question: Do I need a Unit test or Functional test?
Answer: Yes
(источник)

Без юнит-тестов никак не обойтись и в этом заголовке шутка. На мой пост меня натолкнула заметка Сергея о Как тестировать приватные функции. У него можно найти бесспорный ответ на вопрос как: "Только через публичный контракт этого класса"

Можно ли обойтись без юнит-тестов? Чаще можно ответить да чем нет. Что бы понять ответ достаточно определить о каком коде идет речь:

  • Или бизнес-код
  • Или инфрастуктура (framework)

Бизнес-код,
прикладной код

Библиотека,
инфрастуктура

Количество зависимого кода

Определено и ограничено

Неопределено и недоступно

Доступность зависимого кода

Весь код в одном VS-решение

Недоступен

Окружение

Известное и ограниченное - определенный продукт или inhouse-система

Неограниченное, неопределенное множество. Т.е. зависимый код будет когда-то кем-то написан

Тестирование

функциональные тесты с помощью инженера по качеству

Юнит-тесты

Источник знаний о коде

Зависимый код

Юнит-тесты

Текучесть требований

Высокая изменчивость требований. Модификация кода в соответсвии с изменчивостью требований происходит легко поскольку вся система доступна в коде и любые изменения легко отслеживаются в зависимом коде.

Теоретически изменения возможны только в одном случае: не требуется модификация зависимого кода. Практически новые фичи инфрастуктуры выпускаются новой версией, новым релизом.

Верификация результатов рефакторинга.

Компиляция кода, регресионные тесты инженером по качеству.

Регрессионные юнит-тесты

Ну таблица, и что дальше? А дальше - цитата. Brad Abram в одном из своих постов об обновлении Framework Design Guidelines цитирует Фила, разработчика ASP.NET MVC: "Well-Designed Frameworks Are Testable." Мне так и не удалось найти ответ почему?, без которого любой пост становится похож на догму. Но ведь Фил прав! Попробую ответить за Бреда, почему Ваш framework становится хорошим, когда он легко тестируется (unit-тестами):

  • Вы всегда знаете, как расширить вашу инфрастуктуру
  • Вы всегда уверены в том, как поступить с новыми требованиями к вашей инфрастуктуре
  • Вы легко можете научить пользоваться вашей инрастуктурой ваших коллег
  • Вы всегда можете дать ответ на пригодность вашей инфрастуктуры в новом окружении

В защиту заголовка:

  • Не бывает вопроса как протестировать код
  • Не бывает вопроса нужно ли тестировать этот код?
  • Не бывает вопроса зачем тестировать код?
На все вопросы ответ известен раньше чем будет написана первая строчка кода - см. выше

пятница, 24 апреля 2009 г.

Профилирование WCF Трафика

Пролог

Память, процессор, сеть и диск. Вот они четыре системных ресурса без которых ни одно приложение жить не может:

К этой четверке я бы добавил энергопотребление. Кто знает, возможно со временем в блоки питания будут встраивать счетчики энергопотребления и Windows будет считать микро- и нано-ватты так же точно как и четверку выше. Если вдруг это случится возможно моя заметка окажется одной их первых о мониторинге и профилировании энергопотребления :)

Для ЦП и памяти есть JetBrains, Для дисковой очереди - MS SQL Express и старше. Для трафика - ни-че-го :(

- А зачем задумываться над количеством трафика?, резонно звучит легкомысленный вопрос,
- Гигабитная копеешная сеть легко справится с трафком большим, чем может обработать моя система N! Нечего трафик профилировать!

Я тоже так думал, пока однажды техдир не предложил мне исправить очень медленную работ системы, в разработке которо я учавствую. Оказалось, что клиент связал компоненты системы по интернету на платном канале толщиной всего 2Mбит!

Traffic Statisic Behavior

Профилирование трафика критически необходимо для случаев медленного либо дорогого трафика. Т.е. необходимость профилирования трафика между компонентами возникает в двух случаях:

  • Клиенты используют платные каналы связи между компонентами системы и активно требуют минимизации расходов на трафик
  • Клиенты используют медленные каналы связи и выражают недовольство производительностью на медленных каналах связи.

Мне хотелось добиться максимальной изоляции клиентского кода от инфраструктуры, реализующей удаленные вызовы. Необходимость вызвана не простой прихотью, а потребностью миграции на WCF от Remoting продукта, в разработке которого я участвую. В нем используется Sink на сервере, который на каждый удаленный вызов вызывает делегат и передает в нем:

  • Тип серверного объекта и имя метода
  • Кол-во переданных и полученных байт

Реализация делегата накапливает сумму трафика и группирует по типам и методам удаленных вызовов.

Behavior, о котором сейчас идет речь, реализует ту же концепцию для WCF. Небольшое отличие заключается в идентификации удаленного вызова. Вместо типа и метода используется имя Action. Вся работа behavior заключается в вызове делегата на каждую пару запрос-ответ:

delegate void Handler(string action, long requests, long recieved, long sent);

Для облегчения такой рутины библиотека включает класс TrafficStatistic, реализующий кандидат на обработчик делегата:

public void Merge(string action, long requests, long recieved, long sent)

Сколько трафика стоит определенная работа?

Этот вопрос – это начало, побудительный мотив, движущая сила и определяющий business value профилирования. На основе ответа становится легко доступным два простых в реализации шага:

  1. Поиск бутылочного горлышка - самого «тяжелого» запроса, или запросов
  2. Рефакторинг клиентской и или серверной стороны рядом с бутылочным горлышком с целью снижения трафика

Обычно рефакторинг не требуется. Концептуальной перестройки логики мне не потребовалось ни разу. Меня всегда выручало два способа которыми сейчас поделюсь:

  • Кэширование нужных данных
  • Удаление ненужных из сообщений

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

Параллельно есть возможность в коде точно замерить трафик и его структуру. Достаточно перед и после определенной работы сохранить в памяти трафик и структуру (трафик и структура описывается экземпляром TrafficStatistic) и потом вычесть «перед» из «после». Получится очень точно и наглядно. Это очень красивая возможность. Я о такой удобной возможности подумал сразу и реализовал по аналогии с JetBrains dotTrace Profiler и его SDK. Увы и ах – с трафиком точной подсчет пока не пригодился.

В сухом остатке мне оказалось лень заниматься вычитанием и делать копии отчетов структуры трафика в первом случае. Мне так же оказалось лень искать точки съема показаний трафика в коде для второго случая. Все благодаря тому же dotTrace от, не рекламы ради, JetBrains.

Ну действительно, когда память и CPU надо профилировать, то достаточно в GUI нажать «Start profiling» перед, и «Get Snapshot» после. А когда трафик приходится профилировать, нужно искать отчет структуры трафика и выискивать глазами разницу, а потом еще и с клавиатурой калькулятора упражняться в вычитании натуральных чисел. В итоге лень взяла свое – и появилась GUI Traffic Profiler с кнопками Start & Stop и немедленным отчетом о структуре трафика. Не хотел показаться нескромным, но все как у JetBrains.

Voilà:

WCF Profiler

Ярлыки (Tags)