Мониторинг работы Java-приложений: Часть 3. Мониторинг производительности и степени готовности среды выполнения приложений

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

Третья и завершающая статья серии "Мониторинг работы Java-приложений" будет посвящена стратегиям и подходам к мониторингу производительности и степени готовности внешних компонентов. К ним относятся операционные системы серверов, на которых развернуты приложения, рабочие базы данных и системы обмена сообщениями. В завершение статьи мы обсудим различные вопросы манипулирования собранной информацией, касающейся производительности приложения, в том числе рассмотрим варианты построения отчетов и визуализации.

Николас Уайтхед, старший технический архитектор, ADP

Николас Уайтхед (Nicholas Whitehead) занимает должность старшего технического архитектора в подразделении ADP под названием Small Business Services в Флорхэм Парк, штат Нью-Джерси. Он имеет более чем десятилетний опыт разработки Java-приложений в таких областях, как инвестиционная банковская деятельность, электронная коммерция и коммерческое программное обеспечение. Накопленный опыт развертывания и поддержки приложений (в том числе созданных собственноручно) подтолкнул его к изучению и реализации систем мониторинга производительности.



10.04.2009

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

Коллекторы на базе Spring

Во второй статье мы создали базовый вариант компонентной модели на основе Spring, при помощи которой можно управлять средствами мониторинга. У этой модели есть следующие преимущества:

  • конфигурационные XML-файлы облегчают управление большим числом параметров, требующихся для настройки сложных объектов-коллекторов;
  • принцип разделения ответственности (separation of concerns) позволяет упростить индивидуальные компоненты модели, взаимодействующие между собой при помощи механизма внедрения зависимостей (dependency injection) в Spring;
  • spring поддерживает простую схему управления жизненным циклом объектов, включающую операции инициализации, запуска и остановки. Кроме того, Spring предоставляет возможности контроля, мониторинга и диагностики на этапе выполнения объектов, реализующих интерфейсы JMX (Java Management Extensions);

Ниже мы более подробно расскажем о коллекторах на базе Spring, так как именно они будут применяться во всех разделах статьи.

Мониторинг компьютеров и операционных систем

В конечном счете, приложения Java исполняются операционной системой (ОС) и аппаратным обеспечением компьютера, на котором запущена JVM. Ввиду этого совершенно необходимы средства полноценного мониторинга и сбора данных о производительности, работоспособности и степени готовности этих компонентов. Как правило, данная информация может быть получена через интерфейсы операционной системы. В этом разделе мы расскажем о некоторых способах получения подобных данных и их передачи APM-системе при помощи интерфейса ITracer, описанного в первой статье.

Базовые метрики производительности операционных систем

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

  • Степень загрузки процессора: этот показатель отражает интенсивность использования процессора (CPU) на данном компьютере. Как правило, он измеряется в процентах, т.е. значение показывает процент времени, в течение которого процессор был загружен, за определенный временной интервал. Компьютеры могут быть многопроцессорными, а каждый процессор – многоядерным, однако большинство операционных систем позволяет приложениям абстрагироваться от этих технических подробностей, позволяя им работать с каждым ядром как с отдельным процессором. Например, сервер, имеющий два двухъядерных процессора, с точки зрения приложений будет выглядеть как четырехпроцессорная машина. Степень загрузки обычно подсчитывается для каждого процессора индивидуально, однако может также агрегироваться по всем процессорам. Решение об отдельном мониторинге каждого процессора или агрегировании, как правило, принимается исходя из структуры и природы конкретного приложения. В частности, стандартное многопоточное Java-приложение обычно по умолчанию распределяет нагрузку по всем процессорам, поэтому имеет смысл применять агрегирование. Однако в некоторых случаях некоторые процессы ОС могут быть приписаны к конкретным процессорам, поэтому использование общего показателя приведет к потере детальности картины.

    Выделяют четыре основных типа использования процессорного времени:

    • системная загрузка: процессорное время, потраченное на выполнение системных процессов, либо процессов, исполняющихся на уровне ядра ОС;
    • пользовательская загрузка: процессорное время, потраченное на выполнение пользовательских программ;
    • ожидание окончания операции ввода-вывода: время, которое процессор простаивал, ожидания окончания операции ввода-вывода;
    • бездействие: отсутствие какой-либо нагрузки;
    Существуют еще два показателя, тесно связанных со степенью загрузки: длина очереди и число переключений контекста. Первый показатель отражает число потоков, ожидающих доступа к процессору, а второй – число переключений процессора от одного потока к другому.
  • Память: наиболее простыми метриками, относящимися к памяти, являются процент свободной и занятой физической памяти. Кроме того, часто принимаются во внимание показатели работы виртуальной памяти, а также скорость выделения и освобождения блоков. Наконец, более детальные метрики могут относиться к определенным областям адресного пространства.
  • Диск и операции ввода-вывода: простыми, но очень важными показателями являются объем доступного места на каждом логическом или физическом диске, а также скорость записи и чтения данных.
  • Сеть: типичные показатели отражают скорость передачи данных и частоту возникновения ошибок. Как правило, показатели считаются отдельно для каждого высокоуровневого сетевого протокола, например, TCP или IP.
  • Процессы и их группы: все перечисленные выше метрики показывают общую интенсивность вычислений на данном компьютере. Однако те же показатели активности или расхода ресурсов могут считаться для каждого процесса или группы процессов по отдельности. Мониторинг использования ресурсов отдельным процессом помогает вычислить долю общих ресурсов компьютера, потребляемых конкретным приложением или сервисом. Некоторые приложения представляют собой единственный процесс, другие же, например, Web-сервер Apache 2, могут создавать пул процессов, составляющих единый логический сервис.

Обычный и агентный мониторинг

В разных операционных системах используются разные механизмы для сбора показателей производительности. Мы рассмотрим множество способов, имеющих множество различий, однако в том, что касается мониторинга, главным моментом является наличие или отсутствие агентов. В некоторых случаях данные могут быть получены без использования специального программного обеспечения на компьютере, на котором развернуто приложение. Тем не менее очевидно, что в том или ином виде агент присутствует, так как мониторинг всегда осуществляется через некоторый интерфейс, служащий для сбора данных. Главный вопрос заключается в том, является ли агент стандартным компонентом операционной системы как, например, SSH в серверах Linux®, или он представляет собой специализированную программу, служащую исключительно для предоставления внешним коллекторам доступа к необходимым данным. Оба варианта имеют свои преимущества и недостатки:

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

Не исключен вариант, что оптимальным решением будет комбинированный мониторинг, сочетающий использование локального агента, отвечающего за сбор различных данных, и удаленной системы, способной самостоятельно запрашивать базовые показатели, такие как состояние сервера или самого локального агента.

Агенты бывают нескольких типов, в частности, автономные агенты активизируются в соответствии со своим собственным расписанием в отличие от агентов, собирающих и передающих данные в ответ на запросы. Кроме того, некоторые агенты просто передают данные запрашивающим объектам, в то время как другие прямо или косвенно трассируют их в систему APM.

Далее мы рассмотрим способы мониторинга компьютеров, работающих под управлением операционных систем Linux и UNIX®.


Мониторинг работы компьютеров под управлением Linux и UNIX

Существует ряд программ-агентов, предоставляющих специализированные возможности для сбора данных о производительности работы хостов под управлением Linux и UNIX. Однако Linux и большинство систем семейства UNIX имеют большое число встроенных средств, предоставляющих доступ к данным о быстродействии через виртуальную файловую систему под названием /proc. С ее помощью можно работать со специальными структурами данных в памяти компьютера как с обыкновенными текстовыми файлами на диске. Это не представляет никаких сложностей, так как существует множество стандартных утилит и других программ, способных читать и анализировать текстовые файлы. Содержимое самих файлов может представлять собой как общую информацию, так и очень специализированные данные. Обращение к данным происходит очень быстро, так как информация читается непосредственно из памяти.

Наиболее распространенными утилитами для чтения данных из файлов /proc являются ps, sar, iostat и vmstat (ссылки на документацию по этим программам приведены в разделе Ресурсы). Таким образом, для мониторинга компьютеров под управлением Linux и UNIX достаточно просто запускать эти утилиты из командной строки и анализировать результаты. Этот подход применим к множеству различных ОС семейств Linux и UNIX. Структуры для конкретных систем могут незначительно различаться, однако не составляет труда форматировать данные таким образом, чтобы процедура их сбора совершенно не зависела от ОС. Этим данный подход отличается от использования специализированных библиотек-агентов, которые должны создаваться под каждый дистрибутив Linux или UNIX (причем не исключено, что они также обращаются к /proc). Кроме того, написать собственные команды интерпретатора (shell) для специальных целей, например для стандартизации формата данных, не представляет сложности и не приводит к существенным накладным расходам.

Далее рассмотрим несколько методов вызова команд интерпретатора и трассировки полученных данных.

Выполнение команд интерпретатора

Перед тем как начать сбор данных о производительности компьютера под управлением Linux, необходимо запустить на нем командный интерпретатор. Примерами могут служить bash, csh, ksh и любые интерпретаторы, позволяющие запускать команды и скрипты, а затем возвращающие результаты их работы. Существует несколько способов запуска интерпретатора:

  • локальный запуск: если на целевом компьютере есть JVM, то поток может запустить интерпретатор при помощи класса java.lang.Process;
  • удаленный запуск через Telnet или rsh: оба этих сервиса позволяют запускать интерпретатор и его команды. Однако их использование в настоящее время ограничено из-за проблем с безопасностью. Данные сервисы по умолчанию остановлены в большинстве современных дистрибутивов;
  • SSH (Secure Shell): в настоящее время SSH – это наиболее популярный интерпретатор, использующийся для доступа к удаленным компьютерам. Он предоставляет полный доступ к командной оболочке Linux и считается достаточно безопасным. Именно он будет использоваться в большинстве примеров данной статьи. SSH входит в состав множества ОС, в том числе всех систем семейства UNIX, Microsoft® Windows®, OS/400 и z/OS.

Принципиальная разница между локальным и удаленным интерпретаторами показана на рисунке 1.

Рисунок 1. Локальный и удаленный вызовы командного интерпретатора
Local and remote shells

Для инициализации автономной SSH-сессии по обмену данными с сервером необходимо выполнить следующие действия: вначале следует создать пару ключей SSH – открытый ключ и секретный ключ. Открытый ключ должен находиться на целевом сервере, а секретный – на сервере мониторинга – там, где он будет доступен коллектору данных. После этого коллектор может открыть безопасное соединение с интерпретатором на удаленном сервере, используя секретный ключ и фразу-пароль секретного ключа (passphrase). При использовании пары ключей пароль учетной записи на целевом сервере указывать не обязательно. Вся последовательность действия выглядит следующим образом.

  1. Убедитесь, что в локальном файле, в котором перечислены IP-адреса или имена компьютеров и ассоциированные с ними открытые SSH-ключи, существует запись, соответствующая целевому серверу. Если вы работаете под пользовательской учетной записью, то этот файл, как правило, называется ~/.ssh/known_hosts и расположен в вашем домашнем каталоге.
  2. Соединитесь с целевым сервером, используя учетную запись для мониторинга (например, monitoruser).
  3. Создайте директорию с именем .ssh в домашнем каталоге.
  4. Создайте каталог в директории .ssh и выполните команду ssh-keygen -t dsa. Вам будет предложено ввести ключ и фразу-пароль. В результате будет создано два файла: monitoruser_dsa (секретный ключ) и monitoruser._dsa.pub (открытый ключ).
  5. Скопируйте секретный ключ в безопасное место, доступное с компьютера, на котором будут работать коллекторы.
  6. Добавьте содержимое открытого ключа в файл authorized_keys в каталоге .ssh при помощи команды cat monitoruser_dsa.pub >> authorized_keys.

Весь процесс приведен в листинге 1.

Листинг 1. Создание пары ключей SSH
whitehen@whitehen-desktop:~$ mkdir .ssh
whitehen@whitehen-desktop:~$ cd .ssh
whitehen@whitehen-desktop:~/.ssh$ ssh-keygen -t dsa
Generating public/private dsa key pair.
Enter file in which to save the key (/home/whitehen/.ssh/id_dsa): whitehen_dsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in whitehen_dsa.
Your public key has been saved in whitehen_dsa.pub.
The key fingerprint is:
46:cd:d4:e4:b1:28:d0:41:f3:ea:3b:8a:74:cb:57:e5 whitehen@whitehen-desktop
whitehen@whitehen-desktop:~/.ssh$ cat whitehen_dsa.pub >> authorized_keys
whitehen@whitehen-desktop:~/.ssh$

Теперь коллектор может открыть SSH-соединение с целевым хостом whitehen-desktop, работающим под управлением Ubuntu Linux.

В этом примере сбор данных будет осуществляться при помощи базового класса org.runtimemonitoring.spring.collectors.shell.ShellCollector. Экземпляр данного класса будет развернут в контексте Spring под именем UbuntuDesktopRemoteShellCollector. Кроме того, необходимо добавить следующие компоненты:

  • планировщик, который будет запускать коллектор один раз в минуту. Для этого мы будем использовать класс java.util.concurrent.ScheduledThreadPoolExeutor, который поддерживает пул потоков и механизм обратных вызовов. Экземпляр данного класса будет развернут в Spring под именем CollectionScheduler;
  • реализация интерпретатора SSH, необходимая для запуска команд на сервере и получения результатов. В этих целях будет использоваться класс org.runtimemonitoring.spring.collectors.shell.ssh.JSchRemoteShell. Он реализует интерфейс интерпретатора org.runtimemonitoring.spring.collectors.shell.IRemoteShell. Объект будет развернут под именем UbuntuDesktopRemoteShell;
  • вместо жесткого задания набора команд и соответствующих им процедур разбора результатов в коде коллектора будет использоваться экземпляр класса org.runtimemonitoring.spring.collectors.shell.commands.CommandSet, который будет развернут в Spring под именем UbuntuDesktopCommandSet. Таким образом, набор команд будет загружаться из XML-файла, в котором заданы следующие параметры:
    • целевая платформа, на которой будет выполняться интерпретатор;
    • набор выполняемых команд;
    • параметры, описывающие схему получения данных и их сопоставления с пространствами имен при трассировке в систему APM.
    Ниже мы подробнее поговорим об этих параметрах, а пока обратите внимание на рисунок 2, на котором изображены связи между коллектором, интерпретатором и набором команд.
Рисунок 2. Взаимодействие коллектора, интерпретатора и набора команд
The collector, shell, and command set

Далее рассмотрим примеры настройки и использования конкретных команд для получения данных о производительности. Классическим примером является команда sar. Ее описание, приведенное в страницах man в Linux (см. Ресурсы), выглядит следующим образом: "Collect, report, or save system activity information" (сбор, выдача и сохранение данных о процессах, выполняющихся в системе). Данная команда обладает весьма гибкими возможностями, поддерживая более 20 комбинируемых параметров. В качестве простого примера можно выполнить sar -u 1 3. Результатом будет являться степень загрузки процессора, измеренная в течение трех односекундных интервалов. Вывод команды показан в листинге 2.

Листинг 2. Пример результатов запуска sar
whitehen@whitehen-desktop:~$ sar -u 1 3
Linux 2.6.22-14-generic (whitehen-desktop)      06/02/2008

06:53:24 PM     CPU     %user     %nice   %system   %iowait    %steal     %idle
06:53:25 PM     all      0.00      0.00      0.00      0.00      0.00    100.00
06:53:26 PM     all      0.00     35.71      0.00      0.00      0.00     64.29
06:53:27 PM     all      0.00     20.79      0.99      0.00      0.00     78.22
Average:        all      0.00     18.73      0.33      0.00      0.00     80.94

Вывод можно условно разделить на начальную часть, заголовок, три секции с данными и заключительную часть, содержащую усредненные показатели. Нашей целью является выполнение команды, получение и разбор результатов, а также их передача APM-системе. Приведенный выше формат очень прост, однако он может варьироваться в широких пределах в зависимости от версии. Кроме того, при использовании других параметров команда sar будет выдавать совершенно другие результаты, не говоря уже о том, что другие команды также используют собственный формат. Например, в листинге 3 приведен вывод команды sar, в котором показаны данные о работе активных сокетов.

Листинг 3. Получение информации о работе сокетов при помощи sar
whitehen@whitehen-desktop:~$ sar -n SOCK 1
Linux 2.6.22-14-generic (whitehen-desktop)      06/02/2008

06:55:10 PM    totsck    tcpsck    udpsck    rawsck   ip-frag
06:55:11 PM       453         7         9         0         0
Average:          453         7         9         0         0

