1. 概要

Netflix Servo は、Javaアプリケーション用のメトリクスツールです。 ServoはDropwizardMetrics に似ていますが、はるかに単純です。 JMXのみを活用して、アプリケーションメトリックを公開および公開するためのシンプルなインターフェイスを提供します。

この記事では、Servoが提供するものと、Servoを使用してアプリケーションメトリックを収集および公開する方法を紹介します。

2. Mavenの依存関係

実際の実装に飛び込む前に、Servo依存関係をpom.xmlファイルに追加しましょう。

<dependency>
    <groupId>com.netflix.servo</groupId>
    <artifactId>servo-core</artifactId>
    <version>0.12.16</version>
</dependency>

さらに、 Servo-Apache Servo-AWS など、多くの拡張機能を利用できます。 後で必要になるかもしれません。 これらの拡張機能の最新バージョンは、 MavenCentralにもあります。

3. 指標を収集する

まず、アプリケーションからメトリックを収集する方法を見てみましょう。

Servoは、 Counter Gauge Timer、、およびInformationalの4つの主要なメトリックタイプを提供します。

3.1. メトリックタイプ– カウンター

Counters は、インクリメントを記録するために使用されます。 一般的に使用される実装は、 BasicCounter StepCounter 、およびPeakRateCounterです。

BasicCounter は、カウンターが行うべきことをわかりやすく簡単に実行します。

Counter counter = new BasicCounter(MonitorConfig.builder("test").build());
assertEquals("counter should start with 0", 0, counter.getValue().intValue());

counter.increment();
 
assertEquals("counter should have increased by 1", 1, counter.getValue().intValue());

counter.increment(-1);
 
assertEquals("counter should have decreased by 1", 0, counter.getValue().intValue());

PeakRateCounter は、ポーリング間隔中の特定の秒の最大カウントを返します。

Counter counter = new PeakRateCounter(MonitorConfig.builder("test").build());
assertEquals(
  "counter should start with 0", 
  0, counter.getValue().intValue());

counter.increment();
SECONDS.sleep(1);

counter.increment();
counter.increment();

assertEquals("peak rate should have be 2", 2, counter.getValue().intValue());

他のカウンターとは異なり、 StepCounter は、前のポーリング間隔の1秒あたりのレートを記録します。

System.setProperty("servo.pollers", "1000");
Counter counter = new StepCounter(MonitorConfig.builder("test").build());
 
assertEquals("counter should start with rate 0.0", 0.0, counter.getValue());

counter.increment();
SECONDS.sleep(1);

assertEquals(
  "counter rate should have increased to 1.0", 
  1.0, counter.getValue());

上記のコードでservo.pollers1000に設定していることに注意してください。 これは、ポーリング間隔をデフォルトの60秒と10秒の間隔ではなく、1秒に設定することでした。 これについては後で詳しく説明します。

3.2. メトリックタイプ– ゲージ

Gauge は、現在の値を返すシンプルなモニターです。 BasicGauge MinGauge MaxGauge 、およびNumberGaugesが提供されます。

BasicGauge は、Callableを呼び出して現在の値を取得します。 コレクションのサイズ、 BlockingQueue の最新の値、または小さな計算が必要な任意の値を取得できます。

Gauge<Double> gauge = new BasicGauge<>(MonitorConfig.builder("test")
  .build(), () -> 2.32);
 
assertEquals(2.32, gauge.getValue(), 0.01);

MaxGaugeMinGaugeは、それぞれ最大値と最小値を追跡するために使用されます。

MaxGauge gauge = new MaxGauge(MonitorConfig.builder("test").build());
assertEquals(0, gauge.getValue().intValue());

gauge.update(4);
assertEquals(4, gauge.getCurrentValue(0));

gauge.update(1);
assertEquals(4, gauge.getCurrentValue(0));

NumberGauge LongGauge DoubleGauge )は、指定された Number Long Double )。 これらのゲージを使用してメトリックを収集するには、Numberがスレッドセーフであることを確認する必要があります。

3.3. メトリックタイプ– タイマー

