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 
PerformancePoint()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 
Timer()81     public Timer() {
82         this(false);
83     }
84 
Timer(boolean enable)85     public Timer(boolean enable) {
86         mEnabled = enable;
87     }
88 
89     @SuppressWarnings("unused")
isEnabled()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")
start(String tag)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")
pause(String tag)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")
count(String tag)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")
clear()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")
dumpResults()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      */
startTiming(String tag)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      */
stopTiming(String tag)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      */
stopTiming(String tag, int numSubIterations)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 
dumpTimings(String tag, int numSubIterations, long cpuDurationNanos, long wallDurationMillis)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