1 /* 2 * Copyright (C) 2020 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.power; 18 19 import android.annotation.Nullable; 20 import android.app.ActivityManager; 21 import android.app.IActivityManager; 22 import android.os.Process; 23 import android.os.RemoteException; 24 import android.util.AtomicFile; 25 import android.util.Log; 26 import android.util.Slog; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 30 import java.io.File; 31 import java.io.FileOutputStream; 32 import java.io.FilenameFilter; 33 import java.io.IOException; 34 import java.io.PrintWriter; 35 import java.text.SimpleDateFormat; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.Date; 39 import java.util.List; 40 41 /** 42 * The shutdown check points are a recording of more detailed information of the origin of calls to 43 * system shutdown and reboot framework methods. 44 * 45 * @hide 46 */ 47 public final class ShutdownCheckPoints { 48 49 private static final String TAG = "ShutdownCheckPoints"; 50 51 private static final ShutdownCheckPoints INSTANCE = new ShutdownCheckPoints(); 52 53 private static final int MAX_CHECK_POINTS = 100; 54 private static final int MAX_DUMP_FILES = 20; 55 private static final SimpleDateFormat DATE_FORMAT = 56 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z"); 57 private static final File[] EMPTY_FILE_ARRAY = {}; 58 59 private final ArrayList<CheckPoint> mCheckPoints; 60 private final Injector mInjector; 61 ShutdownCheckPoints()62 private ShutdownCheckPoints() { 63 this(new Injector() { 64 @Override 65 public long currentTimeMillis() { 66 return System.currentTimeMillis(); 67 } 68 69 @Override 70 public int maxCheckPoints() { 71 return MAX_CHECK_POINTS; 72 } 73 74 @Override 75 public int maxDumpFiles() { 76 return MAX_DUMP_FILES; 77 } 78 79 @Override 80 public IActivityManager activityManager() { 81 return ActivityManager.getService(); 82 } 83 }); 84 } 85 86 @VisibleForTesting ShutdownCheckPoints(Injector injector)87 ShutdownCheckPoints(Injector injector) { 88 mCheckPoints = new ArrayList<>(); 89 mInjector = injector; 90 } 91 92 /** Records the stack trace of this {@link Thread} as a shutdown check point. */ recordCheckPoint(@ullable String reason)93 public static void recordCheckPoint(@Nullable String reason) { 94 INSTANCE.recordCheckPointInternal(reason); 95 } 96 97 /** Records the pid of the caller process as a shutdown check point. */ recordCheckPoint(int callerProcessId, @Nullable String reason)98 public static void recordCheckPoint(int callerProcessId, @Nullable String reason) { 99 INSTANCE.recordCheckPointInternal(callerProcessId, reason); 100 } 101 102 /** Records the {@link android.content.Intent} name and package as a shutdown check point. */ recordCheckPoint( String intentName, String packageName, @Nullable String reason)103 public static void recordCheckPoint( 104 String intentName, String packageName, @Nullable String reason) { 105 INSTANCE.recordCheckPointInternal(intentName, packageName, reason); 106 } 107 108 /** Serializes the recorded check points and writes them to given {@code printWriter}. */ dump(PrintWriter printWriter)109 public static void dump(PrintWriter printWriter) { 110 INSTANCE.dumpInternal(printWriter); 111 } 112 113 /** 114 * Creates a {@link Thread} that calls {@link #dump(PrintWriter)} on a rotating file created 115 * from given {@code baseFile} and a timestamp suffix. Older dump files are also deleted by this 116 * thread. 117 */ newDumpThread(File baseFile)118 public static Thread newDumpThread(File baseFile) { 119 return INSTANCE.newDumpThreadInternal(baseFile); 120 } 121 122 @VisibleForTesting recordCheckPointInternal(@ullable String reason)123 void recordCheckPointInternal(@Nullable String reason) { 124 recordCheckPointInternal(new SystemServerCheckPoint(mInjector.currentTimeMillis(), reason)); 125 Slog.v(TAG, "System server shutdown checkpoint recorded"); 126 } 127 128 @VisibleForTesting recordCheckPointInternal(int callerProcessId, @Nullable String reason)129 void recordCheckPointInternal(int callerProcessId, @Nullable String reason) { 130 long timestamp = mInjector.currentTimeMillis(); 131 recordCheckPointInternal(callerProcessId == Process.myPid() 132 ? new SystemServerCheckPoint(timestamp, reason) 133 : new BinderCheckPoint(timestamp, callerProcessId, reason)); 134 Slog.v(TAG, "Binder shutdown checkpoint recorded with pid=" + callerProcessId); 135 } 136 137 @VisibleForTesting recordCheckPointInternal(String intentName, String packageName, @Nullable String reason)138 void recordCheckPointInternal(String intentName, String packageName, @Nullable String reason) { 139 long timestamp = mInjector.currentTimeMillis(); 140 recordCheckPointInternal("android".equals(packageName) 141 ? new SystemServerCheckPoint(timestamp, reason) 142 : new IntentCheckPoint(timestamp, intentName, packageName, reason)); 143 Slog.v(TAG, String.format("Shutdown intent checkpoint recorded intent=%s from package=%s", 144 intentName, packageName)); 145 } 146 recordCheckPointInternal(CheckPoint checkPoint)147 private void recordCheckPointInternal(CheckPoint checkPoint) { 148 synchronized (mCheckPoints) { 149 mCheckPoints.add(checkPoint); 150 if (mCheckPoints.size() > mInjector.maxCheckPoints()) mCheckPoints.remove(0); 151 } 152 } 153 154 @VisibleForTesting dumpInternal(PrintWriter printWriter)155 void dumpInternal(PrintWriter printWriter) { 156 final List<CheckPoint> records; 157 synchronized (mCheckPoints) { 158 records = new ArrayList<>(mCheckPoints); 159 } 160 for (CheckPoint record : records) { 161 record.dump(mInjector, printWriter); 162 printWriter.println(); 163 } 164 } 165 166 @VisibleForTesting newDumpThreadInternal(File baseFile)167 Thread newDumpThreadInternal(File baseFile) { 168 return new FileDumperThread(this, baseFile, mInjector.maxDumpFiles()); 169 } 170 171 /** Injector used by {@link ShutdownCheckPoints} for testing purposes. */ 172 @VisibleForTesting 173 interface Injector { 174 currentTimeMillis()175 long currentTimeMillis(); 176 maxCheckPoints()177 int maxCheckPoints(); 178 maxDumpFiles()179 int maxDumpFiles(); 180 activityManager()181 IActivityManager activityManager(); 182 } 183 184 /** Representation of a generic shutdown call, which can be serialized. */ 185 private abstract static class CheckPoint { 186 187 private final long mTimestamp; 188 @Nullable private final String mReason; 189 CheckPoint(long timestamp, @Nullable String reason)190 CheckPoint(long timestamp, @Nullable String reason) { 191 mTimestamp = timestamp; 192 mReason = reason; 193 } 194 dump(Injector injector, PrintWriter printWriter)195 final void dump(Injector injector, PrintWriter printWriter) { 196 printWriter.print("Shutdown request from "); 197 printWriter.print(getOrigin()); 198 if (mReason != null) { 199 printWriter.print(" for reason "); 200 printWriter.print(mReason); 201 } 202 printWriter.print(" at "); 203 printWriter.print(DATE_FORMAT.format(new Date(mTimestamp))); 204 printWriter.println(" (epoch=" + mTimestamp + ")"); 205 dumpDetails(injector, printWriter); 206 } 207 getOrigin()208 abstract String getOrigin(); 209 dumpDetails(Injector injector, PrintWriter printWriter)210 abstract void dumpDetails(Injector injector, PrintWriter printWriter); 211 } 212 213 /** Representation of a shutdown call from the system server, with stack trace. */ 214 private static class SystemServerCheckPoint extends CheckPoint { 215 216 private final StackTraceElement[] mStackTraceElements; 217 SystemServerCheckPoint(long timestamp, @Nullable String reason)218 SystemServerCheckPoint(long timestamp, @Nullable String reason) { 219 super(timestamp, reason); 220 mStackTraceElements = Thread.currentThread().getStackTrace(); 221 } 222 223 @Override getOrigin()224 String getOrigin() { 225 return "SYSTEM"; 226 } 227 228 @Override dumpDetails(Injector injector, PrintWriter printWriter)229 void dumpDetails(Injector injector, PrintWriter printWriter) { 230 String methodName = findMethodName(); 231 printWriter.println(methodName == null ? "Failed to get method name" : methodName); 232 printStackTrace(printWriter); 233 } 234 235 @Nullable findMethodName()236 String findMethodName() { 237 int idx = findCallSiteIndex(); 238 if (idx < mStackTraceElements.length) { 239 StackTraceElement element = mStackTraceElements[idx]; 240 return String.format("%s.%s", element.getClassName(), element.getMethodName()); 241 } 242 return null; 243 } 244 printStackTrace(PrintWriter printWriter)245 void printStackTrace(PrintWriter printWriter) { 246 // Skip the call site line, as it's already considered with findMethodName. 247 for (int i = findCallSiteIndex() + 1; i < mStackTraceElements.length; i++) { 248 printWriter.print(" at "); 249 printWriter.println(mStackTraceElements[i]); 250 } 251 } 252 findCallSiteIndex()253 private int findCallSiteIndex() { 254 String className = ShutdownCheckPoints.class.getCanonicalName(); 255 int idx = 0; 256 // Skip system trace lines until finding ShutdownCheckPoints call site. 257 while (idx < mStackTraceElements.length 258 && !mStackTraceElements[idx].getClassName().equals(className)) { 259 ++idx; 260 } 261 // Skip trace lines from ShutdownCheckPoints class. 262 while (idx < mStackTraceElements.length 263 && mStackTraceElements[idx].getClassName().equals(className)) { 264 ++idx; 265 } 266 return idx; 267 } 268 } 269 270 /** Representation of a shutdown call to {@link android.os.Binder}, with caller process id. */ 271 private static class BinderCheckPoint extends SystemServerCheckPoint { 272 private final int mCallerProcessId; 273 BinderCheckPoint(long timestamp, int callerProcessId, @Nullable String reason)274 BinderCheckPoint(long timestamp, int callerProcessId, @Nullable String reason) { 275 super(timestamp, reason); 276 mCallerProcessId = callerProcessId; 277 } 278 279 @Override getOrigin()280 String getOrigin() { 281 return "BINDER"; 282 } 283 284 @Override dumpDetails(Injector injector, PrintWriter printWriter)285 void dumpDetails(Injector injector, PrintWriter printWriter) { 286 String methodName = findMethodName(); 287 printWriter.println(methodName == null ? "Failed to get method name" : methodName); 288 289 String processName = findProcessName(injector.activityManager()); 290 printWriter.print("From process "); 291 printWriter.print(processName == null ? "?" : processName); 292 printWriter.println(" (pid=" + mCallerProcessId + ")"); 293 } 294 295 @Nullable findProcessName(@ullable IActivityManager activityManager)296 private String findProcessName(@Nullable IActivityManager activityManager) { 297 try { 298 List<ActivityManager.RunningAppProcessInfo> runningProcesses = null; 299 if (activityManager != null) { 300 runningProcesses = activityManager.getRunningAppProcesses(); 301 } else { 302 Slog.v(TAG, "No ActivityManager to find name of process with pid=" 303 + mCallerProcessId); 304 } 305 if (runningProcesses != null) { 306 for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { 307 if (processInfo.pid == mCallerProcessId) { 308 return processInfo.processName; 309 } 310 } 311 } 312 } catch (RemoteException e) { 313 Slog.e(TAG, "Failed to get running app processes from ActivityManager", e); 314 } 315 return null; 316 } 317 } 318 319 /** Representation of a shutdown call with {@link android.content.Intent}. */ 320 private static class IntentCheckPoint extends CheckPoint { 321 private final String mIntentName; 322 private final String mPackageName; 323 IntentCheckPoint( long timestamp, String intentName, String packageName, @Nullable String reason)324 IntentCheckPoint( 325 long timestamp, String intentName, String packageName, @Nullable String reason) { 326 super(timestamp, reason); 327 mIntentName = intentName; 328 mPackageName = packageName; 329 } 330 331 @Override getOrigin()332 String getOrigin() { 333 return "INTENT"; 334 } 335 336 @Override dumpDetails(Injector injector, PrintWriter printWriter)337 void dumpDetails(Injector injector, PrintWriter printWriter) { 338 printWriter.print("Intent: "); 339 printWriter.println(mIntentName); 340 printWriter.print("Package: "); 341 printWriter.println(mPackageName); 342 } 343 } 344 345 /** 346 * Thread that writes {@link ShutdownCheckPoints#dumpInternal(PrintWriter)} to a new file and 347 * deletes old ones to keep the total number of files down to a given limit. 348 */ 349 private static final class FileDumperThread extends Thread { 350 351 private final ShutdownCheckPoints mInstance; 352 private final File mBaseFile; 353 private final int mFileCountLimit; 354 FileDumperThread(ShutdownCheckPoints instance, File baseFile, int fileCountLimit)355 FileDumperThread(ShutdownCheckPoints instance, File baseFile, int fileCountLimit) { 356 mInstance = instance; 357 mBaseFile = baseFile; 358 mFileCountLimit = fileCountLimit; 359 } 360 361 @Override run()362 public void run() { 363 mBaseFile.getParentFile().mkdirs(); 364 File[] checkPointFiles = listCheckPointsFiles(); 365 366 int filesToDelete = checkPointFiles.length - mFileCountLimit + 1; 367 for (int i = 0; i < filesToDelete; i++) { 368 checkPointFiles[i].delete(); 369 } 370 371 File nextCheckPointsFile = new File(String.format("%s-%d", 372 mBaseFile.getAbsolutePath(), System.currentTimeMillis())); 373 writeCheckpoints(nextCheckPointsFile); 374 } 375 listCheckPointsFiles()376 private File[] listCheckPointsFiles() { 377 String filePrefix = mBaseFile.getName() + "-"; 378 File[] files = mBaseFile.getParentFile().listFiles(new FilenameFilter() { 379 @Override 380 public boolean accept(File dir, String name) { 381 if (!name.startsWith(filePrefix)) { 382 return false; 383 } 384 try { 385 Long.valueOf(name.substring(filePrefix.length())); 386 } catch (NumberFormatException e) { 387 return false; 388 } 389 return true; 390 } 391 }); 392 if (files == null) { 393 return EMPTY_FILE_ARRAY; 394 } 395 Arrays.sort(files); 396 return files; 397 } 398 writeCheckpoints(File file)399 private void writeCheckpoints(File file) { 400 AtomicFile tmpFile = new AtomicFile(mBaseFile); 401 FileOutputStream fos = null; 402 try { 403 fos = tmpFile.startWrite(); 404 PrintWriter pw = new PrintWriter(fos); 405 mInstance.dumpInternal(pw); 406 pw.flush(); 407 tmpFile.finishWrite(fos); // This also closes the output stream. 408 } catch (IOException e) { 409 Log.e(TAG, "Failed to write shutdown checkpoints", e); 410 if (fos != null) { 411 tmpFile.failWrite(fos); // This also closes the output stream. 412 } 413 } 414 mBaseFile.renameTo(file); 415 } 416 } 417 } 418