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}