/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.util; import org.snmp4j.smi.TimeTicks; import java.lang.management.ManagementFactory; import java.math.BigDecimal; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; /** * <p>Title: SystemClock</p> * <p>Description: </p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.util.SystemClock</code></p> */ public enum SystemClock { DIRECT(new DirectClock()), NANO(new NanoClock()), TEST(new TestClock()); private SystemClock(Clock clock) { this.clock = clock; } private final Clock clock; public static long time() { return currentClock.get().getTime(); } /** * Calculates the beginning time of the current period based on the passed step and the clock's current time * @param step The period step size * @return The current period */ public static long period(long step) { return currentClock.get().clock.period(step); } /** * Calculates the beginning time of the current period based on the passed step and the clock's current time * @param step The period step size * @param time The raw time to calculate the period for * @return The current period */ public static long period(long step, long time) { return currentClock.get().clock.period(step, time); } /** * Returns the current time in seconds * @return the current time in seconds */ public static long unixTime() { return currentClock.get().clock.unixTime(); } /** * @return */ public static long timeTick() { return currentClock.get().clock.timeTick(); } /** * Returns the JVM up time in ms. * @return the up time in ms. */ public static long upTime() { return ManagementFactory.getRuntimeMXBean().getUptime(); } public static long toTimeTicks(long time) { long tt = time/100; return tt; } public static long unixTime(long msTime) { return TimeUnit.SECONDS.convert(msTime, TimeUnit.MILLISECONDS); } public static SystemClock currentClock() { return currentClock.get(); } public static void setCurrentClock(SystemClock clock) { if(clock==null) throw new IllegalArgumentException("SystemClock cannot be set to null", new Throwable()); currentClock.set(clock); } /** * Returns the current time from this clock * @return the current time */ public long getTime() { return clock.time(); } /** The VM start time in ms. */ public static final long startTime = ManagementFactory.getRuntimeMXBean().getStartTime(); /** The test time */ private static final AtomicLong testTime = new AtomicLong(0L); /** A reference to the current clock impl. */ private static final AtomicReference<SystemClock> currentClock = new AtomicReference<SystemClock>(DIRECT); /** Holds the start timestamp of an elapsed time measurement */ private static final ThreadLocal<long[]> timerStart = new ThreadLocal<long[]>() { protected long[] initialValue() { return new long[1]; } }; /** * Returns the elapsed time in ms. since the passed timestamp * @param time The base time to get the elapsed from * @return the elapsed time */ public static long elapsedMsSince(long time) { return currentClock.get().getTime()-time; } /** * Causes the calling thread to join itself for the indicated time * @param time The time to join in ms. */ public static void sleep(long time) { sleep(time, null); } /** * Causes the calling thread to join itself for the indicated time * @param time The time to join * @param unit The unit of time (optional, if null, means ms.) */ public static void sleep(long time, TimeUnit unit) { try { long actual = unit==null ? time : TimeUnit.MILLISECONDS.convert(time, unit); Thread.currentThread().join(actual); } catch (Exception e) { throw new RuntimeException("Failed to sleep", e); } } /** * Starts an elapsed timer for the current thread * @return the start time in ns. */ public static long startTimer() { long st = System.nanoTime(); timerStart.get()[0] = st; return st; } public static ElapsedTime endTimer() { return ElapsedTime.newInstance(System.nanoTime()); } public static ElapsedTime lapTimer() { return ElapsedTime.newInstance(true, System.nanoTime()); } /** * <p>Title: AggregatedElapsedTime</p> * <p>Description: An aggregate for multiple {@link ElapsedTime}s</p> * <p>Company: ICE Futures US</p> * @author Whitehead (nicholas.whitehead@theice.com) * @version $LastChangedRevision$ * <p><code>org.helios.apmrouter.util.SystemClock.AggregatedElapsedTime</code></p> */ public static class AggregatedElapsedTime { /** The maximum elapsed time of the aggregate in ns. */ public final long maxElapsedNs; /** The minimum elapsed time of the aggregate in ns. */ public final long minElapsedNs; /** The average elapsed time of the aggregate in ns. */ public final long avgElapsedNs; /** The maximum elapsed time of the aggregate in ms. */ public final long maxElapsedMs; /** The minimum elapsed time of the aggregate in Ms. */ public final long minElapsedMs; /** The average elapsed time of the aggregate in Ms. */ public final long avgElapsedMs; /** * Creates a new AggregatedElapsedTime * @param maxElapsedNs The maximum elapsed time of the aggregate in ns. * @param minElapsedNs The minimum elapsed time of the aggregate in ns. * @param avgElapsedNs The average elapsed time of the aggregate in ns. */ private AggregatedElapsedTime(long maxElapsedNs, long minElapsedNs, long avgElapsedNs) { this.maxElapsedNs = maxElapsedNs; this.minElapsedNs = minElapsedNs; this.avgElapsedNs = avgElapsedNs; maxElapsedMs = TimeUnit.MILLISECONDS.convert(this.maxElapsedNs, TimeUnit.NANOSECONDS); minElapsedMs = TimeUnit.MILLISECONDS.convert(this.minElapsedNs, TimeUnit.NANOSECONDS); avgElapsedMs = TimeUnit.MILLISECONDS.convert(this.avgElapsedNs, TimeUnit.NANOSECONDS); } /** * Aggregates a collection of {@link ElapsedTime}s * @param times The times to aggregate * @return the aggregated elapsed time */ public static AggregatedElapsedTime aggregate(Collection<ElapsedTime> times) { return aggregate(times.toArray(new ElapsedTime[0])); } /** * Aggregates an array of {@link ElapsedTime}s * @param times The times to aggregate * @return the aggregated elapsed time */ public static AggregatedElapsedTime aggregate(ElapsedTime...times) { if(times==null || times.length<1) throw new IllegalArgumentException("Must pass at least 1 ElapsedTime to aggregate", new Throwable()); long max = Long.MIN_VALUE; long min = Long.MAX_VALUE; BigDecimal total = new BigDecimal(0); long avg = 0; int cnt = times.length; for(ElapsedTime et: times) { total = total.add(BigDecimal.valueOf(et.elapsedNs)); if(et.elapsedNs>max) max = et.elapsedNs; if(et.elapsedNs<min) min = et.elapsedNs; } avg = SimpleMath.avg(cnt, total.longValue()); return new AggregatedElapsedTime(max, min, avg); } /** * {@inheritDoc} * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("AggregatedElapsedTime [maxElapsedNs="); builder.append(maxElapsedNs); builder.append(", minElapsedNs="); builder.append(minElapsedNs); builder.append(", avgElapsedNs="); builder.append(avgElapsedNs); builder.append(", maxElapsedMs="); builder.append(maxElapsedMs); builder.append(", minElapsedMs="); builder.append(minElapsedMs); builder.append(", avgElapsedMs="); builder.append(avgElapsedMs); builder.append("]"); return builder.toString(); } /** * Returns the average elapsed time in ms. for the passed number of events * @param cnt The number of events * @return The average elapsed time in ms. */ public long avgMs(double cnt) { return _avg(avgElapsedMs, cnt); } /** * Returns the maximum elapsed time in ms. for the passed number of events * @param cnt The number of events * @return The maximum elapsed time in ms. */ public long maxMs(double cnt) { return _avg(maxElapsedMs, cnt); } /** * Returns the minimum elapsed time in ms. for the passed number of events * @param cnt The number of events * @return The minimum elapsed time in ms. */ public long minMs(double cnt) { return _avg(minElapsedMs, cnt); } /** * Returns the average elapsed time in ns. for the passed number of events * @param cnt The number of events * @return The average elapsed time in ns. */ public long avgNs(double cnt) { return _avg(avgElapsedNs, cnt); } /** * Returns the maximum elapsed time in ns. for the passed number of events * @param cnt The number of events * @return The maximum elapsed time in ns. */ public long maxNs(double cnt) { return _avg(maxElapsedNs, cnt); } /** * Returns the minimum elapsed time in ns. for the passed number of events * @param cnt The number of events * @return The minimum elapsed time in ns. */ public long minNs(double cnt) { return _avg(minElapsedNs, cnt); } private long _avg(double time, double cnt) { if(time==0 || cnt==0 ) return 0L; double d = time/cnt; return (long)d; } } /** * <p>Title: ElapsedTime</p> * <p>Description: Encapsulates various values associated to a timer's elapsed time.</p> */ public static class ElapsedTime { public final long startNs; public final long endNs; public final long elapsedNs; public final long elapsedMs; public volatile long lastLapNs = -1L; public volatile long elapsedSinceLastLapNs = -1L; public volatile long elapsedSinceLastLapMs = -1L; /** Holds the start last lap of an elapsed time measurement */ private static final ThreadLocal<long[]> lapTime = new ThreadLocal<long[]>(); static ElapsedTime newInstance(long endTime) { return newInstance(false, endTime); } static ElapsedTime newInstance(boolean lap, long endTime) { return new ElapsedTime(lap, endTime); } /** Some extended time unit entries */ public static final Map<TimeUnit, String> UNITS; static { Map<TimeUnit, String> tmp = new HashMap<TimeUnit, String>(); tmp.put(TimeUnit.DAYS, "days"); tmp.put(TimeUnit.HOURS, "hrs."); tmp.put(TimeUnit.MICROSECONDS, "us."); tmp.put(TimeUnit.MILLISECONDS, "ms."); tmp.put(TimeUnit.MINUTES, "min."); tmp.put(TimeUnit.NANOSECONDS, "ns."); tmp.put(TimeUnit.SECONDS, "s."); UNITS = Collections.unmodifiableMap(tmp); } private ElapsedTime(boolean lap, long endTime) { endNs = endTime; startNs = timerStart.get()[0]; long[] lastLapRead = lapTime.get(); if(lastLapRead!=null) { lastLapNs = lastLapRead[0]; } if(lap) { lapTime.set(new long[]{endTime}); } else { timerStart.remove(); lapTime.remove(); } elapsedNs = endNs-startNs; elapsedMs = TimeUnit.MILLISECONDS.convert(elapsedNs, TimeUnit.NANOSECONDS); if(lastLapNs!=-1L) { elapsedSinceLastLapNs = endTime -lastLapNs; elapsedSinceLastLapMs = TimeUnit.MILLISECONDS.convert(elapsedSinceLastLapNs, TimeUnit.NANOSECONDS); } } /** * Returns the average elapsed time in ms. for the passed number of events * @param cnt The number of events * @return The average elapsed time in ms. */ public long avgMs(double cnt) { return _avg(elapsedMs, cnt); } /** * Returns the average elapsed time in ns. for the passed number of events * @param cnt The number of events * @return The average elapsed time in ns. */ public long avgNs(double cnt) { return _avg(elapsedNs, cnt); } private long _avg(double time, double cnt) { if(time==0 || cnt==0 ) return 0L; double d = time/cnt; return Math.round(d); } public String toString() { StringBuilder b = new StringBuilder("["); b.append(elapsedNs).append("] ns."); b.append(" / [").append(elapsedMs).append("] ms."); if(elapsedSinceLastLapNs!=-1L) { b.append(" Elapsed Lap: [").append(elapsedSinceLastLapNs).append("] ns. / [").append(elapsedSinceLastLapMs).append("] ms."); } return b.toString(); } public long elapsed() { return elapsed(TimeUnit.NANOSECONDS); } public long elapsed(TimeUnit unit) { if(unit==null) unit = TimeUnit.NANOSECONDS; return unit.convert(elapsedNs, TimeUnit.NANOSECONDS); } public String elapsedStr(TimeUnit unit) { if(unit==null) unit = TimeUnit.NANOSECONDS; return new StringBuilder("[").append(unit.convert(elapsedNs, TimeUnit.NANOSECONDS)).append("] ").append(UNITS.get(unit)).toString(); } public String elapsedStr() { return elapsedStr(TimeUnit.NANOSECONDS); } } /** * <p>Title: Clock</p> * <p>Description: Defines a clock impl.</p> */ public static interface Clock { /** * Returns the current time in ms. * @return the current time in ms. */ long time(); /** * Returns the current UNIX time in s. * @return the current UNIX time in s. */ long unixTime(); /** * Returns the current time in SNMP {@link TimeTicks} equivalent or <b><code>1/100th</code></b> seconds * @return the current time in SNMP {@link TimeTicks} */ long timeTick(); /** * Calculates the beginning time of the current period based on the passed step and the clock's current time * @param step The period step size * @return The current period */ long period(long step); /** * Calculates the beginning time of the current period based on the passed step and the clock's current time * @param step The period step size * @param time The raw time to calculate the period for * @return The current period */ long period(long step, long time); } /** * <p>Title: DirectClock</p> * <p>Description: A clock implementation that simply returns {@code System.currentTimeMillis()}</p> */ private static class DirectClock implements Clock { public long time() { return System.currentTimeMillis(); } public long unixTime() { return TimeUnit.SECONDS.convert(time(), TimeUnit.MILLISECONDS); } public long timeTick() { long tt = time()/100; return tt; } /** * {@inheritDoc} * @see org.helios.apmrouter.util.SystemClock.Clock#period(long) */ @Override public long period(long step) { final long t = time(); return t-(t%step); } /** * {@inheritDoc} * @see org.helios.apmrouter.util.SystemClock.Clock#period(long, long) */ @Override public long period(long step, long time) { return time-(time%step); } } private static class NanoClock implements Clock { public long time() { return TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS) + startTime; } public long unixTime() { return TimeUnit.SECONDS.convert(time(), TimeUnit.MILLISECONDS); } public long timeTick() { long tt = time()/100; return tt; } /** * {@inheritDoc} * @see org.helios.apmrouter.util.SystemClock.Clock#period(long) */ @Override public long period(long step) { final long t = time(); return t-(t%step); } /** * {@inheritDoc} * @see org.helios.apmrouter.util.SystemClock.Clock#period(long, long) */ @Override public long period(long step, long time) { return time-(time%step); } } private static class TestClock implements Clock { public long time() { return testTime.get(); } public long unixTime() { return TimeUnit.SECONDS.convert(time(), TimeUnit.MILLISECONDS); } public long timeTick() { long tt = time()/100; return tt; } /** * {@inheritDoc} * @see org.helios.apmrouter.util.SystemClock.Clock#period(long) */ @Override public long period(long step) { final long t = time(); return t-(t%step); } /** * {@inheritDoc} * @see org.helios.apmrouter.util.SystemClock.Clock#period(long, long) */ @Override public long period(long step, long time) { return time-(time%step); } } public static long setTestTime(long time) { testTime.set(time); return time; } public static long setTestTime() { testTime.set(DIRECT.getTime()); return testTime.get(); } public static long tickTestTime() { return testTime.incrementAndGet(); } }