| // 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. */ |
| 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 Duration getTotalDuration(Function<SpawnMetrics, Duration> extract) { |
| Duration result = Duration.ZERO; |
| for (SpawnMetrics metric : metricsMap.values()) { |
| result = result.plus(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(total, 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); |
| } |
| } |
| } |