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 17 package androidx.benchmark 18 19 import android.os.Bundle 20 import androidx.annotation.RestrictTo 21 import kotlin.math.pow 22 import kotlin.math.sqrt 23 24 /** 25 * Results for a given metric from a benchmark, including each measurement made and general stats 26 * for those measurements (min/median/max). 27 */ 28 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 29 public class MetricResult( 30 val name: String, 31 val data: List<Double>, 32 val iterationData: List<List<Double>>? = null 33 ) { 34 val median: Double 35 val medianIndex: Int 36 val min: Double 37 val minIndex: Int 38 val max: Double 39 val maxIndex: Int 40 val standardDeviation: Double 41 val coefficientOfVariation: Double 42 43 val p50: Double 44 val p90: Double 45 val p95: Double 46 val p99: Double 47 48 init { 49 val values = data.sorted() 50 val size = values.size <lambda>null51 require(size >= 1) { "At least one result is necessary, $size found for $name." } 52 53 val mean: Double = data.average() 54 min = values.first() 55 max = values.last() 56 median = getPercentile(values, 50) 57 58 p50 = getPercentile(values, 50) 59 p90 = getPercentile(values, 90) 60 p95 = getPercentile(values, 95) 61 p99 = getPercentile(values, 99) 62 <lambda>null63 minIndex = data.indexOfFirst { it == min } <lambda>null64 maxIndex = data.indexOfFirst { it == max } 65 medianIndex = data.size / 2 66 67 standardDeviation = 68 if (data.size == 1) { 69 0.0 70 } else { <lambda>null71 val sum = values.map { (it - mean).pow(2) }.sum() 72 sqrt(sum / (size - 1).toDouble()) 73 } 74 coefficientOfVariation = 75 if (mean == 0.0) { 76 0.0 77 } else { 78 standardDeviation / mean 79 } 80 } 81 getSummarynull82 internal fun getSummary(): String { 83 return "Metric ($name) results: median $median, min $min, max $max, " + 84 "standardDeviation: $standardDeviation" 85 } 86 putInBundlenull87 public fun putInBundle(status: Bundle, prefix: String) { 88 // format string to be in instrumentation results format 89 val bundleName = name.toOutputMetricName() 90 91 status.putDouble("${prefix}${bundleName}_min", min) 92 status.putDouble("${prefix}${bundleName}_median", median) 93 status.putDouble("${prefix}${bundleName}_stddev", standardDeviation) 94 } 95 putPercentilesInBundlenull96 public fun putPercentilesInBundle(status: Bundle, prefix: String) { 97 // format string to be in instrumentation results format 98 val bundleName = name.toOutputMetricName() 99 100 status.putDouble("${prefix}${bundleName}_p50", p50) 101 status.putDouble("${prefix}${bundleName}_p90", p90) 102 status.putDouble("${prefix}${bundleName}_p95", p95) 103 status.putDouble("${prefix}${bundleName}_p99", p99) 104 } 105 106 // NOTE: Studio-generated, re-generate if members change equalsnull107 override fun equals(other: Any?): Boolean { 108 if (this === other) return true 109 if (javaClass != other?.javaClass) return false 110 111 other as MetricResult 112 113 if (name != other.name) return false 114 if (median != other.median) return false 115 if (medianIndex != other.medianIndex) return false 116 if (min != other.min) return false 117 if (minIndex != other.minIndex) return false 118 if (max != other.max) return false 119 if (maxIndex != other.maxIndex) return false 120 if (standardDeviation != other.standardDeviation) return false 121 122 return true 123 } 124 hashCodenull125 override fun hashCode(): Int { 126 var result = name.hashCode() 127 result = 31 * result + median.hashCode() 128 result = 31 * result + medianIndex 129 result = 31 * result + min.hashCode() 130 result = 31 * result + minIndex 131 result = 31 * result + max.hashCode() 132 result = 31 * result + maxIndex 133 result = 31 * result + standardDeviation.hashCode() 134 return result 135 } 136 137 companion object { lerpnull138 internal fun lerp(a: Double, b: Double, ratio: Double): Double { 139 return (a * (1 - ratio) + b * (ratio)) 140 } 141 getPercentilenull142 fun getPercentile(sortedData: List<Double>, percentile: Int): Double { 143 val idealIndex = percentile.coerceIn(0, 100) / 100.0 * (sortedData.size - 1) 144 val firstIndex = idealIndex.toInt() 145 val secondIndex = firstIndex + 1 146 147 val firstValue = sortedData[firstIndex] 148 val secondValue = sortedData.getOrElse(secondIndex) { firstValue } 149 return lerp(firstValue, secondValue, idealIndex - firstIndex) 150 } 151 } 152 } 153