• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 com.android.server.wifi;
18 
19 import static com.android.server.wifi.proto.WifiStatsLog.WIFI_THREAD_TASK_EXECUTED;
20 
21 import android.annotation.NonNull;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.HandlerThread;
25 import android.os.Looper;
26 import android.os.Message;
27 import android.os.SystemClock;
28 import android.os.Trace;
29 import android.util.LocalLog;
30 
31 import com.android.modules.utils.HandlerExecutor;
32 import com.android.server.wifi.proto.WifiStatsLog;
33 
34 import java.util.HashSet;
35 import java.util.Set;
36 
37 /**
38  * RunnerHandler tracks all the Runnable jobs posted to the handler for the running time and
39  * monitor if the running time exceeds the expected threshold.
40  *
41  */
42 public class RunnerHandler extends Handler {
43     private static final String TAG = "WifiThreadRunner";
44 
45     public static final String KEY_SIGNATURE = "KEY_RUNNER_HANDLER_SIGNATURE";
46     private static final String KEY_WHEN = "KEY_RUNNER_HANDLER_WHEN";
47     private static final int METRICS_THRESHOLD_MILLIS = 100;
48 
49     private final int mRunningTimeThresholdInMilliseconds;
50     private Set<String> mIgnoredClasses = new HashSet<>();
51     private Set<String> mIgnoredMethods = new HashSet<>();
52 
53     // TODO: b/246623192 Add Wifi metric for Runner state overruns.
54     private final LocalLog mLocalLog;
55     private boolean mVerboseLoggingEnabled = false;
56 
57     /**
58      * The Runner handler Constructor
59      *
60      * @param looper looper for the handler
61      * @param threshold the running time threshold in milliseconds
62      */
RunnerHandler(Looper looper, int threshold, @NonNull LocalLog localLog)63     public RunnerHandler(Looper looper, int threshold, @NonNull LocalLog localLog) {
64         super(looper);
65         mRunningTimeThresholdInMilliseconds = threshold;
66         mLocalLog = localLog;
67         mIgnoredClasses.add(WifiThreadRunner.class.getName());
68         mIgnoredClasses.add(WifiThreadRunner.class.getName() + "$BlockingRunnable");
69         mIgnoredClasses.add(RunnerHandler.class.getName());
70         mIgnoredClasses.add(HandlerExecutor.class.getName());
71         mIgnoredClasses.add(Handler.class.getName());
72         mIgnoredClasses.add(HandlerThread.class.getName());
73         mIgnoredClasses.add(Looper.class.getName());
74         mIgnoredMethods.add("handleMessage");
75     }
76 
77     /** Enable/disable verbose logging. */
enableVerboseLogging(boolean verboseEnabled)78     public void enableVerboseLogging(boolean verboseEnabled) {
79         mVerboseLoggingEnabled = verboseEnabled;
80     }
81 
getSignature(StackTraceElement[] elements, Runnable callback)82     private String getSignature(StackTraceElement[] elements, Runnable callback) {
83         StringBuilder sb = new StringBuilder();
84         for (StackTraceElement e : elements) {
85             // Go through the stack elements to find out the caller who schedule the job.
86             // Ignore the stack frames generated with ignored classes and methods, until the stack
87             // frame where the runnable job is posted to the handler.
88             if (!mIgnoredClasses.contains(e.getClassName()) && !mIgnoredMethods.contains(
89                     e.getMethodName())) {
90                 String[] nameArr = e.getClassName().split("\\.", 5);
91                 final int len = nameArr.length;
92                 if (len > 0) {
93                     sb.append(nameArr[len - 1]).append("#").append(e.getMethodName());
94                     break;
95                 }
96             }
97             // The callback is the lambada function posted as Runnable#run function.
98             // If we can't identify the caller from the stack trace, then we will use the symbol
99             // of the lambada function as the signature of the caller.
100             if (HandlerThread.class.getName().equals(e.getClassName())) {
101                 sb.append(callback);
102                 break;
103             }
104         }
105         return sb.length() == 0 ? "<UNKNOWN>" : sb.toString();
106     }
107 
108     @Override
sendMessageAtTime(Message msg, long uptimeMillis)109     public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
110         Bundle bundle = msg.getData();
111         if (bundle.getString(KEY_SIGNATURE) == null) {
112             String signature = getSignature(new Throwable("RunnerHandler:").getStackTrace(),
113                     msg.getCallback());
114             bundle.putString(KEY_SIGNATURE, signature);
115         }
116         return super.sendMessageAtTime(msg, uptimeMillis);
117     }
118 
119     @Override
dispatchMessage(@onNull Message msg)120     public void dispatchMessage(@NonNull Message msg) {
121         final Bundle bundle = msg.getData();
122         final String signature = bundle.getString(KEY_SIGNATURE);
123         boolean traceEvent = mVerboseLoggingEnabled;
124         if (signature != null && traceEvent) {
125             Trace.traceBegin(Trace.TRACE_TAG_NETWORK, signature);
126         }
127         // The message sent to front of the queue has when=0, get from the bundle in that case.
128         final long when = msg.getWhen() != 0 ? msg.getWhen() : bundle.getLong(KEY_WHEN);
129         final long start = SystemClock.uptimeMillis();
130         final long scheduleLatency = start - when;
131         super.dispatchMessage(msg);
132         final long runTime = SystemClock.uptimeMillis() - start;
133         if (signature != null && traceEvent) {
134             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
135         }
136         final String signatureToLog = signature != null ? signature : "unknown";
137         if (runTime > mRunningTimeThresholdInMilliseconds) {
138             mLocalLog.log(signatureToLog + " was running for " + runTime);
139         }
140         if (scheduleLatency > WifiThreadRunner.getScissorsTimeoutThreshold()) {
141             mLocalLog.log(signatureToLog + " schedule latency " + scheduleLatency + " ms");
142         }
143         if (runTime > METRICS_THRESHOLD_MILLIS || scheduleLatency > METRICS_THRESHOLD_MILLIS) {
144             WifiStatsLog.write(
145                     WIFI_THREAD_TASK_EXECUTED,
146                     (int) runTime,
147                     (int) scheduleLatency,
148                     signatureToLog);
149         }
150     }
151 
152     /**
153      * Use this helper function rather than directly calling Handler#postAtFrontOfQueue, which does
154      * not call sendMessageAtTime and set the signature. This function will set the signature
155      * before enqueueing the message to front of the queue.
156      * @param r runnable to be queued to the front
157      * @return true when success
158      */
postToFront(@onNull Runnable r)159     public final boolean postToFront(@NonNull Runnable r) {
160         Message msg = Message.obtain(this, r);
161         String signature = getSignature(new Throwable("RunnerHandler:").getStackTrace(),
162                 msg.getCallback());
163         Bundle bundle = msg.getData();
164         bundle.putString(KEY_SIGNATURE, signature);
165         bundle.putLong(KEY_WHEN, SystemClock.uptimeMillis());
166         return sendMessageAtFrontOfQueue(msg);
167     }
168 }
169