diff options
author | Adam Pardyl <apardyl@google.com> | 2019-08-19 15:24:11 +0200 |
---|---|---|
committer | Adam Pardyl <apardyl@google.com> | 2019-09-23 15:47:29 +0000 |
commit | 0f1b3d460259e0b739c363c1158f774315e45e87 (patch) | |
tree | 978f3c6f2d166cbc6a5dda46f6dde24b8fc04794 | |
parent | eba21f1d7a8bc93826487f4674aafe9033318b6d (diff) |
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
46 files changed, 1999 insertions, 245 deletions
diff --git a/Android.bp b/Android.bp index bef32513d18b..efddc0a17029 100644 --- a/Android.bp +++ b/Android.bp @@ -244,8 +244,9 @@ java_defaults { }, required: [ - // TODO: remove gps_debug when the build system propagates "required" properly. + // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly. "gps_debug.conf", + "protolog.conf.json.gz", ], } diff --git a/core/proto/Android.bp b/core/proto/Android.bp index e199dab181e0..6119d71d4456 100644 --- a/core/proto/Android.bp +++ b/core/proto/Android.bp @@ -30,9 +30,9 @@ cc_library_static { } java_library_host { - name: "windowmanager-log-proto", + name: "protolog-proto", srcs: [ - "android/server/windowmanagerlog.proto" + "android/server/protolog.proto" ], proto: { type: "full", diff --git a/core/proto/android/server/windowmanagerlog.proto b/core/proto/android/server/protolog.proto index 5bee1bd670fc..7c98d318570b 100644 --- a/core/proto/android/server/windowmanagerlog.proto +++ b/core/proto/android/server/protolog.proto @@ -16,7 +16,7 @@ syntax = "proto2"; -package com.android.server.wm; +package com.android.server.protolog; option java_multiple_files = true; @@ -36,16 +36,16 @@ message ProtoLogMessage { repeated bool boolean_params = 6 [packed=true]; } -/* represents a log file containing window manager log entries. - Encoded, it should start with 0x9 0x57 0x49 0x4e 0x44 0x4f 0x4c 0x4f 0x47 (.WINDOLOG), such +/* represents a log file containing ProtoLog log entries. + Encoded, it should start with 0x9 0x50 0x52 0x4f 0x54 0x4f 0x4c 0x4f 0x47 (.PROTOLOG), such that they can be easily identified. */ -message WindowManagerLogFileProto { +message ProtoLogFileProto { /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L (this is needed because enums have to be 32 bits and there's no nice way to put 64bit constants into .proto files. */ enum MagicNumber { INVALID = 0; - MAGIC_NUMBER_L = 0x444e4957; /* WIND (little-endian ASCII) */ + MAGIC_NUMBER_L = 0x544f5250; /* PROT (little-endian ASCII) */ MAGIC_NUMBER_H = 0x474f4c4f; /* OLOG (little-endian ASCII) */ } diff --git a/data/etc/Android.bp b/data/etc/Android.bp index 4493f3a8dddc..7e167c9bfb79 100644 --- a/data/etc/Android.bp +++ b/data/etc/Android.bp @@ -133,3 +133,8 @@ prebuilt_etc { sub_dir: "permissions", src: "com.android.timezone.updater.xml", } + +filegroup { + name: "services.core.protolog.json", + srcs: ["services.core.protolog.json"], +} diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json new file mode 100644 index 000000000000..261891fb3bbc --- /dev/null +++ b/data/etc/services.core.protolog.json @@ -0,0 +1,15 @@ +{ + "version": "1.0.0", + "messages": { + "485522692": { + "message": "Test completed successfully: %b %d %o %x %e %g %f %% %s.", + "level": "ERROR", + "group": "TEST_GROUP" + } + }, + "groups": { + "TEST_GROUP": { + "tag": "WindowManagetProtoLogTest" + } + } +} diff --git a/services/core/Android.bp b/services/core/Android.bp index 16432212d8e2..80bc1af4d160 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 @@ java_library_static { ], }, srcs: [ - "java/**/*.java", + ":services.core.protologsrc", ":dumpstate_aidl", ":idmap2_aidl", ":installd_aidl", @@ -34,6 +91,7 @@ java_library_static { required: [ "gps_debug.conf", + "protolog.conf.json.gz", ], static_libs: [ @@ -81,3 +139,15 @@ prebuilt_etc { 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 000000000000..239a4259438b --- /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 000000000000..494421717800 --- /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 000000000000..7bb27b2d9bcd --- /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 000000000000..2c65341453e9 --- /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 000000000000..947bf98eea3c --- /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 000000000000..e73b41abddc7 --- /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 000000000000..b631bcb23f5f --- /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 index 8c65884a4d89..0567960e05e4 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.Arrays; 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 @@ class WindowTraceBuffer { * @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 @@ class WindowTraceBuffer { } /** - * 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 @@ class WindowTraceBuffer { /** * Removes all elements form the buffer */ - void resetBuffer() { + public void resetBuffer() { synchronized (mBufferLock) { mBuffer.clear(); mBufferUsedSize = 0; @@ -143,7 +139,10 @@ class WindowTraceBuffer { 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 000000000000..313ccebc778d --- /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 14214b4be7d1..4dd33139a755 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.input.InputManagerService; 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub } 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 7384bb7e1587..e01cbf26dadc 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.Display; 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 @@ public class WindowManagerShellCommand extends ShellCommand { // 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 @@ public class WindowManagerShellCommand extends ShellCommand { 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 765e34788ac8..bb66530d7c15 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 @@ 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.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.Log; 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 @@ class WindowTracing { 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 @@ class WindowTracing { 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 @@ class WindowTracing { mService = service; mGlobalLock = globalLock; mTraceFile = file; - mBuffer = new WindowTraceBuffer(bufferCapacity); + mBuffer = new TraceBuffer(bufferCapacity); setLogLevel(WindowTraceLogLevel.TRIM, null /* pw */); } @@ -94,6 +101,7 @@ class WindowTracing { return; } synchronized (mEnabledLock) { + ProtoLogImpl.getSingleInstance().startProtoLog(pw); logAndPrintln(pw, "Start tracing to " + mTraceFile + "."); mBuffer.resetBuffer(); mEnabled = mEnabledLockFree = true; @@ -132,6 +140,7 @@ class WindowTracing { logAndPrintln(pw, "Trace written to " + mTraceFile + "."); } } + ProtoLogImpl.getSingleInstance().stopProtoLog(pw, writeToFile); } private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) { @@ -317,6 +326,7 @@ class WindowTracing { synchronized (mEnabledLock) { writeTraceToFileLocked(); } + ProtoLogImpl.getSingleInstance().writeProtoLogToFile(); } private void logAndPrintln(@Nullable PrintWriter pw, String msg) { @@ -334,11 +344,13 @@ class WindowTracing { 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 000000000000..7aa3d0dfdd1f --- /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 000000000000..02540559fbd0 --- /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 000000000000..4c7f5fdc821c --- /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 index b299f0dd7253..09b75e71d946 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 @@ import java.io.File; /** - * 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 @@ public class WindowTraceBufferTest { 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 000000000000..8eecff532ad9 --- /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"})); + } +} diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp index a86c226c2179..d1a86c245dec 100644 --- a/tools/protologtool/Android.bp +++ b/tools/protologtool/Android.bp @@ -1,27 +1,32 @@ -java_binary_host { - name: "protologtool", - manifest: "manifest.txt", +java_library_host { + name: "protologtool-lib", srcs: [ - "src/**/*.kt", + "src/com/android/protolog/tool/**/*.kt", ], static_libs: [ + "protolog-common", "javaparser", - "windowmanager-log-proto", + "protolog-proto", "jsonlib", ], } +java_binary_host { + name: "protologtool", + manifest: "manifest.txt", + static_libs: [ + "protologtool-lib", + ], +} + java_test_host { name: "protologtool-tests", test_suites: ["general-tests"], srcs: [ - "src/**/*.kt", "tests/**/*.kt", ], static_libs: [ - "javaparser", - "windowmanager-log-proto", - "jsonlib", + "protologtool-lib", "junit", "mockito", ], diff --git a/tools/protologtool/manifest.txt b/tools/protologtool/manifest.txt index f5e53c450f2a..cabebd51a2fa 100644 --- a/tools/protologtool/manifest.txt +++ b/tools/protologtool/manifest.txt @@ -1 +1 @@ -Main-class: com.android.protologtool.ProtoLogTool +Main-class: com.android.protolog.tool.ProtoLogTool diff --git a/tools/protologtool/src/com/android/protologtool/CodeUtils.kt b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt index facca6290c91..5c921612df45 100644 --- a/tools/protologtool/src/com/android/protologtool/CodeUtils.kt +++ b/tools/protologtool/src/com/android/protolog/tool/CodeUtils.kt @@ -14,20 +14,13 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool -import com.github.javaparser.StaticJavaParser import com.github.javaparser.ast.CompilationUnit import com.github.javaparser.ast.ImportDeclaration -import com.github.javaparser.ast.NodeList import com.github.javaparser.ast.expr.BinaryExpr import com.github.javaparser.ast.expr.Expression -import com.github.javaparser.ast.expr.MethodCallExpr -import com.github.javaparser.ast.expr.SimpleName import com.github.javaparser.ast.expr.StringLiteralExpr -import com.github.javaparser.ast.expr.TypeExpr -import com.github.javaparser.ast.type.PrimitiveType -import com.github.javaparser.ast.type.Type object CodeUtils { /** @@ -78,58 +71,4 @@ object CodeUtils { "or concatenation of string literals.", expr) } } - - enum class LogDataTypes( - val type: Type, - val toType: (Expression) -> Expression = { expr -> expr } - ) { - // When adding new LogDataType make sure to update {@code logDataTypesToBitMask} accordingly - STRING(StaticJavaParser.parseClassOrInterfaceType("String"), - { expr -> - MethodCallExpr(TypeExpr(StaticJavaParser.parseClassOrInterfaceType("String")), - SimpleName("valueOf"), NodeList(expr)) - }), - LONG(PrimitiveType.longType()), - DOUBLE(PrimitiveType.doubleType()), - BOOLEAN(PrimitiveType.booleanType()); - } - - fun parseFormatString(messageString: String): List<LogDataTypes> { - val types = mutableListOf<LogDataTypes>() - var i = 0 - while (i < messageString.length) { - if (messageString[i] == '%') { - if (i + 1 >= messageString.length) { - throw InvalidFormatStringException("Invalid format string in config") - } - when (messageString[i + 1]) { - 'b' -> types.add(CodeUtils.LogDataTypes.BOOLEAN) - 'd', 'o', 'x' -> types.add(CodeUtils.LogDataTypes.LONG) - 'f', 'e', 'g' -> types.add(CodeUtils.LogDataTypes.DOUBLE) - 's' -> types.add(CodeUtils.LogDataTypes.STRING) - '%' -> { - } - else -> throw InvalidFormatStringException("Invalid format string field" + - " %${messageString[i + 1]}") - } - i += 2 - } else { - i += 1 - } - } - return types - } - - fun logDataTypesToBitMask(types: List<LogDataTypes>): Int { - if (types.size > 16) { - throw InvalidFormatStringException("Too many log call parameters " + - "- max 16 parameters supported") - } - var mask = 0 - types.forEachIndexed { idx, type -> - val x = LogDataTypes.values().indexOf(type) - mask = mask or (x shl (idx * 2)) - } - return mask - } } diff --git a/tools/protologtool/src/com/android/protologtool/CommandOptions.kt b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt index df49e1566fbc..3dfa4d216cc2 100644 --- a/tools/protologtool/src/com/android/protologtool/CommandOptions.kt +++ b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import java.util.regex.Pattern diff --git a/tools/protologtool/src/com/android/protologtool/Constants.kt b/tools/protologtool/src/com/android/protolog/tool/Constants.kt index 2ccfc4d20182..83b3c00ebc28 100644 --- a/tools/protologtool/src/com/android/protologtool/Constants.kt +++ b/tools/protologtool/src/com/android/protolog/tool/Constants.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool object Constants { const val NAME = "protologtool" const val VERSION = "1.0.0" - const val IS_ENABLED_METHOD = "isEnabled" - const val IS_LOG_TO_LOGCAT_METHOD = "isLogToLogcat" const val IS_LOG_TO_ANY_METHOD = "isLogToAny" - const val GET_TAG_METHOD = "getTag" const val ENUM_VALUES_METHOD = "values" } diff --git a/tools/protologtool/src/com/android/protologtool/LogGroup.kt b/tools/protologtool/src/com/android/protolog/tool/LogGroup.kt index 42a37a26e08a..587f7b9db016 100644 --- a/tools/protologtool/src/com/android/protologtool/LogGroup.kt +++ b/tools/protologtool/src/com/android/protolog/tool/LogGroup.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool data class LogGroup( val name: String, diff --git a/tools/protologtool/src/com/android/protologtool/LogLevel.kt b/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt index dc29557ef440..7759f35b33fe 100644 --- a/tools/protologtool/src/com/android/protologtool/LogLevel.kt +++ b/tools/protologtool/src/com/android/protolog/tool/LogLevel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.github.javaparser.ast.Node diff --git a/tools/protologtool/src/com/android/protologtool/LogParser.kt b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt index 4d0eb0e4a705..a59038fc99a0 100644 --- a/tools/protologtool/src/com/android/protologtool/LogParser.kt +++ b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt @@ -14,11 +14,13 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.android.json.stream.JsonReader -import com.android.server.wm.ProtoLogMessage -import com.android.server.wm.WindowManagerLogFileProto +import com.android.server.protolog.common.InvalidFormatStringException +import com.android.server.protolog.common.LogDataType +import com.android.server.protolog.ProtoLogMessage +import com.android.server.protolog.ProtoLogFileProto import java.io.BufferedReader import java.io.InputStream import java.io.InputStreamReader @@ -36,8 +38,8 @@ class LogParser(private val configParser: ViewerConfigParser) { companion object { private val dateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) private val magicNumber = - WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or - WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong() + ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or + ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong() } private fun printTime(time: Long, offset: Long, ps: PrintStream) { @@ -55,14 +57,15 @@ class LogParser(private val configParser: ViewerConfigParser) { val boolParamsIt = protoLogMessage.booleanParamsList.iterator() val args = mutableListOf<Any>() val format = configEntry.messageString - val argTypes = CodeUtils.parseFormatString(format) + val argTypes = LogDataType.parseFormatString(format) try { argTypes.forEach { when (it) { - CodeUtils.LogDataTypes.BOOLEAN -> args.add(boolParamsIt.next()) - CodeUtils.LogDataTypes.LONG -> args.add(longParamsIt.next()) - CodeUtils.LogDataTypes.DOUBLE -> args.add(doubleParamsIt.next()) - CodeUtils.LogDataTypes.STRING -> args.add(strParmIt.next()) + LogDataType.BOOLEAN -> args.add(boolParamsIt.next()) + LogDataType.LONG -> args.add(longParamsIt.next()) + LogDataType.DOUBLE -> args.add(doubleParamsIt.next()) + LogDataType.STRING -> args.add(strParmIt.next()) + null -> throw NullPointerException() } } } catch (ex: NoSuchElementException) { @@ -85,7 +88,7 @@ class LogParser(private val configParser: ViewerConfigParser) { fun parse(protoLogInput: InputStream, jsonConfigInput: InputStream, ps: PrintStream) { val jsonReader = JsonReader(BufferedReader(InputStreamReader(jsonConfigInput))) val config = configParser.parseConfig(jsonReader) - val protoLog = WindowManagerLogFileProto.parseFrom(protoLogInput) + val protoLog = ProtoLogFileProto.parseFrom(protoLogInput) if (protoLog.magicNumber != magicNumber) { throw InvalidInputException("ProtoLog file magic number is invalid.") diff --git a/tools/protologtool/src/com/android/protologtool/ProtoLogCallProcessor.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt index 29d8ae5c6694..eae63962161c 100644 --- a/tools/protologtool/src/com/android/protologtool/ProtoLogCallProcessor.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessor.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.github.javaparser.ast.CompilationUnit import com.github.javaparser.ast.expr.Expression diff --git a/tools/protologtool/src/com/android/protologtool/ProtoLogCallVisitor.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt index 42a75f8cc22f..aa58b69d61cb 100644 --- a/tools/protologtool/src/com/android/protologtool/ProtoLogCallVisitor.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallVisitor.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.github.javaparser.ast.expr.MethodCallExpr diff --git a/tools/protologtool/src/com/android/protologtool/ProtoLogGroupReader.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt index 664c8a6506b2..75493b6427cb 100644 --- a/tools/protologtool/src/com/android/protologtool/ProtoLogGroupReader.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool -import com.android.protologtool.Constants.ENUM_VALUES_METHOD -import com.android.protologtool.Constants.GET_TAG_METHOD -import com.android.protologtool.Constants.IS_ENABLED_METHOD -import com.android.protologtool.Constants.IS_LOG_TO_LOGCAT_METHOD +import com.android.protolog.tool.Constants.ENUM_VALUES_METHOD +import com.android.server.protolog.common.IProtoLogGroup import java.io.File -import java.lang.RuntimeException import java.net.URLClassLoader class ProtoLogGroupReader { @@ -31,18 +28,10 @@ class ProtoLogGroupReader { return URLClassLoader(arrayOf(url), ProtoLogGroupReader::class.java.classLoader) } - private fun getEnumValues(clazz: Class<*>): List<Enum<*>> { + private fun getEnumValues(clazz: Class<*>): List<IProtoLogGroup> { val valuesMethod = clazz.getMethod(ENUM_VALUES_METHOD) @Suppress("UNCHECKED_CAST") - return (valuesMethod.invoke(null) as Array<Enum<*>>).toList() - } - - private fun getLogGroupFromEnumValue(group: Any, clazz: Class<*>): LogGroup { - val enabled = clazz.getMethod(IS_ENABLED_METHOD).invoke(group) as Boolean - val textEnabled = clazz.getMethod(IS_LOG_TO_LOGCAT_METHOD).invoke(group) as Boolean - val tag = clazz.getMethod(GET_TAG_METHOD).invoke(group) as String - val name = (group as Enum<*>).name - return LogGroup(name, enabled, textEnabled, tag) + return (valuesMethod.invoke(null) as Array<IProtoLogGroup>).toList() } fun loadFromJar(jarPath: String, className: String): Map<String, LogGroup> { @@ -51,7 +40,8 @@ class ProtoLogGroupReader { val clazz = classLoader.loadClass(className) val values = getEnumValues(clazz) return values.map { group -> - group.name to getLogGroupFromEnumValue(group, clazz) + group.name() to + LogGroup(group.name(), group.isEnabled, group.isLogToLogcat, group.tag) }.toMap() } catch (ex: ReflectiveOperationException) { throw RuntimeException("Unable to load ProtoLogGroup enum class", ex) diff --git a/tools/protologtool/src/com/android/protologtool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt index 618e4b14e4c5..9678ec3a02ba 100644 --- a/tools/protologtool/src/com/android/protologtool/ProtoLogTool.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool -import com.android.protologtool.CommandOptions.Companion.USAGE +import com.android.protolog.tool.CommandOptions.Companion.USAGE import com.github.javaparser.StaticJavaParser import java.io.File import java.io.FileInputStream @@ -31,6 +31,11 @@ object ProtoLogTool { exitProcess(-1) } + private fun containsProtoLogText(source: String, protoLogClassName: String): Boolean { + val protoLogSimpleClassName = protoLogClassName.substringAfterLast('.') + return source.contains(protoLogSimpleClassName) + } + private fun processClasses(command: CommandOptions) { val groups = ProtoLogGroupReader() .loadFromJar(command.protoLogGroupsJarArg, command.protoLogGroupsClassNameArg) @@ -44,7 +49,11 @@ object ProtoLogTool { val file = File(path) val text = file.readText() val code = StaticJavaParser.parse(text) - val outSrc = transformer.processClass(text, code) + val outSrc = when { + containsProtoLogText(text, command.protoLogClassNameArg) -> + transformer.processClass(text, code) + else -> text + } val pack = if (code.packageDeclaration.isPresent) code.packageDeclaration .get().nameAsString else "" val newPath = pack.replace('.', '/') + '/' + file.name @@ -65,14 +74,17 @@ object ProtoLogTool { val builder = ViewerConfigBuilder(processor) command.javaSourceArgs.forEach { path -> val file = File(path) - builder.processClass(StaticJavaParser.parse(file)) + val text = file.readText() + if (containsProtoLogText(text, command.protoLogClassNameArg)) { + builder.processClass(StaticJavaParser.parse(text)) + } } val out = FileOutputStream(command.viewerConfigJsonArg) out.write(builder.build().toByteArray()) out.close() } - fun read(command: CommandOptions) { + private fun read(command: CommandOptions) { LogParser(ViewerConfigParser()) .parse(FileInputStream(command.logProtofileArg), FileInputStream(command.viewerConfigJsonArg), System.out) diff --git a/tools/protologtool/src/com/android/protologtool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt index f915ea6eb186..c3920780b22a 100644 --- a/tools/protologtool/src/com/android/protologtool/SourceTransformer.kt +++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt @@ -14,26 +14,32 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool -import com.android.protologtool.Constants.IS_LOG_TO_ANY_METHOD +import com.android.protolog.tool.Constants.IS_LOG_TO_ANY_METHOD +import com.android.server.protolog.common.LogDataType import com.github.javaparser.StaticJavaParser import com.github.javaparser.ast.CompilationUnit import com.github.javaparser.ast.NodeList import com.github.javaparser.ast.body.VariableDeclarator import com.github.javaparser.ast.expr.BooleanLiteralExpr import com.github.javaparser.ast.expr.CastExpr +import com.github.javaparser.ast.expr.Expression import com.github.javaparser.ast.expr.FieldAccessExpr import com.github.javaparser.ast.expr.IntegerLiteralExpr import com.github.javaparser.ast.expr.MethodCallExpr import com.github.javaparser.ast.expr.NameExpr import com.github.javaparser.ast.expr.NullLiteralExpr import com.github.javaparser.ast.expr.SimpleName +import com.github.javaparser.ast.expr.TypeExpr import com.github.javaparser.ast.expr.VariableDeclarationExpr import com.github.javaparser.ast.stmt.BlockStmt import com.github.javaparser.ast.stmt.ExpressionStmt import com.github.javaparser.ast.stmt.IfStmt import com.github.javaparser.ast.type.ArrayType +import com.github.javaparser.ast.type.ClassOrInterfaceType +import com.github.javaparser.ast.type.PrimitiveType +import com.github.javaparser.ast.type.Type import com.github.javaparser.printer.PrettyPrinter import com.github.javaparser.printer.PrettyPrinterConfiguration import com.github.javaparser.printer.lexicalpreservation.LexicalPreservingPrinter @@ -77,8 +83,8 @@ class SourceTransformer( // Insert message string hash as a second argument. // Out: ProtoLog.e(GROUP, 1234, null, arg) newCall.arguments.add(1, IntegerLiteralExpr(hash)) - val argTypes = CodeUtils.parseFormatString(messageString) - val typeMask = CodeUtils.logDataTypesToBitMask(argTypes) + val argTypes = LogDataType.parseFormatString(messageString) + val typeMask = LogDataType.logDataTypesToBitMask(argTypes) // Insert bitmap representing which Number parameters are to be considered as // floating point numbers. // Out: ProtoLog.e(GROUP, 1234, 0, null, arg) @@ -101,8 +107,8 @@ class SourceTransformer( // Out: long protoLogParam0 = arg argTypes.forEachIndexed { idx, type -> val varName = "protoLogParam$idx" - val declaration = VariableDeclarator(type.type, varName, - type.toType(newCall.arguments[idx + 4].clone())) + val declaration = VariableDeclarator(getASTTypeForDataType(type), varName, + getConversionForType(type)(newCall.arguments[idx + 4].clone())) blockStmt.addStatement(ExpressionStmt(VariableDeclarationExpr(declaration))) newCall.setArgument(idx + 4, NameExpr(SimpleName(varName))) } @@ -174,6 +180,34 @@ class SourceTransformer( inlinePrinter = PrettyPrinter(config) } + companion object { + private val stringType: ClassOrInterfaceType = + StaticJavaParser.parseClassOrInterfaceType("String") + + fun getASTTypeForDataType(type: Int): Type { + return when (type) { + LogDataType.STRING -> stringType.clone() + LogDataType.LONG -> PrimitiveType.longType() + LogDataType.DOUBLE -> PrimitiveType.doubleType() + LogDataType.BOOLEAN -> PrimitiveType.booleanType() + else -> { + // Should never happen. + throw RuntimeException("Invalid LogDataType") + } + } + } + + fun getConversionForType(type: Int): (Expression) -> Expression { + return when (type) { + LogDataType.STRING -> { expr -> + MethodCallExpr(TypeExpr(StaticJavaParser.parseClassOrInterfaceType("String")), + SimpleName("valueOf"), NodeList(expr)) + } + else -> { expr -> expr } + } + } + } + private val protoLogImplClassNode = StaticJavaParser.parseExpression<FieldAccessExpr>(protoLogImplClassName) private var processedCode: MutableList<String> = mutableListOf() diff --git a/tools/protologtool/src/com/android/protologtool/ViewerConfigBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt index 8ce9a49c0302..a75b5c9bbe4b 100644 --- a/tools/protologtool/src/com/android/protologtool/ViewerConfigBuilder.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigBuilder.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.android.json.stream.JsonWriter import com.github.javaparser.ast.CompilationUnit -import com.android.protologtool.Constants.VERSION +import com.android.protolog.tool.Constants.VERSION import com.github.javaparser.ast.expr.MethodCallExpr import java.io.StringWriter diff --git a/tools/protologtool/src/com/android/protologtool/ViewerConfigParser.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt index 69cf92d4d228..7278db0094e6 100644 --- a/tools/protologtool/src/com/android/protologtool/ViewerConfigParser.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigParser.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.android.json.stream.JsonReader @@ -31,8 +31,7 @@ open class ViewerConfigParser { var level: String? = null var groupName: String? = null while (jsonReader.hasNext()) { - val key = jsonReader.nextName() - when (key) { + when (jsonReader.nextName()) { "message" -> message = jsonReader.nextString() "level" -> level = jsonReader.nextString() "group" -> groupName = jsonReader.nextString() @@ -52,8 +51,7 @@ open class ViewerConfigParser { jsonReader.beginObject() var tag: String? = null while (jsonReader.hasNext()) { - val key = jsonReader.nextName() - when (key) { + when (jsonReader.nextName()) { "tag" -> tag = jsonReader.nextString() else -> jsonReader.skipValue() } @@ -98,8 +96,7 @@ open class ViewerConfigParser { jsonReader.beginObject() while (jsonReader.hasNext()) { - val key = jsonReader.nextName() - when (key) { + when (jsonReader.nextName()) { "messages" -> messages = parseMessages(jsonReader) "groups" -> groups = parseGroups(jsonReader) "version" -> version = jsonReader.nextString() diff --git a/tools/protologtool/src/com/android/protologtool/exceptions.kt b/tools/protologtool/src/com/android/protolog/tool/exceptions.kt index 2199785a335b..0401d8f8baa0 100644 --- a/tools/protologtool/src/com/android/protologtool/exceptions.kt +++ b/tools/protologtool/src/com/android/protolog/tool/exceptions.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.github.javaparser.ast.Node import java.lang.Exception @@ -27,17 +27,7 @@ class IllegalImportException(message: String) : Exception(message) class InvalidProtoLogCallException(message: String, node: Node) : RuntimeException("$message\nAt: $node") -class InvalidViewerConfigException : Exception { - constructor(message: String) : super(message) - - constructor(message: String, ex: Exception) : super(message, ex) -} - -class InvalidFormatStringException : Exception { - constructor(message: String) : super(message) - - constructor(message: String, ex: Exception) : super(message, ex) -} +class InvalidViewerConfigException(message: String) : Exception(message) class InvalidInputException(message: String) : Exception(message) diff --git a/tools/protologtool/tests/com/android/protologtool/CodeUtilsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt index 82daa736e1bc..337ed995891c 100644 --- a/tools/protologtool/tests/com/android/protologtool/CodeUtilsTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/CodeUtilsTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.github.javaparser.StaticJavaParser import com.github.javaparser.ast.expr.BinaryExpr @@ -164,43 +164,4 @@ class CodeUtilsTest { val out = CodeUtils.concatMultilineString(code) assertEquals("testabc1234test", out) } - - @Test - fun parseFormatString() { - val str = "%b %d %o %x %f %e %g %s %%" - val out = CodeUtils.parseFormatString(str) - assertEquals(listOf( - CodeUtils.LogDataTypes.BOOLEAN, - CodeUtils.LogDataTypes.LONG, - CodeUtils.LogDataTypes.LONG, - CodeUtils.LogDataTypes.LONG, - CodeUtils.LogDataTypes.DOUBLE, - CodeUtils.LogDataTypes.DOUBLE, - CodeUtils.LogDataTypes.DOUBLE, - CodeUtils.LogDataTypes.STRING - ), out) - } - - @Test(expected = InvalidFormatStringException::class) - fun parseFormatString_invalid() { - val str = "%q" - CodeUtils.parseFormatString(str) - } - - @Test - fun logDataTypesToBitMask() { - val types = listOf(CodeUtils.LogDataTypes.STRING, CodeUtils.LogDataTypes.DOUBLE, - CodeUtils.LogDataTypes.LONG, CodeUtils.LogDataTypes.BOOLEAN) - val mask = CodeUtils.logDataTypesToBitMask(types) - assertEquals(0b11011000, mask) - } - - @Test(expected = InvalidFormatStringException::class) - fun logDataTypesToBitMask_toManyParams() { - val types = mutableListOf<CodeUtils.LogDataTypes>() - for (i in 0..16) { - types.add(CodeUtils.LogDataTypes.STRING) - } - CodeUtils.logDataTypesToBitMask(types) - } } diff --git a/tools/protologtool/tests/com/android/protologtool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt index c1cd473574c2..615712e10bcf 100644 --- a/tools/protologtool/tests/com/android/protologtool/CommandOptionsTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import org.junit.Assert.assertEquals import org.junit.Test diff --git a/tools/protologtool/tests/com/android/protologtool/LogParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt index 7106ea6fa168..04a3bfa499d8 100644 --- a/tools/protologtool/tests/com/android/protologtool/LogParserTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.android.json.stream.JsonReader -import com.android.server.wm.ProtoLogMessage -import com.android.server.wm.WindowManagerLogFileProto +import com.android.server.protolog.ProtoLogMessage +import com.android.server.protolog.ProtoLogFileProto import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -51,11 +51,11 @@ class LogParserTest { return "".byteInputStream() } - private fun buildProtoInput(logBuilder: WindowManagerLogFileProto.Builder): InputStream { + private fun buildProtoInput(logBuilder: ProtoLogFileProto.Builder): InputStream { logBuilder.setVersion(Constants.VERSION) logBuilder.magicNumber = - WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or - WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong() + ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or + ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong() return logBuilder.build().toByteArray().inputStream() } @@ -68,7 +68,7 @@ class LogParserTest { config[70933285] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b", "ERROR", "WindowManager") - val logBuilder = WindowManagerLogFileProto.newBuilder() + val logBuilder = ProtoLogFileProto.newBuilder() val logMessageBuilder = ProtoLogMessage.newBuilder() logMessageBuilder .setMessageHash(70933285) @@ -87,7 +87,7 @@ class LogParserTest { config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" + " %x %e %g %s %f", "ERROR", "WindowManager") - val logBuilder = WindowManagerLogFileProto.newBuilder() + val logBuilder = ProtoLogFileProto.newBuilder() val logMessageBuilder = ProtoLogMessage.newBuilder() logMessageBuilder .setMessageHash(123) @@ -110,7 +110,7 @@ class LogParserTest { config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o", "ERROR", "WindowManager") - val logBuilder = WindowManagerLogFileProto.newBuilder() + val logBuilder = ProtoLogFileProto.newBuilder() val logMessageBuilder = ProtoLogMessage.newBuilder() logMessageBuilder .setMessageHash(123) @@ -132,7 +132,7 @@ class LogParserTest { config[123] = ViewerConfigParser.ConfigEntry("Test completed successfully: %b %d %% %o" + " %x %e %g %s %f", "ERROR", "WindowManager") - val logBuilder = WindowManagerLogFileProto.newBuilder() + val logBuilder = ProtoLogFileProto.newBuilder() val logMessageBuilder = ProtoLogMessage.newBuilder() logMessageBuilder .setMessageHash(123) @@ -149,7 +149,7 @@ class LogParserTest { @Test(expected = InvalidInputException::class) fun parse_invalidMagicNumber() { - val logBuilder = WindowManagerLogFileProto.newBuilder() + val logBuilder = ProtoLogFileProto.newBuilder() logBuilder.setVersion(Constants.VERSION) logBuilder.magicNumber = 0 val stream = logBuilder.build().toByteArray().inputStream() @@ -159,11 +159,11 @@ class LogParserTest { @Test(expected = InvalidInputException::class) fun parse_invalidVersion() { - val logBuilder = WindowManagerLogFileProto.newBuilder() + val logBuilder = ProtoLogFileProto.newBuilder() logBuilder.setVersion("invalid") logBuilder.magicNumber = - WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or - WindowManagerLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong() + ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_H.number.toLong() shl 32 or + ProtoLogFileProto.MagicNumber.MAGIC_NUMBER_L.number.toLong() val stream = logBuilder.build().toByteArray().inputStream() parser.parse(stream, getConfigDummyStream(), printStream) @@ -171,7 +171,7 @@ class LogParserTest { @Test fun parse_noConfig() { - val logBuilder = WindowManagerLogFileProto.newBuilder() + val logBuilder = ProtoLogFileProto.newBuilder() val logMessageBuilder = ProtoLogMessage.newBuilder() logMessageBuilder .setMessageHash(70933285) diff --git a/tools/protologtool/tests/com/android/protologtool/ProtoLogCallProcessorTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt index dcb1f7fe3366..d20ce7ec4dcb 100644 --- a/tools/protologtool/tests/com/android/protologtool/ProtoLogCallProcessorTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.github.javaparser.StaticJavaParser import com.github.javaparser.ast.expr.MethodCallExpr diff --git a/tools/protologtool/tests/com/android/protologtool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt index 2cd85627b94b..d6e4a36dc3da 100644 --- a/tools/protologtool/tests/com/android/protologtool/SourceTransformerTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.github.javaparser.StaticJavaParser import com.github.javaparser.ast.CompilationUnit diff --git a/tools/protologtool/tests/com/android/protologtool/ViewerConfigBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt index 53d2e8b0f4fa..f435d4065256 100644 --- a/tools/protologtool/tests/com/android/protologtool/ViewerConfigBuilderTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigBuilderTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.android.json.stream.JsonReader import com.github.javaparser.ast.CompilationUnit diff --git a/tools/protologtool/tests/com/android/protologtool/ViewerConfigParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigParserTest.kt index c0cea733eadd..dc3ef7c57b35 100644 --- a/tools/protologtool/tests/com/android/protologtool/ViewerConfigParserTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigParserTest.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.protologtool +package com.android.protolog.tool import com.android.json.stream.JsonReader +import org.junit.Assert.assertEquals import org.junit.Test import java.io.StringReader -import org.junit.Assert.assertEquals class ViewerConfigParserTest { private val parser = ViewerConfigParser() @@ -322,6 +322,6 @@ class ViewerConfigParserTest { } } """ - val config = parser.parseConfig(getJSONReader(json)) + parser.parseConfig(getJSONReader(json)) } } |