// Copyright 2018 The Bazel Authors. All rights reserved. // // 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.google.devtools.build.lib.actions; import com.google.common.base.Joiner; import java.time.Duration; import java.util.ArrayList; import java.util.List; /** Timing, size, and memory statistics for a Spawn execution. */ public final class SpawnMetrics { /** Any non important stats < than 10% will not be shown in the summary. */ private static final double STATS_SHOW_THRESHOLD = 0.10; /** Represents a zero cost/null statistic. */ public static final SpawnMetrics EMPTY = new SpawnMetrics( Duration.ZERO, Duration.ZERO, Duration.ZERO, Duration.ZERO, Duration.ZERO, Duration.ZERO, Duration.ZERO, Duration.ZERO, Duration.ZERO, 0L, 0L, 0L); public static SpawnMetrics forLocalExecution(Duration wallTime) { return new SpawnMetrics( wallTime, Duration.ZERO, Duration.ZERO, Duration.ZERO, Duration.ZERO, Duration.ZERO, Duration.ZERO, wallTime, Duration.ZERO, 0L, 0L, 0L); } private final Duration totalTime; private final Duration parseTime; private final Duration fetchTime; private final Duration remoteQueueTime; private final Duration uploadTime; private final Duration setupTime; private final Duration executionWallTime; private final Duration retryTime; private final Duration networkTime; private final long inputBytes; private final long inputFiles; private final long memoryEstimateBytes; public SpawnMetrics( Duration totalTime, Duration parseTime, Duration networkTime, Duration fetchTime, Duration remoteQueueTime, Duration setupTime, Duration uploadTime, Duration executionWallTime, Duration retryTime, long inputBytes, long inputFiles, long memoryEstimateBytes) { this.totalTime = totalTime; this.parseTime = parseTime; this.networkTime = networkTime; this.fetchTime = fetchTime; this.remoteQueueTime = remoteQueueTime; this.setupTime = setupTime; this.uploadTime = uploadTime; this.executionWallTime = executionWallTime; this.retryTime = retryTime; this.inputBytes = inputBytes; this.inputFiles = inputFiles; this.memoryEstimateBytes = memoryEstimateBytes; } /** * Total (measured locally) wall time spent running a spawn. This should be at least as large as * all the other times summed together. */ public Duration totalTime() { return totalTime; } /** * Total time spent getting on network. This includes time getting network-side errors and the * time of the round-trip, found by taking the difference of wall time here and the server time * reported by the RPC. This is 0 for locally executed spawns. */ public Duration networkTime() { return networkTime; } /** * Total time waiting in remote queues. Includes queue time for any failed attempts. This is 0 for * locally executed spawns. */ public Duration remoteQueueTime() { return remoteQueueTime; } /** The time spent transferring files to the backends. This is 0 for locally executed spawns. */ public Duration uploadTime() { return uploadTime; } /** * The time required to setup the environment in which the spawn is run. This may be 0 for locally * executed spawns, or may include time to setup a sandbox or other environment. Does not include * failed attempts. */ public Duration setupTime() { return setupTime; } /** Time spent running the subprocess. */ public Duration executionWallTime() { return executionWallTime; } /** * The time taken to convert the spawn into a network request, e.g., collecting runfiles, and * digests for all input files. */ public Duration parseTime() { return parseTime; } /** Total time spent fetching remote outputs. */ public Duration fetchTime() { return fetchTime; } /** Time spent in previous failed attempts. Does not include queue time. */ public Duration retryTime() { return retryTime; } /** Any time that is not measured by a more specific component, out of {@code totalTime()}. */ public Duration otherTime() { return totalTime .minus(parseTime) .minus(networkTime) .minus(remoteQueueTime) .minus(uploadTime) .minus(setupTime) .minus(executionWallTime) .minus(fetchTime) .minus(retryTime); } /** Total size in bytes of inputs or 0 if unavailable. */ public long inputBytes() { return inputBytes; } /** Total number of input files or 0 if unavailable. */ public long inputFiles() { return inputFiles; } /** Estimated memory usage or 0 if unavailable. */ public long memoryEstimate() { return memoryEstimateBytes; } /** * Generates a String representation of the stats. * * @param total total time used to compute the percentages * @param summary whether to exclude input file count and sizes, and memory estimates */ public String toString(Duration total, boolean summary) { StringBuilder sb = new StringBuilder(); sb.append("("); sb.append(prettyPercentage(totalTime, total)); sb.append(" of the time): ["); List stats = new ArrayList<>(8); addStatToString(stats, "parse", !summary, parseTime, total); addStatToString(stats, "queue", true, remoteQueueTime, total); addStatToString(stats, "network", !summary, networkTime, total); addStatToString(stats, "upload", !summary, uploadTime, total); addStatToString(stats, "setup", true, setupTime, total); addStatToString(stats, "process", true, executionWallTime, total); addStatToString(stats, "fetch", !summary, fetchTime, total); addStatToString(stats, "retry", !summary, retryTime, total); addStatToString(stats, "other", !summary, otherTime(), total); if (!summary) { stats.add("input files: " + inputFiles); stats.add("input bytes: " + inputBytes); stats.add("memory bytes: " + memoryEstimateBytes); } Joiner.on(", ").appendTo(sb, stats); sb.append("]"); return sb.toString(); } /** * Add to {@code strings} the string representation of {@code name} component. If {@code * forceShow} is set to false it will only show if it is above certain threshold. */ private static void addStatToString( List strings, String name, boolean forceShow, Duration time, Duration totalTime) { if (forceShow || isAboveThreshold(time, totalTime)) { strings.add(name + ": " + prettyPercentage(time, totalTime)); } } private static boolean isAboveThreshold(Duration time, Duration totalTime) { return totalTime.toMillis() > 0 && (((float) time.toMillis() / totalTime.toMillis()) >= STATS_SHOW_THRESHOLD); } /** * Converts relative duration to the percentage string. * * @return formatted percentage string or "N/A" if result is undefined */ private static String prettyPercentage(Duration duration, Duration total) { // Duration.toMillis() != 0 does not imply !Duration.isZero() (due to truncation). if (total.toMillis() == 0) { // Return "not available" string if total is 0 and result is undefined. return "N/A"; } return String.format("%.2f%%", duration.toMillis() * 100.0 / total.toMillis()); } }