1 /* 2 * Copyright (C) 2021 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.car.telemetry.publisher; 18 19 import static com.android.car.telemetry.AtomsProto.Atom.ACTIVITY_FOREGROUND_STATE_CHANGED_FIELD_NUMBER; 20 import static com.android.car.telemetry.AtomsProto.Atom.ANR_OCCURRED_FIELD_NUMBER; 21 import static com.android.car.telemetry.AtomsProto.Atom.APP_CRASH_OCCURRED_FIELD_NUMBER; 22 import static com.android.car.telemetry.AtomsProto.Atom.APP_START_MEMORY_STATE_CAPTURED_FIELD_NUMBER; 23 import static com.android.car.telemetry.AtomsProto.Atom.PROCESS_CPU_TIME_FIELD_NUMBER; 24 import static com.android.car.telemetry.AtomsProto.Atom.PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER; 25 import static com.android.car.telemetry.AtomsProto.Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER; 26 import static com.android.car.telemetry.AtomsProto.Atom.PROCESS_START_TIME_FIELD_NUMBER; 27 import static com.android.car.telemetry.AtomsProto.Atom.WTF_OCCURRED_FIELD_NUMBER; 28 import static com.android.car.telemetry.CarTelemetryService.DEBUG; 29 30 import static java.nio.charset.StandardCharsets.UTF_16; 31 32 import android.annotation.NonNull; 33 import android.app.StatsManager.StatsUnavailableException; 34 import android.car.builtin.os.TraceHelper; 35 import android.car.builtin.util.Slogf; 36 import android.car.builtin.util.TimingsTraceLog; 37 import android.car.telemetry.TelemetryProto; 38 import android.car.telemetry.TelemetryProto.Publisher.PublisherCase; 39 import android.os.Handler; 40 import android.os.PersistableBundle; 41 import android.os.Process; 42 import android.util.LongSparseArray; 43 44 import com.android.car.CarLog; 45 import com.android.car.telemetry.AtomsProto.ProcessCpuTime; 46 import com.android.car.telemetry.AtomsProto.ProcessMemorySnapshot; 47 import com.android.car.telemetry.AtomsProto.ProcessMemoryState; 48 import com.android.car.telemetry.ResultStore; 49 import com.android.car.telemetry.StatsLogProto; 50 import com.android.car.telemetry.StatsdConfigProto; 51 import com.android.car.telemetry.StatsdConfigProto.StatsdConfig; 52 import com.android.car.telemetry.databroker.DataSubscriber; 53 import com.android.car.telemetry.publisher.statsconverters.ConfigMetricsReportListConverter; 54 import com.android.car.telemetry.publisher.statsconverters.StatsConversionException; 55 import com.android.car.telemetry.sessioncontroller.SessionAnnotation; 56 import com.android.internal.annotations.VisibleForTesting; 57 import com.android.internal.util.Preconditions; 58 59 import com.google.protobuf.InvalidProtocolBufferException; 60 61 import java.time.Duration; 62 import java.util.ArrayList; 63 import java.util.HashSet; 64 import java.util.List; 65 import java.util.Map; 66 67 /** 68 * Publisher for {@link TelemetryProto.StatsPublisher}. 69 * 70 * <p>The publisher adds subscriber configurations in StatsD and they persist between reboots and 71 * CarTelemetryService restarts. Please use {@link #removeAllDataSubscribers} to clean-up these 72 * configs from StatsD store. 73 */ 74 public class StatsPublisher extends AbstractPublisher { 75 // These IDs are used in StatsdConfig and ConfigMetricsReport. 76 @VisibleForTesting 77 static final long APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID = 1; 78 @VisibleForTesting 79 static final long APP_START_MEMORY_STATE_CAPTURED_EVENT_METRIC_ID = 2; 80 @VisibleForTesting 81 static final long PROCESS_MEMORY_STATE_MATCHER_ID = 3; 82 @VisibleForTesting 83 static final long PROCESS_MEMORY_STATE_GAUGE_METRIC_ID = 4; 84 @VisibleForTesting 85 static final long ACTIVITY_FOREGROUND_STATE_CHANGED_ATOM_MATCHER_ID = 5; 86 @VisibleForTesting 87 static final long ACTIVITY_FOREGROUND_STATE_CHANGED_EVENT_METRIC_ID = 6; 88 @VisibleForTesting 89 static final long PROCESS_CPU_TIME_MATCHER_ID = 7; 90 @VisibleForTesting 91 static final long PROCESS_CPU_TIME_GAUGE_METRIC_ID = 8; 92 @VisibleForTesting 93 static final long APP_CRASH_OCCURRED_ATOM_MATCHER_ID = 9; 94 @VisibleForTesting 95 static final long APP_CRASH_OCCURRED_EVENT_METRIC_ID = 10; 96 @VisibleForTesting 97 static final long ANR_OCCURRED_ATOM_MATCHER_ID = 11; 98 @VisibleForTesting 99 static final long ANR_OCCURRED_EVENT_METRIC_ID = 12; 100 @VisibleForTesting 101 static final long WTF_OCCURRED_ATOM_MATCHER_ID = 13; 102 @VisibleForTesting 103 static final long WTF_OCCURRED_EVENT_METRIC_ID = 14; 104 @VisibleForTesting 105 static final long PROCESS_MEMORY_SNAPSHOT_ATOM_MATCHER_ID = 15; 106 @VisibleForTesting 107 static final long PROCESS_MEMORY_SNAPSHOT_GAUGE_METRIC_ID = 16; 108 @VisibleForTesting 109 static final long PROCESS_START_TIME_ATOM_MATCHER_ID = 17; 110 @VisibleForTesting 111 static final long PROCESS_START_TIME_EVENT_METRIC_ID = 18; 112 113 // TODO(b/202115033): Flatten the load spike by pulling reports for each MetricsConfigs 114 // using separate periodical timers. 115 private static final Duration PULL_REPORTS_PERIOD = Duration.ofMinutes(10); 116 117 private static final String BUNDLE_CONFIG_KEY_PREFIX = "statsd-publisher-config-id-"; 118 private static final String BUNDLE_CONFIG_VERSION_PREFIX = "statsd-publisher-config-version-"; 119 120 @VisibleForTesting 121 static final StatsdConfigProto.FieldMatcher PROCESS_MEMORY_STATE_FIELDS_MATCHER = 122 StatsdConfigProto.FieldMatcher.newBuilder() 123 .setField( 124 PROCESS_MEMORY_STATE_FIELD_NUMBER) 125 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 126 .setField(ProcessMemoryState.OOM_ADJ_SCORE_FIELD_NUMBER)) 127 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 128 .setField(ProcessMemoryState.PAGE_FAULT_FIELD_NUMBER)) 129 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 130 .setField(ProcessMemoryState.PAGE_MAJOR_FAULT_FIELD_NUMBER)) 131 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 132 .setField(ProcessMemoryState.RSS_IN_BYTES_FIELD_NUMBER)) 133 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 134 .setField(ProcessMemoryState.CACHE_IN_BYTES_FIELD_NUMBER)) 135 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 136 .setField(ProcessMemoryState.SWAP_IN_BYTES_FIELD_NUMBER)) 137 .build(); 138 139 @VisibleForTesting 140 static final StatsdConfigProto.FieldMatcher PROCESS_CPU_TIME_FIELDS_MATCHER = 141 StatsdConfigProto.FieldMatcher.newBuilder() 142 .setField(PROCESS_CPU_TIME_FIELD_NUMBER) 143 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 144 .setField(ProcessCpuTime.USER_TIME_MILLIS_FIELD_NUMBER)) 145 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 146 .setField(ProcessCpuTime.SYSTEM_TIME_MILLIS_FIELD_NUMBER)) 147 .build(); 148 149 @VisibleForTesting 150 static final StatsdConfigProto.FieldMatcher PROCESS_MEMORY_SNAPSHOT_FIELDS_MATCHER = 151 StatsdConfigProto.FieldMatcher.newBuilder() 152 .setField( 153 PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER) 154 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 155 .setField(ProcessMemorySnapshot.PID_FIELD_NUMBER)) 156 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 157 .setField(ProcessMemorySnapshot.OOM_SCORE_ADJ_FIELD_NUMBER)) 158 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 159 .setField(ProcessMemorySnapshot.RSS_IN_KILOBYTES_FIELD_NUMBER)) 160 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 161 .setField(ProcessMemorySnapshot.ANON_RSS_IN_KILOBYTES_FIELD_NUMBER)) 162 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 163 .setField(ProcessMemorySnapshot.SWAP_IN_KILOBYTES_FIELD_NUMBER)) 164 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 165 .setField(ProcessMemorySnapshot 166 .ANON_RSS_AND_SWAP_IN_KILOBYTES_FIELD_NUMBER)) 167 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 168 .setField(ProcessMemorySnapshot.GPU_MEMORY_KB_FIELD_NUMBER)) 169 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 170 .setField(ProcessMemorySnapshot.HAS_FOREGROUND_SERVICES_FIELD_NUMBER)) 171 .build(); 172 173 private final StatsManagerProxy mStatsManager; 174 private final ResultStore mResultStore; 175 private final Handler mTelemetryHandler; 176 177 /** Assign the method to {@link Runnable}, otherwise the handler fails to remove it. */ 178 private final Runnable mPullReportsPeriodically = this::pullReportsPeriodically; 179 180 // LongSparseArray is memory optimized, but they can be bit slower for more 181 // than 100 items. We're expecting much less number of subscribers, so these data structures 182 // are ok. 183 // Maps config_key to the set of DataSubscriber. 184 private final LongSparseArray<DataSubscriber> mConfigKeyToSubscribers = new LongSparseArray<>(); 185 186 private PersistableBundle mSavedStatsConfigs; 187 188 // True if the publisher is periodically pulling reports from StatsD. 189 private boolean mIsPullingReports = false; 190 StatsPublisher( @onNull PublisherListener listener, @NonNull StatsManagerProxy statsManager, @NonNull ResultStore resultStore, @NonNull Handler telemetryHandler)191 StatsPublisher( 192 @NonNull PublisherListener listener, 193 @NonNull StatsManagerProxy statsManager, 194 @NonNull ResultStore resultStore, 195 @NonNull Handler telemetryHandler) { 196 super(listener); 197 mStatsManager = statsManager; 198 mResultStore = resultStore; 199 mTelemetryHandler = telemetryHandler; 200 // Loads the PersistableBundle containing stats config keys and versions from disk 201 mSavedStatsConfigs = mResultStore.getPublisherData( 202 StatsPublisher.class.getSimpleName(), false); 203 if (mSavedStatsConfigs == null) { 204 mSavedStatsConfigs = new PersistableBundle(); 205 } 206 } 207 208 /** Writes the PersistableBundle containing stats config keys and versions to disk. */ savePublisherState()209 private void savePublisherState() { 210 if (mSavedStatsConfigs.size() == 0) { 211 mResultStore.removePublisherData(StatsPublisher.class.getSimpleName()); 212 return; 213 } 214 mResultStore.putPublisherData(StatsPublisher.class.getSimpleName(), mSavedStatsConfigs); 215 } 216 217 @Override addDataSubscriber(@onNull DataSubscriber subscriber)218 public void addDataSubscriber(@NonNull DataSubscriber subscriber) { 219 TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam(); 220 Preconditions.checkArgument( 221 publisherParam.getPublisherCase() == PublisherCase.STATS, 222 "Subscribers only with StatsPublisher are supported by this class."); 223 224 long configKey = buildConfigKey(subscriber); 225 mConfigKeyToSubscribers.put(configKey, subscriber); 226 addStatsConfig(configKey, subscriber); 227 228 if (!mIsPullingReports) { 229 if (DEBUG) { 230 Slogf.d(CarLog.TAG_TELEMETRY, "Triggering pull stats reports"); 231 } 232 mIsPullingReports = true; 233 mTelemetryHandler.post(mPullReportsPeriodically); 234 } 235 } 236 processReport( long configKey, @NonNull StatsLogProto.ConfigMetricsReportList report)237 private void processReport( 238 long configKey, @NonNull StatsLogProto.ConfigMetricsReportList report) { 239 Slogf.i(CarLog.TAG_TELEMETRY, "Received reports: " + report.getReportsCount()); 240 if (report.getReportsCount() == 0) { 241 return; 242 } 243 DataSubscriber subscriber = mConfigKeyToSubscribers.get(configKey); 244 if (subscriber == null) { 245 Slogf.w(CarLog.TAG_TELEMETRY, "No subscribers found for config " + configKey); 246 return; 247 } 248 TimingsTraceLog traceLog = new TimingsTraceLog( 249 CarLog.TAG_TELEMETRY, TraceHelper.TRACE_TAG_CAR_SERVICE); 250 Map<Long, PersistableBundle> metricBundles = null; 251 try { 252 traceLog.traceBegin("convert stats report"); 253 metricBundles = ConfigMetricsReportListConverter.convert(report); 254 traceLog.traceEnd(); 255 } catch (StatsConversionException ex) { 256 traceLog.traceEnd(); 257 Slogf.e(CarLog.TAG_TELEMETRY, "Stats conversion exception for config " + configKey, ex); 258 return; 259 } 260 Long metricId; 261 switch (subscriber.getPublisherParam().getStats().getSystemMetric()) { 262 case APP_START_MEMORY_STATE_CAPTURED: 263 metricId = APP_START_MEMORY_STATE_CAPTURED_EVENT_METRIC_ID; 264 break; 265 case PROCESS_MEMORY_STATE: 266 metricId = PROCESS_MEMORY_STATE_GAUGE_METRIC_ID; 267 break; 268 case ACTIVITY_FOREGROUND_STATE_CHANGED: 269 metricId = ACTIVITY_FOREGROUND_STATE_CHANGED_EVENT_METRIC_ID; 270 break; 271 case PROCESS_CPU_TIME: 272 metricId = PROCESS_CPU_TIME_GAUGE_METRIC_ID; 273 break; 274 case APP_CRASH_OCCURRED: 275 metricId = APP_CRASH_OCCURRED_EVENT_METRIC_ID; 276 break; 277 case ANR_OCCURRED: 278 metricId = ANR_OCCURRED_EVENT_METRIC_ID; 279 break; 280 case WTF_OCCURRED: 281 metricId = WTF_OCCURRED_EVENT_METRIC_ID; 282 break; 283 case PROCESS_MEMORY_SNAPSHOT: 284 metricId = PROCESS_MEMORY_SNAPSHOT_GAUGE_METRIC_ID; 285 break; 286 case PROCESS_START_TIME: 287 metricId = PROCESS_START_TIME_EVENT_METRIC_ID; 288 break; 289 default: 290 return; 291 } 292 if (!metricBundles.containsKey(metricId)) { 293 Slogf.w(CarLog.TAG_TELEMETRY, 294 "No reports for metric id " + metricId + " (" 295 + subscriber.getPublisherParam().getStats().getSystemMetric() 296 + ") for config " + configKey); 297 return; 298 } 299 PersistableBundle bundle = metricBundles.get(metricId); 300 subscriber.push(bundle, isBundleLargeData(bundle)); 301 } 302 303 @VisibleForTesting isBundleLargeData(@onNull PersistableBundle bundle)304 boolean isBundleLargeData(@NonNull PersistableBundle bundle) { 305 String[] keys = bundle.keySet().toArray(new String[0]); 306 int bytes = 0; 307 for (int i = 0; i < keys.length; ++i) { 308 Object array = bundle.get(keys[i]); 309 if (array instanceof boolean[]) { 310 boolean[] boolArray = (boolean[]) array; 311 bytes += boolArray.length; // Java boolean is 1 byte 312 } else if (array instanceof long[]) { 313 long[] longArray = (long[]) array; 314 bytes += longArray.length * Long.BYTES; 315 } else if (array instanceof int[]) { 316 int[] intArray = (int[]) array; 317 bytes += intArray.length * Integer.BYTES; 318 } else if (array instanceof double[]) { 319 double[] doubleArray = (double[]) array; 320 bytes += doubleArray.length * Double.BYTES; 321 } else if (array instanceof String[]) { 322 String[] stringArray = (String[]) array; 323 for (String str : stringArray) { 324 bytes += str.getBytes(UTF_16).length; 325 } 326 } 327 } 328 329 return bytes >= DataSubscriber.SCRIPT_INPUT_SIZE_THRESHOLD_BYTES; 330 } 331 processStatsMetadata(@onNull StatsLogProto.StatsdStatsReport statsReport)332 private void processStatsMetadata(@NonNull StatsLogProto.StatsdStatsReport statsReport) { 333 int myUid = Process.myUid(); 334 // configKey and StatsdConfig.id are the same, see this#addStatsConfig(). 335 HashSet<Long> activeConfigKeys = new HashSet<>(getActiveConfigKeys()); 336 HashSet<TelemetryProto.MetricsConfig> failedConfigs = new HashSet<>(); 337 for (int i = 0; i < statsReport.getConfigStatsCount(); i++) { 338 StatsLogProto.StatsdStatsReport.ConfigStats stats = statsReport.getConfigStats(i); 339 if (stats.getUid() != myUid || !activeConfigKeys.contains(stats.getId())) { 340 continue; 341 } 342 if (!stats.getIsValid()) { 343 Slogf.w(CarLog.TAG_TELEMETRY, "Config key " + stats.getId() + " is invalid."); 344 failedConfigs.add(mConfigKeyToSubscribers.get(stats.getId()).getMetricsConfig()); 345 } 346 } 347 if (!failedConfigs.isEmpty()) { 348 // Notify DataBroker so it can disable invalid MetricsConfigs. 349 onPublisherFailure( 350 new ArrayList<>(failedConfigs), 351 new IllegalStateException("Found invalid configs")); 352 } 353 } 354 pullReportsPeriodically()355 private void pullReportsPeriodically() { 356 if (!mIsPullingReports) { 357 return; 358 } 359 360 TimingsTraceLog traceLog = new TimingsTraceLog( 361 CarLog.TAG_TELEMETRY, TraceHelper.TRACE_TAG_CAR_SERVICE); 362 try { 363 traceLog.traceBegin("pull stats report"); 364 // TODO(b/202131100): Get the active list of configs using 365 // StatsManager#setActiveConfigsChangedOperation() 366 processStatsMetadata( 367 StatsLogProto.StatsdStatsReport.parseFrom(mStatsManager.getStatsMetadata())); 368 369 for (long configKey : getActiveConfigKeys()) { 370 processReport(configKey, StatsLogProto.ConfigMetricsReportList.parseFrom( 371 mStatsManager.getReports(configKey))); 372 } 373 traceLog.traceEnd(); 374 } catch (InvalidProtocolBufferException | StatsUnavailableException e) { 375 traceLog.traceEnd(); 376 // If the StatsD is not available, retry in the next pullReportsPeriodically call. 377 Slogf.w(CarLog.TAG_TELEMETRY, e); 378 } 379 380 if (DEBUG) { 381 Slogf.d(CarLog.TAG_TELEMETRY, "Stats report will be pulled in " 382 + PULL_REPORTS_PERIOD.toMinutes() + " minutes."); 383 } 384 mTelemetryHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis()); 385 } 386 387 @NonNull getActiveConfigKeys()388 private List<Long> getActiveConfigKeys() { 389 ArrayList<Long> result = new ArrayList<>(); 390 for (String key : mSavedStatsConfigs.keySet()) { 391 // filter out all the config versions 392 if (!key.startsWith(BUNDLE_CONFIG_KEY_PREFIX)) { 393 continue; 394 } 395 // the remaining values are config keys 396 result.add(mSavedStatsConfigs.getLong(key)); 397 } 398 return result; 399 } 400 401 /** 402 * Removes the subscriber from the publisher and removes StatsdConfig from StatsD service. 403 * If StatsdConfig is present in Statsd, it removes it even if the subscriber is not present 404 * in the publisher (it happens when subscriber was added before and CarTelemetryService was 405 * restarted and lost publisher state). 406 */ 407 @Override removeDataSubscriber(@onNull DataSubscriber subscriber)408 public void removeDataSubscriber(@NonNull DataSubscriber subscriber) { 409 TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam(); 410 if (publisherParam.getPublisherCase() != PublisherCase.STATS) { 411 Slogf.w(CarLog.TAG_TELEMETRY, 412 "Expected STATS publisher, but received " 413 + publisherParam.getPublisherCase().name()); 414 return; 415 } 416 long configKey = removeStatsConfig(subscriber); 417 mConfigKeyToSubscribers.remove(configKey); 418 if (mConfigKeyToSubscribers.size() == 0) { 419 mIsPullingReports = false; 420 mTelemetryHandler.removeCallbacks(mPullReportsPeriodically); 421 } 422 } 423 424 /** 425 * Removes all the subscribers from the publisher removes StatsdConfigs from StatsD service. 426 */ 427 @Override removeAllDataSubscribers()428 public void removeAllDataSubscribers() { 429 for (String key : mSavedStatsConfigs.keySet()) { 430 // filter out all the config versions 431 if (key == null || !key.startsWith(BUNDLE_CONFIG_KEY_PREFIX)) { 432 continue; 433 } 434 // the remaining values are config keys 435 long configKey = mSavedStatsConfigs.getLong(key); 436 try { 437 mStatsManager.removeConfig(configKey); 438 String bundleVersion = buildBundleConfigVersionKey(configKey); 439 mSavedStatsConfigs.remove(key); 440 mSavedStatsConfigs.remove(bundleVersion); 441 } catch (StatsUnavailableException e) { 442 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to remove config " + configKey 443 + ". Ignoring the failure. Will retry removing again when" 444 + " removeAllDataSubscribers() is called.", e); 445 // If it cannot remove statsd config, it's less likely it can delete it even if 446 // retry. So we will just ignore the failures. The next call of this method 447 // will ry deleting StatsD configs again. 448 } 449 } 450 mSavedStatsConfigs.clear(); 451 savePublisherState(); 452 mIsPullingReports = false; 453 mTelemetryHandler.removeCallbacks(mPullReportsPeriodically); 454 } 455 456 /** 457 * Returns true if the publisher has the subscriber. 458 */ 459 @Override hasDataSubscriber(@onNull DataSubscriber subscriber)460 public boolean hasDataSubscriber(@NonNull DataSubscriber subscriber) { 461 TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam(); 462 if (publisherParam.getPublisherCase() != PublisherCase.STATS) { 463 return false; 464 } 465 long configKey = buildConfigKey(subscriber); 466 return mConfigKeyToSubscribers.indexOfKey(configKey) >= 0; 467 } 468 469 /** Returns all the {@link TelemetryProto.MetricsConfig} associated with added subscribers. */ 470 @NonNull getMetricsConfigs()471 private List<TelemetryProto.MetricsConfig> getMetricsConfigs() { 472 HashSet<TelemetryProto.MetricsConfig> uniqueConfigs = new HashSet<>(); 473 for (int i = 0; i < mConfigKeyToSubscribers.size(); i++) { 474 uniqueConfigs.add(mConfigKeyToSubscribers.valueAt(i).getMetricsConfig()); 475 } 476 return new ArrayList<>(uniqueConfigs); 477 } 478 479 /** 480 * Returns the key for PersistableBundle to store/retrieve configKey associated with the 481 * subscriber. 482 */ buildBundleConfigKey(@onNull DataSubscriber subscriber)483 private static String buildBundleConfigKey(@NonNull DataSubscriber subscriber) { 484 return BUNDLE_CONFIG_KEY_PREFIX + subscriber.getMetricsConfig().getName() + "-" 485 + subscriber.getSubscriber().getHandler(); 486 } 487 488 /** 489 * Returns the key for PersistableBundle to store/retrieve {@link TelemetryProto.MetricsConfig} 490 * version associated with the configKey (which is generated per DataSubscriber). 491 */ buildBundleConfigVersionKey(long configKey)492 private static String buildBundleConfigVersionKey(long configKey) { 493 return BUNDLE_CONFIG_VERSION_PREFIX + configKey; 494 } 495 496 /** 497 * This method can be called even if StatsdConfig was added to StatsD service before. It stores 498 * previously added config_keys in the persistable bundle and only updates StatsD when 499 * the MetricsConfig (of CarTelemetryService) has a new version. 500 */ addStatsConfig(long configKey, @NonNull DataSubscriber subscriber)501 private void addStatsConfig(long configKey, @NonNull DataSubscriber subscriber) { 502 // Store MetricsConfig (of CarTelemetryService) version per handler_function. 503 String bundleVersion = buildBundleConfigVersionKey(configKey); 504 if (mSavedStatsConfigs.getInt(bundleVersion) != 0) { 505 int currentVersion = mSavedStatsConfigs.getInt(bundleVersion); 506 if (currentVersion >= subscriber.getMetricsConfig().getVersion()) { 507 // It's trying to add current or older MetricsConfig version, just ignore it. 508 return; 509 } // if the subscriber's MetricsConfig version is newer, it will replace the old one. 510 } 511 String bundleConfigKey = buildBundleConfigKey(subscriber); 512 StatsdConfig config = buildStatsdConfig(subscriber, configKey); 513 try { 514 // It doesn't throw exception if the StatsdConfig is invalid. But it shouldn't happen, 515 // as we generate well-tested StatsdConfig in this service. 516 mStatsManager.addConfig(configKey, config.toByteArray()); 517 mSavedStatsConfigs.putInt(bundleVersion, subscriber.getMetricsConfig().getVersion()); 518 mSavedStatsConfigs.putLong(bundleConfigKey, configKey); 519 savePublisherState(); 520 } catch (StatsUnavailableException e) { 521 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to add config" + configKey, e); 522 // We will notify the failure immediately, as we're expecting StatsManager to be stable. 523 onPublisherFailure( 524 getMetricsConfigs(), 525 new IllegalStateException("Failed to add config " + configKey, e)); 526 } 527 } 528 529 /** Removes StatsdConfig and returns configKey. */ removeStatsConfig(@onNull DataSubscriber subscriber)530 private long removeStatsConfig(@NonNull DataSubscriber subscriber) { 531 String bundleConfigKey = buildBundleConfigKey(subscriber); 532 long configKey = buildConfigKey(subscriber); 533 // Store MetricsConfig (of CarTelemetryService) version per handler_function. 534 String bundleVersion = buildBundleConfigVersionKey(configKey); 535 try { 536 mStatsManager.removeConfig(configKey); 537 mSavedStatsConfigs.remove(bundleVersion); 538 mSavedStatsConfigs.remove(bundleConfigKey); 539 savePublisherState(); 540 } catch (StatsUnavailableException e) { 541 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to remove config " + configKey 542 + ". Ignoring the failure. Will retry removing again when" 543 + " removeAllDataSubscribers() is called.", e); 544 // If it cannot remove statsd config, it's less likely it can delete it even if we 545 // retry. So we will just ignore the failures. The next call of this method will 546 // try deleting StatsD configs again. 547 } 548 return configKey; 549 } 550 551 /** 552 * Builds StatsdConfig id (aka config_key) using subscriber handler name. 553 * 554 * <p>StatsD uses ConfigKey struct to uniquely identify StatsdConfigs. StatsD ConfigKey consists 555 * of two parts: client uid and config_key number. The StatsdConfig is added to StatsD from 556 * CarService - which has uid=1000. Currently there is no client under uid=1000 and there will 557 * not be config_key collision. 558 */ buildConfigKey(@onNull DataSubscriber subscriber)559 private static long buildConfigKey(@NonNull DataSubscriber subscriber) { 560 // Not to be confused with statsd metric, this one is a global CarTelemetry metric name. 561 String metricConfigName = subscriber.getMetricsConfig().getName(); 562 String handlerFnName = subscriber.getSubscriber().getHandler(); 563 return HashUtils.sha256(metricConfigName + "-" + handlerFnName); 564 } 565 566 /** Builds {@link StatsdConfig} proto for given subscriber. */ 567 @VisibleForTesting 568 @NonNull buildStatsdConfig(@onNull DataSubscriber subscriber, long configId)569 static StatsdConfig buildStatsdConfig(@NonNull DataSubscriber subscriber, long configId) { 570 TelemetryProto.StatsPublisher.SystemMetric metric = 571 subscriber.getPublisherParam().getStats().getSystemMetric(); 572 StatsdConfig.Builder builder = StatsdConfig.newBuilder() 573 // This id is not used in StatsD, but let's make it the same as config_key 574 // just in case. 575 .setId(configId) 576 .addAllowedLogSource("AID_SYSTEM"); 577 578 switch (metric) { 579 case APP_START_MEMORY_STATE_CAPTURED: 580 return buildAppStartMemoryStateStatsdConfig(builder); 581 case PROCESS_MEMORY_STATE: 582 return buildProcessMemoryStateStatsdConfig(builder); 583 case ACTIVITY_FOREGROUND_STATE_CHANGED: 584 return buildActivityForegroundStateStatsdConfig(builder); 585 case PROCESS_CPU_TIME: 586 return buildProcessCpuTimeStatsdConfig(builder); 587 case APP_CRASH_OCCURRED: 588 return buildAppCrashOccurredStatsdConfig(builder); 589 case ANR_OCCURRED: 590 return buildAnrOccurredStatsdConfig(builder); 591 case WTF_OCCURRED: 592 return buildWtfOccurredStatsdConfig(builder); 593 case PROCESS_MEMORY_SNAPSHOT: 594 return buildProcessMemorySnapshotStatsdConfig(builder); 595 case PROCESS_START_TIME: 596 return buildProcessStartTimeStatsdConfig(builder); 597 default: 598 throw new IllegalArgumentException("Unsupported metric " + metric.name()); 599 } 600 } 601 602 @NonNull buildAppStartMemoryStateStatsdConfig( @onNull StatsdConfig.Builder builder)603 private static StatsdConfig buildAppStartMemoryStateStatsdConfig( 604 @NonNull StatsdConfig.Builder builder) { 605 return builder 606 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder() 607 // The id must be unique within StatsdConfig/matchers 608 .setId(APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID) 609 .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder() 610 .setAtomId(APP_START_MEMORY_STATE_CAPTURED_FIELD_NUMBER))) 611 .addEventMetric(StatsdConfigProto.EventMetric.newBuilder() 612 // The id must be unique within StatsdConfig/metrics 613 .setId(APP_START_MEMORY_STATE_CAPTURED_EVENT_METRIC_ID) 614 .setWhat(APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID)) 615 .build(); 616 } 617 618 @NonNull buildProcessMemoryStateStatsdConfig( @onNull StatsdConfig.Builder builder)619 private static StatsdConfig buildProcessMemoryStateStatsdConfig( 620 @NonNull StatsdConfig.Builder builder) { 621 return builder 622 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder() 623 // The id must be unique within StatsdConfig/matchers 624 .setId(PROCESS_MEMORY_STATE_MATCHER_ID) 625 .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder() 626 .setAtomId(PROCESS_MEMORY_STATE_FIELD_NUMBER))) 627 .addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder() 628 // The id must be unique within StatsdConfig/metrics 629 .setId(PROCESS_MEMORY_STATE_GAUGE_METRIC_ID) 630 .setWhat(PROCESS_MEMORY_STATE_MATCHER_ID) 631 .setDimensionsInWhat(StatsdConfigProto.FieldMatcher.newBuilder() 632 .setField(PROCESS_MEMORY_STATE_FIELD_NUMBER) 633 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 634 .setField(ProcessMemoryState.UID_FIELD_NUMBER)) 635 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 636 .setField(ProcessMemoryState.PROCESS_NAME_FIELD_NUMBER)) 637 ) 638 .setGaugeFieldsFilter(StatsdConfigProto.FieldFilter.newBuilder() 639 .setFields(PROCESS_MEMORY_STATE_FIELDS_MATCHER) 640 ) // setGaugeFieldsFilter 641 .setSamplingType( 642 StatsdConfigProto.GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE) 643 .setBucket(StatsdConfigProto.TimeUnit.FIVE_MINUTES) 644 ) 645 .addPullAtomPackages(StatsdConfigProto.PullAtomPackages.newBuilder() 646 .setAtomId(PROCESS_MEMORY_STATE_FIELD_NUMBER) 647 .addPackages("AID_SYSTEM")) 648 .build(); 649 } 650 651 @NonNull buildActivityForegroundStateStatsdConfig( @onNull StatsdConfig.Builder builder)652 private static StatsdConfig buildActivityForegroundStateStatsdConfig( 653 @NonNull StatsdConfig.Builder builder) { 654 return builder 655 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder() 656 // The id must be unique within StatsdConfig/matchers 657 .setId(ACTIVITY_FOREGROUND_STATE_CHANGED_ATOM_MATCHER_ID) 658 .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder() 659 .setAtomId(ACTIVITY_FOREGROUND_STATE_CHANGED_FIELD_NUMBER))) 660 .addEventMetric(StatsdConfigProto.EventMetric.newBuilder() 661 // The id must be unique within StatsdConfig/metrics 662 .setId(ACTIVITY_FOREGROUND_STATE_CHANGED_EVENT_METRIC_ID) 663 .setWhat(ACTIVITY_FOREGROUND_STATE_CHANGED_ATOM_MATCHER_ID)) 664 .build(); 665 } 666 667 @NonNull buildProcessCpuTimeStatsdConfig( @onNull StatsdConfig.Builder builder)668 private static StatsdConfig buildProcessCpuTimeStatsdConfig( 669 @NonNull StatsdConfig.Builder builder) { 670 return builder 671 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder() 672 // The id must be unique within StatsdConfig/matchers 673 .setId(PROCESS_CPU_TIME_MATCHER_ID) 674 .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder() 675 .setAtomId(PROCESS_CPU_TIME_FIELD_NUMBER))) 676 .addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder() 677 // The id must be unique within StatsdConfig/metrics 678 .setId(PROCESS_CPU_TIME_GAUGE_METRIC_ID) 679 .setWhat(PROCESS_CPU_TIME_MATCHER_ID) 680 .setDimensionsInWhat(StatsdConfigProto.FieldMatcher.newBuilder() 681 .setField(PROCESS_CPU_TIME_FIELD_NUMBER) 682 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 683 .setField(ProcessCpuTime.UID_FIELD_NUMBER)) 684 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 685 .setField(ProcessCpuTime.PROCESS_NAME_FIELD_NUMBER)) 686 ) 687 .setGaugeFieldsFilter(StatsdConfigProto.FieldFilter.newBuilder() 688 .setFields(PROCESS_CPU_TIME_FIELDS_MATCHER) 689 ) // setGaugeFieldsFilter 690 .setSamplingType( 691 StatsdConfigProto.GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE) 692 .setBucket(StatsdConfigProto.TimeUnit.FIVE_MINUTES) 693 ) 694 .addPullAtomPackages(StatsdConfigProto.PullAtomPackages.newBuilder() 695 .setAtomId(PROCESS_CPU_TIME_FIELD_NUMBER) 696 .addPackages("AID_SYSTEM")) 697 .build(); 698 } 699 700 @NonNull buildAppCrashOccurredStatsdConfig( @onNull StatsdConfig.Builder builder)701 private static StatsdConfig buildAppCrashOccurredStatsdConfig( 702 @NonNull StatsdConfig.Builder builder) { 703 return builder 704 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder() 705 // The id must be unique within StatsdConfig/matchers 706 .setId(APP_CRASH_OCCURRED_ATOM_MATCHER_ID) 707 .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder() 708 .setAtomId(APP_CRASH_OCCURRED_FIELD_NUMBER))) 709 .addEventMetric(StatsdConfigProto.EventMetric.newBuilder() 710 // The id must be unique within StatsdConfig/metrics 711 .setId(APP_CRASH_OCCURRED_EVENT_METRIC_ID) 712 .setWhat(APP_CRASH_OCCURRED_ATOM_MATCHER_ID)) 713 .build(); 714 } 715 716 @NonNull buildAnrOccurredStatsdConfig( @onNull StatsdConfig.Builder builder)717 private static StatsdConfig buildAnrOccurredStatsdConfig( 718 @NonNull StatsdConfig.Builder builder) { 719 return builder 720 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder() 721 // The id must be unique within StatsdConfig/matchers 722 .setId(ANR_OCCURRED_ATOM_MATCHER_ID) 723 .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder() 724 .setAtomId(ANR_OCCURRED_FIELD_NUMBER))) 725 .addEventMetric(StatsdConfigProto.EventMetric.newBuilder() 726 // The id must be unique within StatsdConfig/metrics 727 .setId(ANR_OCCURRED_EVENT_METRIC_ID) 728 .setWhat(ANR_OCCURRED_ATOM_MATCHER_ID)) 729 .build(); 730 } 731 732 @NonNull buildWtfOccurredStatsdConfig( @onNull StatsdConfig.Builder builder)733 private static StatsdConfig buildWtfOccurredStatsdConfig( 734 @NonNull StatsdConfig.Builder builder) { 735 return builder 736 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder() 737 // The id must be unique within StatsdConfig/matchers 738 .setId(WTF_OCCURRED_ATOM_MATCHER_ID) 739 .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder() 740 .setAtomId(WTF_OCCURRED_FIELD_NUMBER))) 741 .addEventMetric(StatsdConfigProto.EventMetric.newBuilder() 742 // The id must be unique within StatsdConfig/metrics 743 .setId(WTF_OCCURRED_EVENT_METRIC_ID) 744 .setWhat(WTF_OCCURRED_ATOM_MATCHER_ID)) 745 .build(); 746 } 747 748 @NonNull buildProcessMemorySnapshotStatsdConfig( @onNull StatsdConfig.Builder builder)749 private static StatsdConfig buildProcessMemorySnapshotStatsdConfig( 750 @NonNull StatsdConfig.Builder builder) { 751 return builder 752 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder() 753 // The id must be unique within StatsdConfig/matchers 754 .setId(PROCESS_MEMORY_SNAPSHOT_ATOM_MATCHER_ID) 755 .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder() 756 .setAtomId(PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER))) 757 .addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder() 758 // The id must be unique within StatsdConfig/metrics 759 .setId(PROCESS_MEMORY_SNAPSHOT_GAUGE_METRIC_ID) 760 .setWhat(PROCESS_MEMORY_SNAPSHOT_ATOM_MATCHER_ID) 761 .setDimensionsInWhat(StatsdConfigProto.FieldMatcher.newBuilder() 762 .setField(PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER) 763 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 764 .setField(ProcessMemorySnapshot.UID_FIELD_NUMBER)) 765 .addChild(StatsdConfigProto.FieldMatcher.newBuilder() 766 .setField(ProcessMemorySnapshot.PROCESS_NAME_FIELD_NUMBER)) 767 ) 768 .setGaugeFieldsFilter(StatsdConfigProto.FieldFilter.newBuilder() 769 .setFields(PROCESS_MEMORY_SNAPSHOT_FIELDS_MATCHER) 770 ) // setGaugeFieldsFilter 771 .setSamplingType( 772 StatsdConfigProto.GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE) 773 .setBucket(StatsdConfigProto.TimeUnit.FIVE_MINUTES) 774 ) 775 .addPullAtomPackages(StatsdConfigProto.PullAtomPackages.newBuilder() 776 .setAtomId(PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER) 777 .addPackages("AID_SYSTEM")) 778 .build(); 779 } 780 781 @NonNull buildProcessStartTimeStatsdConfig( @onNull StatsdConfig.Builder builder)782 private static StatsdConfig buildProcessStartTimeStatsdConfig( 783 @NonNull StatsdConfig.Builder builder) { 784 return builder 785 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder() 786 // The id must be unique within StatsdConfig/matchers 787 .setId(PROCESS_START_TIME_ATOM_MATCHER_ID) 788 .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder() 789 .setAtomId(PROCESS_START_TIME_FIELD_NUMBER))) 790 .addEventMetric(StatsdConfigProto.EventMetric.newBuilder() 791 // The id must be unique within StatsdConfig/metrics 792 .setId(PROCESS_START_TIME_EVENT_METRIC_ID) 793 .setWhat(PROCESS_START_TIME_ATOM_MATCHER_ID)) 794 .build(); 795 } 796 797 @Override handleSessionStateChange(SessionAnnotation annotation)798 protected void handleSessionStateChange(SessionAnnotation annotation) {} 799 } 800