WindowManager ProtoLog feature
This CL implements the on-device part of ProtoLog
- the new logging system for WindowManager.
Design doc: go/windowmanager-log2proto
Change-Id: I2c88c97dabb3465ffc0615b8017b335a494bca59
Bug:
Test: atest FrameworksServicesTests:com.android.server.protolog protologtool-tests
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 1643221..80bc1af 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -1,3 +1,60 @@
+java_library {
+ name: "protolog-common",
+ srcs: [
+ "java/com/android/server/protolog/common/**/*.java",
+ ],
+ host_supported: true,
+}
+
+java_library {
+ name: "services.core.wm.protologgroups",
+ srcs: [
+ "java/com/android/server/wm/ProtoLogGroup.java",
+ ],
+ static_libs: ["protolog-common"],
+}
+
+genrule {
+ name: "services.core.protologsrc",
+ srcs: [":services.core.wm.protologgroups", "java/**/*.java"],
+ tools: ["protologtool"],
+ cmd: "$(location protologtool) transform-protolog-calls " +
+ "--protolog-class com.android.server.protolog.common.ProtoLog " +
+ "--protolog-impl-class com.android.server.protolog.ProtoLogImpl " +
+ "--loggroups-class com.android.server.wm.ProtoLogGroup " +
+ "--loggroups-jar $(location :services.core.wm.protologgroups) " +
+ "--output-srcjar $(out) " +
+ "$(locations java/**/*.java)",
+ out: ["services.core.protolog.srcjar"],
+}
+
+genrule {
+ name: "generate-protolog.json",
+ srcs: [":services.core.wm.protologgroups", "java/**/*.java"],
+ tools: ["protologtool"],
+ cmd: "$(location protologtool) generate-viewer-config " +
+ "--protolog-class com.android.server.protolog.common.ProtoLog " +
+ "--loggroups-class com.android.server.wm.ProtoLogGroup " +
+ "--loggroups-jar $(location :services.core.wm.protologgroups) " +
+ "--viewer-conf $(out) " +
+ "$(locations java/**/*.java)",
+ out: ["services.core.protolog.json"],
+}
+
+genrule {
+ name: "checked-protolog.json",
+ srcs: [
+ ":generate-protolog.json",
+ ":services.core.protolog.json",
+ ],
+ cmd: "cp $(location :generate-protolog.json) $(out) && " +
+ "{ diff $(out) $(location :services.core.protolog.json) >/dev/null 2>&1 || " +
+ "{ echo -e '##### ProtoLog viewer config is stale. ### \nRun: \n " +
+ "cp $(location :generate-protolog.json) " +
+ "$(location :services.core.protolog.json)\n' >&2 && false; } }",
+ out: ["services.core.protolog.json"],
+}
+
java_library_static {
name: "services.core.unboosted",
@@ -12,7 +69,7 @@
],
},
srcs: [
- "java/**/*.java",
+ ":services.core.protologsrc",
":dumpstate_aidl",
":idmap2_aidl",
":installd_aidl",
@@ -34,6 +91,7 @@
required: [
"gps_debug.conf",
+ "protolog.conf.json.gz",
],
static_libs: [
@@ -81,3 +139,15 @@
name: "gps_debug.conf",
src: "java/com/android/server/location/gps_debug.conf",
}
+
+genrule {
+ name: "services.core.json.gz",
+ srcs: [":checked-protolog.json"],
+ out: ["services.core.protolog.json.gz"],
+ cmd: "gzip < $(in) > $(out)",
+}
+
+prebuilt_etc {
+ name: "protolog.conf.json.gz",
+ src: ":services.core.json.gz",
+}
diff --git a/services/core/java/com/android/server/protolog/ProtoLogImpl.java b/services/core/java/com/android/server/protolog/ProtoLogImpl.java
new file mode 100644
index 0000000..239a425
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/ProtoLogImpl.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2019 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.protolog;
+
+import static com.android.server.protolog.ProtoLogFileProto.LOG;
+import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER;
+import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER_H;
+import static com.android.server.protolog.ProtoLogFileProto.MAGIC_NUMBER_L;
+import static com.android.server.protolog.ProtoLogFileProto.REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS;
+import static com.android.server.protolog.ProtoLogFileProto.VERSION;
+import static com.android.server.protolog.ProtoLogMessage.BOOLEAN_PARAMS;
+import static com.android.server.protolog.ProtoLogMessage.DOUBLE_PARAMS;
+import static com.android.server.protolog.ProtoLogMessage.ELAPSED_REALTIME_NANOS;
+import static com.android.server.protolog.ProtoLogMessage.MESSAGE_HASH;
+import static com.android.server.protolog.ProtoLogMessage.SINT64_PARAMS;
+import static com.android.server.protolog.ProtoLogMessage.STR_PARAMS;
+
+import android.annotation.Nullable;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.protolog.common.IProtoLogGroup;
+import com.android.server.protolog.common.LogDataType;
+import com.android.server.utils.TraceBuffer;
+import com.android.server.wm.ProtoLogGroup;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.IllegalFormatConversionException;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+
+/**
+ * A service for the ProtoLog logging system.
+ */
+public class ProtoLogImpl {
+ private static final TreeMap<String, IProtoLogGroup> LOG_GROUPS = new TreeMap<>();
+
+ private static void addLogGroupEnum(IProtoLogGroup[] config) {
+ Arrays.stream(config).forEach(group -> LOG_GROUPS.put(group.name(), group));
+ }
+
+ static {
+ addLogGroupEnum(ProtoLogGroup.values());
+ }
+
+ /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+ public static void d(IProtoLogGroup group, int messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance()
+ .log(LogLevel.DEBUG, group, messageHash, paramsMask, messageString, args);
+ }
+
+ /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+ public static void v(IProtoLogGroup group, int messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, messageString,
+ args);
+ }
+
+ /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+ public static void i(IProtoLogGroup group, int messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, messageString, args);
+ }
+
+ /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+ public static void w(IProtoLogGroup group, int messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, messageString, args);
+ }
+
+ /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+ public static void e(IProtoLogGroup group, int messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance()
+ .log(LogLevel.ERROR, group, messageHash, paramsMask, messageString, args);
+ }
+
+ /** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
+ public static void wtf(IProtoLogGroup group, int messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, messageString, args);
+ }
+
+ private static final int BUFFER_CAPACITY = 1024 * 1024;
+ private static final String LOG_FILENAME = "/data/misc/wmtrace/wm_log.pb";
+ private static final String VIEWER_CONFIG_FILENAME = "/system/etc/protolog.conf.json.gz";
+ private static final String TAG = "ProtoLog";
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+ static final String PROTOLOG_VERSION = "1.0.0";
+
+ private final File mLogFile;
+ private final TraceBuffer mBuffer;
+ private final ProtoLogViewerConfigReader mViewerConfig;
+
+ private boolean mProtoLogEnabled;
+ private boolean mProtoLogEnabledLockFree;
+ private final Object mProtoLogEnabledLock = new Object();
+
+ private static ProtoLogImpl sServiceInstance = null;
+
+ /**
+ * Returns the single instance of the ProtoLogImpl singleton class.
+ */
+ public static synchronized ProtoLogImpl getSingleInstance() {
+ if (sServiceInstance == null) {
+ sServiceInstance = new ProtoLogImpl(new File(LOG_FILENAME), BUFFER_CAPACITY,
+ new ProtoLogViewerConfigReader());
+ }
+ return sServiceInstance;
+ }
+
+ @VisibleForTesting
+ public static synchronized void setSingleInstance(@Nullable ProtoLogImpl instance) {
+ sServiceInstance = instance;
+ }
+
+ @VisibleForTesting
+ public enum LogLevel {
+ DEBUG, VERBOSE, INFO, WARN, ERROR, WTF
+ }
+
+ /**
+ * Main log method, do not call directly.
+ */
+ @VisibleForTesting
+ public void log(LogLevel level, IProtoLogGroup group, int messageHash, int paramsMask,
+ @Nullable String messageString, Object[] args) {
+ if (group.isLogToProto()) {
+ logToProto(messageHash, paramsMask, args);
+ }
+ if (group.isLogToLogcat()) {
+ logToLogcat(group.getTag(), level, messageHash, messageString, args);
+ }
+ }
+
+ private void logToLogcat(String tag, LogLevel level, int messageHash,
+ @Nullable String messageString, Object[] args) {
+ String message = null;
+ if (messageString == null) {
+ messageString = mViewerConfig.getViewerString(messageHash);
+ }
+ if (messageString != null) {
+ try {
+ message = String.format(messageString, args);
+ } catch (IllegalFormatConversionException ex) {
+ Slog.w(TAG, "Invalid ProtoLog format string.", ex);
+ }
+ }
+ if (message == null) {
+ StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE (" + messageHash + ")");
+ for (Object o : args) {
+ builder.append(" ").append(o);
+ }
+ message = builder.toString();
+ }
+ passToLogcat(tag, level, message);
+ }
+
+ /**
+ * SLog wrapper.
+ */
+ @VisibleForTesting
+ public void passToLogcat(String tag, LogLevel level, String message) {
+ switch (level) {
+ case DEBUG:
+ Slog.d(tag, message);
+ break;
+ case VERBOSE:
+ Slog.v(tag, message);
+ break;
+ case INFO:
+ Slog.i(tag, message);
+ break;
+ case WARN:
+ Slog.w(tag, message);
+ break;
+ case ERROR:
+ Slog.e(tag, message);
+ break;
+ case WTF:
+ Slog.wtf(tag, message);
+ break;
+ }
+ }
+
+ private void logToProto(int messageHash, int paramsMask, Object[] args) {
+ if (!isProtoEnabled()) {
+ return;
+ }
+ try {
+ ProtoOutputStream os = new ProtoOutputStream();
+ long token = os.start(LOG);
+ os.write(MESSAGE_HASH, messageHash);
+ os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
+
+ int argIndex = 0;
+ ArrayList<Long> longParams = new ArrayList<>();
+ ArrayList<Double> doubleParams = new ArrayList<>();
+ ArrayList<Boolean> booleanParams = new ArrayList<>();
+ for (Object o : args) {
+ int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex);
+ try {
+ switch (type) {
+ case LogDataType.STRING:
+ os.write(STR_PARAMS, o.toString());
+ break;
+ case LogDataType.LONG:
+ longParams.add(((Number) o).longValue());
+ break;
+ case LogDataType.DOUBLE:
+ doubleParams.add(((Number) o).doubleValue());
+ break;
+ case LogDataType.BOOLEAN:
+ booleanParams.add((boolean) o);
+ break;
+ }
+ } catch (ClassCastException ex) {
+ // Should not happen unless there is an error in the ProtoLogTool.
+ os.write(STR_PARAMS, "(INVALID PARAMS_MASK) " + o.toString());
+ Slog.e(TAG, "Invalid ProtoLog paramsMask", ex);
+ }
+ argIndex++;
+ }
+ if (longParams.size() > 0) {
+ os.writePackedSInt64(SINT64_PARAMS,
+ longParams.stream().mapToLong(i -> i).toArray());
+ }
+ if (doubleParams.size() > 0) {
+ os.writePackedDouble(DOUBLE_PARAMS,
+ doubleParams.stream().mapToDouble(i -> i).toArray());
+ }
+ if (booleanParams.size() > 0) {
+ boolean[] arr = new boolean[booleanParams.size()];
+ for (int i = 0; i < booleanParams.size(); i++) {
+ arr[i] = booleanParams.get(i);
+ }
+ os.writePackedBool(BOOLEAN_PARAMS, arr);
+ }
+ os.end(token);
+ mBuffer.add(os);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception while logging to proto", e);
+ }
+ }
+
+
+ @VisibleForTesting
+ ProtoLogImpl(File file, int bufferCapacity, ProtoLogViewerConfigReader viewerConfig) {
+ mLogFile = file;
+ mBuffer = new TraceBuffer(bufferCapacity);
+ mViewerConfig = viewerConfig;
+ }
+
+ /**
+ * Starts the logging a circular proto buffer.
+ *
+ * @param pw Print writer
+ */
+ public void startProtoLog(@Nullable PrintWriter pw) {
+ if (isProtoEnabled()) {
+ return;
+ }
+ synchronized (mProtoLogEnabledLock) {
+ logAndPrintln(pw, "Start logging to " + mLogFile + ".");
+ mBuffer.resetBuffer();
+ mProtoLogEnabled = true;
+ mProtoLogEnabledLockFree = true;
+ }
+ }
+
+ /**
+ * Stops logging to proto.
+ *
+ * @param pw Print writer
+ * @param writeToFile If the current buffer should be written to disk or not
+ */
+ public void stopProtoLog(@Nullable PrintWriter pw, boolean writeToFile) {
+ if (!isProtoEnabled()) {
+ return;
+ }
+ synchronized (mProtoLogEnabledLock) {
+ logAndPrintln(pw, "Stop logging to " + mLogFile + ". Waiting for log to flush.");
+ mProtoLogEnabled = mProtoLogEnabledLockFree = false;
+ if (writeToFile) {
+ writeProtoLogToFileLocked();
+ logAndPrintln(pw, "Log written to " + mLogFile + ".");
+ }
+ if (mProtoLogEnabled) {
+ logAndPrintln(pw, "ERROR: logging was re-enabled while waiting for flush.");
+ throw new IllegalStateException("logging enabled while waiting for flush.");
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} iff logging to proto is enabled.
+ */
+ public boolean isProtoEnabled() {
+ return mProtoLogEnabledLockFree;
+ }
+
+ private int setLogging(ShellCommand shell, boolean setTextLogging, boolean value) {
+ String group;
+ while ((group = shell.getNextArg()) != null) {
+ IProtoLogGroup g = LOG_GROUPS.get(group);
+ if (g != null) {
+ if (setTextLogging) {
+ g.setLogToLogcat(value);
+ } else {
+ g.setLogToProto(value);
+ }
+ } else {
+ logAndPrintln(shell.getOutPrintWriter(), "No IProtoLogGroup named " + group);
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ private int unknownCommand(PrintWriter pw) {
+ pw.println("Unknown command");
+ pw.println("Window manager logging options:");
+ pw.println(" start: Start proto logging");
+ pw.println(" stop: Stop proto logging");
+ pw.println(" enable [group...]: Enable proto logging for given groups");
+ pw.println(" disable [group...]: Disable proto logging for given groups");
+ pw.println(" enable-text [group...]: Enable logcat logging for given groups");
+ pw.println(" disable-text [group...]: Disable logcat logging for given groups");
+ return -1;
+ }
+
+ /**
+ * Responds to a shell command.
+ */
+ public int onShellCommand(ShellCommand shell) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ String cmd = shell.getNextArg();
+ if (cmd == null) {
+ return unknownCommand(pw);
+ }
+ switch (cmd) {
+ case "start":
+ startProtoLog(pw);
+ return 0;
+ case "stop":
+ stopProtoLog(pw, true);
+ return 0;
+ case "status":
+ logAndPrintln(pw, getStatus());
+ return 0;
+ case "enable":
+ return setLogging(shell, false, true);
+ case "enable-text":
+ mViewerConfig.loadViewerConfig(pw, VIEWER_CONFIG_FILENAME);
+ return setLogging(shell, true, true);
+ case "disable":
+ return setLogging(shell, false, false);
+ case "disable-text":
+ return setLogging(shell, true, false);
+ default:
+ return unknownCommand(pw);
+ }
+ }
+
+ /**
+ * Returns a human-readable ProtoLog status text.
+ */
+ public String getStatus() {
+ return "ProtoLog status: "
+ + ((isProtoEnabled()) ? "Enabled" : "Disabled")
+ + "\nEnabled log groups: \n Proto: "
+ + LOG_GROUPS.values().stream().filter(
+ it -> it.isEnabled() && it.isLogToProto())
+ .map(IProtoLogGroup::name).collect(Collectors.joining(" "))
+ + "\n Logcat: "
+ + LOG_GROUPS.values().stream().filter(
+ it -> it.isEnabled() && it.isLogToLogcat())
+ .map(IProtoLogGroup::name).collect(Collectors.joining(" "))
+ + "\nLogging definitions loaded: " + mViewerConfig.knownViewerStringsNumber();
+ }
+
+ /**
+ * Writes the log buffer to a new file for the bugreport.
+ *
+ * This method is synchronized with {@code #startProtoLog(PrintWriter)} and
+ * {@link #stopProtoLog(PrintWriter, boolean)}.
+ */
+ public void writeProtoLogToFile() {
+ synchronized (mProtoLogEnabledLock) {
+ writeProtoLogToFileLocked();
+ }
+ }
+
+ private void writeProtoLogToFileLocked() {
+ try {
+ long offset =
+ (System.currentTimeMillis() - (SystemClock.elapsedRealtimeNanos() / 1000000));
+ ProtoOutputStream proto = new ProtoOutputStream();
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ proto.write(VERSION, PROTOLOG_VERSION);
+ proto.write(REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS, offset);
+ mBuffer.writeTraceToFile(mLogFile, proto);
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to write buffer to file", e);
+ }
+ }
+
+
+ static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
+ Slog.i(TAG, msg);
+ if (pw != null) {
+ pw.println(msg);
+ pw.flush();
+ }
+ }
+}
+
diff --git a/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java b/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java
new file mode 100644
index 0000000..4944217
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/ProtoLogViewerConfigReader.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2019 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.protolog;
+
+import static com.android.server.protolog.ProtoLogImpl.logAndPrintln;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Handles loading and parsing of ProtoLog viewer configuration.
+ */
+public class ProtoLogViewerConfigReader {
+ private Map<Integer, String> mLogMessageMap = null;
+
+ /** Returns message format string for its hash or null if unavailable. */
+ public synchronized String getViewerString(int messageHash) {
+ if (mLogMessageMap != null) {
+ return mLogMessageMap.get(messageHash);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Reads the specified viewer configuration file. Does nothing if the config is already loaded.
+ */
+ public synchronized void loadViewerConfig(PrintWriter pw, String viewerConfigFilename) {
+ if (mLogMessageMap != null) {
+ return;
+ }
+ try {
+ InputStreamReader config = new InputStreamReader(
+ new GZIPInputStream(new FileInputStream(viewerConfigFilename)));
+ BufferedReader reader = new BufferedReader(config);
+ StringBuilder builder = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ builder.append(line).append('\n');
+ }
+ reader.close();
+ JSONObject json = new JSONObject(builder.toString());
+ JSONObject messages = json.getJSONObject("messages");
+
+ mLogMessageMap = new TreeMap<>();
+ Iterator it = messages.keys();
+ while (it.hasNext()) {
+ String key = (String) it.next();
+ try {
+ int hash = Integer.parseInt(key);
+ JSONObject val = messages.getJSONObject(key);
+ String msg = val.getString("message");
+ mLogMessageMap.put(hash, msg);
+ } catch (NumberFormatException expected) {
+ // Not a messageHash - skip it
+ }
+ }
+ logAndPrintln(pw, "Loaded " + mLogMessageMap.size() + " log definitions from "
+ + viewerConfigFilename);
+ } catch (FileNotFoundException e) {
+ logAndPrintln(pw, "Unable to load log definitions: File "
+ + viewerConfigFilename + " not found." + e);
+ } catch (IOException e) {
+ logAndPrintln(pw, "Unable to load log definitions: IOException while reading "
+ + viewerConfigFilename + ". " + e);
+ } catch (JSONException e) {
+ logAndPrintln(pw,
+ "Unable to load log definitions: JSON parsing exception while reading "
+ + viewerConfigFilename + ". " + e);
+ }
+ }
+
+ /**
+ * Returns the number of loaded log definitions kept in memory.
+ */
+ public synchronized int knownViewerStringsNumber() {
+ if (mLogMessageMap != null) {
+ return mLogMessageMap.size();
+ }
+ return 0;
+ }
+}
diff --git a/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java b/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
new file mode 100644
index 0000000..7bb27b2
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/BitmaskConversionException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 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.protolog.common;
+
+/**
+ * Error while converting a bitmask representing a list of LogDataTypes.
+ */
+public class BitmaskConversionException extends RuntimeException {
+ BitmaskConversionException(String msg) {
+ super(msg);
+ }
+}
diff --git a/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java b/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java
new file mode 100644
index 0000000..2c65341
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/IProtoLogGroup.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 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.protolog.common;
+
+/**
+ * Defines a log group configuration object for ProtoLog. Should be implemented as en enum.
+ */
+public interface IProtoLogGroup {
+ /**
+ * if false all log statements for this group are excluded from compilation,
+ */
+ boolean isEnabled();
+
+ /**
+ * is binary logging enabled for the group.
+ */
+ boolean isLogToProto();
+
+ /**
+ * is text logging enabled for the group.
+ */
+ boolean isLogToLogcat();
+
+ /**
+ * returns true is any logging is enabled for this group.
+ */
+ default boolean isLogToAny() {
+ return isLogToLogcat() || isLogToProto();
+ }
+
+ /**
+ * returns the name of the source of the logged message
+ */
+ String getTag();
+
+ /**
+ * set binary logging for this group.
+ */
+ void setLogToProto(boolean logToProto);
+
+ /**
+ * set text logging for this group.
+ */
+ void setLogToLogcat(boolean logToLogcat);
+
+ /**
+ * returns name of the logging group.
+ */
+ String name();
+}
diff --git a/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java b/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java
new file mode 100644
index 0000000..947bf98
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/InvalidFormatStringException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.protolog.common;
+
+/**
+ * Unsupported/invalid message format string error.
+ */
+public class InvalidFormatStringException extends RuntimeException {
+ public InvalidFormatStringException(String message) {
+ super(message);
+ }
+
+ public InvalidFormatStringException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/services/core/java/com/android/server/protolog/common/LogDataType.java b/services/core/java/com/android/server/protolog/common/LogDataType.java
new file mode 100644
index 0000000..e73b41a
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/LogDataType.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 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.protolog.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a type of logged data encoded in the proto.
+ */
+public class LogDataType {
+ // When updating this list make sure to update bitmask conversion methods accordingly.
+ // STR type should be the first in the enum in order to be the default type.
+ public static final int STRING = 0b00;
+ public static final int LONG = 0b01;
+ public static final int DOUBLE = 0b10;
+ public static final int BOOLEAN = 0b11;
+
+ private static final int TYPE_WIDTH = 2;
+ private static final int TYPE_MASK = 0b11;
+
+ /**
+ * Creates a bitmask representing a list of data types.
+ */
+ public static int logDataTypesToBitMask(List<Integer> types) {
+ if (types.size() > 16) {
+ throw new BitmaskConversionException("Too many log call parameters "
+ + "- max 16 parameters supported");
+ }
+ int mask = 0;
+ for (int i = 0; i < types.size(); i++) {
+ int x = types.get(i);
+ mask = mask | (x << (i * TYPE_WIDTH));
+ }
+ return mask;
+ }
+
+ /**
+ * Decodes a bitmask to a list of LogDataTypes of provided length.
+ */
+ public static int bitmaskToLogDataType(int bitmask, int index) {
+ if (index > 16) {
+ throw new BitmaskConversionException("Max 16 parameters allowed");
+ }
+ return (bitmask >> (index * TYPE_WIDTH)) & TYPE_MASK;
+ }
+
+ /**
+ * Creates a list of LogDataTypes from a message format string.
+ */
+ public static List<Integer> parseFormatString(String messageString) {
+ ArrayList<Integer> types = new ArrayList<>();
+ for (int i = 0; i < messageString.length(); ) {
+ if (messageString.charAt(i) == '%') {
+ if (i + 1 >= messageString.length()) {
+ throw new InvalidFormatStringException("Invalid format string in config");
+ }
+ switch (messageString.charAt(i + 1)) {
+ case 'b':
+ types.add(LogDataType.BOOLEAN);
+ break;
+ case 'd':
+ case 'o':
+ case 'x':
+ types.add(LogDataType.LONG);
+ break;
+ case 'f':
+ case 'e':
+ case 'g':
+ types.add(LogDataType.DOUBLE);
+ break;
+ case 's':
+ types.add(LogDataType.STRING);
+ break;
+ case '%':
+ break;
+ default:
+ throw new InvalidFormatStringException("Invalid format string field"
+ + " %${messageString[i + 1]}");
+ }
+ i += 2;
+ } else {
+ i += 1;
+ }
+ }
+ return types;
+ }
+}
diff --git a/services/core/java/com/android/server/protolog/common/ProtoLog.java b/services/core/java/com/android/server/protolog/common/ProtoLog.java
new file mode 100644
index 0000000..b631bcb
--- /dev/null
+++ b/services/core/java/com/android/server/protolog/common/ProtoLog.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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.protolog.common;
+
+/**
+ * ProtoLog API - exposes static logging methods. Usage of this API is similar
+ * to {@code android.utils.Log} class. Instead of plain text log messages each call consists of
+ * a messageString, which is a format string for the log message (has to be a string literal or
+ * a concatenation of string literals) and a vararg array of parameters for the formatter.
+ *
+ * The syntax for the message string is a subset of {@code java.util.Formatter} syntax.
+ * Supported conversions:
+ * %b - boolean
+ * %d, %o and %x - integral type (Short, Integer or Long)
+ * %f, %e and %g - floating point type (Float or Double)
+ * %s - string
+ * %% - a literal percent character
+ * The width and precision modifiers are supported, argument_index and flags are not.
+ *
+ * Methods in this class are stubs, that are replaced by optimised versions by the ProtoLogTool
+ * during build.
+ */
+public class ProtoLog {
+ /**
+ * DEBUG level log.
+ *
+ * @param group {@code IProtoLogGroup} controlling this log call.
+ * @param messageString constant format string for the logged message.
+ * @param args parameters to be used with the format string.
+ */
+ public static void d(IProtoLogGroup group, String messageString, Object... args) {
+ // Stub, replaced by the ProtoLogTool.
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
+
+ /**
+ * VERBOSE level log.
+ *
+ * @param group {@code IProtoLogGroup} controlling this log call.
+ * @param messageString constant format string for the logged message.
+ * @param args parameters to be used with the format string.
+ */
+ public static void v(IProtoLogGroup group, String messageString, Object... args) {
+ // Stub, replaced by the ProtoLogTool.
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
+
+ /**
+ * INFO level log.
+ *
+ * @param group {@code IProtoLogGroup} controlling this log call.
+ * @param messageString constant format string for the logged message.
+ * @param args parameters to be used with the format string.
+ */
+ public static void i(IProtoLogGroup group, String messageString, Object... args) {
+ // Stub, replaced by the ProtoLogTool.
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
+
+ /**
+ * WARNING level log.
+ *
+ * @param group {@code IProtoLogGroup} controlling this log call.
+ * @param messageString constant format string for the logged message.
+ * @param args parameters to be used with the format string.
+ */
+ public static void w(IProtoLogGroup group, String messageString, Object... args) {
+ // Stub, replaced by the ProtoLogTool.
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
+
+ /**
+ * ERROR level log.
+ *
+ * @param group {@code IProtoLogGroup} controlling this log call.
+ * @param messageString constant format string for the logged message.
+ * @param args parameters to be used with the format string.
+ */
+ public static void e(IProtoLogGroup group, String messageString, Object... args) {
+ // Stub, replaced by the ProtoLogTool.
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
+
+ /**
+ * WHAT A TERRIBLE FAILURE level log.
+ *
+ * @param group {@code IProtoLogGroup} controlling this log call.
+ * @param messageString constant format string for the logged message.
+ * @param args parameters to be used with the format string.
+ */
+ public static void wtf(IProtoLogGroup group, String messageString, Object... args) {
+ // Stub, replaced by the ProtoLogTool.
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowTraceBuffer.java b/services/core/java/com/android/server/utils/TraceBuffer.java
similarity index 79%
rename from services/core/java/com/android/server/wm/WindowTraceBuffer.java
rename to services/core/java/com/android/server/utils/TraceBuffer.java
index 8c65884..0567960 100644
--- a/services/core/java/com/android/server/wm/WindowTraceBuffer.java
+++ b/services/core/java/com/android/server/utils/TraceBuffer.java
@@ -14,11 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm;
-
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
-import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
+package com.android.server.utils;
import android.util.proto.ProtoOutputStream;
@@ -33,31 +29,32 @@
import java.util.Queue;
/**
- * Buffer used for window tracing.
+ * Buffer used for tracing and logging.
*/
-class WindowTraceBuffer {
- private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-
+public class TraceBuffer {
private final Object mBufferLock = new Object();
private final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>();
private int mBufferUsedSize;
private int mBufferCapacity;
- WindowTraceBuffer(int bufferCapacity) {
+ public TraceBuffer(int bufferCapacity) {
mBufferCapacity = bufferCapacity;
resetBuffer();
}
- int getAvailableSpace() {
+ public int getAvailableSpace() {
return mBufferCapacity - mBufferUsedSize;
}
- int size() {
+ /**
+ * Returns buffer size.
+ */
+ public int size() {
return mBuffer.size();
}
- void setCapacity(int capacity) {
+ public void setCapacity(int capacity) {
mBufferCapacity = capacity;
}
@@ -68,7 +65,7 @@
* @throws IllegalStateException if the element cannot be added because it is larger
* than the buffer size.
*/
- void add(ProtoOutputStream proto) {
+ public void add(ProtoOutputStream proto) {
int protoLength = proto.getRawSize();
if (protoLength > mBufferCapacity) {
throw new IllegalStateException("Trace object too large for the buffer. Buffer size:"
@@ -88,19 +85,18 @@
}
/**
- * Writes the trace buffer to disk.
+ * Writes the trace buffer to disk inside the encapsulatingProto..
*/
- void writeTraceToFile(File traceFile) throws IOException {
+ public void writeTraceToFile(File traceFile, ProtoOutputStream encapsulatingProto)
+ throws IOException {
synchronized (mBufferLock) {
traceFile.delete();
try (OutputStream os = new FileOutputStream(traceFile)) {
traceFile.setReadable(true /* readable */, false /* ownerOnly */);
- ProtoOutputStream proto = new ProtoOutputStream();
- proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
- os.write(proto.getBytes());
+ os.write(encapsulatingProto.getBytes());
for (ProtoOutputStream protoOutputStream : mBuffer) {
- proto = protoOutputStream;
- byte[] protoBytes = proto.getBytes();
+ encapsulatingProto = protoOutputStream;
+ byte[] protoBytes = encapsulatingProto.getBytes();
os.write(protoBytes);
}
os.flush();
@@ -131,7 +127,7 @@
/**
* Removes all elements form the buffer
*/
- void resetBuffer() {
+ public void resetBuffer() {
synchronized (mBufferLock) {
mBuffer.clear();
mBufferUsedSize = 0;
@@ -143,7 +139,10 @@
return mBufferUsedSize;
}
- String getStatus() {
+ /**
+ * Returns the buffer status in human-readable form.
+ */
+ public String getStatus() {
synchronized (mBufferLock) {
return "Buffer size: "
+ mBufferCapacity
diff --git a/services/core/java/com/android/server/wm/ProtoLogGroup.java b/services/core/java/com/android/server/wm/ProtoLogGroup.java
new file mode 100644
index 0000000..313cceb
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ProtoLogGroup.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 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 com.android.internal.annotations.VisibleForTesting;
+import com.android.server.protolog.common.IProtoLogGroup;
+import com.android.server.protolog.common.ProtoLog;
+
+/**
+ * Defines logging groups for ProtoLog.
+ *
+ * This file is used by the ProtoLogTool to generate optimized logging code. All of its dependencies
+ * must be included in services.core.wm.protologgroups build target.
+ */
+public enum ProtoLogGroup implements IProtoLogGroup {
+ GENERIC_WM(true, true, false, "WindowManager"),
+
+ TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
+
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final String mTag;
+
+ /**
+ * @param enabled set to false to exclude all log statements for this group from
+ * compilation,
+ * they will not be available in runtime.
+ * @param logToProto enable binary logging for the group
+ * @param logToLogcat enable text logging for the group
+ * @param tag name of the source of the logged message
+ */
+ ProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ /**
+ * Test function for automated integration tests. Can be also called manually from adb shell.
+ */
+ @VisibleForTesting
+ public static void testProtoLog() {
+ ProtoLog.e(ProtoLogGroup.TEST_GROUP,
+ "Test completed successfully: %b %d %o %x %e %g %f %% %s.",
+ true, 1, 2, 3, 0.4, 0.5, 0.6, "ok");
+ }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 14214b4..4dd3313 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -265,6 +265,7 @@
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.power.ShutdownThread;
+import com.android.server.protolog.ProtoLogImpl;
import com.android.server.utils.PriorityDump;
import java.io.BufferedWriter;
@@ -5811,6 +5812,11 @@
pw.print(mWindowTracing.getStatus() + "\n");
}
+ private void dumpLogStatus(PrintWriter pw) {
+ pw.println("WINDOW MANAGER LOGGING (dumpsys window logging)");
+ pw.println(ProtoLogImpl.getSingleInstance().getStatus());
+ }
+
private void dumpSessionsLocked(PrintWriter pw, boolean dumpAll) {
pw.println("WINDOW MANAGER SESSIONS (dumpsys window sessions)");
for (int i=0; i<mSessions.size(); i++) {
@@ -6206,6 +6212,9 @@
} else if ("trace".equals(cmd)) {
dumpTraceStatus(pw);
return;
+ } else if ("logging".equals(cmd)) {
+ dumpLogStatus(pw);
+ return;
} else if ("refresh".equals(cmd)) {
dumpHighRefreshRateBlacklist(pw);
return;
@@ -6267,6 +6276,10 @@
if (dumpAll) {
pw.println(separator);
}
+ dumpLogStatus(pw);
+ if (dumpAll) {
+ pw.println(separator);
+ }
dumpHighRefreshRateBlacklist(pw);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 7384bb7..e01cbf2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -28,6 +28,8 @@
import android.view.IWindowManager;
import android.view.Surface;
+import com.android.server.protolog.ProtoLogImpl;
+
import java.io.PrintWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -75,6 +77,8 @@
// the output trace file, so the shell gets the correct semantics for where
// trace files can be written.
return mInternal.mWindowTracing.onShellCommand(this);
+ case "logging":
+ return ProtoLogImpl.getSingleInstance().onShellCommand(this);
case "set-user-rotation":
return runSetDisplayUserRotation(pw);
case "set-fix-to-user-rotation":
@@ -389,6 +393,8 @@
if (!IS_USER) {
pw.println(" tracing (start | stop)");
pw.println(" Start or stop window tracing.");
+ pw.println(" logging (start | stop | enable | disable | enable-text | disable-text)");
+ pw.println(" Logging settings.");
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 765e347..bb66530 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -19,6 +19,9 @@
import static android.os.Build.IS_USER;
import static com.android.server.wm.WindowManagerTraceFileProto.ENTRY;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
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;
@@ -31,6 +34,9 @@
import android.util.proto.ProtoOutputStream;
import android.view.Choreographer;
+import com.android.server.protolog.ProtoLogImpl;
+import com.android.server.utils.TraceBuffer;
+
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
@@ -50,6 +56,7 @@
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 static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
private final WindowManagerService mService;
private final Choreographer mChoreographer;
@@ -57,7 +64,7 @@
private final Object mEnabledLock = new Object();
private final File mTraceFile;
- private final WindowTraceBuffer mBuffer;
+ private final com.android.server.utils.TraceBuffer mBuffer;
private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) ->
log("onFrame" /* where */);
@@ -84,7 +91,7 @@
mService = service;
mGlobalLock = globalLock;
mTraceFile = file;
- mBuffer = new WindowTraceBuffer(bufferCapacity);
+ mBuffer = new TraceBuffer(bufferCapacity);
setLogLevel(WindowTraceLogLevel.TRIM, null /* pw */);
}
@@ -94,6 +101,7 @@
return;
}
synchronized (mEnabledLock) {
+ ProtoLogImpl.getSingleInstance().startProtoLog(pw);
logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
mBuffer.resetBuffer();
mEnabled = mEnabledLockFree = true;
@@ -132,6 +140,7 @@
logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
}
}
+ ProtoLogImpl.getSingleInstance().stopProtoLog(pw, writeToFile);
}
private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
@@ -317,6 +326,7 @@
synchronized (mEnabledLock) {
writeTraceToFileLocked();
}
+ ProtoLogImpl.getSingleInstance().writeProtoLogToFile();
}
private void logAndPrintln(@Nullable PrintWriter pw, String msg) {
@@ -334,11 +344,13 @@
private void writeTraceToFileLocked() {
try {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked");
- mBuffer.writeTraceToFile(mTraceFile);
+ ProtoOutputStream proto = new ProtoOutputStream();
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ mBuffer.writeTraceToFile(mTraceFile, proto);
} catch (IOException e) {
Log.e(TAG, "Unable to write buffer to file", e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java b/services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java
new file mode 100644
index 0000000..7aa3d0d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/protolog/ProtoLogImplTest.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2019 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.protolog;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.protolog.ProtoLogImpl.PROTOLOG_VERSION;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoInputStream;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.protolog.common.IProtoLogGroup;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.LinkedList;
+
+/**
+ * Test class for {@link ProtoLogImpl}.
+ */
+@SuppressWarnings("ConstantConditions")
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogImplTest {
+
+ private static final byte[] MAGIC_HEADER = new byte[]{
+ 0x9, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47
+ };
+
+ private ProtoLogImpl mProtoLog;
+ private File mFile;
+
+ @Mock
+ private ProtoLogViewerConfigReader mReader;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ final Context testContext = getInstrumentation().getContext();
+ mFile = testContext.getFileStreamPath("tracing_test.dat");
+ //noinspection ResultOfMethodCallIgnored
+ mFile.delete();
+ mProtoLog = new ProtoLogImpl(mFile, 1024 * 1024, mReader);
+ }
+
+ @After
+ public void tearDown() {
+ if (mFile != null) {
+ //noinspection ResultOfMethodCallIgnored
+ mFile.delete();
+ }
+ ProtoLogImpl.setSingleInstance(null);
+ }
+
+ @Test
+ public void isEnabled_returnsFalseByDefault() {
+ assertFalse(mProtoLog.isProtoEnabled());
+ }
+
+ @Test
+ public void isEnabled_returnsTrueAfterStart() {
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ assertTrue(mProtoLog.isProtoEnabled());
+ }
+
+ @Test
+ public void isEnabled_returnsFalseAfterStop() {
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+ assertFalse(mProtoLog.isProtoEnabled());
+ }
+
+ @Test
+ public void logFile_startsWithMagicHeader() throws Exception {
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+
+ assertTrue("Log file should exist", mFile.exists());
+
+ byte[] header = new byte[MAGIC_HEADER.length];
+ try (InputStream is = new FileInputStream(mFile)) {
+ assertEquals(MAGIC_HEADER.length, is.read(header));
+ assertArrayEquals(MAGIC_HEADER, header);
+ }
+ }
+
+ @Test
+ public void getSingleInstance() {
+ ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ assertSame(mockedProtoLog, ProtoLogImpl.getSingleInstance());
+ }
+
+ @Test
+ public void d_logCalled() {
+ ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.DEBUG), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+ }
+
+ @Test
+ public void v_logCalled() {
+ ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.v(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.VERBOSE), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+ }
+
+ @Test
+ public void i_logCalled() {
+ ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.i(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.INFO), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+ }
+
+ @Test
+ public void w_logCalled() {
+ ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.w(TestProtoLogGroup.TEST_GROUP, 1234,
+ 4321, "test %d");
+ verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.WARN), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+ }
+
+ @Test
+ public void e_logCalled() {
+ ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.e(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
+ verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.ERROR), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+ }
+
+ @Test
+ public void wtf_logCalled() {
+ ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogImpl.wtf(TestProtoLogGroup.TEST_GROUP,
+ 1234, 4321, "test %d");
+ verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.WTF), eq(
+ TestProtoLogGroup.TEST_GROUP),
+ eq(1234), eq(4321), eq("test %d"), eq(new Object[]{}));
+ }
+
+ @Test
+ public void log_logcatEnabledExternalMessage() {
+ when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %o %x %e %g %s %f");
+ ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ new Object[]{true, 10000, 20000, 30000, 0.0001, 0.00002, "test", 0.000003});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ ProtoLogImpl.LogLevel.INFO),
+ eq("test true 10000 % 47040 7530 1.000000e-04 2.00000e-05 test 0.000003"));
+ verify(mReader).getViewerString(eq(1234));
+ }
+
+ @Test
+ public void log_logcatEnabledInvalidMessage() {
+ when(mReader.getViewerString(anyInt())).thenReturn("test %b %d %% %o %x %e %g %s %f");
+ ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ new Object[]{true, 10000, 0.0001, 0.00002, "test"});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ ProtoLogImpl.LogLevel.INFO),
+ eq("UNKNOWN MESSAGE (1234) true 10000 1.0E-4 2.0E-5 test"));
+ verify(mReader).getViewerString(eq(1234));
+ }
+
+ @Test
+ public void log_logcatEnabledInlineMessage() {
+ when(mReader.getViewerString(anyInt())).thenReturn("test %d");
+ ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+ new Object[]{5});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ ProtoLogImpl.LogLevel.INFO), eq("test 5"));
+ verify(mReader, never()).getViewerString(anyInt());
+ }
+
+ @Test
+ public void log_logcatEnabledNoMessage() {
+ when(mReader.getViewerString(anyInt())).thenReturn(null);
+ ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
+ new Object[]{5});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ ProtoLogImpl.LogLevel.INFO), eq("UNKNOWN MESSAGE (1234) 5"));
+ verify(mReader).getViewerString(eq(1234));
+ }
+
+ @Test
+ public void log_logcatDisabled() {
+ when(mReader.getViewerString(anyInt())).thenReturn("test %d");
+ ProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+ new Object[]{5});
+
+ verify(implSpy, never()).passToLogcat(any(), any(), any());
+ verify(mReader, never()).getViewerString(anyInt());
+ }
+
+ private static class ProtoLogData {
+ Integer mMessageHash = null;
+ Long mElapsedTime = null;
+ LinkedList<String> mStrParams = new LinkedList<>();
+ LinkedList<Long> mSint64Params = new LinkedList<>();
+ LinkedList<Double> mDoubleParams = new LinkedList<>();
+ LinkedList<Boolean> mBooleanParams = new LinkedList<>();
+ }
+
+ private ProtoLogData readProtoLogSingle(ProtoInputStream ip) throws IOException {
+ while (ip.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ if (ip.getFieldNumber() == (int) ProtoLogFileProto.VERSION) {
+ assertEquals(PROTOLOG_VERSION, ip.readString(ProtoLogFileProto.VERSION));
+ continue;
+ }
+ if (ip.getFieldNumber() != (int) ProtoLogFileProto.LOG) {
+ continue;
+ }
+ long token = ip.start(ProtoLogFileProto.LOG);
+ ProtoLogData data = new ProtoLogData();
+ while (ip.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (ip.getFieldNumber()) {
+ case (int) ProtoLogMessage.MESSAGE_HASH: {
+ data.mMessageHash = ip.readInt(ProtoLogMessage.MESSAGE_HASH);
+ break;
+ }
+ case (int) ProtoLogMessage.ELAPSED_REALTIME_NANOS: {
+ data.mElapsedTime = ip.readLong(ProtoLogMessage.ELAPSED_REALTIME_NANOS);
+ break;
+ }
+ case (int) ProtoLogMessage.STR_PARAMS: {
+ data.mStrParams.add(ip.readString(ProtoLogMessage.STR_PARAMS));
+ break;
+ }
+ case (int) ProtoLogMessage.SINT64_PARAMS: {
+ data.mSint64Params.add(ip.readLong(ProtoLogMessage.SINT64_PARAMS));
+ break;
+ }
+ case (int) ProtoLogMessage.DOUBLE_PARAMS: {
+ data.mDoubleParams.add(ip.readDouble(ProtoLogMessage.DOUBLE_PARAMS));
+ break;
+ }
+ case (int) ProtoLogMessage.BOOLEAN_PARAMS: {
+ data.mBooleanParams.add(ip.readBoolean(ProtoLogMessage.BOOLEAN_PARAMS));
+ break;
+ }
+ }
+ }
+ ip.end(token);
+ return data;
+ }
+ return null;
+ }
+
+ @Test
+ public void log_protoEnabled() throws Exception {
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(true);
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ long before = SystemClock.elapsedRealtimeNanos();
+ mProtoLog.log(
+ ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
+ 0b1110101001010100, null,
+ new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
+ long after = SystemClock.elapsedRealtimeNanos();
+ mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+ try (InputStream is = new FileInputStream(mFile)) {
+ ProtoInputStream ip = new ProtoInputStream(is);
+ ProtoLogData data = readProtoLogSingle(ip);
+ assertNotNull(data);
+ assertEquals(1234, data.mMessageHash.longValue());
+ assertTrue(before < data.mElapsedTime && data.mElapsedTime < after);
+ assertArrayEquals(new String[]{"test"}, data.mStrParams.toArray());
+ assertArrayEquals(new Long[]{1L, 2L, 3L}, data.mSint64Params.toArray());
+ assertArrayEquals(new Double[]{0.4, 0.5, 0.6}, data.mDoubleParams.toArray());
+ assertArrayEquals(new Boolean[]{true}, data.mBooleanParams.toArray());
+ }
+ }
+
+ @Test
+ public void log_invalidParamsMask() throws Exception {
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(true);
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ long before = SystemClock.elapsedRealtimeNanos();
+ mProtoLog.log(
+ ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
+ 0b01100100, null,
+ new Object[]{"test", 1, 0.1, true});
+ long after = SystemClock.elapsedRealtimeNanos();
+ mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+ try (InputStream is = new FileInputStream(mFile)) {
+ ProtoInputStream ip = new ProtoInputStream(is);
+ ProtoLogData data = readProtoLogSingle(ip);
+ assertNotNull(data);
+ assertEquals(1234, data.mMessageHash.longValue());
+ assertTrue(before < data.mElapsedTime && data.mElapsedTime < after);
+ assertArrayEquals(new String[]{"test", "(INVALID PARAMS_MASK) true"},
+ data.mStrParams.toArray());
+ assertArrayEquals(new Long[]{1L}, data.mSint64Params.toArray());
+ assertArrayEquals(new Double[]{0.1}, data.mDoubleParams.toArray());
+ assertArrayEquals(new Boolean[]{}, data.mBooleanParams.toArray());
+ }
+ }
+
+ @Test
+ public void log_protoDisabled() throws Exception {
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+ mProtoLog.startProtoLog(mock(PrintWriter.class));
+ mProtoLog.log(ProtoLogImpl.LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
+ 0b11, null, new Object[]{true});
+ mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
+ try (InputStream is = new FileInputStream(mFile)) {
+ ProtoInputStream ip = new ProtoInputStream(is);
+ ProtoLogData data = readProtoLogSingle(ip);
+ assertNull(data);
+ }
+ }
+
+ private enum TestProtoLogGroup implements IProtoLogGroup {
+ TEST_GROUP(true, true, false, "WindowManagetProtoLogTest");
+
+ private final boolean mEnabled;
+ private volatile boolean mLogToProto;
+ private volatile boolean mLogToLogcat;
+ private final String mTag;
+
+ /**
+ * @param enabled set to false to exclude all log statements for this group from
+ * compilation,
+ * they will not be available in runtime.
+ * @param logToProto enable binary logging for the group
+ * @param logToLogcat enable text logging for the group
+ * @param tag name of the source of the logged message
+ */
+ TestProtoLogGroup(boolean enabled, boolean logToProto, boolean logToLogcat, String tag) {
+ this.mEnabled = enabled;
+ this.mLogToProto = logToProto;
+ this.mLogToLogcat = logToLogcat;
+ this.mTag = tag;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ @Override
+ public boolean isLogToProto() {
+ return mLogToProto;
+ }
+
+ @Override
+ public boolean isLogToLogcat() {
+ return mLogToLogcat;
+ }
+
+ @Override
+ public boolean isLogToAny() {
+ return mLogToLogcat || mLogToProto;
+ }
+
+ @Override
+ public String getTag() {
+ return mTag;
+ }
+
+ @Override
+ public void setLogToProto(boolean logToProto) {
+ this.mLogToProto = logToProto;
+ }
+
+ @Override
+ public void setLogToLogcat(boolean logToLogcat) {
+ this.mLogToLogcat = logToLogcat;
+ }
+
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java b/services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java
new file mode 100644
index 0000000..0254055
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/protolog/ProtoLogViewerConfigReaderTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 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.protolog;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.zip.GZIPOutputStream;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class ProtoLogViewerConfigReaderTest {
+ private static final String TEST_VIEWER_CONFIG = "{\n"
+ + " \"version\": \"1.0.0\",\n"
+ + " \"messages\": {\n"
+ + " \"70933285\": {\n"
+ + " \"message\": \"Test completed successfully: %b\",\n"
+ + " \"level\": \"ERROR\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"1792430067\": {\n"
+ + " \"message\": \"Attempted to add window to a display that does not exist: %d."
+ + " Aborting.\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"1352021864\": {\n"
+ + " \"message\": \"Test 2\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " },\n"
+ + " \"409412266\": {\n"
+ + " \"message\": \"Window %s is already added\",\n"
+ + " \"level\": \"WARN\",\n"
+ + " \"group\": \"GENERIC_WM\"\n"
+ + " }\n"
+ + " },\n"
+ + " \"groups\": {\n"
+ + " \"GENERIC_WM\": {\n"
+ + " \"tag\": \"WindowManager\"\n"
+ + " }\n"
+ + " }\n"
+ + "}\n";
+
+
+ private ProtoLogViewerConfigReader
+ mConfig = new ProtoLogViewerConfigReader();
+ private File mTestViewerConfig;
+
+ @Before
+ public void setUp() throws IOException {
+ mTestViewerConfig = File.createTempFile("testConfig", ".json.gz");
+ OutputStreamWriter writer = new OutputStreamWriter(
+ new GZIPOutputStream(new FileOutputStream(mTestViewerConfig)));
+ writer.write(TEST_VIEWER_CONFIG);
+ writer.close();
+ }
+
+ @After
+ public void tearDown() {
+ //noinspection ResultOfMethodCallIgnored
+ mTestViewerConfig.delete();
+ }
+
+ @Test
+ public void getViewerString_notLoaded() {
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig() {
+ mConfig.loadViewerConfig(null, mTestViewerConfig.getAbsolutePath());
+ assertEquals("Test completed successfully: %b", mConfig.getViewerString(70933285));
+ assertEquals("Test 2", mConfig.getViewerString(1352021864));
+ assertEquals("Window %s is already added", mConfig.getViewerString(409412266));
+ assertNull(mConfig.getViewerString(1));
+ }
+
+ @Test
+ public void loadViewerConfig_invalidFile() {
+ mConfig.loadViewerConfig(null, "/tmp/unknown/file/does/not/exist");
+ // No exception is thrown.
+ assertNull(mConfig.getViewerString(1));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java b/services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java
new file mode 100644
index 0000000..4c7f5fd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/protolog/common/LogDataTypeTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 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.protolog.common;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class LogDataTypeTest {
+ @Test
+ public void parseFormatString() {
+ String str = "%b %d %o %x %f %e %g %s %%";
+ List<Integer> out = LogDataType.parseFormatString(str);
+ assertEquals(Arrays.asList(
+ LogDataType.BOOLEAN,
+ LogDataType.LONG,
+ LogDataType.LONG,
+ LogDataType.LONG,
+ LogDataType.DOUBLE,
+ LogDataType.DOUBLE,
+ LogDataType.DOUBLE,
+ LogDataType.STRING
+ ), out);
+ }
+
+ @Test(expected = InvalidFormatStringException.class)
+ public void parseFormatString_invalid() {
+ String str = "%q";
+ LogDataType.parseFormatString(str);
+ }
+
+ @Test
+ public void logDataTypesToBitMask() {
+ List<Integer> types = Arrays.asList(LogDataType.STRING, LogDataType.DOUBLE,
+ LogDataType.LONG, LogDataType.BOOLEAN);
+ int mask = LogDataType.logDataTypesToBitMask(types);
+ assertEquals(0b11011000, mask);
+ }
+
+ @Test(expected = BitmaskConversionException.class)
+ public void logDataTypesToBitMask_toManyParams() {
+ ArrayList<Integer> types = new ArrayList<>();
+ for (int i = 0; i <= 16; i++) {
+ types.add(LogDataType.STRING);
+ }
+ LogDataType.logDataTypesToBitMask(types);
+ }
+
+ @Test
+ public void bitmaskToLogDataTypes() {
+ int bitmask = 0b11011000;
+ List<Integer> types = Arrays.asList(LogDataType.STRING, LogDataType.DOUBLE,
+ LogDataType.LONG, LogDataType.BOOLEAN);
+ for (int i = 0; i < types.size(); i++) {
+ assertEquals(types.get(i).intValue(), LogDataType.bitmaskToLogDataType(bitmask, i));
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java b/services/tests/servicestests/src/com/android/server/utils/TraceBufferTest.java
similarity index 95%
rename from services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java
rename to services/tests/servicestests/src/com/android/server/utils/TraceBufferTest.java
index b299f0d..09b75e7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/TraceBufferTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm;
+package com.android.server.utils;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -39,16 +39,16 @@
/**
- * Test class for {@link WindowTraceBuffer}.
+ * Test class for {@link TraceBuffer}.
*
* Build/Install/Run:
- * atest WmTests:WindowTraceBufferTest
+ * atest WmTests:TraceBufferTest
*/
@SmallTest
@Presubmit
-public class WindowTraceBufferTest {
+public class TraceBufferTest {
private File mFile;
- private WindowTraceBuffer mBuffer;
+ private TraceBuffer mBuffer;
@Before
public void setUp() throws Exception {
@@ -56,7 +56,7 @@
mFile = testContext.getFileStreamPath("tracing_test.dat");
mFile.delete();
- mBuffer = new WindowTraceBuffer(10);
+ mBuffer = new TraceBuffer(10);
}
@After
diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
new file mode 100644
index 0000000..8eecff5
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 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 org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.protolog.ProtoLogImpl;
+
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Check if the ProtoLogTools is used to process the WindowManager source code.
+ */
+@SmallTest
+@Presubmit
+public class ProtoLogIntegrationTest {
+ @After
+ public void tearDown() {
+ ProtoLogImpl.setSingleInstance(null);
+ }
+
+ @Test
+ public void testProtoLogToolIntegration() {
+ ProtoLogImpl mockedProtoLog = mock(ProtoLogImpl.class);
+ ProtoLogImpl.setSingleInstance(mockedProtoLog);
+ ProtoLogGroup.testProtoLog();
+ verify(mockedProtoLog).log(eq(ProtoLogImpl.LogLevel.ERROR), eq(
+ ProtoLogGroup.TEST_GROUP),
+ eq(485522692), eq(0b0010101001010111),
+ eq(ProtoLogGroup.TEST_GROUP.isLogToLogcat()
+ ? "Test completed successfully: %b %d %o %x %e %g %f %% %s"
+ : null),
+ eq(new Object[]{true, 1L, 2L, 3L, 0.4, 0.5, 0.6, "ok"}));
+ }
+}