Home | History | Annotate | Download | only in wm
      1 /*
      2  * Copyright (C) 2017 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.wm;
     18 
     19 import static android.os.Build.IS_USER;
     20 import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
     21 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
     22 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
     23 import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
     24 import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS;
     25 import static com.android.server.wm.WindowManagerTraceProto.WHERE;
     26 import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE;
     27 
     28 import android.content.Context;
     29 import android.os.ShellCommand;
     30 import android.os.SystemClock;
     31 import android.os.Trace;
     32 import android.annotation.Nullable;
     33 import android.util.Log;
     34 import android.util.proto.ProtoOutputStream;
     35 
     36 import com.android.internal.annotations.VisibleForTesting;
     37 
     38 import java.io.File;
     39 import java.io.FileOutputStream;
     40 import java.io.IOException;
     41 import java.io.OutputStream;
     42 import java.io.PrintWriter;
     43 import java.util.concurrent.ArrayBlockingQueue;
     44 import java.util.concurrent.BlockingQueue;
     45 
     46 /**
     47  * A class that allows window manager to dump its state continuously to a trace file, such that a
     48  * time series of window manager state can be analyzed after the fact.
     49  */
     50 class WindowTracing {
     51 
     52     private static final String TAG = "WindowTracing";
     53     private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
     54 
     55     private final Object mLock = new Object();
     56     private final File mTraceFile;
     57     private final BlockingQueue<ProtoOutputStream> mWriteQueue = new ArrayBlockingQueue<>(200);
     58 
     59     private boolean mEnabled;
     60     private volatile boolean mEnabledLockFree;
     61 
     62     WindowTracing(File file) {
     63         mTraceFile = file;
     64     }
     65 
     66     void startTrace(@Nullable PrintWriter pw) throws IOException {
     67         if (IS_USER){
     68             logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
     69             return;
     70         }
     71         synchronized (mLock) {
     72             logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
     73             mWriteQueue.clear();
     74             mTraceFile.delete();
     75             try (OutputStream os = new FileOutputStream(mTraceFile)) {
     76                 mTraceFile.setReadable(true, false);
     77                 ProtoOutputStream proto = new ProtoOutputStream(os);
     78                 proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
     79                 proto.flush();
     80             }
     81             mEnabled = mEnabledLockFree = true;
     82         }
     83     }
     84 
     85     private void logAndPrintln(@Nullable PrintWriter pw, String msg) {
     86         Log.i(TAG, msg);
     87         if (pw != null) {
     88             pw.println(msg);
     89             pw.flush();
     90         }
     91     }
     92 
     93     void stopTrace(@Nullable PrintWriter pw) {
     94         if (IS_USER){
     95             logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
     96             return;
     97         }
     98         synchronized (mLock) {
     99             logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
    100             mEnabled = mEnabledLockFree = false;
    101             while (!mWriteQueue.isEmpty()) {
    102                 if (mEnabled) {
    103                     logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush.");
    104                     throw new IllegalStateException("tracing enabled while waiting for flush.");
    105                 }
    106                 try {
    107                     mLock.wait();
    108                     mLock.notify();
    109                 } catch (InterruptedException e) {
    110                     Thread.currentThread().interrupt();
    111                 }
    112             }
    113             logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
    114         }
    115     }
    116 
    117     void appendTraceEntry(ProtoOutputStream proto) {
    118         if (!mEnabledLockFree) {
    119             return;
    120         }
    121 
    122         if (!mWriteQueue.offer(proto)) {
    123             Log.e(TAG, "Dropping window trace entry, queue full");
    124         }
    125     }
    126 
    127     void loop() {
    128         for (;;) {
    129             loopOnce();
    130         }
    131     }
    132 
    133     @VisibleForTesting
    134     void loopOnce() {
    135         ProtoOutputStream proto;
    136         try {
    137             proto = mWriteQueue.take();
    138         } catch (InterruptedException e) {
    139             Thread.currentThread().interrupt();
    140             return;
    141         }
    142 
    143         synchronized (mLock) {
    144             try {
    145                 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToFile");
    146                 try (OutputStream os = new FileOutputStream(mTraceFile, true /* append */)) {
    147                     os.write(proto.getBytes());
    148                 }
    149             } catch (IOException e) {
    150                 Log.e(TAG, "Failed to write file " + mTraceFile, e);
    151             } finally {
    152                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
    153             }
    154             mLock.notify();
    155         }
    156     }
    157 
    158     boolean isEnabled() {
    159         return mEnabledLockFree;
    160     }
    161 
    162     static WindowTracing createDefaultAndStartLooper(Context context) {
    163         File file = new File("/data/misc/wmtrace/wm_trace.pb");
    164         WindowTracing windowTracing = new WindowTracing(file);
    165         if (!IS_USER){
    166             new Thread(windowTracing::loop, "window_tracing").start();
    167         }
    168         return windowTracing;
    169     }
    170 
    171     int onShellCommand(ShellCommand shell, String cmd) {
    172         PrintWriter pw = shell.getOutPrintWriter();
    173         try {
    174             switch (cmd) {
    175                 case "start":
    176                     startTrace(pw);
    177                     return 0;
    178                 case "stop":
    179                     stopTrace(pw);
    180                     return 0;
    181                 default:
    182                     pw.println("Unknown command: " + cmd);
    183                     return -1;
    184             }
    185         } catch (IOException e) {
    186             logAndPrintln(pw, e.toString());
    187             throw new RuntimeException(e);
    188         }
    189     }
    190 
    191     void traceStateLocked(String where, WindowManagerService service) {
    192         if (!isEnabled()) {
    193             return;
    194         }
    195         ProtoOutputStream os = new ProtoOutputStream();
    196         long tokenOuter = os.start(ENTRY);
    197         os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
    198         os.write(WHERE, where);
    199 
    200         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked");
    201         try {
    202             long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
    203             service.writeToProtoLocked(os, true /* trim */);
    204             os.end(tokenInner);
    205         } finally {
    206             Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
    207         }
    208         os.end(tokenOuter);
    209         appendTraceEntry(os);
    210         Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
    211     }
    212 }
    213