Timers は、特定のイベントの期間を測定するのに役立ちます。 デフォルトの実装は、 BasicTimer StatsTimer 、およびBucketTimerです。

BasicTimer は、合計時間、カウント、およびその他の単純な統計を記録します。

BasicTimer timer = new BasicTimer(MonitorConfig.builder("test").build(), SECONDS);
Stopwatch stopwatch = timer.start();

SECONDS.sleep(1);
timer.record(2, SECONDS);
stopwatch.stop();

assertEquals("timer should count 1 second", 1, timer.getValue().intValue());
assertEquals("timer should count 3 seconds in total", 
  3.0, timer.getTotalTime(), 0.01);
assertEquals("timer should record 2 updates", 2, timer.getCount().intValue());
assertEquals("timer should have max 2", 2, timer.getMax(), 0.01);

StatsTimer は、ポーリング間隔間でサンプリングすることにより、はるかに豊富な統計を提供します。

System.setProperty("netflix.servo", "1000");
StatsTimer timer = new StatsTimer(MonitorConfig
  .builder("test")
  .build(), new StatsConfig.Builder()
  .withComputeFrequencyMillis(2000)
  .withPercentiles(new double[] { 99.0, 95.0, 90.0 })
  .withPublishMax(true)
  .withPublishMin(true)
  .withPublishCount(true)
  .withPublishMean(true)
  .withPublishStdDev(true)
  .withPublishVariance(true)
  .build(), SECONDS);
Stopwatch stopwatch = timer.start();

SECONDS.sleep(1);
timer.record(3, SECONDS);
stopwatch.stop();

stopwatch = timer.start();
timer.record(6, SECONDS);
SECONDS.sleep(2);
stopwatch.stop();

assertEquals("timer should count 12 seconds in total", 
  12, timer.getTotalTime());
assertEquals("timer should count 12 seconds in total", 
  12, timer.getTotalMeasurement());
assertEquals("timer should record 4 updates", 4, timer.getCount());
assertEquals("stats timer value time-cost/update should be 2", 
  3, timer.getValue().intValue());

final Map<String, Number> metricMap = timer.getMonitors().stream()
  .collect(toMap(monitor -> getMonitorTagValue(monitor, "statistic"),
    monitor -> (Number) monitor.getValue()));
 
assertThat(metricMap.keySet(), containsInAnyOrder(
  "count", "totalTime", "max", "min", "variance", "stdDev", "avg", 
  "percentile_99", "percentile_95", "percentile_90"));

BucketTimer は、値の範囲をバケット化することによってサンプルの分布を取得する方法を提供します。

BucketTimer timer = new BucketTimer(MonitorConfig
  .builder("test")
  .build(), new BucketConfig.Builder()
  .withBuckets(new long[] { 2L, 5L })
  .withTimeUnit(SECONDS)
  .build(), SECONDS);

timer.record(3);
timer.record(6);

assertEquals(
  "timer should count 9 seconds in total",
  9, timer.getTotalTime().intValue());
 
Map<String, Long> metricMap = timer.getMonitors().stream()
  .filter(monitor -> monitor.getConfig().getTags().containsKey("servo.bucket"))
  .collect(toMap(
    m -> getMonitorTagValue(m, "servo.bucket"),
    m -> (Long) m.getValue()));

assertThat(metricMap, allOf(hasEntry("bucket=2s", 0L), hasEntry("bucket=5s", 1L),
  hasEntry("bucket=overflow", 1L)));

何時間も続く可能性のある長時間の操作を追跡するために、コンポジットモニターDurationTimerを使用できます。

3.4. メトリックタイプ– 情報

また、 Informational モニターを使用して、デバッグと診断に役立つ説明情報を記録できます。 唯一の実装はBasicInformationalであり、その使用法をこれ以上簡単にすることはできません。

BasicInformational informational = new BasicInformational(
  MonitorConfig.builder("test").build());
informational.setValue("information collected");

3.5. MonitorRegistry

