| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.wm; |
| |
| import static android.os.Build.IS_USER; |
| |
| import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY; |
| import static com.android.server.wm.WindowManagerTraceProto.ELAPSED_REALTIME_NANOS; |
| import static com.android.server.wm.WindowManagerTraceProto.WHERE; |
| import static com.android.server.wm.WindowManagerTraceProto.WINDOW_MANAGER_SERVICE; |
| |
| import android.annotation.Nullable; |
| import android.os.ShellCommand; |
| import android.os.SystemClock; |
| import android.os.Trace; |
| import android.util.Log; |
| import android.util.proto.ProtoOutputStream; |
| import android.view.Choreographer; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| |
| /** |
| * A class that allows window manager to dump its state continuously to a trace file, such that a |
| * time series of window manager state can be analyzed after the fact. |
| */ |
| class WindowTracing { |
| |
| /** |
| * Maximum buffer size, currently defined as 512 KB |
| * Size was experimentally defined to fit between 100 to 150 elements. |
| */ |
| private static final int BUFFER_CAPACITY_CRITICAL = 512 * 1024; |
| private static final int BUFFER_CAPACITY_TRIM = 2048 * 1024; |
| private static final int BUFFER_CAPACITY_ALL = 4096 * 1024; |
| private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace.pb"; |
| private static final String TAG = "WindowTracing"; |
| |
| private final WindowManagerService mService; |
| private final Choreographer mChoreographer; |
| private final WindowManagerGlobalLock mGlobalLock; |
| |
| private final Object mEnabledLock = new Object(); |
| private final File mTraceFile; |
| private final WindowTraceBuffer mBuffer; |
| private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) -> |
| log("onFrame" /* where */); |
| |
| private @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM; |
| private boolean mLogOnFrame = false; |
| private boolean mEnabled; |
| private volatile boolean mEnabledLockFree; |
| private boolean mScheduled; |
| |
| static WindowTracing createDefaultAndStartLooper(WindowManagerService service, |
| Choreographer choreographer) { |
| File file = new File(TRACE_FILENAME); |
| return new WindowTracing(file, service, choreographer, BUFFER_CAPACITY_TRIM); |
| } |
| |
| private WindowTracing(File file, WindowManagerService service, Choreographer choreographer, |
| int bufferCapacity) { |
| this(file, service, choreographer, service.mGlobalLock, bufferCapacity); |
| } |
| |
| WindowTracing(File file, WindowManagerService service, Choreographer choreographer, |
| WindowManagerGlobalLock globalLock, int bufferCapacity) { |
| mChoreographer = choreographer; |
| mService = service; |
| mGlobalLock = globalLock; |
| mTraceFile = file; |
| mBuffer = new WindowTraceBuffer(bufferCapacity); |
| setLogLevel(WindowTraceLogLevel.TRIM, null /* pw */); |
| } |
| |
| void startTrace(@Nullable PrintWriter pw) { |
| if (IS_USER) { |
| logAndPrintln(pw, "Error: Tracing is not supported on user builds."); |
| return; |
| } |
| synchronized (mEnabledLock) { |
| logAndPrintln(pw, "Start tracing to " + mTraceFile + "."); |
| mBuffer.resetBuffer(); |
| mEnabled = mEnabledLockFree = true; |
| } |
| log("trace.enable"); |
| } |
| |
| /** |
| * Stops the trace and write the current buffer to disk |
| * @param pw Print writer |
| */ |
| void stopTrace(@Nullable PrintWriter pw) { |
| stopTrace(pw, true /* writeToFile */); |
| } |
| |
| /** |
| * Stops the trace |
| * @param pw Print writer |
| * @param writeToFile If the current buffer should be written to disk or not |
| */ |
| void stopTrace(@Nullable PrintWriter pw, boolean writeToFile) { |
| if (IS_USER) { |
| logAndPrintln(pw, "Error: Tracing is not supported on user builds."); |
| return; |
| } |
| synchronized (mEnabledLock) { |
| logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush."); |
| mEnabled = mEnabledLockFree = false; |
| |
| if (mEnabled) { |
| logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush."); |
| throw new IllegalStateException("tracing enabled while waiting for flush."); |
| } |
| if (writeToFile) { |
| writeTraceToFileLocked(); |
| logAndPrintln(pw, "Trace written to " + mTraceFile + "."); |
| } |
| } |
| } |
| |
| private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) { |
| logAndPrintln(pw, "Setting window tracing log level to " + logLevel); |
| mLogLevel = logLevel; |
| |
| switch (logLevel) { |
| case WindowTraceLogLevel.ALL: { |
| setBufferCapacity(BUFFER_CAPACITY_ALL, pw); |
| break; |
| } |
| case WindowTraceLogLevel.TRIM: { |
| setBufferCapacity(BUFFER_CAPACITY_TRIM, pw); |
| break; |
| } |
| case WindowTraceLogLevel.CRITICAL: { |
| setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw); |
| break; |
| } |
| } |
| } |
| |
| private void setLogFrequency(boolean onFrame, PrintWriter pw) { |
| logAndPrintln(pw, "Setting window tracing log frequency to " |
| + ((onFrame) ? "frame" : "transaction")); |
| mLogOnFrame = onFrame; |
| } |
| |
| private void setBufferCapacity(int capacity, PrintWriter pw) { |
| logAndPrintln(pw, "Setting window tracing buffer capacity to " + capacity + "bytes"); |
| mBuffer.setCapacity(capacity); |
| } |
| |
| boolean isEnabled() { |
| return mEnabledLockFree; |
| } |
| |
| int onShellCommand(ShellCommand shell) { |
| PrintWriter pw = shell.getOutPrintWriter(); |
| String cmd = shell.getNextArgRequired(); |
| switch (cmd) { |
| case "start": |
| startTrace(pw); |
| return 0; |
| case "stop": |
| stopTrace(pw); |
| return 0; |
| case "status": |
| logAndPrintln(pw, getStatus()); |
| return 0; |
| case "frame": |
| setLogFrequency(true /* onFrame */, pw); |
| mBuffer.resetBuffer(); |
| return 0; |
| case "transaction": |
| setLogFrequency(false /* onFrame */, pw); |
| mBuffer.resetBuffer(); |
| return 0; |
| case "level": |
| String logLevelStr = shell.getNextArgRequired().toLowerCase(); |
| switch (logLevelStr) { |
| case "all": { |
| setLogLevel(WindowTraceLogLevel.ALL, pw); |
| break; |
| } |
| case "trim": { |
| setLogLevel(WindowTraceLogLevel.TRIM, pw); |
| break; |
| } |
| case "critical": { |
| setLogLevel(WindowTraceLogLevel.CRITICAL, pw); |
| break; |
| } |
| default: { |
| setLogLevel(WindowTraceLogLevel.TRIM, pw); |
| break; |
| } |
| } |
| mBuffer.resetBuffer(); |
| return 0; |
| case "size": |
| setBufferCapacity(Integer.parseInt(shell.getNextArgRequired()) * 1024, pw); |
| mBuffer.resetBuffer(); |
| return 0; |
| default: |
| pw.println("Unknown command: " + cmd); |
| pw.println("Window manager trace options:"); |
| pw.println(" start: Start logging"); |
| pw.println(" stop: Stop logging"); |
| pw.println(" frame: Log trace once per frame"); |
| pw.println(" transaction: Log each transaction"); |
| pw.println(" size: Set the maximum log size (in KB)"); |
| pw.println(" status: Print trace status"); |
| pw.println(" level [lvl]: Set the log level between"); |
| pw.println(" lvl may be one of:"); |
| pw.println(" critical: Only visible windows with reduced information"); |
| pw.println(" trim: All windows with reduced"); |
| pw.println(" all: All window and information"); |
| return -1; |
| } |
| } |
| |
| String getStatus() { |
| return "Status: " |
| + ((isEnabled()) ? "Enabled" : "Disabled") |
| + "\n" |
| + "Log level: " |
| + mLogLevel |
| + "\n" |
| + mBuffer.getStatus(); |
| } |
| |
| /** |
| * If tracing is enabled, log the current state or schedule the next frame to be logged, |
| * according to {@link #mLogOnFrame}. |
| * |
| * @param where Logging point descriptor |
| */ |
| void logState(String where) { |
| if (!isEnabled()) { |
| return; |
| } |
| |
| if (mLogOnFrame) { |
| schedule(); |
| } else { |
| log(where); |
| } |
| } |
| |
| /** |
| * Schedule the log to trace the next frame |
| */ |
| private void schedule() { |
| if (mScheduled) { |
| return; |
| } |
| |
| mScheduled = true; |
| mChoreographer.postFrameCallback(mFrameCallback); |
| } |
| |
| /** |
| * Write the current frame to the buffer |
| * |
| * @param where Logging point descriptor |
| */ |
| private void log(String where) { |
| Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked"); |
| try { |
| ProtoOutputStream os = new ProtoOutputStream(); |
| long tokenOuter = os.start(ENTRY); |
| os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos()); |
| os.write(WHERE, where); |
| |
| long tokenInner = os.start(WINDOW_MANAGER_SERVICE); |
| synchronized (mGlobalLock) { |
| Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked"); |
| try { |
| mService.writeToProtoLocked(os, mLogLevel); |
| } finally { |
| Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); |
| } |
| } |
| os.end(tokenInner); |
| os.end(tokenOuter); |
| mBuffer.add(os); |
| mScheduled = false; |
| } catch (Exception e) { |
| Log.wtf(TAG, "Exception while tracing state", e); |
| } finally { |
| Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); |
| } |
| } |
| |
| /** |
| * Writes the trace buffer to new file for the bugreport. |
| * |
| * This method is synchronized with {@code #startTrace(PrintWriter)} and |
| * {@link #stopTrace(PrintWriter)}. |
| */ |
| void writeTraceToFile() { |
| synchronized (mEnabledLock) { |
| writeTraceToFileLocked(); |
| } |
| } |
| |
| private void logAndPrintln(@Nullable PrintWriter pw, String msg) { |
| Log.i(TAG, msg); |
| if (pw != null) { |
| pw.println(msg); |
| pw.flush(); |
| } |
| } |
| |
| /** |
| * Writes the trace buffer to disk. This method has no internal synchronization and should be |
| * externally synchronized |
| */ |
| private void writeTraceToFileLocked() { |
| try { |
| Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked"); |
| mBuffer.writeTraceToFile(mTraceFile); |
| } catch (IOException e) { |
| Log.e(TAG, "Unable to write buffer to file", e); |
| } finally { |
| Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); |
| } |
| } |
| } |