Move ProtoLog to shared place
Designed to work with both WM and WM-Shell
- source code is moved to com.android.internal.protolog, on contrast,
com.android.internal.logging is mainly used for uieventlogger
- the protolog-lib is defined in frameworks/base/Android.bp, similar to
uieventloggerlib as well
- to include protolog, reference what's in services/core/Android.bp
See also go/protolog-migration-s
Bug: 161561475
Test: atest ProtoLogImplTest \
ProtoLogViewerConfigReaderTest \
WmTests:ProtoLogIntegrationTest \
LogDataTypeTest \
protologtool-tests
Change-Id: I249a5675527d54778ac01529b4cb39fb09cb5d59
This commit is contained in:
125
core/java/com/android/internal/protolog/ProtoLogGroup.java
Normal file
125
core/java/com/android/internal/protolog/ProtoLogGroup.java
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.internal.protolog;
|
||||
|
||||
import com.android.internal.protolog.common.IProtoLogGroup;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
WM_ERROR(true, true, true, Consts.TAG_WM),
|
||||
WM_DEBUG_ORIENTATION(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_FOCUS_LIGHT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_BOOT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_RESIZE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_ADD_REMOVE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_FOCUS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
|
||||
WM_DEBUG_STARTING_WINDOW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_SHOW_TRANSACTIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_SHOW_SURFACE_ALLOC(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_APP_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_APP_TRANSITIONS_ANIM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_RECENTS_ANIMATIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_DRAW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
|
||||
WM_DEBUG_REMOTE_ANIMATIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_SCREEN_ON(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),
|
||||
WM_DEBUG_KEEP_SCREEN_ON(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_WINDOW_MOVEMENT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
WM_DEBUG_IME(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM),
|
||||
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;
|
||||
}
|
||||
|
||||
private static class Consts {
|
||||
private static final String TAG_WM = "WindowManager";
|
||||
|
||||
private static final boolean ENABLE_DEBUG = true;
|
||||
private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
|
||||
}
|
||||
}
|
||||
463
core/java/com/android/internal/protolog/ProtoLogImpl.java
Normal file
463
core/java/com/android/internal/protolog/ProtoLogImpl.java
Normal file
@@ -0,0 +1,463 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.internal.protolog;
|
||||
|
||||
import static com.android.internal.protolog.ProtoLogFileProto.LOG;
|
||||
import static com.android.internal.protolog.ProtoLogFileProto.MAGIC_NUMBER;
|
||||
import static com.android.internal.protolog.ProtoLogFileProto.MAGIC_NUMBER_H;
|
||||
import static com.android.internal.protolog.ProtoLogFileProto.MAGIC_NUMBER_L;
|
||||
import static com.android.internal.protolog.ProtoLogFileProto.REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS;
|
||||
import static com.android.internal.protolog.ProtoLogFileProto.VERSION;
|
||||
import static com.android.internal.protolog.ProtoLogMessage.BOOLEAN_PARAMS;
|
||||
import static com.android.internal.protolog.ProtoLogMessage.DOUBLE_PARAMS;
|
||||
import static com.android.internal.protolog.ProtoLogMessage.ELAPSED_REALTIME_NANOS;
|
||||
import static com.android.internal.protolog.ProtoLogMessage.MESSAGE_HASH;
|
||||
import static com.android.internal.protolog.ProtoLogMessage.SINT64_PARAMS;
|
||||
import static com.android.internal.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.internal.protolog.common.IProtoLogGroup;
|
||||
import com.android.internal.protolog.common.LogDataType;
|
||||
import com.android.internal.util.TraceBuffer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
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<>();
|
||||
|
||||
/**
|
||||
* A runnable to update the cached output of {@link #isEnabled}.
|
||||
*
|
||||
* Must be invoked after every action that could change the result of {@link #isEnabled}, eg.
|
||||
* starting / stopping proto log, or enabling / disabling log groups.
|
||||
*/
|
||||
public static Runnable sCacheUpdater = () -> { };
|
||||
|
||||
private static void addLogGroupEnum(IProtoLogGroup[] config) {
|
||||
for (IProtoLogGroup group : config) {
|
||||
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);
|
||||
}
|
||||
|
||||
/** Returns true iff logging is enabled for the given {@code IProtoLogGroup}. */
|
||||
public static boolean isEnabled(IProtoLogGroup group) {
|
||||
return group.isLogToLogcat()
|
||||
|| (group.isLogToProto() && getSingleInstance().isProtoEnabled());
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
if (args != null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
sCacheUpdater.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.");
|
||||
}
|
||||
}
|
||||
sCacheUpdater.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
sCacheUpdater.run();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.internal.protolog;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
ProtoLogImpl.logAndPrintln(pw, "Loaded " + mLogMessageMap.size()
|
||||
+ " log definitions from " + viewerConfigFilename);
|
||||
} catch (FileNotFoundException e) {
|
||||
ProtoLogImpl.logAndPrintln(pw, "Unable to load log definitions: File "
|
||||
+ viewerConfigFilename + " not found." + e);
|
||||
} catch (IOException e) {
|
||||
ProtoLogImpl.logAndPrintln(pw,
|
||||
"Unable to load log definitions: IOException while reading "
|
||||
+ viewerConfigFilename + ". " + e);
|
||||
} catch (JSONException e) {
|
||||
ProtoLogImpl.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.internal.protolog.common;
|
||||
|
||||
/**
|
||||
* Error while converting a bitmask representing a list of LogDataTypes.
|
||||
*/
|
||||
public class BitmaskConversionException extends RuntimeException {
|
||||
BitmaskConversionException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.internal.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();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.internal.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);
|
||||
}
|
||||
}
|
||||
102
core/java/com/android/internal/protolog/common/LogDataType.java
Normal file
102
core/java/com/android/internal/protolog/common/LogDataType.java
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.internal.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;
|
||||
}
|
||||
}
|
||||
115
core/java/com/android/internal/protolog/common/ProtoLog.java
Normal file
115
core/java/com/android/internal/protolog/common/ProtoLog.java
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.internal.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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user