blob: a07f196ded9ebc654b51c41a79b7e0bf00a11c71 [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
2 * Copyright 2014 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.net.statistic.impl;
alshabiba43aa252014-10-21 21:36:41 -070017
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -080018import com.google.common.base.MoreObjects;
19import com.google.common.base.Predicate;
20import com.google.common.collect.FluentIterable;
21import com.google.common.collect.ImmutableSet;
alshabiba43aa252014-10-21 21:36:41 -070022import org.apache.felix.scr.annotations.Activate;
23import org.apache.felix.scr.annotations.Component;
24import org.apache.felix.scr.annotations.Deactivate;
25import org.apache.felix.scr.annotations.Reference;
26import org.apache.felix.scr.annotations.ReferenceCardinality;
27import org.apache.felix.scr.annotations.Service;
Brian O'Connorabafb502014-12-02 22:26:20 -080028import org.onosproject.core.ApplicationId;
29import org.onosproject.core.GroupId;
Changhoon Yoon541ef712015-05-23 17:18:34 +090030import org.onosproject.core.Permission;
Brian O'Connorabafb502014-12-02 22:26:20 -080031import org.onosproject.net.ConnectPoint;
32import org.onosproject.net.Link;
33import org.onosproject.net.Path;
alshabib2374fc92014-10-22 11:03:23 -070034
Brian O'Connorabafb502014-12-02 22:26:20 -080035import org.onosproject.net.flow.FlowEntry;
36import org.onosproject.net.flow.FlowRule;
37import org.onosproject.net.flow.FlowRuleEvent;
38import org.onosproject.net.flow.FlowRuleListener;
39import org.onosproject.net.flow.FlowRuleService;
40import org.onosproject.net.statistic.DefaultLoad;
41import org.onosproject.net.statistic.Load;
42import org.onosproject.net.statistic.StatisticService;
43import org.onosproject.net.statistic.StatisticStore;
alshabiba43aa252014-10-21 21:36:41 -070044import org.slf4j.Logger;
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -080045
46import java.util.Collections;
47import java.util.Objects;
48import java.util.Optional;
alshabib3d643ec2014-10-22 18:33:00 -070049import java.util.Set;
50
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -080051import static com.google.common.base.Preconditions.checkNotNull;
alshabiba43aa252014-10-21 21:36:41 -070052import static org.slf4j.LoggerFactory.getLogger;
Changhoon Yoon541ef712015-05-23 17:18:34 +090053import static org.onosproject.security.AppGuard.checkPermission;
54
alshabiba43aa252014-10-21 21:36:41 -070055
56/**
57 * Provides an implementation of the Statistic Service.
58 */
59@Component(immediate = true)
60@Service
61public class StatisticManager implements StatisticService {
62
63 private final Logger log = getLogger(getClass());
64
65 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
66 protected FlowRuleService flowRuleService;
67
68 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
69 protected StatisticStore statisticStore;
70
alshabib2374fc92014-10-22 11:03:23 -070071
alshabiba43aa252014-10-21 21:36:41 -070072 private final InternalFlowRuleListener listener = new InternalFlowRuleListener();
73
74 @Activate
75 public void activate() {
76 flowRuleService.addListener(listener);
77 log.info("Started");
alshabib2374fc92014-10-22 11:03:23 -070078
alshabiba43aa252014-10-21 21:36:41 -070079 }
80
81 @Deactivate
82 public void deactivate() {
83 flowRuleService.removeListener(listener);
84 log.info("Stopped");
85 }
86
87 @Override
88 public Load load(Link link) {
Changhoon Yoon541ef712015-05-23 17:18:34 +090089 checkPermission(Permission.STATISTIC_READ);
90
91 return load(link.src());
alshabiba43aa252014-10-21 21:36:41 -070092 }
93
94 @Override
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -080095 public Load load(Link link, ApplicationId appId, Optional<GroupId> groupId) {
Changhoon Yoon541ef712015-05-23 17:18:34 +090096 checkPermission(Permission.STATISTIC_READ);
97
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -080098 Statistics stats = getStatistics(link.src());
99 if (!stats.isValid()) {
100 return new DefaultLoad();
101 }
102
103 ImmutableSet<FlowEntry> current = FluentIterable.from(stats.current())
104 .filter(hasApplicationId(appId))
105 .filter(hasGroupId(groupId))
106 .toSet();
107 ImmutableSet<FlowEntry> previous = FluentIterable.from(stats.previous())
108 .filter(hasApplicationId(appId))
109 .filter(hasGroupId(groupId))
110 .toSet();
111
112 return new DefaultLoad(aggregate(current), aggregate(previous));
113 }
114
115 @Override
alshabiba43aa252014-10-21 21:36:41 -0700116 public Load load(ConnectPoint connectPoint) {
Changhoon Yoon541ef712015-05-23 17:18:34 +0900117 checkPermission(Permission.STATISTIC_READ);
118
alshabib3d643ec2014-10-22 18:33:00 -0700119 return loadInternal(connectPoint);
alshabiba43aa252014-10-21 21:36:41 -0700120 }
121
122 @Override
123 public Link max(Path path) {
Changhoon Yoon541ef712015-05-23 17:18:34 +0900124 checkPermission(Permission.STATISTIC_READ);
125
alshabib46122d82014-10-23 09:05:31 -0700126 if (path.links().isEmpty()) {
127 return null;
128 }
129 Load maxLoad = new DefaultLoad();
130 Link maxLink = null;
131 for (Link link : path.links()) {
132 Load load = loadInternal(link.src());
133 if (load.rate() > maxLoad.rate()) {
134 maxLoad = load;
135 maxLink = link;
136 }
137 }
138 return maxLink;
alshabiba43aa252014-10-21 21:36:41 -0700139 }
140
141 @Override
142 public Link min(Path path) {
Changhoon Yoon541ef712015-05-23 17:18:34 +0900143 checkPermission(Permission.STATISTIC_READ);
144
alshabib46122d82014-10-23 09:05:31 -0700145 if (path.links().isEmpty()) {
146 return null;
147 }
148 Load minLoad = new DefaultLoad();
149 Link minLink = null;
150 for (Link link : path.links()) {
151 Load load = loadInternal(link.src());
152 if (load.rate() < minLoad.rate()) {
153 minLoad = load;
154 minLink = link;
155 }
156 }
157 return minLink;
alshabiba43aa252014-10-21 21:36:41 -0700158 }
159
160 @Override
161 public FlowRule highestHitter(ConnectPoint connectPoint) {
Changhoon Yoon541ef712015-05-23 17:18:34 +0900162 checkPermission(Permission.STATISTIC_READ);
163
alshabib46122d82014-10-23 09:05:31 -0700164 Set<FlowEntry> hitters = statisticStore.getCurrentStatistic(connectPoint);
165 if (hitters.isEmpty()) {
166 return null;
167 }
168
169 FlowEntry max = hitters.iterator().next();
170 for (FlowEntry entry : hitters) {
171 if (entry.bytes() > max.bytes()) {
172 max = entry;
173 }
174 }
175 return max;
alshabiba43aa252014-10-21 21:36:41 -0700176 }
177
alshabib3d643ec2014-10-22 18:33:00 -0700178 private Load loadInternal(ConnectPoint connectPoint) {
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800179 Statistics stats = getStatistics(connectPoint);
180 if (!stats.isValid()) {
181 return new DefaultLoad();
182 }
183
184 return new DefaultLoad(aggregate(stats.current), aggregate(stats.previous));
185 }
186
187 /**
188 * Returns statistics of the specified port.
189 *
190 * @param connectPoint port to query
191 * @return statistics
192 */
193 private Statistics getStatistics(ConnectPoint connectPoint) {
alshabib3d643ec2014-10-22 18:33:00 -0700194 Set<FlowEntry> current;
195 Set<FlowEntry> previous;
196 synchronized (statisticStore) {
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800197 current = getCurrentStatistic(connectPoint);
198 previous = getPreviousStatistic(connectPoint);
alshabib3d643ec2014-10-22 18:33:00 -0700199 }
alshabib3d643ec2014-10-22 18:33:00 -0700200
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800201 return new Statistics(current, previous);
alshabib3d643ec2014-10-22 18:33:00 -0700202 }
203
204 /**
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800205 * Returns the current statistic of the specified port.
206
207 * @param connectPoint port to query
208 * @return set of flow entries
209 */
210 private Set<FlowEntry> getCurrentStatistic(ConnectPoint connectPoint) {
211 Set<FlowEntry> stats = statisticStore.getCurrentStatistic(connectPoint);
212 if (stats == null) {
213 return Collections.emptySet();
214 } else {
215 return stats;
216 }
217 }
218
219 /**
220 * Returns the previous statistic of the specified port.
221 *
222 * @param connectPoint port to query
223 * @return set of flow entries
224 */
225 private Set<FlowEntry> getPreviousStatistic(ConnectPoint connectPoint) {
alshabib507ac372014-11-28 18:12:07 -0500226 Set<FlowEntry> stats = statisticStore.getPreviousStatistic(connectPoint);
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800227 if (stats == null) {
228 return Collections.emptySet();
229 } else {
230 return stats;
231 }
232 }
233
234 // TODO: make aggregation function generic by passing a function
235 // (applying Java 8 Stream API?)
236 /**
alshabib3d643ec2014-10-22 18:33:00 -0700237 * Aggregates a set of values.
238 * @param values the values to aggregate
239 * @return a long value
240 */
241 private long aggregate(Set<FlowEntry> values) {
242 long sum = 0;
243 for (FlowEntry f : values) {
244 sum += f.bytes();
245 }
246 return sum;
247 }
248
alshabiba43aa252014-10-21 21:36:41 -0700249 /**
250 * Internal flow rule event listener.
251 */
252 private class InternalFlowRuleListener implements FlowRuleListener {
253
254 @Override
255 public void event(FlowRuleEvent event) {
alshabib3d643ec2014-10-22 18:33:00 -0700256 FlowRule rule = event.subject();
257 switch (event.type()) {
258 case RULE_ADDED:
259 case RULE_UPDATED:
260 if (rule instanceof FlowEntry) {
261 statisticStore.addOrUpdateStatistic((FlowEntry) rule);
alshabib3d643ec2014-10-22 18:33:00 -0700262 }
263 break;
264 case RULE_ADD_REQUESTED:
alshabib3d643ec2014-10-22 18:33:00 -0700265 statisticStore.prepareForStatistics(rule);
266 break;
267 case RULE_REMOVE_REQUESTED:
alshabib3d643ec2014-10-22 18:33:00 -0700268 statisticStore.removeFromStatistics(rule);
269 break;
270 case RULE_REMOVED:
271 break;
272 default:
273 log.warn("Unknown flow rule event {}", event);
274 }
alshabiba43aa252014-10-21 21:36:41 -0700275 }
276 }
277
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800278 /**
279 * Internal data class holding two set of flow entries.
280 */
281 private static class Statistics {
282 private final ImmutableSet<FlowEntry> current;
283 private final ImmutableSet<FlowEntry> previous;
alshabiba43aa252014-10-21 21:36:41 -0700284
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800285 public Statistics(Set<FlowEntry> current, Set<FlowEntry> previous) {
286 this.current = ImmutableSet.copyOf(checkNotNull(current));
287 this.previous = ImmutableSet.copyOf(checkNotNull(previous));
288 }
289
290 /**
291 * Returns flow entries as the current value.
292 *
293 * @return flow entries as the current value
294 */
295 public ImmutableSet<FlowEntry> current() {
296 return current;
297 }
298
299 /**
300 * Returns flow entries as the previous value.
301 *
302 * @return flow entries as the previous value
303 */
304 public ImmutableSet<FlowEntry> previous() {
305 return previous;
306 }
307
308 /**
309 * Validates values are not empty.
310 *
311 * @return false if either of the sets is empty. Otherwise, true.
312 */
313 public boolean isValid() {
314 return !(current.isEmpty() || previous.isEmpty());
315 }
316
317 @Override
318 public int hashCode() {
319 return Objects.hash(current, previous);
320 }
321
322 @Override
323 public boolean equals(Object obj) {
324 if (this == obj) {
325 return true;
326 }
327 if (!(obj instanceof Statistics)) {
328 return false;
329 }
330 final Statistics other = (Statistics) obj;
331 return Objects.equals(this.current, other.current) && Objects.equals(this.previous, other.previous);
332 }
333
334 @Override
335 public String toString() {
336 return MoreObjects.toStringHelper(this)
337 .add("current", current)
338 .add("previous", previous)
339 .toString();
340 }
341 }
342
343 /**
344 * Creates a predicate that checks the application ID of a flow entry is the same as
345 * the specified application ID.
346 *
347 * @param appId application ID to be checked
348 * @return predicate
349 */
350 private static Predicate<FlowEntry> hasApplicationId(ApplicationId appId) {
351 return new Predicate<FlowEntry>() {
352 @Override
353 public boolean apply(FlowEntry flowEntry) {
354 return flowEntry.appId() == appId.id();
355 }
356 };
357 }
358
359 /**
360 * Create a predicate that checks the group ID of a flow entry is the same as
361 * the specified group ID.
362 *
363 * @param groupId group ID to be checked
364 * @return predicate
365 */
366 private static Predicate<FlowEntry> hasGroupId(Optional<GroupId> groupId) {
367 return new Predicate<FlowEntry>() {
368 @Override
369 public boolean apply(FlowEntry flowEntry) {
370 if (!groupId.isPresent()) {
371 return false;
372 }
373 // FIXME: The left hand type and right hand type don't match
374 // FlowEntry.groupId() still returns a short value, not int.
Sho SHIMIZU75a5bd92014-11-25 11:22:56 -0800375 return flowEntry.groupId().equals(groupId.get());
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800376 }
377 };
378 }
alshabiba43aa252014-10-21 21:36:41 -0700379}