Прицельный обзор: Reporter'ы в библиотеке Metrics

| Комментарии

Metrics - библиотека проведения измерения различных метрик как приложения, так и самого JVM от разработчиков Yammer.

Build tool - Maven с использованием мультимодульности.

Помимо неплохого JavaDoc’а ведется внешняя документация с использованием Sphinx - генератора документации на python. Текст документации ведется в reStructuredText разметке. Этот язык разметки сильно упрощен относительно HTML, при этом текст с разметкой не захламлен тегами и читается очень естественно. Сильно напоминает markdown. Документация преобразуется в html посредством генератора из скрипта Makefile.

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

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

1
private static final MetricsRegistry DEFAULT_REGISTRY = new MetricsRegistry();

Сам класс Metrics передает управление делегируемому объекту при вызове его методов:

1
2
3
4
5
public static <T> Gauge<T> newGauge(Class<?> klass,
                                  String name,
                                  Gauge<T> metric) {
  return DEFAULT_REGISTRY.newGauge(klass, name, metric);
}

Библиотека оперирует несколькими измерительными сущностями:

Метриками: 1. Gauges для контроля значений используемых объектов 2. Counters для ведения счетчиков. Используется AtomicLong для подсчета 3. Meters для измерения скорости работы того или иного компонента 4. Histograms для измерения статистического распределения значений в потоке данных 5. Timers для измерения продолжительности и скорости выполнения кода

и HealthChecks для централизованного контроля работоспособности системы.

Прицип работы:

  1. Объявляем метрики
  2. Указываем точки определения метрик в коде
  3. Собираем статистику с помощью Reporter’ов

Reporter - объект, который следит за метриками и в случае их появления или изменения оповещает пользователя, доступным reporter’у способом.

Основным Reporter’ом является JMXReporter, который позволяет получать результаты измерения в JMX консоли. При загрузке класса Metrics JMXReporter подписывается на события регистра метрик, реализуя при этом интерфейс слушателя событий регистра метрик.

1
2
3
4
static {
  JmxReporter.startDefault(DEFAULT_REGISTRY);
  Runtime.getRuntime().addShutdownHook(SHUTDOWN_HOOK);
}

Тогда же вешается хук на событие завершения приложения, чтобы иметь возможность освободить зарегистрированные MBean’ы JMX сервера. При этом SHUTDOWN_HOOK - это поток, реализующий логику, выполняемую при выключении приложения.

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

Внутри библиотеки реализованы дополнительные reporter’ы для вывода данных в CSV файлы или консоль. Эти reporter’ы сами периодически опрашивают метрики, получая данные из них. Опрос метрик происходит в отдельном потоке, созданный с помощью SingleThreadScheduledExecutor.

В аргументе принимается фабрика потоков, которая пораждает потоки и добавляет их в единую группу, тем самым разделяя потоки, запущенные разными executor’ами. Т.е. запущенные из разных executor’ов потоки не будут иметь прав на доступ к потокам из другой группы. Группа получается из стандартного SecurityManager’а. Потоки являются демонами.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static class NamedThreadFactory implements ThreadFactory {
  private final ThreadGroup group;
  private final AtomicInteger threadNumber = new AtomicInteger(1);
  private final String namePrefix;

  private NamedThreadFactory(String name) {
      final SecurityManager s = System.getSecurityManager();
      this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
      this.namePrefix = "metrics-" + name + "-thread-";
  }

  @Override
  public Thread newThread(Runnable r) {
      final Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
      t.setDaemon(true);
      if (t.getPriority() != Thread.NORM_PRIORITY) {
          t.setPriority(Thread.NORM_PRIORITY);
      }
      return t;
  }
}

Основной метод снятия показаний с метрик и вывод их в том или ином виде переопределяется в конкретном классе reporter’а. Все метрики зарегистрированны в реестре метрик и любой репортер в момент создания связывается с конкретным реестром. А ссылка на единый реестр метрик хранится в базовых классах Metrics и HealthChecks.

Comments