IdeSummaryString.kt

/*
 * Copyright 2021 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 androidx.benchmark.macro

import androidx.benchmark.Outputs
import androidx.benchmark.Stats
import java.util.Collections
import kotlin.math.max

/**
 * Returns a pair of ideSummaryStrings - v1 (pre Arctic-fox) and v2 (Arctic-fox+)
 *
 * These strings are to be displayed in Studio, depending on the version.
 *
 * The V2 string embeds links to trace files relative to the output path sent to the IDE via
 * `[link](file://<relative/path/to/trace>)`
 *
 * @see androidx.benchmark.InstrumentationResultScope#ideSummaryRecord
 */
internal fun ideSummaryStrings(
    warningLines: String,
    benchmarkName: String,
    statsList: List<Stats>,
    absoluteTracePaths: List<String>
): Pair<String, String> {
    require(statsList.isNotEmpty()) { "Require non-empty list of stats." }

    val maxLabelLength = Collections.max(statsList.map { it.name.length })

    // max string length of any printed min/median/max is the largest max value seen. used to pad.
    val maxValueLength = statsList
        .map { it.max }
        .reduce { acc, maxValue -> max(acc, maxValue) }
        .toString().length

    fun ideSummaryString(
        transform: (name: String, min: String, median: String, max: String, stats: Stats) -> String
    ): String {
        return warningLines + benchmarkName + "\n" + statsList.joinToString("\n") {
            transform(
                it.name.padStart(maxLabelLength),
                it.min.toString().padStart(maxValueLength),
                it.median.toString().padStart(maxValueLength),
                it.max.toString().padStart(maxValueLength),
                it
            )
        } + "\n"
    }
    val relativeTracePaths = absoluteTracePaths.map { absolutePath ->
        Outputs.relativePathFor(absolutePath)
    }
    return Pair(
        first = ideSummaryString { name, min, median, max, _ ->
            "  $name   min $min,   median $median,   max $max"
        },
        second = ideSummaryString { name, min, median, max, stats ->
            "  $name" +
                "   [min $min](file://${relativeTracePaths[stats.minIndex]})," +
                "   [median $median](file://${relativeTracePaths[stats.medianIndex]})," +
                "   [max $max](file://${relativeTracePaths[stats.maxIndex]})"
        } + "    Traces: Iteration " + relativeTracePaths.mapIndexed { index, path ->
            "[$index](file://$path)"
        }.joinToString(separator = " ") + "\n"
    )
}