• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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