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