Home | History | Annotate | Download | only in metricregression
      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 com.android.tradefed.testtype.metricregression;
     17 
     18 import com.android.tradefed.log.LogUtil.CLog;
     19 import com.android.tradefed.result.TestDescription;
     20 import com.android.tradefed.util.MetricsXmlParser;
     21 import com.android.tradefed.util.MultiMap;
     22 import com.android.tradefed.util.Pair;
     23 
     24 import com.google.common.annotations.VisibleForTesting;
     25 
     26 /** A metrics object to hold run metrics and test metrics parsed by {@link MetricsXmlParser} */
     27 public class Metrics {
     28     private int mNumRuns;
     29     private int mNumTests = -1;
     30     private final boolean mStrictMode;
     31     private final MultiMap<String, Double> mRunMetrics = new MultiMap<>();
     32     private final MultiMap<Pair<TestDescription, String>, Double> mTestMetrics = new MultiMap<>();
     33 
     34     /** Throw when metrics validation fails in strict mode. */
     35     public static class MetricsException extends RuntimeException {
     36         MetricsException(String cause) {
     37             super(cause);
     38         }
     39     }
     40 
     41     /**
     42      * Constructs an empty Metrics object.
     43      *
     44      * @param strictMode whether exception should be thrown when validation fails
     45      */
     46     public Metrics(boolean strictMode) {
     47         mStrictMode = strictMode;
     48     }
     49 
     50     /**
     51      * Sets the number of tests. This method also checks if each call sets the same number of test,
     52      * since this number should be consistent across multiple runs.
     53      *
     54      * @param numTests the number of tests
     55      * @throws MetricsException if subsequent calls set a different number.
     56      */
     57     public void setNumTests(int numTests) {
     58         if (mNumTests == -1) {
     59             mNumTests = numTests;
     60         } else {
     61             if (mNumTests != numTests) {
     62                 String msg =
     63                         String.format(
     64                                 "Number of test entries differ: expect #%d actual #%d",
     65                                 mNumTests, numTests);
     66                 throw new MetricsException(msg);
     67             }
     68         }
     69     }
     70 
     71     /**
     72      * Adds a run metric.
     73      *
     74      * @param name metric name
     75      * @param value metric value
     76      */
     77     public void addRunMetric(String name, String value) {
     78         try {
     79             mRunMetrics.put(name, Double.parseDouble(value));
     80         } catch (NumberFormatException e) {
     81             // This is normal. We often get some string metrics like device name. Just log it.
     82             CLog.w(String.format("Run metric \"%s\" is not a number: \"%s\"", name, value));
     83         }
     84     }
     85 
     86     /**
     87      * Adds a test metric.
     88      *
     89      * @param testId TestDescription of the metric
     90      * @param name metric name
     91      * @param value metric value
     92      */
     93     public void addTestMetric(TestDescription testId, String name, String value) {
     94         Pair<TestDescription, String> metricId = new Pair<>(testId, name);
     95         try {
     96             mTestMetrics.put(metricId, Double.parseDouble(value));
     97         } catch (NumberFormatException e) {
     98             // This is normal. We often get some string metrics like device name. Just log it.
     99             CLog.w(
    100                     String.format(
    101                             "Test %s metric \"%s\" is not a number: \"%s\"", testId, name, value));
    102         }
    103     }
    104 
    105     /**
    106      * Validates that the number of entries of each metric equals to the number of runs.
    107      *
    108      * @param numRuns number of runs
    109      * @throws MetricsException when validation fails in strict mode
    110      */
    111     public void validate(int numRuns) {
    112         mNumRuns = numRuns;
    113         for (String name : mRunMetrics.keySet()) {
    114             if (mRunMetrics.get(name).size() < mNumRuns) {
    115                 error(
    116                         String.format(
    117                                 "Run metric \"%s\" too few entries: expected #%d actual #%d",
    118                                 name, mNumRuns, mRunMetrics.get(name).size()));
    119             }
    120         }
    121         for (Pair<TestDescription, String> id : mTestMetrics.keySet()) {
    122             if (mTestMetrics.get(id).size() < mNumRuns) {
    123                 error(
    124                         String.format(
    125                                 "Test %s metric \"%s\" too few entries: expected #%d actual #%d",
    126                                 id.first, id.second, mNumRuns, mTestMetrics.get(id).size()));
    127             }
    128         }
    129     }
    130 
    131     /**
    132      * Validates with after-patch Metrics object. Make sure two metrics object contain same run
    133      * metric entries and test metric entries. Assume this object contains before-patch metrics.
    134      *
    135      * @param after a Metrics object containing after-patch metrics
    136      * @throws MetricsException when cross validation fails in strict mode
    137      */
    138     public void crossValidate(Metrics after) {
    139         if (mNumTests != after.mNumTests) {
    140             error(
    141                     String.format(
    142                             "Number of test entries differ: before #%d after #%d",
    143                             mNumTests, after.mNumTests));
    144         }
    145 
    146         for (String name : mRunMetrics.keySet()) {
    147             if (!after.mRunMetrics.containsKey(name)) {
    148                 warn(String.format("Run metric \"%s\" only in before-patch run.", name));
    149             }
    150         }
    151 
    152         for (String name : after.mRunMetrics.keySet()) {
    153             if (!mRunMetrics.containsKey(name)) {
    154                 warn(String.format("Run metric \"%s\" only in after-patch run.", name));
    155             }
    156         }
    157 
    158         for (Pair<TestDescription, String> id : mTestMetrics.keySet()) {
    159             if (!after.mTestMetrics.containsKey(id)) {
    160                 warn(
    161                         String.format(
    162                                 "Test %s metric \"%s\" only in before-patch run.",
    163                                 id.first, id.second));
    164             }
    165         }
    166 
    167         for (Pair<TestDescription, String> id : after.mTestMetrics.keySet()) {
    168             if (!mTestMetrics.containsKey(id)) {
    169                 warn(
    170                         String.format(
    171                                 "Test %s metric \"%s\" only in after-patch run.",
    172                                 id.first, id.second));
    173             }
    174         }
    175     }
    176 
    177     @VisibleForTesting
    178     void error(String msg) {
    179         if (mStrictMode) {
    180             throw new MetricsException(msg);
    181         } else {
    182             CLog.e(msg);
    183         }
    184     }
    185 
    186     @VisibleForTesting
    187     void warn(String msg) {
    188         if (mStrictMode) {
    189             throw new MetricsException(msg);
    190         } else {
    191             CLog.w(msg);
    192         }
    193     }
    194 
    195     /**
    196      * Gets the number of test runs stored in this object.
    197      *
    198      * @return number of test runs
    199      */
    200     public int getNumRuns() {
    201         return mNumRuns;
    202     }
    203 
    204     /**
    205      * Gets the number of tests stored in this object.
    206      *
    207      * @return number of tests
    208      */
    209     public int getNumTests() {
    210         return mNumTests;
    211     }
    212 
    213     /**
    214      * Gets all run metrics stored in this object.
    215      *
    216      * @return a {@link MultiMap} from test name String to Double
    217      */
    218     public MultiMap<String, Double> getRunMetrics() {
    219         return mRunMetrics;
    220     }
    221 
    222     /**
    223      * Gets all test metrics stored in this object.
    224      *
    225      * @return a {@link MultiMap} from (TestDescription, test name) pair to Double
    226      */
    227     public MultiMap<Pair<TestDescription, String>, Double> getTestMetrics() {
    228         return mTestMetrics;
    229     }
    230 }
    231