1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.protolog; 18 19 import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STACKTRACE; 20 import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STRING_ARGS; 21 import static android.internal.perfetto.protos.ProfileCommon.InternedString.IID; 22 import static android.internal.perfetto.protos.ProfileCommon.InternedString.STR; 23 import static android.internal.perfetto.protos.Protolog.ProtoLogMessage.BOOLEAN_PARAMS; 24 import static android.internal.perfetto.protos.Protolog.ProtoLogMessage.DOUBLE_PARAMS; 25 import static android.internal.perfetto.protos.Protolog.ProtoLogMessage.MESSAGE_ID; 26 import static android.internal.perfetto.protos.Protolog.ProtoLogMessage.SINT64_PARAMS; 27 import static android.internal.perfetto.protos.Protolog.ProtoLogMessage.STACKTRACE_IID; 28 import static android.internal.perfetto.protos.Protolog.ProtoLogMessage.STR_PARAM_IIDS; 29 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS; 30 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID; 31 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME; 32 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG; 33 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; 34 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; 35 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; 36 import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; 37 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.INTERNED_DATA; 38 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_MESSAGE; 39 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_VIEWER_CONFIG; 40 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.SEQUENCE_FLAGS; 41 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.SEQ_INCREMENTAL_STATE_CLEARED; 42 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.SEQ_NEEDS_INCREMENTAL_STATE; 43 import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP; 44 45 import android.annotation.Nullable; 46 import android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData; 47 import android.os.ShellCommand; 48 import android.os.SystemClock; 49 import android.os.Trace; 50 import android.text.TextUtils; 51 import android.tracing.perfetto.DataSourceParams; 52 import android.tracing.perfetto.InitArguments; 53 import android.tracing.perfetto.Producer; 54 import android.tracing.perfetto.TracingContext; 55 import android.util.ArrayMap; 56 import android.util.Log; 57 import android.util.LongArray; 58 import android.util.Slog; 59 import android.util.proto.ProtoInputStream; 60 import android.util.proto.ProtoOutputStream; 61 62 import com.android.internal.annotations.VisibleForTesting; 63 import com.android.internal.protolog.common.ILogger; 64 import com.android.internal.protolog.common.IProtoLog; 65 import com.android.internal.protolog.common.IProtoLogGroup; 66 import com.android.internal.protolog.common.LogDataType; 67 import com.android.internal.protolog.common.LogLevel; 68 69 import java.io.FileInputStream; 70 import java.io.FileNotFoundException; 71 import java.io.IOException; 72 import java.io.PrintWriter; 73 import java.io.StringWriter; 74 import java.util.ArrayList; 75 import java.util.Map; 76 import java.util.Set; 77 import java.util.TreeMap; 78 import java.util.concurrent.ExecutorService; 79 import java.util.concurrent.Executors; 80 import java.util.concurrent.atomic.AtomicInteger; 81 82 /** 83 * A service for the ProtoLog logging system. 84 */ 85 public class PerfettoProtoLogImpl implements IProtoLog { 86 private static final String LOG_TAG = "ProtoLog"; 87 private final AtomicInteger mTracingInstances = new AtomicInteger(); 88 89 private final ProtoLogDataSource mDataSource = new ProtoLogDataSource( 90 this::onTracingInstanceStart, 91 this::dumpTransitionTraceConfig, 92 this::onTracingInstanceStop 93 ); 94 private final ProtoLogViewerConfigReader mViewerConfigReader; 95 private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider; 96 private final TreeMap<String, IProtoLogGroup> mLogGroups; 97 private final Runnable mCacheUpdater; 98 99 private final Map<LogLevel, Integer> mDefaultLogLevelCounts = new ArrayMap<>(); 100 private final Map<IProtoLogGroup, Map<LogLevel, Integer>> mLogLevelCounts = new ArrayMap<>(); 101 102 private final ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor(); 103 PerfettoProtoLogImpl(String viewerConfigFilePath, TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater)104 public PerfettoProtoLogImpl(String viewerConfigFilePath, 105 TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) { 106 this(() -> { 107 try { 108 return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); 109 } catch (FileNotFoundException e) { 110 Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e); 111 return null; 112 } 113 }, logGroups, cacheUpdater); 114 } 115 PerfettoProtoLogImpl( ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater )116 public PerfettoProtoLogImpl( 117 ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, 118 TreeMap<String, IProtoLogGroup> logGroups, 119 Runnable cacheUpdater 120 ) { 121 this(viewerConfigInputStreamProvider, 122 new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups, 123 cacheUpdater); 124 } 125 126 @VisibleForTesting PerfettoProtoLogImpl( ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, ProtoLogViewerConfigReader viewerConfigReader, TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater )127 public PerfettoProtoLogImpl( 128 ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, 129 ProtoLogViewerConfigReader viewerConfigReader, 130 TreeMap<String, IProtoLogGroup> logGroups, 131 Runnable cacheUpdater 132 ) { 133 Producer.init(InitArguments.DEFAULTS); 134 DataSourceParams params = 135 new DataSourceParams.Builder() 136 .setBufferExhaustedPolicy( 137 DataSourceParams 138 .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) 139 .build(); 140 mDataSource.register(params); 141 this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider; 142 this.mViewerConfigReader = viewerConfigReader; 143 this.mLogGroups = logGroups; 144 this.mCacheUpdater = cacheUpdater; 145 } 146 147 /** 148 * Main log method, do not call directly. 149 */ 150 @VisibleForTesting 151 @Override log(LogLevel level, IProtoLogGroup group, long messageHash, int paramsMask, @Nullable String messageString, Object[] args)152 public void log(LogLevel level, IProtoLogGroup group, long messageHash, int paramsMask, 153 @Nullable String messageString, Object[] args) { 154 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "log"); 155 156 long tsNanos = SystemClock.elapsedRealtimeNanos(); 157 try { 158 mBackgroundLoggingService.submit(() -> 159 logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos)); 160 if (group.isLogToLogcat()) { 161 logToLogcat(group.getTag(), level, messageHash, messageString, args); 162 } 163 } finally { 164 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); 165 } 166 } 167 dumpTransitionTraceConfig()168 private void dumpTransitionTraceConfig() { 169 ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream(); 170 171 if (pis == null) { 172 Slog.w(LOG_TAG, "Failed to get viewer input stream."); 173 return; 174 } 175 176 mDataSource.trace(ctx -> { 177 try { 178 final ProtoOutputStream os = ctx.newTracePacket(); 179 180 os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos()); 181 182 final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG); 183 while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 184 if (pis.getFieldNumber() == (int) MESSAGES) { 185 writeViewerConfigMessage(pis, os); 186 } 187 188 if (pis.getFieldNumber() == (int) GROUPS) { 189 writeViewerConfigGroup(pis, os); 190 } 191 } 192 193 os.end(outProtologViewerConfigToken); 194 } catch (IOException e) { 195 Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e); 196 } 197 }); 198 } 199 writeViewerConfigGroup( ProtoInputStream pis, ProtoOutputStream os)200 private static void writeViewerConfigGroup( 201 ProtoInputStream pis, ProtoOutputStream os) throws IOException { 202 final long inGroupToken = pis.start(GROUPS); 203 final long outGroupToken = os.start(GROUPS); 204 205 while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 206 switch (pis.getFieldNumber()) { 207 case (int) ID: 208 int id = pis.readInt(ID); 209 os.write(ID, id); 210 break; 211 case (int) NAME: 212 String name = pis.readString(NAME); 213 os.write(NAME, name); 214 break; 215 case (int) TAG: 216 String tag = pis.readString(TAG); 217 os.write(TAG, tag); 218 break; 219 default: 220 throw new RuntimeException( 221 "Unexpected field id " + pis.getFieldNumber()); 222 } 223 } 224 225 pis.end(inGroupToken); 226 os.end(outGroupToken); 227 } 228 writeViewerConfigMessage( ProtoInputStream pis, ProtoOutputStream os)229 private static void writeViewerConfigMessage( 230 ProtoInputStream pis, ProtoOutputStream os) throws IOException { 231 final long inMessageToken = pis.start(MESSAGES); 232 final long outMessagesToken = os.start(MESSAGES); 233 234 while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { 235 switch (pis.getFieldNumber()) { 236 case (int) MessageData.MESSAGE_ID: 237 os.write(MessageData.MESSAGE_ID, 238 pis.readLong(MessageData.MESSAGE_ID)); 239 break; 240 case (int) MESSAGE: 241 os.write(MESSAGE, pis.readString(MESSAGE)); 242 break; 243 case (int) LEVEL: 244 os.write(LEVEL, pis.readInt(LEVEL)); 245 break; 246 case (int) GROUP_ID: 247 os.write(GROUP_ID, pis.readInt(GROUP_ID)); 248 break; 249 default: 250 throw new RuntimeException( 251 "Unexpected field id " + pis.getFieldNumber()); 252 } 253 } 254 255 pis.end(inMessageToken); 256 os.end(outMessagesToken); 257 } 258 logToLogcat(String tag, LogLevel level, long messageHash, @Nullable String messageString, Object[] args)259 private void logToLogcat(String tag, LogLevel level, long messageHash, 260 @Nullable String messageString, Object[] args) { 261 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logToLogcat"); 262 try { 263 doLogToLogcat(tag, level, messageHash, messageString, args); 264 } finally { 265 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); 266 } 267 } 268 doLogToLogcat(String tag, LogLevel level, long messageHash, @androidx.annotation.Nullable String messageString, Object[] args)269 private void doLogToLogcat(String tag, LogLevel level, long messageHash, 270 @androidx.annotation.Nullable String messageString, Object[] args) { 271 String message = null; 272 if (messageString == null) { 273 messageString = mViewerConfigReader.getViewerString(messageHash); 274 } 275 if (messageString != null) { 276 if (args != null) { 277 try { 278 message = TextUtils.formatSimple(messageString, args); 279 } catch (Exception ex) { 280 Slog.w(LOG_TAG, "Invalid ProtoLog format string.", ex); 281 } 282 } else { 283 message = messageString; 284 } 285 } 286 if (message == null) { 287 StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE (" + messageHash + ")"); 288 for (Object o : args) { 289 builder.append(" ").append(o); 290 } 291 message = builder.toString(); 292 } 293 passToLogcat(tag, level, message); 294 } 295 296 /** 297 * SLog wrapper. 298 */ 299 @VisibleForTesting passToLogcat(String tag, LogLevel level, String message)300 public void passToLogcat(String tag, LogLevel level, String message) { 301 switch (level) { 302 case DEBUG: 303 Slog.d(tag, message); 304 break; 305 case VERBOSE: 306 Slog.v(tag, message); 307 break; 308 case INFO: 309 Slog.i(tag, message); 310 break; 311 case WARN: 312 Slog.w(tag, message); 313 break; 314 case ERROR: 315 Slog.e(tag, message); 316 break; 317 case WTF: 318 Slog.wtf(tag, message); 319 break; 320 } 321 } 322 logToProto(LogLevel level, String groupName, long messageHash, int paramsMask, Object[] args, long tsNanos)323 private void logToProto(LogLevel level, String groupName, long messageHash, int paramsMask, 324 Object[] args, long tsNanos) { 325 if (!isProtoEnabled()) { 326 return; 327 } 328 329 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logToProto"); 330 try { 331 doLogToProto(level, groupName, messageHash, paramsMask, args, tsNanos); 332 } finally { 333 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); 334 } 335 } 336 doLogToProto(LogLevel level, String groupName, long messageHash, int paramsMask, Object[] args, long tsNanos)337 private void doLogToProto(LogLevel level, String groupName, long messageHash, int paramsMask, 338 Object[] args, long tsNanos) { 339 mDataSource.trace(ctx -> { 340 final ProtoLogDataSource.TlsState tlsState = ctx.getCustomTlsState(); 341 final LogLevel logFrom = tlsState.getLogFromLevel(groupName); 342 343 if (level.ordinal() < logFrom.ordinal()) { 344 return; 345 } 346 347 if (args != null) { 348 // Intern all string params before creating the trace packet for the proto 349 // message so that the interned strings appear before in the trace to make the 350 // trace processing easier. 351 int argIndex = 0; 352 for (Object o : args) { 353 int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex); 354 if (type == LogDataType.STRING) { 355 internStringArg(ctx, o.toString()); 356 } 357 argIndex++; 358 } 359 } 360 361 int internedStacktrace = 0; 362 if (tlsState.getShouldCollectStacktrace(groupName)) { 363 // Intern stackstraces before creating the trace packet for the proto message so 364 // that the interned stacktrace strings appear before in the trace to make the 365 // trace processing easier. 366 String stacktrace = collectStackTrace(); 367 internedStacktrace = internStacktraceString(ctx, stacktrace); 368 } 369 370 final ProtoOutputStream os = ctx.newTracePacket(); 371 os.write(TIMESTAMP, tsNanos); 372 long token = os.start(PROTOLOG_MESSAGE); 373 os.write(MESSAGE_ID, messageHash); 374 375 boolean needsIncrementalState = false; 376 377 if (args != null) { 378 379 int argIndex = 0; 380 LongArray longParams = new LongArray(); 381 ArrayList<Double> doubleParams = new ArrayList<>(); 382 ArrayList<Boolean> booleanParams = new ArrayList<>(); 383 for (Object o : args) { 384 int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex); 385 try { 386 switch (type) { 387 case LogDataType.STRING: 388 final int internedStringId = internStringArg(ctx, o.toString()); 389 os.write(STR_PARAM_IIDS, internedStringId); 390 needsIncrementalState = true; 391 break; 392 case LogDataType.LONG: 393 longParams.add(((Number) o).longValue()); 394 break; 395 case LogDataType.DOUBLE: 396 doubleParams.add(((Number) o).doubleValue()); 397 break; 398 case LogDataType.BOOLEAN: 399 booleanParams.add((boolean) o); 400 break; 401 } 402 } catch (ClassCastException ex) { 403 Slog.e(LOG_TAG, "Invalid ProtoLog paramsMask", ex); 404 } 405 argIndex++; 406 } 407 408 for (int i = 0; i < longParams.size(); ++i) { 409 os.write(SINT64_PARAMS, longParams.get(i)); 410 } 411 doubleParams.forEach(it -> os.write(DOUBLE_PARAMS, it)); 412 // Converting booleans to int because Perfetto doesn't yet support repeated 413 // booleans, so we use a repeated integers instead (b/313651412). 414 booleanParams.forEach(it -> os.write(BOOLEAN_PARAMS, it ? 1 : 0)); 415 } 416 417 if (tlsState.getShouldCollectStacktrace(groupName)) { 418 os.write(STACKTRACE_IID, internedStacktrace); 419 } 420 421 os.end(token); 422 423 if (needsIncrementalState) { 424 os.write(SEQUENCE_FLAGS, SEQ_NEEDS_INCREMENTAL_STATE); 425 } 426 427 }); 428 } 429 430 private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 12; 431 collectStackTrace()432 private String collectStackTrace() { 433 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); 434 StringWriter sw = new StringWriter(); 435 try (PrintWriter pw = new PrintWriter(sw)) { 436 for (int i = STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL; i < stackTrace.length; ++i) { 437 pw.println("\tat " + stackTrace[i]); 438 } 439 } 440 441 return sw.toString(); 442 } 443 internStacktraceString(TracingContext< ProtoLogDataSource.Instance, ProtoLogDataSource.TlsState, ProtoLogDataSource.IncrementalState> ctx, String stacktrace)444 private int internStacktraceString(TracingContext< 445 ProtoLogDataSource.Instance, 446 ProtoLogDataSource.TlsState, 447 ProtoLogDataSource.IncrementalState> ctx, 448 String stacktrace) { 449 final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState(); 450 return internString(ctx, incrementalState.stacktraceInterningMap, 451 PROTOLOG_STACKTRACE, stacktrace); 452 } 453 internStringArg( TracingContext<ProtoLogDataSource.Instance, ProtoLogDataSource.TlsState, ProtoLogDataSource.IncrementalState> ctx, String string )454 private int internStringArg( 455 TracingContext<ProtoLogDataSource.Instance, ProtoLogDataSource.TlsState, 456 ProtoLogDataSource.IncrementalState> ctx, 457 String string 458 ) { 459 final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState(); 460 return internString(ctx, incrementalState.argumentInterningMap, 461 PROTOLOG_STRING_ARGS, string); 462 } 463 internString( TracingContext<ProtoLogDataSource.Instance, ProtoLogDataSource.TlsState, ProtoLogDataSource.IncrementalState> ctx, Map<String, Integer> internMap, long fieldId, String string )464 private int internString( 465 TracingContext<ProtoLogDataSource.Instance, ProtoLogDataSource.TlsState, 466 ProtoLogDataSource.IncrementalState> ctx, 467 Map<String, Integer> internMap, 468 long fieldId, 469 String string 470 ) { 471 final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState(); 472 473 if (!incrementalState.clearReported) { 474 final ProtoOutputStream os = ctx.newTracePacket(); 475 os.write(SEQUENCE_FLAGS, SEQ_INCREMENTAL_STATE_CLEARED); 476 incrementalState.clearReported = true; 477 } 478 479 if (!internMap.containsKey(string)) { 480 final int internedIndex = internMap.size() + 1; 481 internMap.put(string, internedIndex); 482 483 final ProtoOutputStream os = ctx.newTracePacket(); 484 final long token = os.start(INTERNED_DATA); 485 final long innerToken = os.start(fieldId); 486 os.write(IID, internedIndex); 487 os.write(STR, string.getBytes()); 488 os.end(innerToken); 489 os.end(token); 490 } 491 492 return internMap.get(string); 493 } 494 495 /** 496 * Returns {@code true} iff logging to proto is enabled. 497 */ isProtoEnabled()498 public boolean isProtoEnabled() { 499 return mTracingInstances.get() > 0; 500 } 501 502 /** 503 * Start text logging 504 * @param groups Groups to start text logging for 505 * @param logger A logger to write status updates to 506 * @return status code 507 */ startLoggingToLogcat(String[] groups, ILogger logger)508 public int startLoggingToLogcat(String[] groups, ILogger logger) { 509 mViewerConfigReader.loadViewerConfig(logger); 510 return setTextLogging(true, logger, groups); 511 } 512 513 /** 514 * Stop text logging 515 * @param groups Groups to start text logging for 516 * @param logger A logger to write status updates to 517 * @return status code 518 */ stopLoggingToLogcat(String[] groups, ILogger logger)519 public int stopLoggingToLogcat(String[] groups, ILogger logger) { 520 mViewerConfigReader.unloadViewerConfig(); 521 return setTextLogging(false, logger, groups); 522 } 523 524 @Override isEnabled(IProtoLogGroup group, LogLevel level)525 public boolean isEnabled(IProtoLogGroup group, LogLevel level) { 526 return group.isLogToLogcat() || getLogFromLevel(group).ordinal() <= level.ordinal(); 527 } 528 getLogFromLevel(IProtoLogGroup group)529 private LogLevel getLogFromLevel(IProtoLogGroup group) { 530 if (mLogLevelCounts.containsKey(group)) { 531 for (LogLevel logLevel : LogLevel.values()) { 532 if (mLogLevelCounts.get(group).getOrDefault(logLevel, 0) > 0) { 533 return logLevel; 534 } 535 } 536 } else { 537 for (LogLevel logLevel : LogLevel.values()) { 538 if (mDefaultLogLevelCounts.getOrDefault(logLevel, 0) > 0) { 539 return logLevel; 540 } 541 } 542 } 543 544 return LogLevel.WTF; 545 } 546 547 /** 548 * Start logging the stack trace of the when the log message happened for target groups 549 * @return status code 550 */ startLoggingStackTrace(String[] groups, ILogger logger)551 public int startLoggingStackTrace(String[] groups, ILogger logger) { 552 return -1; 553 } 554 555 /** 556 * Stop logging the stack trace of the when the log message happened for target groups 557 * @return status code 558 */ stopLoggingStackTrace()559 public int stopLoggingStackTrace() { 560 return -1; 561 } 562 setTextLogging(boolean value, ILogger logger, String... groups)563 private int setTextLogging(boolean value, ILogger logger, String... groups) { 564 for (int i = 0; i < groups.length; i++) { 565 String group = groups[i]; 566 IProtoLogGroup g = mLogGroups.get(group); 567 if (g != null) { 568 g.setLogToLogcat(value); 569 } else { 570 logger.log("No IProtoLogGroup named " + group); 571 return -1; 572 } 573 } 574 575 mCacheUpdater.run(); 576 return 0; 577 } 578 579 /** 580 * Responds to a shell command. 581 */ onShellCommand(ShellCommand shell)582 public int onShellCommand(ShellCommand shell) { 583 PrintWriter pw = shell.getOutPrintWriter(); 584 String cmd = shell.getNextArg(); 585 if (cmd == null) { 586 return unknownCommand(pw); 587 } 588 ArrayList<String> args = new ArrayList<>(); 589 String arg; 590 while ((arg = shell.getNextArg()) != null) { 591 args.add(arg); 592 } 593 final ILogger logger = (msg) -> logAndPrintln(pw, msg); 594 String[] groups = args.toArray(new String[0]); 595 switch (cmd) { 596 case "start", "stop" -> { 597 pw.println("Command not supported. " 598 + "Please start and stop ProtoLog tracing with Perfetto."); 599 return -1; 600 } 601 case "enable-text" -> { 602 mViewerConfigReader.loadViewerConfig(logger); 603 return setTextLogging(true, logger, groups); 604 } 605 case "disable-text" -> { 606 return setTextLogging(false, logger, groups); 607 } 608 default -> { 609 return unknownCommand(pw); 610 } 611 } 612 } 613 unknownCommand(PrintWriter pw)614 private int unknownCommand(PrintWriter pw) { 615 pw.println("Unknown command"); 616 pw.println("Window manager logging options:"); 617 pw.println(" enable-text [group...]: Enable logcat logging for given groups"); 618 pw.println(" disable-text [group...]: Disable logcat logging for given groups"); 619 return -1; 620 } 621 onTracingInstanceStart(ProtoLogDataSource.ProtoLogConfig config)622 private synchronized void onTracingInstanceStart(ProtoLogDataSource.ProtoLogConfig config) { 623 this.mTracingInstances.incrementAndGet(); 624 625 final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom; 626 mDefaultLogLevelCounts.put(defaultLogFrom, 627 mDefaultLogLevelCounts.getOrDefault(defaultLogFrom, 0) + 1); 628 629 final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs(); 630 631 for (String overriddenGroupTag : overriddenGroupTags) { 632 IProtoLogGroup group = mLogGroups.get(overriddenGroupTag); 633 634 mLogLevelCounts.putIfAbsent(group, new ArrayMap<>()); 635 final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group); 636 637 final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom; 638 logLevelsCountsForGroup.put(logFromLevel, 639 logLevelsCountsForGroup.getOrDefault(logFromLevel, 0) + 1); 640 } 641 642 mCacheUpdater.run(); 643 } 644 onTracingInstanceStop(ProtoLogDataSource.ProtoLogConfig config)645 private synchronized void onTracingInstanceStop(ProtoLogDataSource.ProtoLogConfig config) { 646 this.mTracingInstances.decrementAndGet(); 647 648 final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom; 649 mDefaultLogLevelCounts.put(defaultLogFrom, 650 mDefaultLogLevelCounts.get(defaultLogFrom) - 1); 651 if (mDefaultLogLevelCounts.get(defaultLogFrom) <= 0) { 652 mDefaultLogLevelCounts.remove(defaultLogFrom); 653 } 654 655 final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs(); 656 657 for (String overriddenGroupTag : overriddenGroupTags) { 658 IProtoLogGroup group = mLogGroups.get(overriddenGroupTag); 659 660 mLogLevelCounts.putIfAbsent(group, new ArrayMap<>()); 661 final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group); 662 663 final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom; 664 logLevelsCountsForGroup.put(logFromLevel, 665 logLevelsCountsForGroup.get(logFromLevel) - 1); 666 if (logLevelsCountsForGroup.get(logFromLevel) <= 0) { 667 logLevelsCountsForGroup.remove(logFromLevel); 668 } 669 if (logLevelsCountsForGroup.isEmpty()) { 670 mLogLevelCounts.remove(group); 671 } 672 } 673 674 mCacheUpdater.run(); 675 } 676 logAndPrintln(@ullable PrintWriter pw, String msg)677 static void logAndPrintln(@Nullable PrintWriter pw, String msg) { 678 Slog.i(LOG_TAG, msg); 679 if (pw != null) { 680 pw.println(msg); 681 pw.flush(); 682 } 683 } 684 } 685 686