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