/* * Copyright (c) 2011,2013 Big Switch Networks, Inc. * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html * * 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. * * This file incorporates work covered by the following copyright and * permission notice: * * Originally created by David Erickson, Stanford University * * 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.sdnplatform.counter; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.sdnplatform.counter.CounterValue.CounterType; /** * This module needs to be updated with CounterValue. * * This is a crumby attempt at a highly concurrent implementation of the Counter interface. * * (Help! Help! Someone please re-write me! This will almost certainly break at high loads.) * * The gist is that this class, ConcurrentCounter, keeps an internal highly transient buffer that is occasionally flushed * in to a set of CountBuffers (circular buffers) which store a longer term historical view of the count values at different * moments in time. * * This Counter implementation may be a bit over-engineered... The goal here was to present an implementation that is very * predictable with respect to memory and CPU time and, at the same time, present a very fast increment() method. The reasoning * here is that this will be a go-to class when it comes to debugging, particularly in high-load situations where logging * may introduce so much variability to the system that it foils the results. * * @author kyle * */ public class ConcurrentCounter implements ICounter { protected static final Map<DateSpan, Integer> MAX_HISTORY = new HashMap<DateSpan, Integer>(); static { MAX_HISTORY.put(DateSpan.REALTIME, new Integer(1)); MAX_HISTORY.put(DateSpan.SECONDS, new Integer(120)); MAX_HISTORY.put(DateSpan.MINUTES, new Integer(60)); MAX_HISTORY.put(DateSpan.HOURS, new Integer(48)); MAX_HISTORY.put(DateSpan.DAYS, new Integer(60)); MAX_HISTORY.put(DateSpan.WEEKS, new Integer(2)); } protected static Set<ConcurrentCounter> liveCounters; static { liveCounters = Collections.newSetFromMap(new ConcurrentHashMap<ConcurrentCounter, Boolean>()); //nifty way to get concurrent hash set //Set a background thread to flush any liveCounters every 100 milliseconds Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() { public void run() { for(ConcurrentCounter c : liveCounters) { c.flush(); } }}, 100, 100, TimeUnit.MILLISECONDS); } /** * Very simple data structure to store off a single count entry at a single point in time * @author kyle * */ protected static final class CountAtom { protected Date date; protected Long delta; protected CountAtom(Date date, Long delta) { this.date = date; this.delta = delta; } public String toString() { return "[" + this.date + ": " + this.delta + "]"; } } protected Queue<CountAtom> unprocessedCountBuffer; protected Map<DateSpan, CountBuffer> counts; protected Date startDate; /** * Factory method to create a new counter instance. (Design note - * use a factory pattern here as it may be necessary to hook in other * registrations around counter objects as they are created.) * * @param startDate * @return */ public static ICounter createCounter(Date startDate) { ConcurrentCounter cc = new ConcurrentCounter(startDate); ConcurrentCounter.liveCounters.add(cc); return cc; } /** * Protected constructor - use createCounter factory method instead * @param startDate */ protected ConcurrentCounter(Date startDate) { init(startDate); } protected void init(Date startDate) { this.startDate = startDate; this.unprocessedCountBuffer = new ConcurrentLinkedQueue<CountAtom>(); this.counts = new HashMap<DateSpan, CountBuffer>(); for(DateSpan ds : DateSpan.values()) { CountBuffer cb = new CountBuffer(startDate, ds, MAX_HISTORY.get(ds)); counts.put(ds, cb); } } /** * This is the key method that has to be both fast and very thread-safe. */ @Override public void increment() { this.increment(new Date(), (long)1); } @Override public void increment(Date d, long delta) { this.unprocessedCountBuffer.add(new CountAtom(d, delta)); } @Override public void setCounter(Date d, CounterValue value) { // To be done later } /** * Reset the value. */ @Override public void reset(Date startDate) { init(startDate); } /** * Flushes values out of the internal buffer and in to structures * that can be fetched with a call to snapshot() */ public synchronized void flush() { for(CountAtom c = this.unprocessedCountBuffer.poll(); c != null; c = this.unprocessedCountBuffer.poll()) { for(DateSpan ds : DateSpan.values()) { CountBuffer cb = counts.get(ds); cb.increment(c.date, c.delta); } } } @Override public CounterValue getCounterValue() { // To be done later //CountSeries cs = counts.get(DateSpan.REALTIME).snapshot(); //return cs.getSeries()[0]; return new CounterValue(CounterType.LONG); } @Override public Date getCounterDate() { // To be done later //CountSeries cs = counts.get(DateSpan.REALTIME).snapshot(); //return cs.getSeries()[0]; return new Date(); } @Override /** * This method returns a disconnected copy of the underlying CountSeries corresponding to dateSpan. */ public CountSeries snapshot(DateSpan dateSpan) { flush(); CountSeries cs = counts.get(dateSpan).snapshot(); return cs; } }