MetricsAsserts.java

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hadoop.test;

import static org.apache.hadoop.util.Preconditions.*;

import org.junit.Assert;

import static org.mockito.AdditionalMatchers.geq;
import static org.mockito.Mockito.*;

import org.mockito.stubbing.Answer;
import org.mockito.invocation.InvocationOnMock;

import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;

import org.apache.hadoop.metrics2.MetricsInfo;
import org.apache.hadoop.metrics2.MetricsCollector;
import org.apache.hadoop.metrics2.MetricsSource;
import org.apache.hadoop.metrics2.MetricsRecordBuilder;
import org.apache.hadoop.metrics2.MetricsSystem;
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
import org.apache.hadoop.metrics2.lib.MutableQuantiles;
import org.apache.hadoop.metrics2.util.Quantile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.apache.hadoop.metrics2.lib.Interns.*;

/**
 * Helpers for metrics source tests
 */
public class MetricsAsserts {

  final static Logger LOG = LoggerFactory.getLogger(MetricsAsserts.class);
  private static final double EPSILON = 0.00001;

  public static MetricsSystem mockMetricsSystem() {
    MetricsSystem ms = mock(MetricsSystem.class);
    DefaultMetricsSystem.setInstance(ms);
    return ms;
  }

  public static MetricsRecordBuilder mockMetricsRecordBuilder() {
    final MetricsCollector mc = mock(MetricsCollector.class);
    MetricsRecordBuilder rb = mock(MetricsRecordBuilder.class,
        new Answer<Object>() {
      @Override
      public Object answer(InvocationOnMock invocation) {
        Object[] args = invocation.getArguments();
        StringBuilder sb = new StringBuilder();
        for (Object o : args) {
          if (sb.length() > 0) sb.append(", ");
          sb.append(String.valueOf(o));
        }
        String methodName = invocation.getMethod().getName();
        LOG.debug(methodName +": "+ sb);
        return methodName.equals("parent") || methodName.equals("endRecord") ?
               mc : invocation.getMock();
      }
    });
    when(mc.addRecord(anyString())).thenReturn(rb);
    when(mc.addRecord(anyInfo())).thenReturn(rb);
    return rb;
  }

  /**
   * Call getMetrics on source and get a record builder mock to verify
   * @param source  the metrics source
   * @param all     if true, return all metrics even if not changed
   * @return the record builder mock to verify��
   */
  public static MetricsRecordBuilder getMetrics(MetricsSource source,
                                                boolean all) {
    MetricsRecordBuilder rb = mockMetricsRecordBuilder();
    MetricsCollector mc = rb.parent();
    source.getMetrics(mc, all);
    return rb;
  }

  public static MetricsRecordBuilder getMetrics(String name) {
    return getMetrics(DefaultMetricsSystem.instance().getSource(name));
  }

  public static MetricsRecordBuilder getMetrics(MetricsSource source) {
    return getMetrics(source, true);
  }

  private static class InfoWithSameName
      implements ArgumentMatcher<MetricsInfo>{
    private final String expected;

    InfoWithSameName(MetricsInfo info) {
      expected = checkNotNull(info.name(), "info name");
    }

    @Override
    public boolean matches(MetricsInfo info) {
      return expected.equals(info.name());
    }

    @Override
    public String toString() {
      return "Info with name=" + expected;
    }
  }

  /**
   * MetricInfo with the same name
   * @param info to match
   * @return <code>null</code>
   */
  public static MetricsInfo eqName(MetricsInfo info) {
    return argThat(new InfoWithSameName(info));
  }

  private static class AnyInfo implements ArgumentMatcher<MetricsInfo> {
    @Override
    public boolean matches(MetricsInfo info) {
      return info != null;
    }
  }

  public static MetricsInfo anyInfo() {
    return argThat(new AnyInfo());
  }

  /**
   * Assert an int gauge metric as expected
   * @param name  of the metric
   * @param expected  value of the metric
   * @param rb  the record builder mock used to getMetrics
   */
  public static void assertGauge(String name, int expected,
                                 MetricsRecordBuilder rb) {
    Assert.assertEquals("Bad value for metric " + name,
        expected, getIntGauge(name, rb));
  }

