blob: c93915a58e28b5b078f1bae6115509a4c5db8d2c [file] [log] [blame]
// Copyright 2020 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.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.time.Duration;
import java.util.EnumMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.ToLongFunction;
import javax.annotation.Nullable;
/** Metrics aggregated per execution kind. */
@SuppressWarnings("GoodTime") // Use ints instead of Durations to improve build time (cl/505728570)
public final class AggregatedSpawnMetrics {
public static final AggregatedSpawnMetrics EMPTY = new AggregatedSpawnMetrics(ImmutableMap.of());
/** Static holder for lazy initialization. */
private static class EmptyMetrics {
/**
* Map with empty {@link SpawnMetrics}.
*
* <p>This is useful for {@link #getMetrics(SpawnMetrics.ExecKind)} where we need to return an
* empty {@link SpawnMetrics} with the correct {@link SpawnMetrics.ExecKind}.
*/
private static final ImmutableMap<SpawnMetrics.ExecKind, SpawnMetrics> INSTANCE =
createEmptyMetrics();
private static final ImmutableMap<SpawnMetrics.ExecKind, SpawnMetrics> createEmptyMetrics() {
EnumMap<SpawnMetrics.ExecKind, SpawnMetrics> map = new EnumMap<>(SpawnMetrics.ExecKind.class);
for (SpawnMetrics.ExecKind kind : SpawnMetrics.ExecKind.values()) {
map.put(kind, SpawnMetrics.Builder.forExec(kind).build());
}
return Maps.immutableEnumMap(map);
}
}
private final ImmutableMap<SpawnMetrics.ExecKind, SpawnMetrics> metricsMap;
private AggregatedSpawnMetrics(ImmutableMap<SpawnMetrics.ExecKind, SpawnMetrics> metricsMap) {
this.metricsMap = metricsMap;
}
/**
* Returns all present {@link SpawnMetrics}.
*
* <p>There will be at most one {@link SpawnMetrics} object per {@link SpawnMetrics.ExecKind}.
*/
public ImmutableCollection<SpawnMetrics> getAllMetrics() {
return metricsMap.values();
}
/**
* Returns {@link SpawnMetrics} for the provided execution kind.
*
* <p>This will never return {@code null}, but the {@link SpawnMetrics} can be empty.
*/
public SpawnMetrics getMetrics(SpawnMetrics.ExecKind kind) {
@Nullable SpawnMetrics metrics = metricsMap.get(kind);
if (metrics == null) {
return EmptyMetrics.INSTANCE.get(kind);
}
return metrics;
}
/**
* Returns {@link SpawnMetrics} for the remote execution.
*
* @see #getMetrics(SpawnMetrics.ExecKind)
*/
public SpawnMetrics getRemoteMetrics() {
return getMetrics(SpawnMetrics.ExecKind.REMOTE);
}
/**
* Returns a new {@link AggregatedSpawnMetrics} that incorporates the provided metrics by summing
* them with the existing ones (if any).
*/
public AggregatedSpawnMetrics sumAllMetrics(SpawnMetrics other) {
SpawnMetrics existing = getMetrics(other.execKind());
SpawnMetrics.Builder builder =
SpawnMetrics.Builder.forExec(other.execKind())
.addDurations(existing)
.addDurations(other)
.addNonDurations(existing)
.addNonDurations(other);
EnumMap<SpawnMetrics.ExecKind, SpawnMetrics> map = new EnumMap<>(SpawnMetrics.ExecKind.class);
map.putAll(metricsMap);
map.put(other.execKind(), builder.build());
return new AggregatedSpawnMetrics(Maps.immutableEnumMap(map));
}
/**
* Returns a new {@link AggregatedSpawnMetrics} that incorporates the provided metrics by summing
* the duration ones and taking the maximum for the non-duration ones.
*/
public AggregatedSpawnMetrics sumDurationsMaxOther(SpawnMetrics other) {
SpawnMetrics existing = getMetrics(other.execKind());
SpawnMetrics.Builder builder =
SpawnMetrics.Builder.forExec(other.execKind())
.addDurations(existing)
.addDurations(other)
.maxNonDurations(existing)
.maxNonDurations(other);
EnumMap<SpawnMetrics.ExecKind, SpawnMetrics> map = new EnumMap<>(SpawnMetrics.ExecKind.class);
map.putAll(metricsMap);
map.put(other.execKind(), builder.build());
return new AggregatedSpawnMetrics(Maps.immutableEnumMap(map));
}
/**
* Returns the total duration across all execution kinds.
*
* <p>Example: {@code getTotalDuration(SpawnMetrics::queueTime)} will give the total queue time
* across all execution kinds.
*/
public int getTotalDuration(Function<SpawnMetrics, Integer> extract) {
int result = 0;
for (SpawnMetrics metric : metricsMap.values()) {
result += extract.apply(metric);
}
return result;
}
/**
* Returns the maximum value of a non-duration metric across all execution kinds.
*
* <p>Example: {@code getMaxNonDuration(0, SpawnMetrics::inputFiles)} returns the maximum number
* of input files across all the execution kinds.
*/
public long getMaxNonDuration(long initialValue, ToLongFunction<SpawnMetrics> extract) {
long result = initialValue;
for (SpawnMetrics metric : metricsMap.values()) {
result = Long.max(result, extract.applyAsLong(metric));
}
return result;
}
public String toString(Duration total, boolean summary) {
// For now keep compatibility with the old output and only report the remote execution.
// TODO(michalt): Change this once the local and worker executions populate more metrics.
return SpawnMetrics.ExecKind.REMOTE
+ " "
+ getRemoteMetrics().toString((int) total.toMillis(), summary);
}
/** Builder for {@link AggregatedSpawnMetrics}. */
public static class Builder {
private final EnumMap<SpawnMetrics.ExecKind, SpawnMetrics.Builder> builderMap =
new EnumMap<>(SpawnMetrics.ExecKind.class);
public AggregatedSpawnMetrics build() {
EnumMap<SpawnMetrics.ExecKind, SpawnMetrics> map = new EnumMap<>(SpawnMetrics.ExecKind.class);
for (Map.Entry<SpawnMetrics.ExecKind, SpawnMetrics.Builder> entry : builderMap.entrySet()) {
map.put(entry.getKey(), entry.getValue().build());
}
return new AggregatedSpawnMetrics(Maps.immutableEnumMap(map));
}
@CanIgnoreReturnValue
public Builder addDurations(SpawnMetrics metrics) {
getBuilder(metrics.execKind()).addDurations(metrics);
return this;
}
@CanIgnoreReturnValue
public Builder addDurations(AggregatedSpawnMetrics aggregated) {
aggregated.getAllMetrics().forEach(this::addDurations);
return this;
}
@CanIgnoreReturnValue
public Builder addNonDurations(SpawnMetrics metrics) {
getBuilder(metrics.execKind()).addNonDurations(metrics);
return this;
}
@CanIgnoreReturnValue
public Builder addNonDurations(AggregatedSpawnMetrics aggregated) {
aggregated.getAllMetrics().forEach(this::addNonDurations);
return this;
}
@CanIgnoreReturnValue
public Builder maxNonDurations(SpawnMetrics metrics) {
getBuilder(metrics.execKind()).maxNonDurations(metrics);
return this;
}
@CanIgnoreReturnValue
public Builder maxNonDurations(AggregatedSpawnMetrics aggregated) {
aggregated.getAllMetrics().forEach(this::maxNonDurations);
return this;
}
private SpawnMetrics.Builder getBuilder(SpawnMetrics.ExecKind kind) {
return builderMap.computeIfAbsent(kind, SpawnMetrics.Builder::forExec);
}
}
}