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.ENABLE_ALL;
21 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.GROUP_OVERRIDES;
22 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogConfig.TRACING_MODE;
23 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogGroup.COLLECT_STACKTRACE;
24 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogGroup.GROUP_NAME;
25 import static android.internal.perfetto.protos.ProtologConfig.ProtoLogGroup.LOG_FROM;
26 
27 import android.internal.perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig;
28 import android.internal.perfetto.protos.ProtologCommon;
29 import android.tracing.perfetto.CreateIncrementalStateArgs;
30 import android.tracing.perfetto.CreateTlsStateArgs;
31 import android.tracing.perfetto.DataSource;
32 import android.tracing.perfetto.DataSourceInstance;
33 import android.tracing.perfetto.FlushCallbackArguments;
34 import android.tracing.perfetto.StartCallbackArguments;
35 import android.tracing.perfetto.StopCallbackArguments;
36 import android.util.proto.ProtoInputStream;
37 import android.util.proto.WireTypeMismatchException;
38 
39 import com.android.internal.protolog.common.LogLevel;
40 
41 import java.io.IOException;
42 import java.util.HashMap;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.function.Consumer;
46 
47 public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
48         ProtoLogDataSource.TlsState,
49         ProtoLogDataSource.IncrementalState> {
50 
51     private final Consumer<ProtoLogConfig> mOnStart;
52     private final Runnable mOnFlush;
53     private final Consumer<ProtoLogConfig> mOnStop;
54 
ProtoLogDataSource(Consumer<ProtoLogConfig> onStart, Runnable onFlush, Consumer<ProtoLogConfig> onStop)55     public ProtoLogDataSource(Consumer<ProtoLogConfig> onStart, Runnable onFlush,
56             Consumer<ProtoLogConfig> onStop) {
57         super("android.protolog");
58         this.mOnStart = onStart;
59         this.mOnFlush = onFlush;
60         this.mOnStop = onStop;
61     }
62 
63     @Override
createInstance(ProtoInputStream configStream, int instanceIndex)64     public Instance createInstance(ProtoInputStream configStream, int instanceIndex) {
65         ProtoLogConfig config = null;
66 
67         try {
68             while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
69                 try {
70                     if (configStream.getFieldNumber() == (int) DataSourceConfig.PROTOLOG_CONFIG) {
71                         if (config != null) {
72                             throw new RuntimeException("ProtoLog config already set in loop");
73                         }
74                         config = readProtoLogConfig(configStream);
75                     }
76                 } catch (WireTypeMismatchException e) {
77                     throw new RuntimeException("Failed to parse ProtoLog DataSource config", e);
78                 }
79             }
80         } catch (IOException e) {
81             throw new RuntimeException("Failed to read ProtoLog DataSource config", e);
82         }
83 
84         if (config == null) {
85             // No config found
86             config = ProtoLogConfig.DEFAULT;
87         }
88 
89         return new Instance(
90                 this, instanceIndex, config, mOnStart, mOnFlush, mOnStop);
91     }
92 
93     @Override
createTlsState(CreateTlsStateArgs<Instance> args)94     public TlsState createTlsState(CreateTlsStateArgs<Instance> args) {
95         try (Instance dsInstance = args.getDataSourceInstanceLocked()) {
96             if (dsInstance == null) {
97                 // Datasource instance has been removed
98                 return new TlsState(ProtoLogConfig.DEFAULT);
99             }
100             return new TlsState(dsInstance.mConfig);
101         }
102     }
103 
104     @Override
createIncrementalState(CreateIncrementalStateArgs<Instance> args)105     public IncrementalState createIncrementalState(CreateIncrementalStateArgs<Instance> args) {
106         return new IncrementalState();
107     }
108 
109     public static class TlsState {
110         private final ProtoLogConfig mConfig;
111 
TlsState(ProtoLogConfig config)112         private TlsState(ProtoLogConfig config) {
113             this.mConfig = config;
114         }
115 
116         /**
117          * Get the log from level for a group.
118          * @param groupTag The tag of the group to get the log from level.
119          * @return The lowest LogLevel (inclusive) to log message from.
120          */
getLogFromLevel(String groupTag)121         public LogLevel getLogFromLevel(String groupTag) {
122             return getConfigFor(groupTag).logFrom;
123         }
124 
125         /**
126          * Get if the stacktrace for the log message should be collected for this group.
127          * @param groupTag The tag of the group to get whether or not a stacktrace was requested.
128          * @return True iff a stacktrace was requested to be collected from this group in the
129          *         tracing config.
130          */
getShouldCollectStacktrace(String groupTag)131         public boolean getShouldCollectStacktrace(String groupTag) {
132             return getConfigFor(groupTag).collectStackTrace;
133         }
134 
getConfigFor(String groupTag)135         private GroupConfig getConfigFor(String groupTag) {
136             return mConfig.getConfigFor(groupTag);
137         }
138     }
139 
140     public static class IncrementalState {
141         public final Map<String, Integer> argumentInterningMap = new HashMap<>();
142         public final Map<String, Integer> stacktraceInterningMap = new HashMap<>();
143         public boolean clearReported = false;
144     }
145 
146     public static class ProtoLogConfig {
147         private final LogLevel mDefaultLogFromLevel;
148         private final Map<String, GroupConfig> mGroupConfigs;
149 
150         private static final ProtoLogConfig DEFAULT =
151                 new ProtoLogConfig(LogLevel.WTF, new HashMap<>());
152 
ProtoLogConfig( LogLevel defaultLogFromLevel, Map<String, GroupConfig> groupConfigs)153         private ProtoLogConfig(
154                 LogLevel defaultLogFromLevel, Map<String, GroupConfig> groupConfigs) {
155             this.mDefaultLogFromLevel = defaultLogFromLevel;
156             this.mGroupConfigs = groupConfigs;
157         }
158 
getConfigFor(String groupTag)159         public GroupConfig getConfigFor(String groupTag) {
160             return mGroupConfigs.getOrDefault(groupTag, getDefaultGroupConfig());
161         }
162 
getDefaultGroupConfig()163         public GroupConfig getDefaultGroupConfig() {
164             return new GroupConfig(mDefaultLogFromLevel, false);
165         }
166 
getGroupTagsWithOverriddenConfigs()167         public Set<String> getGroupTagsWithOverriddenConfigs() {
168             return mGroupConfigs.keySet();
169         }
170     }
171 
172     public static class GroupConfig {
173         public final LogLevel logFrom;
174         public final boolean collectStackTrace;
175 
GroupConfig(LogLevel logFromLevel, boolean collectStackTrace)176         public GroupConfig(LogLevel logFromLevel, boolean collectStackTrace) {
177             this.logFrom = logFromLevel;
178             this.collectStackTrace = collectStackTrace;
179         }
180     }
181 
readProtoLogConfig(ProtoInputStream configStream)182     private ProtoLogConfig readProtoLogConfig(ProtoInputStream configStream)
183             throws IOException {
184         final long config_token = configStream.start(DataSourceConfig.PROTOLOG_CONFIG);
185 
186         LogLevel defaultLogFromLevel = LogLevel.WTF;
187         final Map<String, GroupConfig> groupConfigs = new HashMap<>();
188 
189         while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
190             if (configStream.getFieldNumber() == (int) TRACING_MODE) {
191                 int tracingMode = configStream.readInt(TRACING_MODE);
192                 switch (tracingMode) {
193                     case DEFAULT:
194                         break;
195                     case ENABLE_ALL:
196                         defaultLogFromLevel = LogLevel.DEBUG;
197                         break;
198                     default:
199                         throw new RuntimeException("Unhandled ProtoLog tracing mode type");
200                 }
201             }
202             if (configStream.getFieldNumber() == (int) GROUP_OVERRIDES) {
203                 final long group_overrides_token  = configStream.start(GROUP_OVERRIDES);
204 
205                 String tag = null;
206                 LogLevel logFromLevel = defaultLogFromLevel;
207                 boolean collectStackTrace = false;
208                 while (configStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
209                     if (configStream.getFieldNumber() == (int) GROUP_NAME) {
210                         tag = configStream.readString(GROUP_NAME);
211                     }
212                     if (configStream.getFieldNumber() == (int) LOG_FROM) {
213                         final int logFromInt = configStream.readInt(LOG_FROM);
214                         switch (logFromInt) {
215                             case (ProtologCommon.PROTOLOG_LEVEL_DEBUG): {
216                                 logFromLevel = LogLevel.DEBUG;
217                                 break;
218                             }
219                             case (ProtologCommon.PROTOLOG_LEVEL_VERBOSE): {
220                                 logFromLevel = LogLevel.VERBOSE;
221                                 break;
222                             }
223                             case (ProtologCommon.PROTOLOG_LEVEL_INFO): {
224                                 logFromLevel = LogLevel.INFO;
225                                 break;
226                             }
227                             case (ProtologCommon.PROTOLOG_LEVEL_WARN): {
228                                 logFromLevel = LogLevel.WARN;
229                                 break;
230                             }
231                             case (ProtologCommon.PROTOLOG_LEVEL_ERROR): {
232                                 logFromLevel = LogLevel.ERROR;
233                                 break;
234                             }
235                             case (ProtologCommon.PROTOLOG_LEVEL_WTF): {
236                                 logFromLevel = LogLevel.WTF;
237                                 break;
238                             }
239                             default: {
240                                 throw new RuntimeException("Unhandled log level");
241                             }
242                         }
243                     }
244                     if (configStream.getFieldNumber() == (int) COLLECT_STACKTRACE) {
245                         collectStackTrace = configStream.readBoolean(COLLECT_STACKTRACE);
246                     }
247                 }
248 
249                 if (tag == null) {
250                     throw new RuntimeException("Failed to decode proto config. "
251                             + "Got a group override without a group tag.");
252                 }
253 
254                 groupConfigs.put(tag, new GroupConfig(logFromLevel, collectStackTrace));
255 
256                 configStream.end(group_overrides_token);
257             }
258         }
259 
260         configStream.end(config_token);
261 
262         return new ProtoLogConfig(defaultLogFromLevel, groupConfigs);
263     }
264 
265     public static class Instance extends DataSourceInstance {
266 
267         private final Consumer<ProtoLogConfig> mOnStart;
268         private final Runnable mOnFlush;
269         private final Consumer<ProtoLogConfig> mOnStop;
270         private final ProtoLogConfig mConfig;
271 
Instance( DataSource<Instance, TlsState, IncrementalState> dataSource, int instanceIdx, ProtoLogConfig config, Consumer<ProtoLogConfig> onStart, Runnable onFlush, Consumer<ProtoLogConfig> onStop )272         public Instance(
273                 DataSource<Instance, TlsState, IncrementalState> dataSource,
274                 int instanceIdx,
275                 ProtoLogConfig config,
276                 Consumer<ProtoLogConfig> onStart,
277                 Runnable onFlush,
278                 Consumer<ProtoLogConfig> onStop
279         ) {
280             super(dataSource, instanceIdx);
281             this.mOnStart = onStart;
282             this.mOnFlush = onFlush;
283             this.mOnStop = onStop;
284             this.mConfig = config;
285         }
286 
287         @Override
onStart(StartCallbackArguments args)288         public void onStart(StartCallbackArguments args) {
289             this.mOnStart.accept(this.mConfig);
290         }
291 
292         @Override
onFlush(FlushCallbackArguments args)293         public void onFlush(FlushCallbackArguments args) {
294             this.mOnFlush.run();
295         }
296 
297         @Override
onStop(StopCallbackArguments args)298         public void onStop(StopCallbackArguments args) {
299             this.mOnStop.accept(this.mConfig);
300         }
301     }
302 }
303