  public static int getIntGauge(String name, MetricsRecordBuilder rb) {
    ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class);
    verify(rb, atLeast(0)).addGauge(eqName(info(name, "")), captor.capture());
    checkCaptured(captor, name);
    return captor.getValue();
  }

  /**
   * Assert an int counter metric as expected
   * @param name  of the metric
   * @param expected  value of the metric
   * @param rb  the record builder mock used to getMetrics
   */
  public static void assertCounter(String name, int expected,
                                   MetricsRecordBuilder rb) {
    Assert.assertEquals("Bad value for metric " + name,
        expected, getIntCounter(name, rb));
  }

  public static int getIntCounter(String name, MetricsRecordBuilder rb) {
    ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(
        Integer.class);
    verify(rb, atLeast(0)).addCounter(eqName(info(name, "")), captor.capture());
    checkCaptured(captor, name);
    return captor.getValue();
  }
  
  /**
   * Assert a long gauge metric as expected
   * @param name  of the metric
   * @param expected  value of the metric
   * @param rb  the record builder mock used to getMetrics
   */
  public static void assertGauge(String name, long expected,
                                 MetricsRecordBuilder rb) {
    Assert.assertEquals("Bad value for metric " + name,
        expected, getLongGauge(name, rb));
  }

  public static long getLongGauge(String name, MetricsRecordBuilder rb) {
    ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
    verify(rb, atLeast(0)).addGauge(eqName(info(name, "")), captor.capture());
    checkCaptured(captor, name);
    return captor.getValue();
  }

   /**
   * Assert a double gauge metric as expected
   * @param name  of the metric
   * @param expected  value of the metric
   * @param rb  the record builder mock used to getMetrics
   */
  public static void assertGauge(String name, double expected,
                                 MetricsRecordBuilder rb) {
    Assert.assertEquals("Bad value for metric " + name,
        expected, getDoubleGauge(name, rb), EPSILON);
  }

  public static double getDoubleGauge(String name, MetricsRecordBuilder rb) {
    ArgumentCaptor<Double> captor = ArgumentCaptor.forClass(Double.class);
    verify(rb, atLeast(0)).addGauge(eqName(info(name, "")), captor.capture());
    checkCaptured(captor, name);
    return captor.getValue();
  }

  /**
   * Assert a long counter metric as expected
   * @param name  of the metric
   * @param expected  value of the metric
   * @param rb  the record builder mock used to getMetrics
   */
  public static void assertCounter(String name, long expected,
                                   MetricsRecordBuilder rb) {
    Assert.assertEquals("Bad value for metric " + name,
        expected, getLongCounter(name, rb));
  }

  public static long getLongCounter(String name, MetricsRecordBuilder rb) {
    ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
    verify(rb, atLeast(0)).addCounter(eqName(info(name, "")), captor.capture());
    checkCaptured(captor, name);
    return captor.getValue();
  }

  public static long getLongCounterWithoutCheck(String name,
    MetricsRecordBuilder rb) {
    ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
    verify(rb, atLeast(0)).addCounter(eqName(info(name, "")), captor.capture());
    return captor.getValue();
  }

  public static String getStringMetric(String name, MetricsRecordBuilder rb) {
    ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
    verify(rb, atLeast(0)).tag(eqName(info(name, "")), captor.capture());
    checkCaptured(captor, name);
    return captor.getValue();
  }

   /**
   * Assert a float gauge metric as expected
   * @param name  of the metric
   * @param expected  value of the metric
   * @param rb  the record builder mock used to getMetrics
   */
  public static void assertGauge(String name, float expected,
                                 MetricsRecordBuilder rb) {
    Assert.assertEquals("Bad value for metric " + name,
        expected, getFloatGauge(name, rb), EPSILON);
  }

  public static float getFloatGauge(String name, MetricsRecordBuilder rb) {
    ArgumentCaptor<Float> captor = ArgumentCaptor.forClass(Float.class);
    verify(rb, atLeast(0)).addGauge(eqName(info(name, "")), captor.capture());
    checkCaptured(captor, name);
    return captor.getValue();
  }

  /**
   * Check that this metric was captured exactly once.
   */
  private static void checkCaptured(ArgumentCaptor<?> captor, String name) {
    Assert.assertEquals("Expected exactly one metric for name " + name,
        1, captor.getAllValues().size());
  }

  /**
   * Assert an int gauge metric as expected
   * @param name  of the metric
   * @param expected  value of the metric
   * @param source  to get metrics from
   */
  public static void assertGauge(String name, int expected,
                                 MetricsSource source) {
    assertGauge(name, expected, getMetrics(source));
  }

  /**
   * Assert an int counter metric as expected
   * @param name  of the metric
   * @param expected  value of the metric
   * @param source  to get metrics from
   */
  public static void assertCounter(String name, int expected,
                                   MetricsSource source) {
    assertCounter(name, expected, getMetrics(source));
  }

  /**
   * Assert a long gauge metric as expected
   * @param name  of the metric
   * @param expected  value of the metric
   * @param source  to get metrics from
   */
  public static void assertGauge(String name, long expected,
                                 MetricsSource source) {
    assertGauge(name, expected, getMetrics(source));
  }

  /**
   * Assert a long counter metric as expected
   * @param name  of the metric
   * @param expected  value of the metric
   * @param source  to get metrics from
   */
  public static void assertCounter(String name, long expected,
                                   MetricsSource source) {
    assertCounter(name, expected, getMetrics(source));
  }

  /**
   * Assert that a long counter metric is greater than a value
   * @param name  of the metric
   * @param greater value of the metric should be greater than this
   * @param rb  the record builder mock used to getMetrics
   */
  public static void assertCounterGt(String name, long greater,
                                     MetricsRecordBuilder rb) {
    Assert.assertTrue("Bad value for metric " + name,
        getLongCounter(name, rb) > greater);
  }

  /**
   * Assert that a long counter metric is greater than a value
   * @param name  of the metric
   * @param greater value of the metric should be greater than this
   * @param source  the metrics source
   */
  public static void assertCounterGt(String name, long greater,
                                     MetricsSource source) {
    assertCounterGt(name, greater, getMetrics(source));
  }

  /**
   * Assert that a double gauge metric is greater than a value
   * @param name  of the metric
   * @param greater value of the metric should be greater than this
   * @param rb  the record builder mock used to getMetrics
   */
  public static void assertGaugeGt(String name, double greater,
                                   MetricsRecordBuilder rb) {
    Assert.assertTrue("Bad value for metric " + name,
        getDoubleGauge(name, rb) > greater);
  }

  /**
   * Assert that a double gauge metric is greater than or equal to a value.
   * @param name  of the metric
   * @param greater value of the metric should be greater than or equal to this
   * @param rb  the record builder mock used to getMetrics
   */
  public static void assertGaugeGte(String name, double greater,
      MetricsRecordBuilder rb) {
    double curValue = getDoubleGauge(name, rb);
    Assert.assertTrue("Bad value for metric " + name,
        curValue >= greater);
  }

  /**
   * Assert that a double gauge metric is greater than a value
   * @param name  of the metric
   * @param greater value of the metric should be greater than this
   * @param source  the metrics source
   */
  public static void assertGaugeGt(String name, double greater,
                                   MetricsSource source) {
    assertGaugeGt(name, greater, getMetrics(source));
  }
  
  /**
   * Asserts that the NumOps and quantiles for a metric with value name
   * "Latency" have been changed at some point to a non-zero value.
   * 
   * @param prefix of the metric
   * @param rb MetricsRecordBuilder with the metric
   */
  public static void assertQuantileGauges(String prefix,
      MetricsRecordBuilder rb) {
    assertQuantileGauges(prefix, rb, "Latency");
  }

  /**
   * Asserts that the NumOps and quantiles for a metric have been changed at
   * some point to a non-zero value, for the specified value name of the
   * metrics (e.g., "Latency", "Count").
   *
   * @param prefix of the metric
   * @param rb MetricsRecordBuilder with the metric
   * @param valueName the value name for the metric
   */
  public static void assertQuantileGauges(String prefix,
      MetricsRecordBuilder rb, String valueName) {
    verify(rb).addGauge(eqName(info(prefix + "NumOps", "")), geq(0L));
    for (Quantile q : MutableQuantiles.QUANTILES) {
      String nameTemplate = prefix + "%dthPercentile" + valueName;
      int percentile = (int) (100 * q.quantile);
      verify(rb).addGauge(
          eqName(info(String.format(nameTemplate, percentile), "")),
          geq(0L));
    }
  }

  /**
   * Asserts that the NumOps and inverse quantiles for a metric have been changed at
   * some point to a non-zero value, for the specified value name of the
   * metrics (e.g., "Rate").
   *
   * @param prefix of the metric
   * @param rb MetricsRecordBuilder with the metric
   * @param valueName the value name for the metric
   */
  public static void assertInverseQuantileGauges(String prefix,
      MetricsRecordBuilder rb, String valueName) {
    verify(rb).addGauge(eqName(info(prefix + "NumOps", "")), geq(0L));
    for (Quantile q : MutableQuantiles.QUANTILES) {
      String nameTemplate = prefix + "%dthInversePercentile" + valueName;
      int percentile = (int) (100 * q.quantile);
      verify(rb).addGauge(
          eqName(info(String.format(nameTemplate, percentile), "")),
          geq(0L));
    }
  }

  /**
   * Assert a tag of metric as expected.
   * @param name  of the metric tag
   * @param expected  value of the metric tag
   * @param rb  the record builder mock used to getMetrics
   */
  public static void assertTag(String name, String expected,
      MetricsRecordBuilder rb) {
    Assert.assertEquals("Bad Tag for metric " + name,
        expected, getStringTag(name, rb));
  }

  /**
   * get the value tag for the metric.
   * @param name  of the metric tag
   * @param rb value of the metric tag
   * @return the value tag for the metric
   */
  public static String getStringTag(String name, MetricsRecordBuilder rb) {
    ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
    verify(rb).tag(eqName(info(name, "")), captor.capture());
    checkCaptured(captor, name);
    return captor.getValue();
  }
}