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 */ 16package com.android.mail.perf; 17 18import android.os.Debug; 19import android.os.SystemClock; 20 21import com.android.mail.utils.LogTag; 22import com.android.mail.utils.LogUtils; 23import com.google.common.collect.Maps; 24 25import java.util.ArrayList; 26import java.util.Map; 27import 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 */ 58public 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