Pavlin Radoslavov

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
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
5 + <modelVersion>4.0.0</modelVersion>
6 +
7 + <parent>
8 + <groupId>org.onlab.onos</groupId>
9 + <artifactId>onos-apps</artifactId>
10 + <version>1.0.0-SNAPSHOT</version>
11 + <relativePath>../pom.xml</relativePath>
12 + </parent>
13 +
14 + <artifactId>onos-app-metrics</artifactId>
15 + <packaging>pom</packaging>
16 +
17 + <description>ONOS metrics applications</description>
18 +
19 + <modules>
20 + <module>topology</module>
21 + </modules>
22 +
23 + <dependencies>
24 + <dependency>
25 + <groupId>org.onlab.onos</groupId>
26 + <artifactId>onlab-misc</artifactId>
27 + </dependency>
28 +
29 + <dependency>
30 + <groupId>com.fasterxml.jackson.core</groupId>
31 + <artifactId>jackson-databind</artifactId>
32 + </dependency>
33 + <dependency>
34 + <groupId>com.fasterxml.jackson.core</groupId>
35 + <artifactId>jackson-annotations</artifactId>
36 + </dependency>
37 +
38 + </dependencies>
39 +
40 +</project>
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
5 + <modelVersion>4.0.0</modelVersion>
6 +
7 + <parent>
8 + <groupId>org.onlab.onos</groupId>
9 + <artifactId>onos-app-metrics</artifactId>
10 + <version>1.0.0-SNAPSHOT</version>
11 + <relativePath>../pom.xml</relativePath>
12 + </parent>
13 +
14 + <artifactId>onos-app-metrics-topology</artifactId>
15 + <packaging>bundle</packaging>
16 +
17 + <description>ONOS topology metrics application</description>
18 +
19 + <dependencies>
20 + <dependency>
21 + <groupId>org.onlab.onos</groupId>
22 + <artifactId>onos-cli</artifactId>
23 + <version>${project.version}</version>
24 + </dependency>
25 +
26 + <dependency>
27 + <groupId>org.apache.karaf.shell</groupId>
28 + <artifactId>org.apache.karaf.shell.console</artifactId>
29 + </dependency>
30 + </dependencies>
31 +
32 +</project>
1 +package org.onlab.onos.metrics.topology;
2 +
3 +import static org.slf4j.LoggerFactory.getLogger;
4 +
5 +import java.util.LinkedList;
6 +import java.util.List;
7 +
8 +import com.codahale.metrics.Gauge;
9 +import com.codahale.metrics.Meter;
10 +import com.google.common.collect.ImmutableList;
11 +import org.apache.felix.scr.annotations.Activate;
12 +import org.apache.felix.scr.annotations.Component;
13 +import org.apache.felix.scr.annotations.Deactivate;
14 +import org.apache.felix.scr.annotations.Reference;
15 +import org.apache.felix.scr.annotations.ReferenceCardinality;
16 +import org.apache.felix.scr.annotations.Service;
17 +import org.onlab.metrics.MetricsComponent;
18 +import org.onlab.metrics.MetricsFeature;
19 +import org.onlab.metrics.MetricsService;
20 +import org.onlab.onos.event.Event;
21 +import org.onlab.onos.net.topology.TopologyEvent;
22 +import org.onlab.onos.net.topology.TopologyListener;
23 +import org.onlab.onos.net.topology.TopologyService;
24 +import org.slf4j.Logger;
25 +
26 +/**
27 + * ONOS Topology Metrics Application that collects topology-related metrics.
28 + */
29 +@Component(immediate = true)
30 +@Service
31 +public class TopologyMetrics implements TopologyMetricsService,
32 + TopologyListener {
33 + private static final Logger log = getLogger(TopologyMetrics.class);
34 +
35 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
36 + protected TopologyService topologyService;
37 + private LinkedList<TopologyEvent> lastEvents = new LinkedList<>();
38 + private static final int LAST_EVENTS_MAX_N = 10;
39 +
40 + //
41 + // Metrics
42 + //
43 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
44 + protected MetricsService metricsService;
45 + //
46 + private static final String COMPONENT_NAME = "Topology";
47 + private static final String FEATURE_NAME = "EventNotification";
48 + private static final String GAUGE_NAME = "LastEventTimestamp.EpochMs";
49 + private static final String METER_NAME = "EventRate";
50 + //
51 + private MetricsComponent metricsComponent;
52 + private MetricsFeature metricsFeatureEventNotification;
53 + //
54 + // Timestamp of the last Topology event (ms from the Epoch)
55 + private volatile long lastEventTimestampEpochMs = 0;
56 + private Gauge<Long> lastEventTimestampEpochMsGauge;
57 + // Rate of the Topology events published to the Topology listeners
58 + private Meter eventRateMeter;
59 +
60 + @Activate
61 + protected void activate() {
62 + clear();
63 + registerMetrics();
64 + topologyService.addListener(this);
65 + log.info("ONOS Topology Metrics started.");
66 + }
67 +
68 + @Deactivate
69 + public void deactivate() {
70 + topologyService.removeListener(this);
71 + removeMetrics();
72 + clear();
73 + log.info("ONOS Topology Metrics stopped.");
74 + }
75 +
76 + @Override
77 + public List<TopologyEvent> getEvents() {
78 + synchronized (lastEvents) {
79 + return ImmutableList.<TopologyEvent>copyOf(lastEvents);
80 + }
81 + }
82 +
83 + @Override
84 + public Gauge<Long> lastEventTimestampEpochMsGauge() {
85 + return lastEventTimestampEpochMsGauge;
86 + }
87 +
88 + @Override
89 + public Meter eventRateMeter() {
90 + return eventRateMeter;
91 + }
92 +
93 + @Override
94 + public void event(TopologyEvent event) {
95 + lastEventTimestampEpochMs = System.currentTimeMillis();
96 + //
97 + // NOTE: If we want to count each "reason" as a separate event,
98 + // then we should use 'event.reason().size()' instead of '1' to
99 + // mark the meter below.
100 + //
101 + eventRateMeter.mark(1);
102 +
103 + log.debug("Topology Event: time = {} type = {} subject = {}",
104 + event.time(), event.type(), event.subject());
105 + for (Event reason : event.reasons()) {
106 + log.debug("Topology Event Reason: time = {} type = {} subject = {}",
107 + reason.time(), reason.type(), reason.subject());
108 + }
109 +
110 + //
111 + // Keep only the last N events, where N = LAST_EVENTS_MAX_N
112 + //
113 + synchronized (lastEvents) {
114 + while (lastEvents.size() >= LAST_EVENTS_MAX_N) {
115 + lastEvents.remove();
116 + }
117 + lastEvents.add(event);
118 + }
119 + }
120 +
121 + /**
122 + * Clears the internal state.
123 + */
124 + private void clear() {
125 + lastEventTimestampEpochMs = 0;
126 + synchronized (lastEvents) {
127 + lastEvents.clear();
128 + }
129 + }
130 +
131 + /**
132 + * Registers the metrics.
133 + */
134 + private void registerMetrics() {
135 + metricsComponent = metricsService.registerComponent(COMPONENT_NAME);
136 + metricsFeatureEventNotification =
137 + metricsComponent.registerFeature(FEATURE_NAME);
138 + lastEventTimestampEpochMsGauge =
139 + metricsService.registerMetric(metricsComponent,
140 + metricsFeatureEventNotification,
141 + GAUGE_NAME,
142 + new Gauge<Long>() {
143 + @Override
144 + public Long getValue() {
145 + return lastEventTimestampEpochMs;
146 + }
147 + });
148 + eventRateMeter =
149 + metricsService.createMeter(metricsComponent,
150 + metricsFeatureEventNotification,
151 + METER_NAME);
152 +
153 + }
154 +
155 + /**
156 + * Removes the metrics.
157 + */
158 + private void removeMetrics() {
159 + metricsService.removeMetric(metricsComponent,
160 + metricsFeatureEventNotification,
161 + GAUGE_NAME);
162 + metricsService.removeMetric(metricsComponent,
163 + metricsFeatureEventNotification,
164 + METER_NAME);
165 + }
166 +}
1 +package org.onlab.onos.metrics.topology;
2 +
3 +import java.util.List;
4 +
5 +import com.codahale.metrics.Gauge;
6 +import com.codahale.metrics.Meter;
7 +import org.onlab.onos.net.topology.TopologyEvent;
8 +
9 +/**
10 + * Service interface exported by TopologyMetrics.
11 + */
12 +public interface TopologyMetricsService {
13 + /**
14 + * Gets the last saved topology events.
15 + *
16 + * @return the last saved topology events.
17 + */
18 + public List<TopologyEvent> getEvents();
19 +
20 + /**
21 + * Gets the Metrics' Gauge for the last topology event timestamp
22 + * (ms from the epoch).
23 + *
24 + * @return the Metrics' Gauge for the last topology event timestamp
25 + * (ms from the epoch)
26 + */
27 + public Gauge<Long> lastEventTimestampEpochMsGauge();
28 +
29 + /**
30 + * Gets the Metrics' Meter for the topology events rate.
31 + *
32 + * @return the Metrics' Meter for the topology events rate
33 + */
34 + public Meter eventRateMeter();
35 +}
1 +package org.onlab.onos.metrics.topology.cli;
2 +
3 +import java.util.List;
4 +
5 +import com.fasterxml.jackson.databind.JsonNode;
6 +import com.fasterxml.jackson.databind.ObjectMapper;
7 +import com.fasterxml.jackson.databind.node.ArrayNode;
8 +import com.fasterxml.jackson.databind.node.ObjectNode;
9 +import org.apache.karaf.shell.commands.Command;
10 +import org.onlab.onos.cli.AbstractShellCommand;
11 +import org.onlab.onos.event.Event;
12 +import org.onlab.onos.metrics.topology.TopologyMetricsService;
13 +import org.onlab.onos.net.topology.TopologyEvent;
14 +
15 +/**
16 + * Command to show the list of last topology events.
17 + */
18 +@Command(scope = "onos", name = "topology-events",
19 + description = "Lists the last topology events")
20 +public class TopologyEventsListCommand extends AbstractShellCommand {
21 +
22 + private static final String FORMAT_EVENT =
23 + "Topology Event time=%d type=%s subject=%s";
24 + private static final String FORMAT_REASON =
25 + " Reason time=%d type=%s subject=%s";
26 +
27 + @Override
28 + protected void execute() {
29 + TopologyMetricsService service = get(TopologyMetricsService.class);
30 +
31 + if (outputJson()) {
32 + print("%s", json(service.getEvents()));
33 + } else {
34 + for (TopologyEvent event : service.getEvents()) {
35 + print(FORMAT_EVENT, event.time(), event.type(),
36 + event.subject());
37 + for (Event reason : event.reasons()) {
38 + print(FORMAT_REASON, reason.time(), reason.type(),
39 + reason.subject());
40 + }
41 + print(""); // Extra empty line for clarity
42 + }
43 + }
44 + }
45 +
46 + /**
47 + * Produces a JSON array of topology events.
48 + *
49 + * @param topologyEvents the topology events with the data
50 + * @return JSON array with the topology events
51 + */
52 + private JsonNode json(List<TopologyEvent> topologyEvents) {
53 + ObjectMapper mapper = new ObjectMapper();
54 + ArrayNode result = mapper.createArrayNode();
55 +
56 + for (TopologyEvent event : topologyEvents) {
57 + result.add(json(mapper, event));
58 + }
59 + return result;
60 + }
61 +
62 + /**
63 + * Produces JSON object for a topology event.
64 + *
65 + * @param mapper the JSON object mapper to use
66 + * @param topologyEvent the topology event with the data
67 + * @return JSON object for the topology event
68 + */
69 + private ObjectNode json(ObjectMapper mapper, TopologyEvent topologyEvent) {
70 + ObjectNode result = mapper.createObjectNode();
71 + ArrayNode reasons = mapper.createArrayNode();
72 +
73 + for (Event reason : topologyEvent.reasons()) {
74 + reasons.add(json(mapper, reason));
75 + }
76 + result.put("time", topologyEvent.time())
77 + .put("type", topologyEvent.type().toString())
78 + .put("subject", topologyEvent.subject().toString())
79 + .put("reasons", reasons);
80 + return result;
81 + }
82 +
83 + /**
84 + * Produces JSON object for a generic event.
85 + *
86 + * @param event the generic event with the data
87 + * @return JSON object for the generic event
88 + */
89 + private ObjectNode json(ObjectMapper mapper, Event event) {
90 + ObjectNode result = mapper.createObjectNode();
91 +
92 + result.put("time", event.time())
93 + .put("type", event.type().toString())
94 + .put("subject", event.subject().toString());
95 + return result;
96 + }
97 +}
1 +package org.onlab.onos.metrics.topology.cli;
2 +
3 +import java.io.IOException;
4 +import java.util.concurrent.TimeUnit;
5 +
6 +import com.codahale.metrics.Gauge;
7 +import com.codahale.metrics.Meter;
8 +import com.codahale.metrics.json.MetricsModule;
9 +import com.fasterxml.jackson.core.JsonProcessingException;
10 +import com.fasterxml.jackson.databind.JsonNode;
11 +import com.fasterxml.jackson.databind.ObjectMapper;
12 +import com.fasterxml.jackson.databind.node.ObjectNode;
13 +import org.apache.karaf.shell.commands.Command;
14 +import org.onlab.onos.cli.AbstractShellCommand;
15 +import org.onlab.onos.metrics.topology.TopologyMetricsService;
16 +
17 +/**
18 + * Command to show the topology events metrics.
19 + */
20 +@Command(scope = "onos", name = "topology-events-metrics",
21 + description = "Lists topology events metrics")
22 +public class TopologyEventsMetricsCommand extends AbstractShellCommand {
23 +
24 + private static final String FORMAT_GAUGE =
25 + "Last Topology Event Timestamp (ms from epoch)=%d";
26 + private static final String FORMAT_METER =
27 + "Topology Events count=%d rate(events/sec) mean=%f m1=%f m5=%f m15=%f";
28 +
29 + @Override
30 + protected void execute() {
31 + TopologyMetricsService service = get(TopologyMetricsService.class);
32 + Gauge<Long> gauge = service.lastEventTimestampEpochMsGauge();
33 + Meter meter = service.eventRateMeter();
34 +
35 + if (outputJson()) {
36 + ObjectMapper mapper = new ObjectMapper()
37 + .registerModule(new MetricsModule(TimeUnit.SECONDS,
38 + TimeUnit.MILLISECONDS,
39 + false));
40 + ObjectNode result = mapper.createObjectNode();
41 + try {
42 + //
43 + // NOTE: The API for custom serializers is incomplete,
44 + // hence we have to parse the JSON string to create JsonNode.
45 + //
46 + final String gaugeJson = mapper.writeValueAsString(gauge);
47 + final String meterJson = mapper.writeValueAsString(meter);
48 + JsonNode gaugeNode = mapper.readTree(gaugeJson);
49 + JsonNode meterNode = mapper.readTree(meterJson);
50 + result.put("lastTopologyEventTimestamp", gaugeNode);
51 + result.put("listenerEventRate", meterNode);
52 + } catch (JsonProcessingException e) {
53 + log.error("Error writing value as JSON string", e);
54 + } catch (IOException e) {
55 + log.error("Error writing value as JSON string", e);
56 + }
57 + print("%s", result);
58 + } else {
59 + TimeUnit rateUnit = TimeUnit.SECONDS;
60 + double rateFactor = rateUnit.toSeconds(1);
61 + print(FORMAT_GAUGE, gauge.getValue());
62 + print(FORMAT_METER, meter.getCount(),
63 + meter.getMeanRate() * rateFactor,
64 + meter.getOneMinuteRate() * rateFactor,
65 + meter.getFiveMinuteRate() * rateFactor,
66 + meter.getFifteenMinuteRate() * rateFactor);
67 + }
68 + }
69 +}
1 +/**
2 + * ONOS Topology Metrics Application that collects topology-related metrics.
3 + */
4 +package org.onlab.onos.metrics.topology;
1 +<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
2 +
3 + <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
4 + <command>
5 + <action class="org.onlab.onos.metrics.topology.cli.TopologyEventsListCommand"/>
6 + </command>
7 + <command>
8 + <action class="org.onlab.onos.metrics.topology.cli.TopologyEventsMetricsCommand"/>
9 + </command>
10 + </command-bundle>
11 +
12 +</blueprint>
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
27 <module>sdnip</module> 27 <module>sdnip</module>
28 <module>calendar</module> 28 <module>calendar</module>
29 <module>optical</module> 29 <module>optical</module>
30 + <module>metrics</module>
30 </modules> 31 </modules>
31 32
32 <properties> 33 <properties>
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
37 37
38 <bundle>mvn:com.hazelcast/hazelcast/3.3</bundle> 38 <bundle>mvn:com.hazelcast/hazelcast/3.3</bundle>
39 <bundle>mvn:io.dropwizard.metrics/metrics-core/3.1.0</bundle> 39 <bundle>mvn:io.dropwizard.metrics/metrics-core/3.1.0</bundle>
40 + <bundle>mvn:io.dropwizard.metrics/metrics-json/3.1.0</bundle>
40 <bundle>mvn:com.eclipsesource.minimal-json/minimal-json/0.9.1</bundle> 41 <bundle>mvn:com.eclipsesource.minimal-json/minimal-json/0.9.1</bundle>
41 42
42 <bundle>mvn:com.esotericsoftware/kryo/3.0.0</bundle> 43 <bundle>mvn:com.esotericsoftware/kryo/3.0.0</bundle>
...@@ -183,7 +184,6 @@ ...@@ -183,7 +184,6 @@
183 <bundle>mvn:org.onlab.onos/onos-app-optical/1.0.0-SNAPSHOT</bundle> 184 <bundle>mvn:org.onlab.onos/onos-app-optical/1.0.0-SNAPSHOT</bundle>
184 </feature> 185 </feature>
185 186
186 -
187 <feature name="onos-app-sdnip" version="1.0.0" 187 <feature name="onos-app-sdnip" version="1.0.0"
188 description="SDN-IP peering application"> 188 description="SDN-IP peering application">
189 <feature>onos-api</feature> 189 <feature>onos-api</feature>
...@@ -197,4 +197,15 @@ ...@@ -197,4 +197,15 @@
197 <bundle>mvn:org.onlab.onos/onos-app-calendar/1.0.0-SNAPSHOT</bundle> 197 <bundle>mvn:org.onlab.onos/onos-app-calendar/1.0.0-SNAPSHOT</bundle>
198 </feature> 198 </feature>
199 199
200 + <feature name="onos-app-metrics" version="1.0.0"
201 + description="ONOS metrics applications">
202 + <feature>onos-app-metrics-topology</feature>
203 + </feature>
204 +
205 + <feature name="onos-app-metrics-topology" version="1.0.0"
206 + description="ONOS topology metrics application">
207 + <feature>onos-api</feature>
208 + <bundle>mvn:org.onlab.onos/onos-app-metrics-topology/1.0.0-SNAPSHOT</bundle>
209 + </feature>
210 +
200 </features> 211 </features>
......
...@@ -53,6 +53,11 @@ ...@@ -53,6 +53,11 @@
53 <version>3.1.0</version> 53 <version>3.1.0</version>
54 </dependency> 54 </dependency>
55 <dependency> 55 <dependency>
56 + <groupId>io.dropwizard.metrics</groupId>
57 + <artifactId>metrics-json</artifactId>
58 + <version>3.1.0</version>
59 + </dependency>
60 + <dependency>
56 <groupId>org.apache.felix</groupId> 61 <groupId>org.apache.felix</groupId>
57 <artifactId>org.apache.felix.scr.annotations</artifactId> 62 <artifactId>org.apache.felix.scr.annotations</artifactId>
58 </dependency> 63 </dependency>
......