/* * Copyright 2014-present 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.statistic.impl; import com.google.common.base.MoreObjects; import com.google.common.base.Predicate; import com.google.common.collect.FluentIterable; 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; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.Service; import org.onosproject.core.ApplicationId; import org.onosproject.core.GroupId; import org.onosproject.net.ConnectPoint; import org.onosproject.net.Link; import org.onosproject.net.Path; import org.onosproject.net.flow.FlowEntry; import org.onosproject.net.flow.FlowRule; import org.onosproject.net.flow.FlowRuleEvent; import org.onosproject.net.flow.FlowRuleListener; import org.onosproject.net.flow.FlowRuleService; import org.onosproject.net.statistic.DefaultLoad; import org.onosproject.net.statistic.Load; import org.onosproject.net.statistic.StatisticService; import org.onosproject.net.statistic.StatisticStore; import org.slf4j.Logger; import java.util.Collections; import java.util.Objects; import java.util.Optional; import java.util.Set; import static com.google.common.base.Preconditions.checkNotNull; import static org.slf4j.LoggerFactory.getLogger; import static org.onosproject.security.AppGuard.checkPermission; import static org.onosproject.security.AppPermission.Type.*; /** * Provides an implementation of the Statistic Service. */ @Component(immediate = true) @Service public class StatisticManager implements StatisticService { private final Logger log = getLogger(getClass()); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected FlowRuleService flowRuleService; @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected StatisticStore statisticStore; private final InternalFlowRuleListener listener = new InternalFlowRuleListener(); @Activate public void activate() { flowRuleService.addListener(listener); log.info("Started"); } @Deactivate public void deactivate() { flowRuleService.removeListener(listener); log.info("Stopped"); } @Override public Load load(Link link) { checkPermission(STATISTIC_READ); return load(link.src()); } @Override public Load load(Link link, ApplicationId appId, Optional<GroupId> groupId) { checkPermission(STATISTIC_READ); Statistics stats = getStatistics(link.src()); if (!stats.isValid()) { return new DefaultLoad(); } ImmutableSet<FlowEntry> current = FluentIterable.from(stats.current()) .filter(hasApplicationId(appId)) .filter(hasGroupId(groupId)) .toSet(); ImmutableSet<FlowEntry> previous = FluentIterable.from(stats.previous()) .filter(hasApplicationId(appId)) .filter(hasGroupId(groupId)) .toSet(); return new DefaultLoad(aggregate(current), aggregate(previous)); } @Override public Load load(ConnectPoint connectPoint) { checkPermission(STATISTIC_READ); return loadInternal(connectPoint); } @Override public Link max(Path path) { checkPermission(STATISTIC_READ); if (path.links().isEmpty()) { return null; } Load maxLoad = new DefaultLoad(); Link maxLink = null; for (Link link : path.links()) { Load load = loadInternal(link.src()); if (load.rate() > maxLoad.rate()) { maxLoad = load; maxLink = link; } } return maxLink; } @Override public Link min(Path path) { checkPermission(STATISTIC_READ); if (path.links().isEmpty()) { return null; } Load minLoad = new DefaultLoad(); Link minLink = null; for (Link link : path.links()) { Load load = loadInternal(link.src()); if (load.rate() < minLoad.rate()) { minLoad = load; minLink = link; } } return minLink; } @Override public FlowRule highestHitter(ConnectPoint connectPoint) { checkPermission(STATISTIC_READ); Set<FlowEntry> hitters = statisticStore.getCurrentStatistic(connectPoint); if (hitters.isEmpty()) { return null; } FlowEntry max = hitters.iterator().next(); for (FlowEntry entry : hitters) { if (entry.bytes() > max.bytes()) { max = entry; } } return max; } private Load loadInternal(ConnectPoint connectPoint) { Statistics stats = getStatistics(connectPoint); if (!stats.isValid()) { return new DefaultLoad(); } return new DefaultLoad(aggregate(stats.current), aggregate(stats.previous)); } /** * Returns statistics of the specified port. * * @param connectPoint port to query * @return statistics */ private Statistics getStatistics(ConnectPoint connectPoint) { Set<FlowEntry> current; Set<FlowEntry> previous; synchronized (statisticStore) { current = getCurrentStatistic(connectPoint); previous = getPreviousStatistic(connectPoint); } return new Statistics(current, previous); } /** * Returns the current statistic of the specified port. * @param connectPoint port to query * @return set of flow entries */ private Set<FlowEntry> getCurrentStatistic(ConnectPoint connectPoint) { Set<FlowEntry> stats = statisticStore.getCurrentStatistic(connectPoint); if (stats == null) { return Collections.emptySet(); } else { return stats; } } /** * Returns the previous statistic of the specified port. * * @param connectPoint port to query * @return set of flow entries */ private Set<FlowEntry> getPreviousStatistic(ConnectPoint connectPoint) { Set<FlowEntry> stats = statisticStore.getPreviousStatistic(connectPoint); if (stats == null) { return Collections.emptySet(); } else { return stats; } } // TODO: make aggregation function generic by passing a function // (applying Java 8 Stream API?) /** * Aggregates a set of values. * @param values the values to aggregate * @return a long value */ private long aggregate(Set<FlowEntry> values) { long sum = 0; for (FlowEntry f : values) { sum += f.bytes(); } return sum; } /** * Internal flow rule event listener. */ private class InternalFlowRuleListener implements FlowRuleListener { @Override public void event(FlowRuleEvent event) { FlowRule rule = event.subject(); switch (event.type()) { case RULE_ADDED: case RULE_UPDATED: if (rule instanceof FlowEntry) { statisticStore.addOrUpdateStatistic((FlowEntry) rule); } break; case RULE_ADD_REQUESTED: statisticStore.prepareForStatistics(rule); break; case RULE_REMOVE_REQUESTED: statisticStore.removeFromStatistics(rule); break; case RULE_REMOVED: break; default: log.warn("Unknown flow rule event {}", event); } } } /** * Internal data class holding two set of flow entries. */ private static class Statistics { private final ImmutableSet<FlowEntry> current; private final ImmutableSet<FlowEntry> previous; public Statistics(Set<FlowEntry> current, Set<FlowEntry> previous) { this.current = ImmutableSet.copyOf(checkNotNull(current)); this.previous = ImmutableSet.copyOf(checkNotNull(previous)); } /** * Returns flow entries as the current value. * * @return flow entries as the current value */ public ImmutableSet<FlowEntry> current() { return current; } /** * Returns flow entries as the previous value. * * @return flow entries as the previous value */ public ImmutableSet<FlowEntry> previous() { return previous; } /** * Validates values are not empty. * * @return false if either of the sets is empty. Otherwise, true. */ public boolean isValid() { return !(current.isEmpty() || previous.isEmpty()); } @Override public int hashCode() { return Objects.hash(current, previous); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Statistics)) { return false; } final Statistics other = (Statistics) obj; return Objects.equals(this.current, other.current) && Objects.equals(this.previous, other.previous); } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("current", current) .add("previous", previous) .toString(); } } /** * Creates a predicate that checks the application ID of a flow entry is the same as * the specified application ID. * * @param appId application ID to be checked * @return predicate */ private static Predicate<FlowEntry> hasApplicationId(ApplicationId appId) { return flowEntry -> flowEntry.appId() == appId.id(); } /** * Create a predicate that checks the group ID of a flow entry is the same as * the specified group ID. * * @param groupId group ID to be checked * @return predicate */ private static Predicate<FlowEntry> hasGroupId(Optional<GroupId> groupId) { return flowEntry -> { if (!groupId.isPresent()) { return false; } // FIXME: The left hand type and right hand type don't match // FlowEntry.groupId() still returns a short value, not int. return flowEntry.groupId().equals(groupId.get()); }; } }