• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.ProtologConfig.ProtoLogConfig.DEFAULT;
20 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.DEFAULT_LOG_FROM_LEVEL;
21 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.ENABLE_ALL;
22 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.GROUP_OVERRIDES;
23 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.TRACING_MODE;
24 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogGroup.COLLECT_STACKTRACE;
25 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogGroup.GROUP_NAME;
26 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogGroup.LOG_FROM;
27 
28 import android.annotation.NonNull;
29 import android.internal.perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig;
30 import android.internal.perfetto.protos.ProtologCommon;
31 import android.tracing.perfetto.CreateIncrementalStateArgs;
32 import android.tracing.perfetto.CreateTlsStateArgs;
33 import android.tracing.perfetto.DataSource;
34 import android.tracing.perfetto.DataSourceInstance;
35 import android.tracing.perfetto.FlushCallbackArguments;
36 import android.tracing.perfetto.StartCallbackArguments;
37 import android.tracing.perfetto.StopCallbackArguments;
38 import android.util.proto.ProtoInputStream;
39 import android.util.proto.WireTypeMismatchException;
40 
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.internal.protolog.common.LogLevel;
43 
44 import java.io.IOException;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.Map;
48 import java.util.Set;
49 import java.util.TreeMap;
50 
51 public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
52         ProtoLogDataSource.TlsState,
53         ProtoLogDataSource.IncrementalState> {
54     private static final String DATASOURCE_NAME = "android.protolog";
55 
56     private final Map<Integer, ProtoLogConfig> mRunningInstances = new TreeMap<>();
57 
58     @NonNull
59     private final Set<Instance.TracingInstanceStartCallback> mOnStartCallbacks = new HashSet<>();
60     @NonNull
61     private final Set<Runnable> mOnFlushCallbacks = new HashSet<>();
62     @NonNull
63     private final Set<Instance.TracingInstanceStopCallback> mOnStopCallbacks = new HashSet<>();
64 
ProtoLogDataSource()65     public ProtoLogDataSource() {
66         this(DATASOURCE_NAME);
67     }
68 
69     @VisibleForTesting
ProtoLogDataSource( @onNull String dataSourceName)70     public ProtoLogDataSource(
71             @NonNull String dataSourceName) {
72         super(dataSourceName);
73     }
74 
75     @Override
76     @NonNull
createInstance(@onNull ProtoInputStream configStream, int instanceIndex)77     public Instance createInstance(@NonNull ProtoInputStream configStream, int instanceIndex) {
78         ProtoLogConfig config = null;
79 
80         try {
81             while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
82                 try {
83                     if (configStream.getFieldNumber() == (int) DataSourceConfig.PROTOLOG_CONFIG) {
84                         if (config != null) {
85                             throw new RuntimeException("ProtoLog config already set in loop");
86                         }
87                         config = readProtoLogConfig(configStream);
88                     }
89                 } catch (WireTypeMismatchException e) {
90                     throw new RuntimeException("Failed to parse ProtoLog DataSource config", e);
91                 }
92             }
93         } catch (IOException e) {
94             throw new RuntimeException("Failed to read ProtoLog DataSource config", e);
95         }
96 
97         if (config == null) {
98             // No config found
99             config = ProtoLogConfig.DEFAULT;
100         }
101 
102         return new Instance(
103                 this, instanceIndex, config, this::executeOnStartCallbacks,
104                 this::executeOnFlushCallbacks, this::executeOnStopCallbacks);
105     }
106 
107     @Override
108     @NonNull
createTlsState(@onNull CreateTlsStateArgs<Instance> args)109     public TlsState createTlsState(@NonNull CreateTlsStateArgs<Instance> args) {
110         try (Instance dsInstance = args.getDataSourceInstanceLocked()) {
111             if (dsInstance == null) {
112                 // Datasource instance has been removed
113                 return new TlsState(ProtoLogConfig.DEFAULT);
114             }
115             return new TlsState(dsInstance.mConfig);
116         }
117     }
118 
119     @Override
120     @NonNull
createIncrementalState( @onNull CreateIncrementalStateArgs<Instance> args)121     public IncrementalState createIncrementalState(
122             @NonNull CreateIncrementalStateArgs<Instance> args) {
123         return new IncrementalState();
124     }
125 
126     /**
127      * Register an onStart callback that will be called when a tracing instance is started.
128      * The callback will be called immediately for all currently running tracing instances on
129      * registration.
130      * @param onStartCallback The callback to call on starting a tracing instance.
131      */
registerOnStartCallback( Instance.TracingInstanceStartCallback onStartCallback)132     public synchronized void registerOnStartCallback(
133             Instance.TracingInstanceStartCallback onStartCallback) {
134         mOnStartCallbacks.add(onStartCallback);
135 
136         for (var instanceIndex : mRunningInstances.keySet()) {
137             var config = mRunningInstances.get(instanceIndex);
138             onStartCallback.run(instanceIndex, config);
139         }
140     }
141 
142     /**
143      * Register an onFlush callback that will be called when a tracing instance is about to flush.
144      * @param onFlushCallback The callback to call on flushing a tracing instance
145      */
registerOnFlushCallback(Runnable onFlushCallback)146     public void registerOnFlushCallback(Runnable onFlushCallback) {
147         mOnFlushCallbacks.add(onFlushCallback);
148     }
149 
150     /**
151      * Register an onStop callback that will be called when a tracing instance is being stopped.
152      * @param onStopCallback The callback to call on stopping a tracing instance.
153      */
registerOnStopCallback(Instance.TracingInstanceStopCallback onStopCallback)154     public void registerOnStopCallback(Instance.TracingInstanceStopCallback onStopCallback) {
155         mOnStopCallbacks.add(onStopCallback);
156     }
157 
158     /**
159      * Unregister an onStart callback.
160      * @param onStartCallback The callback object to unregister.
161      */
unregisterOnStartCallback(Instance.TracingInstanceStartCallback onStartCallback)162     public void unregisterOnStartCallback(Instance.TracingInstanceStartCallback onStartCallback) {
163         mOnStartCallbacks.add(onStartCallback);
164     }
165 
166     /**
167      * Unregister an onFlush callback.
168      * @param onFlushCallback The callback object to unregister.
169      */
unregisterOnFlushCallback(Runnable onFlushCallback)170     public void unregisterOnFlushCallback(Runnable onFlushCallback) {
171         mOnFlushCallbacks.add(onFlushCallback);
172     }
173 
174     /**
175      * Unregister an onStop callback.
176      * @param onStopCallback The callback object to unregister.
177      */
unregisterOnStopCallback(Instance.TracingInstanceStopCallback onStopCallback)178     public void unregisterOnStopCallback(Instance.TracingInstanceStopCallback onStopCallback) {
179         mOnStopCallbacks.add(onStopCallback);
180     }
181 
executeOnStartCallbacks(int instanceIdx, ProtoLogConfig config)182     private synchronized void executeOnStartCallbacks(int instanceIdx, ProtoLogConfig config) {
183         mRunningInstances.put(instanceIdx, config);
184 
185         for (var onStart : mOnStartCallbacks) {
186             onStart.run(instanceIdx, config);
187         }
188     }
189 
executeOnFlushCallbacks()190     private void executeOnFlushCallbacks() {
191         for (var onFlush : mOnFlushCallbacks) {
192             onFlush.run();
193         }
194     }
195 
executeOnStopCallbacks(int instanceIdx, ProtoLogConfig config)196     private synchronized void executeOnStopCallbacks(int instanceIdx, ProtoLogConfig config) {
197         mRunningInstances.remove(instanceIdx, config);
198 
199         for (var onStop : mOnStopCallbacks) {
200             onStop.run(instanceIdx, config);
201         }
202     }
203 
204     public static class TlsState {
205         @NonNull
206         private final ProtoLogConfig mConfig;
207 
TlsState(@onNull ProtoLogConfig config)208         private TlsState(@NonNull ProtoLogConfig config) {
209             this.mConfig = config;
210         }
211 
212         /**
213          * Get the log from level for a group.
214          * @param groupTag The tag of the group to get the log from level.
215          * @return The lowest LogLevel (inclusive) to log message from.
216          */
getLogFromLevel(String groupTag)217         public LogLevel getLogFromLevel(String groupTag) {
218             return getConfigFor(groupTag).logFrom;
219         }
220 
221         /**
222          * Get if the stacktrace for the log message should be collected for this group.
223          * @param groupTag The tag of the group to get whether or not a stacktrace was requested.
224          * @return True iff a stacktrace was requested to be collected from this group in the
225          *         tracing config.
226          */
getShouldCollectStacktrace(String groupTag)227         public boolean getShouldCollectStacktrace(String groupTag) {
228             return getConfigFor(groupTag).collectStackTrace;
229         }
230 
getConfigFor(String groupTag)231         private GroupConfig getConfigFor(String groupTag) {
232             return mConfig.getConfigFor(groupTag);
233         }
234     }
235 
236     public static class IncrementalState {
237         public final Set<Integer> protologGroupInterningSet = new HashSet<>();
238         public final Set<Long> protologMessageInterningSet = new HashSet<>();
239         public final Map<String, Integer> argumentInterningMap = new HashMap<>();
240         public final Map<String, Integer> stacktraceInterningMap = new HashMap<>();
241         public boolean clearReported = false;
242     }
243 
244     public static class ProtoLogConfig {
245         private final LogLevel mDefaultLogFromLevel;
246         private final Map<String, GroupConfig> mGroupConfigs;
247 
248         private static final ProtoLogConfig DEFAULT =
249                 new ProtoLogConfig(LogLevel.WTF, new HashMap<>());
250 
ProtoLogConfig( LogLevel defaultLogFromLevel, Map<String, GroupConfig> groupConfigs)251         private ProtoLogConfig(
252                 LogLevel defaultLogFromLevel, Map<String, GroupConfig> groupConfigs) {
253             this.mDefaultLogFromLevel = defaultLogFromLevel;
254             this.mGroupConfigs = groupConfigs;
255         }
256 
getConfigFor(String groupTag)257         public GroupConfig getConfigFor(String groupTag) {
258             return mGroupConfigs.getOrDefault(groupTag, getDefaultGroupConfig());
259         }
260 
getDefaultGroupConfig()261         public GroupConfig getDefaultGroupConfig() {
262             return new GroupConfig(mDefaultLogFromLevel, false);
263         }
264 
getGroupTagsWithOverriddenConfigs()265         public Set<String> getGroupTagsWithOverriddenConfigs() {
266             return mGroupConfigs.keySet();
267         }
268     }
269 
270     public static class GroupConfig {
271         public final LogLevel logFrom;
272         public final boolean collectStackTrace;
273 
GroupConfig(LogLevel logFromLevel, boolean collectStackTrace)274         public GroupConfig(LogLevel logFromLevel, boolean collectStackTrace) {
275             this.logFrom = logFromLevel;
276             this.collectStackTrace = collectStackTrace;
277         }
278     }
279 
readProtoLogConfig(ProtoInputStream configStream)280     private ProtoLogConfig readProtoLogConfig(ProtoInputStream configStream)
281             throws IOException {
282         final long config_token = configStream.start(DataSourceConfig.PROTOLOG_CONFIG);
283 
284         LogLevel defaultLogFromLevel = LogLevel.WTF;
285         final Map<String, GroupConfig> groupConfigs = new HashMap<>();
286 
287         while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
288             switch (configStream.getFieldNumber()) {
289                 case (int) DEFAULT_LOG_FROM_LEVEL:
290                     int defaultLogFromLevelInt = configStream.readInt(DEFAULT_LOG_FROM_LEVEL);
291                     if (defaultLogFromLevelInt < defaultLogFromLevel.ordinal()) {
292                         defaultLogFromLevel =
293                                 logLevelFromInt(defaultLogFromLevelInt);
294                     }
295                     break;
296                 case (int) TRACING_MODE:
297                     int tracingMode = configStream.readInt(TRACING_MODE);
298                     switch (tracingMode) {
299                         case DEFAULT:
300                             break;
301                         case ENABLE_ALL:
302                             defaultLogFromLevel = LogLevel.DEBUG;
303                             break;
304                         default:
305                             throw new RuntimeException("Unhandled ProtoLog tracing mode type");
306                     }
307                     break;
308                 case (int) GROUP_OVERRIDES:
309                     final long group_overrides_token  = configStream.start(GROUP_OVERRIDES);
310 
311                     String tag = null;
312                     LogLevel logFromLevel = defaultLogFromLevel;
313                     boolean collectStackTrace = false;
314                     while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
315                         if (configStream.getFieldNumber() == (int) GROUP_NAME) {
316                             tag = configStream.readString(GROUP_NAME);
317                         }
318                         if (configStream.getFieldNumber() == (int) LOG_FROM) {
319                             final int logFromInt = configStream.readInt(LOG_FROM);
320                             logFromLevel = logLevelFromInt(logFromInt);
321                         }
322                         if (configStream.getFieldNumber() == (int) COLLECT_STACKTRACE) {
323                             collectStackTrace = configStream.readBoolean(COLLECT_STACKTRACE);
324                         }
325                     }
326 
327                     if (tag == null) {
328                         throw new RuntimeException("Failed to decode proto config. "
329                                 + "Got a group override without a group tag.");
330                     }
331 
332                     groupConfigs.put(tag, new GroupConfig(logFromLevel, collectStackTrace));
333 
334                     configStream.end(group_overrides_token);
335                     break;
336             }
337         }
338 
339         configStream.end(config_token);
340 
341         return new ProtoLogConfig(defaultLogFromLevel, groupConfigs);
342     }
343 
logLevelFromInt(int logFromInt)344     private LogLevel logLevelFromInt(int logFromInt) {
345         return switch (logFromInt) {
346             case (ProtologCommon.PROTOLOG_LEVEL_DEBUG) -> LogLevel.DEBUG;
347             case (ProtologCommon.PROTOLOG_LEVEL_VERBOSE) -> LogLevel.VERBOSE;
348             case (ProtologCommon.PROTOLOG_LEVEL_INFO) -> LogLevel.INFO;
349             case (ProtologCommon.PROTOLOG_LEVEL_WARN) -> LogLevel.WARN;
350             case (ProtologCommon.PROTOLOG_LEVEL_ERROR) -> LogLevel.ERROR;
351             case (ProtologCommon.PROTOLOG_LEVEL_WTF) -> LogLevel.WTF;
352             default -> throw new RuntimeException("Unhandled log level");
353         };
354     }
355 
356     public static class Instance extends DataSourceInstance {
357 
358         public interface TracingInstanceStartCallback {
359             /**
360              * Execute the tracing instance's onStart callback.
361              * @param instanceIdx The index of the tracing instance we are executing the callback
362              *                    for.
363              * @param config The protolog configuration for the tracing instance we are executing
364              *               the callback for.
365              */
run(int instanceIdx, @NonNull ProtoLogConfig config)366             void run(int instanceIdx, @NonNull ProtoLogConfig config);
367         }
368 
369         public interface TracingInstanceStopCallback {
370             /**
371              * Execute the tracing instance's onStop callback.
372              * @param instanceIdx The index of the tracing instance we are executing the callback
373              *                    for.
374              * @param config The protolog configuration for the tracing instance we are executing
375              *               the callback for.
376              */
run(int instanceIdx, @NonNull ProtoLogConfig config)377             void run(int instanceIdx, @NonNull ProtoLogConfig config);
378         }
379 
380         @NonNull
381         private final TracingInstanceStartCallback mOnStart;
382         @NonNull
383         private final Runnable mOnFlush;
384         @NonNull
385         private final TracingInstanceStopCallback mOnStop;
386         @NonNull
387         private final ProtoLogConfig mConfig;
388         private final int mInstanceIndex;
389 
Instance( @onNull DataSource<Instance, TlsState, IncrementalState> dataSource, int instanceIdx, @NonNull ProtoLogConfig config, @NonNull TracingInstanceStartCallback onStart, @NonNull Runnable onFlush, @NonNull TracingInstanceStopCallback onStop )390         public Instance(
391                 @NonNull DataSource<Instance, TlsState, IncrementalState> dataSource,
392                 int instanceIdx,
393                 @NonNull ProtoLogConfig config,
394                 @NonNull TracingInstanceStartCallback onStart,
395                 @NonNull Runnable onFlush,
396                 @NonNull TracingInstanceStopCallback onStop
397         ) {
398             super(dataSource, instanceIdx);
399             this.mInstanceIndex = instanceIdx;
400             this.mOnStart = onStart;
401             this.mOnFlush = onFlush;
402             this.mOnStop = onStop;
403             this.mConfig = config;
404         }
405 
406         @Override
onStart(StartCallbackArguments args)407         public void onStart(StartCallbackArguments args) {
408             this.mOnStart.run(this.mInstanceIndex, this.mConfig);
409         }
410 
411         @Override
onFlush(FlushCallbackArguments args)412         public void onFlush(FlushCallbackArguments args) {
413             this.mOnFlush.run();
414         }
415 
416         @Override
onStop(StopCallbackArguments args)417         public void onStop(StopCallbackArguments args) {
418             this.mOnStop.run(this.mInstanceIndex, this.mConfig);
419         }
420     }
421 }
422