Home | History | Annotate | Download | only in perf
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.mail.perf;
     17 
     18 import android.os.Debug;
     19 import android.os.SystemClock;
     20 
     21 import com.android.mail.utils.LogTag;
     22 import com.android.mail.utils.LogUtils;
     23 import com.google.common.collect.Maps;
     24 
     25 import java.util.ArrayList;
     26 import java.util.Map;
     27 import java.util.concurrent.ConcurrentHashMap;
     28 
     29 /**
     30  * Performance timing utilities for Gmail.
     31  *
     32  * There are two main ways to log performance. For simple, one off events, the static methods
     33  * {@link #startTiming} and {@link #stopTiming} are sufficient:
     34  *
     35  * <pre>
     36  * Timer.startTiming("myEvent");
     37  * ... code for myEvent ...
     38  * Timer.stopTiming("myEvent");
     39  * </pre>
     40  *
     41  * The other way is to instantiate a timer that can be passed around, and started and paused. The
     42  * timer will accumulate the results for each tag, and dump the results when asked.
     43  *
     44  * <pre>
     45  * Timer timer = new Timer();
     46  *
     47  * for (int i = 0; i < lots; i++) {
     48  *   timer.start("tagA");
     49  *   ... code for tagA ...
     50  *   timer.pause("tagA");
     51  *   ... code that isn't relevant to timing ...
     52  * }
     53  *
     54  * timer.dumpResults();
     55  * </pre>
     56  *
     57  */
     58 public class Timer {
     59     private static final String LOG_TAG = LogTag.getLogTag();
     60 
     61     // set this to false to compile out all timer calls
     62     public static final boolean ENABLE_TIMER = false;
     63     // Set to true to enable logging of performance stats.
     64     private static final boolean LOG_PERFORMANCE_STATS = true;
     65 
     66     /** An internal structure used for performance markers. */
     67     private static class PerformancePoint {
     68         public final long mCpuTimeNanos;
     69         public final long mWallTimeMillis;
     70 
     71         public PerformancePoint() {
     72             mCpuTimeNanos = Debug.threadCpuTimeNanos();
     73             mWallTimeMillis = SystemClock.uptimeMillis();
     74         }
     75     }
     76 
     77     private final Map<String, ArrayList<PerformancePoint>> mPoints = Maps.newHashMap();
     78     private final Map<String, Integer> mCounts = Maps.newHashMap();
     79     private final boolean mEnabled;
     80 
     81     public Timer() {
     82         this(false);
     83     }
     84 
     85     public Timer(boolean enable) {
     86         mEnabled = enable;
     87     }
     88 
     89     @SuppressWarnings("unused")
     90     public boolean isEnabled() {
     91         return ENABLE_TIMER && mEnabled;
     92     }
     93 
     94     /**
     95      * Starts timing an event indicated by the {@code tag}.
     96      */
     97     @SuppressWarnings("unused")
     98     public void start(String tag) {
     99         if (ENABLE_TIMER && mEnabled) {
    100             ArrayList<PerformancePoint> values = mPoints.get(tag);
    101             if (values == null) {
    102                 values = new ArrayList<PerformancePoint>();
    103                 mPoints.put(tag, values);
    104             }
    105             if (values.size() % 2 == 0) {
    106                 values.add(new PerformancePoint());
    107             }
    108         }
    109     }
    110 
    111     /**
    112      * Stops timing an event indicated by the {@code tag}
    113      */
    114     @SuppressWarnings("unused")
    115     public void pause(String tag) {
    116         if (ENABLE_TIMER && mEnabled) {
    117             ArrayList<PerformancePoint> values = mPoints.get(tag);
    118             if (values != null && values.size() % 2 == 1) {
    119                 values.add(new PerformancePoint());
    120             }
    121         }
    122     }
    123 
    124     @SuppressWarnings("unused")
    125     public void count(String tag) {
    126         if (ENABLE_TIMER && mEnabled) {
    127             Integer counts = mCounts.get(tag);
    128             if (counts == null) {
    129                 counts = 0;
    130             }
    131             mCounts.put(tag, counts + 1);
    132         }
    133     }
    134 
    135     @SuppressWarnings("unused")
    136     public void clear() {
    137         if (ENABLE_TIMER && mEnabled) {
    138             mPoints.clear();
    139             mCounts.clear();
    140         }
    141     }
    142 
    143     /**
    144      * Dumps cumulative timing results for all tags recognized by this timer.
    145      */
    146     @SuppressWarnings("unused")
    147     public void dumpResults() {
    148         if (ENABLE_TIMER && mEnabled) {
    149             for (Map.Entry<String, ArrayList<PerformancePoint>> entry : mPoints.entrySet()) {
    150                 String tag = entry.getKey();
    151                 ArrayList<PerformancePoint> values = entry.getValue();
    152                 long cpuDurationNanos = 0;
    153                 long wallDurationMillis = 0;
    154 
    155                 for (int i = 0; i < values.size() - 1; i += 2) {
    156                     PerformancePoint startPoint = values.get(i);
    157                     PerformancePoint endPoint = values.get(i + 1);
    158 
    159                     cpuDurationNanos += endPoint.mCpuTimeNanos - startPoint.mCpuTimeNanos;
    160                     wallDurationMillis += endPoint.mWallTimeMillis - startPoint.mWallTimeMillis;
    161                 }
    162 
    163                 if (cpuDurationNanos == 0) {
    164                     cpuDurationNanos = 1;
    165                 }
    166 
    167                 dumpTimings(tag, 1, cpuDurationNanos, wallDurationMillis);
    168             }
    169 
    170             if (LOG_PERFORMANCE_STATS) {
    171                 for (Map.Entry<String, Integer> entry : mCounts.entrySet()) {
    172                     LogUtils.d(LOG_TAG, "Perf %s count: %d", entry.getKey(), entry.getValue());
    173                 }
    174             }
    175         }
    176     }
    177 
    178     /**
    179      * Used for timing one off events.
    180      */
    181     private static Map<String, PerformancePoint> sPerformanceCollector =
    182             new ConcurrentHashMap<String, PerformancePoint>();
    183 
    184     /**
    185      * Starts a one-off timer for an event. The event is denoted by {@code tag} and only one event
    186      * of that tag may be timed at a given time.
    187      */
    188     public static void startTiming(String tag) {
    189         if (ENABLE_TIMER) {
    190             sPerformanceCollector.put(tag, new PerformancePoint());
    191         }
    192     }
    193 
    194     /**
    195      * Stops a one-off timer for an event indicated by {@code tag}.
    196      */
    197     public static void stopTiming(String tag) {
    198         if (ENABLE_TIMER) {
    199             stopTiming(tag, 1 /* one subiteration */);
    200         }
    201     }
    202 
    203     /**
    204      * Stops a one-off timer for an event indicated by {@code tag}, and indicates that the event
    205      * consisted of {@code numSubIterations} sub-events, so that performance output will be denoted
    206      * as such.
    207      */
    208     public static void stopTiming(String tag, int numSubIterations) {
    209         if (ENABLE_TIMER) {
    210             PerformancePoint endPoint = new PerformancePoint();
    211             PerformancePoint startPoint = sPerformanceCollector.get(tag);
    212             if (startPoint == null) {
    213                 return;
    214             }
    215             long cpuDurationNanos = endPoint.mCpuTimeNanos - startPoint.mCpuTimeNanos;
    216             long wallDurationMillis = endPoint.mWallTimeMillis - startPoint.mWallTimeMillis;
    217             // Make sure cpu Duration is non 0
    218             if (cpuDurationNanos == 0) {
    219                 cpuDurationNanos = 1;
    220             }
    221 
    222             dumpTimings(tag, numSubIterations, cpuDurationNanos, wallDurationMillis);
    223         }
    224     }
    225 
    226     private static void dumpTimings(String tag, int numSubIterations,
    227             long cpuDurationNanos, long wallDurationMillis) {
    228 
    229         if (LOG_PERFORMANCE_STATS) {
    230             LogUtils.d(LOG_TAG, "Perf %s wall: %d cpu: %d",
    231                     tag, wallDurationMillis, (cpuDurationNanos / 1000000));
    232             // print out the average time for each sub iteration
    233             if (numSubIterations > 1) {
    234                 LogUtils.d(LOG_TAG, "Perf/operation %s wall: %d cpu: %d", tag,
    235                         (wallDurationMillis / numSubIterations),
    236                         ((cpuDurationNanos / 1000000) / numSubIterations));
    237             }
    238         }
    239     }
    240 
    241 }
    242