1 /*
2  * Copyright (C) 2016 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 
17 package android.util;
18 
19 import android.annotation.NonNull;
20 import android.os.Build;
21 import android.os.SystemClock;
22 import android.os.Trace;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.List;
29 
30 /**
31  * Helper class for reporting boot and shutdown timing metrics.
32  *
33  * <p><b>NOTE:</b> This class is not thread-safe. Use a separate copy for other threads.
34  *
35  * @hide
36  */
37 @android.ravenwood.annotation.RavenwoodKeepWholeClass
38 public class TimingsTraceLog {
39     // Debug boot time for every step if it's non-user build.
40     private static final boolean DEBUG_BOOT_TIME = !Build.IS_USER;
41 
42     // Maximum number of nested calls that are stored
43     private static final int MAX_NESTED_CALLS = 10;
44 
45     private final String[] mStartNames;
46     private final long[] mStartTimes;
47 
48     private final String mTag;
49     private final long mTraceTag;
50     private final long mThreadId;
51     private final int mMaxNestedCalls;
52 
53     private int mCurrentLevel = -1;
54 
TimingsTraceLog(String tag, long traceTag)55     public TimingsTraceLog(String tag, long traceTag) {
56         this(tag, traceTag, DEBUG_BOOT_TIME ? MAX_NESTED_CALLS : -1);
57     }
58 
59     @VisibleForTesting
TimingsTraceLog(String tag, long traceTag, int maxNestedCalls)60     public TimingsTraceLog(String tag, long traceTag, int maxNestedCalls) {
61         mTag = tag;
62         mTraceTag = traceTag;
63         mThreadId = Thread.currentThread().getId();
64         mMaxNestedCalls = maxNestedCalls;
65         this.mStartNames = createAndGetStartNamesArray();
66         this.mStartTimes = createAndGetStartTimesArray();
67     }
68 
69     /**
70      * Note: all fields will be copied except for {@code mStartNames} and {@code mStartTimes}
71      * in order to save memory. The copied object is only expected to be used at levels deeper than
72      * the value of {@code mCurrentLevel} when the object is copied.
73      *
74      * @param other object to be copied
75      */
TimingsTraceLog(TimingsTraceLog other)76     protected TimingsTraceLog(TimingsTraceLog other) {
77         this.mTag = other.mTag;
78         this.mTraceTag = other.mTraceTag;
79         this.mThreadId = Thread.currentThread().getId();
80         this.mMaxNestedCalls = other.mMaxNestedCalls;
81         this.mStartNames = createAndGetStartNamesArray();
82         this.mStartTimes = createAndGetStartTimesArray();
83         this.mCurrentLevel = other.mCurrentLevel;
84     }
85 
createAndGetStartNamesArray()86     private String[] createAndGetStartNamesArray() {
87         return mMaxNestedCalls > 0 ? new String[mMaxNestedCalls] : null;
88     }
89 
createAndGetStartTimesArray()90     private long[] createAndGetStartTimesArray() {
91         return mMaxNestedCalls > 0 ? new long[mMaxNestedCalls] : null;
92     }
93 
94     /**
95      * Begin tracing named section
96      * @param name name to appear in trace
97      */
traceBegin(String name)98     public void traceBegin(String name) {
99         assertSameThread();
100         Trace.traceBegin(mTraceTag, name);
101 
102         if (!DEBUG_BOOT_TIME) return;
103 
104         if (mCurrentLevel + 1 >= mMaxNestedCalls) {
105             Slog.w(mTag, "not tracing duration of '" + name + "' because already reached "
106                     + mMaxNestedCalls + " levels");
107             return;
108         }
109 
110         mCurrentLevel++;
111         mStartNames[mCurrentLevel] = name;
112         mStartTimes[mCurrentLevel] = SystemClock.elapsedRealtime();
113     }
114 
115     /**
116      * End tracing previously {@link #traceBegin(String) started} section.
117      *
118      * <p>Also {@link #logDuration logs} the duration.
119      */
traceEnd()120     public void traceEnd() {
121         assertSameThread();
122         Trace.traceEnd(mTraceTag);
123 
124         if (!DEBUG_BOOT_TIME) return;
125 
126         if (mCurrentLevel < 0) {
127             Slog.w(mTag, "traceEnd called more times than traceBegin");
128             return;
129         }
130 
131         final String name = mStartNames[mCurrentLevel];
132         final long duration = SystemClock.elapsedRealtime() - mStartTimes[mCurrentLevel];
133         mCurrentLevel--;
134 
135         logDuration(name, duration);
136     }
137 
assertSameThread()138     private void assertSameThread() {
139         final Thread currentThread = Thread.currentThread();
140         if (currentThread.getId() != mThreadId) {
141             throw new IllegalStateException("Instance of TimingsTraceLog can only be called from "
142                     + "the thread it was created on (tid: " + mThreadId + "), but was from "
143                     + currentThread.getName() + " (tid: " + currentThread.getId() + ")");
144         }
145     }
146 
147     /**
148      * Logs a duration so it can be parsed by external tools for performance reporting.
149      */
logDuration(String name, long timeMs)150     public void logDuration(String name, long timeMs) {
151         Slog.v(mTag, name + " took to complete: " + timeMs + "ms");
152     }
153 
154     /**
155      * Gets the names of the traces that {@link #traceBegin(String) have begun} but
156      * {@link #traceEnd() have not finished} yet.
157      *
158      * <p><b>NOTE:</b> this method is expensive and it should not be used in "production" - it
159      * should only be used for debugging purposes during development (and/or guarded by
160      * static {@code DEBUG} constants that are {@code false}).
161      */
162     @NonNull
getUnfinishedTracesForDebug()163     public final List<String> getUnfinishedTracesForDebug() {
164         if (mStartTimes == null || mCurrentLevel < 0) return Collections.emptyList();
165         final ArrayList<String> list = new ArrayList<>(mCurrentLevel + 1);
166         for (int i = 0; i <= mCurrentLevel; i++) {
167             list.add(mStartNames[i]);
168         }
169         return list;
170     }
171 }
172