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