content: /*
   * Copyright (C) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.android.statsd.loadtest;
  
  import android.content.Context;
  import android.content.res.Resources;
  import android.util.Log;
  
  import com.android.internal.os.StatsdConfigProto.Predicate;
  import com.android.internal.os.StatsdConfigProto.CountMetric;
  import com.android.internal.os.StatsdConfigProto.DurationMetric;
  import com.android.internal.os.StatsdConfigProto.MetricConditionLink;
  import com.android.internal.os.StatsdConfigProto.EventMetric;
  import com.android.internal.os.StatsdConfigProto.GaugeMetric;
  import com.android.internal.os.StatsdConfigProto.ValueMetric;
  import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
  import com.android.internal.os.StatsdConfigProto.AtomMatcher;
  import com.android.internal.os.StatsdConfigProto.SimplePredicate;
  import com.android.internal.os.StatsdConfigProto.StatsdConfig;
  import com.android.internal.os.StatsdConfigProto.TimeUnit;
  
  import java.io.InputStream;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * Creates StatsdConfig protos for loadtesting.
   */
  public class ConfigFactory {
      public static class ConfigMetadata {
          public final byte[] bytes;
          public final int numMetrics;
  
          public ConfigMetadata(byte[] bytes, int numMetrics) {
              this.bytes = bytes;
              this.numMetrics = numMetrics;
          }
      }
  
      public static final long CONFIG_ID = 123456789;
  
      private static final String TAG = "loadtest.ConfigFactory";
  
      private final StatsdConfig mTemplate;
  
      public ConfigFactory(Context context) {
          // Read the config template from the resoures.
          Resources res = context.getResources();
          byte[] template = null;
          StatsdConfig templateProto = null;
          try {
              InputStream inputStream = res.openRawResource(R.raw.loadtest_config);
              template = new byte[inputStream.available()];
              inputStream.read(template);
              templateProto = StatsdConfig.parseFrom(template);
          } catch (IOException e) {
              Log.e(TAG, "Unable to read or parse loadtest config template. Using an empty config.");
          }
          mTemplate = templateProto == null ? StatsdConfig.newBuilder().build() : templateProto;
  
          Log.d(TAG, "Loadtest template config: " + mTemplate);
      }
  
      /**
       * Generates a config.
       *
       * All configs are based on the same template.
       * That template is designed to make the most use of the set of atoms that {@code SequencePusher}
       * pushes, and to exercise as many of the metrics features as possible.
       * Furthermore, by passing a replication factor to this method, one can artificially inflate
       * the number of metrics in the config. One can also adjust the bucket size for aggregate
       * metrics.
       *
       * @param replication The number of times each metric is replicated in the config.
       *        If the config template has n metrics, the generated config will have n * replication
       *        ones
       * @param bucketMillis The bucket size, in milliseconds, for aggregate metrics
       * @param placebo If true, only return an empty config
       * @return The serialized config and the number of metrics.
       */
      public ConfigMetadata getConfig(int replication, TimeUnit bucket, boolean placebo,
              boolean includeCount, boolean includeDuration, boolean includeEvent,
              boolean includeValue, boolean includeGauge) {
          StatsdConfig.Builder config = StatsdConfig.newBuilder()
              .setId(CONFIG_ID);
          if (placebo) {
            replication = 0;  // Config will be empty, aside from a name.
          }
          int numMetrics = 0;
          for (int i = 0; i < replication; i++) {
              // metrics
              if (includeEvent) {
                  for (EventMetric metric : mTemplate.getEventMetricList()) {
                      addEventMetric(metric, i, config);
                      numMetrics++;
                  }
              }
              if (includeCount) {
                  for (CountMetric metric : mTemplate.getCountMetricList()) {
                      addCountMetric(metric, i, bucket, config);
                      numMetrics++;
                  }
              }
              if (includeDuration) {
                  for (DurationMetric metric : mTemplate.getDurationMetricList()) {
                      addDurationMetric(metric, i, bucket, config);
                      numMetrics++;
                  }
              }
              if (includeGauge) {
                  for (GaugeMetric metric : mTemplate.getGaugeMetricList()) {
                      addGaugeMetric(metric, i, bucket, config);
                      numMetrics++;
                  }
              }
              if (includeValue) {
                  for (ValueMetric metric : mTemplate.getValueMetricList()) {
                      addValueMetric(metric, i, bucket, config);
                      numMetrics++;
                  }
              }
              // predicates
              for (Predicate predicate : mTemplate.getPredicateList()) {
                addPredicate(predicate, i, config);
              }
              // matchers
              for (AtomMatcher matcher : mTemplate.getAtomMatcherList()) {
                addMatcher(matcher, i, config);
              }
          }
  
          Log.d(TAG, "Loadtest config is : " + config.build());
          Log.d(TAG, "Generated config has " + numMetrics + " metrics");
  
          return new ConfigMetadata(config.build().toByteArray(), numMetrics);
      }
  
      /**
       * Creates {@link MetricConditionLink}s that are identical to the one passed to this method,
       * except that the names are appended with the provided suffix.
       */
      private List<MetricConditionLink> getLinks(
          List<MetricConditionLink> links, int suffix) {
          List<MetricConditionLink> newLinks = new ArrayList();
          for (MetricConditionLink link : links) {
              newLinks.add(link.toBuilder()
                  .setCondition(link.getCondition() + suffix)
                  .build());
          }
          return newLinks;
      }
  
      /**
       * Creates an {@link EventMetric} based on the template. Makes sure that all names are appended
       * with the provided suffix. Then adds that metric to the config.
       */
      private void addEventMetric(EventMetric template, int suffix, StatsdConfig.Builder config) {
          EventMetric.Builder metric = template.toBuilder()
              .setId(template.getId() + suffix)
              .setWhat(template.getWhat() + suffix);
          if (template.hasCondition()) {
              metric.setCondition(template.getCondition() + suffix);
          }
          if (template.getLinksCount() > 0) {
              List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
              metric.clearLinks();
              metric.addAllLinks(links);
          }
          config.addEventMetric(metric);
      }
  
      /**
       * Creates a {@link CountMetric} based on the template. Makes sure that all names are appended
       * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
       */
      private void addCountMetric(CountMetric template, int suffix, TimeUnit bucket,
          StatsdConfig.Builder config) {
          CountMetric.Builder metric = template.toBuilder()
              .setId(template.getId() + suffix)
              .setWhat(template.getWhat() + suffix);
          if (template.hasCondition()) {
              metric.setCondition(template.getCondition() + suffix);
          }
          if (template.getLinksCount() > 0) {
              List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
              metric.clearLinks();
              metric.addAllLinks(links);
          }
          metric.setBucket(bucket);
          config.addCountMetric(metric);
      }
  
      /**
       * Creates a {@link DurationMetric} based on the template. Makes sure that all names are appended
       * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
       */
      private void addDurationMetric(DurationMetric template, int suffix, TimeUnit bucket,
          StatsdConfig.Builder config) {
          DurationMetric.Builder metric = template.toBuilder()
              .setId(template.getId() + suffix)
              .setWhat(template.getWhat() + suffix);
          if (template.hasCondition()) {
              metric.setCondition(template.getCondition() + suffix);
          }
          if (template.getLinksCount() > 0) {
              List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
              metric.clearLinks();
              metric.addAllLinks(links);
          }
          metric.setBucket(bucket);
          config.addDurationMetric(metric);
      }
  
      /**
       * Creates a {@link GaugeMetric} based on the template. Makes sure that all names are appended
       * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
       */
      private void addGaugeMetric(GaugeMetric template, int suffix, TimeUnit bucket,
          StatsdConfig.Builder config) {
          GaugeMetric.Builder metric = template.toBuilder()
              .setId(template.getId() + suffix)
              .setWhat(template.getWhat() + suffix);
          if (template.hasCondition()) {
              metric.setCondition(template.getCondition() + suffix);
          }
          if (template.getLinksCount() > 0) {
              List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
              metric.clearLinks();
              metric.addAllLinks(links);
          }
          metric.setBucket(bucket);
          config.addGaugeMetric(metric);
      }
  
      /**
       * Creates a {@link ValueMetric} based on the template. Makes sure that all names are appended
       * with the provided suffix, and overrides the bucket size. Then adds that metric to the config.
       */
      private void addValueMetric(ValueMetric template, int suffix, TimeUnit bucket,
          StatsdConfig.Builder config) {
          ValueMetric.Builder metric = template.toBuilder()
              .setId(template.getId() + suffix)
              .setWhat(template.getWhat() + suffix);
          if (template.hasCondition()) {
              metric.setCondition(template.getCondition() + suffix);
          }
          if (template.getLinksCount() > 0) {
              List<MetricConditionLink> links = getLinks(template.getLinksList(), suffix);
              metric.clearLinks();
              metric.addAllLinks(links);
          }
          metric.setBucket(bucket);
          config.addValueMetric(metric);
      }
  
      /**
       * Creates a {@link Predicate} based on the template. Makes sure that all names
       * are appended with the provided suffix. Then adds that predicate to the config.
       */
      private void addPredicate(Predicate template, int suffix, StatsdConfig.Builder config) {
          Predicate.Builder predicate = template.toBuilder()
              .setId(template.getId() + suffix);
          if (template.hasCombination()) {
              Predicate.Combination.Builder cb = template.getCombination().toBuilder()
                  .clearPredicate();
              for (long child : template.getCombination().getPredicateList()) {
                  cb.addPredicate(child + suffix);
              }
              predicate.setCombination(cb.build());
          }
          if (template.hasSimplePredicate()) {
              SimplePredicate.Builder sc = template.getSimplePredicate().toBuilder()
                  .setStart(template.getSimplePredicate().getStart() + suffix)
                  .setStop(template.getSimplePredicate().getStop() + suffix);
              if (template.getSimplePredicate().hasStopAll()) {
                  sc.setStopAll(template.getSimplePredicate().getStopAll() + suffix);
              }
              predicate.setSimplePredicate(sc.build());
          }
          config.addPredicate(predicate);
      }
  
      /**
       * Creates a {@link AtomMatcher} based on the template. Makes sure that all names
       * are appended with the provided suffix. Then adds that matcher to the config.
       */
      private void addMatcher(AtomMatcher template, int suffix, StatsdConfig.Builder config) {
          AtomMatcher.Builder matcher = template.toBuilder()
              .setId(template.getId() + suffix);
          if (template.hasCombination()) {
              AtomMatcher.Combination.Builder cb = template.getCombination().toBuilder()
                  .clearMatcher();
              for (long child : template.getCombination().getMatcherList()) {
                  cb.addMatcher(child + suffix);
              }
              matcher.setCombination(cb);
          }
          config.addAtomMatcher(matcher);
      }
  }
  