MetricsContainer.kt

/*
 * Copyright 2020 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

import android.util.Log

internal class MetricsContainer(
    /**
     * Each MetricCapture represents a single metric to be captured. It is possible this may change.
     *
     * Metrics are usually indexed by the names provided for them by the MetricCaptures, or an index
     */
    private val metrics: Array<MetricCapture> = arrayOf(TimeCapture()),
    private val REPEAT_COUNT: Int
) {

    internal val data: Array<LongArray> = Array(metrics.size) {
        LongArray(REPEAT_COUNT)
    }
    private var runNum: Int = 0

    /**
     * Sets up the parameters for this benchmark, and clears leftover data.
     *
     * Call when initializing a benchmark.
     */
    fun captureInit() {
        runNum = 0
    }

    /**
     * Starts collecting data for a run.
     *
     * Must be called at the start of each run.
     */
    fun captureStart() {
        for (i in 0..metrics.lastIndex) {
            metrics[i].captureStart() // put the most sensitive benchmark last to avoid overhead
        }
    }

    /**
     * Marks the end of a run, and stores the metric value changes since the last start.
     *
     * Should be called when a run stops.
     */
    fun captureStop() {
        for (i in metrics.lastIndex downTo 0) { // stop in reverse order
            data[i][runNum] = metrics[i].captureStop()
        }
        runNum += 1
    }

    /**
     * Pauses data collection.
     *
     * Call when you want to not capture the following part of a run.
     */
    fun capturePaused() {
        for (i in metrics.lastIndex downTo 0) { // like stop, pause in reverse order
            metrics[metrics.lastIndex - i].capturePaused()
        }
    }

    /**
     * Resumes data collection.
     *
     * Call when you want to resume capturing a capturePaused-ed run.
     */
    fun captureResumed() {
        for (i in 0..metrics.lastIndex) {
            metrics[i].captureResumed()
        }
    }

    /**
     * Finishes and cleans up a benchmark, and returns statistics about all that benchmark's data.
     *
     * Call exactly once at the end of a benchmark.
     */
    fun captureFinished(maxIterations: Int): List<Stats> {
        for (eachArray in data) { // do this at the end for efficiency
            for (i in 0..eachArray.lastIndex) {
                eachArray[i] = eachArray[i] / maxIterations
            }
        }
        doLog()
        return metrics.mapIndexed { i, it -> Stats(data[i], it.name) }
    }

    private fun doLog() {
        metrics.forEachIndexed { i, metric -> data[i].toList().chunked(10)
            .forEachIndexed { chunkNum, chunk ->
                Log.d(
                    BenchmarkState.TAG, metric.name + "[%2d:%2d]: %s".format(
                        chunkNum * 10,
                        (chunkNum + 1) * 10,
                        chunk.joinToString()
                    )
                )
            }
        }
    }
}