diff --git a/cli/src/main/java/org/onosproject/cli/net/TableStatisticsCommand.java b/cli/src/main/java/org/onosproject/cli/net/TableStatisticsCommand.java new file mode 100644 index 0000000000..e0cd72fcfc --- /dev/null +++ b/cli/src/main/java/org/onosproject/cli/net/TableStatisticsCommand.java @@ -0,0 +1,145 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.cli.net; + +import static com.google.common.collect.Lists.newArrayList; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.apache.karaf.shell.commands.Option; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.cli.Comparators; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TableStatisticsEntry; + +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; + +/** + * Lists port statistic of all ports in the system. + */ +@Command(scope = "onos", name = "tablestats", + description = "Lists statistics of all tables in the device") +public class TableStatisticsCommand extends AbstractShellCommand { + + @Option(name = "-t", aliases = "--table", description = "Show human readable table format for statistics", + required = false, multiValued = false) + private boolean table = false; + + @Argument(index = 0, name = "uri", description = "Device ID", + required = false, multiValued = false) + String uri = null; + + private static final String FORMAT = + " table=%s, active=%s, lookedup=%s, matched=%s"; + + @Override + protected void execute() { + FlowRuleService flowService = get(FlowRuleService.class); + DeviceService deviceService = get(DeviceService.class); + + SortedMap> deviceTableStats = + getSortedTableStats(deviceService, flowService); + + if (outputJson()) { + print("%s", json(deviceTableStats.keySet(), deviceTableStats)); + } else { + deviceTableStats.forEach((device, tableStats) -> printTableStats(device, tableStats)); + } + } + + /** + * Produces a JSON array of table statistics grouped by the each device. + * + * @param devices collection of devices + * @param deviceTableStats collection of table statistics per each device + * @return JSON array + */ + private JsonNode json(Iterable devices, + Map> deviceTableStats) { + ObjectMapper mapper = new ObjectMapper(); + ArrayNode result = mapper.createArrayNode(); + for (Device device : devices) { + result.add(json(mapper, device, deviceTableStats.get(device))); + } + return result; + } + + // Produces JSON object with the table statistics of the given device. + private ObjectNode json(ObjectMapper mapper, + Device device, List tableStats) { + ObjectNode result = mapper.createObjectNode(); + ArrayNode array = mapper.createArrayNode(); + + tableStats.forEach(tableStat -> array.add(jsonForEntity(tableStat, TableStatisticsEntry.class))); + + result.put("device", device.id().toString()) + .put("tableCount", tableStats.size()) + .set("tables", array); + return result; + } + + /** + * Prints flow table statistics. + * + * @param d the device + * @param tableStats the set of flow table statistics for that device + */ + protected void printTableStats(Device d, + List tableStats) { + boolean empty = tableStats == null || tableStats.isEmpty(); + print("deviceId=%s, tableCount=%d", d.id(), empty ? 0 : tableStats.size()); + if (!empty) { + for (TableStatisticsEntry t : tableStats) { + print(FORMAT, t.tableId(), t.activeFlowEntries(), + t.packetsLookedup(), t.packetsMatched()); + } + } + } + + /** + * Returns the list of table statistics sorted using the device ID URIs and table IDs. + * + * @param deviceService device service + * @param flowService flow rule service + * @return sorted table statistics list + */ + protected SortedMap> getSortedTableStats(DeviceService deviceService, + FlowRuleService flowService) { + SortedMap> deviceTableStats = new TreeMap<>(Comparators.ELEMENT_COMPARATOR); + List tableStatsList; + Iterable devices = uri == null ? deviceService.getDevices() : + Collections.singletonList(deviceService.getDevice(DeviceId.deviceId(uri))); + for (Device d : devices) { + tableStatsList = newArrayList(flowService.getFlowTableStatistics(d.id())); + tableStatsList.sort((p1, p2) -> Integer.valueOf(p1.tableId()).compareTo(Integer.valueOf(p2.tableId()))); + deviceTableStats.put(d, tableStatsList); + } + return deviceTableStats; + } + +} diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml index 8c56a494ad..c28824aa8f 100644 --- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml +++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml @@ -347,6 +347,10 @@ + + + + diff --git a/core/api/src/main/java/org/onosproject/net/flow/DefaultTableStatisticsEntry.java b/core/api/src/main/java/org/onosproject/net/flow/DefaultTableStatisticsEntry.java new file mode 100644 index 0000000000..929b285dc6 --- /dev/null +++ b/core/api/src/main/java/org/onosproject/net/flow/DefaultTableStatisticsEntry.java @@ -0,0 +1,89 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.net.flow; + +import org.onosproject.net.DeviceId; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Default implementation of table statistics entry interface. + */ +public final class DefaultTableStatisticsEntry implements TableStatisticsEntry { + + private final DeviceId deviceId; + private final int tableId; + private final long activeFlowEntries; + private final long packetsLookedupCount; + private final long packetsMatchedCount; + + /** + * Default table statistics constructor. + * + * @param deviceId device identifier + * @param tableId table identifier + * @param activeFlowEntries number of active flow entries in the table + * @param packetsLookedupCount number of packets looked up in table + * @param packetsMatchedCount number of packets that hit table + */ + public DefaultTableStatisticsEntry(DeviceId deviceId, + int tableId, + long activeFlowEntries, + long packetsLookedupCount, + long packetsMatchedCount) { + this.deviceId = checkNotNull(deviceId); + this.tableId = tableId; + this.activeFlowEntries = activeFlowEntries; + this.packetsLookedupCount = packetsLookedupCount; + this.packetsMatchedCount = packetsMatchedCount; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("device: " + deviceId + ", "); + + sb.append("tableId: " + this.tableId + ", "); + sb.append("activeEntries: " + this.activeFlowEntries + ", "); + sb.append("packetsLookedUp: " + this.packetsLookedupCount + ", "); + sb.append("packetsMatched: " + this.packetsMatchedCount); + + return sb.toString(); + } + + @Override + public int tableId() { + return tableId; + } + + @Override + public long activeFlowEntries() { + return activeFlowEntries; + } + + @Override + public long packetsLookedup() { + return packetsLookedupCount; + } + + @Override + public long packetsMatched() { + return packetsMatchedCount; + } + + @Override + public DeviceId deviceId() { + return deviceId; + } +} diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderService.java b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderService.java index 48aa5047ac..aefa96b4a7 100644 --- a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderService.java +++ b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleProviderService.java @@ -15,6 +15,8 @@ */ package org.onosproject.net.flow; +import java.util.List; + import org.onosproject.net.DeviceId; import org.onosproject.net.provider.ProviderService; @@ -49,6 +51,15 @@ public interface FlowRuleProviderService extends ProviderService flowEntries); + /** + * Pushes the collection of table statistics entries currently extracted + * from the given device. + * + * @param deviceId device identifier + * @param tableStatsEntries collection of flow table statistics entries + */ + void pushTableStatistics(DeviceId deviceId, List tableStatsEntries); + /** * Indicates to the core that the requested batch operation has * been completed. @@ -57,5 +68,4 @@ public interface FlowRuleProviderService extends ProviderService getFlowTableStatistics(DeviceId deviceId); } diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java index cece989364..d81c73c9e5 100644 --- a/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java +++ b/core/api/src/main/java/org/onosproject/net/flow/FlowRuleStore.java @@ -15,6 +15,8 @@ */ package org.onosproject.net.flow; +import java.util.List; + import org.onosproject.net.DeviceId; import org.onosproject.store.Store; @@ -93,4 +95,23 @@ public interface FlowRuleStore extends Store tableStats); + + /** + * Returns the flow table statistics associated with a device. + * + * @param deviceId the device ID + * @return the flow table statistics + */ + Iterable getTableStatistics(DeviceId deviceId); } diff --git a/core/api/src/main/java/org/onosproject/net/flow/TableStatisticsEntry.java b/core/api/src/main/java/org/onosproject/net/flow/TableStatisticsEntry.java new file mode 100644 index 0000000000..563f31cedb --- /dev/null +++ b/core/api/src/main/java/org/onosproject/net/flow/TableStatisticsEntry.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.net.flow; + +import org.onosproject.net.DeviceId; + +/** + * Interface for flow table statistics of a device. + */ +public interface TableStatisticsEntry { + + /** + * Returns the device Id. + * + * @return device id + */ + DeviceId deviceId(); + + /** + * Returns the table number. + * + * @return table number + */ + int tableId(); + + /** + * Returns the number of active flow entries in this table. + * + * @return the number of active flow entries + */ + long activeFlowEntries(); + + /** + * Returns the number of packets looked up in the table. + * + * @return the number of packets looked up in the table + */ + long packetsLookedup(); + + /** + * Returns the number of packets that successfully matched in the table. + * + * @return the number of packets that successfully matched in the table + */ + long packetsMatched(); +} diff --git a/core/api/src/test/java/org/onosproject/net/flow/FlowRuleServiceAdapter.java b/core/api/src/test/java/org/onosproject/net/flow/FlowRuleServiceAdapter.java index c7b78791cc..56e5911884 100644 --- a/core/api/src/test/java/org/onosproject/net/flow/FlowRuleServiceAdapter.java +++ b/core/api/src/test/java/org/onosproject/net/flow/FlowRuleServiceAdapter.java @@ -35,17 +35,14 @@ public class FlowRuleServiceAdapter implements FlowRuleService { @Override public void applyFlowRules(FlowRule... flowRules) { - } @Override public void removeFlowRules(FlowRule... flowRules) { - } @Override public void removeFlowRulesById(ApplicationId appId) { - } @Override @@ -60,16 +57,18 @@ public class FlowRuleServiceAdapter implements FlowRuleService { @Override public void apply(FlowRuleOperations ops) { - } @Override public void addListener(FlowRuleListener listener) { - } @Override public void removeListener(FlowRuleListener listener) { + } + @Override + public Iterable getFlowTableStatistics(DeviceId deviceId) { + return null; } } diff --git a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java index eb53152e98..3433b3b7d5 100644 --- a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java +++ b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java @@ -16,6 +16,7 @@ package org.onosproject.codec.impl; import com.google.common.collect.ImmutableSet; + import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; @@ -36,6 +37,7 @@ import org.onosproject.net.Port; import org.onosproject.net.driver.Driver; import org.onosproject.net.flow.FlowEntry; import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TableStatisticsEntry; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flow.criteria.Criterion; @@ -99,6 +101,7 @@ public class CodecManager implements CodecService { registerCodec(Driver.class, new DriverCodec()); registerCodec(GroupBucket.class, new GroupBucketCodec()); registerCodec(Load.class, new LoadCodec()); + registerCodec(TableStatisticsEntry.class, new TableStatisticsEntryCodec()); log.info("Started"); } diff --git a/core/common/src/main/java/org/onosproject/codec/impl/TableStatisticsEntryCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/TableStatisticsEntryCodec.java new file mode 100644 index 0000000000..7834ceb18e --- /dev/null +++ b/core/common/src/main/java/org/onosproject/codec/impl/TableStatisticsEntryCodec.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.codec.impl; + +import org.onosproject.codec.CodecContext; +import org.onosproject.codec.JsonCodec; +import org.onosproject.net.flow.TableStatisticsEntry; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Table statistics entry JSON codec. + */ +public final class TableStatisticsEntryCodec extends JsonCodec { + + @Override + public ObjectNode encode(TableStatisticsEntry entry, CodecContext context) { + checkNotNull(entry, "Table Statistics entry cannot be null"); + + final ObjectNode result = context.mapper().createObjectNode() + .put("tableId", entry.tableId()) + .put("deviceId", entry.deviceId().toString()) + .put("activeEntries", entry.activeFlowEntries()) + .put("packetsLookedUp", entry.packetsLookedup()) + .put("packetsMatched", entry.packetsMatched()); + + return result; + } + +} + diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java index c8c92aa5a8..bed32a2dfe 100644 --- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java +++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleFlowRuleStore.java @@ -20,8 +20,10 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.common.util.concurrent.SettableFuture; + import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; @@ -44,6 +46,7 @@ import org.onosproject.net.flow.FlowRuleEvent.Type; import org.onosproject.net.flow.FlowRuleStore; import org.onosproject.net.flow.FlowRuleStoreDelegate; import org.onosproject.net.flow.StoredFlowEntry; +import org.onosproject.net.flow.TableStatisticsEntry; import org.onosproject.store.AbstractStore; import org.slf4j.Logger; @@ -79,6 +82,9 @@ public class SimpleFlowRuleStore private final ConcurrentMap>> flowEntries = new ConcurrentHashMap<>(); + private final ConcurrentMap> + deviceTableStats = new ConcurrentHashMap<>(); + private final AtomicInteger localBatchIdGen = new AtomicInteger(); // TODO: make this configurable @@ -97,6 +103,7 @@ public class SimpleFlowRuleStore @Deactivate public void deactivate() { + deviceTableStats.clear(); flowEntries.clear(); log.info("Stopped"); } @@ -315,4 +322,20 @@ public class SimpleFlowRuleStore } } } + + @Override + public FlowRuleEvent updateTableStatistics(DeviceId deviceId, + List tableStats) { + deviceTableStats.put(deviceId, tableStats); + return null; + } + + @Override + public Iterable getTableStatistics(DeviceId deviceId) { + List tableStats = deviceTableStats.get(deviceId); + if (tableStats == null) { + return Collections.emptyList(); + } + return ImmutableList.copyOf(tableStats); + } } diff --git a/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java index 9bbd0aaf50..5958d1f5dd 100644 --- a/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java +++ b/core/net/src/main/java/org/onosproject/net/flow/impl/FlowRuleManager.java @@ -22,6 +22,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; + import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; @@ -58,6 +59,7 @@ import org.onosproject.net.flow.FlowRuleProviderService; import org.onosproject.net.flow.FlowRuleService; import org.onosproject.net.flow.FlowRuleStore; import org.onosproject.net.flow.FlowRuleStoreDelegate; +import org.onosproject.net.flow.TableStatisticsEntry; import org.onosproject.net.provider.AbstractProviderService; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; @@ -448,6 +450,12 @@ public class FlowRuleManager operation )); } + + @Override + public void pushTableStatistics(DeviceId deviceId, + List tableStats) { + store.updateTableStatistics(deviceId, tableStats); + } } // Store delegate to re-post events emitted from the store. @@ -603,4 +611,10 @@ public class FlowRuleManager } } + + @Override + public Iterable getFlowTableStatistics(DeviceId deviceId) { + checkPermission(FLOWRULE_READ); + return store.getTableStatistics(deviceId); + } } diff --git a/core/store/dist/src/main/java/org/onosproject/store/flow/impl/NewDistributedFlowRuleStore.java b/core/store/dist/src/main/java/org/onosproject/store/flow/impl/NewDistributedFlowRuleStore.java index de7a3ac312..8cd63e7dd8 100644 --- a/core/store/dist/src/main/java/org/onosproject/store/flow/impl/NewDistributedFlowRuleStore.java +++ b/core/store/dist/src/main/java/org/onosproject/store/flow/impl/NewDistributedFlowRuleStore.java @@ -16,6 +16,7 @@ package org.onosproject.store.flow.impl; import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; @@ -57,6 +58,7 @@ import org.onosproject.net.flow.FlowRuleService; import org.onosproject.net.flow.FlowRuleStore; import org.onosproject.net.flow.FlowRuleStoreDelegate; import org.onosproject.net.flow.StoredFlowEntry; +import org.onosproject.net.flow.TableStatisticsEntry; import org.onosproject.store.AbstractStore; import org.onosproject.store.cluster.messaging.ClusterCommunicationService; import org.onosproject.store.cluster.messaging.ClusterMessage; @@ -64,9 +66,16 @@ import org.onosproject.store.cluster.messaging.ClusterMessageHandler; import org.onosproject.store.flow.ReplicaInfoEvent; import org.onosproject.store.flow.ReplicaInfoEventListener; import org.onosproject.store.flow.ReplicaInfoService; +import org.onosproject.store.impl.MastershipBasedTimestamp; +import org.onosproject.store.serializers.KryoNamespaces; import org.onosproject.store.serializers.KryoSerializer; import org.onosproject.store.serializers.StoreSerializer; import org.onosproject.store.serializers.custom.DistributedStoreSerializers; +import org.onosproject.store.service.EventuallyConsistentMap; +import org.onosproject.store.service.EventuallyConsistentMapEvent; +import org.onosproject.store.service.EventuallyConsistentMapListener; +import org.onosproject.store.service.StorageService; +import org.onosproject.store.service.WallClockTimestamp; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; @@ -151,6 +160,13 @@ public class NewDistributedFlowRuleStore private final ScheduledExecutorService backupSenderExecutor = Executors.newSingleThreadScheduledExecutor(groupedThreads("onos/flow", "backup-sender")); + private EventuallyConsistentMap> deviceTableStats; + private final EventuallyConsistentMapListener> tableStatsListener = + new InternalTableStatsListener(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected StorageService storageService; + protected static final StoreSerializer SERIALIZER = new KryoSerializer() { @Override protected void setupKryoPool() { @@ -161,6 +177,11 @@ public class NewDistributedFlowRuleStore } }; + protected static final KryoNamespace.Builder SERIALIZER_BUILDER = KryoNamespace.newBuilder() + .register(KryoNamespaces.API) + .register(MastershipBasedTimestamp.class); + + private IdGenerator idGenerator; private NodeId local; @@ -186,6 +207,15 @@ public class NewDistributedFlowRuleStore TimeUnit.MILLISECONDS); } + deviceTableStats = storageService.>eventuallyConsistentMapBuilder() + .withName("onos-flow-table-stats") + .withSerializer(SERIALIZER_BUILDER) + .withAntiEntropyPeriod(5, TimeUnit.SECONDS) + .withTimestampProvider((k, v) -> new WallClockTimestamp()) + .withTombstonesDisabled() + .build(); + deviceTableStats.addListener(tableStatsListener); + logConfig("Started"); } @@ -197,6 +227,8 @@ public class NewDistributedFlowRuleStore } configService.unregisterProperties(getClass(), false); unregisterMessageHandlers(); + deviceTableStats.removeListener(tableStatsListener); + deviceTableStats.destroy(); messageHandlingExecutor.shutdownNow(); backupSenderExecutor.shutdownNow(); log.info("Stopped"); @@ -786,4 +818,36 @@ public class NewDistributedFlowRuleStore return backedupDevices; } } + + @Override + public FlowRuleEvent updateTableStatistics(DeviceId deviceId, + List tableStats) { + deviceTableStats.put(deviceId, tableStats); + return null; + } + + @Override + public Iterable getTableStatistics(DeviceId deviceId) { + NodeId master = mastershipService.getMasterFor(deviceId); + + if (master == null) { + log.debug("Failed to getTableStats: No master for {}", deviceId); + return Collections.emptyList(); + } + + List tableStats = deviceTableStats.get(deviceId); + if (tableStats == null) { + return Collections.emptyList(); + } + return ImmutableList.copyOf(tableStats); + } + + private class InternalTableStatsListener + implements EventuallyConsistentMapListener> { + @Override + public void event(EventuallyConsistentMapEvent> event) { + //TODO: Generate an event to listeners (do we need?) + } + } } diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java index 4734672cdc..5b5056cb1a 100644 --- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java +++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java @@ -84,6 +84,7 @@ import org.onosproject.net.device.PortStatistics; import org.onosproject.net.flow.CompletedBatchOperation; import org.onosproject.net.flow.DefaultFlowEntry; import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTableStatisticsEntry; import org.onosproject.net.flow.DefaultTrafficSelector; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.FlowEntry; @@ -95,6 +96,7 @@ import org.onosproject.net.flow.FlowRuleBatchRequest; import org.onosproject.net.flow.FlowRuleEvent; import org.onosproject.net.flow.FlowRuleExtPayLoad; import org.onosproject.net.flow.StoredFlowEntry; +import org.onosproject.net.flow.TableStatisticsEntry; import org.onosproject.net.flow.criteria.Criterion; import org.onosproject.net.flow.criteria.EthCriterion; import org.onosproject.net.flow.criteria.EthTypeCriterion; @@ -421,7 +423,9 @@ public final class KryoNamespaces { DefaultAnnotations.class, PortStatistics.class, DefaultPortStatistics.class, - IntentDomainId.class + IntentDomainId.class, + TableStatisticsEntry.class, + DefaultTableStatisticsEntry.class ) .register(new DefaultApplicationIdSerializer(), DefaultApplicationId.class) .register(new URISerializer(), URI.class) diff --git a/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java b/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java index a25b7a8039..d0429947ef 100644 --- a/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java +++ b/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java @@ -47,6 +47,8 @@ import org.projectfloodlight.openflow.protocol.OFExperimenter; import org.projectfloodlight.openflow.protocol.OFFactories; import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry; import org.projectfloodlight.openflow.protocol.OFFlowStatsReply; +import org.projectfloodlight.openflow.protocol.OFTableStatsEntry; +import org.projectfloodlight.openflow.protocol.OFTableStatsReply; import org.projectfloodlight.openflow.protocol.OFGroupDescStatsEntry; import org.projectfloodlight.openflow.protocol.OFGroupDescStatsReply; import org.projectfloodlight.openflow.protocol.OFGroupStatsEntry; @@ -129,6 +131,9 @@ public class OpenFlowControllerImpl implements OpenFlowController { protected Multimap fullFlowStats = ArrayListMultimap.create(); + protected Multimap fullTableStats = + ArrayListMultimap.create(); + protected Multimap fullGroupStats = ArrayListMultimap.create(); @@ -230,6 +235,7 @@ public class OpenFlowControllerImpl implements OpenFlowController { @Override public void processPacket(Dpid dpid, OFMessage msg) { Collection flowStats; + Collection tableStats; Collection groupStats; Collection groupDescStats; Collection portStats; @@ -277,6 +283,15 @@ public class OpenFlowControllerImpl implements OpenFlowController { executorMsgs.submit(new OFMessageHandler(dpid, rep.build())); } break; + case TABLE: + tableStats = publishTableStats(dpid, (OFTableStatsReply) reply); + if (tableStats != null) { + OFTableStatsReply.Builder rep = + OFFactories.getFactory(msg.getVersion()).buildTableStatsReply(); + rep.setEntries(Lists.newLinkedList(tableStats)); + executorMsgs.submit(new OFMessageHandler(dpid, rep.build())); + } + break; case GROUP: groupStats = publishGroupStats(dpid, (OFGroupStatsReply) reply); if (groupStats != null) { @@ -395,6 +410,16 @@ public class OpenFlowControllerImpl implements OpenFlowController { return null; } + private synchronized Collection publishTableStats(Dpid dpid, + OFTableStatsReply reply) { + //TODO: Get rid of synchronized + fullTableStats.putAll(dpid, reply.getEntries()); + if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) { + return fullTableStats.removeAll(dpid); + } + return null; + } + private synchronized Collection publishGroupStats(Dpid dpid, OFGroupStatsReply reply) { //TODO: Get rid of synchronized diff --git a/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java index 949c65766c..4c38d7ad9e 100644 --- a/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java +++ b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java @@ -21,6 +21,7 @@ import com.google.common.cache.RemovalCause; import com.google.common.cache.RemovalNotification; import com.google.common.collect.Maps; import com.google.common.collect.Sets; + import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; @@ -32,6 +33,7 @@ import org.onosproject.cfg.ComponentConfigService; import org.onosproject.core.ApplicationId; import org.onosproject.net.DeviceId; import org.onosproject.net.flow.CompletedBatchOperation; +import org.onosproject.net.flow.DefaultTableStatisticsEntry; import org.onosproject.net.flow.FlowEntry; import org.onosproject.net.flow.FlowRule; import org.onosproject.net.flow.FlowRuleBatchEntry; @@ -40,6 +42,7 @@ import org.onosproject.net.flow.FlowRuleExtPayLoad; import org.onosproject.net.flow.FlowRuleProvider; import org.onosproject.net.flow.FlowRuleProviderRegistry; import org.onosproject.net.flow.FlowRuleProviderService; +import org.onosproject.net.flow.TableStatisticsEntry; import org.onosproject.net.provider.AbstractProvider; import org.onosproject.net.provider.ProviderId; import org.onosproject.net.statistic.DefaultLoad; @@ -58,6 +61,8 @@ import org.projectfloodlight.openflow.protocol.OFErrorType; import org.projectfloodlight.openflow.protocol.OFFlowMod; import org.projectfloodlight.openflow.protocol.OFFlowRemoved; import org.projectfloodlight.openflow.protocol.OFFlowStatsReply; +import org.projectfloodlight.openflow.protocol.OFTableStatsReply; +import org.projectfloodlight.openflow.protocol.OFTableStatsEntry; import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFPortStatus; import org.projectfloodlight.openflow.protocol.OFStatsReply; @@ -70,6 +75,7 @@ import java.util.Collections; import java.util.Dictionary; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.Timer; @@ -121,6 +127,8 @@ public class OpenFlowRuleProvider extends AbstractProvider // NewAdaptiveFlowStatsCollector Set private final Map afsCollectors = Maps.newHashMap(); + private final Map collectors = Maps.newHashMap(); + private final Map tableStatsCollectors = Maps.newHashMap(); /** * Creates an OpenFlow host provider. @@ -214,6 +222,9 @@ public class OpenFlowRuleProvider extends AbstractProvider fsc.start(); simpleCollectors.put(new Dpid(sw.getId()), fsc); } + TableStatisticsCollector tsc = new TableStatisticsCollector(timer, sw, flowPollFrequency); + tsc.start(); + tableStatsCollectors.put(new Dpid(sw.getId()), tsc); } private void stopCollectors() { @@ -225,17 +236,19 @@ public class OpenFlowRuleProvider extends AbstractProvider simpleCollectors.values().forEach(FlowStatsCollector::stop); simpleCollectors.clear(); } + tableStatsCollectors.values().forEach(TableStatisticsCollector::stop); + tableStatsCollectors.clear(); } private void adjustRate() { DefaultLoad.setPollInterval(flowPollFrequency); - if (adaptiveFlowSampling) { // NewAdaptiveFlowStatsCollector calAndPollInterval afsCollectors.values().forEach(fsc -> fsc.adjustCalAndPollInterval(flowPollFrequency)); } else { simpleCollectors.values().forEach(fsc -> fsc.adjustPollInterval(flowPollFrequency)); } + tableStatsCollectors.values().forEach(tsc -> tsc.adjustPollInterval(flowPollFrequency)); } @Override @@ -384,6 +397,10 @@ public class OpenFlowRuleProvider extends AbstractProvider collector.stop(); } } + TableStatisticsCollector tsc = tableStatsCollectors.remove(dpid); + if (tsc != null) { + tsc.stop(); + } } @Override @@ -413,6 +430,8 @@ public class OpenFlowRuleProvider extends AbstractProvider case STATS_REPLY: if (((OFStatsReply) msg).getStatsType() == OFStatsType.FLOW) { pushFlowMetrics(dpid, (OFFlowStatsReply) msg); + } else if (((OFStatsReply) msg).getStatsType() == OFStatsType.TABLE) { + pushTableStatistics(dpid, (OFTableStatsReply) msg); } break; case BARRIER_REPLY: @@ -473,7 +492,6 @@ public class OpenFlowRuleProvider extends AbstractProvider private void pushFlowMetrics(Dpid dpid, OFFlowStatsReply replies) { DeviceId did = DeviceId.deviceId(Dpid.uri(dpid)); - OpenFlowSwitch sw = controller.getSwitch(dpid); List flowEntries = replies.getEntries().stream() .map(entry -> new FlowEntryBuilder(dpid, entry).build()) @@ -512,6 +530,31 @@ public class OpenFlowRuleProvider extends AbstractProvider providerService.pushFlowMetrics(did, flowEntries); } } + + private void pushTableStatistics(Dpid dpid, OFTableStatsReply replies) { + + DeviceId did = DeviceId.deviceId(Dpid.uri(dpid)); + List tableStatsEntries = replies.getEntries().stream() + .map(entry -> buildTableStatistics(did, entry)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + providerService.pushTableStatistics(did, tableStatsEntries); + } + + private TableStatisticsEntry buildTableStatistics(DeviceId deviceId, + OFTableStatsEntry ofEntry) { + TableStatisticsEntry entry = null; + if (ofEntry != null) { + entry = new DefaultTableStatisticsEntry(deviceId, + ofEntry.getTableId().getValue(), + ofEntry.getActiveCount(), + ofEntry.getLookupCount().getValue(), + ofEntry.getMatchedCount().getValue()); + } + + return entry; + + } } /** diff --git a/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/TableStatisticsCollector.java b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/TableStatisticsCollector.java new file mode 100644 index 0000000000..922a470a32 --- /dev/null +++ b/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/TableStatisticsCollector.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.flow.impl; + +import org.onlab.util.SharedExecutors; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFTableStatsRequest; +import org.slf4j.Logger; + +import java.util.Timer; +import java.util.TimerTask; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Collects Table statistics for the specified switch. + */ +class TableStatisticsCollector { + + private final Logger log = getLogger(getClass()); + + public static final int SECONDS = 1000; + + private final OpenFlowSwitch sw; + private Timer timer; + private TimerTask task; + + private int pollInterval; + + /** + * Creates a new table statistics collector for the given switch and poll frequency. + * + * @param timer timer to use for scheduling + * @param sw switch to pull + * @param pollInterval poll frequency in seconds + */ + TableStatisticsCollector(Timer timer, OpenFlowSwitch sw, int pollInterval) { + this.timer = timer; + this.sw = sw; + this.pollInterval = pollInterval; + } + + /** + * Adjusts poll frequency. + * + * @param pollInterval poll frequency in seconds + */ + synchronized void adjustPollInterval(int pollInterval) { + this.pollInterval = pollInterval; + task.cancel(); + task = new InternalTimerTask(); + timer.scheduleAtFixedRate(task, pollInterval * SECONDS, pollInterval * 1000); + } + + private class InternalTimerTask extends TimerTask { + @Override + public void run() { + if (sw.getRole() == RoleState.MASTER) { + log.trace("Collecting stats for {}", sw.getStringId()); + OFTableStatsRequest request = sw.factory().buildTableStatsRequest() + .build(); + sw.sendMsg(request); + } + } + } + + public synchronized void start() { + // Initially start polling quickly. Then drop down to configured value + log.debug("Starting Table Stats collection thread for {}", sw.getStringId()); + task = new InternalTimerTask(); + SharedExecutors.getTimer().scheduleAtFixedRate(task, 1 * SECONDS, + pollInterval * SECONDS); + } + + public synchronized void stop() { + log.debug("Stopping Table Stats collection thread for {}", sw.getStringId()); + task.cancel(); + task = null; + } + +} diff --git a/web/api/src/main/java/org/onosproject/rest/resources/StatisticsWebResource.java b/web/api/src/main/java/org/onosproject/rest/resources/StatisticsWebResource.java index 2ffa2295ab..c91cb6d093 100644 --- a/web/api/src/main/java/org/onosproject/rest/resources/StatisticsWebResource.java +++ b/web/api/src/main/java/org/onosproject/rest/resources/StatisticsWebResource.java @@ -21,6 +21,7 @@ import java.util.stream.StreamSupport; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; @@ -31,7 +32,12 @@ import javax.ws.rs.core.UriInfo; import org.onosproject.codec.JsonCodec; import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; import org.onosproject.net.Link; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TableStatisticsEntry; import org.onosproject.net.link.LinkService; import org.onosproject.net.statistic.Load; import org.onosproject.net.statistic.StatisticService; @@ -92,4 +98,59 @@ public class StatisticsWebResource extends AbstractWebResource { result.set("loads", loads); return ok(result).build(); } + + /** + * Get table statistics for all tables of all devices. + * + * @return JSON encoded array of table statistics + */ + @GET + @Path("flows/tables") + @Produces(MediaType.APPLICATION_JSON) + public Response getTableStatistics() { + final FlowRuleService service = get(FlowRuleService.class); + final Iterable devices = get(DeviceService.class).getDevices(); + final ObjectNode root = mapper().createObjectNode(); + final ArrayNode rootArrayNode = root.putArray("device-table-statistics"); + for (final Device device : devices) { + final ObjectNode deviceStatsNode = mapper().createObjectNode(); + deviceStatsNode.put("device", device.id().toString()); + final ArrayNode statisticsNode = deviceStatsNode.putArray("table-statistics"); + final Iterable tableStatsEntries = service.getFlowTableStatistics(device.id()); + if (tableStatsEntries != null) { + for (final TableStatisticsEntry entry : tableStatsEntries) { + statisticsNode.add(codec(TableStatisticsEntry.class).encode(entry, this)); + } + } + rootArrayNode.add(deviceStatsNode); + } + + return ok(root).build(); + } + + /** + * Get table statistics for all tables of a specified device. + * + * @param deviceId device ID + * @return JSON encoded array of table statistics + */ + @GET + @Path("flows/tables/{deviceId}") + @Produces(MediaType.APPLICATION_JSON) + public Response getTableStatisticsByDeviceId(@PathParam("deviceId") String deviceId) { + final FlowRuleService service = get(FlowRuleService.class); + final Iterable tableStatisticsEntries = + service.getFlowTableStatistics(DeviceId.deviceId(deviceId)); + final ObjectNode root = mapper().createObjectNode(); + final ArrayNode rootArrayNode = root.putArray("table-statistics"); + + final ObjectNode deviceStatsNode = mapper().createObjectNode(); + deviceStatsNode.put("device", deviceId); + final ArrayNode statisticsNode = deviceStatsNode.putArray("table-statistics"); + for (final TableStatisticsEntry entry : tableStatisticsEntries) { + statisticsNode.add(codec(TableStatisticsEntry.class).encode(entry, this)); + } + rootArrayNode.add(deviceStatsNode); + return ok(root).build(); + } }