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