Thomas Vachuska

Merge remote-tracking branch 'origin/master'

<?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-intent</artifactId>
<packaging>bundle</packaging>
<description>ONOS intent 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>
package org.onlab.onos.metrics.intent;
import java.util.List;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import org.onlab.onos.net.intent.IntentEvent;
/**
* Service interface exported by IntentMetrics.
*/
public interface IntentMetricsService {
/**
* Gets the last saved intent events.
*
* @return the last saved intent events.
*/
public List<IntentEvent> getEvents();
/**
* Gets the Metrics' Gauge for the intent SUBMITTED event timestamp
* (ms from the epoch).
*
* @return the Metrics' Gauge for the intent SUBMITTED event timestamp
* (ms from the epoch)
*/
public Gauge<Long> intentSubmittedTimestampEpochMsGauge();
/**
* Gets the Metrics' Gauge for the intent INSTALLED event timestamp
* (ms from the epoch).
*
* @return the Metrics' Gauge for the intent INSTALLED event timestamp
* (ms from the epoch)
*/
public Gauge<Long> intentInstalledTimestampEpochMsGauge();
/**
* Gets the Metrics' Gauge for the intent WITHDRAW_REQUESTED event
* timestamp (ms from the epoch).
*
* TODO: This intent event is not implemented yet.
*
* @return the Metrics' Gauge for the intent WITHDRAW_REQUESTED event
* timestamp (ms from the epoch)
*/
public Gauge<Long> intentWithdrawRequestedTimestampEpochMsGauge();
/**
* Gets the Metrics' Gauge for the intent WITHDRAWN event timestamp
* (ms from the epoch).
*
* @return the Metrics' Gauge for the intent WITHDRAWN event timestamp
* (ms from the epoch)
*/
public Gauge<Long> intentWithdrawnTimestampEpochMsGauge();
/**
* Gets the Metrics' Meter for the submitted intents event rate.
*
* @return the Metrics' Meter for the submitted intents event rate
*/
public Meter intentSubmittedRateMeter();
/**
* Gets the Metrics' Meter for the installed intents event rate.
*
* @return the Metrics' Meter for the installed intent event rate
*/
public Meter intentInstalledRateMeter();
/**
* Gets the Metrics' Meter for the withdraw requested intents event rate.
*
* @return the Metrics' Meter for the withdraw requested intents event rate
*/
public Meter intentWithdrawRequestedRateMeter();
/**
* Gets the Metrics' Meter for the withdraw completed intents event rate.
*
* @return the Metrics' Meter for the withdraw completed intents event rate
*/
public Meter intentWithdrawnRateMeter();
}
package org.onlab.onos.metrics.intent.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.metrics.intent.IntentMetricsService;
import org.onlab.onos.net.intent.IntentEvent;
/**
* Command to show the list of last intent events.
*/
@Command(scope = "onos", name = "intents-events",
description = "Lists the last intent events")
public class IntentEventsListCommand extends AbstractShellCommand {
private static final String FORMAT_EVENT = "Event=%s";
@Override
protected void execute() {
IntentMetricsService service = get(IntentMetricsService.class);
if (outputJson()) {
print("%s", json(service.getEvents()));
} else {
for (IntentEvent event : service.getEvents()) {
print(FORMAT_EVENT, event);
print(""); // Extra empty line for clarity
}
}
}
/**
* Produces a JSON array of intent events.
*
* @param intentEvents the intent events with the data
* @return JSON array with the intent events
*/
private JsonNode json(List<IntentEvent> intentEvents) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (IntentEvent event : intentEvents) {
result.add(json(mapper, event));
}
return result;
}
/**
* Produces JSON object for a intent event.
*
* @param mapper the JSON object mapper to use
* @param intentEvent the intent event with the data
* @return JSON object for the intent event
*/
private ObjectNode json(ObjectMapper mapper, IntentEvent intentEvent) {
ObjectNode result = mapper.createObjectNode();
result.put("time", intentEvent.time())
.put("type", intentEvent.type().toString())
.put("event", intentEvent.toString());
return result;
}
}
package org.onlab.onos.metrics.intent.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.intent.IntentMetricsService;
/**
* Command to show the intent events metrics.
*/
@Command(scope = "onos", name = "intents-events-metrics",
description = "Lists intent events metrics")
public class IntentEventsMetricsCommand extends AbstractShellCommand {
private static final String FORMAT_GAUGE =
"Intent %s Event Timestamp (ms from epoch)=%d";
private static final String FORMAT_METER =
"Intent %s Events count=%d rate(events/sec) mean=%f m1=%f m5=%f m15=%f";
@Override
protected void execute() {
IntentMetricsService service = get(IntentMetricsService.class);
Gauge<Long> gauge;
Meter meter;
if (outputJson()) {
ObjectMapper mapper = new ObjectMapper()
.registerModule(new MetricsModule(TimeUnit.SECONDS,
TimeUnit.MILLISECONDS,
false));
ObjectNode result = mapper.createObjectNode();
//
gauge = service.intentSubmittedTimestampEpochMsGauge();
result.put("intentSubmittedTimestamp", json(mapper, gauge));
gauge = service.intentInstalledTimestampEpochMsGauge();
result.put("intentInstalledTimestamp", json(mapper, gauge));
gauge = service.intentWithdrawRequestedTimestampEpochMsGauge();
result.put("intentWithdrawRequestedTimestamp",
json(mapper, gauge));
gauge = service.intentWithdrawnTimestampEpochMsGauge();
result.put("intentWithdrawnTimestamp", json(mapper, gauge));
//
meter = service.intentSubmittedRateMeter();
result.put("intentSubmittedRate", json(mapper, meter));
meter = service.intentInstalledRateMeter();
result.put("intentInstalledRate", json(mapper, meter));
meter = service.intentWithdrawRequestedRateMeter();
result.put("intentWithdrawRequestedRate", json(mapper, meter));
meter = service.intentWithdrawnRateMeter();
result.put("intentWithdrawnRate", json(mapper, meter));
//
print("%s", result);
} else {
gauge = service.intentSubmittedTimestampEpochMsGauge();
printGauge("Submitted", gauge);
gauge = service.intentInstalledTimestampEpochMsGauge();
printGauge("Installed", gauge);
gauge = service.intentWithdrawRequestedTimestampEpochMsGauge();
printGauge("Withdraw Requested", gauge);
gauge = service.intentWithdrawnTimestampEpochMsGauge();
printGauge("Withdrawn", gauge);
//
meter = service.intentSubmittedRateMeter();
printMeter("Submitted", meter);
meter = service.intentInstalledRateMeter();
printMeter("Installed", meter);
meter = service.intentWithdrawRequestedRateMeter();
printMeter("Withdraw Requested", meter);
meter = service.intentWithdrawnRateMeter();
printMeter("Withdrawn", meter);
}
}
/**
* Produces JSON node for an Object.
*
* @param mapper the JSON object mapper to use
* @param object the Object with the data
* @return JSON node for the Object
*/
private JsonNode json(ObjectMapper mapper, Object object) {
//
// NOTE: The API for custom serializers is incomplete,
// hence we have to parse the JSON string to create JsonNode.
//
try {
final String objectJson = mapper.writeValueAsString(object);
JsonNode objectNode = mapper.readTree(objectJson);
return objectNode;
} catch (JsonProcessingException e) {
log.error("Error writing value as JSON string", e);
} catch (IOException e) {
log.error("Error writing value as JSON string", e);
}
return null;
}
/**
* Prints a Gauge.
*
* @param operationStr the string with the intent operation to print
* @param gauge the Gauge to print
*/
private void printGauge(String operationStr, Gauge<Long> gauge) {
print(FORMAT_GAUGE, operationStr, gauge.getValue());
}
/**
* Prints a Meter.
*
* @param operationStr the string with the intent operation to print
* @param meter the Meter to print
*/
private void printMeter(String operationStr, Meter meter) {
TimeUnit rateUnit = TimeUnit.SECONDS;
double rateFactor = rateUnit.toSeconds(1);
print(FORMAT_METER, operationStr, meter.getCount(),
meter.getMeanRate() * rateFactor,
meter.getOneMinuteRate() * rateFactor,
meter.getFiveMinuteRate() * rateFactor,
meter.getFifteenMinuteRate() * rateFactor);
}
}
/**
* ONOS Intent Metrics Application that collects intent-related metrics.
*/
package org.onlab.onos.metrics.intent;
<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.intent.cli.IntentEventsListCommand"/>
</command>
<command>
<action class="org.onlab.onos.metrics.intent.cli.IntentEventsMetricsCommand"/>
</command>
</command-bundle>
</blueprint>
......@@ -17,6 +17,7 @@
<description>ONOS metrics applications</description>
<modules>
<module>intent</module>
<module>topology</module>
</modules>
......
......@@ -18,6 +18,15 @@ 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.device.DeviceEvent;
import org.onlab.onos.net.device.DeviceListener;
import org.onlab.onos.net.device.DeviceService;
import org.onlab.onos.net.host.HostEvent;
import org.onlab.onos.net.host.HostListener;
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.link.LinkEvent;
import org.onlab.onos.net.link.LinkListener;
import org.onlab.onos.net.link.LinkService;
import org.onlab.onos.net.topology.TopologyEvent;
import org.onlab.onos.net.topology.TopologyListener;
import org.onlab.onos.net.topology.TopologyService;
......@@ -28,14 +37,26 @@ import org.slf4j.Logger;
*/
@Component(immediate = true)
@Service
public class TopologyMetrics implements TopologyMetricsService,
TopologyListener {
public class TopologyMetrics implements TopologyMetricsService {
private static final Logger log = getLogger(TopologyMetrics.class);
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LinkService linkService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected TopologyService topologyService;
private LinkedList<TopologyEvent> lastEvents = new LinkedList<>();
private static final int LAST_EVENTS_MAX_N = 10;
private LinkedList<Event> lastEvents = new LinkedList<>();
private static final int LAST_EVENTS_MAX_N = 100;
private final DeviceListener deviceListener = new InnerDeviceListener();
private final HostListener hostListener = new InnerHostListener();
private final LinkListener linkListener = new InnerLinkListener();
private final TopologyListener topologyListener =
new InnerTopologyListener();
//
// Metrics
......@@ -61,22 +82,33 @@ public class TopologyMetrics implements TopologyMetricsService,
protected void activate() {
clear();
registerMetrics();
topologyService.addListener(this);
// Register for all topology-related events
deviceService.addListener(deviceListener);
hostService.addListener(hostListener);
linkService.addListener(linkListener);
topologyService.addListener(topologyListener);
log.info("ONOS Topology Metrics started.");
}
@Deactivate
public void deactivate() {
topologyService.removeListener(this);
// De-register from all topology-related events
deviceService.removeListener(deviceListener);
hostService.removeListener(hostListener);
linkService.removeListener(linkListener);
topologyService.removeListener(topologyListener);
removeMetrics();
clear();
log.info("ONOS Topology Metrics stopped.");
}
@Override
public List<TopologyEvent> getEvents() {
public List<Event> getEvents() {
synchronized (lastEvents) {
return ImmutableList.<TopologyEvent>copyOf(lastEvents);
return ImmutableList.<Event>copyOf(lastEvents);
}
}
......@@ -90,27 +122,22 @@ public class TopologyMetrics implements TopologyMetricsService,
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
//
/**
* Records an event.
*
* @param event the event to record
* @param updateEventRateMeter if true, update the Event Rate Meter
*/
private void recordEvent(Event event, boolean updateEventRateMeter) {
synchronized (lastEvents) {
lastEventTimestampEpochMs = System.currentTimeMillis();
if (updateEventRateMeter) {
eventRateMeter.mark(1);
}
//
// Keep only the last N events, where N = LAST_EVENTS_MAX_N
//
while (lastEvents.size() >= LAST_EVENTS_MAX_N) {
lastEvents.remove();
}
......@@ -119,11 +146,67 @@ public class TopologyMetrics implements TopologyMetricsService,
}
/**
* Inner Device Event Listener class.
*/
private class InnerDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent event) {
recordEvent(event, true);
log.debug("Device Event: time = {} type = {} event = {}",
event.time(), event.type(), event);
}
}
/**
* Inner Host Event Listener class.
*/
private class InnerHostListener implements HostListener {
@Override
public void event(HostEvent event) {
recordEvent(event, true);
log.debug("Host Event: time = {} type = {} event = {}",
event.time(), event.type(), event);
}
}
/**
* Inner Link Event Listener class.
*/
private class InnerLinkListener implements LinkListener {
@Override
public void event(LinkEvent event) {
recordEvent(event, true);
log.debug("Link Event: time = {} type = {} event = {}",
event.time(), event.type(), event);
}
}
/**
* Inner Topology Event Listener class.
*/
private class InnerTopologyListener implements TopologyListener {
@Override
public void event(TopologyEvent event) {
//
// NOTE: Don't update the eventRateMeter, because the real
// events are already captured/counted.
//
recordEvent(event, false);
log.debug("Topology Event: time = {} type = {} event = {}",
event.time(), event.type(), event);
for (Event reason : event.reasons()) {
log.debug("Topology Event Reason: time = {} type = {} event = {}",
reason.time(), reason.type(), reason);
}
}
}
/**
* Clears the internal state.
*/
private void clear() {
lastEventTimestampEpochMs = 0;
synchronized (lastEvents) {
lastEventTimestampEpochMs = 0;
lastEvents.clear();
}
}
......
......@@ -4,7 +4,7 @@ import java.util.List;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import org.onlab.onos.net.topology.TopologyEvent;
import org.onlab.onos.event.Event;
/**
* Service interface exported by TopologyMetrics.
......@@ -15,7 +15,7 @@ public interface TopologyMetricsService {
*
* @return the last saved topology events.
*/
public List<TopologyEvent> getEvents();
public List<Event> getEvents();
/**
* Gets the Metrics' Gauge for the last topology event timestamp
......
......@@ -19,10 +19,8 @@ import org.onlab.onos.net.topology.TopologyEvent;
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";
private static final String FORMAT_EVENT = "Event=%s";
private static final String FORMAT_REASON = " Reason=%s";
@Override
protected void execute() {
......@@ -31,12 +29,13 @@ public class TopologyEventsListCommand extends AbstractShellCommand {
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());
for (Event event : service.getEvents()) {
print(FORMAT_EVENT, event);
if (event instanceof TopologyEvent) {
TopologyEvent topologyEvent = (TopologyEvent) event;
for (Event reason : topologyEvent.reasons()) {
print(FORMAT_REASON, reason);
}
}
print(""); // Extra empty line for clarity
}
......@@ -46,14 +45,14 @@ public class TopologyEventsListCommand extends AbstractShellCommand {
/**
* Produces a JSON array of topology events.
*
* @param topologyEvents the topology events with the data
* @param events the topology events with the data
* @return JSON array with the topology events
*/
private JsonNode json(List<TopologyEvent> topologyEvents) {
private JsonNode json(List<Event> events) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (TopologyEvent event : topologyEvents) {
for (Event event : events) {
result.add(json(mapper, event));
}
return result;
......@@ -66,32 +65,23 @@ public class TopologyEventsListCommand extends AbstractShellCommand {
* @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());
.put("event", event.toString());
// Add the reasons if a TopologyEvent
if (event instanceof TopologyEvent) {
TopologyEvent topologyEvent = (TopologyEvent) event;
ArrayNode reasons = mapper.createArrayNode();
for (Event reason : topologyEvent.reasons()) {
reasons.add(json(mapper, reason));
}
result.put("reasons", reasons);
}
return result;
}
}
......
......@@ -199,9 +199,16 @@
<feature name="onos-app-metrics" version="1.0.0"
description="ONOS metrics applications">
<feature>onos-app-metrics-intent</feature>
<feature>onos-app-metrics-topology</feature>
</feature>
<feature name="onos-app-metrics-intent" version="1.0.0"
description="ONOS intent metrics application">
<feature>onos-api</feature>
<bundle>mvn:org.onlab.onos/onos-app-metrics-intent/1.0.0-SNAPSHOT</bundle>
</feature>
<feature name="onos-app-metrics-topology" version="1.0.0"
description="ONOS topology metrics application">
<feature>onos-api</feature>
......