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