Umesh Krishnaswamy | 345ee99 | 2012-12-13 20:29:48 -0800 | [diff] [blame] | 1 | /** |
| 2 | * Copyright 2011, Big Switch Networks, Inc. |
| 3 | * Originally created by David Erickson, Stanford University |
| 4 | * |
| 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 6 | * not use this file except in compliance with the License. You may obtain |
| 7 | * a copy of the License at |
| 8 | * |
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | * |
| 11 | * Unless required by applicable law or agreed to in writing, software |
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 14 | * License for the specific language governing permissions and limitations |
| 15 | * under the License. |
| 16 | **/ |
| 17 | |
| 18 | package net.floodlightcontroller.counter; |
| 19 | |
| 20 | import java.util.Date; |
| 21 | |
| 22 | import net.floodlightcontroller.counter.ICounter.DateSpan; |
| 23 | |
| 24 | |
| 25 | /** |
| 26 | * Implements a circular buffer to store the last x time-based counter values. This is pretty crumby |
| 27 | * implementation, basically wrapping everything with synchronized blocks, in order to ensure that threads |
| 28 | * which will be updating the series don't result in a thread which is reading the series getting stuck with |
| 29 | * a start date which does not correspond to the count values in getSeries. |
| 30 | * |
| 31 | * This could probably use a re-think... |
| 32 | * |
| 33 | * @author kyle |
| 34 | * |
| 35 | */ |
| 36 | public class CountBuffer { |
| 37 | protected long[] counterValues; |
| 38 | protected Date startDate; |
| 39 | protected DateSpan dateSpan; |
| 40 | protected int currentIndex; |
| 41 | protected int seriesLength; |
| 42 | |
| 43 | |
| 44 | public CountBuffer(Date startDate, DateSpan dateSpan, int seriesLength) { |
| 45 | this.seriesLength = seriesLength; |
| 46 | this.counterValues = new long[seriesLength]; |
| 47 | this.dateSpan = dateSpan; |
| 48 | |
| 49 | this.startDate = startDate; |
| 50 | this.currentIndex = 0; |
| 51 | } |
| 52 | |
| 53 | /** |
| 54 | * Increment the count associated with Date d, forgetting some of the older count values if necessary to ensure |
| 55 | * that the total span of time covered by this series corresponds to DateSpan * seriesLength (circular buffer). |
| 56 | * |
| 57 | * Note - fails silently if the Date falls prior to the start of the tracked count values. |
| 58 | * |
| 59 | * Note - this should be a reasonably fast method, though it will have to block if there is another thread reading the |
| 60 | * series at the same time. |
| 61 | * |
| 62 | * @param d |
| 63 | * @param delta |
| 64 | */ |
| 65 | public synchronized void increment(Date d, long delta) { |
| 66 | |
| 67 | long dsMillis = CountSeries.dateSpanToMilliseconds(this.dateSpan); |
| 68 | Date endDate = new Date(startDate.getTime() + seriesLength * dsMillis - 1); |
| 69 | |
| 70 | if(d.getTime() < startDate.getTime()) { |
| 71 | return; //silently fail rather than insert a count at a time older than the history buffer we're keeping |
| 72 | } |
| 73 | else if (d.getTime() >= startDate.getTime() && d.getTime() <= endDate.getTime()) { |
| 74 | int index = (int) (( d.getTime() - startDate.getTime() ) / dsMillis); // java rounds down on long/long |
| 75 | int modIndex = (index + currentIndex) % seriesLength; |
| 76 | long currentValue = counterValues[modIndex]; |
| 77 | counterValues[modIndex] = currentValue + delta; |
| 78 | } |
| 79 | else if (d.getTime() > endDate.getTime()) { |
| 80 | //Initialize new buckets |
| 81 | int newBuckets = (int)((d.getTime() - endDate.getTime()) / dsMillis) + 1; // java rounds down on long/long |
| 82 | for(int i = 0; i < newBuckets; i++) { |
| 83 | int modIndex = (i + currentIndex) % seriesLength; |
| 84 | counterValues[modIndex] = 0; |
| 85 | } |
| 86 | //Update internal vars |
| 87 | this.startDate = new Date(startDate.getTime() + dsMillis * newBuckets); |
| 88 | this.currentIndex = (currentIndex + newBuckets) % this.seriesLength; |
| 89 | |
| 90 | //Call again (date should be in the range this time) |
| 91 | this.increment(d, delta); |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * Relatively slow method, expected to be called primarily from UI rather than from in-packet-path. |
| 97 | * |
| 98 | * @return the count values associated with each time interval starting with startDate and demarc'ed by dateSpan |
| 99 | */ |
| 100 | public long[] getSeries() { //synchronized here should lock on 'this', implying that it shares the lock with increment |
| 101 | long[] ret = new long[this.seriesLength]; |
| 102 | for(int i = 0; i < this.seriesLength; i++) { |
| 103 | int modIndex = (currentIndex + i) % this.seriesLength; |
| 104 | ret[i] = this.counterValues[modIndex]; |
| 105 | } |
| 106 | return ret; |
| 107 | } |
| 108 | |
| 109 | |
| 110 | /** |
| 111 | * Returns an immutable count series that represents a snapshot of this |
| 112 | * series at a specific moment in time. |
| 113 | * @return |
| 114 | */ |
| 115 | public synchronized CountSeries snapshot() { |
| 116 | long[] cvs = new long[this.seriesLength]; |
| 117 | for(int i = 0; i < this.seriesLength; i++) { |
| 118 | int modIndex = (this.currentIndex + i) % this.seriesLength; |
| 119 | cvs[i] = this.counterValues[modIndex]; |
| 120 | } |
| 121 | |
| 122 | return new CountSeries(this.startDate, this.dateSpan, cvs); |
| 123 | } |
| 124 | |
| 125 | } |