|  | // 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 Builder().build(); | 
|  |  | 
|  | public static SpawnMetrics forLocalExecution(Duration wallTime) { | 
|  | return new Builder().setTotalTime(wallTime).setExecutionWallTime(wallTime).build(); | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | private SpawnMetrics(Builder builder) { | 
|  | this.totalTime = builder.totalTime; | 
|  | this.parseTime = builder.parseTime; | 
|  | this.networkTime = builder.networkTime; | 
|  | this.fetchTime = builder.fetchTime; | 
|  | this.remoteQueueTime = builder.remoteQueueTime; | 
|  | this.setupTime = builder.setupTime; | 
|  | this.uploadTime = builder.uploadTime; | 
|  | this.executionWallTime = builder.executionWallTime; | 
|  | this.retryTime = builder.retryTime; | 
|  | this.inputBytes = builder.inputBytes; | 
|  | this.inputFiles = builder.inputFiles; | 
|  | this.memoryEstimateBytes = builder.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<String> 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<String> 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()); | 
|  | } | 
|  |  | 
|  | /** Builder class for SpawnMetrics. */ | 
|  | public static class Builder { | 
|  | private Duration totalTime = Duration.ZERO; | 
|  | private Duration parseTime = Duration.ZERO; | 
|  | private Duration networkTime = Duration.ZERO; | 
|  | private Duration fetchTime = Duration.ZERO; | 
|  | private Duration remoteQueueTime = Duration.ZERO; | 
|  | private Duration setupTime = Duration.ZERO; | 
|  | private Duration uploadTime = Duration.ZERO; | 
|  | private Duration executionWallTime = Duration.ZERO; | 
|  | private Duration retryTime = Duration.ZERO; | 
|  | private long inputBytes = 0; | 
|  | private long inputFiles = 0; | 
|  | private long memoryEstimateBytes = 0; | 
|  |  | 
|  | public SpawnMetrics build() { | 
|  | // TODO(ulfjack): Add consistency checks here? | 
|  | return new SpawnMetrics(this); | 
|  | } | 
|  |  | 
|  | public Builder setTotalTime(Duration totalTime) { | 
|  | this.totalTime = totalTime; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setParseTime(Duration parseTime) { | 
|  | this.parseTime = parseTime; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setNetworkTime(Duration networkTime) { | 
|  | this.networkTime = networkTime; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setFetchTime(Duration fetchTime) { | 
|  | this.fetchTime = fetchTime; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setRemoteQueueTime(Duration remoteQueueTime) { | 
|  | this.remoteQueueTime = remoteQueueTime; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setSetupTime(Duration setupTime) { | 
|  | this.setupTime = setupTime; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setUploadTime(Duration uploadTime) { | 
|  | this.uploadTime = uploadTime; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setExecutionWallTime(Duration executionWallTime) { | 
|  | this.executionWallTime = executionWallTime; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setRetryTime(Duration retryTime) { | 
|  | this.retryTime = retryTime; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setInputBytes(long inputBytes) { | 
|  | this.inputBytes = inputBytes; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setInputFiles(long inputFiles) { | 
|  | this.inputFiles = inputFiles; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | public Builder setMemoryEstimateBytes(long memoryEstimateBytes) { | 
|  | this.memoryEstimateBytes = memoryEstimateBytes; | 
|  | return this; | 
|  | } | 
|  | } | 
|  | } |