• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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