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