Added a framework for metrics
Added a metrics framework based on the codahale Metrics
package. ONOS can create a registry and maintain Metrics,
as well as dump out metrics values via a REST API. Unit
tests are include that test the REST APIs. Currently
supports Timers, Counters, Gauges, Meters, and Histograms.
Change-Id: I0f6ed87f889dc7037caf9aefc92e663702c6dda8
diff --git a/conf/onos.properties b/conf/onos.properties
index c8509cf..a19cee5 100644
--- a/conf/onos.properties
+++ b/conf/onos.properties
@@ -5,7 +5,8 @@
net.onrc.onos.core.flowprogrammer.FlowProgrammer,\
net.onrc.onos.core.intent.runtime.PathCalcRuntimeModule,\
net.onrc.onos.core.intent.runtime.PlanInstallModule,\
-net.onrc.onos.core.registry.ZookeeperRegistry
+net.onrc.onos.core.registry.ZookeeperRegistry, \
+net.onrc.onos.core.metrics.OnosMetricsModule
net.floodlightcontroller.restserver.RestApiServer.port = 8080
net.floodlightcontroller.core.FloodlightProvider.openflowport = 6633
net.floodlightcontroller.core.FloodlightProvider.workerthreads = 16
diff --git a/pom.xml b/pom.xml
index bced4a7..61f8a8c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,6 +46,7 @@
<github.global.server>github</github.global.server>
-->
<hazelcast.version>3.2.3</hazelcast.version>
+ <metrics.version>3.0.1</metrics.version>
</properties>
<build>
<plugins>
@@ -634,6 +635,16 @@
<artifactId>findbugs</artifactId>
<version>${findbugs.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.codahale.metrics</groupId>
+ <artifactId>metrics-core</artifactId>
+ <version>${metrics.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.codahale.metrics</groupId>
+ <artifactId>metrics-json</artifactId>
+ <version>${metrics.version}</version>
+ </dependency>
<!-- Floodlight's dependencies -->
<dependency>
<groupId>args4j</groupId>
diff --git a/src/main/java/net/onrc/onos/core/metrics/MetricsObjectResource.java b/src/main/java/net/onrc/onos/core/metrics/MetricsObjectResource.java
new file mode 100644
index 0000000..efb0213
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/metrics/MetricsObjectResource.java
@@ -0,0 +1,283 @@
+package net.onrc.onos.core.metrics;
+
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.Histogram;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+import java.util.List;
+
+/**
+ * Resource class to hold Metrics information. Timers, Gauges, Counters,
+ * Meters and Historgrams are currently implemented.
+ */
+@JsonSerialize(using = MetricsObjectSerializer.class)
+@SuppressWarnings("rawtypes")
+public class MetricsObjectResource {
+
+ /**
+ * Base Metric object that all metrics inherit from. Defines common
+ * attributes.
+ */
+ static class BaseMetricObject {
+ private final String name;
+
+ /**
+ * Constructor for the base object. Sets the name attribute.
+ *
+ * @param newName name of the Metric
+ */
+ protected BaseMetricObject(final String newName) {
+ name = newName;
+ }
+
+ /**
+ * Get the name of the Metric.
+ *
+ * @return metric name
+ */
+ public String getName() {
+ return name;
+ }
+ }
+
+ /**
+ * Metric object that represents a Timer.
+ */
+ static class TimerObjectResource extends BaseMetricObject {
+ private final Timer timer;
+
+ /**
+ * Construct a new Timer resource object.
+ *
+ * @param newName name to use for the timer
+ * @param newTimer Metrics Timer object
+ */
+ public TimerObjectResource(final String newName,
+ final Timer newTimer) {
+ super(newName);
+ timer = newTimer;
+ }
+
+ /**
+ * Get the Metrics Timer object for this resource.
+ *
+ * @return Metrics Timer object.
+ */
+ public Timer getTimer() {
+ return timer;
+ }
+ }
+
+ /**
+ * Metric object that represents a Gauge.
+ */
+ static class GaugeObjectResource extends BaseMetricObject {
+ private final Gauge gauge;
+
+ /**
+ * Constructs a new Gauge resource object.
+ *
+ * @param newName name to use for the Gauge object
+ * @param newGauge Metrics Gauge object
+ */
+ public GaugeObjectResource(final String newName,
+ final Gauge newGauge) {
+ super(newName);
+ gauge = newGauge;
+ }
+
+ /**
+ * Gets the Metrics Gauge object for this resource.
+ *
+ * @return Metrics Gauge object.
+ */
+ public Gauge getGauge() {
+ return gauge;
+ }
+ }
+
+ /**
+ * Metric object that represents a Counter.
+ */
+ static class CounterObjectResource extends BaseMetricObject {
+ private final Counter counter;
+
+ /**
+ * Constructs a new Counter resource object.
+ *
+ * @param newName name to use for the Counter object
+ * @param newCounter Metrics Counter object
+ */
+ public CounterObjectResource(final String newName,
+ final Counter newCounter) {
+ super(newName);
+ counter = newCounter;
+ }
+
+ /**
+ * Gets the Metrics Counter object for this resource.
+ *
+ * @return Metrics Counter object.
+ */
+ public Counter getCounter() {
+ return counter;
+ }
+ }
+
+ /**
+ * Metric object that represents a Meter.
+ */
+ static class MeterObjectResource extends BaseMetricObject {
+ private final Meter meter;
+
+ /**
+ * Constructs a new Meter resource object.
+ *
+ * @param newName name to use for the Meter object
+ * @param newMeter Metrics Meter object
+ */
+ public MeterObjectResource(final String newName,
+ final Meter newMeter) {
+ super(newName);
+ meter = newMeter;
+ }
+
+ /**
+ * Gets the Metrics Meter object for this resource.
+ *
+ * @return Metrics Meter object.
+ */
+ public Meter getMeter() {
+ return meter;
+ }
+ }
+
+ /**
+ * Metric objerct that represents a Histogram.
+ */
+ static class HistogramObjectResource extends BaseMetricObject {
+ private final Histogram histogram;
+
+ /**
+ * Constructs a new Histogram resource object.
+ *
+ * @param newName name to use for Histogram object.
+ * @param newHistogram Metrics Histogram object.
+ */
+ public HistogramObjectResource(final String newName,
+ final Histogram newHistogram) {
+ super(newName);
+ histogram = newHistogram;
+ }
+
+ /**
+ * Gets the Metrics Histogram object for this resource.
+ *
+ * @return Metrics Histogram Object
+ */
+ public Histogram getHistogram() {
+ return histogram;
+ }
+ }
+
+
+ private List<TimerObjectResource> timers;
+ private List<GaugeObjectResource> gauges;
+ private List<CounterObjectResource> counters;
+ private List<MeterObjectResource> meters;
+ private List<HistogramObjectResource> histograms;
+
+ /**
+ * Gets the list of Gauge objects.
+ *
+ * @return list of gauges
+ */
+ public List<GaugeObjectResource> getGauges() {
+ return gauges;
+ }
+
+ /**
+ * Defines the list of Gauge objects.
+ *
+ * @param gauges list of gauges
+ */
+ public void setGauges(List<GaugeObjectResource> gauges) {
+ this.gauges = gauges;
+ }
+
+ /**
+ * Gets the list of Timer objects.
+ *
+ * @return list of Timers
+ */
+ public List<TimerObjectResource> getTimers() {
+ return timers;
+ }
+
+ /**
+ * Defines the list of Timer objects.
+ *
+ * @param newTimers list of Timers
+ */
+ public void setTimers(List<TimerObjectResource> newTimers) {
+ timers = newTimers;
+ }
+
+ /**
+ * Gets the list of Counter objects.
+ *
+ * @return list of Counters
+ */
+ public List<CounterObjectResource> getCounters() {
+ return counters;
+ }
+
+ /**
+ * Defines the list of Counter objects.
+ *
+ * @param counters list of Counters
+ */
+ public void setCounters(List<CounterObjectResource> counters) {
+ this.counters = counters;
+ }
+
+ /**
+ * Gets the list of Meter objects.
+ *
+ * @return list of Meters
+ */
+ public List<MeterObjectResource> getMeters() {
+ return meters;
+ }
+
+ /**
+ * Defines the list of Meter objects.
+ *
+ * @param meters list of Meters
+ */
+ public void setMeters(List<MeterObjectResource> meters) {
+ this.meters = meters;
+ }
+
+ /**
+ * Gets the list of Histogram objects.
+ *
+ * @return list of Histograms
+ */
+ public List<HistogramObjectResource> getHistograms() {
+ return histograms;
+ }
+
+ /**
+ * Defines the list of Histogram objects.
+ *
+ * @param histograms list of Histograms.
+ */
+ public void setHistograms(List<HistogramObjectResource> histograms) {
+ this.histograms = histograms;
+ }
+}
diff --git a/src/main/java/net/onrc/onos/core/metrics/MetricsObjectSerializer.java b/src/main/java/net/onrc/onos/core/metrics/MetricsObjectSerializer.java
new file mode 100644
index 0000000..ba32381
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/metrics/MetricsObjectSerializer.java
@@ -0,0 +1,123 @@
+package net.onrc.onos.core.metrics;
+
+import com.codahale.metrics.json.MetricsModule;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.codehaus.jackson.map.ser.std.SerializerBase;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * JSON serializer for the Metrics resource.
+ */
+public class MetricsObjectSerializer extends SerializerBase<MetricsObjectResource> {
+
+ /**
+ * Public constructor - just calls its super class constructor.
+ */
+ public MetricsObjectSerializer() {
+ super(MetricsObjectResource.class);
+ }
+
+ /**
+ * Convenience method to serialize a Metrics field.
+ *
+ * @param jsonGenerator generator to use for serialization
+ * @param fieldName name of the top level field
+ * @param serializedObjectJSON JSON representation from the Metrics serializer
+ * @param object internal resource for the Metric
+ * @throws IOException if JSON generation fails.
+ */
+ private void serializeItem(final JsonGenerator jsonGenerator,
+ final String fieldName,
+ final String serializedObjectJSON,
+ final MetricsObjectResource.BaseMetricObject object)
+ throws IOException {
+ jsonGenerator.writeStartObject();
+ jsonGenerator.writeStringField("name", object.getName());
+
+ // If you write the JSON for the Metric using a StringField, the
+ // generator applies an extra set of double quotes and breaks the
+ // syntax. You have to use the raw JSON output to get it right.
+ jsonGenerator.writeRaw(",\"" + fieldName + "\": " + serializedObjectJSON);
+ jsonGenerator.writeEndObject();
+ }
+
+ /**
+ * Serialize a MetricsObjectResource into JSON. For each kind of Metric,
+ * his serializes common ONOS defined fields like name and
+ * then calls the Metrics serializer to make the JSON string
+ * for the actual Metric.
+ *
+ * @param metrics resource for all ONOS Metrics
+ * @param jsonGenerator generator to use for the JSON output
+ * @param serializerProvider unused, needed for Override
+ * @throws IOException if any of the JSON serializations fail
+ */
+ @Override
+ @SuppressWarnings("rawtypes")
+ public void serialize(final MetricsObjectResource metrics,
+ final JsonGenerator jsonGenerator,
+ final SerializerProvider serializerProvider)
+ throws IOException {
+
+ final ObjectMapper mapper = new ObjectMapper().registerModule(
+ new MetricsModule(TimeUnit.SECONDS, TimeUnit.MILLISECONDS, false));
+ jsonGenerator.writeStartObject();
+
+ // serialize Timers
+ jsonGenerator.writeArrayFieldStart("timers");
+
+ for (final MetricsObjectResource.TimerObjectResource timer :
+ metrics.getTimers()) {
+ final String timerJSON = mapper.writeValueAsString(timer.getTimer());
+ serializeItem(jsonGenerator, "timer", timerJSON, timer);
+ }
+ jsonGenerator.writeEndArray();
+
+ // Serialize Gauges
+ jsonGenerator.writeArrayFieldStart("gauges");
+
+ for (final MetricsObjectResource.GaugeObjectResource gauge :
+ metrics.getGauges()) {
+ final String gaugeJSON = mapper.writeValueAsString(gauge.getGauge());
+ serializeItem(jsonGenerator, "gauge", gaugeJSON, gauge);
+ }
+ jsonGenerator.writeEndArray();
+
+ // Serialize Counters
+ jsonGenerator.writeArrayFieldStart("counters");
+
+ for (final MetricsObjectResource.CounterObjectResource counter :
+ metrics.getCounters()) {
+ final String counterJSON = mapper.writeValueAsString(counter.getCounter());
+ serializeItem(jsonGenerator, "counter", counterJSON, counter);
+ }
+ jsonGenerator.writeEndArray();
+
+ // Serialize Meters
+ jsonGenerator.writeArrayFieldStart("meters");
+
+ for (final MetricsObjectResource.MeterObjectResource meter :
+ metrics.getMeters()) {
+ final String meterJSON = mapper.writeValueAsString(meter.getMeter());
+ serializeItem(jsonGenerator, "meter", meterJSON, meter);
+ }
+ jsonGenerator.writeEndArray();
+
+ // Serialize Histograms
+ jsonGenerator.writeArrayFieldStart("histograms");
+
+ for (final MetricsObjectResource.HistogramObjectResource histogram :
+ metrics.getHistograms()) {
+ final String histogramJSON = mapper.writeValueAsString(histogram.getHistogram());
+ serializeItem(jsonGenerator, "histogram", histogramJSON, histogram);
+ }
+ jsonGenerator.writeEndArray();
+
+ jsonGenerator.writeEndObject();
+ }
+
+}
diff --git a/src/main/java/net/onrc/onos/core/metrics/MetricsResource.java b/src/main/java/net/onrc/onos/core/metrics/MetricsResource.java
new file mode 100644
index 0000000..64b7996
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/metrics/MetricsResource.java
@@ -0,0 +1,82 @@
+package net.onrc.onos.core.metrics;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Histogram;
+import org.restlet.representation.Representation;
+import org.restlet.resource.Get;
+import org.restlet.resource.ServerResource;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * REST APIs for Metrics objects.
+ */
+public class MetricsResource extends ServerResource {
+
+ /**
+ * REST API to get all of the system's metrics.
+ *
+ * @return a Representation object containing the metrics
+ */
+ @Get("json")
+ @SuppressWarnings("rawtypes")
+ public Representation retrieve() throws Exception {
+ final MetricRegistry registry = OnosMetrics.getMetricsRegistry();
+ final MetricsObjectResource result = new MetricsObjectResource();
+
+ final List<MetricsObjectResource.TimerObjectResource> timers =
+ new ArrayList<>();
+ final List<MetricsObjectResource.GaugeObjectResource> gauges =
+ new ArrayList<>();
+ final List<MetricsObjectResource.CounterObjectResource> counters =
+ new ArrayList<>();
+ final List<MetricsObjectResource.MeterObjectResource> meters =
+ new ArrayList<>();
+ final List<MetricsObjectResource.HistogramObjectResource> histograms =
+ new ArrayList<>();
+
+ for (final Map.Entry<String, Timer> timer :
+ registry.getTimers().entrySet()) {
+ timers.add(new MetricsObjectResource.TimerObjectResource(
+ timer.getKey(), timer.getValue()));
+ }
+ result.setTimers(timers);
+
+ for (final Map.Entry<String, Gauge> gauge :
+ registry.getGauges().entrySet()) {
+ gauges.add(new MetricsObjectResource.GaugeObjectResource(
+ gauge.getKey(), gauge.getValue()));
+ }
+ result.setGauges(gauges);
+
+ for (final Map.Entry<String, Counter> counter :
+ registry.getCounters().entrySet()) {
+ counters.add(new MetricsObjectResource.CounterObjectResource(
+ counter.getKey(), counter.getValue()));
+ }
+ result.setCounters(counters);
+
+ for (final Map.Entry<String, Meter> meter :
+ registry.getMeters().entrySet()) {
+ meters.add(new MetricsObjectResource.MeterObjectResource(
+ meter.getKey(), meter.getValue()));
+ }
+ result.setMeters(meters);
+
+ for (final Map.Entry<String, Histogram> histogram :
+ registry.getHistograms().entrySet()) {
+ histograms.add(new MetricsObjectResource.HistogramObjectResource(
+ histogram.getKey(), histogram.getValue()));
+ }
+ result.setHistograms(histograms);
+
+ return toRepresentation(result, null);
+ }
+
+}
diff --git a/src/main/java/net/onrc/onos/core/metrics/MetricsWebRoutable.java b/src/main/java/net/onrc/onos/core/metrics/MetricsWebRoutable.java
new file mode 100644
index 0000000..2761e71
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/metrics/MetricsWebRoutable.java
@@ -0,0 +1,31 @@
+package net.onrc.onos.core.metrics;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+/**
+ * Restlet Router for Metrics REST APIs.
+ */
+public class MetricsWebRoutable implements RestletRoutable {
+ /**
+ * Creates the Restlet router and binds to the proper resources.
+ */
+ @Override
+ public Restlet getRestlet(Context context) {
+ Router router = new Router(context);
+ // GET all metrics
+ router.attach("", MetricsResource.class);
+ return router;
+ }
+
+
+ /**
+ * Sets the base path for the Metrics.
+ */
+ @Override
+ public String basePath() {
+ return "/wm/onos/metrics";
+ }
+}
diff --git a/src/main/java/net/onrc/onos/core/metrics/OnosMetrics.java b/src/main/java/net/onrc/onos/core/metrics/OnosMetrics.java
new file mode 100644
index 0000000..bfd603f
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/metrics/OnosMetrics.java
@@ -0,0 +1,28 @@
+package net.onrc.onos.core.metrics;
+
+import com.codahale.metrics.MetricRegistry;
+
+/**
+ * This class acts a singleton to hold the Metrics registry for ONOS.
+ */
+public final class OnosMetrics {
+
+ /**
+ * Hide constructor. The only way to get the registry is through the
+ * singleton getter.
+ */
+ private OnosMetrics() {}
+
+ private static final MetricRegistry METRICS_REGISTRY = new MetricRegistry();
+
+ /**
+ * Get the singleton Metrics registry. A single instance of
+ * the registry is statically allocated and then used by all callers.
+ *
+ * @return Metrics registry
+ */
+ public static MetricRegistry getMetricsRegistry() {
+ return METRICS_REGISTRY;
+ }
+}
+
diff --git a/src/main/java/net/onrc/onos/core/metrics/OnosMetricsModule.java b/src/main/java/net/onrc/onos/core/metrics/OnosMetricsModule.java
new file mode 100644
index 0000000..6f046cf
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/metrics/OnosMetricsModule.java
@@ -0,0 +1,53 @@
+package net.onrc.onos.core.metrics;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.restserver.IRestApiService;
+import net.onrc.onos.core.registry.IControllerRegistryService;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Floodlight module to add the REST APIs for Metrics.
+ */
+public class OnosMetricsModule implements IFloodlightModule {
+
+ private IRestApiService restApi;
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ return null;
+ }
+
+ @Override
+ public Map<Class<? extends IFloodlightService>, IFloodlightService>
+ getServiceImpls() {
+ return null;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+ List<Class<? extends IFloodlightService>> dependencies =
+ new ArrayList<>();
+ dependencies.add(IControllerRegistryService.class);
+ dependencies.add(IRestApiService.class);
+ return dependencies;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ restApi = context.getServiceImpl(IRestApiService.class);
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context) {
+ restApi.addRestletRoutable(new MetricsWebRoutable());
+ }
+}
+
diff --git a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
index 8460383..28c4a83 100644
--- a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
+++ b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
@@ -18,3 +18,4 @@
net.onrc.onos.core.intent.runtime.PathCalcRuntimeModule
net.onrc.onos.core.intent.runtime.PlanInstallModule
net.onrc.onos.core.packetservice.PacketModule
+net.onrc.onos.core.metrics.OnosMetricsModule
diff --git a/src/test/java/net/onrc/onos/api/rest/TestRestMetrics.java b/src/test/java/net/onrc/onos/api/rest/TestRestMetrics.java
new file mode 100644
index 0000000..6b5f453
--- /dev/null
+++ b/src/test/java/net/onrc/onos/api/rest/TestRestMetrics.java
@@ -0,0 +1,119 @@
+package net.onrc.onos.api.rest;
+
+import com.codahale.metrics.Clock;
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.onrc.onos.core.intent.runtime.IntentTestMocks;
+import net.onrc.onos.core.intent.runtime.PathCalcRuntimeModule;
+import net.onrc.onos.core.metrics.MetricsWebRoutable;
+import net.onrc.onos.core.topology.ITopologyService;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Test harness for Metrics based REST API tests. This class maintains the
+ * web server and mocks required for testing metrics APIs. REST API tests
+ * for metrics should inherit from this class.
+ */
+public class TestRestMetrics extends TestRest {
+
+ private IntentTestMocks mocks;
+
+ /**
+ * Fetch the Intent mocking object.
+ *
+ * @return intent mocking object
+ */
+ IntentTestMocks getMocks() {
+ return mocks;
+ }
+
+ /**
+ * Create the web server and mocks required for the topology tests.
+ */
+ @Override
+ public void setUp() {
+ mocks = new IntentTestMocks();
+ mocks.setUpIntentMocks();
+
+ addRestlet(new MetricsWebRoutable());
+ super.setUp();
+
+ final PathCalcRuntimeModule runtime = new PathCalcRuntimeModule();
+ final FloodlightModuleContext moduleContext = getMocks().getModuleContext();
+ try {
+ runtime.init(moduleContext);
+ } catch (FloodlightModuleException floodlightEx) {
+ throw new IllegalArgumentException(floodlightEx);
+ }
+ runtime.startUp(moduleContext);
+
+ getRestApiServer().addAttribute(ITopologyService.class.getCanonicalName(),
+ mocks.getTopologyService());
+ }
+
+ /**
+ * Remove anything that will interfere with the next test running correctly.
+ * Shuts down the test REST web server and removes the mocks.
+ */
+ @Override
+ public void tearDown() {
+ getMocks().tearDownIntentMocks();
+ super.tearDown();
+ }
+
+ /**
+ * Fetch the base URL for Metrics REST APIs.
+ *
+ * @return base URL
+ */
+ String getBaseRestMetricsUrl() {
+ return getBaseRestUrl() + "/metrics";
+ }
+
+ /**
+ * Check that the given list of elements in a JSON object are all 0 length
+ * arrays.
+ *
+ * @param elementNames names of top level elements to check
+ * @param jsonObject top level JSON object
+ * @throws JSONException if JSON fetching throws an error
+ */
+ public void checkEmptyLists(final JSONObject jsonObject,
+ final String ... elementNames)
+ throws JSONException {
+ for (final String elementName : elementNames) {
+ final JSONArray element = jsonObject.getJSONArray(elementName);
+ assertThat(element, is(notNullValue()));
+ assertThat(element.length(), is(0));
+ }
+ }
+
+ public static final int MOCK_CLOCK_MILISECONDS_PER_TICK = 50;
+
+ /**
+ * Mock clock used for Timer and Meter tests to give known time values for
+ * test data. Each simulated tick increments the time by
+ * MOCK_CLOCK_MILISECONDS_PER_TICK which is currently defined for 50
+ * millisecond ticks.
+ */
+ protected final Clock mockClock = new Clock() {
+ private long currentTime = 0;
+
+ @Override
+ public long getTick() {
+ final long tickInNanoseconds =
+ TimeUnit.NANOSECONDS.convert(MOCK_CLOCK_MILISECONDS_PER_TICK,
+ TimeUnit.MILLISECONDS);
+ currentTime = currentTime + tickInNanoseconds;
+ return currentTime;
+ }
+ };
+}
diff --git a/src/test/java/net/onrc/onos/api/rest/TestRestMetricsCounters.java b/src/test/java/net/onrc/onos/api/rest/TestRestMetricsCounters.java
new file mode 100644
index 0000000..84fb4db
--- /dev/null
+++ b/src/test/java/net/onrc/onos/api/rest/TestRestMetricsCounters.java
@@ -0,0 +1,136 @@
+package net.onrc.onos.api.rest;
+
+import com.codahale.metrics.Counter;
+import net.onrc.onos.core.intent.runtime.PathCalcRuntimeModule;
+import net.onrc.onos.core.metrics.OnosMetrics;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.restlet.resource.ClientResource;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Unit tests for REST APIs for Counter Metrics.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(PathCalcRuntimeModule.class)
+public class TestRestMetricsCounters extends TestRestMetrics {
+
+ /**
+ * Create the web server and mocks required for
+ * all of the tests.
+ */
+ @Before
+ @SuppressWarnings("ununsed")
+ public void beforeTest() {
+ setRestPort(generateRandomPort());
+ setUp();
+ }
+
+ /**
+ * Remove anything that will interfere with the next test running correctly.
+ * Shuts down the test REST web server and removes the mocks.
+ */
+ @After
+ @SuppressWarnings("unused")
+ public void afterTest() {
+ tearDown();
+ }
+
+ // Test Counter data objects
+ private static final String COUNTER1_NAME = "COUNTER1";
+ private static final int COUNTER1_COUNT = 0;
+
+ private static final String COUNTER2_NAME = "COUNTER2";
+ private static final int COUNTER2_COUNT = -1;
+
+ private static final String COUNTER3_NAME = "COUNTER3";
+ private static final int COUNTER3_COUNT = 5;
+
+ private final Counter counter1 =
+ OnosMetrics.getMetricsRegistry().counter(COUNTER1_NAME);
+ private final Counter counter2 =
+ OnosMetrics.getMetricsRegistry().counter(COUNTER2_NAME);
+ private final Counter counter3 =
+ OnosMetrics.getMetricsRegistry().counter(COUNTER3_NAME);
+
+ /**
+ * Create some test data for the tests.
+ */
+ private void fillCounters() {
+ counter1.inc(COUNTER1_COUNT);
+ counter2.inc(COUNTER2_COUNT);
+ counter3.inc(COUNTER3_COUNT);
+ }
+
+ /**
+ * Check that a Counter object has the right contents.
+ *
+ * @param counterContainer JSON for the Counter
+ * @param name name of the Counter
+ * @param count expected count for the Counter
+ * @throws JSONException if any of the JSON fetches fail
+ */
+ private void checkCounter(final JSONObject counterContainer,
+ final String name,
+ final int count) throws JSONException {
+ final String counterName = counterContainer.getString("name");
+ assertThat(counterName, is(notNullValue()));
+ assertThat(counterName, is(equalTo(name)));
+
+ final JSONObject counterObject = counterContainer.getJSONObject("counter");
+ assertThat(counterObject, is(notNullValue()));
+
+ final int counterValue = counterObject.getInt("count");
+ assertThat(counterValue, is(equalTo(count)));
+ }
+
+ /**
+ * Test the REST APIs for Metrics Counter objects.
+ *
+ * @throws JSONException
+ */
+ @Test
+ public void testCounters() throws JSONException {
+
+ fillCounters();
+
+ // Read the metrics from the REST API for the test data
+ final ClientResource client = new ClientResource(getBaseRestMetricsUrl());
+
+ final JSONObject metrics = getJSONObject(client);
+ assertThat(metrics.length(), is(equalTo(5)));
+
+ // There should be 3 counters
+ final JSONArray counters = metrics.getJSONArray("counters");
+ assertThat(counters, is(notNullValue()));
+ assertThat(counters.length(), is(3));
+
+ // There should be no timers, gauges, meters or histograms
+ checkEmptyLists(metrics, "timers", "gauges", "meters", "histograms");
+
+ // Check the values for counter 1
+ final JSONObject counter1Container = counters.getJSONObject(0);
+ checkCounter(counter1Container, COUNTER1_NAME, COUNTER1_COUNT);
+
+ // Check the values for counter 1
+ final JSONObject counter2Container = counters.getJSONObject(1);
+ checkCounter(counter2Container, COUNTER2_NAME, COUNTER2_COUNT);
+
+ // Check the values for counter 1
+ final JSONObject counter3Container = counters.getJSONObject(2);
+ checkCounter(counter3Container, COUNTER3_NAME, COUNTER3_COUNT);
+
+ }
+
+}
diff --git a/src/test/java/net/onrc/onos/api/rest/TestRestMetricsGauges.java b/src/test/java/net/onrc/onos/api/rest/TestRestMetricsGauges.java
new file mode 100644
index 0000000..91cc86f
--- /dev/null
+++ b/src/test/java/net/onrc/onos/api/rest/TestRestMetricsGauges.java
@@ -0,0 +1,148 @@
+package net.onrc.onos.api.rest;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.MetricRegistry;
+import net.onrc.onos.core.intent.runtime.PathCalcRuntimeModule;
+import net.onrc.onos.core.metrics.OnosMetrics;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.restlet.resource.ClientResource;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Unit tests for REST APIs for Gauges Metrics.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(PathCalcRuntimeModule.class)
+public class TestRestMetricsGauges extends TestRestMetrics {
+
+ /**
+ * Create the web server and mocks required for
+ * all of the tests.
+ */
+ @Before
+ @SuppressWarnings("ununsed")
+ public void beforeTest() {
+ setRestPort(generateRandomPort());
+ setUp();
+ }
+
+ /**
+ * Remove anything that will interfere with the next test running correctly.
+ * Shuts down the test REST web server and removes the mocks.
+ */
+ @After
+ @SuppressWarnings("unused")
+ public void afterTest() {
+ tearDown();
+ }
+
+ // Test data for Gauges
+ private static final String GAUGE1_NAME = "gauge1";
+ private static final int GAUGE1_VALUE = 0;
+
+ private static final String GAUGE2_NAME = "gauge2";
+ private static final int GAUGE2_VALUE = -1;
+
+ private static final String GAUGE3_NAME = "gauge3";
+ private static final int GAUGE3_VALUE = 123456789;
+
+ private final Gauge<Integer> gauge1 = OnosMetrics.getMetricsRegistry().
+ register(MetricRegistry.name(GAUGE1_NAME),
+ new Gauge<Integer>() {
+ @Override
+ public Integer getValue() {
+ return GAUGE1_VALUE;
+ }
+ });
+
+ private final Gauge<Integer> gauge2 = OnosMetrics.getMetricsRegistry().
+ register(MetricRegistry.name(GAUGE2_NAME),
+ new Gauge<Integer>() {
+ @Override
+ public Integer getValue() {
+ return GAUGE2_VALUE;
+ }
+ });
+
+ private final Gauge<Integer> gauge3 = OnosMetrics.getMetricsRegistry().
+ register(MetricRegistry.name(GAUGE3_NAME),
+ new Gauge<Integer>() {
+ @Override
+ public Integer getValue() {
+ return GAUGE3_VALUE;
+ }
+ });
+
+ /**
+ * Check that the JSON for a Gauge obect has the correct data values.
+ *
+ * @param gaugeContainer JSON object for the Gauge
+ * @param name expected name of the gauge
+ * @param gauge Metrics Gauge object that hold the expected value
+ * @throws JSONException if any JSON operation fails
+ */
+ private void checkGauge(final JSONObject gaugeContainer,
+ final String name,
+ final Gauge<Integer> gauge)
+ throws JSONException {
+ assertThat(gaugeContainer, is(notNullValue()));
+
+ final String gaugeName = gaugeContainer.getString("name");
+ assertThat(gaugeName, is(notNullValue()));
+ assertThat(gaugeName, is(equalTo(name)));
+
+ final JSONObject gaugeObject = gaugeContainer.getJSONObject("gauge");
+ assertThat(gaugeObject, is(notNullValue()));
+
+ final int gaugeValue = gaugeObject.getInt("value");
+ assertThat(gaugeValue, is(equalTo(gauge.getValue())));
+ }
+
+ /**
+ * Unit test for the Gauges portion of the Metrics REST API.
+ *
+ * @throws JSONException if any JSON operation fails
+ */
+ @Test
+ public void testGauges() throws Exception {
+
+ // Read the metrics from the REST API for the test data
+ final ClientResource client = new ClientResource(getBaseRestMetricsUrl());
+
+ final JSONObject metrics = getJSONObject(client);
+ assertThat(metrics.length(), is(equalTo(5)));
+
+ // There should be 3 gauges
+ final JSONArray gauges = metrics.getJSONArray("gauges");
+ assertThat(gauges, is(notNullValue()));
+ assertThat(gauges.length(), is(3));
+
+ // There should be no timers, meters, histograms or counters
+ checkEmptyLists(metrics, "timers", "meters", "histograms", "counters");
+
+ // Check the values for gauge 1
+ final JSONObject gauge1Container = gauges.getJSONObject(0);
+ checkGauge(gauge1Container, GAUGE1_NAME, gauge1);
+
+ // Check the values for gauge 2
+ final JSONObject gauge2Container = gauges.getJSONObject(1);
+ checkGauge(gauge2Container, GAUGE2_NAME, gauge2);
+
+ // Check the values for gauge 3
+ final JSONObject gauge3Container = gauges.getJSONObject(2);
+ checkGauge(gauge3Container, GAUGE3_NAME, gauge3);
+ }
+
+}
diff --git a/src/test/java/net/onrc/onos/api/rest/TestRestMetricsHistograms.java b/src/test/java/net/onrc/onos/api/rest/TestRestMetricsHistograms.java
new file mode 100644
index 0000000..ae528db
--- /dev/null
+++ b/src/test/java/net/onrc/onos/api/rest/TestRestMetricsHistograms.java
@@ -0,0 +1,204 @@
+package net.onrc.onos.api.rest;
+
+import com.codahale.metrics.Histogram;
+import net.onrc.onos.core.intent.runtime.PathCalcRuntimeModule;
+import net.onrc.onos.core.metrics.OnosMetrics;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.restlet.resource.ClientResource;
+
+import java.util.Arrays;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.both;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Unit tests for REST APIs for Histogram Metrics.
+ */
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(PathCalcRuntimeModule.class)
+public class TestRestMetricsHistograms extends TestRestMetrics {
+
+ /**
+ * Create the web server and mocks required for
+ * all of the tests.
+ */
+ @Before
+ @SuppressWarnings("ununsed")
+ public void beforeTest() {
+ setRestPort(generateRandomPort());
+ setUp();
+ }
+
+ /**
+ * Remove anything that will interfere with the next test running correctly.
+ * Shuts down the test REST web server and removes the mocks.
+ */
+ @After
+ @SuppressWarnings("unused")
+ public void afterTest() {
+ tearDown();
+ }
+
+ // Test data for Histograms
+
+ private static final String HISTOGRAM1_NAME = "HISTOGRAM1";
+ private static final int[] HISTOGRAM1_VALUES =
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+
+ private static final String HISTOGRAM2_NAME = "HISTOGRAM2";
+ private static final int[] HISTOGRAM2_VALUES =
+ {100, 100, 100, 100, 100, 100, 100};
+
+ private static final String HISTOGRAM3_NAME = "HISTOGRAM3";
+ private static final int[] HISTOGRAM3_VALUES =
+ {555};
+
+ private final Histogram histogram1 =
+ OnosMetrics.getMetricsRegistry().histogram(HISTOGRAM1_NAME);
+ private final Histogram histogram2 =
+ OnosMetrics.getMetricsRegistry().histogram(HISTOGRAM2_NAME);
+ private final Histogram histogram3 =
+ OnosMetrics.getMetricsRegistry().histogram(HISTOGRAM3_NAME);
+
+ /**
+ * Add each int in an array to a Histogram.
+ *
+ * @param histogram Histogram object to update
+ * @param values list of values to add to the Histogram
+ */
+ private void updateHistogramFromArray(final Histogram histogram,
+ final int[] values) {
+ for (final int value : values) {
+ histogram.update(value);
+ }
+ }
+
+ /**
+ * Initialize all the Histograms.
+ */
+ private void fillHistograms() {
+ updateHistogramFromArray(histogram1, HISTOGRAM1_VALUES);
+ updateHistogramFromArray(histogram2, HISTOGRAM2_VALUES);
+ updateHistogramFromArray(histogram3, HISTOGRAM3_VALUES);
+ }
+
+ /**
+ * Check that a JSON object representing a histogram contains the correct
+ * data.
+ *
+ * @param histogramContainer JSON object for the Histogram
+ * @param name the name of the Histogram
+ * @param values the array of expected values in the histogram
+ * @throws JSONException if any of the JSON processing generates an error
+ */
+ private void checkHistogram(final JSONObject histogramContainer,
+ final String name,
+ final int[] values) throws JSONException {
+
+ // Check that the name is correct
+ final String histogramName = histogramContainer.getString("name");
+ assertThat(histogramName, is(notNullValue()));
+ assertThat(histogramName, is(equalTo(name)));
+
+ // Make sure a histogram is present
+ final JSONObject histogramObject = histogramContainer.getJSONObject("histogram");
+ assertThat(histogramObject, is(notNullValue()));
+
+ // The histogram count should equal the length of the array used to
+ // initialize it.
+ final int histogramCount = histogramObject.getInt("count");
+ assertThat(histogramCount, is(equalTo(values.length)));
+
+ final int[] sortedValues = Arrays.copyOf(values, values.length);
+ Arrays.sort(sortedValues);
+
+ // max should be the largest value from the array
+ final int max = histogramObject.getInt("max");
+ assertThat(max, is(equalTo(sortedValues[sortedValues.length - 1])));
+
+ // min should be the smallest value from the array
+ final int min = histogramObject.getInt("min");
+ assertThat(min, is(equalTo(sortedValues[0])));
+
+ // Each of the probability values should be between the min and the max
+ // value
+ final double p999 = histogramObject.getDouble("p999");
+ assertThat((int) p999,
+ is(both(greaterThanOrEqualTo(min)).and(lessThanOrEqualTo(max))));
+
+ final double p99 = histogramObject.getDouble("p99");
+ assertThat((int) p99,
+ is(both(greaterThanOrEqualTo(min)).and(lessThanOrEqualTo(max))));
+
+ final double p98 = histogramObject.getDouble("p98");
+ assertThat((int) p98,
+ is(both(greaterThanOrEqualTo(min)).and(lessThanOrEqualTo(max))));
+
+ final double p95 = histogramObject.getDouble("p95");
+ assertThat((int) p95,
+ is(both(greaterThanOrEqualTo(min)).and(lessThanOrEqualTo(max))));
+
+ final double p75 = histogramObject.getDouble("p75");
+ assertThat((int) p75,
+ is(both(greaterThanOrEqualTo(min)).and(lessThanOrEqualTo(max))));
+
+ final double p50 = histogramObject.getDouble("p50");
+ assertThat((int) p50,
+ is(both(greaterThanOrEqualTo(min)).and(lessThanOrEqualTo(max))));
+
+ }
+
+ /**
+ * Unit test for REST APIs for Histogram Metrics.
+ *
+ * @throws JSONException if any JSON processing causes an error
+ */
+ @Test
+ public void testHistograms() throws JSONException {
+
+ fillHistograms();
+
+ // Read the metrics from the REST API for the test data
+ final ClientResource client = new ClientResource(getBaseRestMetricsUrl());
+
+ final JSONObject metrics = getJSONObject(client);
+ assertThat(metrics.length(), is(equalTo(5)));
+
+ // There should be 3 histograms
+ final JSONArray histograms = metrics.getJSONArray("histograms");
+ assertThat(histograms, is(notNullValue()));
+ assertThat(histograms.length(), is(3));
+
+ // There should be no timers, gauges, meters or counters
+ checkEmptyLists(metrics, "timers", "gauges", "meters", "counters");
+
+ // Check the values for histogram 1
+ final JSONObject histogram1Container = histograms.getJSONObject(0);
+ checkHistogram(histogram1Container, HISTOGRAM1_NAME, HISTOGRAM1_VALUES);
+
+ // Check the values for histogram 2
+ final JSONObject histogram2Container = histograms.getJSONObject(1);
+ checkHistogram(histogram2Container, HISTOGRAM2_NAME, HISTOGRAM2_VALUES);
+
+ // Check the values for histogram 3
+ final JSONObject histogram3Container = histograms.getJSONObject(2);
+ checkHistogram(histogram3Container, HISTOGRAM3_NAME, HISTOGRAM3_VALUES);
+
+ }
+
+}
+
diff --git a/src/test/java/net/onrc/onos/api/rest/TestRestMetricsMeters.java b/src/test/java/net/onrc/onos/api/rest/TestRestMetricsMeters.java
new file mode 100644
index 0000000..c1b21b1
--- /dev/null
+++ b/src/test/java/net/onrc/onos/api/rest/TestRestMetricsMeters.java
@@ -0,0 +1,191 @@
+package net.onrc.onos.api.rest;
+
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricSet;
+import net.onrc.onos.core.intent.runtime.PathCalcRuntimeModule;
+import net.onrc.onos.core.metrics.OnosMetrics;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.restlet.resource.ClientResource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.both;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Unit tests for REST APIs for Meter Metrics.
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(PathCalcRuntimeModule.class)
+public class TestRestMetricsMeters extends TestRestMetrics {
+
+ /**
+ * Create the web server and mocks required for
+ * all of the tests.
+ */
+ @Before
+ @SuppressWarnings("ununsed")
+ public void beforeTest() {
+ setRestPort(generateRandomPort());
+ setUp();
+ }
+
+ /**
+ * Remove anything that will interfere with the next test running correctly.
+ * Shuts down the test REST web server and removes the mocks.
+ */
+ @After
+ @SuppressWarnings("unused")
+ public void afterTest() {
+ tearDown();
+ }
+
+ // Test data for Meters
+
+ private static final String METER1_NAME = "METER1";
+ private static final int METER1_ITERATIONS = 1;
+
+ private static final String METER2_NAME = "METER2";
+ private static final int METER2_ITERATIONS = 10;
+
+ private static final String METER3_NAME = "METER3";
+ private static final int METER3_ITERATIONS = 100;
+
+ private final Meter meter1 = new Meter(mockClock);
+ private final Meter meter2 = new Meter(mockClock);
+ private final Meter meter3 = new Meter(mockClock);
+
+ /**
+ * Fill in test data for a given Meter.
+ *
+ * @param meter Meter object to fill in
+ * @param iterations How many times to mark the meter
+ */
+ private void fillMeter(final Meter meter,
+ final long iterations) {
+ for (int i = 0; i < iterations; i++) {
+ meter.mark();
+ }
+ }
+
+ /**
+ * Fill in test data in the test Meters.
+ */
+ private void fillMeters() {
+ fillMeter(meter1, METER1_ITERATIONS);
+ fillMeter(meter2, METER2_ITERATIONS);
+ fillMeter(meter3, METER3_ITERATIONS);
+
+ final MetricSet meterSet = new MetricSet() {
+ @Override
+ public Map<String, Metric> getMetrics() {
+ final Map<String, Metric> meters = new HashMap<>();
+ meters.put(METER1_NAME, meter1);
+ meters.put(METER2_NAME, meter2);
+ meters.put(METER3_NAME, meter3);
+ return meters;
+ }
+ };
+ OnosMetrics.getMetricsRegistry().registerAll(meterSet);
+ }
+
+ /**
+ * Check if the data in a Meter matches the expected values.
+ *
+ * @param meter the meter object that this JSON representation should match
+ * @param meterContainer JSON object for the Meter
+ * @param name name of the Meter
+ * @param iterations count of marks that should be in the Meter
+ * @throws JSONException if any of the JSON processing fails
+ */
+ private void checkMeter(final Meter meter,
+ final JSONObject meterContainer,
+ final String name,
+ final int iterations)
+ throws JSONException {
+ final String meterName = meterContainer.getString("name");
+ assertThat(meterName, is(notNullValue()));
+ assertThat(meterName, is(equalTo(name)));
+
+ final JSONObject meterObject = meterContainer.getJSONObject("meter");
+ assertThat(meterObject, is(notNullValue()));
+
+ final int meterCount = meterObject.getInt("count");
+ assertThat(meterCount, is(equalTo(iterations)));
+
+ final double m15Rate = meterObject.getDouble("m15_rate");
+ assertThat(m15Rate, is(equalTo(meter.getFifteenMinuteRate())));
+
+ final double m5Rate = meterObject.getDouble("m5_rate");
+ assertThat(m5Rate, is(equalTo(meter.getFiveMinuteRate())));
+
+ final double m1Rate = meterObject.getDouble("m1_rate");
+ assertThat(m1Rate, is(equalTo(meter.getOneMinuteRate())));
+
+ // mean should be between 0.0 and the longest rate. Since all the rates
+ // are the same because of the mocked clock, use the 15 minute rate as
+ // the max - all the rates should be the same.
+ final double meanRate = meterObject.getDouble("mean_rate");
+ assertThat(meanRate,
+ is(both(greaterThanOrEqualTo(0.0)).
+ and(lessThanOrEqualTo(m15Rate))));
+
+ final String units = meterObject.getString("units");
+ assertThat(units, is(equalTo("events/second")));
+ }
+
+ /**
+ * UNIT test for the Metrics REST API for Meters.
+ *
+ * @throws JSONException if any JSON processing fails
+ */
+ @Test
+ public void testMeters() throws JSONException {
+
+ fillMeters();
+
+ // Read the metrics from the REST API for the test data
+ final ClientResource client = new ClientResource(getBaseRestMetricsUrl());
+
+ final JSONObject metrics = getJSONObject(client);
+ assertThat(metrics.length(), is(equalTo(5)));
+
+ // There should be 3 meters
+ final JSONArray meters = metrics.getJSONArray("meters");
+ assertThat(meters, is(notNullValue()));
+ assertThat(meters.length(), is(3));
+
+ // There should be no timers, gauges, histograms or counters
+ checkEmptyLists(metrics, "timers", "gauges", "histograms", "counters");
+
+ // Check the values for meter 1
+ final JSONObject meter1Container = meters.getJSONObject(0);
+ checkMeter(meter1, meter1Container, METER1_NAME, METER1_ITERATIONS);
+
+ // Check the values for meter 2
+ final JSONObject meter2Container = meters.getJSONObject(1);
+ checkMeter(meter2, meter2Container, METER2_NAME, METER2_ITERATIONS);
+
+ // Check the values for meter 3
+ final JSONObject meter3Container = meters.getJSONObject(2);
+ checkMeter(meter3, meter3Container, METER3_NAME, METER3_ITERATIONS);
+
+ }
+
+}
+
diff --git a/src/test/java/net/onrc/onos/api/rest/TestRestMetricsTimers.java b/src/test/java/net/onrc/onos/api/rest/TestRestMetricsTimers.java
new file mode 100644
index 0000000..e6670b4
--- /dev/null
+++ b/src/test/java/net/onrc/onos/api/rest/TestRestMetricsTimers.java
@@ -0,0 +1,212 @@
+package net.onrc.onos.api.rest;
+
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricSet;
+import com.codahale.metrics.Reservoir;
+import com.codahale.metrics.SlidingWindowReservoir;
+import com.codahale.metrics.Timer;
+import net.onrc.onos.core.intent.runtime.PathCalcRuntimeModule;
+import net.onrc.onos.core.metrics.OnosMetrics;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.restlet.resource.ClientResource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.both;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Unit tests for REST APIs for Timer Metrics.
+ */
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(PathCalcRuntimeModule.class)
+public class TestRestMetricsTimers extends TestRestMetrics {
+
+ /**
+ * Create the web server and mocks required for
+ * all of the tests.
+ */
+ @Before
+ @SuppressWarnings("ununsed")
+ public void beforeTest() {
+ setRestPort(generateRandomPort());
+ setUp();
+ }
+
+ // Test data objects for Timers
+
+ // timer1 will be called 3 times
+ private static final String TIMER1_NAME = name(TestRestMetricsTimers.class,
+ "timer1");
+ private static final int TIMER1_COUNT = 3;
+
+ // timer1 will be called 10 times
+ private static final String TIMER2_NAME = name(TestRestMetricsTimers.class,
+ "timer2");
+ private static final int TIMER2_COUNT = 10;
+
+ private static final int RESERVOIR_SIZE = 100;
+ private Reservoir reservoir = new SlidingWindowReservoir(RESERVOIR_SIZE);
+
+ private final Timer timer1 = new Timer(reservoir, mockClock);
+ private final Timer timer2 = new Timer(reservoir, mockClock);
+
+ /**
+ * Fill in test data in the Timer objects.
+ */
+ private void fillTimers() {
+ // The mock clock will simulate ticks at MOCK_CLOCK_MILISECONDS_PER_TICK
+ // Each timer.time(), context.stop() pair will elapse
+ // MOCK_CLOCK_MILISECONDS_PER_TICK (currently 50 milliseconds)
+
+ // Initialize timer1 - 3 ticks, 50 milliseconds (simulted) apart
+ for (int i = 0; i < TIMER1_COUNT; i++) {
+ final Timer.Context context = timer1.time();
+ context.stop();
+ }
+
+ // Initialize timer2 - 10 ticks, 50 milliseconds (simulated) apart
+ for (int i = 0; i < TIMER2_COUNT; i++) {
+ final Timer.Context context = timer2.time();
+ context.stop();
+ }
+
+ // add the two timers to the registry so the REST APIs will pick them
+ // up
+ final MetricSet timerSet = new MetricSet() {
+ @Override
+ public Map<String, Metric> getMetrics() {
+ final Map<String, Metric> timers = new HashMap<>();
+ timers.put(TIMER1_NAME, timer1);
+ timers.put(TIMER2_NAME, timer2);
+ return timers;
+ }
+ };
+ OnosMetrics.getMetricsRegistry().registerAll(timerSet);
+ }
+
+ /**
+ * Check that the time values for a JSON Timer object are within the
+ * allowed range.
+ *
+ * @param timer JSON Timer object
+ * @param timeValue expected time value
+ * @throws JSONException if any of the JSON operations fail
+ */
+ private void assertThatTimesAreInRange(JSONObject timer, int timeValue)
+ throws JSONException {
+
+ final double timerMax = timer.getDouble("max");
+ assertThat((int) timerMax,
+ is(both(greaterThanOrEqualTo(timeValue)).
+ and(lessThan(timeValue + 5))));
+
+ final double timerMin = timer.getDouble("min");
+ assertThat((int) timerMin,
+ is(both(greaterThanOrEqualTo(timeValue)).
+ and(lessThan(timeValue + 5))));
+
+ final double timerP99 = timer.getDouble("p99");
+ assertThat((int) timerP99,
+ is(both(greaterThanOrEqualTo(timeValue)).
+ and(lessThan(timeValue + 5))));
+
+ final double timerP999 = timer.getDouble("p999");
+ assertThat((int) timerP999,
+ is(both(greaterThanOrEqualTo(timeValue)).
+ and(lessThan(timeValue + 5))));
+ }
+
+ /**
+ * Remove anything that will interfere with the next test running correctly.
+ * Shuts down the test REST web server and removes the mocks.
+ */
+ @After
+ @SuppressWarnings("unused")
+ public void afterTest() {
+ tearDown();
+ }
+
+ /**
+ * Unit test for the REST APIs for Metrics Timers.
+ *
+ * @throws JSONException if any of the JSON processing fails.
+ */
+ @Test
+ public void testTimers() throws JSONException {
+ // Make some test data
+ fillTimers();
+
+ // Read the metrics from the REST API for the test data
+ final ClientResource client = new ClientResource(getBaseRestMetricsUrl());
+
+ final JSONObject metrics = getJSONObject(client);
+ assertThat(metrics.length(), is(equalTo(5)));
+
+ // There should be 2 timers
+ final JSONArray timers = metrics.getJSONArray("timers");
+ assertThat(timers, is(notNullValue()));
+ assertThat(timers.length(), is(2));
+
+
+ // There should be no historgramss, gauges, meters or counters
+ checkEmptyLists(metrics, "histograms", "gauges", "meters", "counters");
+
+ // Check the values for timer 1
+ final JSONObject timer1Values = timers.getJSONObject(0);
+ assertThat(timer1Values, is(notNullValue()));
+
+ final String timer1Name = timer1Values.getString("name");
+ assertThat(timer1Name, is(notNullValue()));
+ assertThat(timer1Name, is(equalTo(TIMER1_NAME)));
+
+ final JSONObject timer1TimerObject = timer1Values.getJSONObject("timer");
+ assertThat(timer1TimerObject, is(notNullValue()));
+
+ final int timer1Count = timer1TimerObject.getInt("count");
+ assertThat(timer1Count, is(equalTo(TIMER1_COUNT)));
+
+ final String timer1DurationUnits = timer1TimerObject.getString("duration_units");
+ assertThat(timer1DurationUnits, is(equalTo("milliseconds")));
+
+ assertThatTimesAreInRange(timer1TimerObject,
+ MOCK_CLOCK_MILISECONDS_PER_TICK);
+
+ // Check the values for timer 2
+ final JSONObject timer2Values = timers.getJSONObject(1);
+ assertThat(timer2Values, is(notNullValue()));
+
+ final String timer2Name = timer2Values.getString("name");
+ assertThat(timer2Name, is(notNullValue()));
+ assertThat(timer2Name, is(equalTo(TIMER2_NAME)));
+
+ final JSONObject timer2TimerObject = timer2Values.getJSONObject("timer");
+ assertThat(timer2TimerObject, is(notNullValue()));
+
+ final int timer2Count = timer2TimerObject.getInt("count");
+ assertThat(timer2Count, is(equalTo(TIMER2_COUNT)));
+
+ final String timer2DurationUnits = timer2TimerObject.getString("duration_units");
+ assertThat(timer2DurationUnits, is(equalTo("milliseconds")));
+
+ assertThatTimesAreInRange(timer2TimerObject,
+ MOCK_CLOCK_MILISECONDS_PER_TICK);
+ }
+
+}