001    /*
002    Copyright (c) 2008 Evan Jones, Yang Zhang
003    
004    Permission is hereby granted, free of charge, to any person obtaining a copy
005    of this software and associated documentation files (the "Software"), to deal
006    in the Software without restriction, including without limitation the rights
007    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008    copies of the Software, and to permit persons to whom the Software is
009    furnished to do so, subject to the following conditions:
010    
011    The above copyright notice and this permission notice shall be included in
012    all copies or substantial portions of the Software.
013    
014    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
020    THE SOFTWARE.
021     */
022    
023    package util;
024    
025    import java.util.Random;
026    import java.util.concurrent.atomic.AtomicBoolean;
027    import java.util.concurrent.locks.Condition;
028    import java.util.concurrent.locks.ReentrantLock;
029    
030    /**
031     * Abstract class for simple sampling daemons.
032     * 
033     *
034     */
035    public abstract class Sampler implements Runnable {
036      private final int intervalMillis;
037      private final AtomicBoolean doStop;
038      private boolean isDone;
039      private final ReentrantLock lock;
040      private final Condition done;
041      private final Random random;
042    
043      /**
044       * Creates a sampler that executes periodically. The sampler uses randomization
045       * so the interval is probabilistic with uniform distribution.
046       * 
047       * @param intervalMillis  the expected interval period in milliseconds. If
048       *                        <code>intervalMillis</code> is zero, the sampler
049       *                        will run with an infinite interval (i.e., no-op).
050       */
051      protected Sampler(int intervalMillis) {
052        this.intervalMillis = intervalMillis;
053    
054        doStop = new AtomicBoolean(false);
055        lock = new ReentrantLock();
056        done = lock.newCondition();
057        random = new Random();
058      }
059    
060      /**
061       * Takes a sample at the interval.
062       */
063      protected abstract void sample();
064    
065      /**
066       * Returns the statistics associated with this sampler.
067       * 
068       * @return   statistics
069       */
070      protected abstract Statistics dumpSamples();
071    
072      @Override
073      public void run() {
074        while (!doStop.get()) {
075          try {
076            int sleepTime = intervalMillis - (intervalMillis / 2) + random.nextInt(intervalMillis);
077            Thread.sleep(sleepTime);
078          } catch (InterruptedException e) {
079            throw new RuntimeException(e);
080          }
081          sample();
082        }
083    
084        lock.lock();
085        try {
086          isDone = true;
087          done.signal();
088        } finally {
089          lock.unlock();
090        }
091      }
092    
093      /**
094       * Stops the sampler and returns the accumulated statistics.
095       * 
096       * @return   the accumulated statistics or null if the interval is infinite
097       */
098      public Statistics stop() {
099        if (intervalMillis > 0) {
100          doStop.set(true);
101          lock.lock();
102          try {
103            while (!isDone) {
104              done.await();
105            }
106            return dumpSamples();
107          } catch (InterruptedException e) {
108            throw new Error(e);
109          } finally {
110            lock.unlock();
111          }
112        }
113    
114        return null;
115      }
116    
117      /**
118       * Starts sampling thread. Subclasses should use this method to start
119       * sampling.
120       * 
121       * @return   a sampler
122       */
123      protected final static Sampler start(Sampler sampler) {
124        if (sampler.intervalMillis > 0) {
125          Thread t = new Thread(sampler, "Sampler");
126          t.setDaemon(true);
127          t.start();
128        }
129        return sampler;
130      }
131    }