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