blob: 7ba03b824a48ab1471586634d8073237ec9b2963 [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 */
alshabiba43aa252014-10-21 21:36:41 -070016package org.onlab.onos.net.statistic.impl;
17
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;
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -080028import org.onlab.onos.core.ApplicationId;
29import org.onlab.onos.core.GroupId;
alshabiba43aa252014-10-21 21:36:41 -070030import org.onlab.onos.net.ConnectPoint;
31import org.onlab.onos.net.Link;
32import org.onlab.onos.net.Path;
alshabib2374fc92014-10-22 11:03:23 -070033
alshabib3d643ec2014-10-22 18:33:00 -070034import org.onlab.onos.net.flow.FlowEntry;
alshabiba43aa252014-10-21 21:36:41 -070035import org.onlab.onos.net.flow.FlowRule;
36import org.onlab.onos.net.flow.FlowRuleEvent;
37import org.onlab.onos.net.flow.FlowRuleListener;
38import org.onlab.onos.net.flow.FlowRuleService;
alshabib3d643ec2014-10-22 18:33:00 -070039import org.onlab.onos.net.statistic.DefaultLoad;
alshabiba43aa252014-10-21 21:36:41 -070040import org.onlab.onos.net.statistic.Load;
41import org.onlab.onos.net.statistic.StatisticService;
42import org.onlab.onos.net.statistic.StatisticStore;
43import org.slf4j.Logger;
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -080044
45import java.util.Collections;
46import java.util.Objects;
47import java.util.Optional;
alshabib3d643ec2014-10-22 18:33:00 -070048import java.util.Set;
49
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -080050import static com.google.common.base.Preconditions.checkNotNull;
alshabiba43aa252014-10-21 21:36:41 -070051import static org.slf4j.LoggerFactory.getLogger;
52
53/**
54 * Provides an implementation of the Statistic Service.
55 */
56@Component(immediate = true)
57@Service
58public class StatisticManager implements StatisticService {
59
60 private final Logger log = getLogger(getClass());
61
62 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
63 protected FlowRuleService flowRuleService;
64
65 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
66 protected StatisticStore statisticStore;
67
alshabib2374fc92014-10-22 11:03:23 -070068
alshabiba43aa252014-10-21 21:36:41 -070069 private final InternalFlowRuleListener listener = new InternalFlowRuleListener();
70
71 @Activate
72 public void activate() {
73 flowRuleService.addListener(listener);
74 log.info("Started");
alshabib2374fc92014-10-22 11:03:23 -070075
alshabiba43aa252014-10-21 21:36:41 -070076 }
77
78 @Deactivate
79 public void deactivate() {
80 flowRuleService.removeListener(listener);
81 log.info("Stopped");
82 }
83
84 @Override
85 public Load load(Link link) {
alshabib3d643ec2014-10-22 18:33:00 -070086 return load(link.src());
alshabiba43aa252014-10-21 21:36:41 -070087 }
88
89 @Override
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -080090 public Load load(Link link, ApplicationId appId, Optional<GroupId> groupId) {
91 Statistics stats = getStatistics(link.src());
92 if (!stats.isValid()) {
93 return new DefaultLoad();
94 }
95
96 ImmutableSet<FlowEntry> current = FluentIterable.from(stats.current())
97 .filter(hasApplicationId(appId))
98 .filter(hasGroupId(groupId))
99 .toSet();
100 ImmutableSet<FlowEntry> previous = FluentIterable.from(stats.previous())
101 .filter(hasApplicationId(appId))
102 .filter(hasGroupId(groupId))
103 .toSet();
104
105 return new DefaultLoad(aggregate(current), aggregate(previous));
106 }
107
108 @Override
alshabiba43aa252014-10-21 21:36:41 -0700109 public Load load(ConnectPoint connectPoint) {
alshabib3d643ec2014-10-22 18:33:00 -0700110 return loadInternal(connectPoint);
alshabiba43aa252014-10-21 21:36:41 -0700111 }
112
113 @Override
114 public Link max(Path path) {
alshabib46122d82014-10-23 09:05:31 -0700115 if (path.links().isEmpty()) {
116 return null;
117 }
118 Load maxLoad = new DefaultLoad();
119 Link maxLink = null;
120 for (Link link : path.links()) {
121 Load load = loadInternal(link.src());
122 if (load.rate() > maxLoad.rate()) {
123 maxLoad = load;
124 maxLink = link;
125 }
126 }
127 return maxLink;
alshabiba43aa252014-10-21 21:36:41 -0700128 }
129
130 @Override
131 public Link min(Path path) {
alshabib46122d82014-10-23 09:05:31 -0700132 if (path.links().isEmpty()) {
133 return null;
134 }
135 Load minLoad = new DefaultLoad();
136 Link minLink = null;
137 for (Link link : path.links()) {
138 Load load = loadInternal(link.src());
139 if (load.rate() < minLoad.rate()) {
140 minLoad = load;
141 minLink = link;
142 }
143 }
144 return minLink;
alshabiba43aa252014-10-21 21:36:41 -0700145 }
146
147 @Override
148 public FlowRule highestHitter(ConnectPoint connectPoint) {
alshabib46122d82014-10-23 09:05:31 -0700149 Set<FlowEntry> hitters = statisticStore.getCurrentStatistic(connectPoint);
150 if (hitters.isEmpty()) {
151 return null;
152 }
153
154 FlowEntry max = hitters.iterator().next();
155 for (FlowEntry entry : hitters) {
156 if (entry.bytes() > max.bytes()) {
157 max = entry;
158 }
159 }
160 return max;
alshabiba43aa252014-10-21 21:36:41 -0700161 }
162
alshabib3d643ec2014-10-22 18:33:00 -0700163 private Load loadInternal(ConnectPoint connectPoint) {
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800164 Statistics stats = getStatistics(connectPoint);
165 if (!stats.isValid()) {
166 return new DefaultLoad();
167 }
168
169 return new DefaultLoad(aggregate(stats.current), aggregate(stats.previous));
170 }
171
172 /**
173 * Returns statistics of the specified port.
174 *
175 * @param connectPoint port to query
176 * @return statistics
177 */
178 private Statistics getStatistics(ConnectPoint connectPoint) {
alshabib3d643ec2014-10-22 18:33:00 -0700179 Set<FlowEntry> current;
180 Set<FlowEntry> previous;
181 synchronized (statisticStore) {
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800182 current = getCurrentStatistic(connectPoint);
183 previous = getPreviousStatistic(connectPoint);
alshabib3d643ec2014-10-22 18:33:00 -0700184 }
alshabib3d643ec2014-10-22 18:33:00 -0700185
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800186 return new Statistics(current, previous);
alshabib3d643ec2014-10-22 18:33:00 -0700187 }
188
189 /**
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800190 * Returns the current statistic of the specified port.
191
192 * @param connectPoint port to query
193 * @return set of flow entries
194 */
195 private Set<FlowEntry> getCurrentStatistic(ConnectPoint connectPoint) {
196 Set<FlowEntry> stats = statisticStore.getCurrentStatistic(connectPoint);
197 if (stats == null) {
198 return Collections.emptySet();
199 } else {
200 return stats;
201 }
202 }
203
204 /**
205 * Returns the previous statistic of the specified port.
206 *
207 * @param connectPoint port to query
208 * @return set of flow entries
209 */
210 private Set<FlowEntry> getPreviousStatistic(ConnectPoint connectPoint) {
alshabib507ac372014-11-28 18:12:07 -0500211 Set<FlowEntry> stats = statisticStore.getPreviousStatistic(connectPoint);
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800212 if (stats == null) {
213 return Collections.emptySet();
214 } else {
215 return stats;
216 }
217 }
218
219 // TODO: make aggregation function generic by passing a function
220 // (applying Java 8 Stream API?)
221 /**
alshabib3d643ec2014-10-22 18:33:00 -0700222 * Aggregates a set of values.
223 * @param values the values to aggregate
224 * @return a long value
225 */
226 private long aggregate(Set<FlowEntry> values) {
227 long sum = 0;
228 for (FlowEntry f : values) {
229 sum += f.bytes();
230 }
231 return sum;
232 }
233
alshabiba43aa252014-10-21 21:36:41 -0700234 /**
235 * Internal flow rule event listener.
236 */
237 private class InternalFlowRuleListener implements FlowRuleListener {
238
239 @Override
240 public void event(FlowRuleEvent event) {
alshabib3d643ec2014-10-22 18:33:00 -0700241 FlowRule rule = event.subject();
242 switch (event.type()) {
243 case RULE_ADDED:
244 case RULE_UPDATED:
245 if (rule instanceof FlowEntry) {
246 statisticStore.addOrUpdateStatistic((FlowEntry) rule);
alshabib3d643ec2014-10-22 18:33:00 -0700247 }
248 break;
249 case RULE_ADD_REQUESTED:
alshabib3d643ec2014-10-22 18:33:00 -0700250 statisticStore.prepareForStatistics(rule);
251 break;
252 case RULE_REMOVE_REQUESTED:
alshabib3d643ec2014-10-22 18:33:00 -0700253 statisticStore.removeFromStatistics(rule);
254 break;
255 case RULE_REMOVED:
256 break;
257 default:
258 log.warn("Unknown flow rule event {}", event);
259 }
alshabiba43aa252014-10-21 21:36:41 -0700260 }
261 }
262
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800263 /**
264 * Internal data class holding two set of flow entries.
265 */
266 private static class Statistics {
267 private final ImmutableSet<FlowEntry> current;
268 private final ImmutableSet<FlowEntry> previous;
alshabiba43aa252014-10-21 21:36:41 -0700269
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800270 public Statistics(Set<FlowEntry> current, Set<FlowEntry> previous) {
271 this.current = ImmutableSet.copyOf(checkNotNull(current));
272 this.previous = ImmutableSet.copyOf(checkNotNull(previous));
273 }
274
275 /**
276 * Returns flow entries as the current value.
277 *
278 * @return flow entries as the current value
279 */
280 public ImmutableSet<FlowEntry> current() {
281 return current;
282 }
283
284 /**
285 * Returns flow entries as the previous value.
286 *
287 * @return flow entries as the previous value
288 */
289 public ImmutableSet<FlowEntry> previous() {
290 return previous;
291 }
292
293 /**
294 * Validates values are not empty.
295 *
296 * @return false if either of the sets is empty. Otherwise, true.
297 */
298 public boolean isValid() {
299 return !(current.isEmpty() || previous.isEmpty());
300 }
301
302 @Override
303 public int hashCode() {
304 return Objects.hash(current, previous);
305 }
306
307 @Override
308 public boolean equals(Object obj) {
309 if (this == obj) {
310 return true;
311 }
312 if (!(obj instanceof Statistics)) {
313 return false;
314 }
315 final Statistics other = (Statistics) obj;
316 return Objects.equals(this.current, other.current) && Objects.equals(this.previous, other.previous);
317 }
318
319 @Override
320 public String toString() {
321 return MoreObjects.toStringHelper(this)
322 .add("current", current)
323 .add("previous", previous)
324 .toString();
325 }
326 }
327
328 /**
329 * Creates a predicate that checks the application ID of a flow entry is the same as
330 * the specified application ID.
331 *
332 * @param appId application ID to be checked
333 * @return predicate
334 */
335 private static Predicate<FlowEntry> hasApplicationId(ApplicationId appId) {
336 return new Predicate<FlowEntry>() {
337 @Override
338 public boolean apply(FlowEntry flowEntry) {
339 return flowEntry.appId() == appId.id();
340 }
341 };
342 }
343
344 /**
345 * Create a predicate that checks the group ID of a flow entry is the same as
346 * the specified group ID.
347 *
348 * @param groupId group ID to be checked
349 * @return predicate
350 */
351 private static Predicate<FlowEntry> hasGroupId(Optional<GroupId> groupId) {
352 return new Predicate<FlowEntry>() {
353 @Override
354 public boolean apply(FlowEntry flowEntry) {
355 if (!groupId.isPresent()) {
356 return false;
357 }
358 // FIXME: The left hand type and right hand type don't match
359 // FlowEntry.groupId() still returns a short value, not int.
Sho SHIMIZU75a5bd92014-11-25 11:22:56 -0800360 return flowEntry.groupId().equals(groupId.get());
Sho SHIMIZUe3cc0b92014-11-20 16:18:29 -0800361 }
362 };
363 }
alshabiba43aa252014-10-21 21:36:41 -0700364}