Added initial implementation of Topology-related event and
event metrics collector. It can be loaded by one of the following two (new)
features: onos-app-metrics, onos-app-metrics-topology
After loading the module, it subscribes for topology-related events
and keeps the following state:
(a) The last 10 events
(b) The timestamp of the last event (ms after epoch) as observed by this
module
(c) The rate of the topology events: count, median rate, average rate
over the last 1, 5 or 15 minutes
The following CLI commands are added:
* onos:topology-events
Shows the last 10 topology events
* onos:topology-events-metrics
Shows the timestamp of the last event, and the rate of the topology
events: see (b) and (c) above
diff --git a/apps/metrics/pom.xml b/apps/metrics/pom.xml
new file mode 100644
index 0000000..0ce3913
--- /dev/null
+++ b/apps/metrics/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onos-apps</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-app-metrics</artifactId>
+ <packaging>pom</packaging>
+
+ <description>ONOS metrics applications</description>
+
+ <modules>
+ <module>topology</module>
+ </modules>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onlab-misc</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+
+ </dependencies>
+
+</project>
diff --git a/apps/metrics/topology/pom.xml b/apps/metrics/topology/pom.xml
new file mode 100644
index 0000000..dc1c000
--- /dev/null
+++ b/apps/metrics/topology/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onos-app-metrics</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-app-metrics-topology</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>ONOS topology metrics application</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java
new file mode 100644
index 0000000..e2a4532
--- /dev/null
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java
@@ -0,0 +1,166 @@
+package org.onlab.onos.metrics.topology;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+import com.google.common.collect.ImmutableList;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.metrics.MetricsComponent;
+import org.onlab.metrics.MetricsFeature;
+import org.onlab.metrics.MetricsService;
+import org.onlab.onos.event.Event;
+import org.onlab.onos.net.topology.TopologyEvent;
+import org.onlab.onos.net.topology.TopologyListener;
+import org.onlab.onos.net.topology.TopologyService;
+import org.slf4j.Logger;
+
+/**
+ * ONOS Topology Metrics Application that collects topology-related metrics.
+ */
+@Component(immediate = true)
+@Service
+public class TopologyMetrics implements TopologyMetricsService,
+ TopologyListener {
+ private static final Logger log = getLogger(TopologyMetrics.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected TopologyService topologyService;
+ private LinkedList<TopologyEvent> lastEvents = new LinkedList<>();
+ private static final int LAST_EVENTS_MAX_N = 10;
+
+ //
+ // Metrics
+ //
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MetricsService metricsService;
+ //
+ private static final String COMPONENT_NAME = "Topology";
+ private static final String FEATURE_NAME = "EventNotification";
+ private static final String GAUGE_NAME = "LastEventTimestamp.EpochMs";
+ private static final String METER_NAME = "EventRate";
+ //
+ private MetricsComponent metricsComponent;
+ private MetricsFeature metricsFeatureEventNotification;
+ //
+ // Timestamp of the last Topology event (ms from the Epoch)
+ private volatile long lastEventTimestampEpochMs = 0;
+ private Gauge<Long> lastEventTimestampEpochMsGauge;
+ // Rate of the Topology events published to the Topology listeners
+ private Meter eventRateMeter;
+
+ @Activate
+ protected void activate() {
+ clear();
+ registerMetrics();
+ topologyService.addListener(this);
+ log.info("ONOS Topology Metrics started.");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ topologyService.removeListener(this);
+ removeMetrics();
+ clear();
+ log.info("ONOS Topology Metrics stopped.");
+ }
+
+ @Override
+ public List<TopologyEvent> getEvents() {
+ synchronized (lastEvents) {
+ return ImmutableList.<TopologyEvent>copyOf(lastEvents);
+ }
+ }
+
+ @Override
+ public Gauge<Long> lastEventTimestampEpochMsGauge() {
+ return lastEventTimestampEpochMsGauge;
+ }
+
+ @Override
+ public Meter eventRateMeter() {
+ return eventRateMeter;
+ }
+
+ @Override
+ public void event(TopologyEvent event) {
+ lastEventTimestampEpochMs = System.currentTimeMillis();
+ //
+ // NOTE: If we want to count each "reason" as a separate event,
+ // then we should use 'event.reason().size()' instead of '1' to
+ // mark the meter below.
+ //
+ eventRateMeter.mark(1);
+
+ log.debug("Topology Event: time = {} type = {} subject = {}",
+ event.time(), event.type(), event.subject());
+ for (Event reason : event.reasons()) {
+ log.debug("Topology Event Reason: time = {} type = {} subject = {}",
+ reason.time(), reason.type(), reason.subject());
+ }
+
+ //
+ // Keep only the last N events, where N = LAST_EVENTS_MAX_N
+ //
+ synchronized (lastEvents) {
+ while (lastEvents.size() >= LAST_EVENTS_MAX_N) {
+ lastEvents.remove();
+ }
+ lastEvents.add(event);
+ }
+ }
+
+ /**
+ * Clears the internal state.
+ */
+ private void clear() {
+ lastEventTimestampEpochMs = 0;
+ synchronized (lastEvents) {
+ lastEvents.clear();
+ }
+ }
+
+ /**
+ * Registers the metrics.
+ */
+ private void registerMetrics() {
+ metricsComponent = metricsService.registerComponent(COMPONENT_NAME);
+ metricsFeatureEventNotification =
+ metricsComponent.registerFeature(FEATURE_NAME);
+ lastEventTimestampEpochMsGauge =
+ metricsService.registerMetric(metricsComponent,
+ metricsFeatureEventNotification,
+ GAUGE_NAME,
+ new Gauge<Long>() {
+ @Override
+ public Long getValue() {
+ return lastEventTimestampEpochMs;
+ }
+ });
+ eventRateMeter =
+ metricsService.createMeter(metricsComponent,
+ metricsFeatureEventNotification,
+ METER_NAME);
+
+ }
+
+ /**
+ * Removes the metrics.
+ */
+ private void removeMetrics() {
+ metricsService.removeMetric(metricsComponent,
+ metricsFeatureEventNotification,
+ GAUGE_NAME);
+ metricsService.removeMetric(metricsComponent,
+ metricsFeatureEventNotification,
+ METER_NAME);
+ }
+}
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java
new file mode 100644
index 0000000..cc370fa
--- /dev/null
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java
@@ -0,0 +1,35 @@
+package org.onlab.onos.metrics.topology;
+
+import java.util.List;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+import org.onlab.onos.net.topology.TopologyEvent;
+
+/**
+ * Service interface exported by TopologyMetrics.
+ */
+public interface TopologyMetricsService {
+ /**
+ * Gets the last saved topology events.
+ *
+ * @return the last saved topology events.
+ */
+ public List<TopologyEvent> getEvents();
+
+ /**
+ * Gets the Metrics' Gauge for the last topology event timestamp
+ * (ms from the epoch).
+ *
+ * @return the Metrics' Gauge for the last topology event timestamp
+ * (ms from the epoch)
+ */
+ public Gauge<Long> lastEventTimestampEpochMsGauge();
+
+ /**
+ * Gets the Metrics' Meter for the topology events rate.
+ *
+ * @return the Metrics' Meter for the topology events rate
+ */
+ public Meter eventRateMeter();
+}
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsListCommand.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsListCommand.java
new file mode 100644
index 0000000..8bab4d0
--- /dev/null
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsListCommand.java
@@ -0,0 +1,97 @@
+package org.onlab.onos.metrics.topology.cli;
+
+import java.util.List;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.event.Event;
+import org.onlab.onos.metrics.topology.TopologyMetricsService;
+import org.onlab.onos.net.topology.TopologyEvent;
+
+/**
+ * Command to show the list of last topology events.
+ */
+@Command(scope = "onos", name = "topology-events",
+ description = "Lists the last topology events")
+public class TopologyEventsListCommand extends AbstractShellCommand {
+
+ private static final String FORMAT_EVENT =
+ "Topology Event time=%d type=%s subject=%s";
+ private static final String FORMAT_REASON =
+ " Reason time=%d type=%s subject=%s";
+
+ @Override
+ protected void execute() {
+ TopologyMetricsService service = get(TopologyMetricsService.class);
+
+ if (outputJson()) {
+ print("%s", json(service.getEvents()));
+ } else {
+ for (TopologyEvent event : service.getEvents()) {
+ print(FORMAT_EVENT, event.time(), event.type(),
+ event.subject());
+ for (Event reason : event.reasons()) {
+ print(FORMAT_REASON, reason.time(), reason.type(),
+ reason.subject());
+ }
+ print(""); // Extra empty line for clarity
+ }
+ }
+ }
+
+ /**
+ * Produces a JSON array of topology events.
+ *
+ * @param topologyEvents the topology events with the data
+ * @return JSON array with the topology events
+ */
+ private JsonNode json(List<TopologyEvent> topologyEvents) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+
+ for (TopologyEvent event : topologyEvents) {
+ result.add(json(mapper, event));
+ }
+ return result;
+ }
+
+ /**
+ * Produces JSON object for a topology event.
+ *
+ * @param mapper the JSON object mapper to use
+ * @param topologyEvent the topology event with the data
+ * @return JSON object for the topology event
+ */
+ private ObjectNode json(ObjectMapper mapper, TopologyEvent topologyEvent) {
+ ObjectNode result = mapper.createObjectNode();
+ ArrayNode reasons = mapper.createArrayNode();
+
+ for (Event reason : topologyEvent.reasons()) {
+ reasons.add(json(mapper, reason));
+ }
+ result.put("time", topologyEvent.time())
+ .put("type", topologyEvent.type().toString())
+ .put("subject", topologyEvent.subject().toString())
+ .put("reasons", reasons);
+ return result;
+ }
+
+ /**
+ * Produces JSON object for a generic event.
+ *
+ * @param event the generic event with the data
+ * @return JSON object for the generic event
+ */
+ private ObjectNode json(ObjectMapper mapper, Event event) {
+ ObjectNode result = mapper.createObjectNode();
+
+ result.put("time", event.time())
+ .put("type", event.type().toString())
+ .put("subject", event.subject().toString());
+ return result;
+ }
+}
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsMetricsCommand.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsMetricsCommand.java
new file mode 100644
index 0000000..1bc0b57
--- /dev/null
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsMetricsCommand.java
@@ -0,0 +1,69 @@
+package org.onlab.onos.metrics.topology.cli;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.json.MetricsModule;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.metrics.topology.TopologyMetricsService;
+
+/**
+ * Command to show the topology events metrics.
+ */
+@Command(scope = "onos", name = "topology-events-metrics",
+ description = "Lists topology events metrics")
+public class TopologyEventsMetricsCommand extends AbstractShellCommand {
+
+ private static final String FORMAT_GAUGE =
+ "Last Topology Event Timestamp (ms from epoch)=%d";
+ private static final String FORMAT_METER =
+ "Topology Events count=%d rate(events/sec) mean=%f m1=%f m5=%f m15=%f";
+
+ @Override
+ protected void execute() {
+ TopologyMetricsService service = get(TopologyMetricsService.class);
+ Gauge<Long> gauge = service.lastEventTimestampEpochMsGauge();
+ Meter meter = service.eventRateMeter();
+
+ if (outputJson()) {
+ ObjectMapper mapper = new ObjectMapper()
+ .registerModule(new MetricsModule(TimeUnit.SECONDS,
+ TimeUnit.MILLISECONDS,
+ false));
+ ObjectNode result = mapper.createObjectNode();
+ try {
+ //
+ // NOTE: The API for custom serializers is incomplete,
+ // hence we have to parse the JSON string to create JsonNode.
+ //
+ final String gaugeJson = mapper.writeValueAsString(gauge);
+ final String meterJson = mapper.writeValueAsString(meter);
+ JsonNode gaugeNode = mapper.readTree(gaugeJson);
+ JsonNode meterNode = mapper.readTree(meterJson);
+ result.put("lastTopologyEventTimestamp", gaugeNode);
+ result.put("listenerEventRate", meterNode);
+ } catch (JsonProcessingException e) {
+ log.error("Error writing value as JSON string", e);
+ } catch (IOException e) {
+ log.error("Error writing value as JSON string", e);
+ }
+ print("%s", result);
+ } else {
+ TimeUnit rateUnit = TimeUnit.SECONDS;
+ double rateFactor = rateUnit.toSeconds(1);
+ print(FORMAT_GAUGE, gauge.getValue());
+ print(FORMAT_METER, meter.getCount(),
+ meter.getMeanRate() * rateFactor,
+ meter.getOneMinuteRate() * rateFactor,
+ meter.getFiveMinuteRate() * rateFactor,
+ meter.getFifteenMinuteRate() * rateFactor);
+ }
+ }
+}
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/package-info.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/package-info.java
new file mode 100644
index 0000000..42826b7
--- /dev/null
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * ONOS Topology Metrics Application that collects topology-related metrics.
+ */
+package org.onlab.onos.metrics.topology;
diff --git a/apps/metrics/topology/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/metrics/topology/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..c1471da
--- /dev/null
+++ b/apps/metrics/topology/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,12 @@
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+ <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+ <command>
+ <action class="org.onlab.onos.metrics.topology.cli.TopologyEventsListCommand"/>
+ </command>
+ <command>
+ <action class="org.onlab.onos.metrics.topology.cli.TopologyEventsMetricsCommand"/>
+ </command>
+ </command-bundle>
+
+</blueprint>
diff --git a/apps/pom.xml b/apps/pom.xml
index e812c47..b2a3494 100644
--- a/apps/pom.xml
+++ b/apps/pom.xml
@@ -27,6 +27,7 @@
<module>sdnip</module>
<module>calendar</module>
<module>optical</module>
+ <module>metrics</module>
</modules>
<properties>
diff --git a/features/features.xml b/features/features.xml
index cc7aa99..e6c4beb 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -37,6 +37,7 @@
<bundle>mvn:com.hazelcast/hazelcast/3.3</bundle>
<bundle>mvn:io.dropwizard.metrics/metrics-core/3.1.0</bundle>
+ <bundle>mvn:io.dropwizard.metrics/metrics-json/3.1.0</bundle>
<bundle>mvn:com.eclipsesource.minimal-json/minimal-json/0.9.1</bundle>
<bundle>mvn:com.esotericsoftware/kryo/3.0.0</bundle>
@@ -183,7 +184,6 @@
<bundle>mvn:org.onlab.onos/onos-app-optical/1.0.0-SNAPSHOT</bundle>
</feature>
-
<feature name="onos-app-sdnip" version="1.0.0"
description="SDN-IP peering application">
<feature>onos-api</feature>
@@ -197,4 +197,15 @@
<bundle>mvn:org.onlab.onos/onos-app-calendar/1.0.0-SNAPSHOT</bundle>
</feature>
+ <feature name="onos-app-metrics" version="1.0.0"
+ description="ONOS metrics applications">
+ <feature>onos-app-metrics-topology</feature>
+ </feature>
+
+ <feature name="onos-app-metrics-topology" version="1.0.0"
+ description="ONOS topology metrics application">
+ <feature>onos-api</feature>
+ <bundle>mvn:org.onlab.onos/onos-app-metrics-topology/1.0.0-SNAPSHOT</bundle>
+ </feature>
+
</features>
diff --git a/utils/misc/pom.xml b/utils/misc/pom.xml
index 5040fb0..d1d3a50 100644
--- a/utils/misc/pom.xml
+++ b/utils/misc/pom.xml
@@ -53,6 +53,11 @@
<version>3.1.0</version>
</dependency>
<dependency>
+ <groupId>io.dropwizard.metrics</groupId>
+ <artifactId>metrics-json</artifactId>
+ <version>3.1.0</version>
+ </dependency>
+ <dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>