1 package org.robolectric.util; 2 3 import java.util.ArrayList; 4 import java.util.Collection; 5 import java.util.HashMap; 6 import java.util.Map; 7 8 /** 9 * Collects performance statistics for later reporting via {@link PerfStatsReporter}. 10 * 11 * @since 3.6 12 */ 13 public class PerfStatsCollector { 14 15 private static final PerfStatsCollector INSTANCE = new PerfStatsCollector(); 16 17 private final Clock clock; 18 private final Map<Class<?>, Object> metadata = new HashMap<>(); 19 private final Map<MetricKey, Metric> metricMap = new HashMap<>(); 20 private boolean enabled = true; 21 PerfStatsCollector()22 public PerfStatsCollector() { 23 this(System::nanoTime); 24 } 25 PerfStatsCollector(Clock clock)26 PerfStatsCollector(Clock clock) { 27 this.clock = clock; 28 } 29 getInstance()30 public static PerfStatsCollector getInstance() { 31 return INSTANCE; 32 } 33 34 /** 35 * If not enabled, don't bother retaining perf stats, saving some memory and CPU cycles. 36 */ setEnabled(boolean isEnabled)37 public void setEnabled(boolean isEnabled) { 38 this.enabled = isEnabled; 39 } 40 startEvent(String eventName)41 public Event startEvent(String eventName) { 42 return new Event(eventName); 43 } 44 measure(String eventName, ThrowingSupplier<T, E> supplier)45 public <T, E extends Exception> T measure(String eventName, ThrowingSupplier<T, E> supplier) 46 throws E { 47 boolean success = true; 48 Event event = startEvent(eventName); 49 try { 50 return supplier.get(); 51 } catch (Exception e) { 52 success = false; 53 throw e; 54 } finally { 55 event.finished(success); 56 } 57 } 58 59 /** 60 * Supplier that throws an exception. 61 */ 62 // @FunctionalInterface -- not available on Android yet... 63 public interface ThrowingSupplier<T, F extends Exception> { get()64 T get() throws F; 65 } 66 measure(String eventName, ThrowingRunnable<E> runnable)67 public <E extends Exception> void measure(String eventName, ThrowingRunnable<E> runnable) 68 throws E { 69 boolean success = true; 70 Event event = startEvent(eventName); 71 try { 72 runnable.run(); 73 } catch (Exception e) { 74 success = false; 75 throw e; 76 } finally { 77 event.finished(success); 78 } 79 } 80 81 /** 82 * Runnable that throws an exception. 83 */ 84 // @FunctionalInterface -- not available on Android yet... 85 public interface ThrowingRunnable<F extends Exception> { run()86 void run() throws F; 87 } 88 getMetrics()89 public synchronized Collection<Metric> getMetrics() { 90 return new ArrayList<>(metricMap.values()); 91 } 92 putMetadata(Class<T> metadataClass, T metadata)93 public synchronized <T> void putMetadata(Class<T> metadataClass, T metadata) { 94 if (!enabled) { 95 return; 96 } 97 98 this.metadata.put(metadataClass, metadata); 99 } 100 getMetadata()101 public synchronized Metadata getMetadata() { 102 return new Metadata(metadata); 103 } 104 reset()105 public void reset() { 106 metadata.clear(); 107 metricMap.clear(); 108 } 109 110 /** 111 * Event for perf stats collection. 112 */ 113 public class Event { 114 private final String name; 115 private final long startTimeNs; 116 Event(String name)117 Event(String name) { 118 this.name = name; 119 this.startTimeNs = clock.nanoTime(); 120 } 121 finished()122 public void finished() { 123 finished(true); 124 } 125 finished(boolean success)126 public void finished(boolean success) { 127 if (!enabled) { 128 return; 129 } 130 131 synchronized (PerfStatsCollector.this) { 132 MetricKey key = new MetricKey(name, success); 133 Metric metric = metricMap.get(key); 134 if (metric == null) { 135 metricMap.put(key, metric = new Metric(key.name, key.success)); 136 } 137 metric.count++; 138 metric.elapsedNs += clock.nanoTime() - startTimeNs; 139 } 140 } 141 } 142 143 /** 144 * Metric for perf stats collection. 145 */ 146 public static class Metric { 147 private final String name; 148 private int count; 149 private long elapsedNs; 150 private final boolean success; 151 Metric(String name, int count, int elapsedNs, boolean success)152 public Metric(String name, int count, int elapsedNs, boolean success) { 153 this.name = name; 154 this.count = count; 155 this.elapsedNs = elapsedNs; 156 this.success = success; 157 } 158 Metric(String name, boolean success)159 public Metric(String name, boolean success) { 160 this(name, 0, 0, success); 161 } 162 getName()163 public String getName() { 164 return name; 165 } 166 getCount()167 public int getCount() { 168 return count; 169 } 170 getElapsedNs()171 public long getElapsedNs() { 172 return elapsedNs; 173 } 174 isSuccess()175 public boolean isSuccess() { 176 return success; 177 } 178 179 @Override equals(Object o)180 public boolean equals(Object o) { 181 if (this == o) { 182 return true; 183 } 184 if (o == null || getClass() != o.getClass()) { 185 return false; 186 } 187 188 Metric metric = (Metric) o; 189 190 if (count != metric.count) { 191 return false; 192 } 193 if (elapsedNs != metric.elapsedNs) { 194 return false; 195 } 196 if (success != metric.success) { 197 return false; 198 } 199 return name != null ? name.equals(metric.name) : metric.name == null; 200 } 201 202 @Override hashCode()203 public int hashCode() { 204 int result = name != null ? name.hashCode() : 0; 205 result = 31 * result + count; 206 result = 31 * result + (int) (elapsedNs ^ (elapsedNs >>> 32)); 207 result = 31 * result + (success ? 1 : 0); 208 return result; 209 } 210 211 @Override toString()212 public String toString() { 213 return "Metric{" 214 + "name='" + name + '\'' 215 + ", count=" + count 216 + ", elapsedNs=" + elapsedNs 217 + ", success=" + success 218 + '}'; 219 } 220 } 221 222 /** 223 * Metric key for perf stats collection. 224 */ 225 private static class MetricKey { 226 private final String name; 227 private final boolean success; 228 MetricKey(String name, boolean success)229 MetricKey(String name, boolean success) { 230 this.name = name; 231 this.success = success; 232 } 233 234 @Override equals(Object o)235 public boolean equals(Object o) { 236 if (this == o) { 237 return true; 238 } 239 if (o == null || getClass() != o.getClass()) { 240 return false; 241 } 242 243 MetricKey metricKey = (MetricKey) o; 244 245 if (success != metricKey.success) { 246 return false; 247 } 248 return name != null ? name.equals(metricKey.name) : metricKey.name == null; 249 } 250 251 @Override hashCode()252 public int hashCode() { 253 int result = name != null ? name.hashCode() : 0; 254 result = 31 * result + (success ? 1 : 0); 255 return result; 256 } 257 } 258 259 /** 260 * Metadata for perf stats collection. 261 */ 262 public static class Metadata { 263 private final Map<Class<?>, Object> metadata; 264 Metadata(Map<Class<?>, Object> metadata)265 Metadata(Map<Class<?>, Object> metadata) { 266 this.metadata = new HashMap<>(metadata); 267 } 268 get(Class<T> metadataClass)269 public <T> T get(Class<T> metadataClass) { 270 return metadataClass.cast(metadata.get(metadataClass)); 271 } 272 } 273 } 274