Allow recording of Package metrics for all packages when requested. Refactor PackageMetricsPackageLoadingListener to allow for multiple implemenations and provide two implementations of the new PackageMetricRecorder interface. 1) Keeps intact the existing INFO logging for Top-N and honoring the flag to configure the number of extreme packages to track. 2) A PackageRecorder, controlled by a new flag which allows bazel to record metrics for all packages. These are currently unused in bazel. RELNOTES: None. PiperOrigin-RevId: 326486325
diff --git a/src/BUILD b/src/BUILD index 22e7472..296cbf1 100644 --- a/src/BUILD +++ b/src/BUILD
@@ -489,6 +489,7 @@ "//src/main/protobuf:dist_jars", "//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:dist_jars", "//src/main/java/com/google/devtools/build/lib/bazel/debug:dist_jars", + "//src/main/java/com/google/devtools/build/lib/packages/metrics:dist_jars", "//src/main/java/com/google/devtools/build/lib/skyframe/proto:dist_jars", "//src/main/java/com/google/devtools/build/lib/starlarkdebug/proto:dist_jars", "//src/main/java/com/google/devtools/build/skydoc/rendering/proto:dist_jars",
diff --git a/src/main/java/com/google/devtools/build/lib/packages/metrics/BUILD b/src/main/java/com/google/devtools/build/lib/packages/metrics/BUILD index 9c968e6..35df550 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/metrics/BUILD +++ b/src/main/java/com/google/devtools/build/lib/packages/metrics/BUILD
@@ -1,4 +1,6 @@ load("@rules_java//java:defs.bzl", "java_library") +load("//tools/build_rules:utilities.bzl", "java_library_srcs") +load("@rules_java//java:defs.bzl", "java_proto_library") package( default_visibility = ["//src:__subpackages__"], @@ -14,6 +16,7 @@ name = "metrics", srcs = glob(["*.java"]), deps = [ + ":package_metrics_java_proto", "//src/main/java/com/google/devtools/build/lib:runtime", "//src/main/java/com/google/devtools/build/lib/analysis:analysis_cluster", "//src/main/java/com/google/devtools/build/lib/cmdline:cmdline-primitives", @@ -22,8 +25,36 @@ "//src/main/java/com/google/devtools/build/lib/syntax:evaluator", "//src/main/java/com/google/devtools/build/lib/vfs", "//src/main/java/com/google/devtools/common/options", + "//third_party:auto_value", "//third_party:flogger", "//third_party:guava", "//third_party:jsr305", + "//third_party/protobuf:protobuf_java", + "//third_party/protobuf:protobuf_java_util", ], ) + +proto_library( + name = "package_metrics_proto", + srcs = ["package_metrics.proto"], + deps = [ + "@com_google_protobuf//:duration_proto", + ], +) + +java_proto_library( + name = "package_metrics_java_proto", + deps = [ + ":package_metrics_proto", + ], +) + +java_library_srcs( + name = "package_metrics_java_proto_srcs", + deps = [":package_metrics_java_proto"], +) + +filegroup( + name = "dist_jars", + srcs = [":package_metrics_java_proto_srcs"], +)
diff --git a/src/main/java/com/google/devtools/build/lib/packages/metrics/CompletePackageMetricsRecorder.java b/src/main/java/com/google/devtools/build/lib/packages/metrics/CompletePackageMetricsRecorder.java new file mode 100644 index 0000000..f71e0c5 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/packages/metrics/CompletePackageMetricsRecorder.java
@@ -0,0 +1,81 @@ +// 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.packages.metrics; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.Maps; +import com.google.devtools.build.lib.cmdline.PackageIdentifier; +import com.google.protobuf.Duration; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.concurrent.GuardedBy; + +/** PackageMetricsRecorder that records all available metrics for all package loads. */ +final class CompletePackageMetricsRecorder implements PackageMetricsRecorder { + + @GuardedBy("this") + private final HashMap<PackageIdentifier, PackageMetrics> metrics = new HashMap<>(); + + CompletePackageMetricsRecorder() {} + + @Override + public synchronized void recordMetrics(PackageIdentifier pkgId, PackageMetrics metrics) { + this.metrics.put(pkgId, metrics); + } + + @Override + public synchronized Map<PackageIdentifier, Duration> getLoadTimes() { + return Maps.transformValues(metrics, PackageMetrics::getLoadDuration); + } + + @Override + public synchronized Map<PackageIdentifier, Long> getComputationSteps() { + return Maps.transformValues(metrics, PackageMetrics::getComputationSteps); + } + + @Override + public synchronized Map<PackageIdentifier, Long> getNumTargets() { + return Maps.transformValues(metrics, PackageMetrics::getNumTargets); + } + + @Override + public synchronized Map<PackageIdentifier, Long> getNumTransitiveLoads() { + return Maps.transformValues(metrics, PackageMetrics::getNumTransitiveLoads); + } + + @Override + public synchronized void clear() { + metrics.clear(); + } + + @Override + public void loadingFinished() { + clear(); + } + + @Override + public Type getRecorderType() { + return PackageMetricsRecorder.Type.ALL; + } + + @Override + public synchronized ImmutableCollection<PackageMetrics> getPackageMetrics() { + // lazily set the pkgName when requested. + return metrics.entrySet().stream() + .map(e -> e.getValue().toBuilder().setName(e.getKey().toString()).build()) + .collect(toImmutableList()); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/metrics/ExtremaPackageLoadingListener.java b/src/main/java/com/google/devtools/build/lib/packages/metrics/ExtremaPackageLoadingListener.java deleted file mode 100644 index 71460b2..0000000 --- a/src/main/java/com/google/devtools/build/lib/packages/metrics/ExtremaPackageLoadingListener.java +++ /dev/null
@@ -1,184 +0,0 @@ -// 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.packages.metrics; - -import com.google.common.base.Preconditions; -import com.google.devtools.build.lib.cmdline.PackageIdentifier; -import com.google.devtools.build.lib.collect.Extrema; -import com.google.devtools.build.lib.packages.Package; -import com.google.devtools.build.lib.packages.PackageLoadingListener; -import com.google.devtools.build.lib.syntax.StarlarkSemantics; -import java.util.List; -import java.util.concurrent.TimeUnit; -import javax.annotation.concurrent.GuardedBy; - -/** Tracks per-invocation extreme package loading events. */ -public class ExtremaPackageLoadingListener implements PackageLoadingListener { - @GuardedBy("this") - private int currentNumPackagesToTrack; - - @GuardedBy("this") - private Extrema<PackageIdentifierAndLong> slowestPackagesToLoad; - - @GuardedBy("this") - private Extrema<PackageIdentifierAndLong> largestPackages; - - @GuardedBy("this") - private Extrema<PackageIdentifierAndLong> packagesWithMostTransitiveLoads; - - @GuardedBy("this") - private Extrema<PackageIdentifierAndLong> packagesWithMostComputationSteps; - - private static ExtremaPackageLoadingListener instance = null; - - public static synchronized ExtremaPackageLoadingListener getInstance() { - if (instance == null) { - instance = new ExtremaPackageLoadingListener(); - } - return instance; - } - - private ExtremaPackageLoadingListener() { - this.currentNumPackagesToTrack = 0; - this.slowestPackagesToLoad = Extrema.max(0); - this.largestPackages = Extrema.max(0); - this.packagesWithMostTransitiveLoads = Extrema.max(0); - this.packagesWithMostComputationSteps = Extrema.max(0); - } - - synchronized void setNumPackagesToTrack(int numPackagesToTrack) { - Preconditions.checkArgument(numPackagesToTrack >= 0, "num packages must be >= 0"); - if (numPackagesToTrack == currentNumPackagesToTrack) { - // Micro-optimization to avoid turning over collections. - clear(); - return; - } - - currentNumPackagesToTrack = numPackagesToTrack; - this.slowestPackagesToLoad = Extrema.max(currentNumPackagesToTrack); - this.largestPackages = Extrema.max(currentNumPackagesToTrack); - this.packagesWithMostTransitiveLoads = Extrema.max(currentNumPackagesToTrack); - this.packagesWithMostComputationSteps = Extrema.max(currentNumPackagesToTrack); - } - - @Override - public synchronized void onLoadingCompleteAndSuccessful( - Package pkg, StarlarkSemantics starlarkSemantics, long loadTimeNanos) { - if (currentNumPackagesToTrack == 0) { - // Micro-optimization - no need to track. - return; - } - - long loadTimeMillis = TimeUnit.MILLISECONDS.convert(loadTimeNanos, TimeUnit.NANOSECONDS); - slowestPackagesToLoad.aggregate( - new PackageIdentifierAndLong(pkg.getPackageIdentifier(), loadTimeMillis)); - - packagesWithMostComputationSteps.aggregate( - new PackageIdentifierAndLong(pkg.getPackageIdentifier(), pkg.getComputationSteps())); - - largestPackages.aggregate( - new PackageIdentifierAndLong(pkg.getPackageIdentifier(), pkg.getTargets().size())); - - packagesWithMostTransitiveLoads.aggregate( - new PackageIdentifierAndLong( - pkg.getPackageIdentifier(), pkg.getStarlarkFileDependencies().size())); - } - - public synchronized TopPackages getTopPackages() { - TopPackages result = - new TopPackages( - slowestPackagesToLoad.getExtremeElements(), - packagesWithMostComputationSteps.getExtremeElements(), - largestPackages.getExtremeElements(), - packagesWithMostTransitiveLoads.getExtremeElements()); - return result; - } - - synchronized TopPackages getAndResetTopPackages() { - TopPackages result = getTopPackages(); - clear(); - return result; - } - - private synchronized void clear() { - slowestPackagesToLoad.clear(); - packagesWithMostComputationSteps.clear(); - largestPackages.clear(); - packagesWithMostTransitiveLoads.clear(); - } - - /** Container around lists of packages which are slow or large in some form. */ - public static class TopPackages { - private final List<PackageIdentifierAndLong> slowestPackages; - private final List<PackageIdentifierAndLong> packagesWithMostComputationSteps; - private final List<PackageIdentifierAndLong> largestPackages; - private final List<PackageIdentifierAndLong> packagesWithMostTransitiveLoads; - - private TopPackages( - List<PackageIdentifierAndLong> slowestPackages, - List<PackageIdentifierAndLong> packagesWithMostComputationSteps, - List<PackageIdentifierAndLong> largestPackages, - List<PackageIdentifierAndLong> packagesWithMostTransitiveLoads) { - this.slowestPackages = slowestPackages; - this.packagesWithMostComputationSteps = packagesWithMostComputationSteps; - this.largestPackages = largestPackages; - this.packagesWithMostTransitiveLoads = packagesWithMostTransitiveLoads; - } - - public List<PackageIdentifierAndLong> getSlowestPackages() { - return slowestPackages; - } - - public List<PackageIdentifierAndLong> getPackagesWithMostComputationSteps() { - return packagesWithMostComputationSteps; - } - - public List<PackageIdentifierAndLong> getLargestPackages() { - return largestPackages; - } - - public List<PackageIdentifierAndLong> getPackagesWithMostTransitiveLoads() { - return packagesWithMostTransitiveLoads; - } - } - - /** A pair of PackageIdentifier and a corresponding value. */ - public static class PackageIdentifierAndLong implements Comparable<PackageIdentifierAndLong> { - private final PackageIdentifier pkgId; - private final long val; - - private PackageIdentifierAndLong(PackageIdentifier pkgId, long val) { - this.pkgId = pkgId; - this.val = val; - } - - public long getVal() { - return val; - } - - public PackageIdentifier getPkgId() { - return pkgId; - } - - @Override - public int compareTo(PackageIdentifierAndLong other) { - return Long.compare(val, other.val); - } - - @Override - public String toString() { - return String.format("%s (%d)", pkgId, val); - } - } -}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/metrics/ExtremaPackageMetricsRecorder.java b/src/main/java/com/google/devtools/build/lib/packages/metrics/ExtremaPackageMetricsRecorder.java new file mode 100644 index 0000000..8d57748 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/packages/metrics/ExtremaPackageMetricsRecorder.java
@@ -0,0 +1,173 @@ +// 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.packages.metrics; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSet.toImmutableSet; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.Streams; +import com.google.common.flogger.GoogleLogger; +import com.google.devtools.build.lib.cmdline.PackageIdentifier; +import com.google.devtools.build.lib.collect.Extrema; +import com.google.protobuf.Duration; +import com.google.protobuf.util.Durations; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import javax.annotation.concurrent.GuardedBy; + +/** Tracks per-invocation extreme package loading events. */ +class ExtremaPackageMetricsRecorder implements PackageMetricsRecorder { + private final int currentNumPackagesToTrack; + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); + + @GuardedBy("this") + private final Extrema<PackageMetricsContainer> slowestPackagesToLoad; + + @GuardedBy("this") + private final Extrema<PackageMetricsContainer> largestPackages; + + @GuardedBy("this") + private final Extrema<PackageMetricsContainer> packagesWithMostTransitiveLoads; + + @GuardedBy("this") + private final Extrema<PackageMetricsContainer> packagesWithMostComputationSteps; + + ExtremaPackageMetricsRecorder(int currentNumPackagesToTrack) { + Preconditions.checkArgument(currentNumPackagesToTrack >= 0, "num packages must be >= 0"); + this.currentNumPackagesToTrack = currentNumPackagesToTrack; + this.slowestPackagesToLoad = + Extrema.max(currentNumPackagesToTrack, PackageMetricsContainer.LOAD_TIMES_COMP); + this.largestPackages = + Extrema.max(currentNumPackagesToTrack, PackageMetricsContainer.NUM_TARGETS_COMP); + this.packagesWithMostTransitiveLoads = + Extrema.max(currentNumPackagesToTrack, PackageMetricsContainer.TRANSITIVE_LOADS_COMP); + this.packagesWithMostComputationSteps = + Extrema.max(currentNumPackagesToTrack, PackageMetricsContainer.COMPUTATION_STEPS_COMP); + } + + public int getNumPackageToTrack() { + return currentNumPackagesToTrack; + } + + @Override + public synchronized void recordMetrics(PackageIdentifier pkgId, PackageMetrics metrics) { + PackageMetricsContainer cont = PackageMetricsContainer.create(pkgId, metrics); + slowestPackagesToLoad.aggregate(cont); + packagesWithMostComputationSteps.aggregate(cont); + largestPackages.aggregate(cont); + packagesWithMostTransitiveLoads.aggregate(cont); + } + + @Override + public synchronized Map<PackageIdentifier, Duration> getLoadTimes() { + return slowestPackagesToLoad.getExtremeElements().stream() + .collect( + Collectors.toMap( + PackageMetricsContainer::getPackageIdentifier, + v -> v.getPackageMetricsInternal().getLoadDuration(), + (k, v) -> v, + LinkedHashMap::new)); // use a LinkedHashMap to ensure iteration order is maintained + } + + @Override + public synchronized Map<PackageIdentifier, Long> getComputationSteps() { + return toMap(packagesWithMostComputationSteps, PackageMetrics::getComputationSteps); + } + + @Override + public synchronized Map<PackageIdentifier, Long> getNumTargets() { + return toMap(largestPackages, PackageMetrics::getNumTargets); + } + + @Override + public synchronized Map<PackageIdentifier, Long> getNumTransitiveLoads() { + return toMap(packagesWithMostTransitiveLoads, PackageMetrics::getNumTransitiveLoads); + } + + private synchronized Map<PackageIdentifier, Long> toMap( + Extrema<PackageMetricsContainer> ext, Function<PackageMetrics, Long> fn) { + + return ext.getExtremeElements().stream() + .collect( + Collectors.toMap( + PackageMetricsContainer::getPackageIdentifier, + v -> fn.apply(v.getPackageMetricsInternal()), + (k, v) -> v, + LinkedHashMap::new)); // use a LinkedHashMap to ensure iteration order is maintained + } + + @Override + public synchronized void clear() { + slowestPackagesToLoad.clear(); + packagesWithMostComputationSteps.clear(); + largestPackages.clear(); + packagesWithMostTransitiveLoads.clear(); + } + + @Override + public synchronized void loadingFinished() { + logIfNonEmpty( + "Slowest packages (ms)", + slowestPackagesToLoad.getExtremeElements(), + c -> Durations.toMillis(c.getPackageMetricsInternal().getLoadDuration())); + logIfNonEmpty( + "Largest packages (num targets)", + packagesWithMostComputationSteps.getExtremeElements(), + c -> c.getPackageMetricsInternal().getNumTargets()); + logIfNonEmpty( + "Packages with most computation steps", + largestPackages.getExtremeElements(), + c -> c.getPackageMetricsInternal().getComputationSteps()); + logIfNonEmpty( + "Packages with most transitive loads (num bzl files)", + packagesWithMostTransitiveLoads.getExtremeElements(), + c -> c.getPackageMetricsInternal().getNumTransitiveLoads()); + clear(); + } + + @Override + public Type getRecorderType() { + return PackageMetricsRecorder.Type.ONLY_EXTREMES; + } + + @Override + public synchronized Collection<PackageMetrics> getPackageMetrics() { + return Streams.concat( + slowestPackagesToLoad.getExtremeElements().stream(), + packagesWithMostComputationSteps.getExtremeElements().stream(), + largestPackages.getExtremeElements().stream(), + packagesWithMostTransitiveLoads.getExtremeElements().stream()) + .map(c -> c.getPackageMetrics()) + .collect(toImmutableSet()); + } + + private static void logIfNonEmpty( + String logLinePrefix, + List<PackageMetricsContainer> extremeElements, + Function<PackageMetricsContainer, Long> valueMapper) { + List<String> logString = + extremeElements.stream() + .map(v -> String.format("%s (%d)", v.getPackageIdentifier(), valueMapper.apply(v))) + .collect(toImmutableList()); + if (!extremeElements.isEmpty()) { + logger.atInfo().log("%s: %s", logLinePrefix, Joiner.on(", ").join(logString)); + } + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageIdentifierAndLong.java b/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageIdentifierAndLong.java new file mode 100644 index 0000000..25643a6 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageIdentifierAndLong.java
@@ -0,0 +1,45 @@ +// 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.packages.metrics; + +import com.google.devtools.build.lib.cmdline.PackageIdentifier; + +/** A pair of PackageIdentifier and a corresponding value. */ +public class PackageIdentifierAndLong implements Comparable<PackageIdentifierAndLong> { + private final PackageIdentifier pkgId; + private final long val; + + private PackageIdentifierAndLong(PackageIdentifier pkgId, long val) { + this.pkgId = pkgId; + this.val = val; + } + + public long getVal() { + return val; + } + + public PackageIdentifier getPkgId() { + return pkgId; + } + + @Override + public int compareTo(PackageIdentifierAndLong other) { + return Long.compare(val, other.val); + } + + @Override + public String toString() { + return String.format("%s (%d)", pkgId, val); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsContainer.java b/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsContainer.java new file mode 100644 index 0000000..90b04ec --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsContainer.java
@@ -0,0 +1,53 @@ +// 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.packages.metrics; + +import com.google.auto.value.AutoValue; +import com.google.devtools.build.lib.cmdline.PackageIdentifier; +import com.google.protobuf.util.Durations; +import java.util.Comparator; + +/** Container class holding a PackageIdentifier and PackageMetrics proto. */ +@AutoValue +public abstract class PackageMetricsContainer { + + /** Sorts by LoadTime Duration. */ + public static final Comparator<PackageMetricsContainer> LOAD_TIMES_COMP = + Comparator.comparing( + c -> c.getPackageMetricsInternal().getLoadDuration(), Durations.comparator()); + /** Sorts by Num Target count . */ + public static final Comparator<PackageMetricsContainer> NUM_TARGETS_COMP = + Comparator.comparingLong(c -> c.getPackageMetricsInternal().getNumTargets()); + /** Sorts by Comutation Steps count. */ + public static final Comparator<PackageMetricsContainer> COMPUTATION_STEPS_COMP = + Comparator.comparingLong(c -> c.getPackageMetricsInternal().getComputationSteps()); + /** Sorts by Transitive Load Count. */ + public static final Comparator<PackageMetricsContainer> TRANSITIVE_LOADS_COMP = + Comparator.comparingLong(c -> c.getPackageMetricsInternal().getNumTransitiveLoads()); + + public static PackageMetricsContainer create(PackageIdentifier pkgId, PackageMetrics metrics) { + return new AutoValue_PackageMetricsContainer(pkgId, metrics); + } + + public abstract PackageIdentifier getPackageIdentifier(); + + abstract PackageMetrics getPackageMetricsInternal(); + + /** Construct a full PackageMetrics object with the name set lazily from the PackageIdentifier. */ + public PackageMetrics getPackageMetrics() { + return getPackageMetricsInternal().toBuilder() + .setName(getPackageIdentifier().toString()) + .build(); + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsModule.java b/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsModule.java index 2f95729..19b5323 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsModule.java +++ b/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsModule.java
@@ -14,14 +14,10 @@ package com.google.devtools.build.lib.packages.metrics; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; -import com.google.common.flogger.GoogleLogger; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.packages.Package.Builder.PackageSettings; import com.google.devtools.build.lib.packages.PackageLoadingListener; -import com.google.devtools.build.lib.packages.metrics.ExtremaPackageLoadingListener.PackageIdentifierAndLong; -import com.google.devtools.build.lib.packages.metrics.ExtremaPackageLoadingListener.TopPackages; import com.google.devtools.build.lib.runtime.BlazeModule; import com.google.devtools.build.lib.runtime.CommandEnvironment; import com.google.devtools.build.lib.vfs.FileSystem; @@ -29,13 +25,10 @@ import com.google.devtools.common.options.OptionDocumentationCategory; import com.google.devtools.common.options.OptionEffectTag; import com.google.devtools.common.options.OptionsBase; -import java.util.List; import javax.annotation.Nullable; /** Provides logging for extreme package-loading events. */ public class PackageMetricsModule extends BlazeModule { - private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); - /** Options for {@link PackageMetricsModule}. */ public static class Options extends OptionsBase { @Option( @@ -45,16 +38,26 @@ effectTags = {OptionEffectTag.BAZEL_MONITORING}, help = "Configures number of packages included in top-package INFO logging, <= 0 disables.") public int numberOfPackagesToTrack; + + @Option( + name = "record_metrics_for_all_packages", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, + effectTags = {OptionEffectTag.BAZEL_MONITORING}, + help = + "Configures PackageMetrics to record all metrics for all packages. Disables Top-n INFO" + + " logging.") + public boolean enableAllMetrics; } - private final ExtremaPackageLoadingListener packageLoadingListener; + private final PackageMetricsPackageLoadingListener packageLoadingListener; public PackageMetricsModule() { - this(ExtremaPackageLoadingListener.getInstance()); + this(PackageMetricsPackageLoadingListener.getInstance()); } @VisibleForTesting - PackageMetricsModule(ExtremaPackageLoadingListener packageLoadingListener) { + PackageMetricsModule(PackageMetricsPackageLoadingListener packageLoadingListener) { this.packageLoadingListener = packageLoadingListener; } @@ -75,25 +78,17 @@ @Override public void beforeCommand(CommandEnvironment commandEnvironment) { Options options = commandEnvironment.getOptions().getOptions(Options.class); - packageLoadingListener.setNumPackagesToTrack(Math.max(options.numberOfPackagesToTrack, 0)); + PackageMetricsRecorder recorder = + options.enableAllMetrics + ? new CompletePackageMetricsRecorder() + : new ExtremaPackageMetricsRecorder(Math.max(options.numberOfPackagesToTrack, 0)); + packageLoadingListener.setPackageMetricsRecorder(recorder); } @Override public void afterCommand() { - TopPackages topPackages = packageLoadingListener.getAndResetTopPackages(); - logIfNonEmpty("Slowest packages (ms)", topPackages.getSlowestPackages()); - logIfNonEmpty("Largest packages (num targets)", topPackages.getLargestPackages()); - logIfNonEmpty( - "Packages with most computation steps", topPackages.getPackagesWithMostComputationSteps()); - logIfNonEmpty( - "Packages with most transitive loads (num bzl files)", - topPackages.getPackagesWithMostTransitiveLoads()); - } - - private static void logIfNonEmpty( - String logLinePrefix, List<PackageIdentifierAndLong> extremeElements) { - if (!extremeElements.isEmpty()) { - logger.atInfo().log("%s: %s", logLinePrefix, Joiner.on(", ").join(extremeElements)); + if (packageLoadingListener.getPackageMetricsRecorder() != null) { + packageLoadingListener.getPackageMetricsRecorder().loadingFinished(); } } }
diff --git a/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsPackageLoadingListener.java b/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsPackageLoadingListener.java new file mode 100644 index 0000000..904c780 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsPackageLoadingListener.java
@@ -0,0 +1,69 @@ +// 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.packages.metrics; + +import com.google.devtools.build.lib.packages.Package; +import com.google.devtools.build.lib.packages.PackageLoadingListener; +import com.google.devtools.build.lib.syntax.StarlarkSemantics; +import com.google.protobuf.util.Durations; +import javax.annotation.concurrent.GuardedBy; + +/** Tracks per-invocation extreme package loading events. */ +public class PackageMetricsPackageLoadingListener implements PackageLoadingListener { + + @GuardedBy("this") + private PackageMetricsRecorder recorder = null; + + @GuardedBy("PackageMetricsPackageLoadingListener.class") + private static PackageMetricsPackageLoadingListener instance = null; + + public static synchronized PackageMetricsPackageLoadingListener getInstance() { + if (instance == null) { + instance = new PackageMetricsPackageLoadingListener(); + } + return instance; + } + + private PackageMetricsPackageLoadingListener() { + this.recorder = null; + } + + @Override + public synchronized void onLoadingCompleteAndSuccessful( + Package pkg, StarlarkSemantics starlarkSemantics, long loadTimeNanos) { + if (recorder == null) { + // Micro-optimization - no need to track. + return; + } + + recorder.recordMetrics( + pkg.getPackageIdentifier(), + PackageMetrics.newBuilder() + .setLoadDuration(Durations.fromNanos(loadTimeNanos)) + .setComputationSteps(pkg.getComputationSteps()) + .setNumTargets(pkg.getTargets().size()) + .setNumTransitiveLoads(pkg.getStarlarkFileDependencies().size()) + .build()); + } + + /** Set the PackageMetricsRecorder for this listener. */ + public synchronized void setPackageMetricsRecorder(PackageMetricsRecorder recorder) { + this.recorder = recorder; + } + + /** Returns the PackageMetricsRecorder, if any, for the PackageLoadingListener. */ + public synchronized PackageMetricsRecorder getPackageMetricsRecorder() { + return recorder; + } +}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsRecorder.java b/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsRecorder.java new file mode 100644 index 0000000..0f80bec --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsRecorder.java
@@ -0,0 +1,71 @@ +// 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.packages.metrics; + +import com.google.devtools.build.lib.cmdline.PackageIdentifier; +import com.google.protobuf.Duration; +import java.util.Collection; +import java.util.Map; + +/** Interface encapsulating the strategy used for recording Package Metrics. */ +public interface PackageMetricsRecorder { + + /** What type of packages are metrics being recorded for? */ + public enum Type { + ONLY_EXTREMES, + ALL, + } + + /** Records the metrics for a given package. */ + void recordMetrics(PackageIdentifier pkgId, PackageMetrics metrics); + + /** + * Returns a {@code Map<PackageIdentifier, Duration>} of recorded load durations. This may contain + * only a subset of all packages loaded based on the implementation. + */ + Map<PackageIdentifier, Duration> getLoadTimes(); + + /** + * Returns a {@code Map<PackageIdentifier, Long>} of computation steps. This may contain only a + * subset of all packages loaded based on the implementation. + */ + Map<PackageIdentifier, Long> getComputationSteps(); + + /** + * Returns a {@code Map<PackageIdentifier, Long>} of num targets. This may contain only a subset + * of all packages loaded based on the implementation. + */ + Map<PackageIdentifier, Long> getNumTargets(); + + /** + * Returns a {@code Map<PackageIdentifier, Long>} of num targets. This may contain only a subset + * of all packages loaded based on the implementation. + */ + Map<PackageIdentifier, Long> getNumTransitiveLoads(); + + /** Clears the contents of the PackageMetricsRecorder. */ + void clear(); + + /** + * Called after package loading is complete to allow handlers to perform post-loading phase + * processing. + */ + void loadingFinished(); + + /** Returns the type of package metrics being recorded. */ + Type getRecorderType(); + + /** If Type is ALL returns metrics for all Packages loaded. */ + Collection<PackageMetrics> getPackageMetrics(); +}
diff --git a/src/main/java/com/google/devtools/build/lib/packages/metrics/package_metrics.proto b/src/main/java/com/google/devtools/build/lib/packages/metrics/package_metrics.proto new file mode 100644 index 0000000..10386c5 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/packages/metrics/package_metrics.proto
@@ -0,0 +1,39 @@ +// 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. +syntax = "proto2"; + +package devtools.build.lib.packages.metrics; + +import "google/protobuf/duration.proto"; + +option java_package = "com.google.devtools.build.lib.packages.metrics"; +option java_multiple_files = true; + +// Message used to consisely report all package metrics. +message PackageMetrics { + // Name of the package. + optional string name = 1; + + // Walltime Duration it took to construct the package. + optional google.protobuf.Duration load_duration = 2; + + // Number of targets created in the package. + optional uint64 num_targets = 3; + + // Number of Starlark computation steps required to create the package. + optional uint64 computation_steps = 4; + + // Number of transitive Starlark load()s required to create the package. + optional uint64 num_transitive_loads = 5; +}
diff --git a/src/test/java/com/google/devtools/build/lib/packages/metrics/BUILD b/src/test/java/com/google/devtools/build/lib/packages/metrics/BUILD index de6429e..d84b27c 100644 --- a/src/test/java/com/google/devtools/build/lib/packages/metrics/BUILD +++ b/src/test/java/com/google/devtools/build/lib/packages/metrics/BUILD
@@ -15,19 +15,21 @@ ) java_test( - name = "ExtremaPackageLoadingListenerTest", + name = "PackageMetricsPackageLoadingListenerTest", size = "small", - srcs = ["ExtremaPackageLoadingListenerTest.java"], + srcs = ["PackageMetricsPackageLoadingListenerTest.java"], deps = [ "//src/main/java/com/google/devtools/build/lib:syntax", "//src/main/java/com/google/devtools/build/lib/cmdline", "//src/main/java/com/google/devtools/build/lib/collect", "//src/main/java/com/google/devtools/build/lib/packages", "//src/main/java/com/google/devtools/build/lib/packages/metrics", + "//src/main/java/com/google/devtools/build/lib/packages/metrics:package_metrics_java_proto", "//third_party:guava", "//third_party:junit4", "//third_party:mockito", "//third_party:truth", + "//third_party/protobuf:protobuf_java_util", ], ) @@ -41,5 +43,6 @@ "//src/main/java/com/google/devtools/common/options", "//third_party:junit4", "//third_party:mockito", + "//third_party:truth", ], )
diff --git a/src/test/java/com/google/devtools/build/lib/packages/metrics/ExtremaPackageLoadingListenerTest.java b/src/test/java/com/google/devtools/build/lib/packages/metrics/ExtremaPackageLoadingListenerTest.java deleted file mode 100644 index 0a7ddce..0000000 --- a/src/test/java/com/google/devtools/build/lib/packages/metrics/ExtremaPackageLoadingListenerTest.java +++ /dev/null
@@ -1,233 +0,0 @@ -// 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.packages.metrics; - -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.devtools.build.lib.cmdline.Label; -import com.google.devtools.build.lib.cmdline.PackageIdentifier; -import com.google.devtools.build.lib.collect.ImmutableSortedKeyMap; -import com.google.devtools.build.lib.packages.Package; -import com.google.devtools.build.lib.packages.Target; -import com.google.devtools.build.lib.packages.metrics.ExtremaPackageLoadingListener.PackageIdentifierAndLong; -import com.google.devtools.build.lib.packages.metrics.ExtremaPackageLoadingListener.TopPackages; -import com.google.devtools.build.lib.syntax.StarlarkSemantics; -import java.util.List; -import java.util.Map; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** Tests for {@link ExtremaPackageLoadingListener}. */ -@RunWith(JUnit4.class) -public class ExtremaPackageLoadingListenerTest { - - private final ExtremaPackageLoadingListener underTest = - ExtremaPackageLoadingListener.getInstance(); - - @Test - public void testRecordsTopSlowestPackagesPerBuild() { - underTest.setNumPackagesToTrack(2); - - underTest.onLoadingCompleteAndSuccessful( - mockPackage( - "my/pkg1", - /*targets=*/ ImmutableMap.of(), - /*starlarkDependencies=*/ ImmutableList.of()), - StarlarkSemantics.DEFAULT, - /*loadTimeNanos=*/ 42_000_000); - - underTest.onLoadingCompleteAndSuccessful( - mockPackage( - "my/pkg2", - /*targets=*/ ImmutableMap.of(), - /*starlarkDependencies=*/ ImmutableList.of()), - StarlarkSemantics.DEFAULT, - /*loadTimeNanos=*/ 43_000_000); - - underTest.onLoadingCompleteAndSuccessful( - mockPackage( - "my/pkg3", - /*targets=*/ ImmutableMap.of(), - /*starlarkDependencies=*/ ImmutableList.of()), - StarlarkSemantics.DEFAULT, - /*loadTimeNanos=*/ 44_000_000); - - assertThat(toStrings(underTest.getAndResetTopPackages().getSlowestPackages())) - .containsExactly("my/pkg3 (44)", "my/pkg2 (43)") - .inOrder(); - - assertAllTopPackagesEmpty(underTest.getAndResetTopPackages()); - } - - @Test - public void testRecordsTopLargestPackagesPerBuild() { - underTest.setNumPackagesToTrack(2); - - underTest.onLoadingCompleteAndSuccessful( - mockPackage( - "my/pkg1", - ImmutableMap.of("target1", mock(Target.class)), - /*starlarkDependencies=*/ ImmutableList.of()), - StarlarkSemantics.DEFAULT, - /*loadTimeNanos=*/ 100); - - underTest.onLoadingCompleteAndSuccessful( - mockPackage( - "my/pkg2", - ImmutableMap.of("target1", mock(Target.class), "target2", mock(Target.class)), - /*starlarkDependencies=*/ ImmutableList.of()), - StarlarkSemantics.DEFAULT, - /*loadTimeNanos=*/ 100); - - underTest.onLoadingCompleteAndSuccessful( - mockPackage( - "my/pkg3", - ImmutableMap.of( - "target1", - mock(Target.class), - "target2", - mock(Target.class), - "target3", - mock(Target.class)), - /*starlarkDependencies=*/ ImmutableList.of()), - StarlarkSemantics.DEFAULT, - /*loadTimeNanos=*/ 100); - - assertThat(toStrings(underTest.getAndResetTopPackages().getLargestPackages())) - .containsExactly("my/pkg3 (3)", "my/pkg2 (2)") - .inOrder(); - - assertAllTopPackagesEmpty(underTest.getAndResetTopPackages()); - } - - @Test - public void testRecordsTransitiveLoadsPerBuild() { - underTest.setNumPackagesToTrack(2); - - underTest.onLoadingCompleteAndSuccessful( - mockPackage( - "my/pkg1", - /*targets=*/ ImmutableMap.of(), - ImmutableList.of(Label.parseAbsoluteUnchecked("//load:1.bzl"))), - StarlarkSemantics.DEFAULT, - /*loadTimeNanos=*/ 100); - - underTest.onLoadingCompleteAndSuccessful( - mockPackage( - "my/pkg2", - /*targets=*/ ImmutableMap.of(), - ImmutableList.of( - Label.parseAbsoluteUnchecked("//load:1.bzl"), - Label.parseAbsoluteUnchecked("//load:2.bzl"))), - StarlarkSemantics.DEFAULT, - /*loadTimeNanos=*/ 100); - - underTest.onLoadingCompleteAndSuccessful( - mockPackage( - "my/pkg3", - /*targets=*/ ImmutableMap.of(), - ImmutableList.of( - Label.parseAbsoluteUnchecked("//load:1.bzl"), - Label.parseAbsoluteUnchecked("//load:2.bzl"), - Label.parseAbsoluteUnchecked("//load:3.bzl"))), - StarlarkSemantics.DEFAULT, - /*loadTimeNanos=*/ 100); - - assertThat(toStrings(underTest.getAndResetTopPackages().getPackagesWithMostTransitiveLoads())) - .containsExactly("my/pkg3 (3)", "my/pkg2 (2)") - .inOrder(); - - assertAllTopPackagesEmpty(underTest.getAndResetTopPackages()); - } - - @Test - public void testRecordsMostComputationStepsPerBuild() { - underTest.setNumPackagesToTrack(2); - - Package mockPackage1 = - mockPackage( - "my/pkg1", - /*targets=*/ ImmutableMap.of(), - /*starlarkDependencies=*/ ImmutableList.of()); - when(mockPackage1.getComputationSteps()).thenReturn(1000L); - underTest.onLoadingCompleteAndSuccessful( - mockPackage1, StarlarkSemantics.DEFAULT, /*loadTimeNanos=*/ 100); - - Package mockPackage2 = - mockPackage( - "my/pkg2", - /*targets=*/ ImmutableMap.of(), - /*starlarkDependencies=*/ ImmutableList.of()); - when(mockPackage2.getComputationSteps()).thenReturn(100L); - underTest.onLoadingCompleteAndSuccessful( - mockPackage2, StarlarkSemantics.DEFAULT, /*loadTimeNanos=*/ 100); - - Package mockPackage3 = - mockPackage( - "my/pkg3", - /*targets=*/ ImmutableMap.of(), - /*starlarkDependencies=*/ ImmutableList.of()); - when(mockPackage3.getComputationSteps()).thenReturn(10L); - underTest.onLoadingCompleteAndSuccessful( - mockPackage3, StarlarkSemantics.DEFAULT, /*loadTimeNanos=*/ 100); - - assertThat(toStrings(underTest.getAndResetTopPackages().getPackagesWithMostComputationSteps())) - .containsExactly("my/pkg1 (1000)", "my/pkg2 (100)") - .inOrder(); - - assertAllTopPackagesEmpty(underTest.getAndResetTopPackages()); - } - - @Test - public void testDoesntRecordAnythingWhenNumPackagesToTrackIsZero() { - underTest.setNumPackagesToTrack(0); - - underTest.onLoadingCompleteAndSuccessful( - mockPackage( - "my/pkg1", - /*targets=*/ ImmutableMap.of(), - /*starlarkDependencies=*/ ImmutableList.of()), - StarlarkSemantics.DEFAULT, - /*loadTimeNanos=*/ 42_000_000); - - assertAllTopPackagesEmpty(underTest.getAndResetTopPackages()); - } - - private static void assertAllTopPackagesEmpty(TopPackages topPackages) { - assertThat(topPackages.getSlowestPackages()).isEmpty(); - assertThat(topPackages.getLargestPackages()).isEmpty(); - assertThat(topPackages.getPackagesWithMostTransitiveLoads()).isEmpty(); - assertThat(topPackages.getPackagesWithMostComputationSteps()).isEmpty(); - } - - private static Package mockPackage( - String pkgIdString, Map<String, Target> targets, List<Label> starlarkDependencies) { - Package mockPackage = mock(Package.class); - when(mockPackage.getPackageIdentifier()) - .thenReturn(PackageIdentifier.createInMainRepo(pkgIdString)); - when(mockPackage.getTargets()).thenReturn(ImmutableSortedKeyMap.copyOf(targets)); - when(mockPackage.getStarlarkFileDependencies()) - .thenReturn(ImmutableList.copyOf(starlarkDependencies)); - return mockPackage; - } - - private static ImmutableList<String> toStrings(List<PackageIdentifierAndLong> from) { - return from.stream().map(v -> v.toString()).collect(ImmutableList.toImmutableList()); - } -}
diff --git a/src/test/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsModuleTest.java b/src/test/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsModuleTest.java index 68fada5..7279122 100644 --- a/src/test/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsModuleTest.java +++ b/src/test/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsModuleTest.java
@@ -13,63 +13,71 @@ // limitations under the License. package com.google.devtools.build.lib.packages.metrics; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.google.devtools.build.lib.packages.metrics.ExtremaPackageLoadingListener.TopPackages; import com.google.devtools.build.lib.runtime.CommandEnvironment; import com.google.devtools.common.options.OptionsParser; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; /** Tests for {@link PackageMetricsModule}. */ @RunWith(JUnit4.class) public class PackageMetricsModuleTest { - @Rule public MockitoRule mockito = MockitoJUnit.rule(); - - @Mock private ExtremaPackageLoadingListener mockPackageLoadingListener; + private final PackageMetricsPackageLoadingListener listener = + PackageMetricsPackageLoadingListener.getInstance(); private PackageMetricsModule underTest; @Before public void setUp() { - underTest = new PackageMetricsModule(mockPackageLoadingListener); + underTest = new PackageMetricsModule(); + listener.setPackageMetricsRecorder(null); } @Test public void testBeforeCommandConfiguresNumberOfPackagesToTrack() throws Exception { underTest.beforeCommand(commandEnv("--log_top_n_packages=100")); - verify(mockPackageLoadingListener).setNumPackagesToTrack(100); + assertThat(listener.getPackageMetricsRecorder()) + .isInstanceOf(ExtremaPackageMetricsRecorder.class); + ExtremaPackageMetricsRecorder ext = + (ExtremaPackageMetricsRecorder) listener.getPackageMetricsRecorder(); + assertThat(ext.getNumPackageToTrack()).isEqualTo(100); } @Test public void testBeforeCommandConfiguresNumberOfPackagesToTrackTreatsNegativeAsZero() throws Exception { underTest.beforeCommand(commandEnv("--log_top_n_packages=-100")); - verify(mockPackageLoadingListener).setNumPackagesToTrack(0); + assertThat(listener.getPackageMetricsRecorder()) + .isInstanceOf(ExtremaPackageMetricsRecorder.class); + ExtremaPackageMetricsRecorder ext = + (ExtremaPackageMetricsRecorder) listener.getPackageMetricsRecorder(); + assertThat(ext.getNumPackageToTrack()).isEqualTo(0); + } + + @Test + public void testBeforeCommandConfiguresNumberOfPackagesToTrackButRequestsComplete() + throws Exception { + underTest.beforeCommand( + commandEnv("--log_top_n_packages=100", "--record_metrics_for_all_packages=1")); + assertThat(listener.getPackageMetricsRecorder()) + .isInstanceOf(CompletePackageMetricsRecorder.class); } @Test public void testAfterCommandGetsAndResetsMetrics() { // Mocking here is lazy, but it helps verify we actually did something with all of the results. - TopPackages mockTopPackages = mock(TopPackages.class); - when(mockPackageLoadingListener.getAndResetTopPackages()).thenReturn(mockTopPackages); + PackageMetricsRecorder mockRecorder = mock(PackageMetricsRecorder.class); + listener.setPackageMetricsRecorder(mockRecorder); underTest.afterCommand(); - - verify(mockPackageLoadingListener).getAndResetTopPackages(); - verify(mockTopPackages).getSlowestPackages(); - verify(mockTopPackages).getLargestPackages(); - verify(mockTopPackages).getPackagesWithMostComputationSteps(); - verify(mockTopPackages).getPackagesWithMostTransitiveLoads(); + verify(mockRecorder).loadingFinished(); } private static CommandEnvironment commandEnv(String... options) throws Exception {
diff --git a/src/test/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsPackageLoadingListenerTest.java b/src/test/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsPackageLoadingListenerTest.java new file mode 100644 index 0000000..e66c5d9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/packages/metrics/PackageMetricsPackageLoadingListenerTest.java
@@ -0,0 +1,467 @@ +// 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.packages.metrics; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.cmdline.PackageIdentifier; +import com.google.devtools.build.lib.collect.ImmutableSortedKeyMap; +import com.google.devtools.build.lib.packages.Package; +import com.google.devtools.build.lib.packages.Target; +import com.google.devtools.build.lib.syntax.StarlarkSemantics; +import com.google.protobuf.util.Durations; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link PackageMetricsPackageLoadingListener}. */ +@RunWith(JUnit4.class) +public class PackageMetricsPackageLoadingListenerTest { + + private final PackageMetricsPackageLoadingListener underTest = + PackageMetricsPackageLoadingListener.getInstance(); + + @Test + public void testRecordsTopSlowestPackagesPerBuild_extrema() { + PackageMetricsRecorder recorder = new ExtremaPackageMetricsRecorder(2); + underTest.setPackageMetricsRecorder(recorder); + + recordSlowPackages(); + + assertThat(underTest.getPackageMetricsRecorder().getLoadTimes()) + .containsExactlyEntriesIn( + ImmutableMap.of( + PackageIdentifier.createInMainRepo("my/pkg3"), + Durations.fromMillis(44), + PackageIdentifier.createInMainRepo("my/pkg2"), + Durations.fromMillis(43))) + .inOrder(); + + recorder.loadingFinished(); + assertAllMapsEmpty(recorder); + } + + @Test + public void testRecordsTopSlowestPackagesPerBuild_complete() { + PackageMetricsRecorder recorder = new CompletePackageMetricsRecorder(); + underTest.setPackageMetricsRecorder(recorder); + + recordSlowPackages(); + + assertThat(underTest.getPackageMetricsRecorder().getLoadTimes()) + .containsExactly( + PackageIdentifier.createInMainRepo("my/pkg1"), + Durations.fromMillis(42), + PackageIdentifier.createInMainRepo("my/pkg2"), + Durations.fromMillis(43), + PackageIdentifier.createInMainRepo("my/pkg3"), + Durations.fromMillis(44)); + recorder.clear(); + assertAllMapsEmpty(recorder); + } + + private void recordSlowPackages() { + underTest.onLoadingCompleteAndSuccessful( + mockPackage( + "my/pkg1", + /*targets=*/ ImmutableMap.of(), + /*starlarkDependencies=*/ ImmutableList.of()), + StarlarkSemantics.DEFAULT, + /*loadTimeNanos=*/ 42_000_000); + + underTest.onLoadingCompleteAndSuccessful( + mockPackage( + "my/pkg2", + /*targets=*/ ImmutableMap.of(), + /*starlarkDependencies=*/ ImmutableList.of()), + StarlarkSemantics.DEFAULT, + /*loadTimeNanos=*/ 43_000_000); + + underTest.onLoadingCompleteAndSuccessful( + mockPackage( + "my/pkg3", + /*targets=*/ ImmutableMap.of(), + /*starlarkDependencies=*/ ImmutableList.of()), + StarlarkSemantics.DEFAULT, + /*loadTimeNanos=*/ 44_000_000); + } + + @Test + public void testRecordsTopLargestPackagesPerBuild_extrema() { + PackageMetricsRecorder recorder = new ExtremaPackageMetricsRecorder(2); + underTest.setPackageMetricsRecorder(recorder); + + recordLargePackages(); + + assertThat(underTest.getPackageMetricsRecorder().getNumTargets()) + .containsExactlyEntriesIn( + ImmutableMap.of( + PackageIdentifier.createInMainRepo("my/pkg3"), + 3L, + PackageIdentifier.createInMainRepo("my/pkg2"), + 2L)) + .inOrder(); + recorder.loadingFinished(); + ; + assertAllMapsEmpty(recorder); + } + + @Test + public void testRecordsTopLargestPackagesPerBuild_complete() { + PackageMetricsRecorder recorder = new CompletePackageMetricsRecorder(); + underTest.setPackageMetricsRecorder(recorder); + + recordLargePackages(); + + assertThat(underTest.getPackageMetricsRecorder().getNumTargets()) + .containsExactly( + PackageIdentifier.createInMainRepo("my/pkg3"), + 3L, + PackageIdentifier.createInMainRepo("my/pkg2"), + 2L, + PackageIdentifier.createInMainRepo("my/pkg1"), + 1L); + } + + private void recordLargePackages() { + underTest.onLoadingCompleteAndSuccessful( + mockPackage( + "my/pkg1", + ImmutableMap.of("target1", mock(Target.class)), + /*starlarkDependencies=*/ ImmutableList.of()), + StarlarkSemantics.DEFAULT, + /*loadTimeNanos=*/ 100); + + underTest.onLoadingCompleteAndSuccessful( + mockPackage( + "my/pkg2", + ImmutableMap.of("target1", mock(Target.class), "target2", mock(Target.class)), + /*starlarkDependencies=*/ ImmutableList.of()), + StarlarkSemantics.DEFAULT, + /*loadTimeNanos=*/ 100); + + underTest.onLoadingCompleteAndSuccessful( + mockPackage( + "my/pkg3", + ImmutableMap.of( + "target1", + mock(Target.class), + "target2", + mock(Target.class), + "target3", + mock(Target.class)), + /*starlarkDependencies=*/ ImmutableList.of()), + StarlarkSemantics.DEFAULT, + /*loadTimeNanos=*/ 100); + } + + @Test + public void testRecordsTransitiveLoadsPerBuild_extrema() { + PackageMetricsRecorder recorder = new ExtremaPackageMetricsRecorder(2); + underTest.setPackageMetricsRecorder(recorder); + + recordTransitiveLoads(); + + assertThat(underTest.getPackageMetricsRecorder().getNumTransitiveLoads()) + .containsExactlyEntriesIn( + ImmutableMap.of( + PackageIdentifier.createInMainRepo("my/pkg3"), + 3L, + PackageIdentifier.createInMainRepo("my/pkg2"), + 2L)) + .inOrder(); + recorder.loadingFinished(); + assertAllMapsEmpty(recorder); + } + + @Test + public void testRecordsTransitiveLoadsPerBuild_complete() { + PackageMetricsRecorder recorder = new CompletePackageMetricsRecorder(); + underTest.setPackageMetricsRecorder(recorder); + + recordTransitiveLoads(); + + assertThat(underTest.getPackageMetricsRecorder().getNumTransitiveLoads()) + .containsExactly( + PackageIdentifier.createInMainRepo("my/pkg3"), + 3L, + PackageIdentifier.createInMainRepo("my/pkg2"), + 2L, + PackageIdentifier.createInMainRepo("my/pkg1"), + 1L); + } + + private void recordTransitiveLoads() { + underTest.onLoadingCompleteAndSuccessful( + mockPackage( + "my/pkg1", + /*targets=*/ ImmutableMap.of(), + ImmutableList.of(Label.parseAbsoluteUnchecked("//load:1.bzl"))), + StarlarkSemantics.DEFAULT, + /*loadTimeNanos=*/ 100); + + underTest.onLoadingCompleteAndSuccessful( + mockPackage( + "my/pkg2", + /*targets=*/ ImmutableMap.of(), + ImmutableList.of( + Label.parseAbsoluteUnchecked("//load:1.bzl"), + Label.parseAbsoluteUnchecked("//load:2.bzl"))), + StarlarkSemantics.DEFAULT, + /*loadTimeNanos=*/ 100); + + underTest.onLoadingCompleteAndSuccessful( + mockPackage( + "my/pkg3", + /*targets=*/ ImmutableMap.of(), + ImmutableList.of( + Label.parseAbsoluteUnchecked("//load:1.bzl"), + Label.parseAbsoluteUnchecked("//load:2.bzl"), + Label.parseAbsoluteUnchecked("//load:3.bzl"))), + StarlarkSemantics.DEFAULT, + /*loadTimeNanos=*/ 100); + } + + @Test + public void testRecordsMostComputationStepsPerBuild_extrema() { + PackageMetricsRecorder recorder = new ExtremaPackageMetricsRecorder(2); + underTest.setPackageMetricsRecorder(recorder); + + recordComputationSteps(); + + assertThat(underTest.getPackageMetricsRecorder().getComputationSteps()) + .containsExactlyEntriesIn( + ImmutableMap.of( + PackageIdentifier.createInMainRepo("my/pkg1"), + 1000L, + PackageIdentifier.createInMainRepo("my/pkg2"), + 100L)) + .inOrder(); + recorder.loadingFinished(); + ; + assertAllMapsEmpty(recorder); + } + + @Test + public void testRecordsMostComputationStepsPerBuild_comlete() { + PackageMetricsRecorder recorder = new CompletePackageMetricsRecorder(); + underTest.setPackageMetricsRecorder(recorder); + + recordComputationSteps(); + + assertThat(underTest.getPackageMetricsRecorder().getComputationSteps()) + .containsExactly( + PackageIdentifier.createInMainRepo("my/pkg1"), + 1000L, + PackageIdentifier.createInMainRepo("my/pkg2"), + 100L, + PackageIdentifier.createInMainRepo("my/pkg3"), + 10L); + recorder.loadingFinished(); + ; + assertAllMapsEmpty(recorder); + } + + void recordComputationSteps() { + Package mockPackage1 = + mockPackage( + "my/pkg1", + /*targets=*/ ImmutableMap.of(), + /*starlarkDependencies=*/ ImmutableList.of()); + when(mockPackage1.getComputationSteps()).thenReturn(1000L); + underTest.onLoadingCompleteAndSuccessful( + mockPackage1, StarlarkSemantics.DEFAULT, /*loadTimeNanos=*/ 100); + + Package mockPackage2 = + mockPackage( + "my/pkg2", + /*targets=*/ ImmutableMap.of(), + /*starlarkDependencies=*/ ImmutableList.of()); + when(mockPackage2.getComputationSteps()).thenReturn(100L); + underTest.onLoadingCompleteAndSuccessful( + mockPackage2, StarlarkSemantics.DEFAULT, /*loadTimeNanos=*/ 100); + + Package mockPackage3 = + mockPackage( + "my/pkg3", + /*targets=*/ ImmutableMap.of(), + /*starlarkDependencies=*/ ImmutableList.of()); + when(mockPackage3.getComputationSteps()).thenReturn(10L); + underTest.onLoadingCompleteAndSuccessful( + mockPackage3, StarlarkSemantics.DEFAULT, /*loadTimeNanos=*/ 100); + } + + @Test + public void metricMap_extrema() { + PackageMetricsRecorder recorder = new ExtremaPackageMetricsRecorder(2); + underTest.setPackageMetricsRecorder(recorder); + + recordEverything(); + + PackageMetrics pkg1 = + PackageMetrics.newBuilder() + .setName("my/pkg1") + .setLoadDuration(Durations.fromMillis(42)) + .setComputationSteps(1000) + .setNumTargets(1) + .setNumTransitiveLoads(1) + .build(); + + PackageMetrics pkg2 = + PackageMetrics.newBuilder() + .setName("my/pkg2") + .setLoadDuration(Durations.fromMillis(43)) + .setComputationSteps(100) + .setNumTargets(2) + .setNumTransitiveLoads(2) + .build(); + + PackageMetrics pkg3 = + PackageMetrics.newBuilder() + .setName("my/pkg3") + .setLoadDuration(Durations.fromMillis(44)) + .setComputationSteps(10) + .setNumTargets(3) + .setNumTransitiveLoads(3) + .build(); + + assertThat(underTest.getPackageMetricsRecorder().getPackageMetrics()) + .containsExactly(pkg1, pkg2, pkg3); + recorder.loadingFinished(); + assertAllMapsEmpty(recorder); + } + + @Test + public void metricMap_complete() { + PackageMetricsRecorder recorder = new CompletePackageMetricsRecorder(); + underTest.setPackageMetricsRecorder(recorder); + + recordEverything(); + + PackageMetrics pkg1 = + PackageMetrics.newBuilder() + .setName("my/pkg1") + .setLoadDuration(Durations.fromMillis(42)) + .setComputationSteps(1000) + .setNumTargets(1) + .setNumTransitiveLoads(1) + .build(); + + PackageMetrics pkg2 = + PackageMetrics.newBuilder() + .setName("my/pkg2") + .setLoadDuration(Durations.fromMillis(43)) + .setComputationSteps(100) + .setNumTargets(2) + .setNumTransitiveLoads(2) + .build(); + + PackageMetrics pkg3 = + PackageMetrics.newBuilder() + .setName("my/pkg3") + .setLoadDuration(Durations.fromMillis(44)) + .setComputationSteps(10) + .setNumTargets(3) + .setNumTransitiveLoads(3) + .build(); + + assertThat(underTest.getPackageMetricsRecorder().getPackageMetrics()) + .containsExactly(pkg1, pkg2, pkg3); + recorder.loadingFinished(); + assertAllMapsEmpty(recorder); + } + + void recordEverything() { + Package mockPackage1 = + mockPackage( + "my/pkg1", + /*targets=*/ ImmutableMap.of("target1", mock(Target.class)), + /*starlarkDependencies=*/ ImmutableList.of( + Label.parseAbsoluteUnchecked("//load:1.bzl"))); + when(mockPackage1.getComputationSteps()).thenReturn(1000L); + underTest.onLoadingCompleteAndSuccessful( + mockPackage1, StarlarkSemantics.DEFAULT, /*loadTimeNanos=*/ 42_000_000); + + Package mockPackage2 = + mockPackage( + "my/pkg2", + /*targets=*/ ImmutableMap.of( + "target1", mock(Target.class), "target2", mock(Target.class)), + /*starlarkDependencies=*/ ImmutableList.of( + Label.parseAbsoluteUnchecked("//load:1.bzl"), + Label.parseAbsoluteUnchecked("//load:2.bzl"))); + when(mockPackage2.getComputationSteps()).thenReturn(100L); + underTest.onLoadingCompleteAndSuccessful( + mockPackage2, StarlarkSemantics.DEFAULT, /*loadTimeNanos=*/ 43_000_000); + + Package mockPackage3 = + mockPackage( + "my/pkg3", + /*targets=*/ ImmutableMap.of( + "target1", + mock(Target.class), + "target2", + mock(Target.class), + "target3", + mock(Target.class)), + /*starlarkDependencies=*/ ImmutableList.of( + Label.parseAbsoluteUnchecked("//load:1.bzl"), + Label.parseAbsoluteUnchecked("//load:2.bzl"), + Label.parseAbsoluteUnchecked("//load:3.bzl"))); + when(mockPackage3.getComputationSteps()).thenReturn(10L); + underTest.onLoadingCompleteAndSuccessful( + mockPackage3, StarlarkSemantics.DEFAULT, /*loadTimeNanos=*/ 44_000_000); + } + + @Test + public void testDoesntRecordAnythingWhenNumPackagesToTrackIsZero() { + PackageMetricsRecorder recorder = new ExtremaPackageMetricsRecorder(0); + underTest.setPackageMetricsRecorder(recorder); + + underTest.onLoadingCompleteAndSuccessful( + mockPackage( + "my/pkg1", + /*targets=*/ ImmutableMap.of(), + /*starlarkDependencies=*/ ImmutableList.of()), + StarlarkSemantics.DEFAULT, + /*loadTimeNanos=*/ 42_000_000); + + assertAllMapsEmpty(underTest.getPackageMetricsRecorder()); + } + + private static void assertAllMapsEmpty(PackageMetricsRecorder recorder) { + assertThat(recorder.getLoadTimes()).isEmpty(); + assertThat(recorder.getComputationSteps()).isEmpty(); + assertThat(recorder.getNumTargets()).isEmpty(); + assertThat(recorder.getNumTransitiveLoads()).isEmpty(); + } + + private static Package mockPackage( + String pkgIdString, Map<String, Target> targets, List<Label> starlarkDependencies) { + Package mockPackage = mock(Package.class); + when(mockPackage.getPackageIdentifier()) + .thenReturn(PackageIdentifier.createInMainRepo(pkgIdString)); + when(mockPackage.getTargets()).thenReturn(ImmutableSortedKeyMap.copyOf(targets)); + when(mockPackage.getStarlarkFileDependencies()) + .thenReturn(ImmutableList.copyOf(starlarkDependencies)); + return mockPackage; + } +}