001package org.javasimon.callback.lastsplits;
002
003import static org.javasimon.callback.logging.LogTemplates.disabled;
004import static org.javasimon.utils.SimonUtils.presentNanoTime;
005
006import org.javasimon.Split;
007import org.javasimon.callback.logging.LogMessageSource;
008import org.javasimon.callback.logging.LogTemplate;
009
010/**
011 * Object stored among Stopwatch's attributes in charge of <ul>
012 * <li>Managing concurrent access to the inner ring buffer through synchronized blocks</li>
013 * <li>Computing som statistics (min, max, mean, trend) based on retained values</li>
014 * <li>Log retained values and statistics
015 * </ul>
016 *
017 * @author gquintana
018 * @since 3.2
019 */
020public class LastSplits implements LogMessageSource<Split> {
021        /** Ring buffer containing splits. */
022        private final CircularList<Split> splits;
023
024        /** Log template used to log this list of splits. */
025        private LogTemplate<Split> logTemplate = disabled();
026
027        /**
028         * Constructor with ring buffer size.
029         *
030         * @param capacity Buffer size
031         */
032        public LastSplits(int capacity) {
033                this.splits = new CircularList<>(capacity);
034        }
035
036        /**
037         * Adds split to the buffer.
038         *
039         * @param split Split
040         */
041        public void add(Split split) {
042                synchronized (splits) {
043                        splits.add(split);
044                }
045        }
046
047        /** Removes all splits from buffer. */
048        public void clear() {
049                synchronized (splits) {
050                        splits.clear();
051                }
052        }
053
054        public LogTemplate<Split> getLogTemplate() {
055                return logTemplate;
056        }
057
058        public void setLogTemplate(LogTemplate<Split> logTemplate) {
059                this.logTemplate = logTemplate;
060        }
061
062        /**
063         * Gets number of splits in the buffer.
064         *
065         * @return Split number
066         */
067        public int getCount() {
068                synchronized (splits) {
069                        return splits.size();
070                }
071        }
072
073        /**
074         * Evaluate a function over the list of splits.
075         *
076         * @param <T> Function result type
077         * @param function Function to evaluate
078         * @return Function result, null if no splits
079         */
080        private <T> T processFunction(SplitFunction<T> function) {
081                synchronized (splits) {
082                        if (splits.isEmpty()) {
083                                return null;
084                        }
085                        for (Split split : splits) {
086                                function.evaluate(split);
087                        }
088                        return function.result();
089                }
090        }
091
092        /**
093         * Function.
094         *
095         * @param <T> Result type
096         */
097        private static interface SplitFunction<T> {
098                /**
099                 * Called for each split.
100                 *
101                 * @param split Current split
102                 */
103                void evaluate(Split split);
104
105                /**
106                 * Called after all splits.
107                 *
108                 * @return Function result
109                 */
110                T result();
111        }
112
113        /**
114         * Base implementation of functions.
115         *
116         * @param <T> Function return type
117         */
118        private static abstract class AbstractSplitFunction<T> implements SplitFunction<T> {
119                /** Function result. */
120                protected T result;
121
122                /** Initial function result. */
123                public AbstractSplitFunction(T result) {
124                        this.result = result;
125                }
126
127                /**
128                 * Running for duration of the split.
129                 *
130                 * @param runningFor Running for
131                 */
132                public abstract void evaluate(long runningFor);
133
134                /**
135                 * Calls evaluate with split running for duration.
136                 *
137                 * @param split Current split
138                 */
139                public final void evaluate(Split split) {
140                        evaluate(split.runningFor());
141                }
142
143                /** Final result. */
144                public T result() {
145                        return result;
146                }
147        }
148
149        /**
150         * Compute mean duration of splits in the buffer
151         *
152         * @return Mean or average
153         */
154        public Double getMean() {
155                return processFunction(new AbstractSplitFunction<Double>(0.0D) {
156                        @Override
157                        public void evaluate(long runningFor) {
158                                result += (double) runningFor;
159                        }
160
161                        @Override
162                        public Double result() {
163                                return result / (double) splits.size();
164                        }
165
166                });
167        }
168
169        /**
170         * Compute the smallest duration of splits in the buffer
171         *
172         * @return Minimum
173         */
174        public Long getMin() {
175                return processFunction(new AbstractSplitFunction<Long>(Long.MAX_VALUE) {
176                        @Override
177                        public void evaluate(long runningFor) {
178                                if (runningFor < result) {
179                                        result = runningFor;
180                                }
181                        }
182                });
183        }
184
185        /**
186         * Compute the longest duration of splits in the buffer
187         *
188         * @return Maximum
189         */
190        public Long getMax() {
191                return processFunction(new AbstractSplitFunction<Long>(Long.MIN_VALUE) {
192                        @Override
193                        public void evaluate(long runningFor) {
194                                if (runningFor > result) {
195                                        result = runningFor;
196                                }
197                        }
198                });
199        }
200
201        /**
202         * Compute a trend of duration: the average delta of splits between
203         * 2 splits spaced of at least 1 ms.
204         * Sum(splits(t[n])-splits(t[n-1])/SizeOf(splits)
205         *
206         * @return Trend, average delta of splits
207         */
208        public Double getTrend() {
209                return getTrend(1000);
210        }
211
212        /**
213         * Compute a trend of duration: the average delta of splits between
214         * 2 split spaced of at least the given threshold.
215         * The threshold is only here to avoid computing a delta between 2 splits
216         * occurring at the same time by 2 different threads.
217         * Sum(splits(t[n])-splits(t[n-1])/SizeOf(splits)
218         *
219         * @param timeDeltaThreshold Accepted splits space
220         * @return Trend, average delta of splits
221         */
222        public Double getTrend(final long timeDeltaThreshold) {
223                return processFunction(new SplitFunction<Double>() {
224                        Split lastSplit;
225                        long result;
226                        int count;
227
228                        public void evaluate(Split split) {
229                                if (lastSplit == null) {
230                                        lastSplit = split;
231                                } else {
232                                        long timeDelta = split.getStart() - lastSplit.getStart();
233                                        if (timeDelta > timeDeltaThreshold) {
234                                                long durationDelta = split.runningFor() - lastSplit.runningFor();
235                                                result += durationDelta;
236                                                count++;
237                                                lastSplit = split;
238                                        }
239                                }
240                        }
241
242                        public Double result() {
243                                return count > 0 ? (result / ((double) count)) : null;
244                        }
245                });
246        }
247
248        /**
249         * Transforms split values into a String
250         *
251         * @return Splits presented in a String
252         */
253        private String getSplitsAsString() {
254                return processFunction(new AbstractSplitFunction<StringBuilder>(new StringBuilder()) {
255                        private boolean first = true;
256
257                        @Override
258                        public void evaluate(long runningFor) {
259                                if (first) {
260                                        first = false;
261                                } else {
262                                        result.append(',');
263                                }
264                                result.append(presentNanoTime(runningFor));
265                        }
266                }).toString();
267        }
268
269        /**
270         * String containing: count, min, mean, max and trend(1ms).
271         * This method can be expensive, because many computations are done.
272         *
273         * @return String
274         */
275        @Override
276        public String toString() {
277                int count;
278                long min = 0, mean = 0, max = 0, trend = 0;
279                String values = null;
280                // First extract data
281                synchronized (splits) {
282                        count = getCount();
283                        if (count > 0) {
284                                min = getMin();
285                                mean = getMean().longValue();
286                                max = getMax();
287                                values = getSplitsAsString();
288                                if (count > 1) {
289                                        trend = getTrend().longValue();
290                                }
291                        }
292                }
293                // Then free lock, and format data
294                StringBuilder stringBuilder = new StringBuilder("LastSplits[size=");
295                stringBuilder.append(count);
296                if (count > 0) {
297                        stringBuilder.append(",values=[").append(values).append("]")
298                                .append(",min=").append(presentNanoTime(min))
299                                .append(",mean=").append(presentNanoTime(mean))
300                                .append(",max=").append(presentNanoTime(max));
301                        if (count > 1) {
302                                stringBuilder.append(",trend=").append(presentNanoTime(trend));
303                        }
304                }
305                stringBuilder.append("]");
306                return stringBuilder.toString();
307        }
308
309        /**
310         * Transforms this list of splits into a loggable message.
311         */
312        public String getLogMessage(Split lastSplit) {
313                return lastSplit.getStopwatch().getName() + " " + toString();
314        }
315
316        /**
317         * Log eventually this list of splits into log template
318         */
319        public void log(Split lastSplit) {
320                logTemplate.log(lastSplit, this);
321        }
322}