Таким образом, необходимо решение, позволяющее настраивать разбор данных под различные форматы без изменения исходного кода коллекторов. Кроме того, желательно преобразовывать сокращения типа totsck в удобочитаемые фразы, например Total Used Sockets (общее число используемых сокетов), перед трассировкой данных системе APM.

В некоторых случаях данные можно получать в формате XML. Например, команда sadf из пакета SysStat (см. Ресурсы) может преобразовывать большую часть собранных данных о производительности Linux-хоста в XML. XML улучшает и делает структуру данных значительно более предсказуемой, а также фактически избавляет от необходимости разбора данных, их отображения на пространства имен при трассировке и декодирования сокращений. К сожалению, подобные средства не всегда доступны при мониторинге систем через командные интерпретаторы, поэтому необходимы другие гибкие решения по разбору и отображению данных.

Далее рассмотрим пример настройки двух объектов Spring для мониторинга показателей, предоставляемых командой sar (см. два предыдущих примера). Исходный код всех примеров можно найти в архиве, ссылка на который приведена в разделе Загрузка).

Точкой входа в примере SpringCollector является класс org.runtimemonitoring.spring.collectors.SpringCollector. Он принимает на вход один параметр – имя директории, в которой находятся конфигурационные файлы объектов, и загружает все файлы с расширением .xml, интерпретируя их в качестве конфигурационных файлов Spring. В нашем случае такой директорией является ./spring-collectors в корневом каталоге проекта. Далее мы рассмотрим все файлы в данном каталоге (конечно можно описывать свойства всех объектов в одном файле, однако разбиение по типу функциональности улучшает общую организацию проекта). Три объекта, описание которых приведено в листинге 4, представляют собой коллектор, интерпретатор и набор команд.

Листинг 4. Описание коллектора, интерпретатора и набора команд в XML
<!-- Коллектор -->
<bean id="UbuntuDesktopRemoteShellCollector"
  class="org.runtimemonitoring.spring.collectors.shell.ShellCollector"
  init-method="springStart">
  <property name="shell" ref="UbuntuDesktopRemoteShell"/>
  <property name="commandSet" ref="UbuntuDesktopCommandSet"/>
  <property name="scheduler" ref="CollectionScheduler"/>
  <property name="tracingNameSpace" value="Hosts,Linux,whitehen-desktop"/>
  <property name="frequency" value="5000"/>
</bean>

<!-- Интерпретатор -->
<bean id="UbuntuDesktopRemoteShell"
   class="org.runtimemonitoring.spring.collectors.shell.ssh.JSchRemoteShell"
   init-method="init"
   destroy-method="close">
   <property name="userName" value="whitehen"/>
   <property name="hostName" value="whitehen-desktop"/>
   <property name="port" value="22"/>
   <property name="knownHostsFile"
      value="C:/Documents and Settings/whitehen/.ssh/known_hosts"/>
   <property name="privateKey"
      value="C:/keys/whitehen/ubuntu-desktop/whitehen_dsa"/>
   <property name="passphrase" value="Hello World"/>
</bean>

<!-- Набор команд -->
<bean id="UbuntuDesktopCommandSet"
   class="org.runtimemonitoring.spring.collectors.shell.commands.CommandSet">
   <constructor-arg type="java.net.URL"
      value="file:///C:/projects//RuntimeMonitoring/commands/xml/UbuntuDesktop.xml"/>
</bean>

Описание объекта CommandSet в листинге 4 состоит только из атрибута id со значением UbuntuDesktopCommandSet и URL, указывающего на другой файл XML. Это сделано, чтобы не перегружать файл Spring большим числом команд. Описание набора команд будет приведено немного ниже.

Первым объектом в листинге 4 является UbuntuDesktopRemoteShellCollector. Его идентификатор (id) может быть любым, однако необходимо поддерживать согласованность идентификаторов при связывании этого объекта с другими. В данном примере этот объект является экземпляром org.runtimemonitoring.spring.collectors.shell.ShellCollector, который представляет собой базовый класс для сбора данных через интерфейс, напоминающий командный интерпретатор. Другими важными свойствами являются следующие:

  • shell: экземпляр класса-интерпретатора, который будет использоваться данным коллектором для вызова команд и получения их результатов. Spring связывает данное свойство с объектом UbuntuDesktopCommandSet;
  • commandSet: экземпляр CommandSet, представляющий собой набор команд с соответствующими инструкциями по разбору результатов и сопоставлению с пространствами имен при трассировке. Spring связывает данное свойство с объектом UbuntuDesktopRemoteShell;
  • scheduler: ссылка на пул потоков, выполняющий функции планировщика. Он управляет процессом сбора данных, выделяя для этого потоки из пула;
  • tracingNameSpace: префикс пространства имен трассировки, при помощи которого указывается позиция трассируемых данных в дереве метрик APM-системы;
  • frequency: частота сбора данных в миллисекундах.

Второй объект в листинге 4 представляет собой командный интерпретатор. В данном случае используется класс org.runtimemonitoring.spring.collectors.shell.ssh.JSchRemoteShell, реализующий командную оболочку SSH. Этот класс создан на основе библиотеки JSch с сайта JCraft.com (см. Ресурсы). Основными его свойствами являются следующие:

  • userName: имя пользователя для соединения с сервером Linux;
  • hostName: имя или IP-адрес сервера Linux, с которым будет установлено соединение;
  • port: номер порта на сервере Linux, прослушиваемый демоном sshd;
  • knownHostFile: файл, содержащий имена хостов и SSH-сертификаторы SSH-серверов, распознаваемых компьютером, на котором запущен SSH-клиент. Вообще, механизм безопасности в SSH построен по принципам, обратным традиционным иерархиям безопасности, т.е. клиент, а не сервер, должен "доверять" хосту, чтобы с ним соединиться. "Доверие" означает, что хост известен клиенту и предоставляет верный сертификат безопасности;
  • privateKey: секретный ключ SSH, который будет использоваться для аутентификации клиента при открытии SSH-соединения с сервером;
  • passPhrase: фраза-пароль, которая будет использоваться для расшифровки секретного ключа. Она напоминает обычный пароль с той лишь разницей, что не передается на сервер, а используется для расшифровки на локальном компьютере клиента.

Конфигурация объекта CommandSet показана в листинге 5.

Листинг 5. Описание объекта CommandSet в XML
<CommandSet name="UbuntuDesktop">
   <Commands>
      <Command>
         <shellCommand>sar -u 1</shellCommand>
         <paragraphSplitter>\n\n</paragraphSplitter>
    <Extractors>
       <CommandResultExtract>
          <paragraph id="1" name="CPU Utilization"/>
          <columns entryName="1" values="2-7" offset="1">
             <remove>PM</remove>
          </columns>
          <tracers default="SINT"/>
               <filterLine>Average:.*</filterLine>
          <lineSplit>\n</lineSplit>
       </CommandResultExtract>
    </Extractors>
      </Command>
      <Command>
         <shellCommand>sar -n SOCK 1</shellCommand>
    <paragraphSplitter>\n\n</paragraphSplitter>
    <Extractors>
       <CommandResultExtract>
          <paragraph id="1" name="Socket Activity"/>
          <columns values="1-5" offset="1">
             <remove>PM</remove>
             <namemapping from="ip-frag" to="IP Fragments"/>
             <namemapping from="rawsck" to="Raw Sockets"/>
             <namemapping from="tcpsck" to="TCP Sockets"/>
             <namemapping from="totsck" to="Total Sockets"/>
             <namemapping from="udpsck" to="UDP Sockets"/>
          </columns>
          <tracers default="SINT"/>
               <filterLine>Average:.*</filterLine>
          <lineSplit>\n</lineSplit>
       </CommandResultExtract>
    </Extractors>
      </Command>
   </Commands>
</CommandSet>

Объект CommandSet отвечает за управление командами интерпретатора и инструкциями по разбору результатов их работы. Поскольку вывод одной команды может отличаться в разных системах Linux и UNIX, как правило, создается несколько экземпляров CommandSet для каждого вида операционной системы. Конфигурация объектов CommandSet постоянно меняется и настраивается под конкретные ситуации, поэтому полное описание параметров займет слишком много времени. Тем не менее ниже приведена краткая справка по основным элементам:

  • <shellCommand>: описывает команду, которая будет передана командному интерпретатору;
  • <paragraphSplitter>: некоторые команды, в частности представляющие собой цепочку команд, могут возвращать результат в виде нескольких блоков текста, так называемых абзацев. Данное свойство содержит регулярное выражение, разделяющее весь вывод на абзацы. Выражение применяется объектом, реализующим команду, который далее передает запрашиваемые абзацы объектам-экстракторам, извлекающим из них нужную информацию;
  • <Extractors> и вложенные в него теги <CommandResultExtract> содержат инструкции по разбору и отображению результатов;
  • <paragraph>: каждый экстрактор указывает список индексов интересующих его абзацев. Индексы задаются при помощи атрибута id (индексирование начинается с нуля). Кроме того, данные, извлекаемые из абзацев, трассируются в соответствии с пространствами имен, определенными в имени абзаца;
  • <columns>: если указан атрибут entryName, то в пространство имен трассировки добавляется значение каждой строки вывода, соответствующее заданной колонке. Это работает в случае, если разграничение метрик устанавливается в левой колонке. Например, команда sar может выдавать данные о загрузке для каждого процессора, причем номер процессора будет указываться во второй колонке. В листинге 5 при помощи атрибута entryName извлекается квалификатор all, означающий, что трассируются агрегированные данные, относящиеся ко всем процессорам в совокупности. Атрибут values задает индексы колонок для трассируемых значений, а offset используется для корректировки несоответствия между числом колонок в строке данных и в соответствующем заголовке;
  • <tracers>: определяет тип трассировки. При помощи этого элемента можно задавать различные типы трассировщиков для значений с определенными заголовками или индексами entryName;
  • <filterLine>: при наличии этого элемента регулярное выражение игнорирует строки с данными, не соответствующими заданному фильтру;
  • <lineSplit>: задает регулярные выражения для выделения строк в каждом абзаце.

Дерево метрик APM для данного примера показано на рисунке 3.

Рисунок 3. Диаграммы загрузки CPU (в процентах), построенные APM по результатам мониторинга компьютера с ОС Ubuntu Linux
APM Tree for Ubuntu Desktop monitoring

При желании вы можете изменить представление данных в дереве. Например, вывод команды, выполняющейся на сервере, можно передать на вход утилитам, таким как grep, awk или sed, способным переформатировать данные таким образом, чтобы облегчить их дальнейший разбор и отображение. Пример использования такой конвейерной обработки приведен в листинге 6.

Листинг 6. Форматирование вывода команды sar
whitehen@whitehen-desktop:~$ sar -u 1 | grep Average | \ 
   awk '{print "SINT/User:"$3"/System:"$5"/IOWait:"$6}'
SINT/User:34.00/System:66.00/IOWait:0.00

Другим вариантом, оптимально сочетающим гибкость конфигурирования и быстродействие, является использование динамических скриптов. Они особенно полезны в случаях, когда вывод команд выглядит особенно громоздко, а дополнительные средства форматирования по каким-либо причинам недоступны. В следующем примере командный интерпретатор Telnet настраивается для сбора данных о распределении нагрузки балансировщиком, входящим в состав Cisco CSS. В данной ситуации формат выдаваемой информации плохо приспособлен для стандартизированного разбора, к тому же интерпретатор поддерживает ограниченное число команд. Пример вывода показан в листинге 7.

Листинг 7. Результаты, полученные через Telnet-соединение с CSS
Service Name                     State     Conn  Weight  Avg   State
                                                         Load  Transitions

ecommerce1_ssl                   Alive         0      1   255            0
ecommerce2_ssl                   Down          0      1   255            0
admin1_ssl                       Alive         0      1     2         2982
admin2_ssl                       Down          0      1   255            0
clientweb_ssl                    Alive         0      1   255            0

XML: элементы или атрибуты?

