Home | History | Annotate | Download | only in util
      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 
     22   public PerfStatsCollector() {
     23     this(System::nanoTime);
     24   }
     25 
     26   PerfStatsCollector(Clock clock) {
     27     this.clock = clock;
     28   }
     29 
     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    */
     37   public void setEnabled(boolean isEnabled) {
     38     this.enabled = isEnabled;
     39   }
     40 
     41   public Event startEvent(String eventName) {
     42     return new Event(eventName);
     43   }
     44 
     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> {
     64     T get() throws F;
     65   }
     66 
     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> {
     86     void run() throws F;
     87   }
     88 
     89   public synchronized Collection<Metric> getMetrics() {
     90     return new ArrayList<>(metricMap.values());
     91   }
     92 
     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 
    101   public synchronized Metadata getMetadata() {
    102     return new Metadata(metadata);
    103   }
    104 
    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 
    117     Event(String name) {
    118       this.name = name;
    119       this.startTimeNs = clock.nanoTime();
    120     }
    121 
    122     public void finished() {
    123       finished(true);
    124     }
    125 
    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 
    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 
    159     public Metric(String name, boolean success) {
    160       this(name, 0, 0, success);
    161     }
    162 
    163     public String getName() {
    164       return name;
    165     }
    166 
    167     public int getCount() {
    168       return count;
    169     }
    170 
    171     public long getElapsedNs() {
    172       return elapsedNs;
    173     }
    174 
    175     public boolean isSuccess() {
    176       return success;
    177     }
    178 
    179     @Override
    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
    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
    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 
    229     MetricKey(String name, boolean success) {
    230       this.name = name;
    231       this.success = success;
    232     }
    233 
    234     @Override
    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
    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 
    265     Metadata(Map<Class<?>, Object> metadata) {
    266       this.metadata = new HashMap<>(metadata);
    267     }
    268 
    269     public <T> T get(Class<T> metadataClass) {
    270       return metadataClass.cast(metadata.get(metadataClass));
    271     }
    272   }
    273 }
    274