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}