Некоторые приложения очевидным образом предпочитают использовать XML-элементы вместо атрибутов, и наоборот. У меня нет таких ярко выраженных предпочтений, однако, стараясь быть последовательным, я следую двум следующим правилам:

  • краткие именованные значения легче воспринимать на глаз;
  • для длинных строк, возможно, содержащих запрещенные символы, для которых придется использовать escape-последовательности (например, такие символы как & или "), я всегда использую элементы, так как их значениями могут быть блоки CDATA, содержимое которых игнорируется XML-процессорами.

В листинге 8 приведен набор команд, использующихся для выполнения системной утилиты и разбора результатов. Обратите внимание на тег <preFormatter beanName="FormatCSSServiceResult"/>. В нем указывается ссылка на объект Spring, содержащий несколько строк скрипта на языке Groovy. Результат работы утилиты, запущенной через Telnet, поступает на вход Groovy-скрипта, а его вывод, в свою очередь, передается экстрактору в значительно более удобном формате. Кроме того, заметьте, что тип трассировки для данных в колонке Status переопределен и теперь имеет значение STRING. Вы могли заметить, что в выводе утилиты нет колонки с таким заголовком, зато существуют две колонки с заголовками, начинающимися с State. Данная ситуация исправляется тем же скриптом на Groovy, который присваивает имя Status первой из них.

Листинг 8. Описание объекта CommandSet для CSS
<CommandSet name="CiscoCSS">
   <Commands>
      <Command>
         <shellCommand>show service summary</shellCommand>
       <paragraphSplitter>\n\n\n\n</paragraphSplitter>
       <preFormatter beanName="FormatCSSServiceResult"/>
       <Extractors>
          <CommandResultExtract>
             <paragraph id="0" name="Service Summary" header="true"/>
        <columns entryName="0" values="1-5" offset="0"/>
        <tracers default="SINT">
           <tracer type="STRING">Status</tracer>
        </tracers>
        <lineSplit>\n</lineSplit>
          </CommandResultExtract>
       </Extractors>
         </Command>
   </Commands>
</CommandSet>

Использование объекта Groovy имеет ряд преимуществ. Во-первых, скрипт можно настраивать динамически, изменяя его поведение в процессе мониторинга. Объект сам отслеживает изменения исходного кода и вызывает компилятор Groovy при следующем обращении так, что это не приводит к существенному падению быстродействия. Кроме того, сам язык Groovy прост, но обладает мощными средствами текстового анализа. Код данного объекта, содержащий скрипт на Grovvy, показан в листинге 9.

Листинг 9. Описание объекта Groovy, выполняющего функции форматирования
<bean id="FormatCSSServiceResult"
   class="org.runtimemonitoring.spring.groovy.GroovyScriptManager"
   init-method="init" lazy-init="false">
   <property name="sourceCode"><value><![CDATA[
      String[] lines = formatTarget.toString().split("\r\r\n");
      StringBuffer buff = new StringBuffer();
      lines.each() {
         if(!(
            it.contains("Load  Transitions") ||
            it.contains("show service summary") ||
            it.trim().length() < 1)) {
               buff.append(it).append('\n');
           }
      }
      return buff.toString()
      .replaceFirst("State", "Status")
      .replaceFirst("Service Name", "ServiceName")
      .replace("State", "Transitions");
      ]]></value>
   </property>
</bean>

Дерево метрик, построенное APM по данным мониторинга CSS, показано на рисунке 4.

Рисунок 4. Дерево метрик, построенное APM по данным мониторинга CSS
APM Tree for CSS Monitoring

Соединения SSH

В заключение темы о сборе данных через командные интерпретаторы Linux/Unix рассмотрим один момент, связанный с соединениями SSH. Базовым интерфейсом для классов-интерпретаторов является org.runtimemonitoring.spring.collectors.shell.IShell. Он содержит два варианта метода issueOSCommand(), который вызывает команду ОС и возвращает ее результат. В нашем примере используется класс org.runtimemonitoring.spring.collectors.shell.ssh.JSchRemoteShell для удаленных соединений через SSH, который вызывает командный интерпретатор при помощи задания SSHEXEC в Apache Ant (см. Ресурсы). Данный подход привлекает своей простотой, однако у него есть один недостаток: выполнение каждой команды приводит к открытию нового соединения. Очевидно, что это не самый эффективный способ. Несмотря на то что обращение к командному интерпретатору требуется лишь раз в несколько минут, за каждый цикл может выполняться несколько команд для получения необходимых данных мониторинга. К сожалению, управление одной открытой сессией на протяжении определенного промежутка времени (окна мониторинга) и ее использование в нескольких циклах выполнения команд представляет определенные трудности. При этом приходится тщательно анализировать возвращаемые данные, определяя тип интерпретатора, а также фильтруя символы приглашения ввода, которые, разумеется, никак не являются частью запрашиваемых данных.

У меня есть опыт работы с реализациями интерпретатора, поддерживающими сессию в открытом состоянии. Есть две основные альтернативы: либо открывать соединение на каждый цикл мониторинга, либо стараться получать все требуемые данные одной командой. Этого можно добиться при помощи сцепления команд или, в некоторых случаях, используя несколько параметров одной команды. Например, у команды sar на моем SuSe Linux есть опция -A, выдающая примеры всех поддерживаемых метрик (другими словами, sar -А эквивалентно sar -bBcdqrRuvwWy -I SUM -n FULL -P ALL). Данные будут возвращаться в виде нескольких абзацев, но их легко разбирать при помощи набора команд. В качестве примера обратитесь к файлу Suse9LinuxEnterpriseServer.xml в архиве с исходным кодом (см. Загрузка), в котором содержится описание команд в XML.


Мониторинг компьютеров под управлением Windows

Операционные системы Linux/Unix и Microsoft Windows существенно различаются, в том числе и в том, что касается сбора показателей производительности. В Windows практически нет собственных утилит командной строки, сравнимых по объему предоставляемых данных с командами в Linux/Unix. Кроме того, не существует механизма доступа к данным о производительности аналогичного по простоте файловой системе /proc. Вместо этого есть менеджер производительности (Windows Performance Manager – WPM), также известный под именами SysMon, System Monitor или Performance Monitor. Он предоставляет стандартный интерфейс для измерения производительности компьютеров, работающих под управлением Windows. Данный менеджер обладает достаточно широкими возможностями, позволяя измерять множество полезных показателей. Кроме того, многие Windows-приложения делают собственные метрики доступными через интерфейс WPM. Наконец, WPM также предоставляет средства для построения диаграмм, отчетов и рассылки уведомлений. Интерфейс запущенного WPM показан на рисунке 5.

Рисунок 5. Интерфейс менеджера производительности Windows
Windows Performance Manager

WPM поддерживает набор счетчиков производительности – объектов, имеющих составные имена, идентифицирующие конкретные метрики. Имя каждого объекта состоит из следующих компонентов:

  • объект мониторинга, определяющий общую категорию для данной метрики. Примерами таких категорий могут служить Процессор или Память;
  • экземпляр. При мониторинге некоторых объектов требуется указание конкретного экземпляра. Это необходимо, когда в системе присутствуют несколько экземпляров одного объекта. Например, экземплярами типа "Процессор" могут быть физические процессоры, а также виртуальный объект, представляющий общую вычислительную мощность компьютера. В отличие от процессора, объект "Память" является "простым", так как всегда проявляется в единственном экземпляре;
  • счетчик, представляющий собой имя метрики, относящейся к конкретному объекту мониторинга и экземпляру (если последний указан). Например, экземпляр "0" объекта "Процессор" может иметь счетчик под именем % Idle Time (время простоя).

На основе данного принципа составления имен был предложен следующий синтаксис и соглашения для описания объектов:

  • с указанием экземпляра: \объект мониторинга (имя экземпляра)\имя счетчика;
  • без указания экземпляра: \объект мониторинга\имя счетчика.

Существенным недостатком WPM является сложность получения данных, особенно при удаленном доступе. Ситуация выглядит особенно плачевно, если требуется получить удаленный доступ с компьютера, работающего не под Windows. Далее мы рассмотрим несколько способов получения информации через WPM при помощи коллекторов на основе ITracer. Основными являются следующие:

  • чтение лог-файлов: WPM можно настроить для записи всех собранных показателей в лог-файлы. Далее можно считывать данные из этих файлов и трассировать;
  • запросы к базе данных: WPM можно настроить для сохранения собранных показателей в базе данных SQL Server. Затем к данным можно обращаться посредством запросов и трассировать;
  • использование API Win32: клиенты, написанные при помощи API Win32 (например, на языках платформы .NET, а также С++, Visual Basic и т.д.), могут напрямую соединяться с WPM, который предоставляет для этого специальный API;
  • использование специализированных агентов: на Windows-сервере может быть установлена специальная программа-агент, которая будет играть роль посредника между WPM и удаленными клиентами, выполняющимися на других ОС;
  • использование простого протокола управления сетью (Simple Network Management Protocol – SNMP): SNMP является примером универсального агента, способного производить мониторинг сетевых устройств, компьютеров и т.д. Более подробно SNMP будет обсуждаться в следующих разделах;
  • WinRM: WinRM представляет собой реализацию спецификации WS-Management под Windows. Данная спецификация описывает принципы использования Web-сервисов для мониторинга производительности. Будучи независимыми от платформы и языков программирования, Web-сервисы позволяют клиентам ОС, отличных от Windows, получать доступ к показателям, собранным WPM. Данный подход станет стандартным средством в Windows 2008, не требующим установки агентов (хотя, с некоторой натяжкой, Web-сервис можно считать разновидностью агента). Еще интереснее выглядят сформулированные в JSR 262 (коннектор на основе Web-сервисов для JMX-агентов) возможности прямого обращения к WS-Management-сервисам Windows из приложений на Java.

Далее рассмотрим рабочую схему получения показателей производительности при помощи командного интерпретатора Windows и приложения-агента.

Локальное консольное приложение Windows

В качестве простого эксперимента я создал консольное Windows-приложение под названием winsar.exe при помощи C#. Оно реализует часть функциональности sar в Linux/UNIX по предоставлению доступа к статистике производительности компьютера. Консольный интерфейс приложения имеет следующий вид: winsar.exe <объект мониторинга> <имя счетчика> <сырые данные> <имя экземпляра>.

Имя экземпляра необходимо указывать во всех случаях, когда счетчик относится к конкретному экземпляру объекта мониторинга (также можно указывать, что данные требуются для всех экземпляров объекта – для этого служит специальный символ '*'). Символ * также допустим для запрашивания значений всех счетчиков. В остальных случаях должно указываться имя счетчика. Параметр <сырые данные> может принимать значения true или false. Пример получения общего значения счетчиков, а также значения, относящегося к конкретному экземпляру счетчика, приведен в листинге 10.

Листинг 10. Примеры использования winsar для получения значений счетчиков с указанием и без указания экземпляра
C:\NetProjects\WinSar\bin\Debug>winsar Memory "% Committed Bytes In Use" false
 %-Committed-Bytes-In-Use
 79.57401
 
C:\NetProjects\WinSar\bin\Debug>winsar LogicalDisk "Current Disk Queue Length" false C:
         Current-Disk-Queue-Length
C:  2

Поскольку целью было создание приложения, аналогичного sar, результаты выводятся в неотформатированном табличном виде. Их можно анализировать при помощи стандартного набора консольных команд. В случае вывода значений счетчиков, относящихся к конкретным экземплярам, имена последних содержатся в крайней левой колонке, в то время как имена самих счетчиков выводятся в заголовке таблицы. При выводе общих значений счетчиков колонка экземпляров содержит символы '-'. Таким образом, хотя вывод и выглядит несколько неуклюже, но зато легко поддается разбору.

Создание коллектора для сбора статистики, выдаваемой программой winsar, не представляет особых трудностей (в целях упрощения в статье не приводится полное описание показателей, поддерживаемых winsar). В роли командного интерпретатора выступает класс org.runtimemonitoring.spring.collectors.shell.local.WindowsShell, описания набора команд ссылаются на winsar.exe и его параметры. Командный интерпретатор можно также реализовать на основе SSH, но в этом случае придется устанавливать сервер SSH на целевой Windows-хост. Однако это решение будет крайне неэффективным в основном из-за того, что приложение написано под платформу .NET и требует загрузки среды выполнения (CLR). Периодический запуск CLR на короткое время приведет к неоправданно высоким накладным расходам.

Кроме того, winsar можно написать на чистом C++, но мы оставим этот вариант для экспертов в программировании под Windows. Приложение под .NET можно ускорить, но оно все равно должно выполняться в фоновом режиме, каким-то образом обслуживая запросы на получение данных WPM. Самое главное, чтобы приложение не останавливалось после окончания обслуживания каждого запроса. Для этого существует параметр -service, при запуске с которым winsar считывает настройки из конфигурационного файла winsar.exe.config и ожидает входящих запросов через темы (topics) интерфейса сообщений Java (JMS). За исключением пары строк, содержимое файла настроек говорит само за себя. Значением параметра jmsAssembly является имя сборки .NET, в которой содержится .NET-версия клиентских библиотек JBoss 4.2.2, реализующих функциональность JMS. Данная сборка была создана при помощи IKVM (см. Ресурсы). Параметр respondTopic задает имя открытой темы, в которой публикуются ответы на сообщения. Поскольку тема является открытой, данные могут быть получены несколькими подписчиками. При помощи параметра commandSet задается имя набора команд, который будет использоваться получателем данных для их текстового анализа и последующей трассировки. Содержимое файла winsar.exe.config приведено в листинге 11.

Листинг 11. Файл winsar.exe.config
<configuration>
   <appSettings>
      <add key="java.naming.factory.initial"
         value="org.jnp.interfaces.NamingContextFactory"/>
      <add key="java.naming.factory.url.pkgs"
         value="org.jboss.naming:org.jnp.interfaces"/>
      <add key="java.naming.provider.url" value="10.19.38.128:1099"/>
      <add key="connectionFactory" value="ConnectionFactory"/>
      <add key="listenTopic" value="topic/StatRequest"/>
      <add key="respondTopic" value="topic/StatResponse"/>
      <add key="jmsAssembly" value="JBossClient422g" />
      <add key="commandSet" value="WindowsServiceCommandSet" />
   </appSettings>
</configuration>

Spring-коллектор, использующий данный сервис, реализуется аналогично командному интерпретатору. Коллектор является экземпляром класса org.runtimemonitoring.spring.collectors.shell.DelegatingShellCollector, являющегося расширением org.runtimemonitoring.spring.collectors.shell.ShellCollector. Разница заключается в том, что интерпретатор играет роль обычного коллектора, просто запрашивающего необходимые данные, в то время как их получением через JMS, разбором и трассировкой будет заниматься другой компонент. Реализация интерпретатора содержится в классе org.runtimemonitoring.spring.collectors.shell.jms.RemoteJMSShell, который работает как обычная командная оболочка, за тем исключением, что он передает команды на выполнение через JMS. Схема работы показана на рисунке 6.

Рисунок 6. Схема работы делегирующего коллектора
Delegating collector

Такой подход позволяет легко развертывать агентов на различных платформах, поскольку однажды написанный на Java агент JMS может быть запущен на любой операционной системе, для которой существует версия JVM. Принцип работы системы сбора показателей производительности на основе модели "публикация/подписка" в JMS показан на рисунке 7.

Рисунок 7. Мониторинг при помощи схемы "издатель/подписчик" в JMS
JMS pub/sub monitoring

Подобные системы можно классифицировать в соответствии с принципами функционирования агентов JMS. В данном примере иллюстрируется работа так называемых агентов-слушателей по отношению к целевому компьютеру. Другими словами, с момента запуска агенты не выполняют никаких действий до тех пор, пока не получат запрос на сбор данных от центральной системы мониторинга. Существует и альтернативный вариант: агенты могут работать автономно, в соответствии с собственным планом выполнения, собирая данные и публикуя их на том же сервере JMS. Тем не менее у подхода на основе агентов-слушателей есть два преимущества. Во-первых, конфигурирование и хранение параметров сбора данных может осуществляться централизованно, а не распределяться по всем целевым компьютерам. Во-вторых, поскольку сигналы к сбору данных посылаются центральной системой мониторинга, она может отслеживать и сообщать обо всех случаях, когда какой-либо из серверов не отвечает на запросы (эта возможность не реализована в нашем примере). Дерево метрик, построенное системой APM в процессе мониторинга нескольких серверов, показано на рисунке 8.

Рисунок 8. APM-дерево показателей производительности серверов под управлением Windows и Linux
Tree for Windows and Linux servers

Реализация NSClient

Для обращения к серверам Windows и получения необходимых данных, NSClient и NC_Net используют практически одинаковые протоколы на основе сокетов. Библиотека nsclient4j (см. Ресурсы) является Java-надстройкой над этим протоколом и позволяет создавать приложения, обращающиеся к статистике WPM, под практические любые ОС, для которых существует JVM. В архиве с исходным кодом к статье включен пример реализации Spring-коллектора на основе nsclient4j (см. конфигурационный файл /conf/spring-collectors/nsclientj.xml).

Необходимо учитывать, что winsar – это не более чем простой прототип, обладающий рядом недостатков, в частности:

  • к некоторым счетчикам WPM (например, объекту "Процессор") обращаться программно бесполезно – результатом будет пустое или неточное значение. В частности, невозможно за одно обращение получить такой показатель как процент загрузки процессора. Подобные показатели необходимо снимать несколько раз за определенный промежуток времени, после чего вычислять результат. winsar не предоставляет таких функций. Они поддерживаются другими похожими программами-агентами, например NSClient и NC_Net (см. Ресурсы);
  • в целом, несмотря на определенную элегантность, возможности JMS как протокола передачи и получения данных удаленными агентами несколько ограничены. В этих целях NSClient и NC_Net используют низкоуровневый, но достаточно простой протокол на основе сокетов. Одной из целей создания этих сервисов было обеспечение доступа к статистике производительности Windows со стороны Nagios – системы сетевого мониторинга, которая была реализована фактически только под Linux, поэтому ее клиентские компоненты не могли непосредственно использовать программные интерфейсы Win32.

Как упоминалось выше, приложение SpringCollector запускается с единственным параметром, в котором задается имя директории, содержащей конфигурационные XML-файлы. В демонстрационном приложении, чей исходный код содержится в архиве к данной статье, таковым каталогом является /conf/spring-collectors. В примерах выше использовались следующие файлы:

  • shell-collectors.jmx: содержит описания всех коллекторов на основе командных интерпретаторов;
  • management.xml: включает описания всех объектов JMX, а также планировщика, управляющего запуском коллекторов;
  • commandsets.xml: содержит описания наборов команд, использующихся командными интерпретаторами. Элемент /commands каждого описания ссылается на внешний XML-файл;
  • shells.xml: содержит описания всех командных интерпретаторов;
  • jms.xml: описывает фабрику соединений JMS, использующиеся темы, а также контекст JNDI (Java-интерфейс каталогов и служб именования);
  • groovy.xml: содержит определение форматирующего Groovy-объекта.

На этом раздел, посвященный мониторингу операционных систем, подошел к концу, и мы переходим к мониторингу баз данных.


Мониторинг баз данных при помощи JDBC

Мне приходилось часто сталкиваться с мнением, что мониторинг базы данных целиком и полностью находится в ведении администратора БД, который самостоятельно выбирает необходимые программные средства. Однако в целях создания централизованного хранилища данных о производительности и степени готовности системы имеет смысл осуществлять собственный мониторинг БД в дополнение к работе администратора. Это должно быть одной из функций консолидированной APM-системы. Далее мы продемонстрируем некоторые методы использования Spring-коллектора JDBCCollector для получения показателей, которые в ряде случаев могут быть скрыты от других средств мониторинга, но при этом представляют определенный интерес.

Следует рассмотреть вопрос использования следующих вариантов сбора информации о производительности базы данных:

  • измерение степени готовности и времени отклика: наиболее простым способом оценки данных показателей является периодическое подключение к базе данных, выполнение простого запроса и трассировка времени, затраченного сервером на его обработку. В случае, если попытка подключения закончилась неудачей, можно трассировать специальную метрику, характеризующую данную ситуацию. Это не всегда означает, что сервер неработоспособен, но, по крайней мере, сигнализирует о проблемах обращения к базе данных со стороны клиента. В случае фрагментированного мониторинга проблемы с соединением могут быть не выявлены, поэтому нужно всегда помнить, что успешное подключение одного клиента не гарантирует то же самое для других клиентов;
  • сохранение контекстной информации: как вы помните из рассмотрения контекстной трассировки (см. первую статью), при мониторинге полезно периодически сохранять фрагменты данных, которыми оперирует ваше приложение. В ряде случаев картина обращений к базе данных тесно связана с поведением или производительностью остальных компонентов приложения;
  • использование специальных таблиц базы данных: многие СУБД позволяют обращаться к внутренним показателям производительности через специальные таблицы, представления или хранимые процедуры. Таким образом, необходимые данные можно получать стандартным образом через JDBC. Разумеется, при этом возможно определенное дублирование традиционного мониторинга, осуществляемого администратором БД, однако, принимая во внимание то, что общая производительность приложения тесно связана с производительностью БД, крайне нелогично собирать статистику по отдельности и не анализировать вероятные взаимосвязи при помощи консолидированной APM-системы.

Класс JDBCCollector достаточно прост. Он включает в себя только запрос и набор директив для сопоставления результатов запроса с пространством имен трассировки. Обратите внимание на фрагмент SQL в листинге 12.

Листинг 12. Пример запроса SQL
SELECT schemaname, relname, 
SUM(seq_scan) as seq_scan, 
SUM(seq_tup_read) as seq_tup_read
FROM pg_stat_all_tables
where schemaname = 'public'
and seq_scan > 0
group by schemaname, relname
order by seq_tup_read desc

Запрос выбирает значения четырех колонок в таблице. Допустим, нам необходимо связать каждую выбранную строку с пространством имен трассировки, которое частично состоит из данных текущей строки. Как вы помните, пространство имен представляет собой набор сегментов, а также имя метрики, поэтому связывание будет заключаться в задании этих параметров при помощи строковых констант, токенов и их сочетаний. Токены представляют собой значения колонок с определенным индексом, например {2}. При обработке сегментов и имени метрики токены динамически заменяются значениями указанных колонок в текущей строке результатов запроса, в то время как константы остаются без изменений. Принцип работы показан на рисунке 9.

Рисунок 9. Связывание результатов запроса с пространством имен трассировки в классе JDBCCollector
JDBCCollector Mapping

В примере на рисунке 9 результатом запроса является единственная строка. Тем не менее связывание с пространствами имен происходит для каждого описанного правила. Значением сегмента является пара токенов {1},{2}, что соответствует значениям {"public", "sales_order"} в строке-результате запроса. В качестве имен метрик используются константы, поэтому их значения не меняются. Значением метрики является 1 в первом правиле и 2 во втором, что соответствует данным 3459 и 16722637 в строке. Далее рассмотрим процесс связывания на примере конкретной реализации.


Контекстная трассировка при помощи JDBC

В любой операционной базе данных, как правило, содержится информация, которую полезно использовать в качестве контекста при трассировке. Сами данные в большинстве случаев не имеют прямого отношения к производительности, однако если выбрать нужные фрагменты и связать их с историей изменения показателей быстродействия Java-классов, работоспособности JVM и других серверных метрик, то вырисовывается четкая картина поведения системы за определенный промежуток времени. Представьте себе ситуацию, что вы занимаетесь мониторингом Web-сайта электронной коммерции, работающего под высокой нагрузкой. Поступающие от клиентов заказы сохраняются в таблице под именем sales_order, которая содержит уникальный идентификатор и время регистрации для каждого заказа. В этом случае можно вычислить скорость поступления заказов, периодически запрашивая число заказов, зарегистрированных за последние n минут.

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

Далее перейдем к реализации данного примера. В JDBCCollector будет использоваться тот же планировщик, что и в предыдущих примерах. Кроме того, источником данных будет служить DataSource, описанный во второй статье. Описание коллекторов содержится в конфигурационном файле /conf/spring-collectors/jdbc-collectors.xml. Первый из них показан в листинге 13.

Листинг 13. Описание коллектора для сбора данных о скорости поступления заказов
<bean name="OrderFulfilmentRateLast5s"
  class="org.runtimemonitoring.spring.collectors.jdbc.JDBCCollector"
   init-method="springStart">
   <property name="dataSource" ref="RuntimeDataSource" />
   <property name="scheduler" ref="CollectionScheduler" />
   <property name="query">
      <value><![CDATA[
   select count(*) from sales_order
   where order_date > ? and order_date < ?
   ]]></value>
   </property>
   <property name="logErrors" value="true" />
   <property name="tracingNameSpace" value="Database Context" />
   <property name="objectName" 
      value="org.runtime.db:name=OrderFulfilmentRateLast5s,type=JDBCCollector" />
   <property name="frequency" value="5000" />
   <property name="binds">
      <map>
         <entry><key><value>1</value></key><ref bean="RelativeTime"/></entry>
         <entry><key><value>2</value></key><ref bean="CurrentTime"/></entry>
      </map>
   </property>
   <property name="queryMaps">
      <set>
         <bean class="org.runtimemonitoring.spring.collectors.jdbc.QueryMap">
            <property name="valueColumn" value="0"/>
            <property name="segments" value="Sales Order Activity"/>
            <property name="metricName" value="Order Rate"/>
            <property name="metricType" value="SINT"/>
         </bean>
      </set>
   </property>
</bean>


<bean name="RelativeTime" 
   class="org.runtimemonitoring.spring.collectors.jdbc.RelativeTimeStampProvider">
   <property name="period" value="-5000" />
</bean>

<bean name="CurrentTime" 
   class="org.runtimemonitoring.spring.collectors.jdbc.RelativeTimeStampProvider">
   <property name="period" value="10" />
</bean>

В данном примере коллектором является экземпляр класса org.runtimemonitoring.spring.collectors.jdbc.JDBCCollector с именем OrderFulfilmentRateLast5s. При помощи механизма внедрения зависимостей он связывается со стандартным планировщиком (scheduler) и источником данных JDBC (RuntimeDataSource). В свойстве query задается SQL-запрос, который будет выполняться над базой данных. Параметры в запросы можно либо задавать как строковые константы либо, как в данном примере, передавать через переменные связывания. Пример может показаться несколько искусственным, так как граничные значения order_date можно легко задать в SQL. Тем не менее полезно рассмотреть работу с переменными связываниями, потому что без них не обойтись при необходимости передачи в запрос внешних параметров.

Для предоставления возможности передачи параметров через переменные связывания необходимо реализовать интерфейс org.runtimemonitoring.spring.collectors.jdbc.IBindVariableProvider. В данном случае реализацией будет управляемый объект Spring. Мы будем использовать два экземпляра org.runtimemonitoring.spring.collectors.jdbc.RelativeTimeStampProvider – класса, который возвращает текущее время, сдвинутое на значение свойства period. Один экземпляр (RelativeTime) будет возвращать время, отстающее на 5 секунд, а второй (CurrentTime) – время, опережающее текущее на 10 миллисекунд. Ссылки на эти экземпляры помещаются в ассоциативное свойство binds коллектора через внедрение зависимостей. Важно, чтобы значение каждой ассоциативной пары в данном свойстве соответствовало определенной переменной связывания в SQL. В противном случае будет сгенерировано исключение, или будут выданы непредсказуемые результаты.

В данном примере переменные связывания используются для подсчета числа заказов, поступивших за последние примерно 5 секунд. Это непростой для обработки запрос, поэтому необходимо установить частоту сбора данных, а также временной интервал (свойство period экземпляра RelativeTime) таким образом, чтобы избежать излишней нагрузки на базу данных. В дополнение к собранным показателям, коллектор будет также передавать APM данные о продолжительности сбора информации, на основании чего можно будет сделать выводы о накладных расходах и скорректировать значения параметров. Можно развить реализацию коллектора так, чтобы он самостоятельно снижал частоту обращения к базе данных при увеличении продолжительности каждой операции по сбору данных.

Далее рассмотрим реализацию связывания результатов запроса с пространством имен трассировки. В ее основе лежит свойство queryMaps, в котором используются экземпляры внутреннего класса org.runtimemonitoring.spring.collectors.jdbc.QueryMap. Он содержит четыре простых свойства:

  • valueColumn: содержит отсчитываемый от нуля порядковый номер колонки каждой строки результата, значение в которой должно трассироваться в APM. В данном случае трассироваться будут значения count(*);
  • segments: задает сегмент в пространстве имен трассировки. В данном случае в качестве сегмента используется строковая константа;
  • metricName: содержит имя метрики в пространстве имен трассировки (также задано в виде константы);
  • metricType: содержит тип метрики в соответствии с классификацией ITracer. В данном случае используется закрепленный (sticky) int.

Существует возможность определять несколько конфигурационных массивов queryMap для одного коллектора, что позволяет трассировать несколько значений, полученных в результате одного запроса. Далее мы покажем, как можно управлять вставкой данных в пространства имен трассировки при помощи токенов (пока параметры задаются строковыми константами). Например, запрос можно изменить следующим образом: select count(*), 'Sales Order Activity', 'Order Rate' from sales_order where order_date > ? and order_date < ?. Теперь сегменты и имя метрики определяются результатами самого запроса. Для связывания необходимо задать значение параметра segments равным токену {1}, а metricName - {2}. В некоторых редко встречающихся ситуациях даже тип метрики (metricType) может выбираться из результатов запроса, в этих случаях также используются токены. Дерево APM, построенное на основе собранных таким образом данных, показано на рисунке 10.

Рисунок 10. Мониторинг числа поступивших заказов в единицу времени
Sales order rate monitoring

Мониторинг производительности базы данных

Аналогичным образом JDBCCollector может выбирать и трассировать данные из специальных представлений базы данных, содержащих показатели производительности. В нашем примере используется PostgreSQL, в котором для подобных целей применяются таблицы, называемые статистическими представлениями, чьи имена начинаются с префикса pg_stat. Подобные представления, к которым можно обращаться обычным образом через JDBC, существуют во многих базах данных. Таким образом, при мониторинге все того же сайта электронной коммерции можно настроить JDBCCollector для мониторинга интенсивности обращения к пяти наиболее часто используемым таблицам. При этом можно также снимать показатели интенсивности использования индексов, построенных на полях этих таблиц. Доступ к данной статистике осуществляется при помощи SQL-запроса, показанного в листинге 14.

Листинг 14. Описание коллектора для мониторинга интенсивности обращения к таблице и индексам
<bean name="DatabaseIO"
   class="org.runtimemonitoring.spring.collectors.jdbc.JDBCCollector"
   init-method="springStart">
   <property name="dataSource" ref="RuntimeDataSource" />
   <property name="scheduler" ref="CollectionScheduler" />
   <property name="availabilityNameSpace"
      value="Database,Database Availability,Postgres,Runtime" />
   <property name="query">
      <value><![CDATA[
   SELECT schemaname, relname, SUM(seq_scan) as seq_scan,
   SUM(seq_tup_read) as seq_tup_read,
   SUM(idx_scan) as idx_scan, SUM(idx_tup_fetch) as idx_tup_fetch,
   COALESCE(idx_tup_fetch,0) + seq_tup_read
      + seq_scan + COALESCE(idx_scan, 0) as total
   FROM pg_stat_all_tables
   where schemaname = 'public'
   and (COALESCE(idx_tup_fetch,0) + seq_tup_read
      + seq_scan + COALESCE(idx_scan, 0)) > 0
   group by schemaname, relname, idx_tup_fetch,
      seq_tup_read, seq_scan, idx_scan
   order by total desc
   LIMIT 5 ]]>
      </value>
   </property>
   <property name="tracingNameSpace"
     value="Database,Database Performance,Postgres,Runtime,Objects,{beanName}"
   />
   <property name="frequency" value="20000" />
   <property name="queryMaps">
    <set>
    <bean class="org.runtimemonitoring.spring.collectors.jdbc.QueryMap">
      <property name="valueColumn" value="2"/>
      <property name="segments" value="{0},{1}"/>
      <property name="metricName" value="SequentialScans"/>
      <property name="metricType" value="SDLONG"/>
    </bean>
    <bean class="org.runtimemonitoring.spring.collectors.jdbc.QueryMap">
      <property name="valueColumn" value="3"/>
      <property name="segments" value="{0},{1}"/>
      <property name="metricName" value="SequentialTupleReads"/>
      <property name="metricType" value="SDLONG"/>
    </bean>
    <bean class="org.runtimemonitoring.spring.collectors.jdbc.QueryMap">
      <property name="valueColumn" value="4"/>
      <property name="segments" value="{0},{1}"/>
      <property name="metricName" value="IndexScans"/>
      <property name="metricType" value="SDLONG"/>
    </bean>
    <bean class="org.runtimemonitoring.spring.collectors.jdbc.QueryMap">
      <property name="valueColumn" value="5"/>
      <property name="segments" value="{0},{1}"/>
      <property name="metricName" value="IndexTupleReads"/>
      <property name="metricType" value="SDLONG"/>
    </bean>
    </set>
</property>
</bean>

Данный запрос выполняется каждые 20 секунд и возвращает следующие показатели для пяти наиболее активно использующихся таблиц:

  • имя схемы базы данных;
  • имя таблицы;
  • общее число последовательных сканирований;
  • общее число кортежей, полученных в результате последовательных сканирований;
  • общее число сканирований с учетом индексов;
  • общее число кортежей, полученных в результате сканирования по индексам.

Поскольку последние четыре показателя являются монотонно возрастающими, для их трассировки используется режим SDLONG (закрепленная дельта-трассировка данных типа long). Обратите внимание, что в листинге 14 определены четыре ассоциативных массива QueryMap, описывающие сопоставление колонок с пространством имен трассировки.

В данном примере таблица sales_order не содержит индексов, поэтому мониторинг выявит большое число операций последовательного сканирования (иногда называемого табличным сканированием на жаргоне баз данных). Это весьма неэффективный метод выборки данных, поскольку он требует просмотра каждой строки в таблице. То же самое относится к числу последовательных чтений – это не что иное как число кортежей, выбранных в результате последовательного сканирования. Существует немаловажное различие между понятиями строки в таблице и кортежа, однако для нашего примера это непринципиально. За разъяснениями можно обратиться к документации PostgreSQL, ссылка на которую находится в разделе Ресурсы. Так или иначе, при первом же взгляде на собранную статистику, отображаемую APM, становится очевидно, что в базе данных не хватает индекса (рисунок 11).

Рисунок 11. Мониторинг числа операций сканирования и чтения при последовательном доступе
Sequential Reads

Оценив подобную ситуацию, можно выполнить несколько операторов SQL для создания необходимых индексов. По прошествии короткого времени число операций последовательного сканирования упадет до нуля, в то время как операции, использующие индекс, наоборот, появятся (рисунок 12).

Рисунок 12. Мониторинг числа операций сканирования и чтения после создания индекса
After the Index

Положительный эффект от создания индекса быстро распространяется на другие показатели системы. В частности, невооруженным глазом становится заметно снижение степени загрузки процессора на сервере базы данных (рисунок 13).

Рисунок 13. Нагрузка на процессор после создания индекса
CPU After the Index

Степень готовности базы данных

Далее мы рассмотрим понятие степени готовности базы данных при работе через JDBC. В наиболее простой форме готовность базы данных может измеряться стандартными средствами класса JDBCCollector. Если задано значение свойства availabilityNameSpace, то коллектор будет собирать и трассировать следующие показатели с учетом указанного пространства имен:

  • степень готовности: 1 в случае успешного подключения к базе данных, 0 в обратном случае;
  • время соединения: время, затраченное на подключение к базе данных.

При использовании пула соединений, как правило, подключение к базе данных осуществляется очень быстро. Однако многие реализации пулов JDBC могут выполнять предварительно заданные команды SQL перед передачей соединения клиенту, поэтому измерение времени подключения оправдано. Кроме того, время может возрастать в периоды высокой нагрузки на базу данных. В качестве альтернативного варианта можно предложить использовать отдельный источник данных исключительно для тестирования степени готовности. Данный источник можно настроить таким образом, чтобы каждый запрос приводил к попытке открытия нового соединения, т.е. без использования пула. Дерево показателей готовности базы данных PostgreSQL показано на рисунке 14. Пример использования свойства availabilityNameSpace приведен выше, в листинге 14.

Рисунок 14. Тестирование степени готовности базы данных на этапе выполнения
The runtime database availability check

В моей практике встречались случаи, когда определение состояния базы данных требовало выполнения нескольких зависимых друг от друга запросов. Например, запрос на определение состояния, выполняющийся над базой данных А, требует параметров, значения которых могут быть получены только в результате запроса к базе данных Б. Для этого можно создать два объекта JDBCCollector следующим образом:

  • изначально первый запрос к базе данных Б можно сделать инертным, т.е. частота его выполнения устанавливается равной нулю. Данный экземпляр JDBCCollector при этом также реализует интерфейс IBindVariableProvider. Это означает, что он предоставляет переменные связывания и их значения для другого коллектора;
  • второй коллектор указывает первый в качестве источника связывания, т.е. в запросе будут использоваться значения, полученные в результате работы первого коллектора.

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


Мониторинг JMS и других служб сообщений

В данном разделе мы рассмотрим методы мониторинга работоспособности и быстродействия служб обмена сообщениями. Подобные службы, в частности реализующие интерфейс JMS, также называются промежуточным слоем ПО, основанным на передаче сообщений (message-oriented middleware – MOM). Они играют критически важную роль во многих приложениях и, как и остальные компоненты, требуют мониторинга производительности. Зачастую службы сообщений предоставляют асинхронные точки вызова, работающие по принципу "вызвал и забыл" (fire-and-forget). Мониторинг функционирования подобных точек осложняется тем, что в ряде случаев может создаться обманчивое впечатление, что служба работает нормально, т.е. ее методы вызываются с высокой частотой и быстро заканчивают выполнение. Однако проблема может скрываться на следующем уровне, например, передача сообщений целевым объектам может осуществляться крайне медленно или не осуществляться вовсе.

Большинство служб сообщений выполняются либо внутри JVM, либо реализуются в виде одного или нескольких процессов на определенном компьютере (или группе компьютеров) в сети. Таким образом, их мониторинг включает в себя моменты, характерные для мониторинга любых других целевых сервисов. В частности, может быть необходим мониторинг атрибутов JMX в JVM, ресурсов хоста, на котором выполняется служба, отзывчивости сети, а также других показателей выполнения процессов службы, например, объема используемой памяти и загрузки процессора.

Далее рассмотрим четыре разновидности мониторинга служб сообщений, причем три из них специфичны для JMS, а одна применима в случае использования проприетарных API.

  1. Для измерения пропускной способности сервиса коллектор периодически передает набор синтетических тестовых сообщений, а затем засекает время ответа. После этого вычисляются такие показатели как продолжительность отправки, получения, а также общее время цикла. Наконец, добавляются возможные данные о сбоях или тайм-аутах, и вся информация трассируется системе APM.
  2. Многие реализации JMS на Java предоставляют доступ к показателям производительности через JMX, поэтому мы ненадолго вернемся к вопросу создания средств JMX-мониторинга на основе коллекторов Spring.
  3. Некоторые службы поддерживают собственные проприетарные API для управления объектами-посредниками (брокерами) при обмене сообщениями. Как правило, эти API позволяют получать данные о производительности выполняющегося сервиса.
  4. Если ни один из предложенных выше вариантов неприменим, то некоторые данные можно получить, используя стандартные конструкции JMS, такие как javax.jms.QueueBrowser.

Мониторинг на основе синтетических сообщений

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

  • ожидают поступления тестовых сообщений от центральной системы мониторинга;
  • получают эти сообщения;
  • регистрируют время получения каждого сообщения;
  • перенаправляют их целевой службе сообщений, которая посылает ответы системе мониторинга.

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

Рисунок 15. Мониторинг на основе синтетических сообщений
Synthetic messages

Несмотря на то что данный подход отвечает практически всем нуждам мониторинга, у него все же есть некоторые недостатки, а именно:

  • необходимо развертывание и управление удаленным агентом;
  • необходимо создание дополнительных очередей для обмена тестовыми сообщениями;
  • несмотря на то что некоторые службы сообщений поддерживают возможность динамического создания очередей "на лету", для основной массы сервисов подобные очереди приходится создавать вручную через административные интерфейсы либо программно через специальные API.

Мы рассмотрим альтернативный способ, применимый в случае, если служба реализует JMS (при этом многие службы предоставляют аналогичные возможности). Он заключается в использовании временной очереди или темы, которая создается динамически посредством стандартного API JMS и не требует никаких административных действий. Преимущество заключается в том, что все временные структуры прозрачны для всех клиентов JMS за исключением их создателя.

В данном примере мы будем использовать класс JMSCollector, который создает временную очередь в момент запуска. Получив сигнал от планировщика, он отправляет серию тестовых сообщений во временную очередь целевой службы JMS, а затем принимает ответы. Таким образом можно эффективно оценивать пропускную способность сервера JMS, не создавая стандартных очередей и не развертывая удаленных агентов. Принцип работы иллюстрируется на рисунке 16.

Рисунок 16. Использование временной очереди для синтетических сообщений
Synthetic messages with a temporary queue

В качестве коллектора Spring в этом примере используется класс org.runtimemonitoring.spring.collectors.jms.JMSCollector. Он имеет достаточно простой набор зависимостей, многие из которых вам уже знакомы по предыдущим примерам. Для соединения с JMS необходима класс-фабрика javax.jms.ConnectionFactory. Для предоставления ссылки на данную фабрику будет использоваться тот же объект Spring, который ранее применялся для обращения к фабрике соединений с сервисом WPM в Windows. Как вы помните, для этого необходим экземпляр класса org.springframework.jndi.JndiTemplate, который предоставляет JNDI-соединение целевому сервису JMS, а также экземпляр класса org.springframework.jndi.JndiObjectFactoryBean, который использует JNDI-соединение для получения ссылки на фабрику соединений JMS.

Для повышения гибкости генерации синтетических сообщений классу JMSCollector передается набор объектов-фабрик, реализующих интерфейс org.runtimemonitoring.spring.collectors.jms.ISyntheticMessageFactory. Функцией этих объектов является создание массива сообщений. Коллектор вызывает каждую из доступных ему фабрик, получает сгенерированные сообщения, а затем выполняет цикл их отправки и получения ответов. Таким образом можно тестировать пропускную способность службы JMS, варьируя размер и число сообщений.

Каждому экземпляру ISyntheticMessageFactory присваивается имя, добавляемое затем объектом JMSCollector к пространству имен трассировки. Полностью конфигурационный файл Spring приведен в листинге 15.

Листинг 15. Описание объекта JMSCollector, работающего на основе синтетических сообщений
<!-- Провайдер JNDI -->
<bean id="jbossJndiTemplate" class="org.springframework.jndi.JndiTemplate">
   <property name="environment"><props>
      <prop key="java.naming.factory.initial">
         org.jnp.interfaces.NamingContextFactory
      </prop>
      <prop key="java.naming.provider.url">
         localhost:1099
      </prop>
      <prop key="java.naming.factory.url.pkgs">
         org.jboss.naming:org.jnp.interfaces
      </prop>
   </props></property>
</bean>

<!-- Провайдер фабрики соединений JMS -->
<bean id="RealJMSConnectionFactory"
   class="org.springframework.jndi.JndiObjectFactoryBean">
   <property name="jndiTemplate" ref="jbossJndiTemplate" />
   <property name="jndiName" value="ConnectionFactory" />
</bean>

<!-- Набор фабрик синтетических сообщений -->
<bean id="MessageFactories" class="java.util.HashSet">
   <constructor-arg><set>
     <bean
      class="org.runtimemonitoring.spring.collectors.jms.SimpleSyntheticMessageFactory">
         <property name="name" value="MapMessage"/>
         <property name="messageCount" value="10"/>
      </bean>
     <bean
     class="org.runtimemonitoring.spring.collectors.jms.ByteArraySyntheticMessageFactory">
         <constructor-arg type="java.net.URL"
       value="file:///C:/projects3.3/RuntimeMonitoring/lib/jta26.jar"/>
         <property name="name" value="ByteMessage"/>
         <property name="messageCount" value="1"/>
      </bean></set>
   </constructor-arg>
</bean>

<!-- Коллектор JMS -->
<bean id="LocalJMSSyntheticMessageCollector"
   class="org.runtimemonitoring.spring.collectors.jms.JMSCollector"
   init-method="springStart">
   <property name="scheduler" ref="CollectionScheduler" />
   <property name="logErrors" value="true" />
   <property name="tracingNameSpace" value="JMS,Local,Synthetic Messages" />
   <property name="frequency" value="5000" />
   <property name="messageTimeOut" value="10000" />
   <property name="initialDelay" value="3000" />
   <property name="messageFactories" ref="MessageFactories"/>
   <property name="queueConnectionFactory" ref="RealJMSConnectionFactory"/>
</bean>

В листинге 15 определены следующие фабрики сообщений:

  • фабрика сообщений типа javax.jms.MapMessage, которая помещает системные свойства JVM в тело каждого сообщения. За каждый цикл фабрика генерирует 10 сообщений;
  • фабрика сообщений типа javax.jms.ByteMessage, которая загружает содержимое указанного JAR-файла в тело каждого сообщения. За каждый цикл фабрика генерирует одно сообщение.

На рисунке 17 показано дерево метрик, построенное APM-системой по результатам отправки-получения синтетических сообщений. Обратите внимание, что размер полезного содержимого сообщения дописывается в конце имени метрики, которая соответствует фабрике javax.jms.ByteMessage.

Рисунок 17. APM-дерево показателей (в миллисекундах), собранных путем мониторинга на основе синтетических сообщений с использованием временной очереди
APM Tree for synthetic messages with a temporary queue

Мониторинг служб сообщений при помощи JMX

Такие службы сообщений как JBossMQ и ActiveMQ поддерживают интерфейсы управления на основе JMX. Мы уже касались мониторинга при помощи JMX в первой статье серии. Теперь пришло время к нему вернуться и рассмотреть реализацию Spring-коллектора на основе org.runtimemonitoring.spring.collectors.jmx.JMXCollector, а также его использование для мониторинга службы JBossMQ. Поскольку JMX, как стандарт, остается неизменным, описанный ниже подход можно применять для мониторинга любых показателей, доступных через JMX.

Для работы JMXCollector необходимы следующие два компонента:

  • экземпляр javax.management.MBeanServerConnection для локальной службы JBossMQ. Ссылку на этот экземпляр предоставляет объект под именем LocalRMIAdaptor. В этом случае соединение осуществляется через поиск JBoss-ресурса org.jboss.jmx.adaptor.rmi.RMIAdaptor в JNDI. Ссылки на других провайдеров получить, как правило, не составляет труда при условии предоставления необходимых аутентификационных данных. Spring включает в себя пакет org.springframework.jmx.support, в котором содержится ряд фабричных классов, которые можно использовать для получения ссылок на различные реализации MBeanServerConnection (см. Ресурсы);
  • объект, содержащий набор параметров сбора данных через JMX. Данный объект представляет собой коллекцию экземпляров org.runtimemonitoring.spring.collectors.jmx.JMXCollection. В параметрах задаются директивы, указывающие JMXCollector, какие показатели JMX он должен собирать.

Класс JMXCollection обладает рядом свойств, характерных для всех средств JMX-мониторинга. Основными из них являются следующие:

  • targetObjectName: в данном свойстве содержится полное имя (ObjectName) объекта MBean, к которому будет обращаться коллектор. Вместо имени может указываться шаблон – в этом случае коллектор будет запрашивать у JMX-агентов ссылки на все объекты JMX, чье имя соответствует шаблону, а затем считывать атрибуты каждого из них;
  • segments: сегменты пространства имен трассировки, которые будут использоваться при передаче собранных метрик системе APM;
  • metricNames: массив имен метрик. Каждый атрибут целевого объекта MBean должен соответствовать некоторой метрике. Если в качестве значения используется символ '*', то коллектор будет использовать имена атрибутов MBean в качестве метрик при трассировке;
  • attributeNames: массив имен атрибутов, значения которых должны быть получены у каждого объекта MBean;
  • metricTypes или defaultMetricType: массив типов метрик, которые должны использоваться для каждого атрибута, либо тип по умолчанию, который будет применяться ко всем атрибутам.

Поддержка шаблонов имен объектов MBean является очень удобной функцией, так как с ее помощью можно динамически обнаруживать целевые объекты мониторинга вместо того чтобы по отдельности настраивать коллектор для работы с каждым объектом. Управляя очередями JMS, JBossMQ создает отдельный объект MBean для каждой из них, поэтому для мониторинга глубины очередей (числа сообщений) достаточно задать один шаблон имени: jboss.mq.destination:service=Queue,*, которому будут соответствовать имена всех нужных объектов MBean. Однако имя очереди придется определять динамически, поскольку обнаружение объектов происходит "на лету". В данном конкретном случае можно воспользоваться тем фактом, что имя очереди содержится в свойстве name имени каждого из объектов. Например, обнаруженный объект MBean может иметь имя jboss.mq.destination:service=Queue,name=MyQueue. Наконец, необходимо связывать свойства целевых объектов с пространствами имен трассировки, чтобы различать показатели, поступающие от разных источников. Для этого можно использовать токены аналогично строковым токенам при работе с JDBCCollector. Класс JMXCollector поддерживает следующий набор токенов:

  • {target-property:name}: заменяется на значение именованного свойства в объектном имени MBean. Пример: {target-property:name};
  • {this-property:name}: заменяется на значение именованного свойства в объектном имени коллектора. Пример: {this-property:type};
  • {target-domain:index}: заменяется на сегмент объектного имени MBean с указанным индексом. Пример: {target-domain:2};
  • {this-domain:index}: заменяется на сегмент объектного имени коллектора с указанным индексом. Пример: {target-domain:0}.

Сокращенная версия XML-конфигурации объекта JMXCollector для мониторинга службы JBossMQ приведена в листинге 16.

Листинг 16. Описание объекта JMXCollector для мониторинга локальной службы JBossMQ
<!-- RMI-провайдер объектов MBeanServerConnection JBoss -->
<bean id="LocalRMIAdaptor"
   class="org.springframework.jndi.JndiObjectFactoryBean">
   <property name="jndiTemplate" ref="jbossJmxJndiTemplate" />
   <property name="jndiName" value="jmx/invoker/RMIAdaptor" />
</bean>

<!-- Профиль JMXCollection в JBossMQ -->
<bean id="StandardJBossJMSProfile"
   class="org.runtimemonitoring.spring.collectors.collections.InitableHashSet"
   init-method="init" >
   <constructor-arg><set>
      <bean class="org.runtimemonitoring.spring.collectors.jmx.JMXCollection">
         <property name="targetObjectName" value="*:service=Queue,*"/>
         <property name="segments" value="Destinations,Queues,{target-property:name}"/>
         <property name="metricNames" value="*"/>
         <property name="attributeNames"
       value="QueueDepth,ScheduledMessageCount,InProcessMessageCount,ReceiversCount"/>
         <property name="defaultMetricType" value="SINT"/>
       </bean>
       <bean class="org.runtimemonitoring.spring.collectors.jmx.JMXCollection">
         <property name="targetObjectName" value="jboss.mq:service=DestinationManager"/>
         <property name="segments" value="Destniations,{target-property:service}"/>
         <property name="metricNames" value="*"/>
         <property name="attributeNames" value="ClientCount"/>
         <property name="defaultMetricType" value="SINT"/>
       </bean>
       <!-- MBeans Also Included: Topics, ThreadPool, MessageCache -->
       </set>
    </constructor>
</bean>

<!-- Экземпляр JMXCollector для мониторинга локального сервера JBoss MQ -->
<bean id="LocalJBossCollector"
   class="org.runtimemonitoring.spring.collectors.jmx.JMXCollector"
      init-method="springStart">
   <property name="server" ref="LocalRMIAdaptor" />
   <property name="scheduler" ref="CollectionScheduler" />
   <property name="logErrors" value="true" />
   <property name="tracingNameSpace" value="JMS,Local" />
   <property name="objectName"
      value="org.runtime.jms:name=JMSQueueMonitor,type=JMXCollector" />
   <property name="frequency" value="10000" />
   <property name="initialDelay" value="10" />
   <property name="jmxCollections" ref="StandardJBossJMSProfile"/>
</bean>

На рисунке 18 показано APM-дерево показателей JMS-очередей сервера JBossMQ, собранных при помощи JMXCollector:

Рисунок 18. APM-дерево показателей, собранных при помощи мониторинга очередей JBossMW через JMX
APM tree for JMX monitoring of JBossMQ queues

Мониторинг JMS-очередей при помощи браузеров

Если ваша реализация JMS не предоставляет специальных API для мониторинга JMS-очередей, то можно воспользоваться интерфейсом javax.jms.QueueBrowser. Этот интерфейс (браузер очередей) ведет себя практически так же, как javax.jms.QueueReceiver за тем исключением, что после просмотра сообщения не удаляются из очереди и доставляются своим получателям. Важным показателем работы очереди является ее глубина. Как правило, в большинстве систем, основанных на обмене сообщениями, скорость работы компонентов, генерирующих сообщения, превышает скорость работы получателей. Степень этого дисбаланса можно охарактеризовать путем подсчета числа сообщений, находящихся в очереди в промежуточном слое системы (брокере). Таким образом, если недоступны другие методы оценки глубины очередей, то всегда можно использовать браузеры в качестве запасного варианта. Однако этот метод имеет ряд недостатков, главным из которых является необходимость выборки всех сообщений из очереди для вычисления ее глубины. Очевидно, что это связано с серьезными накладными расходами и работает значительно медленнее, чем специальные интерфейсы управления (а также, скорее всего, увеличивает нагрузку на ресурсы сервера JMS). Кроме того, стоит учитывать, что в условиях высокой интенсивности обмена сообщениями вычисленная глубина, вероятно, окажется неверной уже к моменту окончания просмотра очереди. Тем не менее для большинства нужд мониторинга хватает приближенных оценок, тем более что при высокой интенсивности даже самые точные оценки окажутся некорректными в следующий момент времени.

У браузеров есть одно преимущество: благодаря просмотру всех сообщений они способны определять возраст самого раннего из них. В некоторых случаях этот показатель очень важен, но его сложно получить при помощи даже наиболее развитых интерфейсов управления. Представьте себе JMS-очередь, использующуюся в процессе передачи критически важных сообщений. При этом наблюдается стандартная разница в скорости между отправителем и получателем сообщений, а процесс передачи организован таким образом, что в очереди, как правило, присутствуют одно–два сообщения. Скорее всего, это происходит из-за небольшой латентности, поэтому, обращаясь к очереди 1 раз в минуту, система мониторинга должна видеть разные сообщения. А вдруг они останутся теми же? Если они разные, то все в порядке, однако возможна ситуация, при которой сбой произошел одновременно в работе как отправителя, так и получателя, что привело к тому, что в очереди постоянно находятся несколько сообщений. Эту ситуацию можно диагностировать, если параллельно с оценкой глубины очереди определять возраст наиболее раннего сообщения. При нормальной работе возраст не должен превышать нескольких секунд, но если произошел вышеупомянутый сбой, то уже через один цикл обращения к очереди APM-система обнаружит подозрительно старые сообщения.

Использование данной возможности продемонстрировано в классе Spring-коллектора org.runtimemonitoring.spring.collectors.jmx.JMSBrowserCollector. Он имеет два дополнительных конфигурируемых свойства: javax.jms.ConnectionFactory (фабрику соединений, аналогичную той, что использовалась в классе JMSCollector), а также набор очередей для просмотра. Описание свойств данного коллектора в XML показано в листинге 17.

Листинг 17. Описание экземпляра JMSBrowserCollector для мониторинга локальной службы JBossMQ
<!-- Набор очередей для просмотра -->
<bean id="BrowserMonitorQueues" class="java.util.HashSet">
   <constructor-arg>
      <set>
         <bean id="QueueA"
            class="org.springframework.jndi.JndiObjectFactoryBean">
            <property name="jndiTemplate" ref="jbossJndiTemplate" />
            <property name="jndiName" value="queue/A" />
         </bean>
               <bean id="QueueB"
            class="org.springframework.jndi.JndiObjectFactoryBean">
            <property name="jndiTemplate" ref="jbossJndiTemplate" />
            <property name="jndiName" value="queue/B" />
         </bean>
      </set>
   </constructor-arg>
</bean>

<!-- Браузер очередей JMS -->
<bean id="LocalQueueBrowserCollector"
   class="org.runtimemonitoring.spring.collectors.jms.JMSBrowserCollector"
     init-method="springStart">
   <property name="scheduler" ref="CollectionScheduler" />
   <property name="logErrors" value="true" />
   <property name="tracingNameSpace" value="JMS,Local,Queue Browsers" />
   <property name="frequency" value="5000" />
   <property name="initialDelay" value="3000" />
   <property name="queueConnectionFactory" ref="RealJMSConnectionFactory"/>
   <property name="queues" ref="BrowserMonitorQueues"/>
</bean>

Дерево показателей, построенное APM по собранным данным, показано на рисунке 19.

Рисунок 19. APM-дерево показателей (возраст сообщений в миллисекундах), собранных объектом JMSBrowserCollector
APM tree for JMSBrowserCollector

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

Мониторинг служб сообщений при помощи проприетарных API

Некоторые реализации служб сообщений предоставляют специальные интерфейсы для выполнения административных действий, в том числе мониторинга. Несколько подобных интерфейсов используют собственную систему передачи сообщений, работающую по принципу "запрос-ответ" для отправки административных запросов. В частности, ActiveMQ (см. Ресурсы) предоставляется интерфейс управления JMS, основанный на сообщениях, а также API для управления через JMX. Для того чтобы реализовать предоставленный проприетарный интерфейс, необходимо создать специальный коллектор. В нашем примере будет использоваться коллектор для системы WebSphere® MQ, ранее известной под именем MQ Series. Данный коллектор будет вызывать методы следующих API:

  • MS0B: Java-классы WebSphere MQ для PCF: интерфейс PCF – это административный API для системы WebSphere MQ;
  • Базовые Java-классы WebSphere MQ: данный интерфейс, ранее называвшийся MA88, был впоследствии интегрирован в ядро библиотеки классов WebSphere MQ (см. Ресурсы).

На самом деле без одного из этих интерфейсов можно обойтись, но будет полезно показать на примере использование двух различных проприетарных API.

Реализация Spring-коллектора содержится в классе org.runtimemonitoring.spring.collectors.mq.MQCollector. Он будет осуществлять мониторинг всех очередей службы WebSphere MQ, вычисляя их глубину, а также текущее число дескрипторов ввода и вывода. Конфигурация данного коллектора приведена в листинге 18.

Листинг 18. Описание коллектора для мониторинга службы WebSphere MQ
<bean id="MQPCFAgentCollector"
   class="org.runtimemonitoring.spring.collectors.mq.MQCollector"
      init-method="springStart">
   <property name="scheduler" ref="CollectionScheduler" />
   <property name="logErrors" value="true" />
   <property name="tracingNameSpace" value="MQ, Queues" />
   <property name="frequency" value="5000" />
   <property name="initialDelay" value="3000" />
   <property name="channel" value="SERVER1.QM2"/>
   <property name="host" value="192.168.7.32"/>
   <property name="port" value="50002"/>
</bean>

Интерес представляют следующие конфигурируемые свойства:

  • host: IP-адрес сервера WebSphere MQ;
  • port: номер порта, который слушает сервер WebSphere MQ в ожидании входящих соединений;
  • channel: канал WebSphere MQ, с которым будет соединяться коллектор.

Обратите внимание, что в данном примере не задаются никакие параметры для аутентификации.

Дерево показателей, собранных классом org.runtimemonitoring.spring.collectors.mq.MQCollector, показано на рисунке 20.

Рисунок 20. APM-дерево показателей, собранных экземпляром MQCollector
APM Tree for MQCollector

На этом мы заканчиваем рассмотрение мониторинга служб обмена сообщениями. Далее, как и было обещано, перейдем к мониторингу с использованием протокола SNMP.


Мониторинг при помощи SNMP

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

Одним из главных аспектов в SNMP является понятие агентов, которые используются в качестве посредников при передаче запросов целевым устройствам. Сам протокол SNMP достаточно простой и низкоуровневый, поэтому SNMP-агенты обычно легко внедряются внутрь различных сетевых устройств и программных служб. Таким образом, SNMP представляет собой единый стандартный протокол, который может использоваться для мониторинга большинства сервисов, составляющих среду выполнения конкретного приложения. Кроме того, SNMP широко применяется для обнаружения сервисов путем сканирования IP-адресов и портов. Эта возможность SNMP оказывается очень полезной при организации системы мониторинга, так как позволяет автоматически поддерживать централизованные репозитории целевых объектов мониторинга. SNMP во многом напоминает JMX. Несмотря на очевидные различия между ними, можно провести ряд параллелей, поэтому был реализован ряд решений по организации взаимодействия между ними. Точки соприкосновения JMX и SNMP приведены в таблице 1.

Таблица 1. Сравнение SNMP и JMX
Понятия в SNMPАналогичные понятия в JMX
АгентАгент или MBeanServer
МенеджерКлиент, MBeanServerConnection, адаптер протокола
MIBMBean, ObjectName, MBeanInfo
OIDObjectName и ObjectName + имя атрибута (Attribute)
ЛовушкаУведомление (Notification)
GET, SETgetAttribute, setAttribute
BULKGETgetAttributes

С точки зрения мониторинга, все, что требуется для отправки запросов через SNMP – это следующий набор параметров:

  • адрес хоста: IP-адрес или имя хоста, на котором выполняется агент SNMP;
  • порт: номер порта, который прослушивается агентом SNMP. На одном компьютере могут одновременно выполняться несколько SNMP-агентов, при этом каждый из них прослушивает собственный порт;
  • версия протокола: в процессе своего развития SNMP претерпел множество изменений, поэтому агенты могут поддерживать разные версии протокола. Всего существуют три версии: 1, 2с и 3;
  • сообщество: в SNMP сообществом (community) называют области, определенные административным образом. Клиенты SNMP не могут обращаться к агентам неизвестных сообществ, поэтому можно считать, что имя сообщества в какой-то степени служит для аутентификации;
  • OID: уникальный идентификатор метрики или группы метрик. Формат OID представляет собой последовательность целых чисел, разделенных точками. Например, показатель одноминутной нагрузки на Linux-хост имеет OID .1.3.6.1.4.1.2021.10.1.3.1, в то время как OID .1.3.6.1.4.1.2021.10.1.3 идентифицирует показатели 1-, 5- и 15-минутной нагрузки.

В добавление к имени сообщества некоторые агенты могут использовать дополнительные уровни аутентификации.

Перед тем как углубиться в API SNMP, следует упомянуть о том, что метрики SNMP могут быть получены при помощи двух консольных утилит: snmpget, которая возвращает значение метрики, ассоциированной с OID, и snmpwalk, которая возвращает подмножество значений. Таким образом, мы можем расширить набор команд (CommandSet) класса ShellCollector для трассировки метрик по их OID. В листинге 19 показан пример использования snmpwalk для получения показателей 1-, 5- и 15-минутной нагрузки (вывод приведен в необработанном виде). В примере используются SNMP 2c и открытое (public) сообщество.

Листинг 19. Пример использования snmpwalk
$> snmpwalk -v 2c -c public 127.0.0.1 .1.3.6.1.4.1.2021.10.1.3
UCD-SNMP-MIB::laLoad.1 = STRING: 0.22
UCD-SNMP-MIB::laLoad.2 = STRING: 0.27
UCD-SNMP-MIB::laLoad.3 = STRING: 0.26

$> snmpwalk -v 2c -c public 127.0.0.1 .1.3.6.1.4.1.2021.10.1.3 \
   | awk '{ split($1, name, "::"); print name[2] " " $4}'
laLoad.1 0.32
laLoad.2 0.23
laLoad.3 0.22

Второй вызов может быть легко описан при помощи следующего набора команд на моем Linux (листинг 20).

Листинг 20. Набор команд для разбора вывода утилиты snmpwalk
<CommandSet name="LinuxSNMP">
   <Commands><Command>
      <shellCommand><![CDATA[snmpwalk -v 2c -c public 127.0.0.1
      .1.3.6.1.4.1.2021.10.1.3 | awk '{ split($1, name, "::"); print name[2] "
      " $4}']]></shellCommand>
         <Extractors><CommandResultExtract>
             <paragraph id="0" name="System Load Summary" header="false"/>
                <columns entryName="0" values="1" offset="0">
                   <namemapping from="laLoad.1" to="1 Minute Load"/>
                    <namemapping from="laLoad.2" to="5 Minute Load"/>
                    <namemapping from="laLoad.3" to="15 Minute Load"/>
                </columns>
                <tracers default="SINT"/>
                <lineSplit>\n</lineSplit>
          </CommandResultExtract></Extractors>
      </Command></Commands>
</CommandSet>

Существует несколько коммерческих и открытых реализаций API SNMP на Java. Далее мы будем работать с базовым Spring-классом коллектора org.runtimemonitoring.spring.collectors.snmp.SNMPCollector, который внутри вызывает библиотеку joeSNMP (см. Ресурсы). Необходимыми параметрами для коллектора являются следующие:

  • hostName: IP-адрес или имя целевого хоста;
  • port: номер порта, который прослушивается целевым SNMP-агентом (по умолчанию 161);
  • targets: набор OID целевых метрик. Каждый элемент в наборе представляется в виде экземпляра org.runtimemonitoring.spring.collectors.snmp.SNMPCollection, который имеет следующие конфигурируемые свойства:
    • nameSpace: суффикс пространства имен трассировки;
    • oid: идентификатор OID целевой метрики;
  • protocol: версия протокола SNMP. 0 (значение по умолчанию) означает первую версию, а 1 – вторую;
  • community: имя сообщества SNMP (по умолчанию public);
  • retries: число попыток выполнения операции (по умолчанию 1);
  • timeOut: значение тайм-аута для SNMP-вызовов в миллисекундах (по умолчанию 5000).

В листинге 21 приведен пример конфигурирования SNMPCollector для мониторинга локального сервера приложений JBoss.

Листинг 21. Описание экземпляра SNMPCollector
<!-- Список OID нужных метрик и соответствующие им имена показателей -->
<bean id="JBossSNMPProfile" class="java.util.HashSet">
   <constructor-arg><set>
     <bean class="org.runtimemonitoring.spring.collectors.snmp.SNMPCollection">
             <property name="nameSpace" value="Free Memory"/>
             <property name="oid" value=".1.2.3.4.1.2"/>
     </bean>
     <bean class="org.runtimemonitoring.spring.collectors.snmp.SNMPCollection">
             <property name="nameSpace" value="Max Memory"/>
             <property name="oid" value=".1.2.3.4.1.3"/>
     </bean>
     <bean class="org.runtimemonitoring.spring.collectors.snmp.SNMPCollection">
             <property name="nameSpace" value="Thread Pool Queue Size"/>
             <property name="oid" value=".1.2.3.4.1.4"/>
     </bean>
     <bean class="org.runtimemonitoring.spring.collectors.snmp.SNMPCollection">
             <property name="nameSpace" value="TX Manager, Rollback Count"/>
             <property name="oid" value=".1.2.3.4.1.7"/>
     </bean>
     <bean class="org.runtimemonitoring.spring.collectors.snmp.SNMPCollection">
             <property name="nameSpace" value="TX Manager, Current Count"/>
             <property name="oid" value=".1.2.3.4.1.8"/>
     </bean>
  </set></constructor-arg>
</bean>

<!-- Конфигурация SNMP-коллектора для мониторинга локального сервера JBoss -->
<bean id="LocalJBossSNMP"
   class="org.runtimemonitoring.spring.collectors.snmp.SNMPCollector"
   init-method="springStart">
   <property name="scheduler" ref="CollectionScheduler" />
   <property name="logErrors" value="true" />
   <property name="tracingNameSpace" value="Local,JBoss,SNMP" />
   <property name="frequency" value="5000" />
   <property name="initialDelay" value="3000" />
   <property name="hostName" value="localhost"/>
   <property name="port" value="1161"/>
   <property name="targets" ref="JBossSNMPProfile"/>
</bean>

У этого коллектора есть некоторые недостатки, в частности конфигурация выглядит немного громоздко. К тому же его скорость недостаточна, так как он делает запросы для каждого OID вместо того, чтобы получить набор метрик за один запрос. В файле snmp-collectors.xml, который вы можете найти в архиве с исходным кодом (см. Загрузка), также содержится пример настройки SNMP-коллектора для мониторинга сервера под управлением Linux. Дерево метрик, построенное системой APM, показано на рисунке 21.

Рисунок 21. APM-дерево показателей, собранных классом SNMPCollector
APM Tree for SNMPCollector

К этому моменту вы, вероятно, уже имеете представление о создании коллекторов. Для всеобъемлющего мониторинга среды выполнения приложения обычно приходится создавать несколько коллекторов различного типа. Обратитесь к архиву с исходным кодом к статье, в котором содержатся примеры коллекторов для мониторинга различных компонентов среды. Все коллекторы находятся в пакете org.runtimemonitoring.spring.collectors. Полный список с кратким описанием приведен в таблице 2.

Таблица 2. Дополнительные примеры коллекторов
Объект мониторингаКласс
Web-сервисы: измеряют время ответа и степень готовности защищенных Web-сервисовwebservice.MutualAuthWSClient
Проверка Web-сервисов и их URLwebservice.NoAuthWSClient
Мониторинг производительности и степени готовности Web-сервера Apacheapacheweb.ApacheModStatusCollector
Pingnetwork.PingCollector
NTop: утилита для сбора подробной статистики о функционировании сетиnetwork.NTopHostTrafficCollector

Манипулирование данными

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

  • база данных должна предоставлять относительно простой и быстрый механизм получения истории изменения данных. Эта возможность необходима для построения отчетов, визуализации и анализа данных;
  • база данных должна предоставлять возможность хранения истории данных с необходимым уровнем детализации. Система построения отчетов должна позволять задавать такие параметры, как длительность промежутка времени и необходимая точность результатов;
  • механизм сохранения данных должен обладать достаточной производительностью, и уметь обрабатывать параллельные запросы, чтобы не оказывать негативного влияния на быстродействие мониторинга;
  • запросы на выборку и вставку данных должны обрабатываться параллельно, не снижая быстродействие друг друга;
  • механизм запросов должен поддерживать возможность агрегирования данных за указанные промежутки времени;
  • информация должна сохраняться таким образом, чтобы можно было получать данные в виде временных последовательностей, либо должен поддерживаться механизм, позволяющий связывать между собой показатели, относящиеся к одному временному интервалу.

В связи с этим для управления данными понадобятся следующие компоненты:

  • хорошо масштабируемая и быстродействующая база данных, а также объемная дисковая подсистема;
  • СУБД, предоставляющая эффективные алгоритмы поиска. Как вы помните, каждая метрика имеет собственное составное имя, поэтому их можно сохранять в виде строк. При этом в запросах можно указывать шаблоны имен, а при поиске использовать методы на основе сравнения с образцом. Многие реляционные СУБД позволяют в том или ином виде использовать регулярные выражения в SQL-запросах, что очень удобно для задания шаблонов имен метрик. К сожалению, такой подход оказывается достаточно медленным на практике, так как СУБД не может использовать индексы при поиске. Однако некоторые реляционные СУБД поддерживают так называемые функциональные индексы, которые могут использоваться для ускорения запросов, при выполнении которых необходимо сравнение с образцом. Альтернативным вариантом является полная нормализация данных, означающая разбиение составных имен на сегменты (см. раздел "Нормализованные и плоские схемы баз данных");
  • механизм для снижения числа обращений на запись и общего объема информации, сохраняемой в базе данных. Как правило, для этого выполняются специальные операции агрегирования и слияния блоков данных. Они могут вызываться перед записью информации в базу данных, в то время пока она содержится в некотором накопителе (буфере). Собранные данные накапливаются в буфере в течение определенного промежутка времени, причем дополнительно сохраняются такие показатели как границы временного интервала, а также минимальное, максимальное и среднее значение каждой метрики за интервал. Таким образом, информация агрегируется еще до ее записи в базу данных. Например, если коллектор собирает данные с частотой 1 вызов каждые 15 секунд, а период накапливания составляет 1 минуту, то четыре собранных значения будут сливаться в одно перед каждой записью. Разумеется, при этом детальность информации будет определенным образом снижаться, поэтому необходимо правильно выбрать баланс между уровнем детализации сохраняемых значений их объемом. В то же время средства визуализации данных в режиме реального времени могут напрямую обращаться к информации, содержащейся в памяти, поэтому агрегированию будут подвергаться только данные, готовящиеся к сохранению в базе. Разумеется, что у вас должна быть возможность выбрать, какие именно метрики должны сохраняться, а какие нет;
  • дополнительный механизм для снижения сохраняемого объема данных, позволяющий варьировать частоту сохранения. Другими словами, вы должны иметь возможность указывать, что только каждые x значений из y собранных должны помещаться в базу данных. Это также приведет к снижению детализации информации, но позволить сэкономить больше ресурсов (в первую очередь, памяти), чем метод на основе буферов агрегирования;
  • механизм для агрегирования и очищения информации после сохранения в базе данных. При этом в очередной раз страдает детальность данных, однако механизм должен обладать достаточной гибкостью, что позволяет, например, агрегировать показатели по часам, дням, неделям, месяцам и т.д., поддерживая тем самым допустимый уровень детализации даже после удаления исходных значений;
  • очень важно, чтобы описанные выше механизмы могли работать в фоновом режиме, не оказывая никакого влияния на работу коллекторов, которые будут периодически поставлять новые порции данных;
  • построение точных временных диаграмм, как правило, оказываться очень непростой задачей, так как в процессе сбора информации могут использоваться разные по длительности временные интервалы. Например, предположим, что по оси x откладывается время, а по оси y – значения некоторой метрики, причем получаемые сериями из различных источников. Если частота сбора значений существенно различается между источниками, то для поддержания корректности диаграммы требуется дополнительная обработка данных. Одним из вариантов является приведение измерений к наименьшей общей частоте путем агрегирования. Однако очевидно, что этот путь вновь ведет к потере детализации. Гораздо более простое решение заключается в поддержке единой частоты сбора данных для всех источников. Эта возможность реализована во временных базах данных. Одной из наиболее часто использующихся временных БД является RRDTool, которая позволяет задавать единые и равномерно распределенные временные отсечки для всех значений во временном ряду. Хорошим способом обеспечения согласованности между данными и временными точками является использование единого планировщика для сбора всех метрик. Например, единый таймер может срабатывать каждые 2 минуты, в результате чего будут получены значения всех собранных на данный момент показателей, которые затем можно ассоциировать с одной временной отсечкой и поместить в очередь на сохранение в базе данных.

Разумеется, трудно ожидать, что абсолютно все требования удастся удовлетворить оптимальным образом, поэтому приходится идти на компромиссы. Концептуальная схема взаимодействия коллекторов с системой сохранения данных показана на рисунке 22.

Рисунок 22. Взаимодействие коллекторов с системой хранения данных
Data management flow

На рисунке 22 показан процесс поступления данных от коллектора, аналогичного коллекторам Spring, которые рассматривались в предыдущих разделах. Весь процесс заключается в прохождении информации через ряд процедур, которые в итоге заканчиваются сохранением в базе данных. Основными аспектами являются следующие.

  1. Коллектор может поддерживать ряд следующих свойств, касающихся процесса сохранения данных:
    • флаг сохранения: может принимать значения True или False;
    • фильтр имен метрик: значением является регулярное выражение. Метрики, чьи имена соответствуют регулярному выражению, будут помечаться как требующие сохранения в базе данных (в дополнение к хронологическому кэшу);
    • частота дискретизации: указывает, сколько собранных значений должны пропускать этап сохранения в базе данных после каждого сохраненного значения. 0 означает, что будет сохраняться каждое значение;
    • объем хронологического кэша: число значений, которые должны помещаться в кэш. Значения большинства метрик должны присутствовать в хронологическом кэше даже если их не планируется сохранять в базе данных. Это необходимо для построения отчетов в режиме реального времени.
  2. Кэш хранит дискретный набор значений всех собираемых коллектором метрик. Он представляет собой структуру данных, организованную по принципу FIFO (first in, first out), т.е. при превышении предельного размера первыми удаляются наиболее ранние значения. Кэш позволяет регистрировать объекты-слушатели для получения уведомлений об изменениях, например, о поступлении или удалении значений интересующих метрик.
  3. Накопитель коллектора представляет собой набор из двух или более кольцевых кэшей, каждый из которых содержит значения одной метрики. При активации каждый кольцевой буфер регистрирует новое событие трассировки для хронологического кэша и обновляет агрегированные данные. Таким образом происходит интеграция данных за определенный интервал времени. Каждый буфер содержит следующие свойства:
    • начало и конец временного интервала, причем конечное значение задается только в конце периода;
    • минимальное, максимальное и среднее значения метрики за период;
    • тип метрики.
  4. Центральный таймер генерирует событие сброса (flush) в конце каждого периода. При этом индекс буферов в накопителе увеличивается на единицу, и следующий буфер получает уведомление об окончании периода. Таким образом, каждой метрике будет соответствовать единая временная отсечка, после чего накопленные значения помещаются в очередь на сохранение в базе данных.

    Обратите внимание, что при увеличении индекса минимальное, максимальное и среднее значения буфера сбрасываются (за исключением метрик с закрепленным типом трассировки).

  5. Поддерживается пул фоновых потоков, которые читают данные из очереди и сохраняют их в базе данных.

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

Нормализованные и плоские схемы баз данных

RRDTool: практически все необходимое для мониторинга в одном приложении

RRDTool (Round Robin Database Tool) – это приложение, обладающее широчайшими возможностями, которые умещаются в единственном исполняемом файле (см. Ресурсы). Само по себе оно не занимается сбором данных, поэтому при организации системы мониторинга его обычно используют в сочетании с консольными утилитами наподобие snmpget, snmpwalk и другими. RRDTool позволяет создавать кольцевые файлы, содержащие данные с привязкой ко времени (временные ряды), поддерживая различные схемы агрегирования. Циркулярность файлов обеспечивает контроль над их размером. Кроме того, существует возможность экспорта данных, генерации диаграмм для динамической визуализации информации и построения отчетов "на лету". К этому остается только добавить простой Web-сервер, обслуживающий запросы к постоянно обновляемым страницам с графиками и таблицами, и вы получите законченное эффективное средство мониторинга.

Несмотря на существование специализированных хранилищ, наподобие RRDTool, по-прежнему часто используются реляционные базы данных. Существуют два основных варианта реализации схемы хранения метрик в реляционной БД: нормализация данных или хранение их в неструктурированном виде. Это относится только к хранению составных имен метрик; остальные данные должны нормализовываться в соответствии с общепризнанными рекомендациями.

Основным преимуществом нормализованной схемы является то, что после разбиения имен метрик на сегменты практически любая СУБД сможет выстроить индексы для повышения производительности запросов. Кроме того, это позволит снизить дублирование информации, что положительно отразится на размере базы данных, повысит плотность хранения и ускорит выборку данных. Обратной стороной «медали» является сложность написания запросов. Доступ к значениям даже метрик с относительно простыми именами (например, степень пользовательской загрузки процессора на хостах с 11 по 93) требует нескольких операторов join и множества предикатов. Частично эту проблему можно решить путем создания представлений и хранения результатов декодирования распространенных имен метрик. Сохранение каждого значения метрики требует декомпозирования его составного имени для поиска необходимых справочных данных (или создания новых). Эти расходы можно снизить, кэшируя всю необходимую для поиска информацию в процессе сохранения данных в БД.

Модель данных для хранения метрик в нормализованном виде показана на рисунке 23.

Рисунок 23. Нормализованная схема хранения показателей
A normalized model for metric storage

Как видно из рисунка 23, каждому составному имени метрики ставится в соответствие уникальный идентификатор METRIC_ID, а каждому сегменту – идентификатор SEGMENT_ID. Составные имена хранятся в ассоциативной таблице, которая содержит поля METRIC_ID, SEGMENT_ID и последовательность, к которой принадлежат сегменты. Типы метрик хранятся в справочной таблице METRIC_TYPE, а значения метрик – в таблице METRIC_INSTANCE. Кроме самих значений, данная таблица содержит время начала и конца периода, а также ссылки на тип метрики и ее идентификатор (METRIC_ID).

Как видите, неструктурированная модель данных, показанная на рисунке 24, выглядит значительно проще.

Рисунок 24. Плоская модель хранения показателей
A flat model for metric storage

В этой модели различаются имя и составное имя метрики, в то время как все сегменты хранятся в исходной форме в поле segments. Если СУБД предоставляет средства оптимизации поиска данных на основе регулярных выражений, то есть смысл использовать неструктурированную модель, так как она позволяет создавать более простые запросы. Не стоит недооценивать это преимущество, так как оно имеет большое значение при создании средств визуализации и анализа данных, а также построения отчетов. Кроме того, в условиях непредвиденного сбоя скорость написания запросов может и вовсе оказаться наиболее важным критерием!

Главный шаг при организации хранилища данных – это правильный выбор СУБД. Одним из вариантов является реляционная СУБД при условии, что она обладает достаточным быстродействием и предоставляет удобные средства выборки и форматирования данных. Представление данных в виде временных рядов может представлять определенные трудности, однако при помощи агрегирования, группировки и дополнительных библиотек, например JFreeChart (см. Ресурсы), вы сможете создавать нужные отчеты и строить графики. Если же вы решите выбрать более специализированную базу данных, например RRDTool, то приготовьтесь к тому, что выборка данных и построение отчетов потребуют серьезных усилий. Учтите, что если база данных не поддерживает стандартные интерфейсы, такие как ODBC или JDBC, то вы не сможете использовать множество популярных средств для построения отчетов.

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


Визуализация данных и построение отчетов

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

Для визуализации данных чаще всего используется термин "информационная панель" (dashboard). Подобные панели могут отображать практически любые состояния и изменения в данных, их возможности ограничиваются лишь качеством и объемом собранной информации. По существу, на панели отображается все, что происходит в среде выполнения вашего приложения. Одной из отличительных возможностей информационных панелей APM-систем является представление разнородных данных, в частности, поступающих от различных источников, в унифицированном виде. Например, одновременно на панель могут выводиться такие показатели как диаграммы загрузки процессора на сервере БД, интенсивность сетевого обмена данными между серверами приложений, а также текущее число авторизованных пользователей вашего приложения. В этом разделе мы рассмотрим различные подходы к визуализации данных, а также пример реализации слоя отображения данных, собранных коллектором Spring, который использовался в предыдущих примерах.

Слой визуализации информации, предоставляемой коллектором Spring, будет функционировать при следующих условиях:

  • кэш, работающий по схеме FIFO, будет развернут в приложении в виде объекта Spring. При помощи конфигурации можно задавать любой фиксированный объем кэша. Например, размер 50 означает, что, будучи полностью заполненным, он содержит 50 последних собранных значений;
  • коллекторы Spring содержат свойство cacheConfiguration, при помощи которого экземпляры ITracer заключаются в класс-обертку, содержащую ссылку на экземпляр кэша. Собранные данные трассируются как обычно, но кроме этого помещаются в кэш, связанный с данным коллектором. В соответствии с примером, приведенным выше, размер истории кэша равен 50, коллектор собирает данные каждые 30 секунд, поэтому при полном заполнении кэш содержит данные, полученные от коллектора в течение последних 25 минут;
  • каждый коллектор Spring также содержит ссылки на объекты, отвечающие за отображение данных (так называемые рендереры). Классы рендереров реализуют интерфейс org.runtimemonitoring.spring.rendering.IRenderer. В их функции входит получение наборов данных из кэшей и последующая визуализация. Периодический вызов рендереров позволяет обновлять представление данных, поддерживая необходимую синхронизацию с содержимым кэшей;
  • визуализированное представление данных затем передается клиенту и отображается на информационной панели внутри Web-браузера или при помощи "толстого" клиента.

Весь процесс показан на рисунке 25.

Рисунок 25. Процесс кэширования и визуализации данных
Caching and rendering

В данном примере реализация кэша содержится в классе org.runtimemonitoring.spring.collectors.cache.TraceCacheStore. Сторонние объекты могут подписываться на уведомления о событиях изменения кэша, например рендерер может уведомляться о добавлении нового элемента в кэш. Таким образом, рендереры могут поддерживать свой собственный кэш для хранения сгенерированного представления, который будет сбрасываться при поступлении новых данных. Визуализированные данные будут передаваться клиентским панелям через специальный сервлет типа org.runtimemonitoring.spring.rendering.MediaServlet (медиа-сервер). Данный сервлет, получив входящий запрос от клиента, извлекает имя рендерера и запрашивает у него представление данных и его MIME-тип. Визуализированные данные передаются клиенту в виде байтового массива вместе с MIME-типом, чтобы клиент мог правильно его интерпретировать. Предоставление графического содержимого через подобные сервисы имеет то преимущество, что в качестве клиентов могут выступать любые приложения, от Web-браузеров до "толстых" клиентов. Получив от медиа-сервера запрос на предоставление графического контента, рендереры возвращают содержимое собственного кэша при условии, что он не был сброшен в результате поступления новых данных в кэш коллектора. Таким образом, рендерерам не приходится визуализировать данные при обработке каждого запроса.

Генерация, кэширование и передача медиа-контента в виде байтового массива имеет то преимущество, что большинство клиентов смогут правильно его интерпретировать при наличии корректного MIME-типа. Поскольку визуализированные данные кэшируются в памяти рендерера, логично реализовать схему их сжатия для экономии памяти. Как и в случае с MIME-типом, передавая клиентам идентификатор алгоритма сжатия, можно быть уверенным, что большинство клиентов смогут благополучно распаковать данные. Например, большинство современных браузеров поддерживают схему сжатия gzip. Тем не менее распространенные алгоритмы не отличаются очень высокой степенью сжатия (по моим оценкам, она составляет 30–40% на крупных изображениях), поэтому рендереры могут либо сбрасывать содержимое кэша на диск, либо всегда выполнять визуализацию данных "на лету", если это позволяет сэкономить ресурсы.

Далее рассмотрим конкретный пример. Мы будем использовать коллекторы для мониторинга числа рабочих потоков в двух Web-серверах Apache. Каждый коллектор содержит кэш и связан с небольшим числом рендереров для визуализации числа рабочих потоков каждого сервера. В данном примере рендереры будут генерировать изображения в формате PNG, содержащие временные диаграммы показателей для обоих серверов. Описание коллектора и конфигурирование кэша, соответствующего одному из серверов в XML, показано в листинге 22.

Листинг 22. Описание коллектора и кэша для мониторинга Web-сервера Apache
<!-- Коллектор Apache -->
<bean id="Apache2-AP02"
   class="org.runtimemonitoring.spring.collectors.apacheweb.ApacheModStatusCollector"
   init-method="springStart">
   <property name="scheduler" ref="CollectionScheduler" />
   <property name="logErrors" value="true" />
   <property name="tracingNameSpace" value="WebServers,Apache" />
   <property name="frequency" value="15000" />
   <property name="initialDelay" value="3000" />
   <property name="modStatusURL" value="http://WebAP02/server-status?auto" />
   <property name="name" value="Apache2-AP02" />
   <property name="cacheConfiguration">
      <bean
         class="org.runtimemonitoring.spring.collectors.cache.CacheConfiguration">
         <property name="cacheStores" ref="Apache2-AP02-Cache"/>
      </bean>
   </property>
</bean>

<!-- Кэш коллектора Apache -->
<bean id="Apache2-AP02-Cache"
   class="org.runtimemonitoring.spring.collectors.cache.TraceCacheStore">
   <constructor-arg value="50"/>
</bean>

Обратите внимание на свойство cacheConfiguration коллектора, а именно на то, как происходит связывание с экземпляром кэша под именем Apache2-AP02-Cache.

Далее мы настроим рендерер, являющийся экземпляром класса org.runtimemonitoring.spring.rendering.GroovyRenderer. Он будет делегировать всю работу по визуализации данных Groovy-скрипту, находящемуся на диске. Данное решение позволяет редактировать скрипт для корректировки генерируемого изображения в процессе работы системы. Основными свойствами рендерера являются следующие:

  • groovyRenderer: ссылка на экземпляр класса org.runtimemonitoring.spring.groovy.GroovyScriptManager, который отвечает за загрузку скрипта Groovy с диска. Этот тот же класс, который ранее использовался для обработки данных, полученных из сессии Telnet, открытой на Cisco CSS;
  • dataCaches: набор кэшей, из которых рендерер получает данные для визуализации. Рендерер также подписывается на получение уведомлений от кэшей о поступлении новых данных. При наступлении этого события он сбрасывает содержимое собственного кэша и заново генерирует изображение при получении следующего запроса;
  • renderingProperties: набор свойств, задающих параметры по умолчанию для генерируемого изображения, например такие как его размер. Ниже будет показан пример переопределения этих свойств в клиентском запросе;
  • metricLocatorFilters: кэш коллектора хранит значения всех показателей, собираемых данным коллектором. При помощи этого свойства можно определить массив регулярных выражений для отбора только требуемых показателей.

Описание кэша в XML показано в листинге 23.

Листинг 23. Описание рендерера для визуализации данных мониторинга рабочих потоков Web-сервера Apache
<bean id="Apache2-All-BusyWorkers-Line"
   class="org.runtimemonitoring.spring.rendering.GroovyRenderer"
         init-method="init"
         lazy-init="false">
   <property name="groovyRenderer">
      <bean class="org.runtimemonitoring.spring.groovy.GroovyScriptManager">
         <property name="sourceUrl" value="file:///groovy/rendering/multiLine.groovy"/>
      </bean>
   </property>
   <property name="dataCaches">
      <set>
         <ref bean="Apache2-AP01-Cache"/>
          <ref bean="Apache2-AP02-Cache"/>
      </set>
   </property>
   <property name="renderingProperties">
      <value>
        xSize=700
        ySize=300
        title=Apache Servers Busy Workers
        xAxisName=Time
        yAxisName=# of Workers Busy
      </value>
   </property>
   <property name="metricLocatorFilters" value=".*/Busy Workers"/>
</bean>

Реализация рендереров, как правило, не представляет труда, однако, судя по моему опыту, их часто приходится корректировать. В этих условиях удобно использовать скрипты Groovy для таких задач как быстрое создание прототипов новых графиков или даже новых графических пакетов. Скомпилированный код на Groovy выполняется достаточно быстро, поэтому при адекватном кэшировании это не должно быть проблемой. При этом Groovy позволяет легко модифицировать скрипты во время выполнения приложения благодаря функции горячей замены кода и функциональному стилю синтаксиса. Наконец, всегда есть возможность портирования рендерера на Java после того как будут окончательно определены его функции и набор поддерживаемых параметров.

Имена метрик предоставляются классом org.runtimemonitoring.tracing.Trace. Каждый экземпляр этого класса инкапсулирует в себе значение, полученное через ITracer, временную метку и полное пространство имен. Полное имя метрики включает в себя пространство имен и локальное имя. В данном примере визуализируется метрика под именем WebServers/Apache/Apache2-AP01/Busy Workers, которая удовлетворяет регулярному выражению, приведенному в листинге 23. Сгенерированный файл JPG показан на рисунке 26.

Рисунок 26. Визуализация числа рабочих потоков Apache
Rendered Apache busy workers

Иногда может потребоваться изменить процесс генерации изображения в зависимости от типа клиента. Например, некоторые клиенты могут работать только с изображениями ограниченного размера при том, что динамическое изменение размера часто приводит к ухудшению качества. Другие клиенты могут обходиться без заголовка, поскольку заголовок является частью их собственного графического интерфейса. Для подобной настройки класс MediaServlet предоставляет набор свойств, в которых указываются параметры генерации изображения. Параметры добавляются в конец URL-запроса и обрабатываются в формате REST. Формат состоит из контекстного пути сервлета media (данный путь можно настраивать), за которым следует имя кэша: /media/Apache2-All-BusyWorkers-Line. Каждый рендерер может поддерживать свой собственный набор параметров. В качестве примера рассмотрим параметры, поддерживаемые рендерером из листинга 23:

  • URI по умолчанию: /media/Apache2-All-BusyWorkers-Line;
  • уменьшение размера до 300х300: /media/Apache2-All-BusyWorkers-Line/300/300;
  • уменьшение размера до 300х300 с минимизацией заголовка и меток осей: /media/Apache2-All-BusyWorkers-Line/300/300/BusyWorkers/Time/#Workers.

На рисунке 27 показаны две уменьшенные круговые диаграммы без заголовков. Они были сгенерированы в ответ на запросы с URI Apache2-AP02-WorkerStatus-Pie/150/150/.

Рисунок 27. Уменьшенные версии изображений, показывающих число рабочих потоков сервера Apache
Shrunk Images of Apache Server Worker Pools

Рендереры способны генерировать представления данных практически в любом формате, который может отображаться на клиентской стороне. В частности, изображения могут генерироваться в форматах JPG, PNG или GIF. Другие форматы также могут поддерживаться, но для статических изображений, отображаемых в браузерах, лучше всего подходят PNG и GIF. Кроме того, рендереры могут генерировать результаты в текстовом формате, например, в виде документов HTML. Браузеры и толстые клиенты могут отображать фрагменты HTML, который идеально подходит для представления как единичных значений, так и связанных таблиц. В некоторых случаях может иметь смысл генерировать даже обычный текст. Например, Web-браузер может запрашивать текстовые фрагменты, представляющие собой сообщения о системных событиях и помещать их в текстовые поля или списки. Можно легко представить себе сценарии использования и других языков разметки. В частности, многие "толстые" клиенты и графические модули браузеров используют XML для генерирования интерфейсов на клиентской стороне, выигрывая тем самым в быстродействии.

Дополнительные возможности оптимизации открываются при переносе части процесса генерации графики на сторону клиента. Например, если клиент обладает собственными средствами визуализации, то ему можно напрямую передавать уведомления об изменении кэша, минуя промежуточные рендереры (если, конечно, они не должны добавлять дополнительные теги разметки). Таким образом, клиент может самостоятельно подписаться на уведомления о поступлении данных в кэш и обновлять представление при наступлении событий. Передача данных клиентам может осуществляться несколькими способами. При работе через браузер можно реализовать схему периодического опрашивания сервера через Ajax. При этом необходимо создать обработчик, который будет вставлять вновь полученную информацию в нужные места на странице в процессе обновления интерфейса. Существуют и более сложные варианты, например, потоковая передача данных по принципу Комета (Comet), при котором соединение поддерживается в открытом состоянии и клиент читает данные по мере их поступления от сервера (см. Ресурсы). В случае использования "толстых" клиентов идеальным вариантом является использование системы передачи сообщений, позволяющей клиентам подписываться на ленты данных, обновляемых на сервере. Оба последних варианта доступны при использовании брокера ActiveMQ. Если на серверной стороне используется ActiveMQ в сочетании с Web-сервером Jetty, поддерживающим принцип Комета, то можно создать JMS-клиента на JavaScript, способного подписываться на нужные JMS-темы и очереди сообщений.

При визуализации на стороне клиента можно реализовать дополнительные возможности, недоступные при передаче готовых изображений. Одним из примеров может служить возможность нажатия на определенные графические элементы. Это является одним из распространенных требований к информационным панелям APM-систем – они должны позволять пользователю выбирать интересующие его элементы графиков и диаграмм с целью получения более детальной информации. Подобная возможность реализована в проекте Visifire – открытой библиотеке для построения диаграмм на базе Silverlight (см. Ресурсы). В листинге 24 приведен фрагмент XML, на основе которого Visifire генерирует гистограмму загрузки процессора на разных серверах баз данных.

Листинг 24. Входные данные для рендерера, отображающего средние показатели нагрузки на процессор сервера базы данных
<vc:Chart xmlns:vc="clr-namespace:Visifire.Charts;assembly=Visifire.Charts" 
    Theme="Theme3">
         <vc:Title Text="Average CPU Utilization on  Database Servers"/>
         <vc:AxisY Prefix="%" Title="Utilization"/>
         <vc:DataSeries Name="Utilization" RenderAs="Column">
                  <vc:DataPoint AxisLabel="DB01" YValue="13"/>
                  <vc:DataPoint AxisLabel="DB02" YValue="57"/>
                  <vc:DataPoint AxisLabel="DB03" YValue="41"/>
                  <vc:DataPoint AxisLabel="DB04" YValue="10"/>
                  <vc:DataPoint AxisLabel="DB05" YValue="30"/>
         </vc:DataSeries>
</vc:Chart>

Данный фрагмент выглядит достаточно просто, поэтому создание рендерера для его генерации не представляет никаких трудностей, а результат выглядит очень привлекательно. Кроме того, клиентские рендереры могут работать с анимацией. Использование данной возможности на информационных панелях вызывает определенные сомнения, но в некоторых случаях она может оказаться полезной. На рисунке 28 показана гистограмма, генерируемая клиентским браузером c модулем Silverlight.

Рисунок 28. Диаграмма, построенная при помощи VisiFire и Silverlight
A VisiFire Silveright rendered chart

Все стандартные типы графиков находят свое применение на информационных панелях APM. Наиболее распространенными из них являются линейные и многолинейные графики, а также гистограммы и круговые диаграммы. Разные типы графиков часто совмещают, например, можно увидеть сочетание столбцов и линий для подчеркивания пересечения двух различных типов данных. В других случаях можно встретить двойную ось y на линейном графике, благодаря чему удается совместить временные серии значений, различающихся на один или несколько порядков. Например, такая ситуация может возникнуть при совмещении скалярных и процентных значений, таких как степень загрузки процессора (в процентах) и объем данных, переданных маршрутизатором (в байтах).

В некоторых случаях имеет смысл реализовать специализированные интерфейсные элементы (виджеты), способные отображать данные нестандартным образом, зачастую более интуитивно понятным для пользователя. Например, в зависимости от состояния объекта мониторинга пользователь должен видеть ту или иную пиктограмму. Как правило, состояние может принимать ограниченное число значений, поэтому использование графиков в данном случае будет избыточным. Вместо этого можно использовать виджет, напоминающий по виду светофор, на котором красный свет соответствует сбою в системе, желтый – ситуации, требующей внимания, а зеленый означает, что все в порядке. Другим подходящим для подобных целей виджетом является круговая шкала, часто напоминающая по внешнему виду спидометр. Лично мне кажется, что подобные спидометры нерационально расходуют пространство на панели, так как они способны отображать только текущее значение, без истории. В то же время линейные графики могут показывать те же данные, но вместе с хронологией их изменения. Единственным исключением являются многострелочные спидометры, подчеркивающие важные интервалы значений, например, области высоких, низких и текущих показателей. Тем не менее в большинстве случаев их главным преимуществом является броский внешний вид, как у показанных на рисунке 29. Данные виджеты отображают число считываний блоков данных из буфера за единицу времени (1 секунду), а также максимальные, минимальные и текущие показатели за последний час.

Рисунок 29. Графические элементы в виде спидометров
Example dial widgets

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

Рисунок 30. Визуализация процента попаданий в кэш средствами Savant
Savant Cache Hit Ratio Display

На рисунке 30 показаны несколько векторов данных, вращающихся вокруг показателя процента попадания в буфер базы данных:

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

Еще один способ уплотнения данных, который отличается определенным аскетизмом, известен под именем Sparklines. Данный термин был предложен Эвардом Тафти (Edward Tufte, см. Ресурсы) - экспертом в области визуализации данных – для обозначения "небольших графических фрагментов в высоком разрешении, встраиваемых непосредственно в текст, числовые данные и изображения". Этот способ часто используется для отображения статистической информации финансового характера. Sparklines лишены контекста, их задачей является изображение относительных тенденций изменения многих показателей. Sparkline-рендерер реализуется классом org.runtimemonitoring.spring.rendering.SparkLineRenderer, использующим открытую библиотеку Sparlines для Java (см. Ресурсы). На рисунке 31 показаны два увеличенных графика Sparklines, выполненные на основе линейных и столбчатых диаграмм.

Рисунок 31. Визуализация числа рабочих потоков Apache в виде графика Sparklines
Sparklines showing Apache 2 busy workers

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

Рисунок 32. Пример информационной панели
Dashboard Example

Заключение

На этом наша серия подходит к концу. В ней были даны рекомендации, рассмотрены некоторые общие подходы к мониторингу производительности, а также специфические примеры, которые вы можете взять на вооружение при совершенствовании или реализации собственной системы мониторинга. Тщательный сбор и анализ данных может значительно повысить готовность и быстродействие вашего приложения. В связи с этим я рекомендую разработчикам принимать участие в процессе наблюдения за приложениями на этапе их эксплуатации, так как это наилучший способ получения информации о том, что на практике происходит в написанной вами системе. Подобная "обратная связь" является неотъемлемой частью постоянного процесса углубления ваших знаний и совершенствования ваших программ. Счастливого мониторинга!

Благодарности

Автор выражает глубокую признательность Сандипу Малхотре (Sandeep Malhotra) за помощь в написании материала о коллекторах на основе Web-сервисов.


Загрузка

ОписаниеИмяРазмер
Исходный код примеровj-rtm3.zip316 KБ

Ресурсы

Научиться

Получить продукты и технологии

  • Посетите домашнюю страницу проекта JCraft - открытой библиотеки SSH на Java. (EN)
  • Apache Ant: Ant – это средство сборки приложений при помощи конфигурационных файлов XML. Ant также содержит задания для удаленного выполнения команд интерпретатора через SSH. (EN)
  • Nagios: приложение для сетевого мониторинга с открытым кодом. (EN)
  • IKVM: компилятор, транслирующий байт-код Java в байт-код среды выполнения .NET. (EN)
  • NSClient и NC_Net: две службы Windows, позволяющие получать доступ к данным WPM через сокеты. (EN)
  • nsclient4j: API для обращения к NSClient и NC_Net из приложений на Java. (EN)
  • Apache ActiveMQ: открытая реализация JMS от Apache Software Foundation. (EN)
  • WebSphere MQ: совместимый с JMS сервис передачи сообщений от IBM®. (EN)
  • OpenNMS: сайт владельцев Java-библиотеки joeSNMP. (EN)
  • RRDTool: приложение для циклического сохранения данных в виде временных рядов. (EN)
  • MRTG (Multi Router Traffic Grapher): приложение для визуализации данных мониторинга маршрутизатора. (EN)
  • JFreeChart: библиотека для создания графиков и диаграмм по полученным данным. (EN)
  • Visifire: приложение для построения диаграмм при помощи Silverlight. (EN)
  • Microsoft Silverlight: "толстый" клиент на базе языка разметки. (EN)
  • Sparklines для Java: библиотека с открытым кодом для создания графиков Sparkline в приложениях Java. (EN)
  • CA/Wily Introscope: коммерческая система для мониторинга Java- и Web-приложений. (EN)
  • Узнайте больше о системе IBM для мониторинга производительности, посетив информационный центр продукта IBM® Tivoli® Monitoring for Transaction Performance. (EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java, Linux
ArticleID=380953
ArticleTitle=Мониторинг работы Java-приложений: Часть 3. Мониторинг производительности и степени готовности среды выполнения приложений
publish-date=04102009