001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.processor;
018    
019    import java.util.concurrent.TimeUnit;
020    
021    import org.apache.camel.Exchange;
022    import org.apache.camel.Processor;
023    import org.apache.commons.logging.Log;
024    import org.apache.commons.logging.LogFactory;
025    
026    /**
027     * A <code>SamplingThrottler</code> is a special kind of throttler. It also
028     * limits the number of exchanges sent to a downstream endpoint. It differs from
029     * a normal throttler in that it will not queue exchanges above the threshold
030     * for a given period. Instead these exchanges will be stopped, precluding them
031     * from being processed at all by downstream consumers.
032     * <p/>
033     * This kind of throttling can be useful for taking a sample from
034     * an exchange stream, rough consolidation of noisy and bursty exchange traffic
035     * or where queuing of throttled exchanges is undesirable.
036     *
037     * @version $Revision: 885283 $
038     */
039    public class SamplingThrottler extends DelegateProcessor {
040        protected final transient Log log = LogFactory.getLog(getClass());
041        private long samplePeriod;
042        private long periodInNanos;
043        private TimeUnit units;
044        private long timeOfLastExchange;
045        private StopProcessor stopper = new StopProcessor();
046        private Object calculationLock = new Object();
047        private SampleStats sampled = new SampleStats();
048    
049        public SamplingThrottler(Processor processor, long samplePeriod, TimeUnit units) {
050            super(processor);
051    
052            if (samplePeriod <= 0) {
053                throw new IllegalArgumentException("A positive value is required for the sampling period");
054            }
055            if (units == null) {
056                throw new IllegalArgumentException("A invalid null value was supplied for the units of the sampling period");
057            }
058            this.samplePeriod = samplePeriod;
059            this.units = units;
060            periodInNanos = units.toNanos(samplePeriod);
061        }
062    
063        @Override
064        public String toString() {
065            return "SamplingThrottler[1 exchange per: " + samplePeriod + " " + units.toString().toLowerCase() + " -> " + getProcessor() + "]";
066        }
067    
068        public String getTraceLabel() {
069            return "samplingThrottler[1 exchange per: " + samplePeriod + " " + units.toString().toLowerCase() + "]";
070        }
071    
072        public void process(Exchange exchange) throws Exception {
073            boolean doSend = false;
074    
075            synchronized (calculationLock) {
076                long now = System.nanoTime();
077                if (now >= timeOfLastExchange + periodInNanos) {
078                    doSend = true;
079                    if (log.isTraceEnabled()) {
080                        log.trace(sampled.sample());
081                    }
082                    timeOfLastExchange = now;
083                } else {
084                    if (log.isTraceEnabled()) {
085                        log.trace(sampled.drop());
086                    }
087                }
088            }
089    
090            if (doSend) {
091                super.process(exchange);
092            } else {
093                stopper.process(exchange);
094            }
095        }
096    
097        private static class SampleStats {
098            private long droppedThisPeriod;
099            private long totalDropped;
100            private long totalSampled;
101            private long totalThisPeriod;
102    
103            String drop() {
104                droppedThisPeriod++;
105                totalThisPeriod++;
106                totalDropped++;
107                return getDroppedLog();
108            }
109    
110            String sample() {
111                totalThisPeriod = 1; // a new period, reset to 1
112                totalSampled++;
113                droppedThisPeriod = 0;
114                return getSampledLog();
115            }
116    
117            String getSampledLog() {
118                return String.format("Sampled %d of %d total exchanges", totalSampled, totalSampled + totalDropped);
119            }
120    
121            String getDroppedLog() {
122                return String.format("Dropped %d of %d exchanges in this period, totalling %d dropped of %d exchanges overall.",
123                    droppedThisPeriod, totalThisPeriod, totalDropped, totalSampled + totalDropped);
124            }
125        }
126    
127    }