• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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