• 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     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