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 package com.android.car.telemetry; 17 18 import static android.car.telemetry.CarTelemetryManager.STATUS_ADD_METRICS_CONFIG_PARSE_FAILED; 19 import static android.car.telemetry.CarTelemetryManager.STATUS_ADD_METRICS_CONFIG_SUCCEEDED; 20 import static android.car.telemetry.CarTelemetryManager.STATUS_GET_METRICS_CONFIG_DOES_NOT_EXIST; 21 import static android.car.telemetry.CarTelemetryManager.STATUS_GET_METRICS_CONFIG_FINISHED; 22 import static android.car.telemetry.CarTelemetryManager.STATUS_GET_METRICS_CONFIG_INTERIM_RESULTS; 23 import static android.car.telemetry.CarTelemetryManager.STATUS_GET_METRICS_CONFIG_PENDING; 24 import static android.car.telemetry.CarTelemetryManager.STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR; 25 26 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 27 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.app.ActivityManager; 31 import android.car.Car; 32 import android.car.builtin.os.TraceHelper; 33 import android.car.builtin.util.Slogf; 34 import android.car.builtin.util.TimingsTraceLog; 35 import android.car.telemetry.CarTelemetryManager; 36 import android.car.telemetry.ICarTelemetryReportListener; 37 import android.car.telemetry.ICarTelemetryReportReadyListener; 38 import android.car.telemetry.ICarTelemetryService; 39 import android.car.telemetry.TelemetryProto; 40 import android.car.telemetry.TelemetryProto.TelemetryError; 41 import android.content.Context; 42 import android.os.Handler; 43 import android.os.HandlerThread; 44 import android.os.ParcelFileDescriptor; 45 import android.os.PersistableBundle; 46 import android.os.RemoteException; 47 import android.os.ResultReceiver; 48 import android.util.ArrayMap; 49 import android.util.Log; 50 import android.util.proto.ProtoOutputStream; 51 52 import com.android.car.CarLocalServices; 53 import com.android.car.CarLog; 54 import com.android.car.CarPropertyService; 55 import com.android.car.CarServiceBase; 56 import com.android.car.CarServiceUtils; 57 import com.android.car.OnShutdownReboot; 58 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 59 import com.android.car.internal.util.IndentingPrintWriter; 60 import com.android.car.power.CarPowerManagementService; 61 import com.android.car.systeminterface.SystemInterface; 62 import com.android.car.telemetry.MetricsReportProto.MetricsReportContainer; 63 import com.android.car.telemetry.MetricsReportProto.MetricsReportList; 64 import com.android.car.telemetry.databroker.DataBroker; 65 import com.android.car.telemetry.databroker.DataBrokerImpl; 66 import com.android.car.telemetry.databroker.ScriptExecutionTask; 67 import com.android.car.telemetry.publisher.PublisherFactory; 68 import com.android.car.telemetry.sessioncontroller.SessionController; 69 import com.android.car.telemetry.systemmonitor.SystemMonitor; 70 import com.android.car.telemetry.systemmonitor.SystemMonitorEvent; 71 import com.android.car.telemetry.util.IoUtils; 72 import com.android.car.telemetry.util.MetricsReportProtoUtils; 73 import com.android.internal.annotations.VisibleForTesting; 74 75 import com.google.protobuf.ByteString; 76 import com.google.protobuf.InvalidProtocolBufferException; 77 78 import java.io.DataOutputStream; 79 import java.io.File; 80 import java.io.IOException; 81 import java.util.ArrayList; 82 import java.util.Arrays; 83 import java.util.List; 84 import java.util.Set; 85 86 /** 87 * CarTelemetryService manages OEM telemetry collection, processing and communication 88 * with a data upload service. 89 */ 90 public class CarTelemetryService extends ICarTelemetryService.Stub implements CarServiceBase { 91 92 private static final String TAG = CarTelemetryService.class.getSimpleName(); 93 94 public static final boolean DEBUG = false; // STOPSHIP if true 95 96 public static final String TELEMETRY_DIR = "telemetry"; 97 98 /** 99 * Priorities range from 0 to 100, with 0 being the highest priority and 100 being the lowest. 100 * A {@link ScriptExecutionTask} must have equal or higher priority than the threshold in order 101 * to be executed. 102 * The following constants are chosen with the idea that subscribers with a priority of 0 103 * must be executed as soon as data is published regardless of system health conditions. 104 * Otherwise {@link ScriptExecutionTask}s are executed from the highest priority to the lowest 105 * subject to system health constraints from {@link SystemMonitor}. 106 */ 107 public static final int TASK_PRIORITY_HI = 0; 108 public static final int TASK_PRIORITY_MED = 50; 109 public static final int TASK_PRIORITY_LOW = 100; 110 111 private final Context mContext; 112 private final CarPowerManagementService mCarPowerManagementService; 113 private final CarPropertyService mCarPropertyService; 114 private final Dependencies mDependencies; 115 private final HandlerThread mTelemetryThread = CarServiceUtils.getHandlerThread( 116 CarTelemetryService.class.getSimpleName()); 117 private final Handler mTelemetryHandler = new Handler(mTelemetryThread.getLooper()); 118 private final UidPackageMapper mUidMapper; 119 120 private final DataBroker.DataBrokerListener mDataBrokerListener = 121 new DataBroker.DataBrokerListener() { 122 @Override 123 public void onEventConsumed( 124 @NonNull String metricsConfigName, @NonNull PersistableBundle state) { 125 mResultStore.putInterimResult(metricsConfigName, state); 126 mDataBroker.scheduleNextTask(); 127 } 128 @Override 129 public void onReportFinished(@NonNull String metricsConfigName) { 130 cleanupMetricsConfig(metricsConfigName); // schedules next script execution task 131 if (mResultStore.getErrorResult(metricsConfigName, false) != null 132 || mResultStore.getMetricsReports(metricsConfigName, false) != null) { 133 onReportReady(metricsConfigName); 134 } 135 } 136 137 @Override 138 public void onReportFinished( 139 @NonNull String metricsConfigName, @NonNull PersistableBundle report) { 140 cleanupMetricsConfig(metricsConfigName); // schedules next script execution task 141 mResultStore.putMetricsReport(metricsConfigName, report, /* finished = */ true); 142 onReportReady(metricsConfigName); 143 } 144 145 @Override 146 public void onReportFinished( 147 @NonNull String metricsConfigName, @NonNull TelemetryProto.TelemetryError error) { 148 cleanupMetricsConfig(metricsConfigName); // schedules next script execution task 149 mResultStore.putErrorResult(metricsConfigName, error); 150 onReportReady(metricsConfigName); 151 } 152 153 @Override 154 public void onMetricsReport( 155 @NonNull String metricsConfigName, 156 @NonNull PersistableBundle report, 157 @Nullable PersistableBundle state) { 158 mResultStore.putMetricsReport(metricsConfigName, report, /* finished = */ false); 159 if (state != null) { 160 mResultStore.putInterimResult(metricsConfigName, state); 161 } 162 onReportReady(metricsConfigName); 163 mDataBroker.scheduleNextTask(); 164 } 165 }; 166 167 // accessed and updated on the main thread 168 private boolean mReleased = false; 169 170 // all the following fields are accessed and updated on the telemetry thread 171 private DataBroker mDataBroker; 172 private ICarTelemetryReportReadyListener mReportReadyListener; 173 private MetricsConfigStore mMetricsConfigStore; 174 private OnShutdownReboot mOnShutdownReboot; 175 private PublisherFactory mPublisherFactory; 176 private ResultStore mResultStore; 177 private SessionController mSessionController; 178 private SystemMonitor mSystemMonitor; 179 private TimingsTraceLog mTelemetryThreadTraceLog; // can only be used on telemetry thread 180 181 static class Dependencies { 182 183 /** Returns a new PublisherFactory instance. */ getPublisherFactory( CarPropertyService carPropertyService, Handler handler, Context context, SessionController sessionController, ResultStore resultStore, UidPackageMapper uidMapper)184 public PublisherFactory getPublisherFactory( 185 CarPropertyService carPropertyService, 186 Handler handler, 187 Context context, 188 SessionController sessionController, ResultStore resultStore, 189 UidPackageMapper uidMapper) { 190 return new PublisherFactory( 191 carPropertyService, handler, context, sessionController, resultStore, 192 uidMapper); 193 } 194 195 /** Returns a new UidPackageMapper instance. */ getUidPackageMapper(Context context, Handler telemetryHandler)196 public UidPackageMapper getUidPackageMapper(Context context, Handler telemetryHandler) { 197 return new UidPackageMapper(context, telemetryHandler); 198 } 199 } 200 CarTelemetryService( Context context, CarPowerManagementService carPowerManagementService, CarPropertyService carPropertyService)201 public CarTelemetryService( 202 Context context, 203 CarPowerManagementService carPowerManagementService, 204 CarPropertyService carPropertyService) { 205 this(context, carPowerManagementService, carPropertyService, new Dependencies(), 206 /* dataBroker = */ null, /* sessionController = */ null); 207 } 208 209 @VisibleForTesting CarTelemetryService( Context context, CarPowerManagementService carPowerManagementService, CarPropertyService carPropertyService, Dependencies deps, DataBroker dataBroker, SessionController sessionController)210 CarTelemetryService( 211 Context context, 212 CarPowerManagementService carPowerManagementService, 213 CarPropertyService carPropertyService, 214 Dependencies deps, 215 DataBroker dataBroker, 216 SessionController sessionController) { 217 mContext = context; 218 mCarPowerManagementService = carPowerManagementService; 219 mCarPropertyService = carPropertyService; 220 mDependencies = deps; 221 mUidMapper = mDependencies.getUidPackageMapper(mContext, mTelemetryHandler); 222 mDataBroker = dataBroker; 223 mSessionController = sessionController; 224 } 225 226 @Override init()227 public void init() { 228 mTelemetryHandler.post(() -> { 229 mTelemetryThreadTraceLog = new TimingsTraceLog( 230 CarLog.TAG_TELEMETRY, TraceHelper.TRACE_TAG_CAR_SERVICE); 231 mTelemetryThreadTraceLog.traceBegin("init"); 232 SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class); 233 // starts metrics collection after CarService initializes. 234 CarServiceUtils.runOnMain(this::startMetricsCollection); 235 // full root directory path is /data/system/car/telemetry 236 File rootDirectory = new File(systemInterface.getSystemCarDir(), TELEMETRY_DIR); 237 // initialize all necessary components 238 mUidMapper.init(); 239 mMetricsConfigStore = new MetricsConfigStore(rootDirectory); 240 mResultStore = new ResultStore(mContext, rootDirectory); 241 if (mSessionController == null) { 242 mSessionController = new SessionController( 243 mContext, mCarPowerManagementService, mTelemetryHandler); 244 } 245 mPublisherFactory = mDependencies.getPublisherFactory(mCarPropertyService, 246 mTelemetryHandler, mContext, mSessionController, mResultStore, mUidMapper); 247 if (mDataBroker == null) { 248 mDataBroker = new DataBrokerImpl(mContext, mPublisherFactory, mResultStore, 249 mTelemetryThreadTraceLog); 250 } 251 mDataBroker.setDataBrokerListener(mDataBrokerListener); 252 // TODO (b/233973826): Re-enable once SystemMonitor tune-up is complete. 253 if (false) { 254 ActivityManager activityManager = mContext.getSystemService(ActivityManager.class); 255 mSystemMonitor = SystemMonitor.create(activityManager, mTelemetryHandler); 256 mSystemMonitor.setSystemMonitorCallback(this::onSystemMonitorEvent); 257 } else { 258 Slogf.w(TAG, "Not creating mSystemMonitor due to bug 233973826"); 259 } 260 mTelemetryThreadTraceLog.traceEnd(); 261 // save state at reboot and shutdown 262 mOnShutdownReboot = new OnShutdownReboot(mContext); 263 mOnShutdownReboot.init(); 264 mOnShutdownReboot.addAction((context, intent) -> release()); 265 }); 266 } 267 268 @Override release()269 public void release() { 270 if (mReleased) { 271 return; 272 } 273 mReleased = true; 274 mTelemetryHandler.post(() -> { 275 mTelemetryThreadTraceLog.traceBegin("release"); 276 mResultStore.flushToDisk(); 277 mOnShutdownReboot.release(); 278 mSessionController.release(); 279 mUidMapper.release(); 280 mTelemetryThreadTraceLog.traceEnd(); 281 }); 282 CarServiceUtils.runOnLooperSync(mTelemetryThread.getLooper(), () -> {}); 283 } 284 285 @Override 286 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)287 public void dump(IndentingPrintWriter writer) { 288 writer.println("*CarTelemetryService*"); 289 writer.println(); 290 // Print active configs with their interim results and errors. 291 writer.println("Active Configs"); 292 writer.println(); 293 for (TelemetryProto.MetricsConfig config : mMetricsConfigStore.getActiveMetricsConfigs()) { 294 writer.println(" Name: " + config.getName()); 295 writer.println(" Version: " + config.getVersion()); 296 PersistableBundle interimResult = mResultStore.getInterimResult(config.getName()); 297 if (interimResult != null) { 298 writer.println(" Interim Result"); 299 writer.println(" Bundle keys: " 300 + Arrays.toString(interimResult.keySet().toArray())); 301 } 302 writer.println(); 303 } 304 // Print info on stored final results. 305 ArrayMap<String, MetricsReportList> finalResults = mResultStore.getAllMetricsReports(); 306 writer.println("Final Results"); 307 writer.println(); 308 for (int i = 0; i < finalResults.size(); i++) { 309 writer.println("\tConfig name: " + finalResults.keyAt(i)); 310 MetricsReportList reportList = finalResults.valueAt(i); 311 writer.println("\tTotal number of metrics reports: " + reportList.getReportCount()); 312 for (int j = 0; j < reportList.getReportCount(); j++) { 313 writer.println("\tBundle keys for report " + j + ":"); 314 PersistableBundle report = MetricsReportProtoUtils.getBundle(reportList, j); 315 writer.println("\t\t" + Arrays.toString(report.keySet().toArray())); 316 } 317 writer.println(); 318 } 319 // Print info on stored errors. Configs are inactive after producing errors. 320 ArrayMap<String, TelemetryProto.TelemetryError> errors = mResultStore.getAllErrorResults(); 321 writer.println("Errors"); 322 writer.println(); 323 for (int i = 0; i < errors.size(); i++) { 324 writer.println("\tConfig name: " + errors.keyAt(i)); 325 TelemetryProto.TelemetryError error = errors.valueAt(i); 326 writer.println("\tError"); 327 writer.println("\t\tType: " + error.getErrorType()); 328 writer.println("\t\tMessage: " + error.getMessage()); 329 if (error.hasStackTrace() && !error.getStackTrace().isEmpty()) { 330 writer.println("\t\tStack trace: " + error.getStackTrace()); 331 } 332 writer.println(); 333 } 334 } 335 336 @Override 337 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)338 public void dumpProto(ProtoOutputStream proto) {} 339 340 /** 341 * Send a telemetry metrics config to the service. 342 * 343 * @param metricsConfigName name of the MetricsConfig. 344 * @param config the serialized bytes of a MetricsConfig object. 345 * @param callback to send status code to CarTelemetryManager. 346 */ 347 @Override addMetricsConfig(@onNull String metricsConfigName, @NonNull byte[] config, @NonNull ResultReceiver callback)348 public void addMetricsConfig(@NonNull String metricsConfigName, @NonNull byte[] config, 349 @NonNull ResultReceiver callback) { 350 mContext.enforceCallingOrSelfPermission( 351 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "addMetricsConfig"); 352 mTelemetryHandler.post(() -> { 353 mTelemetryThreadTraceLog.traceBegin("addMetricsConfig"); 354 int status = addMetricsConfigInternal(metricsConfigName, config); 355 callback.send(status, null); 356 mTelemetryThreadTraceLog.traceEnd(); 357 }); 358 } 359 360 /** Adds the MetricsConfig and returns the status. */ addMetricsConfigInternal( @onNull String metricsConfigName, @NonNull byte[] config)361 private int addMetricsConfigInternal( 362 @NonNull String metricsConfigName, @NonNull byte[] config) { 363 Slogf.d(CarLog.TAG_TELEMETRY, 364 "Adding metrics config: " + metricsConfigName + " to car telemetry service"); 365 TelemetryProto.MetricsConfig metricsConfig; 366 try { 367 metricsConfig = TelemetryProto.MetricsConfig.parseFrom(config); 368 } catch (InvalidProtocolBufferException e) { 369 Slogf.e(CarLog.TAG_TELEMETRY, "Failed to parse MetricsConfig.", e); 370 return STATUS_ADD_METRICS_CONFIG_PARSE_FAILED; 371 } 372 if (metricsConfig.getName().length() == 0) { 373 Slogf.e(CarLog.TAG_TELEMETRY, "MetricsConfig name cannot be an empty string"); 374 return STATUS_ADD_METRICS_CONFIG_PARSE_FAILED; 375 } 376 if (!metricsConfig.getName().equals(metricsConfigName)) { 377 Slogf.e(CarLog.TAG_TELEMETRY, "Argument config name " + metricsConfigName 378 + " doesn't match name in MetricsConfig (" + metricsConfig.getName() + ")."); 379 return STATUS_ADD_METRICS_CONFIG_PARSE_FAILED; 380 } 381 int status = mMetricsConfigStore.addMetricsConfig(metricsConfig); 382 if (status != STATUS_ADD_METRICS_CONFIG_SUCCEEDED) { 383 return status; 384 } 385 // If no error (config is added to the MetricsConfigStore), remove previously collected data 386 // for this config and add config to the DataBroker for metrics collection. 387 mResultStore.removeResult(metricsConfigName); 388 mDataBroker.removeMetricsConfig(metricsConfigName); 389 // add config to DataBroker could fail due to invalid metrics configurations, such as 390 // containing an illegal field. An example is setting the read_interval_sec to 0 in 391 // MemoryPublisher. The read_interval_sec must be at least 1. 392 try { 393 mDataBroker.addMetricsConfig(metricsConfigName, metricsConfig); 394 } catch (IllegalArgumentException | IllegalStateException e) { 395 Slogf.w(CarLog.TAG_TELEMETRY, "Invalid config, failed to add to DataBroker", e); 396 removeMetricsConfig(metricsConfigName); // clean up 397 return STATUS_ADD_METRICS_CONFIG_PARSE_FAILED; 398 } 399 // TODO(b/199410900): update logic once metrics configs have expiration dates 400 return STATUS_ADD_METRICS_CONFIG_SUCCEEDED; 401 } 402 403 /** 404 * Removes a metrics config based on the name. This will also remove outputs produced by the 405 * MetricsConfig. 406 * 407 * @param metricsConfigName the unique identifier of a MetricsConfig. 408 */ 409 @Override removeMetricsConfig(@onNull String metricsConfigName)410 public void removeMetricsConfig(@NonNull String metricsConfigName) { 411 mContext.enforceCallingOrSelfPermission( 412 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "removeMetricsConfig"); 413 mTelemetryHandler.post(() -> { 414 if (DEBUG) { 415 Slogf.d(CarLog.TAG_TELEMETRY, "Removing metrics config " + metricsConfigName 416 + " from car telemetry service"); 417 } 418 mTelemetryThreadTraceLog.traceBegin("removeMetricsConfig"); 419 mMetricsConfigStore.removeMetricsConfig(metricsConfigName); 420 mDataBroker.removeMetricsConfig(metricsConfigName); 421 mResultStore.removeResult(metricsConfigName); 422 mTelemetryThreadTraceLog.traceEnd(); 423 }); 424 } 425 426 /** 427 * Removes all MetricsConfigs. This will also remove all MetricsConfig outputs. 428 */ 429 @Override removeAllMetricsConfigs()430 public void removeAllMetricsConfigs() { 431 mContext.enforceCallingOrSelfPermission( 432 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "removeAllMetricsConfigs"); 433 mTelemetryHandler.post(() -> { 434 mTelemetryThreadTraceLog.traceBegin("removeAllMetricsConfig"); 435 Slogf.d(CarLog.TAG_TELEMETRY, 436 "Removing all metrics config from car telemetry service"); 437 mDataBroker.removeAllMetricsConfigs(); 438 mMetricsConfigStore.removeAllMetricsConfigs(); 439 mResultStore.removeAllResults(); 440 mTelemetryThreadTraceLog.traceEnd(); 441 }); 442 } 443 444 /** 445 * Sends telemetry reports associated with the given config name using the 446 * {@link ICarTelemetryReportListener}. 447 * 448 * @param metricsConfigName the unique identifier of a MetricsConfig. 449 * @param listener to receive finished report or error. 450 */ 451 @Override getFinishedReport(@onNull String metricsConfigName, @NonNull ICarTelemetryReportListener listener)452 public void getFinishedReport(@NonNull String metricsConfigName, 453 @NonNull ICarTelemetryReportListener listener) { 454 mContext.enforceCallingOrSelfPermission( 455 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "getFinishedReport"); 456 mTelemetryHandler.post(() -> { 457 if (DEBUG) { 458 Slogf.d(CarLog.TAG_TELEMETRY, 459 "Getting report for metrics config " + metricsConfigName); 460 } 461 mTelemetryThreadTraceLog.traceBegin("getFinishedReport"); 462 MetricsReportList reportList; 463 TelemetryProto.TelemetryError error; 464 if ((reportList = mResultStore.getMetricsReports(metricsConfigName, true)) != null) { 465 streamReports(listener, metricsConfigName, reportList); 466 } else if (mResultStore.getInterimResult(metricsConfigName) != null) { 467 sendResult(listener, metricsConfigName, /* reportFd = */ null, /* error = */null, 468 /* status = */ STATUS_GET_METRICS_CONFIG_INTERIM_RESULTS); 469 } else if ((error = mResultStore.getErrorResult(metricsConfigName, true)) != null) { 470 sendResult(listener, metricsConfigName, /* reportFd = */ null, /* error = */ error, 471 /* status = */ STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR); 472 } else if (mMetricsConfigStore.containsConfig(metricsConfigName)) { 473 sendResult(listener, metricsConfigName, /* reportFd = */ null, /* error = */ null, 474 /* status = */ STATUS_GET_METRICS_CONFIG_PENDING); 475 } else { 476 sendResult(listener, metricsConfigName, /* reportFd = */ null, /* error = */ null, 477 /* status = */ STATUS_GET_METRICS_CONFIG_DOES_NOT_EXIST); 478 } 479 mTelemetryThreadTraceLog.traceEnd(); 480 }); 481 } 482 483 /** 484 * Sends all script reports or errors using the {@link ICarTelemetryReportListener}. 485 */ 486 @Override getAllFinishedReports(@onNull ICarTelemetryReportListener listener)487 public void getAllFinishedReports(@NonNull ICarTelemetryReportListener listener) { 488 mContext.enforceCallingOrSelfPermission( 489 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "getAllFinishedReports"); 490 mTelemetryHandler.post(() -> { 491 if (DEBUG) { 492 Slogf.d(CarLog.TAG_TELEMETRY, "Getting all reports"); 493 } 494 mTelemetryThreadTraceLog.traceBegin("getAllFinishedReports"); 495 Set<String> finishedReports = mResultStore.getFinishedMetricsConfigNames(); 496 // TODO(b/236843813): Optimize sending multiple reports 497 for (String configName : finishedReports) { 498 MetricsReportList reportList = 499 mResultStore.getMetricsReports(configName, true); 500 if (reportList != null) { 501 streamReports(listener, configName, reportList); 502 continue; 503 } 504 TelemetryProto.TelemetryError telemetryError = 505 mResultStore.getErrorResult(configName, true); 506 sendResult(listener, configName, /* reportFd = */ null, telemetryError, 507 STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR); 508 } 509 mTelemetryThreadTraceLog.traceEnd(); 510 }); 511 } 512 513 /** 514 * Sets a listener for report ready notifications. 515 */ 516 @Override setReportReadyListener(@onNull ICarTelemetryReportReadyListener listener)517 public void setReportReadyListener(@NonNull ICarTelemetryReportReadyListener listener) { 518 mContext.enforceCallingOrSelfPermission( 519 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setReportReadyListener"); 520 mTelemetryHandler.post(() -> { 521 mReportReadyListener = listener; 522 Set<String> configNames = mResultStore.getFinishedMetricsConfigNames(); 523 for (String name : configNames) { 524 try { 525 mReportReadyListener.onReady(name); 526 } catch (RemoteException e) { 527 Slogf.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryReportReadyListener", e); 528 } 529 } 530 }); 531 } 532 533 /** 534 * Clears the listener to stop report ready notifications. 535 */ 536 @Override clearReportReadyListener()537 public void clearReportReadyListener() { 538 mContext.enforceCallingOrSelfPermission( 539 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "clearReportReadyListener"); 540 mTelemetryHandler.post(() -> mReportReadyListener = null); 541 } 542 543 /** 544 * Invoked when a script produces a report or a runtime error. 545 */ onReportReady(@onNull String metricsConfigName)546 private void onReportReady(@NonNull String metricsConfigName) { 547 if (mReportReadyListener == null) { 548 return; 549 } 550 try { 551 mReportReadyListener.onReady(metricsConfigName); 552 } catch (RemoteException e) { 553 Slogf.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryReportReadyListener", e); 554 } 555 } 556 557 /** 558 * Returns the list of config names and versions. This methods is expected to be used only by 559 * {@code CarShellCommand} class. Other usages are not supported. 560 */ 561 @NonNull getActiveMetricsConfigDetails()562 public List<String> getActiveMetricsConfigDetails() { 563 List<TelemetryProto.MetricsConfig> activeMetricsConfigs = mMetricsConfigStore 564 .getActiveMetricsConfigs(); 565 List<String> activeMetricsConfigDetails = new ArrayList<>(activeMetricsConfigs.size()); 566 for (int index = 0; index < activeMetricsConfigs.size(); index++) { 567 TelemetryProto.MetricsConfig config = activeMetricsConfigs.get(index); 568 activeMetricsConfigDetails.add(config.getName() + " version=" + config.getVersion()); 569 } 570 return activeMetricsConfigDetails; 571 } 572 573 /** 574 * Streams the reports in the reportList to the client using a pipe to prevent exceeding 575 * binder memory limit. 576 */ streamReports( @onNull ICarTelemetryReportListener listener, @NonNull String metricsConfigName, @NonNull MetricsReportList reportList)577 private void streamReports( 578 @NonNull ICarTelemetryReportListener listener, 579 @NonNull String metricsConfigName, 580 @NonNull MetricsReportList reportList) { 581 if (reportList.getReportCount() == 0) { 582 sendResult(listener, metricsConfigName, null, null, STATUS_GET_METRICS_CONFIG_PENDING); 583 return; 584 } 585 // if the last report is produced via 'on_script_finished', the config is finished 586 int getReportStatus = 587 reportList.getReport(reportList.getReportCount() - 1).getIsLastReport() 588 ? STATUS_GET_METRICS_CONFIG_FINISHED 589 : STATUS_GET_METRICS_CONFIG_PENDING; 590 ParcelFileDescriptor[] fds = null; 591 try { 592 fds = ParcelFileDescriptor.createPipe(); 593 } catch (IOException e) { 594 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to create pipe to stream reports", e); 595 return; 596 } 597 // send the file descriptor to the client so it can start reading 598 sendResult(listener, metricsConfigName, fds[0], /* error = */ null, getReportStatus); 599 try (DataOutputStream dataOutputStream = new DataOutputStream( 600 new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]))) { 601 for (MetricsReportContainer reportContainer : reportList.getReportList()) { 602 ByteString reportBytes = reportContainer.getReportBytes(); 603 // write the report size in bytes to the pipe, so the read end of the pipe 604 // knows how many bytes to read for this report 605 dataOutputStream.writeInt(reportBytes.size()); 606 dataOutputStream.write(reportBytes.toByteArray()); 607 } 608 } catch (IOException e) { 609 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to write reports to pipe", e); 610 } 611 // close the read end of the pipe, write end of the pipe should be auto-closed 612 IoUtils.closeQuietly(fds[0]); 613 } 614 615 @Nullable getBytes(@ullable TelemetryProto.TelemetryError error)616 private byte[] getBytes(@Nullable TelemetryProto.TelemetryError error) { 617 if (error == null) { 618 return null; 619 } 620 return error.toByteArray(); 621 } 622 sendResult( @onNull ICarTelemetryReportListener listener, @NonNull String metricsConfigName, @Nullable ParcelFileDescriptor reportFd, @Nullable TelemetryProto.TelemetryError error, @CarTelemetryManager.MetricsReportStatus int status)623 private void sendResult( 624 @NonNull ICarTelemetryReportListener listener, 625 @NonNull String metricsConfigName, 626 @Nullable ParcelFileDescriptor reportFd, 627 @Nullable TelemetryProto.TelemetryError error, 628 @CarTelemetryManager.MetricsReportStatus int status) { 629 try { 630 listener.onResult(metricsConfigName, reportFd, getBytes(error), status); 631 } catch (RemoteException e) { 632 Slogf.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryReportListener", e); 633 } 634 } 635 636 /** 637 * Starts collecting data. Once data is sent by publishers, DataBroker will arrange scripts to 638 * run. This method is called by some thread on executor service, therefore the work needs to 639 * be posted on the telemetry thread. 640 */ startMetricsCollection()641 private void startMetricsCollection() { 642 mTelemetryHandler.post(() -> { 643 for (TelemetryProto.MetricsConfig config : 644 mMetricsConfigStore.getActiveMetricsConfigs()) { 645 try { 646 mDataBroker.addMetricsConfig(config.getName(), config); 647 } catch (IllegalArgumentException | IllegalStateException e) { 648 Slogf.w(CarLog.TAG_TELEMETRY, 649 "Loading MetricsConfig from disk failed, stopping MetricsConfig(" 650 + config.getName() + ") and storing error", e); 651 removeMetricsConfig(config.getName()); // clean up 652 TelemetryError error = TelemetryError.newBuilder() 653 .setErrorType(TelemetryError.ErrorType.PUBLISHER_FAILED) 654 .setMessage("Publisher failed when loading MetricsConfig from disk") 655 .setStackTrace(Log.getStackTraceString(e)) 656 .build(); 657 // this will remove the MetricsConfig from disk and clean up its associated 658 // subscribers and tasks from CarTelemetryService, and also notify the client 659 // that an error report is available for them 660 mDataBrokerListener.onReportFinished(config.getName(), error); 661 } 662 } 663 // By this point all publishers are instantiated according to the active configs 664 // and subscribed to session updates. The publishers are ready to handle session updates 665 // that this call might trigger. 666 mSessionController.initSession(); 667 }); 668 } 669 670 /** 671 * Listens to {@link SystemMonitorEvent} and changes the cut-off priority 672 * for {@link DataBroker} such that only tasks with the same or more urgent 673 * priority can be run. 674 * 675 * Highest priority is 0 and lowest is 100. 676 * 677 * @param event the {@link SystemMonitorEvent} received. 678 */ onSystemMonitorEvent(@onNull SystemMonitorEvent event)679 private void onSystemMonitorEvent(@NonNull SystemMonitorEvent event) { 680 if (event.getCpuUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_HI 681 || event.getMemoryUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_HI) { 682 mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_HI); 683 } else if (event.getCpuUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_MED 684 || event.getMemoryUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_MED) { 685 mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_MED); 686 } else { 687 mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_LOW); 688 } 689 } 690 691 /** 692 * As a MetricsConfig completes its lifecycle, it should be cleaned up from the service. 693 * It will be removed from the MetricsConfigStore, all subscribers should be unsubscribed, 694 * and associated tasks should be removed from DataBroker. 695 */ cleanupMetricsConfig(String metricsConfigName)696 private void cleanupMetricsConfig(String metricsConfigName) { 697 mMetricsConfigStore.removeMetricsConfig(metricsConfigName); 698 mResultStore.removeInterimResult(metricsConfigName); 699 mDataBroker.removeMetricsConfig(metricsConfigName); 700 mDataBroker.scheduleNextTask(); 701 } 702 703 @VisibleForTesting getTelemetryHandler()704 Handler getTelemetryHandler() { 705 return mTelemetryHandler; 706 } 707 708 @VisibleForTesting getResultStore()709 ResultStore getResultStore() { 710 return mResultStore; 711 } 712 713 @VisibleForTesting getMetricsConfigStore()714 MetricsConfigStore getMetricsConfigStore() { 715 return mMetricsConfigStore; 716 } 717 } 718