001package org.javasimon.callback.quantiles;
002
003import static org.javasimon.callback.logging.LogTemplates.disabled;
004import static org.javasimon.callback.logging.LogTemplates.everyNSeconds;
005import static org.javasimon.callback.logging.LogTemplates.toSLF4J;
006
007import org.javasimon.Split;
008import org.javasimon.Stopwatch;
009import org.javasimon.StopwatchSample;
010import org.javasimon.callback.CallbackSkeleton;
011import org.javasimon.callback.logging.LogTemplate;
012
013/**
014 * Callback which stores data in buckets to compute quantiles.
015 * The {@link #createBuckets(org.javasimon.Stopwatch)} should be
016 * implemented to configure the width and resolution of buckets.
017 * Then {@link Buckets} are stored among Simon attributes.
018 * There are 2 implementations:
019 * <ul>
020 * <li>{@link AutoQuantilesCallback} tries to determine the best configuration for each Stopwatch.</li>
021 * <li>{@link FixedQuantilesCallback} uses a fixed configuration for all Stopwatches.</li>
022 * </ul>
023 *
024 * @author gquintana
025 * @see Buckets
026 * @since 3.2
027 */
028@SuppressWarnings("UnusedDeclaration")
029public abstract class QuantilesCallback extends CallbackSkeleton {
030
031        /** Simon attribute name of the buckets stored in Simons after warmup time. */
032        public static final String ATTR_NAME_BUCKETS = "buckets";
033
034        /** SLF4J log template shared by all stopwatches. */
035        private final LogTemplate<Split> enabledStopwatchLogTemplate = toSLF4J(getClass().getName(), "debug");
036
037        /** Global flag indicating whether last splits should be logged once in a while. */
038        private boolean logEnabled = false;
039        /** Type of the buckets: linear or exponential. */
040        private BucketsType bucketsType;
041
042        /** Default constructor. */
043        protected QuantilesCallback() {
044                bucketsType = BucketsType.LINEAR;
045        }
046
047        /**
048         * Constructor with buckets type.
049         *
050         * @param bucketsType Type of buckets
051         */
052        protected QuantilesCallback(BucketsType bucketsType) {
053                this.bucketsType = bucketsType;
054        }
055
056        /**
057         * Returns buckets type.
058         *
059         * @return Buckets type
060         */
061        public BucketsType getBucketsType() {
062                return bucketsType;
063        }
064
065        public boolean isLogEnabled() {
066                return logEnabled;
067        }
068
069        public void setLogEnabled(boolean logEnabled) {
070                this.logEnabled = logEnabled;
071        }
072
073        /**
074         * Create log template for given stopwatch.
075         * This method can be overridden to tune logging strategy.
076         * By default, when enabled, quantiles are logged at most once per minute
077         *
078         * @param stopwatch Stopwatch
079         * @return Logger
080         */
081        @SuppressWarnings("UnusedParameters")
082        protected LogTemplate<Split> createLogTemplate(Stopwatch stopwatch) {
083                LogTemplate<Split> logTemplate;
084                if (logEnabled) {
085                        logTemplate = everyNSeconds(enabledStopwatchLogTemplate, 60);
086                } else {
087                        logTemplate = disabled();
088                }
089                return logTemplate;
090        }
091
092        /** Returns the buckets attribute. */
093        public static Buckets getBuckets(Stopwatch stopwatch) {
094                return (Buckets) stopwatch.getAttribute(ATTR_NAME_BUCKETS);
095        }
096
097        /**
098         * Factory method to create a Buckets object using given configuration.
099         *
100         * @param stopwatch Target Stopwatch
101         * @param min Min bound
102         * @param max Max bound
103         * @param bucketNb Number of buckets between min and max
104         * @return Buckets
105         */
106        protected final Buckets createBuckets(Stopwatch stopwatch, long min, long max, int bucketNb) {
107                Buckets buckets = bucketsType.createBuckets(stopwatch, min, max, bucketNb);
108                buckets.setLogTemplate(createLogTemplate(stopwatch));
109                return buckets;
110        }
111
112        /**
113         * Create Buckets for given stopwatch.
114         * Call {@link #createBuckets(org.javasimon.Stopwatch, long, long, int)} to create a new buckets object.
115         *
116         * @param stopwatch Stopwatch
117         * @return Buckets
118         */
119        protected abstract Buckets createBuckets(Stopwatch stopwatch);
120
121        /** Returns the buckets attribute or create it if it does not exist. */
122        @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
123        protected final Buckets getOrCreateBuckets(Stopwatch stopwatch) {
124                synchronized (stopwatch) {
125                        Buckets buckets = getBuckets(stopwatch);
126                        if (buckets == null) {
127                                buckets = createBuckets(stopwatch);
128                                stopwatch.setAttribute(ATTR_NAME_BUCKETS, buckets);
129                        }
130                        return buckets;
131                }
132        }
133
134        /** Returns the buckets attribute and sample them. */
135        public static BucketsSample sampleBuckets(Stopwatch stopwatch) {
136                final Buckets buckets = getBuckets(stopwatch);
137                return buckets == null ? null : buckets.sample();
138        }
139
140        /**
141         * Called when there is a new split on a Stopwatch, either
142         * {@link #onStopwatchStop} or {@link #onStopwatchAdd}.
143         * If buckets have been initialized, the value is added to appropriate bucket.
144         */
145        protected void onStopwatchSplit(Stopwatch stopwatch, Split split) {
146                Buckets buckets = getOrCreateBuckets(stopwatch);
147                if (buckets != null) {
148                        buckets.addValue(split.runningFor());
149                        buckets.log(split);
150                }
151        }
152
153        /**
154         * When a split is stopped, if buckets have been initialized, the value
155         * is added to appropriate bucket.
156         */
157        @Override
158        public void onStopwatchStop(Split split, StopwatchSample sample) {
159                onStopwatchSplit(split.getStopwatch(), split);
160        }
161
162        /** When a split is added, if buckets have been initialized, the value is added to appropriate bucket. */
163        @Override
164        public void onStopwatchAdd(Stopwatch stopwatch, Split split, StopwatchSample sample) {
165                onStopwatchSplit(split.getStopwatch(), split);
166        }
167}