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