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 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 Log.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 /** 337 * Send a telemetry metrics config to the service. 338 * 339 * @param metricsConfigName name of the MetricsConfig. 340 * @param config the serialized bytes of a MetricsConfig object. 341 * @param callback to send status code to CarTelemetryManager. 342 */ 343 @Override addMetricsConfig(@onNull String metricsConfigName, @NonNull byte[] config, @NonNull ResultReceiver callback)344 public void addMetricsConfig(@NonNull String metricsConfigName, @NonNull byte[] config, 345 @NonNull ResultReceiver callback) { 346 mContext.enforceCallingOrSelfPermission( 347 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "addMetricsConfig"); 348 mTelemetryHandler.post(() -> { 349 mTelemetryThreadTraceLog.traceBegin("addMetricsConfig"); 350 int status = addMetricsConfigInternal(metricsConfigName, config); 351 callback.send(status, null); 352 mTelemetryThreadTraceLog.traceEnd(); 353 }); 354 } 355 356 /** Adds the MetricsConfig and returns the status. */ addMetricsConfigInternal( @onNull String metricsConfigName, @NonNull byte[] config)357 private int addMetricsConfigInternal( 358 @NonNull String metricsConfigName, @NonNull byte[] config) { 359 Slogf.d(CarLog.TAG_TELEMETRY, 360 "Adding metrics config: " + metricsConfigName + " to car telemetry service"); 361 TelemetryProto.MetricsConfig metricsConfig; 362 try { 363 metricsConfig = TelemetryProto.MetricsConfig.parseFrom(config); 364 } catch (InvalidProtocolBufferException e) { 365 Slogf.e(CarLog.TAG_TELEMETRY, "Failed to parse MetricsConfig.", e); 366 return STATUS_ADD_METRICS_CONFIG_PARSE_FAILED; 367 } 368 if (metricsConfig.getName().length() == 0) { 369 Slogf.e(CarLog.TAG_TELEMETRY, "MetricsConfig name cannot be an empty string"); 370 return STATUS_ADD_METRICS_CONFIG_PARSE_FAILED; 371 } 372 if (!metricsConfig.getName().equals(metricsConfigName)) { 373 Slogf.e(CarLog.TAG_TELEMETRY, "Argument config name " + metricsConfigName 374 + " doesn't match name in MetricsConfig (" + metricsConfig.getName() + ")."); 375 return STATUS_ADD_METRICS_CONFIG_PARSE_FAILED; 376 } 377 int status = mMetricsConfigStore.addMetricsConfig(metricsConfig); 378 if (status != STATUS_ADD_METRICS_CONFIG_SUCCEEDED) { 379 return status; 380 } 381 // If no error (config is added to the MetricsConfigStore), remove previously collected data 382 // for this config and add config to the DataBroker for metrics collection. 383 mResultStore.removeResult(metricsConfigName); 384 mDataBroker.removeMetricsConfig(metricsConfigName); 385 // add config to DataBroker could fail due to invalid metrics configurations, such as 386 // containing an illegal field. An example is setting the read_interval_sec to 0 in 387 // MemoryPublisher. The read_interval_sec must be at least 1. 388 try { 389 mDataBroker.addMetricsConfig(metricsConfigName, metricsConfig); 390 } catch (IllegalArgumentException | IllegalStateException e) { 391 Slogf.w(CarLog.TAG_TELEMETRY, "Invalid config, failed to add to DataBroker", e); 392 removeMetricsConfig(metricsConfigName); // clean up 393 return STATUS_ADD_METRICS_CONFIG_PARSE_FAILED; 394 } 395 // TODO(b/199410900): update logic once metrics configs have expiration dates 396 return STATUS_ADD_METRICS_CONFIG_SUCCEEDED; 397 } 398 399 /** 400 * Removes a metrics config based on the name. This will also remove outputs produced by the 401 * MetricsConfig. 402 * 403 * @param metricsConfigName the unique identifier of a MetricsConfig. 404 */ 405 @Override removeMetricsConfig(@onNull String metricsConfigName)406 public void removeMetricsConfig(@NonNull String metricsConfigName) { 407 mContext.enforceCallingOrSelfPermission( 408 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "removeMetricsConfig"); 409 mTelemetryHandler.post(() -> { 410 if (DEBUG) { 411 Slogf.d(CarLog.TAG_TELEMETRY, "Removing metrics config " + metricsConfigName 412 + " from car telemetry service"); 413 } 414 mTelemetryThreadTraceLog.traceBegin("removeMetricsConfig"); 415 mMetricsConfigStore.removeMetricsConfig(metricsConfigName); 416 mDataBroker.removeMetricsConfig(metricsConfigName); 417 mResultStore.removeResult(metricsConfigName); 418 mTelemetryThreadTraceLog.traceEnd(); 419 }); 420 } 421 422 /** 423 * Removes all MetricsConfigs. This will also remove all MetricsConfig outputs. 424 */ 425 @Override removeAllMetricsConfigs()426 public void removeAllMetricsConfigs() { 427 mContext.enforceCallingOrSelfPermission( 428 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "removeAllMetricsConfigs"); 429 mTelemetryHandler.post(() -> { 430 mTelemetryThreadTraceLog.traceBegin("removeAllMetricsConfig"); 431 Slogf.d(CarLog.TAG_TELEMETRY, 432 "Removing all metrics config from car telemetry service"); 433 mDataBroker.removeAllMetricsConfigs(); 434 mMetricsConfigStore.removeAllMetricsConfigs(); 435 mResultStore.removeAllResults(); 436 mTelemetryThreadTraceLog.traceEnd(); 437 }); 438 } 439 440 /** 441 * Sends telemetry reports associated with the given config name using the 442 * {@link ICarTelemetryReportListener}. 443 * 444 * @param metricsConfigName the unique identifier of a MetricsConfig. 445 * @param listener to receive finished report or error. 446 */ 447 @Override getFinishedReport(@onNull String metricsConfigName, @NonNull ICarTelemetryReportListener listener)448 public void getFinishedReport(@NonNull String metricsConfigName, 449 @NonNull ICarTelemetryReportListener listener) { 450 mContext.enforceCallingOrSelfPermission( 451 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "getFinishedReport"); 452 mTelemetryHandler.post(() -> { 453 if (DEBUG) { 454 Slogf.d(CarLog.TAG_TELEMETRY, 455 "Getting report for metrics config " + metricsConfigName); 456 } 457 mTelemetryThreadTraceLog.traceBegin("getFinishedReport"); 458 MetricsReportList reportList; 459 TelemetryProto.TelemetryError error; 460 if ((reportList = mResultStore.getMetricsReports(metricsConfigName, true)) != null) { 461 streamReports(listener, metricsConfigName, reportList); 462 } else if (mResultStore.getInterimResult(metricsConfigName) != null) { 463 sendResult(listener, metricsConfigName, /* reportFd = */ null, /* error = */null, 464 /* status = */ STATUS_GET_METRICS_CONFIG_INTERIM_RESULTS); 465 } else if ((error = mResultStore.getErrorResult(metricsConfigName, true)) != null) { 466 sendResult(listener, metricsConfigName, /* reportFd = */ null, /* error = */ error, 467 /* status = */ STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR); 468 } else if (mMetricsConfigStore.containsConfig(metricsConfigName)) { 469 sendResult(listener, metricsConfigName, /* reportFd = */ null, /* error = */ null, 470 /* status = */ STATUS_GET_METRICS_CONFIG_PENDING); 471 } else { 472 sendResult(listener, metricsConfigName, /* reportFd = */ null, /* error = */ null, 473 /* status = */ STATUS_GET_METRICS_CONFIG_DOES_NOT_EXIST); 474 } 475 mTelemetryThreadTraceLog.traceEnd(); 476 }); 477 } 478 479 /** 480 * Sends all script reports or errors using the {@link ICarTelemetryReportListener}. 481 */ 482 @Override getAllFinishedReports(@onNull ICarTelemetryReportListener listener)483 public void getAllFinishedReports(@NonNull ICarTelemetryReportListener listener) { 484 mContext.enforceCallingOrSelfPermission( 485 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "getAllFinishedReports"); 486 mTelemetryHandler.post(() -> { 487 if (DEBUG) { 488 Slogf.d(CarLog.TAG_TELEMETRY, "Getting all reports"); 489 } 490 mTelemetryThreadTraceLog.traceBegin("getAllFinishedReports"); 491 Set<String> finishedReports = mResultStore.getFinishedMetricsConfigNames(); 492 // TODO(b/236843813): Optimize sending multiple reports 493 for (String configName : finishedReports) { 494 MetricsReportList reportList = 495 mResultStore.getMetricsReports(configName, true); 496 if (reportList != null) { 497 streamReports(listener, configName, reportList); 498 continue; 499 } 500 TelemetryProto.TelemetryError telemetryError = 501 mResultStore.getErrorResult(configName, true); 502 sendResult(listener, configName, /* reportFd = */ null, telemetryError, 503 STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR); 504 } 505 mTelemetryThreadTraceLog.traceEnd(); 506 }); 507 } 508 509 /** 510 * Sets a listener for report ready notifications. 511 */ 512 @Override setReportReadyListener(@onNull ICarTelemetryReportReadyListener listener)513 public void setReportReadyListener(@NonNull ICarTelemetryReportReadyListener listener) { 514 mContext.enforceCallingOrSelfPermission( 515 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setReportReadyListener"); 516 mTelemetryHandler.post(() -> { 517 mReportReadyListener = listener; 518 Set<String> configNames = mResultStore.getFinishedMetricsConfigNames(); 519 for (String name : configNames) { 520 try { 521 mReportReadyListener.onReady(name); 522 } catch (RemoteException e) { 523 Slogf.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryReportReadyListener", e); 524 } 525 } 526 }); 527 } 528 529 /** 530 * Clears the listener to stop report ready notifications. 531 */ 532 @Override clearReportReadyListener()533 public void clearReportReadyListener() { 534 mContext.enforceCallingOrSelfPermission( 535 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "clearReportReadyListener"); 536 mTelemetryHandler.post(() -> mReportReadyListener = null); 537 } 538 539 /** 540 * Invoked when a script produces a report or a runtime error. 541 */ onReportReady(@onNull String metricsConfigName)542 private void onReportReady(@NonNull String metricsConfigName) { 543 if (mReportReadyListener == null) { 544 return; 545 } 546 try { 547 mReportReadyListener.onReady(metricsConfigName); 548 } catch (RemoteException e) { 549 Slogf.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryReportReadyListener", e); 550 } 551 } 552 553 /** 554 * Returns the list of config names and versions. This methods is expected to be used only by 555 * {@code CarShellCommand} class. Other usages are not supported. 556 */ 557 @NonNull getActiveMetricsConfigDetails()558 public List<String> getActiveMetricsConfigDetails() { 559 return mMetricsConfigStore.getActiveMetricsConfigs().stream() 560 .map((config) -> config.getName() + " version=" + config.getVersion()) 561 .collect(toList()); 562 } 563 564 /** 565 * Streams the reports in the reportList to the client using a pipe to prevent exceeding 566 * binder memory limit. 567 */ streamReports( @onNull ICarTelemetryReportListener listener, @NonNull String metricsConfigName, @NonNull MetricsReportList reportList)568 private void streamReports( 569 @NonNull ICarTelemetryReportListener listener, 570 @NonNull String metricsConfigName, 571 @NonNull MetricsReportList reportList) { 572 if (reportList.getReportCount() == 0) { 573 sendResult(listener, metricsConfigName, null, null, STATUS_GET_METRICS_CONFIG_PENDING); 574 return; 575 } 576 // if the last report is produced via 'on_script_finished', the config is finished 577 int getReportStatus = 578 reportList.getReport(reportList.getReportCount() - 1).getIsLastReport() 579 ? STATUS_GET_METRICS_CONFIG_FINISHED 580 : STATUS_GET_METRICS_CONFIG_PENDING; 581 ParcelFileDescriptor[] fds = null; 582 try { 583 fds = ParcelFileDescriptor.createPipe(); 584 } catch (IOException e) { 585 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to create pipe to stream reports", e); 586 return; 587 } 588 // send the file descriptor to the client so it can start reading 589 sendResult(listener, metricsConfigName, fds[0], /* error = */ null, getReportStatus); 590 try (DataOutputStream dataOutputStream = new DataOutputStream( 591 new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]))) { 592 for (MetricsReportContainer reportContainer : reportList.getReportList()) { 593 ByteString reportBytes = reportContainer.getReportBytes(); 594 // write the report size in bytes to the pipe, so the read end of the pipe 595 // knows how many bytes to read for this report 596 dataOutputStream.writeInt(reportBytes.size()); 597 dataOutputStream.write(reportBytes.toByteArray()); 598 } 599 } catch (IOException e) { 600 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to write reports to pipe", e); 601 } 602 // close the read end of the pipe, write end of the pipe should be auto-closed 603 IoUtils.closeQuietly(fds[0]); 604 } 605 606 @Nullable getBytes(@ullable TelemetryProto.TelemetryError error)607 private byte[] getBytes(@Nullable TelemetryProto.TelemetryError error) { 608 if (error == null) { 609 return null; 610 } 611 return error.toByteArray(); 612 } 613 sendResult( @onNull ICarTelemetryReportListener listener, @NonNull String metricsConfigName, @Nullable ParcelFileDescriptor reportFd, @Nullable TelemetryProto.TelemetryError error, @CarTelemetryManager.MetricsReportStatus int status)614 private void sendResult( 615 @NonNull ICarTelemetryReportListener listener, 616 @NonNull String metricsConfigName, 617 @Nullable ParcelFileDescriptor reportFd, 618 @Nullable TelemetryProto.TelemetryError error, 619 @CarTelemetryManager.MetricsReportStatus int status) { 620 try { 621 listener.onResult(metricsConfigName, reportFd, getBytes(error), status); 622 } catch (RemoteException e) { 623 Slogf.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryReportListener", e); 624 } 625 } 626 627 /** 628 * Starts collecting data. Once data is sent by publishers, DataBroker will arrange scripts to 629 * run. This method is called by some thread on executor service, therefore the work needs to 630 * be posted on the telemetry thread. 631 */ startMetricsCollection()632 private void startMetricsCollection() { 633 mTelemetryHandler.post(() -> { 634 for (TelemetryProto.MetricsConfig config : 635 mMetricsConfigStore.getActiveMetricsConfigs()) { 636 try { 637 mDataBroker.addMetricsConfig(config.getName(), config); 638 } catch (IllegalArgumentException | IllegalStateException e) { 639 Slogf.w(CarLog.TAG_TELEMETRY, 640 "Loading MetricsConfig from disk failed, stopping MetricsConfig(" 641 + config.getName() + ") and storing error", e); 642 removeMetricsConfig(config.getName()); // clean up 643 TelemetryError error = TelemetryError.newBuilder() 644 .setErrorType(TelemetryError.ErrorType.PUBLISHER_FAILED) 645 .setMessage("Publisher failed when loading MetricsConfig from disk") 646 .setStackTrace(Log.getStackTraceString(e)) 647 .build(); 648 // this will remove the MetricsConfig from disk and clean up its associated 649 // subscribers and tasks from CarTelemetryService, and also notify the client 650 // that an error report is available for them 651 mDataBrokerListener.onReportFinished(config.getName(), error); 652 } 653 } 654 // By this point all publishers are instantiated according to the active configs 655 // and subscribed to session updates. The publishers are ready to handle session updates 656 // that this call might trigger. 657 mSessionController.initSession(); 658 }); 659 } 660 661 /** 662 * Listens to {@link SystemMonitorEvent} and changes the cut-off priority 663 * for {@link DataBroker} such that only tasks with the same or more urgent 664 * priority can be run. 665 * 666 * Highest priority is 0 and lowest is 100. 667 * 668 * @param event the {@link SystemMonitorEvent} received. 669 */ onSystemMonitorEvent(@onNull SystemMonitorEvent event)670 private void onSystemMonitorEvent(@NonNull SystemMonitorEvent event) { 671 if (event.getCpuUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_HI 672 || event.getMemoryUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_HI) { 673 mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_HI); 674 } else if (event.getCpuUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_MED 675 || event.getMemoryUsageLevel() == SystemMonitorEvent.USAGE_LEVEL_MED) { 676 mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_MED); 677 } else { 678 mDataBroker.setTaskExecutionPriority(TASK_PRIORITY_LOW); 679 } 680 } 681 682 /** 683 * As a MetricsConfig completes its lifecycle, it should be cleaned up from the service. 684 * It will be removed from the MetricsConfigStore, all subscribers should be unsubscribed, 685 * and associated tasks should be removed from DataBroker. 686 */ cleanupMetricsConfig(String metricsConfigName)687 private void cleanupMetricsConfig(String metricsConfigName) { 688 mMetricsConfigStore.removeMetricsConfig(metricsConfigName); 689 mResultStore.removeInterimResult(metricsConfigName); 690 mDataBroker.removeMetricsConfig(metricsConfigName); 691 mDataBroker.scheduleNextTask(); 692 } 693 694 @VisibleForTesting getTelemetryHandler()695 Handler getTelemetryHandler() { 696 return mTelemetryHandler; 697 } 698 699 @VisibleForTesting getResultStore()700 ResultStore getResultStore() { 701 return mResultStore; 702 } 703 704 @VisibleForTesting getMetricsConfigStore()705 MetricsConfigStore getMetricsConfigStore() { 706 return mMetricsConfigStore; 707 } 708 } 709