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 com.android.car.telemetry.publisher;
18 
19 import static com.android.car.telemetry.AtomsProto.Atom.ACTIVITY_FOREGROUND_STATE_CHANGED_FIELD_NUMBER;
20 import static com.android.car.telemetry.AtomsProto.Atom.ANR_OCCURRED_FIELD_NUMBER;
21 import static com.android.car.telemetry.AtomsProto.Atom.APP_CRASH_OCCURRED_FIELD_NUMBER;
22 import static com.android.car.telemetry.AtomsProto.Atom.APP_START_MEMORY_STATE_CAPTURED_FIELD_NUMBER;
23 import static com.android.car.telemetry.AtomsProto.Atom.PROCESS_CPU_TIME_FIELD_NUMBER;
24 import static com.android.car.telemetry.AtomsProto.Atom.PROCESS_MEMORY_STATE_FIELD_NUMBER;
25 import static com.android.car.telemetry.AtomsProto.Atom.WTF_OCCURRED_FIELD_NUMBER;
26 
27 import static java.nio.charset.StandardCharsets.UTF_16;
28 
29 import android.app.StatsManager.StatsUnavailableException;
30 import android.os.Handler;
31 import android.os.PersistableBundle;
32 import android.os.Process;
33 import android.util.AtomicFile;
34 import android.util.LongSparseArray;
35 
36 import com.android.car.CarLog;
37 import com.android.car.telemetry.AtomsProto.ProcessCpuTime;
38 import com.android.car.telemetry.AtomsProto.ProcessMemoryState;
39 import com.android.car.telemetry.StatsLogProto;
40 import com.android.car.telemetry.StatsdConfigProto;
41 import com.android.car.telemetry.StatsdConfigProto.StatsdConfig;
42 import com.android.car.telemetry.TelemetryProto;
43 import com.android.car.telemetry.TelemetryProto.Publisher.PublisherCase;
44 import com.android.car.telemetry.databroker.DataSubscriber;
45 import com.android.car.telemetry.publisher.statsconverters.ConfigMetricsReportListConverter;
46 import com.android.car.telemetry.publisher.statsconverters.StatsConversionException;
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.util.Preconditions;
49 import com.android.server.utils.Slogf;
50 
51 import com.google.protobuf.InvalidProtocolBufferException;
52 
53 import java.io.File;
54 import java.io.FileInputStream;
55 import java.io.FileOutputStream;
56 import java.io.IOException;
57 import java.time.Duration;
58 import java.util.ArrayList;
59 import java.util.HashSet;
60 import java.util.List;
61 import java.util.Map;
62 
63 /**
64  * Publisher for {@link TelemetryProto.StatsPublisher}.
65  *
66  * <p>The publisher adds subscriber configurations in StatsD and they persist between reboots and
67  * CarTelemetryService restarts. Please use {@link #removeAllDataSubscribers} to clean-up these
68  * configs from StatsD store.
69  */
70 public class StatsPublisher extends AbstractPublisher {
71     // These IDs are used in StatsdConfig and ConfigMetricsReport.
72     @VisibleForTesting
73     static final long APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID = 1;
74     @VisibleForTesting
75     static final long APP_START_MEMORY_STATE_CAPTURED_EVENT_METRIC_ID = 2;
76     @VisibleForTesting
77     static final long PROCESS_MEMORY_STATE_MATCHER_ID = 3;
78     @VisibleForTesting
79     static final long PROCESS_MEMORY_STATE_GAUGE_METRIC_ID = 4;
80     @VisibleForTesting
81     static final long ACTIVITY_FOREGROUND_STATE_CHANGED_ATOM_MATCHER_ID = 5;
82     @VisibleForTesting
83     static final long ACTIVITY_FOREGROUND_STATE_CHANGED_EVENT_METRIC_ID = 6;
84     @VisibleForTesting
85     static final long PROCESS_CPU_TIME_MATCHER_ID = 7;
86     @VisibleForTesting
87     static final long PROCESS_CPU_TIME_GAUGE_METRIC_ID = 8;
88     @VisibleForTesting
89     static final long APP_CRASH_OCCURRED_ATOM_MATCHER_ID = 9;
90     @VisibleForTesting
91     static final long APP_CRASH_OCCURRED_EVENT_METRIC_ID = 10;
92     @VisibleForTesting
93     static final long ANR_OCCURRED_ATOM_MATCHER_ID = 11;
94     @VisibleForTesting
95     static final long ANR_OCCURRED_EVENT_METRIC_ID = 12;
96     @VisibleForTesting
97     static final long WTF_OCCURRED_ATOM_MATCHER_ID = 13;
98     @VisibleForTesting
99     static final long WTF_OCCURRED_EVENT_METRIC_ID = 14;
100 
101     // The file that contains stats config key and stats config version
102     @VisibleForTesting
103     static final String SAVED_STATS_CONFIGS_FILE = "stats_config_keys_versions";
104 
105     // TODO(b/202115033): Flatten the load spike by pulling reports for each MetricsConfigs
106     //                    using separate periodical timers.
107     private static final Duration PULL_REPORTS_PERIOD = Duration.ofMinutes(10);
108 
109     private static final String BUNDLE_CONFIG_KEY_PREFIX = "statsd-publisher-config-id-";
110     private static final String BUNDLE_CONFIG_VERSION_PREFIX = "statsd-publisher-config-version-";
111     /**
112      * Binder transaction size limit is 1MB for all binders per process, so for large script input
113      * file pipe will be used to transfer the data to script executor instead of binder call. This
114      * is the input size threshold above which piping is used.
115      */
116     private static final int SCRIPT_INPUT_SIZE_THRESHOLD_BYTES = 20 * 1024; // 20 kb
117 
118     @VisibleForTesting
119     static final StatsdConfigProto.FieldMatcher PROCESS_MEMORY_STATE_FIELDS_MATCHER =
120             StatsdConfigProto.FieldMatcher.newBuilder()
121                     .setField(
122                             PROCESS_MEMORY_STATE_FIELD_NUMBER)
123                     .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
124                             .setField(ProcessMemoryState.OOM_ADJ_SCORE_FIELD_NUMBER))
125                     .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
126                             .setField(ProcessMemoryState.PAGE_FAULT_FIELD_NUMBER))
127                     .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
128                             .setField(ProcessMemoryState.PAGE_MAJOR_FAULT_FIELD_NUMBER))
129                     .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
130                             .setField(ProcessMemoryState.RSS_IN_BYTES_FIELD_NUMBER))
131                     .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
132                             .setField(ProcessMemoryState.CACHE_IN_BYTES_FIELD_NUMBER))
133                     .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
134                             .setField(ProcessMemoryState.SWAP_IN_BYTES_FIELD_NUMBER))
135             .build();
136 
137     @VisibleForTesting
138     static final StatsdConfigProto.FieldMatcher PROCESS_CPU_TIME_FIELDS_MATCHER =
139             StatsdConfigProto.FieldMatcher.newBuilder()
140                     .setField(PROCESS_CPU_TIME_FIELD_NUMBER)
141                     .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
142                             .setField(ProcessCpuTime.USER_TIME_MILLIS_FIELD_NUMBER))
143                     .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
144                             .setField(ProcessCpuTime.SYSTEM_TIME_MILLIS_FIELD_NUMBER))
145             .build();
146 
147     private final StatsManagerProxy mStatsManager;
148     private final AtomicFile mSavedStatsConfigsFile;
149     private final Handler mTelemetryHandler;
150 
151     // True if the publisher is periodically pulling reports from StatsD.
152     private boolean mIsPullingReports = false;
153 
154     /** Assign the method to {@link Runnable}, otherwise the handler fails to remove it. */
155     private final Runnable mPullReportsPeriodically = this::pullReportsPeriodically;
156 
157     // LongSparseArray is memory optimized, but they can be bit slower for more
158     // than 100 items. We're expecting much less number of subscribers, so these data structures
159     // are ok.
160     // Maps config_key to the set of DataSubscriber.
161     private final LongSparseArray<DataSubscriber> mConfigKeyToSubscribers = new LongSparseArray<>();
162 
163     private final PersistableBundle mSavedStatsConfigs;
164 
StatsPublisher( PublisherFailureListener failureListener, StatsManagerProxy statsManager, File publisherDirectory, Handler telemetryHandler)165     StatsPublisher(
166             PublisherFailureListener failureListener,
167             StatsManagerProxy statsManager,
168             File publisherDirectory,
169             Handler telemetryHandler) {
170         super(failureListener);
171         mStatsManager = statsManager;
172         mTelemetryHandler = telemetryHandler;
173         mSavedStatsConfigsFile = new AtomicFile(
174                 new File(publisherDirectory, SAVED_STATS_CONFIGS_FILE));
175         mSavedStatsConfigs = loadBundle();
176     }
177 
178     /** Loads the PersistableBundle containing stats config keys and versions from disk. */
loadBundle()179     private PersistableBundle loadBundle() {
180         if (!mSavedStatsConfigsFile.getBaseFile().exists()) {
181             return new PersistableBundle();
182         }
183         try (FileInputStream fileInputStream = mSavedStatsConfigsFile.openRead()) {
184             return PersistableBundle.readFromStream(fileInputStream);
185         } catch (IOException e) {
186             // TODO(b/199947533): handle failure
187             Slogf.e(CarLog.TAG_TELEMETRY, "Failed to read file "
188                     + mSavedStatsConfigsFile.getBaseFile().getAbsolutePath(), e);
189             return new PersistableBundle();
190         }
191     }
192 
193     /** Writes the PersistableBundle containing stats config keys and versions to disk. */
saveBundle()194     private void saveBundle() {
195         if (mSavedStatsConfigs.size() == 0) {
196             mSavedStatsConfigsFile.delete();
197             return;
198         }
199         FileOutputStream fileOutputStream = null;
200         try {
201             fileOutputStream = mSavedStatsConfigsFile.startWrite();
202             mSavedStatsConfigs.writeToStream(fileOutputStream);
203             mSavedStatsConfigsFile.finishWrite(fileOutputStream);
204         } catch (IOException e) {
205             // TODO(b/199947533): handle failure
206             mSavedStatsConfigsFile.failWrite(fileOutputStream);
207             Slogf.e(CarLog.TAG_TELEMETRY,
208                     "Cannot write to " + mSavedStatsConfigsFile.getBaseFile().getAbsolutePath()
209                             + ". Added stats config info is lost.", e);
210         }
211     }
212 
213     @Override
addDataSubscriber(DataSubscriber subscriber)214     public void addDataSubscriber(DataSubscriber subscriber) {
215         TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
216         Preconditions.checkArgument(
217                 publisherParam.getPublisherCase() == PublisherCase.STATS,
218                 "Subscribers only with StatsPublisher are supported by this class.");
219 
220         long configKey = buildConfigKey(subscriber);
221         mConfigKeyToSubscribers.put(configKey, subscriber);
222         addStatsConfig(configKey, subscriber);
223         if (!mIsPullingReports) {
224             Slogf.d(CarLog.TAG_TELEMETRY, "Stats report will be pulled in "
225                     + PULL_REPORTS_PERIOD.toMinutes() + " minutes.");
226             mIsPullingReports = true;
227             mTelemetryHandler.postDelayed(
228                     mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
229         }
230     }
231 
processReport(long configKey, StatsLogProto.ConfigMetricsReportList report)232     private void processReport(long configKey, StatsLogProto.ConfigMetricsReportList report) {
233         Slogf.i(CarLog.TAG_TELEMETRY, "Received reports: " + report.getReportsCount());
234         if (report.getReportsCount() == 0) {
235             return;
236         }
237         DataSubscriber subscriber = mConfigKeyToSubscribers.get(configKey);
238         if (subscriber == null) {
239             Slogf.w(CarLog.TAG_TELEMETRY, "No subscribers found for config " + configKey);
240             return;
241         }
242         Map<Long, PersistableBundle> metricBundles = null;
243         try {
244             metricBundles = ConfigMetricsReportListConverter.convert(report);
245         } catch (StatsConversionException ex) {
246             Slogf.e(CarLog.TAG_TELEMETRY, "Stats conversion exception for config " + configKey, ex);
247             return;
248         }
249         Long metricId;
250         switch (subscriber.getPublisherParam().getStats().getSystemMetric()) {
251             case APP_START_MEMORY_STATE_CAPTURED:
252                 metricId = APP_START_MEMORY_STATE_CAPTURED_EVENT_METRIC_ID;
253                 break;
254             case PROCESS_MEMORY_STATE:
255                 metricId = PROCESS_MEMORY_STATE_GAUGE_METRIC_ID;
256                 break;
257             case ACTIVITY_FOREGROUND_STATE_CHANGED:
258                 metricId = ACTIVITY_FOREGROUND_STATE_CHANGED_EVENT_METRIC_ID;
259                 break;
260             case PROCESS_CPU_TIME:
261                 metricId = PROCESS_CPU_TIME_GAUGE_METRIC_ID;
262                 break;
263             case APP_CRASH_OCCURRED:
264                 metricId = APP_CRASH_OCCURRED_EVENT_METRIC_ID;
265                 break;
266             case ANR_OCCURRED:
267                 metricId = ANR_OCCURRED_EVENT_METRIC_ID;
268                 break;
269             case WTF_OCCURRED:
270                 metricId = WTF_OCCURRED_EVENT_METRIC_ID;
271                 break;
272             default:
273                 return;
274         }
275         if (!metricBundles.containsKey(metricId)) {
276             Slogf.w(CarLog.TAG_TELEMETRY,
277                     "No reports for metric id " + metricId + " for config " + configKey);
278             return;
279         }
280         PersistableBundle bundle = metricBundles.get(metricId);
281         subscriber.push(bundle, isBundleLargeData(bundle));
282     }
283 
284     @VisibleForTesting
isBundleLargeData(PersistableBundle bundle)285     boolean isBundleLargeData(PersistableBundle bundle) {
286         String[] keys = bundle.keySet().toArray(new String[0]);
287         int bytes = 0;
288         for (int i = 0; i < keys.length; ++i) {
289             Object array = bundle.get(keys[i]);
290             if (array instanceof boolean[]) {
291                 boolean[] boolArray = (boolean[]) array;
292                 bytes += boolArray.length;  // Java boolean is 1 byte
293             } else if (array instanceof long[]) {
294                 long[] longArray = (long[]) array;
295                 bytes += longArray.length * Long.BYTES;
296             } else if (array instanceof int[]) {
297                 int[] intArray = (int[]) array;
298                 bytes += intArray.length * Integer.BYTES;
299             } else if (array instanceof double[]) {
300                 double[] doubleArray = (double[]) array;
301                 bytes += doubleArray.length * Double.BYTES;
302             } else if (array instanceof String[]) {
303                 String[] stringArray = (String[]) array;
304                 for (String str : stringArray) {
305                     bytes += str.getBytes(UTF_16).length;
306                 }
307             }
308         }
309         if (bytes < SCRIPT_INPUT_SIZE_THRESHOLD_BYTES) {
310             return false;
311         }
312         return true;
313     }
314 
processStatsMetadata(StatsLogProto.StatsdStatsReport statsReport)315     private void processStatsMetadata(StatsLogProto.StatsdStatsReport statsReport) {
316         int myUid = Process.myUid();
317         // configKey and StatsdConfig.id are the same, see this#addStatsConfig().
318         HashSet<Long> activeConfigKeys = new HashSet<>(getActiveConfigKeys());
319         HashSet<TelemetryProto.MetricsConfig> failedConfigs = new HashSet<>();
320         for (int i = 0; i < statsReport.getConfigStatsCount(); i++) {
321             StatsLogProto.StatsdStatsReport.ConfigStats stats = statsReport.getConfigStats(i);
322             if (stats.getUid() != myUid || !activeConfigKeys.contains(stats.getId())) {
323                 continue;
324             }
325             if (!stats.getIsValid()) {
326                 Slogf.w(CarLog.TAG_TELEMETRY, "Config key " + stats.getId() + " is invalid.");
327                 failedConfigs.add(mConfigKeyToSubscribers.get(stats.getId()).getMetricsConfig());
328             }
329         }
330         if (!failedConfigs.isEmpty()) {
331             // Notify DataBroker so it can disable invalid MetricsConfigs.
332             onPublisherFailure(
333                     new ArrayList<>(failedConfigs),
334                     new IllegalStateException("Found invalid configs"));
335         }
336     }
337 
pullReportsPeriodically()338     private void pullReportsPeriodically() {
339         if (mIsPullingReports) {
340             Slogf.d(CarLog.TAG_TELEMETRY, "Stats report will be pulled in "
341                     + PULL_REPORTS_PERIOD.toMinutes() + " minutes.");
342             mTelemetryHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
343         }
344 
345         try {
346             // TODO(b/202131100): Get the active list of configs using
347             //                    StatsManager#setActiveConfigsChangedOperation()
348             processStatsMetadata(
349                     StatsLogProto.StatsdStatsReport.parseFrom(mStatsManager.getStatsMetadata()));
350 
351             for (long configKey : getActiveConfigKeys()) {
352                 processReport(configKey, StatsLogProto.ConfigMetricsReportList.parseFrom(
353                         mStatsManager.getReports(configKey)));
354             }
355         } catch (InvalidProtocolBufferException | StatsUnavailableException e) {
356             // If the StatsD is not available, retry in the next pullReportsPeriodically call.
357             Slogf.w(CarLog.TAG_TELEMETRY, e);
358         }
359     }
360 
getActiveConfigKeys()361     private List<Long> getActiveConfigKeys() {
362         ArrayList<Long> result = new ArrayList<>();
363         for (String key : mSavedStatsConfigs.keySet()) {
364             // filter out all the config versions
365             if (!key.startsWith(BUNDLE_CONFIG_KEY_PREFIX)) {
366                 continue;
367             }
368             // the remaining values are config keys
369             result.add(mSavedStatsConfigs.getLong(key));
370         }
371         return result;
372     }
373 
374     /**
375      * Removes the subscriber from the publisher and removes StatsdConfig from StatsD service.
376      * If StatsdConfig is present in Statsd, it removes it even if the subscriber is not present
377      * in the publisher (it happens when subscriber was added before and CarTelemetryService was
378      * restarted and lost publisher state).
379      */
380     @Override
removeDataSubscriber(DataSubscriber subscriber)381     public void removeDataSubscriber(DataSubscriber subscriber) {
382         TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
383         if (publisherParam.getPublisherCase() != PublisherCase.STATS) {
384             Slogf.w(CarLog.TAG_TELEMETRY,
385                     "Expected STATS publisher, but received "
386                             + publisherParam.getPublisherCase().name());
387             return;
388         }
389         long configKey = removeStatsConfig(subscriber);
390         mConfigKeyToSubscribers.remove(configKey);
391         if (mConfigKeyToSubscribers.size() == 0) {
392             mIsPullingReports = false;
393             mTelemetryHandler.removeCallbacks(mPullReportsPeriodically);
394         }
395     }
396 
397     /**
398      * Removes all the subscribers from the publisher removes StatsdConfigs from StatsD service.
399      */
400     @Override
removeAllDataSubscribers()401     public void removeAllDataSubscribers() {
402         for (String key : mSavedStatsConfigs.keySet()) {
403             // filter out all the config versions
404             if (!key.startsWith(BUNDLE_CONFIG_KEY_PREFIX)) {
405                 continue;
406             }
407             // the remaining values are config keys
408             long configKey = mSavedStatsConfigs.getLong(key);
409             try {
410                 mStatsManager.removeConfig(configKey);
411                 String bundleVersion = buildBundleConfigVersionKey(configKey);
412                 mSavedStatsConfigs.remove(key);
413                 mSavedStatsConfigs.remove(bundleVersion);
414             } catch (StatsUnavailableException e) {
415                 Slogf.w(CarLog.TAG_TELEMETRY, "Failed to remove config " + configKey
416                         + ". Ignoring the failure. Will retry removing again when"
417                         + " removeAllDataSubscribers() is called.", e);
418                 // If it cannot remove statsd config, it's less likely it can delete it even if
419                 // retry. So we will just ignore the failures. The next call of this method
420                 // will ry deleting StatsD configs again.
421             }
422         }
423         saveBundle();
424         mSavedStatsConfigs.clear();
425         mIsPullingReports = false;
426         mTelemetryHandler.removeCallbacks(mPullReportsPeriodically);
427     }
428 
429     /**
430      * Returns true if the publisher has the subscriber.
431      */
432     @Override
hasDataSubscriber(DataSubscriber subscriber)433     public boolean hasDataSubscriber(DataSubscriber subscriber) {
434         TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
435         if (publisherParam.getPublisherCase() != PublisherCase.STATS) {
436             return false;
437         }
438         long configKey = buildConfigKey(subscriber);
439         return mConfigKeyToSubscribers.indexOfKey(configKey) >= 0;
440     }
441 
442     /** Returns all the {@link TelemetryProto.MetricsConfig} associated with added subscribers. */
getMetricsConfigs()443     private List<TelemetryProto.MetricsConfig> getMetricsConfigs() {
444         HashSet<TelemetryProto.MetricsConfig> uniqueConfigs = new HashSet<>();
445         for (int i = 0; i < mConfigKeyToSubscribers.size(); i++) {
446             uniqueConfigs.add(mConfigKeyToSubscribers.valueAt(i).getMetricsConfig());
447         }
448         return new ArrayList<>(uniqueConfigs);
449     }
450 
451     /**
452      * Returns the key for PersistableBundle to store/retrieve configKey associated with the
453      * subscriber.
454      */
buildBundleConfigKey(DataSubscriber subscriber)455     private static String buildBundleConfigKey(DataSubscriber subscriber) {
456         return BUNDLE_CONFIG_KEY_PREFIX + subscriber.getMetricsConfig().getName() + "-"
457                 + subscriber.getSubscriber().getHandler();
458     }
459 
460     /**
461      * Returns the key for PersistableBundle to store/retrieve {@link TelemetryProto.MetricsConfig}
462      * version associated with the configKey (which is generated per DataSubscriber).
463      */
buildBundleConfigVersionKey(long configKey)464     private static String buildBundleConfigVersionKey(long configKey) {
465         return BUNDLE_CONFIG_VERSION_PREFIX + configKey;
466     }
467 
468     /**
469      * This method can be called even if StatsdConfig was added to StatsD service before. It stores
470      * previously added config_keys in the persistable bundle and only updates StatsD when
471      * the MetricsConfig (of CarTelemetryService) has a new version.
472      */
addStatsConfig(long configKey, DataSubscriber subscriber)473     private void addStatsConfig(long configKey, DataSubscriber subscriber) {
474         // Store MetricsConfig (of CarTelemetryService) version per handler_function.
475         String bundleVersion = buildBundleConfigVersionKey(configKey);
476         if (mSavedStatsConfigs.getInt(bundleVersion) != 0) {
477             int currentVersion = mSavedStatsConfigs.getInt(bundleVersion);
478             if (currentVersion >= subscriber.getMetricsConfig().getVersion()) {
479                 // It's trying to add current or older MetricsConfig version, just ignore it.
480                 return;
481             }  // if the subscriber's MetricsConfig version is newer, it will replace the old one.
482         }
483         String bundleConfigKey = buildBundleConfigKey(subscriber);
484         StatsdConfig config = buildStatsdConfig(subscriber, configKey);
485         try {
486             // It doesn't throw exception if the StatsdConfig is invalid. But it shouldn't happen,
487             // as we generate well-tested StatsdConfig in this service.
488             mStatsManager.addConfig(configKey, config.toByteArray());
489             mSavedStatsConfigs.putInt(bundleVersion, subscriber.getMetricsConfig().getVersion());
490             mSavedStatsConfigs.putLong(bundleConfigKey, configKey);
491             saveBundle();
492         } catch (StatsUnavailableException e) {
493             Slogf.w(CarLog.TAG_TELEMETRY, "Failed to add config" + configKey, e);
494             // We will notify the failure immediately, as we're expecting StatsManager to be stable.
495             onPublisherFailure(
496                     getMetricsConfigs(),
497                     new IllegalStateException("Failed to add config " + configKey, e));
498         }
499     }
500 
501     /** Removes StatsdConfig and returns configKey. */
removeStatsConfig(DataSubscriber subscriber)502     private long removeStatsConfig(DataSubscriber subscriber) {
503         String bundleConfigKey = buildBundleConfigKey(subscriber);
504         long configKey = buildConfigKey(subscriber);
505         // Store MetricsConfig (of CarTelemetryService) version per handler_function.
506         String bundleVersion = buildBundleConfigVersionKey(configKey);
507         try {
508             mStatsManager.removeConfig(configKey);
509             mSavedStatsConfigs.remove(bundleVersion);
510             mSavedStatsConfigs.remove(bundleConfigKey);
511             saveBundle();
512         } catch (StatsUnavailableException e) {
513             Slogf.w(CarLog.TAG_TELEMETRY, "Failed to remove config " + configKey
514                     + ". Ignoring the failure. Will retry removing again when"
515                     + " removeAllDataSubscribers() is called.", e);
516             // If it cannot remove statsd config, it's less likely it can delete it even if we
517             // retry. So we will just ignore the failures. The next call of this method will
518             // try deleting StatsD configs again.
519         }
520         return configKey;
521     }
522 
523     /**
524      * Builds StatsdConfig id (aka config_key) using subscriber handler name.
525      *
526      * <p>StatsD uses ConfigKey struct to uniquely identify StatsdConfigs. StatsD ConfigKey consists
527      * of two parts: client uid and config_key number. The StatsdConfig is added to StatsD from
528      * CarService - which has uid=1000. Currently there is no client under uid=1000 and there will
529      * not be config_key collision.
530      */
buildConfigKey(DataSubscriber subscriber)531     private static long buildConfigKey(DataSubscriber subscriber) {
532         // Not to be confused with statsd metric, this one is a global CarTelemetry metric name.
533         String metricConfigName = subscriber.getMetricsConfig().getName();
534         String handlerFnName = subscriber.getSubscriber().getHandler();
535         return HashUtils.sha256(metricConfigName + "-" + handlerFnName);
536     }
537 
538     /** Builds {@link StatsdConfig} proto for given subscriber. */
539     @VisibleForTesting
buildStatsdConfig(DataSubscriber subscriber, long configId)540     static StatsdConfig buildStatsdConfig(DataSubscriber subscriber, long configId) {
541         TelemetryProto.StatsPublisher.SystemMetric metric =
542                 subscriber.getPublisherParam().getStats().getSystemMetric();
543         StatsdConfig.Builder builder = StatsdConfig.newBuilder()
544                 // This id is not used in StatsD, but let's make it the same as config_key
545                 // just in case.
546                 .setId(configId)
547                 .addAllowedLogSource("AID_SYSTEM");
548 
549         switch (metric) {
550             case APP_START_MEMORY_STATE_CAPTURED:
551                 return buildAppStartMemoryStateStatsdConfig(builder);
552             case PROCESS_MEMORY_STATE:
553                 return buildProcessMemoryStateStatsdConfig(builder);
554             case ACTIVITY_FOREGROUND_STATE_CHANGED:
555                 return buildActivityForegroundStateStatsdConfig(builder);
556             case PROCESS_CPU_TIME:
557                 return buildProcessCpuTimeStatsdConfig(builder);
558             case APP_CRASH_OCCURRED:
559                 return buildAppCrashOccurredStatsdConfig(builder);
560             case ANR_OCCURRED:
561                 return buildAnrOccurredStatsdConfig(builder);
562             case WTF_OCCURRED:
563                 return buildWtfOccurredStatsdConfig(builder);
564             default:
565                 throw new IllegalArgumentException("Unsupported metric " + metric.name());
566         }
567     }
568 
buildAppStartMemoryStateStatsdConfig(StatsdConfig.Builder builder)569     private static StatsdConfig buildAppStartMemoryStateStatsdConfig(StatsdConfig.Builder builder) {
570         return builder
571                 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
572                         // The id must be unique within StatsdConfig/matchers
573                         .setId(APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID)
574                         .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
575                                 .setAtomId(APP_START_MEMORY_STATE_CAPTURED_FIELD_NUMBER)))
576                 .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
577                         // The id must be unique within StatsdConfig/metrics
578                         .setId(APP_START_MEMORY_STATE_CAPTURED_EVENT_METRIC_ID)
579                         .setWhat(APP_START_MEMORY_STATE_CAPTURED_ATOM_MATCHER_ID))
580                 .build();
581     }
582 
buildProcessMemoryStateStatsdConfig(StatsdConfig.Builder builder)583     private static StatsdConfig buildProcessMemoryStateStatsdConfig(StatsdConfig.Builder builder) {
584         return builder
585                 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
586                         // The id must be unique within StatsdConfig/matchers
587                         .setId(PROCESS_MEMORY_STATE_MATCHER_ID)
588                         .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
589                                 .setAtomId(PROCESS_MEMORY_STATE_FIELD_NUMBER)))
590                 .addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder()
591                         // The id must be unique within StatsdConfig/metrics
592                         .setId(PROCESS_MEMORY_STATE_GAUGE_METRIC_ID)
593                         .setWhat(PROCESS_MEMORY_STATE_MATCHER_ID)
594                         .setDimensionsInWhat(StatsdConfigProto.FieldMatcher.newBuilder()
595                                 .setField(PROCESS_MEMORY_STATE_FIELD_NUMBER)
596                                 .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
597                                         .setField(ProcessMemoryState.UID_FIELD_NUMBER))
598                                 .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
599                                         .setField(ProcessMemoryState.PROCESS_NAME_FIELD_NUMBER))
600                         )
601                         .setGaugeFieldsFilter(StatsdConfigProto.FieldFilter.newBuilder()
602                                 .setFields(PROCESS_MEMORY_STATE_FIELDS_MATCHER)
603                         )  // setGaugeFieldsFilter
604                         .setSamplingType(
605                                 StatsdConfigProto.GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE)
606                         .setBucket(StatsdConfigProto.TimeUnit.FIVE_MINUTES)
607                 )
608                 .addPullAtomPackages(StatsdConfigProto.PullAtomPackages.newBuilder()
609                         .setAtomId(PROCESS_MEMORY_STATE_FIELD_NUMBER)
610                         .addPackages("AID_SYSTEM"))
611                 .build();
612     }
613 
buildActivityForegroundStateStatsdConfig( StatsdConfig.Builder builder)614     private static StatsdConfig buildActivityForegroundStateStatsdConfig(
615             StatsdConfig.Builder builder) {
616         return builder
617                 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
618                         // The id must be unique within StatsdConfig/matchers
619                         .setId(ACTIVITY_FOREGROUND_STATE_CHANGED_ATOM_MATCHER_ID)
620                         .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
621                                 .setAtomId(ACTIVITY_FOREGROUND_STATE_CHANGED_FIELD_NUMBER)))
622                 .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
623                         // The id must be unique within StatsdConfig/metrics
624                         .setId(ACTIVITY_FOREGROUND_STATE_CHANGED_EVENT_METRIC_ID)
625                         .setWhat(ACTIVITY_FOREGROUND_STATE_CHANGED_ATOM_MATCHER_ID))
626                 .build();
627     }
628 
buildProcessCpuTimeStatsdConfig(StatsdConfig.Builder builder)629     private static StatsdConfig buildProcessCpuTimeStatsdConfig(StatsdConfig.Builder builder) {
630         return builder
631                 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
632                         // The id must be unique within StatsdConfig/matchers
633                         .setId(PROCESS_CPU_TIME_MATCHER_ID)
634                         .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
635                                 .setAtomId(PROCESS_CPU_TIME_FIELD_NUMBER)))
636                 .addGaugeMetric(StatsdConfigProto.GaugeMetric.newBuilder()
637                         // The id must be unique within StatsdConfig/metrics
638                         .setId(PROCESS_CPU_TIME_GAUGE_METRIC_ID)
639                         .setWhat(PROCESS_CPU_TIME_MATCHER_ID)
640                         .setDimensionsInWhat(StatsdConfigProto.FieldMatcher.newBuilder()
641                                 .setField(PROCESS_CPU_TIME_FIELD_NUMBER)
642                                 .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
643                                         .setField(ProcessCpuTime.UID_FIELD_NUMBER))
644                                 .addChild(StatsdConfigProto.FieldMatcher.newBuilder()
645                                         .setField(ProcessCpuTime.PROCESS_NAME_FIELD_NUMBER))
646                         )
647                         .setGaugeFieldsFilter(StatsdConfigProto.FieldFilter.newBuilder()
648                                 .setFields(PROCESS_CPU_TIME_FIELDS_MATCHER)
649                         )  // setGaugeFieldsFilter
650                         .setSamplingType(
651                                 StatsdConfigProto.GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE)
652                         .setBucket(StatsdConfigProto.TimeUnit.FIVE_MINUTES)
653                 )
654                 .addPullAtomPackages(StatsdConfigProto.PullAtomPackages.newBuilder()
655                         .setAtomId(PROCESS_CPU_TIME_FIELD_NUMBER)
656                         .addPackages("AID_SYSTEM"))
657                 .build();
658     }
659 
buildAppCrashOccurredStatsdConfig(StatsdConfig.Builder builder)660     private static StatsdConfig buildAppCrashOccurredStatsdConfig(StatsdConfig.Builder builder) {
661         return builder
662                 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
663                         // The id must be unique within StatsdConfig/matchers
664                         .setId(APP_CRASH_OCCURRED_ATOM_MATCHER_ID)
665                         .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
666                                 .setAtomId(APP_CRASH_OCCURRED_FIELD_NUMBER)))
667                 .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
668                         // The id must be unique within StatsdConfig/metrics
669                         .setId(APP_CRASH_OCCURRED_EVENT_METRIC_ID)
670                         .setWhat(APP_CRASH_OCCURRED_ATOM_MATCHER_ID))
671                 .build();
672     }
673 
buildAnrOccurredStatsdConfig(StatsdConfig.Builder builder)674     private static StatsdConfig buildAnrOccurredStatsdConfig(StatsdConfig.Builder builder) {
675         return builder
676                 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
677                         // The id must be unique within StatsdConfig/matchers
678                         .setId(ANR_OCCURRED_ATOM_MATCHER_ID)
679                         .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
680                                 .setAtomId(ANR_OCCURRED_FIELD_NUMBER)))
681                 .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
682                         // The id must be unique within StatsdConfig/metrics
683                         .setId(ANR_OCCURRED_EVENT_METRIC_ID)
684                         .setWhat(ANR_OCCURRED_ATOM_MATCHER_ID))
685                 .build();
686     }
687 
buildWtfOccurredStatsdConfig(StatsdConfig.Builder builder)688     private static StatsdConfig buildWtfOccurredStatsdConfig(StatsdConfig.Builder builder) {
689         return builder
690                 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
691                         // The id must be unique within StatsdConfig/matchers
692                         .setId(WTF_OCCURRED_ATOM_MATCHER_ID)
693                         .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
694                                 .setAtomId(WTF_OCCURRED_FIELD_NUMBER)))
695                 .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
696                         // The id must be unique within StatsdConfig/metrics
697                         .setId(WTF_OCCURRED_EVENT_METRIC_ID)
698                         .setWhat(WTF_OCCURRED_ATOM_MATCHER_ID))
699                 .build();
700     }
701 }
702