ActivityMetricsLogger.java revision 8347163dbb64fb61012c0393163283106a0a351e
1package com.android.server.am;
2
3import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
4import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
5import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
6import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
7import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
8
9import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
10import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
11import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
12
13import android.annotation.Nullable;
14import android.app.ActivityManager.StackId;
15import android.content.Context;
16import android.os.SystemClock;
17import android.util.Slog;
18
19import android.metrics.LogMaker;
20import com.android.internal.logging.MetricsLogger;
21import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
22
23import java.util.ArrayList;
24
25/**
26 * Handles logging into Tron.
27 */
28class ActivityMetricsLogger {
29
30    private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityMetricsLogger" : TAG_AM;
31
32    // Window modes we are interested in logging. If we ever introduce a new type, we need to add
33    // a value here and increase the {@link #TRON_WINDOW_STATE_VARZ_STRINGS} array.
34    private static final int WINDOW_STATE_STANDARD = 0;
35    private static final int WINDOW_STATE_SIDE_BY_SIDE = 1;
36    private static final int WINDOW_STATE_FREEFORM = 2;
37    private static final int WINDOW_STATE_ASSISTANT = 3;
38    private static final int WINDOW_STATE_INVALID = -1;
39
40    private static final long INVALID_START_TIME = -1;
41
42    // Preallocated strings we are sending to tron, so we don't have to allocate a new one every
43    // time we log.
44    private static final String[] TRON_WINDOW_STATE_VARZ_STRINGS = {
45            "window_time_0", "window_time_1", "window_time_2", "window_time_3"};
46
47    private int mWindowState = WINDOW_STATE_STANDARD;
48    private long mLastLogTimeSecs;
49    private final ActivityStackSupervisor mSupervisor;
50    private final Context mContext;
51
52    private long mCurrentTransitionStartTime = INVALID_START_TIME;
53    private boolean mLoggedWindowsDrawn;
54    private boolean mLoggedStartingWindowDrawn;
55    private boolean mLoggedTransitionStarting;
56
57    ActivityMetricsLogger(ActivityStackSupervisor supervisor, Context context) {
58        mLastLogTimeSecs = SystemClock.elapsedRealtime() / 1000;
59        mSupervisor = supervisor;
60        mContext = context;
61    }
62
63    void logWindowState() {
64        final long now = SystemClock.elapsedRealtime() / 1000;
65        if (mWindowState != WINDOW_STATE_INVALID) {
66            // We log even if the window state hasn't changed, because the user might remain in
67            // home/fullscreen move forever and we would like to track this kind of behavior
68            // too.
69            MetricsLogger.count(mContext, TRON_WINDOW_STATE_VARZ_STRINGS[mWindowState],
70                    (int) (now - mLastLogTimeSecs));
71        }
72        mLastLogTimeSecs = now;
73
74        ActivityStack stack = mSupervisor.getStack(DOCKED_STACK_ID);
75        if (stack != null && stack.getStackVisibilityLocked(null) != STACK_INVISIBLE) {
76            mWindowState = WINDOW_STATE_SIDE_BY_SIDE;
77            return;
78        }
79        mWindowState = WINDOW_STATE_INVALID;
80        stack = mSupervisor.getFocusedStack();
81        if (stack.mStackId == PINNED_STACK_ID) {
82            stack = mSupervisor.findStackBehind(stack);
83        }
84        if (StackId.isHomeOrRecentsStack(stack.mStackId)
85                || stack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
86            mWindowState = WINDOW_STATE_STANDARD;
87        } else if (stack.mStackId == DOCKED_STACK_ID) {
88            Slog.wtf(TAG, "Docked stack shouldn't be the focused stack, because it reported not"
89                    + " being visible.");
90            mWindowState = WINDOW_STATE_INVALID;
91        } else if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
92            mWindowState = WINDOW_STATE_FREEFORM;
93        } else if (stack.mStackId == ASSISTANT_STACK_ID) {
94            mWindowState = WINDOW_STATE_ASSISTANT;
95        } else if (StackId.isStaticStack(stack.mStackId)) {
96            throw new IllegalStateException("Unknown stack=" + stack);
97        }
98    }
99
100    /**
101     * Notifies the tracker at the earliest possible point when we are starting to launch an
102     * activity.
103     */
104    void notifyActivityLaunching() {
105        mCurrentTransitionStartTime = System.currentTimeMillis();
106    }
107
108    /**
109     * Notifies the tracker that the activity is actually launching.
110     *
111     * @param resultCode one of the ActivityManager.START_* flags, indicating the result of the
112     *                   launch
113     * @param launchedActivity the activity that is being launched
114     */
115    void notifyActivityLaunched(int resultCode, ActivityRecord launchedActivity) {
116        final ProcessRecord processRecord = launchedActivity != null
117                ? mSupervisor.mService.mProcessNames.get(launchedActivity.processName,
118                        launchedActivity.appInfo.uid)
119                : null;
120        final boolean processRunning = processRecord != null;
121        final String componentName = launchedActivity != null
122                ? launchedActivity.shortComponentName
123                : null;
124
125        // We consider this a "process switch" if the process of the activity that gets launched
126        // didn't have an activity that was in started state. In this case, we assume that lot
127        // of caches might be purged so the time until it produces the first frame is very
128        // interesting.
129        final boolean processSwitch = processRecord == null
130                || !hasStartedActivity(processRecord, launchedActivity);
131
132        notifyActivityLaunched(resultCode, componentName, processRunning, processSwitch);
133    }
134
135    private boolean hasStartedActivity(ProcessRecord record, ActivityRecord launchedActivity) {
136        final ArrayList<ActivityRecord> activities = record.activities;
137        for (int i = activities.size() - 1; i >= 0; i--) {
138            final ActivityRecord activity = activities.get(i);
139            if (launchedActivity == activity) {
140                continue;
141            }
142            if (!activity.stopped) {
143                return true;
144            }
145        }
146        return false;
147    }
148
149    /**
150     * Notifies the tracker the the activity is actually launching.
151     *
152     * @param resultCode one of the ActivityManager.START_* flags, indicating the result of the
153     *                   launch
154     * @param componentName the component name of the activity being launched
155     * @param processRunning whether the process that will contains the activity is already running
156     * @param processSwitch whether the process that will contain the activity didn't have any
157     *                      activity that was stopped, i.e. the started activity is "switching"
158     *                      processes
159     */
160    private void notifyActivityLaunched(int resultCode, @Nullable String componentName,
161            boolean processRunning, boolean processSwitch) {
162
163        if (resultCode < 0 || componentName == null || !processSwitch) {
164
165            // Failed to launch or it was not a process switch, so we don't care about the timing.
166            reset();
167            return;
168        }
169
170        MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_COMPONENT_NAME,
171                componentName);
172        MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_PROCESS_RUNNING,
173                processRunning);
174        MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS,
175                (int) (SystemClock.uptimeMillis() / 1000));
176
177        LogMaker builder = new LogMaker(MetricsEvent.APP_TRANSITION);
178        builder.addTaggedData(MetricsEvent.APP_TRANSITION_COMPONENT_NAME, componentName);
179        builder.addTaggedData(MetricsEvent.APP_TRANSITION_PROCESS_RUNNING, processRunning ? 1 : 0);
180        builder.addTaggedData(MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS,
181                SystemClock.uptimeMillis() / 1000);
182        MetricsLogger.action(builder);
183    }
184
185    /**
186     * Notifies the tracker that all windows of the app have been drawn.
187     */
188    void notifyWindowsDrawn() {
189        if (!isTransitionActive() || mLoggedWindowsDrawn) {
190            return;
191        }
192        MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS,
193                calculateCurrentDelay());
194        mLoggedWindowsDrawn = true;
195        if (mLoggedTransitionStarting) {
196            reset();
197        }
198    }
199
200    /**
201     * Notifies the tracker that the starting window was drawn.
202     */
203    void notifyStartingWindowDrawn() {
204        if (!isTransitionActive() || mLoggedStartingWindowDrawn) {
205            return;
206        }
207        mLoggedStartingWindowDrawn = true;
208        MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_STARTING_WINDOW_DELAY_MS,
209                calculateCurrentDelay());
210    }
211
212    /**
213     * Notifies the tracker that the app transition is starting.
214     *
215     * @param reason The reason why we started it. Must be on of
216     *               ActivityManagerInternal.APP_TRANSITION_* reasons.
217     */
218    void notifyTransitionStarting(int reason) {
219        if (!isTransitionActive() || mLoggedTransitionStarting) {
220            return;
221        }
222        MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_REASON, reason);
223        MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_DELAY_MS,
224                calculateCurrentDelay());
225        mLoggedTransitionStarting = true;
226        if (mLoggedWindowsDrawn) {
227            reset();
228        }
229    }
230
231    private boolean isTransitionActive() {
232        return mCurrentTransitionStartTime != INVALID_START_TIME;
233    }
234
235    private void reset() {
236        mCurrentTransitionStartTime = INVALID_START_TIME;
237        mLoggedWindowsDrawn = false;
238        mLoggedTransitionStarting = false;
239        mLoggedStartingWindowDrawn = false;
240    }
241
242    private int calculateCurrentDelay() {
243
244        // Shouldn't take more than 25 days to launch an app, so int is fine here.
245        return (int) (System.currentTimeMillis() - mCurrentTransitionStartTime);
246    }
247}
248