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