• 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 
17 package android.car.telemetry;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SystemApi;
25 import android.car.Car;
26 import android.car.CarManagerBase;
27 import android.car.annotation.AddedInOrBefore;
28 import android.car.annotation.RequiredFeature;
29 import android.car.builtin.util.Slogf;
30 import android.os.Bundle;
31 import android.os.IBinder;
32 import android.os.ParcelFileDescriptor;
33 import android.os.PersistableBundle;
34 import android.os.RemoteException;
35 import android.os.ResultReceiver;
36 
37 import libcore.io.IoUtils;
38 
39 import java.io.ByteArrayInputStream;
40 import java.io.DataInputStream;
41 import java.io.EOFException;
42 import java.io.IOException;
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 import java.lang.ref.WeakReference;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.concurrent.Executor;
50 import java.util.concurrent.atomic.AtomicReference;
51 
52 /**
53  * Provides an application interface for interacting with the Car Telemetry Service.
54  *
55  * @hide
56  */
57 @RequiredFeature(Car.CAR_TELEMETRY_SERVICE)
58 @SystemApi
59 public final class CarTelemetryManager extends CarManagerBase {
60 
61     private static final boolean DEBUG = false;
62     private static final String TAG = CarTelemetryManager.class.getSimpleName();
63     private static final int METRICS_CONFIG_MAX_SIZE_BYTES = 10 * 1024; // 10 kb
64 
65     private final ICarTelemetryService mService;
66     private final AtomicReference<Executor> mReportReadyListenerExecutor;
67     private final AtomicReference<ReportReadyListener> mReportReadyListener;
68 
69     /** Status to indicate that MetricsConfig was added successfully. */
70     @AddedInOrBefore(majorVersion = 33)
71     public static final int STATUS_ADD_METRICS_CONFIG_SUCCEEDED = 0;
72 
73     /**
74      * Status to indicate that add MetricsConfig failed because the same MetricsConfig of the same
75      * name and version already exists.
76      */
77     @AddedInOrBefore(majorVersion = 33)
78     public static final int STATUS_ADD_METRICS_CONFIG_ALREADY_EXISTS = 1;
79 
80     /**
81      * Status to indicate that add MetricsConfig failed because a newer version of the MetricsConfig
82      * exists.
83      */
84     @AddedInOrBefore(majorVersion = 33)
85     public static final int STATUS_ADD_METRICS_CONFIG_VERSION_TOO_OLD = 2;
86 
87     /**
88      * Status to indicate that add MetricsConfig failed because CarTelemetryService is unable to
89      * parse the given byte array into a MetricsConfig.
90      */
91     @AddedInOrBefore(majorVersion = 33)
92     public static final int STATUS_ADD_METRICS_CONFIG_PARSE_FAILED = 3;
93 
94     /**
95      * Status to indicate that add MetricsConfig failed because of failure to verify the signature
96      * of the MetricsConfig.
97      */
98     @AddedInOrBefore(majorVersion = 33)
99     public static final int STATUS_ADD_METRICS_CONFIG_SIGNATURE_VERIFICATION_FAILED = 4;
100 
101     /** Status to indicate that add MetricsConfig failed because of a general error in cars. */
102     @AddedInOrBefore(majorVersion = 33)
103     public static final int STATUS_ADD_METRICS_CONFIG_UNKNOWN = 5;
104 
105     /** @hide */
106     @IntDef(
107             prefix = {"STATUS_ADD_METRICS_CONFIG_"},
108             value = {
109                 STATUS_ADD_METRICS_CONFIG_SUCCEEDED,
110                 STATUS_ADD_METRICS_CONFIG_ALREADY_EXISTS,
111                 STATUS_ADD_METRICS_CONFIG_VERSION_TOO_OLD,
112                 STATUS_ADD_METRICS_CONFIG_PARSE_FAILED,
113                 STATUS_ADD_METRICS_CONFIG_SIGNATURE_VERIFICATION_FAILED,
114                 STATUS_ADD_METRICS_CONFIG_UNKNOWN
115             })
116     @Retention(RetentionPolicy.SOURCE)
117     public @interface MetricsConfigStatus {}
118 
119     /** Status to indicate that MetricsConfig produced a report. */
120     @AddedInOrBefore(majorVersion = 33)
121     public static final int STATUS_GET_METRICS_CONFIG_FINISHED = 0;
122 
123     /**
124      * Status to indicate a MetricsConfig exists but has produced neither interim/final report nor
125      * runtime execution errors.
126      */
127     @AddedInOrBefore(majorVersion = 33)
128     public static final int STATUS_GET_METRICS_CONFIG_PENDING = 1;
129 
130     /** Status to indicate a MetricsConfig exists and produced interim results. */
131     @AddedInOrBefore(majorVersion = 33)
132     public static final int STATUS_GET_METRICS_CONFIG_INTERIM_RESULTS = 2;
133 
134     /** Status to indicate the MetricsConfig produced a runtime execution error. */
135     @AddedInOrBefore(majorVersion = 33)
136     public static final int STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR = 3;
137 
138     /** Status to indicate a MetricsConfig does not exist and hence no report can be found. */
139     @AddedInOrBefore(majorVersion = 33)
140     public static final int STATUS_GET_METRICS_CONFIG_DOES_NOT_EXIST = 4;
141 
142     /** @hide */
143     @IntDef(
144             prefix = {"STATUS_GET_METRICS_CONFIG_"},
145             value = {
146                 STATUS_GET_METRICS_CONFIG_FINISHED,
147                 STATUS_GET_METRICS_CONFIG_PENDING,
148                 STATUS_GET_METRICS_CONFIG_INTERIM_RESULTS,
149                 STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR,
150                 STATUS_GET_METRICS_CONFIG_DOES_NOT_EXIST
151             })
152     @Retention(RetentionPolicy.SOURCE)
153     public @interface MetricsReportStatus {}
154 
155     /**
156      * Application must pass a {@link AddMetricsConfigCallback} to use {@link
157      * #addMetricsConfig(String, byte[], Executor, AddMetricsConfigCallback)}
158      *
159      * @hide
160      */
161     @SystemApi
162     public interface AddMetricsConfigCallback {
163         /**
164          * Sends the {@link #addMetricsConfig(String, byte[], Executor, AddMetricsConfigCallback)}
165          * status to the client.
166          *
167          * @param metricsConfigName name of the MetricsConfig that the status is associated with.
168          * @param statusCode See {@link MetricsConfigStatus}.
169          */
170         @AddedInOrBefore(majorVersion = 33)
onAddMetricsConfigStatus( @onNull String metricsConfigName, @MetricsConfigStatus int statusCode)171         void onAddMetricsConfigStatus(
172                 @NonNull String metricsConfigName, @MetricsConfigStatus int statusCode);
173     }
174 
175     /**
176      * Application must pass a {@link MetricsReportCallback} object to receive finished reports from
177      * {@link #getFinishedReport(String, Executor, MetricsReportCallback)} and {@link
178      * #getAllFinishedReports(Executor, MetricsReportCallback)}.
179      *
180      * @hide
181      */
182     @SystemApi
183     public interface MetricsReportCallback {
184         /**
185          * Provides the metrics report associated with metricsConfigName. If there is a metrics
186          * report, it provides the metrics report. If the metrics report calculation failed due to a
187          * runtime error during the execution of reporting script, it provides the runtime error in
188          * the error parameter. The status parameter provides more information on the state of the
189          * metrics report.
190          *
191          * TODO(b/184964661): Publish the documentation for the format of the finished reports.
192          *
193          * @param metricsConfigName name of the MetricsConfig that the report is associated with.
194          * @param report the car telemetry report. Null if there is no report.
195          * @param telemetryError the serialized telemetry metrics configuration runtime execution
196          *     error.
197          * @param status of the metrics report. See {@link MetricsReportStatus}.
198          */
199         @AddedInOrBefore(majorVersion = 33)
onResult( @onNull String metricsConfigName, @Nullable PersistableBundle report, @Nullable byte[] telemetryError, @MetricsReportStatus int status)200         void onResult(
201                 @NonNull String metricsConfigName,
202                 @Nullable PersistableBundle report,
203                 @Nullable byte[] telemetryError,
204                 @MetricsReportStatus int status);
205     }
206 
207     /**
208      * Application can optionally use {@link #setReportReadyListener(Executor, ReportReadyListener)}
209      * to receive report ready notifications. Upon receiving the notification, client can use
210      * {@link #getFinishedReport(String, Executor, MetricsReportCallback)} on the received
211      * metricsConfigName.
212      *
213      * @hide
214      */
215     @SystemApi
216     public interface ReportReadyListener {
217         /**
218          * Sends the report ready notification to the client.
219          *
220          * @param metricsConfigName name of the MetricsConfig whose report is ready.
221          */
222         @AddedInOrBefore(majorVersion = 33)
onReady(@onNull String metricsConfigName)223         void onReady(@NonNull String metricsConfigName);
224     }
225 
226     /**
227      * Gets an instance of CarTelemetryManager.
228      *
229      * <p>CarTelemetryManager manages {@link com.android.car.telemetry.CarTelemetryService} and
230      * provides APIs so the client can use the car telemetry service.
231      *
232      * <p>There is only one client to this manager, which is OEM's cloud application. It uses the
233      * APIs to send config to and receive data from CarTelemetryService.
234      *
235      * @hide
236      */
CarTelemetryManager(Car car, IBinder service)237     public CarTelemetryManager(Car car, IBinder service) {
238         super(car);
239         mService = ICarTelemetryService.Stub.asInterface(service);
240         mReportReadyListenerExecutor = new AtomicReference<>(null);
241         mReportReadyListener = new AtomicReference<>(null);
242         if (DEBUG) {
243             Slogf.d(TAG, "starting car telemetry manager");
244         }
245     }
246 
247     /** @hide */
248     @Override
249     @AddedInOrBefore(majorVersion = 33)
onCarDisconnected()250     public void onCarDisconnected() {}
251 
252     /**
253      * Adds a MetricsConfig to CarTelemetryService. The size of the MetricsConfig cannot exceed a
254      * {@link #METRICS_CONFIG_MAX_SIZE_BYTES}, otherwise an exception is thrown.
255      *
256      * <p>The MetricsConfig will be uniquely identified by its name and version. If a MetricsConfig
257      * of the same name already exists in {@link com.android.car.telemetry.CarTelemetryService}, the
258      * config version will be compared. If the version is strictly higher, the existing
259      * MetricsConfig will be replaced by the new one. All legacy data will be cleared if replaced.
260      *
261      * <p>Client should use {@link #getFinishedReport(String, Executor, MetricsReportCallback)} to
262      * get the report before replacing a MetricsConfig.
263      *
264      * <p>The status of this API is sent back asynchronously via {@link AddMetricsConfigCallback}.
265      *
266      * @param metricsConfigName name of the MetricsConfig, must match {@link
267      *     TelemetryProto.MetricsConfig#getName()}.
268      * @param metricsConfig the serialized bytes of a MetricsConfig object.
269      * @param executor The {@link Executor} on which the callback will be invoked.
270      * @param callback A callback for receiving addMetricsConfig status codes.
271      * @throws IllegalArgumentException if the MetricsConfig size exceeds limit.
272      * @hide
273      */
274     @SystemApi
275     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
276     @AddedInOrBefore(majorVersion = 33)
addMetricsConfig( @onNull String metricsConfigName, @NonNull byte[] metricsConfig, @CallbackExecutor @NonNull Executor executor, @NonNull AddMetricsConfigCallback callback)277     public void addMetricsConfig(
278             @NonNull String metricsConfigName,
279             @NonNull byte[] metricsConfig,
280             @CallbackExecutor @NonNull Executor executor,
281             @NonNull AddMetricsConfigCallback callback) {
282         if (metricsConfig.length > METRICS_CONFIG_MAX_SIZE_BYTES) {
283             throw new IllegalArgumentException("MetricsConfig size exceeds limit.");
284         }
285         try {
286             mService.addMetricsConfig(metricsConfigName, metricsConfig, new ResultReceiver(null) {
287                 @Override
288                 protected void onReceiveResult(int resultCode, Bundle resultData) {
289                     executor.execute(() ->
290                             callback.onAddMetricsConfigStatus(metricsConfigName, resultCode));
291                 }
292             });
293         } catch (RemoteException e) {
294             handleRemoteExceptionFromCarService(e);
295         }
296     }
297 
298     /**
299      * Removes a MetricsConfig from {@link com.android.car.telemetry.CarTelemetryService}. This will
300      * also remove outputs produced by the MetricsConfig. If the MetricsConfig does not exist,
301      * nothing will be removed.
302      *
303      * @param metricsConfigName that identify the MetricsConfig.
304      * @hide
305      */
306     @SystemApi
307     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
308     @AddedInOrBefore(majorVersion = 33)
removeMetricsConfig(@onNull String metricsConfigName)309     public void removeMetricsConfig(@NonNull String metricsConfigName) {
310         try {
311             mService.removeMetricsConfig(metricsConfigName);
312         } catch (RemoteException e) {
313             handleRemoteExceptionFromCarService(e);
314         }
315     }
316 
317     /**
318      * Removes all MetricsConfigs from {@link com.android.car.telemetry.CarTelemetryService}. This
319      * will also remove all MetricsConfig outputs.
320      *
321      * @hide
322      */
323     @SystemApi
324     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
325     @AddedInOrBefore(majorVersion = 33)
removeAllMetricsConfigs()326     public void removeAllMetricsConfigs() {
327         try {
328             mService.removeAllMetricsConfigs();
329         } catch (RemoteException e) {
330             handleRemoteExceptionFromCarService(e);
331         }
332     }
333 
334     /**
335      * Gets script execution reports of a MetricsConfig as from the {@link
336      * com.android.car.telemetry.CarTelemetryService}. This API is asynchronous and the report is
337      * sent back asynchronously via the {@link MetricsReportCallback}. This call is destructive. The
338      * returned report will be deleted from CarTelemetryService.
339      *
340      * @param metricsConfigName to identify the MetricsConfig.
341      * @param executor The {@link Executor} on which the callback will be invoked.
342      * @param callback A callback for receiving finished reports.
343      * @hide
344      */
345     @SystemApi
346     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
347     @AddedInOrBefore(majorVersion = 33)
getFinishedReport( @onNull String metricsConfigName, @CallbackExecutor @NonNull Executor executor, @NonNull MetricsReportCallback callback)348     public void getFinishedReport(
349             @NonNull String metricsConfigName,
350             @CallbackExecutor @NonNull Executor executor,
351             @NonNull MetricsReportCallback callback) {
352         try {
353             mService.getFinishedReport(
354                     metricsConfigName, new CarTelemetryReportListenerImpl(executor, callback));
355         } catch (RemoteException e) {
356             handleRemoteExceptionFromCarService(e);
357         }
358     }
359 
360     /**
361      * Gets all script execution reports from {@link com.android.car.telemetry.CarTelemetryService}
362      * asynchronously via the {@link MetricsReportCallback}. The callback will be invoked multiple
363      * times if there are multiple reports. This call is destructive. The returned reports will be
364      * deleted from CarTelemetryService.
365      *
366      * @param executor The {@link Executor} on which the callback will be invoked.
367      * @param callback A callback for receiving finished reports.
368      * @hide
369      */
370     @SystemApi
371     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
372     @AddedInOrBefore(majorVersion = 33)
getAllFinishedReports( @allbackExecutor @onNull Executor executor, @NonNull MetricsReportCallback callback)373     public void getAllFinishedReports(
374             @CallbackExecutor @NonNull Executor executor, @NonNull MetricsReportCallback callback) {
375         try {
376             mService.getAllFinishedReports(new CarTelemetryReportListenerImpl(executor, callback));
377         } catch (RemoteException e) {
378             handleRemoteExceptionFromCarService(e);
379         }
380     }
381 
382     /**
383      * Registers a listener to receive report ready notifications. This is an optional feature that
384      * helps clients decide when is a good time to call {@link
385      * #getFinishedReport(String, Executor, MetricsReportCallback)}.
386      *
387      * <p>When a listener is set, it will receive notifications for reports or errors that are
388      * already produced before the listener is registered.
389      *
390      * <p>Clients who do not register a listener should use {@link
391      * #getFinishedReport(String, Executor, MetricsReportCallback)} periodically to check for
392      * report.
393      *
394      * @param executor The {@link Executor} on which the callback will be invoked.
395      * @param listener The listener to receive report ready notifications.
396      * @throws IllegalStateException if the listener is already set.
397      * @hide
398      */
399     @SystemApi
400     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
401     @AddedInOrBefore(majorVersion = 33)
setReportReadyListener( @allbackExecutor @onNull Executor executor, @NonNull ReportReadyListener listener)402     public void setReportReadyListener(
403             @CallbackExecutor @NonNull Executor executor, @NonNull ReportReadyListener listener) {
404         if (mReportReadyListener.get() != null) {
405             throw new IllegalStateException("ReportReadyListener is already set.");
406         }
407         mReportReadyListenerExecutor.set(executor);
408         mReportReadyListener.set(listener);
409         try {
410             mService.setReportReadyListener(new CarTelemetryReportReadyListenerImpl(this));
411         } catch (RemoteException e) {
412             handleRemoteExceptionFromCarService(e);
413         }
414     }
415 
416     /**
417      * Clears the listener for receiving telemetry report ready notifications.
418      *
419      * @hide
420      */
421     @SystemApi
422     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
423     @AddedInOrBefore(majorVersion = 33)
clearReportReadyListener()424     public void clearReportReadyListener() {
425         mReportReadyListenerExecutor.set(null);
426         mReportReadyListener.set(null);
427         try {
428             mService.clearReportReadyListener();
429         } catch (RemoteException e) {
430             handleRemoteExceptionFromCarService(e);
431         }
432     }
433 
434     /** Listens for report ready notifications.
435      * Atomic variables (mReportReadyListenerExecutor and mReportReadyListener)
436      * can be accessed from different threads simultaneously.
437      * Both of these variables can be set to null by {@link #clearReportReadyListener()}
438      * and simultaneously {@link #onReady(String)} may try to access the null value.
439      * So, to avoid possible NullPointerException due to this race condition,
440      * these atomic variables are needed to be retrieved in local variables
441      * and verified those are not null before accessing. */
442     private static final class CarTelemetryReportReadyListenerImpl
443             extends ICarTelemetryReportReadyListener.Stub {
444         private final WeakReference<CarTelemetryManager> mManager;
445 
CarTelemetryReportReadyListenerImpl(CarTelemetryManager manager)446         private CarTelemetryReportReadyListenerImpl(CarTelemetryManager manager) {
447             mManager = new WeakReference<>(manager);
448         }
449 
450         @Override
onReady(@onNull String metricsConfigName)451         public void onReady(@NonNull String metricsConfigName) {
452             CarTelemetryManager manager = mManager.get();
453             if (manager == null) {
454                 return;
455             }
456             Executor executor = manager.mReportReadyListenerExecutor.get();
457             if (executor == null) {
458                 return;
459             }
460             ReportReadyListener reportReadyListener = manager.mReportReadyListener.get();
461             if (reportReadyListener == null) {
462                 return;
463             }
464             executor.execute(
465                     () -> reportReadyListener.onReady(metricsConfigName));
466         }
467     }
468 
469     /**
470      * Receives responses to {@link #getFinishedReport(String, Executor, MetricsReportCallback)}
471      * requests.
472      */
473     private static final class CarTelemetryReportListenerImpl
474             extends ICarTelemetryReportListener.Stub {
475 
476         private final Executor mExecutor;
477         private final MetricsReportCallback mMetricsReportCallback;
478 
CarTelemetryReportListenerImpl(Executor executor, MetricsReportCallback callback)479         private CarTelemetryReportListenerImpl(Executor executor, MetricsReportCallback callback) {
480             Objects.requireNonNull(executor);
481             Objects.requireNonNull(callback);
482             mExecutor = executor;
483             mMetricsReportCallback = callback;
484         }
485 
486         @Override
onResult( @onNull String metricsConfigName, @Nullable ParcelFileDescriptor reportFileDescriptor, @Nullable byte[] telemetryError, @MetricsReportStatus int status)487         public void onResult(
488                 @NonNull String metricsConfigName,
489                 @Nullable ParcelFileDescriptor reportFileDescriptor,
490                 @Nullable byte[] telemetryError,
491                 @MetricsReportStatus int status) {
492             // return early if no need to stream reports
493             if (reportFileDescriptor == null) {
494                 mExecutor.execute(() -> mMetricsReportCallback.onResult(
495                         metricsConfigName, null, telemetryError, status));
496                 return;
497             }
498             // getting to this line means the reportFileDescriptor is non-null
499             ParcelFileDescriptor dup = null;
500             try {
501                 dup = reportFileDescriptor.dup();
502             } catch (IOException e) {
503                 Slogf.w(TAG, "Could not dup ParcelFileDescriptor", e);
504                 return;
505             } finally {
506                 IoUtils.closeQuietly(reportFileDescriptor);
507             }
508             final ParcelFileDescriptor readFd = dup;
509             mExecutor.execute(() -> {
510                 // read PersistableBundles from the pipe, this method will also close the fd
511                 List<PersistableBundle> reports = parseReports(readFd);
512                 // if a readFd is non-null, CarTelemetryService will write at least 1 report
513                 // to the pipe, so something must have gone wrong to get 0 report
514                 if (reports.size() == 0) {
515                     mMetricsReportCallback.onResult(metricsConfigName, null, null,
516                             STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR);
517                     return;
518                 }
519                 for (PersistableBundle report : reports) {
520                     mMetricsReportCallback
521                             .onResult(metricsConfigName, report, telemetryError, status);
522                 }
523             });
524         }
525 
526         /** Helper method to parse reports (PersistableBundles) from the file descriptor. */
parseReports(ParcelFileDescriptor reportFileDescriptor)527         private List<PersistableBundle> parseReports(ParcelFileDescriptor reportFileDescriptor) {
528             List<PersistableBundle> reports = new ArrayList<>();
529             try (DataInputStream dataInputStream = new DataInputStream(
530                     new ParcelFileDescriptor.AutoCloseInputStream(reportFileDescriptor))) {
531                 while (true) {
532                     // read integer which tells us how many bytes to read for the PersistableBundle
533                     int size = dataInputStream.readInt();
534                     byte[] bundleBytes = dataInputStream.readNBytes(size);
535                     if (bundleBytes.length != size) {
536                         Slogf.e(TAG, "Expected to read " + size
537                                 + " bytes from the pipe, but only read "
538                                 + bundleBytes.length + " bytes");
539                         break;
540                     }
541                     PersistableBundle report = PersistableBundle.readFromStream(
542                             new ByteArrayInputStream(bundleBytes));
543                     reports.add(report);
544                 }
545             } catch (EOFException e) {
546                 // a graceful exit from the while true loop, thrown by DataInputStream#readInt(),
547                 // every successful parse should naturally reach this line
548                 if (DEBUG) {
549                     Slogf.d(TAG, "parseReports reached end of file");
550                 }
551             } catch (IOException e) {
552                 Slogf.e(TAG, "Failed to read metrics reports from pipe", e);
553             }
554             return reports;
555         }
556     }
557 }
558