• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.cts.statsd.alert;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import android.cts.statsd.atom.AtomTestCase;
22 
23 import com.android.internal.os.StatsdConfigProto;
24 import com.android.internal.os.StatsdConfigProto.Alert;
25 import com.android.internal.os.StatsdConfigProto.CountMetric;
26 import com.android.internal.os.StatsdConfigProto.DurationMetric;
27 import com.android.internal.os.StatsdConfigProto.FieldFilter;
28 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
29 import com.android.internal.os.StatsdConfigProto.GaugeMetric;
30 import com.android.internal.os.StatsdConfigProto.IncidentdDetails;
31 import com.android.internal.os.StatsdConfigProto.PerfettoDetails;
32 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
33 import com.android.internal.os.StatsdConfigProto.Subscription;
34 import com.android.internal.os.StatsdConfigProto.TimeUnit;
35 import com.android.internal.os.StatsdConfigProto.ValueMetric;
36 import com.android.os.AtomsProto.AnomalyDetected;
37 import com.android.os.AtomsProto.AppBreadcrumbReported;
38 import com.android.os.AtomsProto.Atom;
39 import com.android.os.AtomsProto.DebugElapsedClock;
40 import com.android.os.StatsLog.EventMetricData;
41 import com.android.tradefed.log.LogUtil.CLog;
42 import java.util.List;
43 
44 /**
45  * Statsd Anomaly Detection tests.
46  */
47 public class AnomalyDetectionTests extends AtomTestCase {
48 
49     private static final String TAG = "Statsd.AnomalyDetectionTests";
50 
51     private static final boolean INCIDENTD_TESTS_ENABLED = false;
52     private static final boolean PERFETTO_TESTS_ENABLED = true;
53 
54     private static final int WAIT_AFTER_BREADCRUMB_MS = 2000;
55 
56     // Config constants
57     private static final int APP_BREADCRUMB_REPORTED_MATCH_START_ID = 1;
58     private static final int APP_BREADCRUMB_REPORTED_MATCH_STOP_ID = 2;
59     private static final int METRIC_ID = 8;
60     private static final int ALERT_ID = 11;
61     private static final int SUBSCRIPTION_ID_INCIDENTD = 41;
62     private static final int SUBSCRIPTION_ID_PERFETTO = 42;
63     private static final int ANOMALY_DETECT_MATCH_ID = 10;
64     private static final int ANOMALY_EVENT_ID = 101;
65     private static final int INCIDENTD_SECTION = -1;
66 
67     private boolean defaultSystemTracingConfigurationHasChanged = false;
68 
69     @Override
setUp()70     protected void setUp() throws Exception {
71         super.setUp();
72         if (!INCIDENTD_TESTS_ENABLED) {
73             CLog.w(TAG, TAG + " anomaly tests are disabled by a flag. Change flag to true to run");
74         }
75         if (PERFETTO_TESTS_ENABLED) {
76             // Default Android configuration can only change for device type that doesn't require SystemTracingEnabled
77             // by default in CDD.
78             String chars = getDevice().getProperty("ro.build.characteristics");
79             if (!isSystemTracingEnabled() && chars.contains("automotive")) {
80                 enableSystemTracing();
81                 defaultSystemTracingConfigurationHasChanged = true;
82             }
83         }
84     }
85 
86     @Override
tearDown()87     protected void tearDown() throws Exception {
88         super.tearDown();
89         if (PERFETTO_TESTS_ENABLED) {
90             // Disable SystemTracing if previously enabled at test setUp()
91             if (defaultSystemTracingConfigurationHasChanged) {
92                 disableSystemTracing();
93             }
94             // Deadline to finish trace collection
95             final long deadLine = System.currentTimeMillis() + 10000;
96             while (isSystemTracingEnabled()) {
97                 if (System.currentTimeMillis() > deadLine) {
98                     CLog.w("/sys/kernel/debug/tracing/tracing_on is still 1 after 10 secs : " + isSystemTracingEnabled());
99                     break;
100                 }
101                 CLog.d("Waiting to finish collecting traces. ");
102                 Thread.sleep(WAIT_TIME_SHORT);
103             }
104         }
105     }
106 
107     // Tests that anomaly detection for count works.
108     // Also tests that anomaly detection works when spanning multiple buckets.
testCountAnomalyDetection()109     public void testCountAnomalyDetection() throws Exception {
110         StatsdConfig.Builder config = getBaseConfig(10, 20, 2 /* threshold: > 2 counts */)
111                 .addCountMetric(CountMetric.newBuilder()
112                         .setId(METRIC_ID)
113                         .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
114                         .setBucket(TimeUnit.CTS) // 1 second
115                         // Slice by label
116                         .setDimensionsInWhat(FieldMatcher.newBuilder()
117                                 .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
118                                 .addChild(FieldMatcher.newBuilder()
119                                         .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
120                                 )
121                         )
122                 );
123         uploadConfig(config);
124 
125         String markTime = getCurrentLogcatDate();
126         // count(label=6) -> 1 (not an anomaly, since not "greater than 2")
127         doAppBreadcrumbReportedStart(6);
128         Thread.sleep(500);
129         assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
130         if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
131 
132         // count(label=6) -> 2 (not an anomaly, since not "greater than 2")
133         doAppBreadcrumbReportedStart(6);
134         Thread.sleep(500);
135         assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
136         if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
137 
138         // count(label=12) -> 1 (not an anomaly, since not "greater than 2")
139         doAppBreadcrumbReportedStart(12);
140         Thread.sleep(1000);
141         assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
142         if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
143 
144         doAppBreadcrumbReportedStart(6); // count(label=6) -> 3 (anomaly, since "greater than 2"!)
145         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
146 
147         List<EventMetricData> data = getEventMetricDataList();
148         assertWithMessage("Expected anomaly").that(data).hasSize(1);
149         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
150         if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
151     }
152 
153     // Tests that anomaly detection for duration works.
154     // Also tests that refractory periods in anomaly detection work.
testDurationAnomalyDetection()155     public void testDurationAnomalyDetection() throws Exception {
156         final int APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE = 1423;
157         StatsdConfig.Builder config =
158                 getBaseConfig(17, 17, 10_000_000_000L  /*threshold: > 10 seconds in nanoseconds*/)
159                         .addDurationMetric(DurationMetric.newBuilder()
160                                 .setId(METRIC_ID)
161                                 .setWhat(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE) // predicate below
162                                 .setAggregationType(DurationMetric.AggregationType.SUM)
163                                 .setBucket(TimeUnit.CTS) // 1 second
164                         )
165                         .addPredicate(StatsdConfigProto.Predicate.newBuilder()
166                                 .setId(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE)
167                                 .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
168                                         .setStart(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
169                                         .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
170                                 )
171                         );
172         uploadConfig(config);
173 
174         // Since timing is crucial and checking logcat for incidentd is slow, we don't test for it.
175 
176         // Test that alarm doesn't fire early.
177         String markTime = getCurrentLogcatDate();
178         doAppBreadcrumbReportedStart(1);
179         Thread.sleep(6_000);  // Recorded duration at end: 6s
180         assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
181 
182         doAppBreadcrumbReportedStop(1);
183         Thread.sleep(4_000);  // Recorded duration at end: 6s
184         assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
185 
186         // Test that alarm does fire when it is supposed to (after 4s, plus up to 5s alarm delay).
187         doAppBreadcrumbReportedStart(1);
188         Thread.sleep(9_000);  // Recorded duration at end: 13s
189         doAppBreadcrumbReported(2);
190         List<EventMetricData> data = getEventMetricDataList();
191         assertWithMessage("Expected anomaly").that(data).hasSize(1);
192         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
193 
194         // Now test that the refractory period is obeyed.
195         markTime = getCurrentLogcatDate();
196         doAppBreadcrumbReportedStop(1);
197         doAppBreadcrumbReportedStart(1);
198         Thread.sleep(3_000);  // Recorded duration at end: 13s
199         // NB: the previous getEventMetricDataList also removes the report, so size is back to 0.
200         assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
201 
202         // Test that detection works again after refractory period finishes.
203         doAppBreadcrumbReportedStop(1);
204         Thread.sleep(8_000);  // Recorded duration at end: 9s
205         doAppBreadcrumbReportedStart(1);
206         Thread.sleep(15_000);  // Recorded duration at end: 15s
207         // We can do an incidentd test now that all the timing issues are done.
208         doAppBreadcrumbReported(2);
209         data = getEventMetricDataList();
210         assertWithMessage("Expected anomaly").that(data).hasSize(1);
211         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
212         if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
213 
214         doAppBreadcrumbReportedStop(1);
215     }
216 
217     // Tests that anomaly detection for duration works even when the alarm fires too late.
testDurationAnomalyDetectionForLateAlarms()218     public void testDurationAnomalyDetectionForLateAlarms() throws Exception {
219         final int APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE = 1423;
220         StatsdConfig.Builder config =
221                 getBaseConfig(50, 0, 6_000_000_000L /* threshold: > 6 seconds in nanoseconds */)
222                         .addDurationMetric(DurationMetric.newBuilder()
223                                 .setId(METRIC_ID)
224                                 .setWhat(
225                                         APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE) // Predicate below.
226                                 .setAggregationType(DurationMetric.AggregationType.SUM)
227                                 .setBucket(TimeUnit.CTS) // 1 second
228                         )
229                         .addPredicate(StatsdConfigProto.Predicate.newBuilder()
230                                 .setId(APP_BREADCRUMB_REPORTED_IS_ON_PREDICATE)
231                                 .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder()
232                                         .setStart(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
233                                         .setStop(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
234                                 )
235                         );
236         uploadConfig(config);
237 
238         doAppBreadcrumbReportedStart(1);
239         Thread.sleep(5_000);
240         doAppBreadcrumbReportedStop(1);
241         Thread.sleep(2_000);
242         assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
243 
244         // Test that alarm does fire when it is supposed to.
245         // The anomaly occurs in 1s, but alarms won't fire that quickly.
246         // It is likely that the alarm will only fire after this period is already over, but the
247         // anomaly should nonetheless be detected when the event stops.
248         doAppBreadcrumbReportedStart(1);
249         Thread.sleep(1_200);
250         // Anomaly should be detected here if the alarm didn't fire yet.
251         doAppBreadcrumbReportedStop(1);
252         Thread.sleep(200);
253         List<EventMetricData> data = getEventMetricDataList();
254         if (data.size() == 2) {
255             // Although we expect that the alarm won't fire, we certainly cannot demand that.
256             CLog.w(TAG, "The anomaly was detected twice. Presumably the alarm did manage to fire.");
257         }
258         assertThat(data.size()).isAnyOf(1, 2);
259         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
260     }
261 
262     // Tests that anomaly detection for value works.
testValueAnomalyDetection()263     public void testValueAnomalyDetection() throws Exception {
264         StatsdConfig.Builder config = getBaseConfig(4, 0, 6 /* threshold: value > 6 */)
265                 .addValueMetric(ValueMetric.newBuilder()
266                         .setId(METRIC_ID)
267                         .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
268                         .setBucket(TimeUnit.ONE_MINUTE)
269                         // Get the label field's value:
270                         .setValueField(FieldMatcher.newBuilder()
271                                 .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
272                                 .addChild(FieldMatcher.newBuilder()
273                                         .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
274                         )
275 
276                 );
277         uploadConfig(config);
278 
279         String markTime = getCurrentLogcatDate();
280         doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
281         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
282         assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
283         if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
284 
285         doAppBreadcrumbReportedStart(14); // value = 14 > trigger
286         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
287 
288         List<EventMetricData> data = getEventMetricDataList();
289         assertWithMessage("Expected anomaly").that(data).hasSize(1);
290         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
291         if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
292     }
293 
294     // Test that anomaly detection integrates with perfetto properly.
testPerfetto()295     public void testPerfetto() throws Exception {
296         String chars = getDevice().getProperty("ro.build.characteristics");
297         if (chars.contains("watch")) {
298                 return;
299         }
300 
301         if (PERFETTO_TESTS_ENABLED) resetPerfettoGuardrails();
302 
303         StatsdConfig.Builder config = getBaseConfig(4, 0, 6 /* threshold: value > 6 */)
304                 .addSubscription(Subscription.newBuilder()
305                         .setId(SUBSCRIPTION_ID_PERFETTO)
306                         .setRuleType(Subscription.RuleType.ALERT)
307                         .setRuleId(ALERT_ID)
308                         .setPerfettoDetails(PerfettoDetails.newBuilder()
309                                 .setTraceConfig(getPerfettoConfig()))
310                 )
311                 .addValueMetric(ValueMetric.newBuilder()
312                         .setId(METRIC_ID)
313                         .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
314                         .setBucket(TimeUnit.ONE_MINUTE)
315                         // Get the label field's value:
316                         .setValueField(FieldMatcher.newBuilder()
317                                 .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
318                                 .addChild(FieldMatcher.newBuilder()
319                                         .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
320                         )
321 
322                 );
323         uploadConfig(config);
324 
325         String markTime = getCurrentLogcatDate();
326         doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
327         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
328         assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
329         if (PERFETTO_TESTS_ENABLED) assertThat(isSystemTracingEnabled()).isFalse();
330 
331         doAppBreadcrumbReportedStart(14); // value = 14 > trigger
332         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
333 
334         List<EventMetricData> data = getEventMetricDataList();
335         assertWithMessage("Expected anomaly").that(data).hasSize(1);
336         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
337 
338         // Pool a few times to allow for statsd <-> traced <-> traced_probes communication to happen.
339         if (PERFETTO_TESTS_ENABLED) {
340                 boolean tracingEnabled = false;
341                 for (int i = 0; i < 5; i++) {
342                         if (isSystemTracingEnabled()) {
343                                 tracingEnabled = true;
344                                 break;
345                         }
346                         Thread.sleep(1000);
347                 }
348                 assertThat(tracingEnabled).isTrue();
349         }
350     }
351 
352     // Tests that anomaly detection for gauge works.
testGaugeAnomalyDetection()353     public void testGaugeAnomalyDetection() throws Exception {
354         StatsdConfig.Builder config = getBaseConfig(1, 20, 6 /* threshold: value > 6 */)
355                 .addGaugeMetric(GaugeMetric.newBuilder()
356                         .setId(METRIC_ID)
357                         .setWhat(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
358                         .setBucket(TimeUnit.CTS)
359                         // Get the label field's value into the gauge:
360                         .setGaugeFieldsFilter(
361                                 FieldFilter.newBuilder().setFields(FieldMatcher.newBuilder()
362                                         .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
363                                         .addChild(FieldMatcher.newBuilder()
364                                                 .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
365                                 )
366                         )
367                 );
368         uploadConfig(config);
369 
370         String markTime = getCurrentLogcatDate();
371         doAppBreadcrumbReportedStart(6); // gauge = 6, which is NOT > trigger
372         Thread.sleep(Math.max(WAIT_AFTER_BREADCRUMB_MS, 1_100)); // Must be >1s to push next bucket.
373         assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
374         if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
375 
376         // We waited for >1s above, so we are now in the next bucket (which is essential).
377         doAppBreadcrumbReportedStart(14); // gauge = 14 > trigger
378         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
379 
380         List<EventMetricData> data = getEventMetricDataList();
381         assertWithMessage("Expected anomaly").that(data).hasSize(1);
382         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
383         if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
384     }
385 
386     // Test that anomaly detection for pulled metrics work.
testPulledAnomalyDetection()387     public void testPulledAnomalyDetection() throws Exception {
388         final int ATOM_ID = Atom.DEBUG_ELAPSED_CLOCK_FIELD_NUMBER; // A pulled atom
389         final int VALUE_FIELD =
390                 DebugElapsedClock.ELAPSED_CLOCK_MILLIS_FIELD_NUMBER; // Something that will be > 0.
391         final int ATOM_MATCHER_ID = 300;
392 
393         StatsdConfig.Builder config = getBaseConfig(10, 20, 0 /* threshold: value > 0 */)
394                 .addAllowedLogSource("AID_SYSTEM")
395                 // Track the ATOM_ID pulled atom
396                 .addAtomMatcher(StatsdConfigProto.AtomMatcher.newBuilder()
397                         .setId(ATOM_MATCHER_ID)
398                         .setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
399                                 .setAtomId(ATOM_ID)))
400                 .addGaugeMetric(GaugeMetric.newBuilder()
401                         .setId(METRIC_ID)
402                         .setWhat(ATOM_MATCHER_ID)
403                         .setBucket(TimeUnit.CTS)
404                         .setSamplingType(GaugeMetric.SamplingType.RANDOM_ONE_SAMPLE)
405                         // Track the VALUE_FIELD (anomaly detection requires exactly one field here)
406                         .setGaugeFieldsFilter(
407                                 FieldFilter.newBuilder().setFields(FieldMatcher.newBuilder()
408                                         .setField(ATOM_ID)
409                                         .addChild(FieldMatcher.newBuilder().setField(VALUE_FIELD))
410                                 )
411                         )
412                 );
413         uploadConfig(config);
414 
415         Thread.sleep(6_000); // Wait long enough to ensure AlarmManager signals >= 1 pull
416 
417         List<EventMetricData> data = getEventMetricDataList();
418         assertThat(data.size()).isEqualTo(1);
419         assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
420     }
421 
422 
getBaseConfig(int numBuckets, int refractorySecs, long triggerIfSumGt)423     private final StatsdConfig.Builder getBaseConfig(int numBuckets,
424                                                      int refractorySecs,
425                                                      long triggerIfSumGt) throws Exception {
426       return createConfigBuilder()
427           // Items of relevance for detecting the anomaly:
428           .addAtomMatcher(
429               StatsdConfigProto.AtomMatcher.newBuilder()
430                   .setId(APP_BREADCRUMB_REPORTED_MATCH_START_ID)
431                   .setSimpleAtomMatcher(
432                       StatsdConfigProto.SimpleAtomMatcher.newBuilder()
433                           .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
434                           // Event only when the uid is this app's uid.
435                           .addFieldValueMatcher(createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
436                                                     .setEqInt(getHostUid()))
437                           .addFieldValueMatcher(
438                               createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
439                                   .setEqInt(AppBreadcrumbReported.State.START.ordinal()))))
440           .addAtomMatcher(
441               StatsdConfigProto.AtomMatcher.newBuilder()
442                   .setId(APP_BREADCRUMB_REPORTED_MATCH_STOP_ID)
443                   .setSimpleAtomMatcher(
444                       StatsdConfigProto.SimpleAtomMatcher.newBuilder()
445                           .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
446                           // Event only when the uid is this app's uid.
447                           .addFieldValueMatcher(createFvm(AppBreadcrumbReported.UID_FIELD_NUMBER)
448                                                     .setEqInt(getHostUid()))
449                           .addFieldValueMatcher(
450                               createFvm(AppBreadcrumbReported.STATE_FIELD_NUMBER)
451                                   .setEqInt(AppBreadcrumbReported.State.STOP.ordinal()))))
452           .addAlert(Alert.newBuilder()
453                         .setId(ALERT_ID)
454                         .setMetricId(METRIC_ID) // The metric itself must yet be added by the test.
455                         .setNumBuckets(numBuckets)
456                         .setRefractoryPeriodSecs(refractorySecs)
457                         .setTriggerIfSumGt(triggerIfSumGt))
458           .addSubscription(
459               Subscription.newBuilder()
460                   .setId(SUBSCRIPTION_ID_INCIDENTD)
461                   .setRuleType(Subscription.RuleType.ALERT)
462                   .setRuleId(ALERT_ID)
463                   .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(INCIDENTD_SECTION)))
464           // We want to trigger anomalies on METRIC_ID, but don't want the actual data.
465           .addNoReportMetric(METRIC_ID)
466 
467           // Items of relevance to reporting the anomaly (we do want this data):
468           .addAtomMatcher(
469               StatsdConfigProto.AtomMatcher.newBuilder()
470                   .setId(ANOMALY_DETECT_MATCH_ID)
471                   .setSimpleAtomMatcher(
472                       StatsdConfigProto.SimpleAtomMatcher.newBuilder()
473                           .setAtomId(Atom.ANOMALY_DETECTED_FIELD_NUMBER)
474                           .addFieldValueMatcher(createFvm(AnomalyDetected.CONFIG_UID_FIELD_NUMBER)
475                                                     .setEqInt(getHostUid()))
476                           .addFieldValueMatcher(createFvm(AnomalyDetected.CONFIG_ID_FIELD_NUMBER)
477                                                     .setEqInt(CONFIG_ID))))
478           .addEventMetric(StatsdConfigProto.EventMetric.newBuilder()
479                               .setId(ANOMALY_EVENT_ID)
480                               .setWhat(ANOMALY_DETECT_MATCH_ID));
481     }
482 }
483