メトリックタイプはすべてMonitorタイプであり、これはServoのまさにベースです。 生のメトリックを収集するツールの種類がわかったのですが、データを報告するには、これらのモニターを登録する必要があります。

メトリックの正確性を確保するために、構成された各モニターを1回だけ登録する必要があることに注意してください。 したがって、シングルトンパターンを使用してモニターを登録できます。

ほとんどの場合、DefaultMonitorRegistryを使用してモニターを登録できます。

Gauge<Double> gauge = new BasicGauge<>(MonitorConfig.builder("test")
  .build(), () -> 2.32);
DefaultMonitorRegistry.getInstance().register(gauge);

モニターを動的に登録する場合は、 DynamicTimer 、およびDynamicCounterを使用できます。

DynamicCounter.increment("monitor-name", "tag-key", "tag-value");

動的登録は、値が更新されるたびにコストのかかるルックアップ操作を引き起こすことに注意してください。

Servoは、オブジェクトで宣言されたモニターを登録するためのいくつかのヘルパーメソッドも提供します。

Monitors.registerObject("testObject", this);
assertTrue(Monitors.isObjectRegistered("testObject", this));

メソッドregisterObjectは、リフレクションを使用して、アノテーション@Monitorによって宣言されたMonitorsのすべてのインスタンスを追加し、@MonitorTagsによって宣言されたタグを追加します。

@Monitor(
  name = "integerCounter",
  type = DataSourceType.COUNTER,
  description = "Total number of update operations.")
private AtomicInteger updateCount = new AtomicInteger(0);

@MonitorTags
private TagList tags = new BasicTagList(
  newArrayList(new BasicTag("tag-key", "tag-value")));

@Test
public void givenAnnotatedMonitor_whenUpdated_thenDataCollected() throws Exception {
    System.setProperty("servo.pollers", "1000");
    Monitors.registerObject("testObject", this);
    assertTrue(Monitors.isObjectRegistered("testObject", this));

    updateCount.incrementAndGet();
    updateCount.incrementAndGet();
    SECONDS.sleep(1);

    List<List<Metric>> metrics = observer.getObservations();
 
    assertThat(metrics, hasSize(greaterThanOrEqualTo(1)));
 
    Iterator<List<Metric>> metricIterator = metrics.iterator();
    metricIterator.next(); //skip first empty observation
 
    while (metricIterator.hasNext()) {
        assertThat(metricIterator.next(), hasItem(
          hasProperty("config", 
          hasProperty("name", is("integerCounter")))));
    }
}

4. 指標を公開する

収集されたメトリックを使用して、さまざまなデータ視覚化プラットフォームで時系列グラフをレンダリングするなど、任意の形式で公開できます。 メトリックを公開するには、モニターの観測から定期的にデータをポーリングする必要があります。

4.1. MetricPoller

MetricPollerはメトリックフェッチャーとして使用されます。 MonitorRegistries JVM JMXのメトリックをフェッチできます。 拡張機能を使用すると、ApacheサーバーステータスTomcatメトリックなどのメトリックをポーリングできます。

MemoryMetricObserver observer = new MemoryMetricObserver();
PollRunnable pollRunnable = new PollRunnable(new JvmMetricPoller(),
  new BasicMetricFilter(true), observer);
PollScheduler.getInstance().start();
PollScheduler.getInstance().addPoller(pollRunnable, 1, SECONDS);

SECONDS.sleep(1);
PollScheduler.getInstance().stop();
List<List<Metric>> metrics = observer.getObservations();

assertThat(metrics, hasSize(greaterThanOrEqualTo(1)));
List<String> keys = extractKeys(metrics);
 
assertThat(keys, hasItems("loadedClassCount", "initUsage", "maxUsage", "threadCount"));

ここでは、JVMのメトリックをポーリングするためのJvmMetricPollerを作成しました。 ポーラーをスケジューラーに追加するときは、ポーリングタスクを毎秒実行させます。 システムのデフォルトのポーラー構成はPollersで定義されていますが、システムプロパティservo.pollersで使用するポーラーを指定できます。

4.2. MetricObserver

