1/*
2 * Copyright (C) 2017 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 */
16package com.android.statsd.loadtest;
17
18import android.content.Context;
19import android.content.res.Resources;
20import android.util.Log;
21
22import com.android.internal.os.StatsdConfigProto.Predicate;
23import com.android.internal.os.StatsdConfigProto.CountMetric;
24import com.android.internal.os.StatsdConfigProto.DurationMetric;
25import com.android.internal.os.StatsdConfigProto.MetricConditionLink;
26import com.android.internal.os.StatsdConfigProto.EventMetric;
27import com.android.internal.os.StatsdConfigProto.GaugeMetric;
28import com.android.internal.os.StatsdConfigProto.ValueMetric;
29import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
30import com.android.internal.os.StatsdConfigProto.AtomMatcher;
31import com.android.internal.os.StatsdConfigProto.SimplePredicate;
32import com.android.internal.os.StatsdConfigProto.StatsdConfig;
33import com.android.internal.os.StatsdConfigProto.TimeUnit;
34
35import java.io.InputStream;
36import java.io.IOException;
37import java.util.ArrayList;
38import java.util.List;
39
40/**
41 * Creates StatsdConfig protos for loadtesting.
42 */
43public class ConfigFactory {
44    public static class ConfigMetadata {
45        public final byte[] bytes;
46        public final int numMetrics;
47
48        public ConfigMetadata(byte[] bytes, int numMetrics) {
49            this.bytes = bytes;
50            this.numMetrics = numMetrics;
51        }
52    }
53
54    public static final long CONFIG_ID = 123456789;
55
56    private static final String TAG = "loadtest.ConfigFactory";
57
58    private final StatsdConfig mTemplate;
59
60    public ConfigFactory(Context context) {
61        // Read the config template from the resoures.
62        Resources res = context.getResources();
63        byte[] template = null;
64        StatsdConfig templateProto = null;
65        try {
66            InputStream inputStream = res.openRawResource(R.raw.loadtest_config);
67            template = new byte[inputStream.available()];
68            inputStream.read(template);
69            templateProto = StatsdConfig.parseFrom(template);
70        } catch (IOException e) {
71            Log.e(TAG, "Unable to read or parse loadtest config template. Using an empty config.");
72        }
73        mTemplate = templateProto == null ? StatsdConfig.newBuilder().build() : templateProto;
74
75        Log.d(TAG, "Loadtest template config: " + mTemplate);
76    }
77
78    /**
79     * Generates a config.
80     *
81     * All configs are based on the same template.
82     * That template is designed to make the most use of the set of atoms that {@code SequencePusher}
83     * pushes, and to exercise as many of the metrics features as possible.
84     * Furthermore, by passing a replication factor to this method, one can artificially inflate
85     * the number of metrics in the config. One can also adjust the bucket size for aggregate
86     * metrics.
87     *
88     * @param replication The number of times each metric is replicated in the config.
89     *        If the config template has n metrics, the generated config will have n * replication
90     *        ones
91     * @param bucketMillis The bucket size, in milliseconds, for aggregate metrics
92     * @param placebo If true, only return an empty config
93     * @return The serialized config and the number of metrics.
94     */
95    public ConfigMetadata getConfig(int replication, TimeUnit bucket, boolean placebo,
96            boolean includeCount, boolean includeDuration, boolean includeEvent,
97            boolean includeValue, boolean includeGauge) {
98        StatsdConfig.Builder config = StatsdConfig.newBuilder()
99            .setId(CONFIG_ID);
100        if (placebo) {
101          replication = 0;  // Config will be empty, aside from a name.
102        }
103        int numMetrics = 0;
104        for (int i = 0; i < replication; i++) {
105            // metrics
106            if (includeEvent) {
107                for (EventMetric metric : mTemplate.getEventMetricList()) {
108                    addEventMetric(metric, i, config);
109                    numMetrics++;
110                }
111            }
112            if (includeCount) {
113                for (CountMetric metric : mTemplate.getCountMetricList()) {
114                    addCountMetric(metric, i, bucket, config);
115                    numMetrics++;
116                }
117            }
118            if (includeDuration) {
119                for (DurationMetric metric : mTemplate.getDurationMetricList()) {
120                    addDurationMetric(metric, i, bucket, config);
121                    numMetrics++;
122                }
123            }
124            if (includeGauge) {
125                for (GaugeMetric metric : mTemplate.getGaugeMetricList()) {
126                    addGaugeMetric(metric, i, bucket, config);
127                    numMetrics++;
128                }
129            }
130            if (includeValue) {
131                for (ValueMetric metric : mTemplate.getValueMetricList()) {
132                    addValueMetric(metric, i, bucket, config);
133                    numMetrics++;
134                }
135            }
136            // predicates
137            for (Predicate predicate : mTemplate.getPredicateList()) {
138              addPredicate(predicate, i, config);
139            }
140            // matchers
141            for (AtomMatcher matcher : mTemplate.getAtomMatcherList()) {
142              addMatcher(matcher, i, config);
143            }
144        }
145
146        Log.d(TAG, "Loadtest config is : " + config.build());
147        Log.d(TAG, "Generated config has " + numMetrics + " metrics");
148
149        return new ConfigMetadata(config.build().toByteArray(), numMetrics);
150    }
151
152    /**
153     * Creates {@link MetricConditionLink}s that are identical to the one passed to this method,
154     * except that the names are appended with the provided suffix.
155     */
156    private List<MetricConditionLink> getLinks(
157        List<MetricConditionLink> links, int suffix) {
158        List<MetricConditionLink> newLinks = new ArrayList();
159        for (MetricConditionLink link : links) {
160            newLinks.add(link.toBuilder()
161                .setCondition(link.getCondition() + suffix)
162                .build());
163        }
164        return newLinks;
165    }
166
167    /**
168     * Creates an {@link EventMetric} based on the template. Makes sure that all names are appended
169     * with the provided suffix. Then adds that metric to the config.
170     */
171    private void addEventMetric(EventMetric template, int suffix, StatsdConfig.Builder config) {
172        EventMetric.Builder metric = template.toBuilder()
173            .setId(template.getId() + suffix)
174            .setWhat(template.getWhat() + suffix);
175        if (template.hasCondition()) {
176            metric.setCondition(template.getCondition() + suffix);
177        }
178        if (template.getLinksCount() > 0) {
179            List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
180            metric.clearLinks();
181            metric.addAllLinks(links);
182        }
183        config.addEventMetric(metric);
184    }
185
186    /**
187     * Creates a {@link CountMetric} based on the template. Makes sure that all names are appended
188     * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
189     */
190    private void addCountMetric(CountMetric template, int suffix, TimeUnit bucket,
191        StatsdConfig.Builder config) {
192        CountMetric.Builder metric = template.toBuilder()
193            .setId(template.getId() + suffix)
194            .setWhat(template.getWhat() + suffix);
195        if (template.hasCondition()) {
196            metric.setCondition(template.getCondition() + suffix);
197        }
198        if (template.getLinksCount() > 0) {
199            List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
200            metric.clearLinks();
201            metric.addAllLinks(links);
202        }
203        metric.setBucket(bucket);
204        config.addCountMetric(metric);
205    }
206
207    /**
208     * Creates a {@link DurationMetric} based on the template. Makes sure that all names are appended
209     * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
210     */
211    private void addDurationMetric(DurationMetric template, int suffix, TimeUnit bucket,
212        StatsdConfig.Builder config) {
213        DurationMetric.Builder metric = template.toBuilder()
214            .setId(template.getId() + suffix)
215            .setWhat(template.getWhat() + suffix);
216        if (template.hasCondition()) {
217            metric.setCondition(template.getCondition() + suffix);
218        }
219        if (template.getLinksCount() > 0) {
220            List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
221            metric.clearLinks();
222            metric.addAllLinks(links);
223        }
224        metric.setBucket(bucket);
225        config.addDurationMetric(metric);
226    }
227
228    /**
229     * Creates a {@link GaugeMetric} based on the template. Makes sure that all names are appended
230     * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
231     */
232    private void addGaugeMetric(GaugeMetric template, int suffix, TimeUnit bucket,
233        StatsdConfig.Builder config) {
234        GaugeMetric.Builder metric = template.toBuilder()
235            .setId(template.getId() + suffix)
236            .setWhat(template.getWhat() + suffix);
237        if (template.hasCondition()) {
238            metric.setCondition(template.getCondition() + suffix);
239        }
240        if (template.getLinksCount() > 0) {
241            List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
242            metric.clearLinks();
243            metric.addAllLinks(links);
244        }
245        metric.setBucket(bucket);
246        config.addGaugeMetric(metric);
247    }
248
249    /**
250     * Creates a {@link ValueMetric} based on the template. Makes sure that all names are appended
251     * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
252     */
253    private void addValueMetric(ValueMetric template, int suffix, TimeUnit bucket,
254        StatsdConfig.Builder config) {
255        ValueMetric.Builder metric = template.toBuilder()
256            .setId(template.getId() + suffix)
257            .setWhat(template.getWhat() + suffix);
258        if (template.hasCondition()) {
259            metric.setCondition(template.getCondition() + suffix);
260        }
261        if (template.getLinksCount() > 0) {
262            List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
263            metric.clearLinks();
264            metric.addAllLinks(links);
265        }
266        metric.setBucket(bucket);
267        config.addValueMetric(metric);
268    }
269
270    /**
271     * Creates a {@link Predicate} based on the template. Makes sure that all names
272     * are appended with the provided suffix. Then adds that predicate to the config.
273     */
274    private void addPredicate(Predicate template, int suffix, StatsdConfig.Builder config) {
275        Predicate.Builder predicate = template.toBuilder()
276            .setId(template.getId() + suffix);
277        if (template.hasCombination()) {
278            Predicate.Combination.Builder cb = template.getCombination().toBuilder()
279                .clearPredicate();
280            for (long child : template.getCombination().getPredicateList()) {
281                cb.addPredicate(child + suffix);
282            }
283            predicate.setCombination(cb.build());
284        }
285        if (template.hasSimplePredicate()) {
286            SimplePredicate.Builder sc = template.getSimplePredicate().toBuilder()
287                .setStart(template.getSimplePredicate().getStart() + suffix)
288                .setStop(template.getSimplePredicate().getStop() + suffix);
289            if (template.getSimplePredicate().hasStopAll()) {
290                sc.setStopAll(template.getSimplePredicate().getStopAll() + suffix);
291            }
292            predicate.setSimplePredicate(sc.build());
293        }
294        config.addPredicate(predicate);
295    }
296
297    /**
298     * Creates a {@link AtomMatcher} based on the template. Makes sure that all names
299     * are appended with the provided suffix. Then adds that matcher to the config.
300     */
301    private void addMatcher(AtomMatcher template, int suffix, StatsdConfig.Builder config) {
302        AtomMatcher.Builder matcher = template.toBuilder()
303            .setId(template.getId() + suffix);
304        if (template.hasCombination()) {
305            AtomMatcher.Combination.Builder cb = template.getCombination().toBuilder()
306                .clearMatcher();
307            for (long child : template.getCombination().getMatcherList()) {
308                cb.addMatcher(child + suffix);
309            }
310            matcher.setCombination(cb);
311        }
312        config.addAtomMatcher(matcher);
313    }
314}
315