メトリックをポーリングすると、登録されているMetricObserversの観測値が更新されます。

デフォルトで提供されるMetricObserversは、 MemoryMetricObserver FileMetricObserver 、およびAsyncMetricObserverです。 前のコードサンプルでMemoryMetricObserverの使用方法をすでに示しました。

現在、いくつかの便利な拡張機能が利用可能です。

  • AtlasMetricObserver :メトリックを Netflix Atlas に公開して、分析用の時系列データをメモリに生成します
  • CloudWatchMetricObserver :メトリクスのモニタリングとトラッキングのためにメトリクスを AmazonCloudWatchにプッシュします
  • GraphiteObserver :メトリックを Graphite に公開して、保存およびグラフ化する

カスタマイズされたMetricObserverを実装して、適切と思われる場所にアプリケーションメトリックを公開できます。 気にする必要があるのは、更新されたメトリックを処理することだけです。

public class CustomObserver extends BaseMetricObserver {

    //...

    @Override
    public void updateImpl(List<Metric> metrics) {
        //TODO
    }
}

4.3. Netflixアトラスに公開

Atlas は、Netflixのもう1つのメトリック関連ツールです。 これは、次元の時系列データを管理するためのツールであり、収集したメトリックを公開するのに最適な場所です。

次に、メトリックをNetflixAtlasに公開する方法を示します。

まず、サーボアトラスの依存関係をpom.xmlに追加しましょう。

<dependency>
      <groupId>com.netflix.servo</groupId>
      <artifactId>servo-atlas</artifactId>
      <version>${netflix.servo.ver}</version>
</dependency>

<properties>
    <netflix.servo.ver>0.12.17</netflix.servo.ver>
</properties>

この依存関係には、Atlasにメトリックを公開するのに役立つAtlasMetricObserverが含まれています。

次に、Atlasサーバーをセットアップします。

$ curl -LO 'https://github.com/Netflix/atlas/releases/download/v1.4.4/atlas-1.4.4-standalone.jar'
$ curl -LO 'https://raw.githubusercontent.com/Netflix/atlas/v1.4.x/conf/memory.conf'
$ java -jar atlas-1.4.4-standalone.jar memory.conf

テストの時間を節約するために、 memory.conf でステップサイズを1秒に設定して、メトリックの十分な詳細を含む時系列グラフを生成できるようにします。

AtlasMetricObserver には、単純な構成とタグのリストが必要です。 指定されたタグのメトリックは、Atlasにプッシュされます。

System.setProperty("servo.pollers", "1000");
System.setProperty("servo.atlas.batchSize", "1");
System.setProperty("servo.atlas.uri", "http://localhost:7101/api/v1/publish");
AtlasMetricObserver observer = new AtlasMetricObserver(
  new BasicAtlasConfig(), BasicTagList.of("servo", "counter"));

PollRunnable task = new PollRunnable(
  new MonitorRegistryMetricPoller(), new BasicMetricFilter(true), observer);

PollRunnableタスクでPollSchedulerを起動した後、メトリックをAtlasに自動的に公開できます。

Counter counter = new BasicCounter(MonitorConfig
  .builder("test")
  .withTag("servo", "counter")
  .build());
DefaultMonitorRegistry
  .getInstance()
  .register(counter);
assertThat(atlasValuesOfTag("servo"), not(containsString("counter")));

for (int i = 0; i < 3; i++) {
    counter.increment(RandomUtils.nextInt(10));
    SECONDS.sleep(1);
    counter.increment(-1 * RandomUtils.nextInt(10));
    SECONDS.sleep(1);
}

assertThat(atlasValuesOfTag("servo"), containsString("counter"));

メトリックに基づいて、AtlasのグラフAPIを使用して折れ線グラフを生成できます。

5. 概要

この記事では、NetflixServoを使用してアプリケーションメトリックを収集および公開する方法を紹介しました。

Dropwizard Metricsの概要をまだ読んでいない場合は、こちらでServoとの簡単な比較を確認してください。

いつものように、この記事の完全な実装コードは、Githubにあります。