Open source MetricsCollectorTest. PiperOrigin-RevId: 427686246
diff --git a/src/test/java/com/google/devtools/build/lib/metrics/BUILD b/src/test/java/com/google/devtools/build/lib/metrics/BUILD index 133fa2f..09b20c3 100644 --- a/src/test/java/com/google/devtools/build/lib/metrics/BUILD +++ b/src/test/java/com/google/devtools/build/lib/metrics/BUILD
@@ -15,7 +15,7 @@ java_test( name = "PostGCMemoryUseRecorderTest", size = "small", - srcs = glob(["*.java"]), + srcs = ["PostGCMemoryUseRecorderTest.java"], deps = [ "//src/main/java/com/google/devtools/build/lib/metrics:memory-use-recorder", "//src/test/java/com/google/devtools/build/lib/testutil", @@ -26,3 +26,21 @@ "//third_party:truth", ], ) + +java_test( + name = "MetricsCollectorTest", + srcs = ["MetricsCollectorTest.java"], + deps = [ + "//src/main/java/com/google/devtools/build/lib:runtime", + "//src/main/java/com/google/devtools/build/lib/actions", + "//src/main/java/com/google/devtools/build/lib/analysis:view_creation_failed_exception", + "//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto", + "//src/main/java/com/google/devtools/build/lib/metrics:event", + "//src/main/java/com/google/devtools/build/lib/metrics:metrics_module", + "//src/main/java/com/google/devtools/build/lib/util:os", + "//src/test/java/com/google/devtools/build/lib/buildtool/util", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +)
diff --git a/src/test/java/com/google/devtools/build/lib/metrics/MetricsCollectorTest.java b/src/test/java/com/google/devtools/build/lib/metrics/MetricsCollectorTest.java new file mode 100644 index 0000000..3690e8a --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/metrics/MetricsCollectorTest.java
@@ -0,0 +1,580 @@ +// Copyright 2022 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.metrics; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.eventbus.Subscribe; +import com.google.devtools.build.lib.actions.BuildFailedException; +import com.google.devtools.build.lib.analysis.ViewCreationFailedException; +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics; +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.ActionSummary.ActionData; +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.ArtifactMetrics; +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.BuildGraphMetrics; +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.CumulativeMetrics; +import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase; +import com.google.devtools.build.lib.runtime.BlazeModule; +import com.google.devtools.build.lib.runtime.BlazeRuntime; +import com.google.devtools.build.lib.runtime.CommandEnvironment; +import com.google.devtools.build.lib.util.OS; +import java.util.List; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests metric collection. */ +@RunWith(JUnit4.class) +public class MetricsCollectorTest extends BuildIntegrationTestCase { + + static class BuildMetricsEventListener extends BlazeModule { + + private BuildMetricsEvent event; + + @Override + public void beforeCommand(CommandEnvironment env) { + env.getEventBus().register(this); + } + + @Subscribe + public void onBuildMetrics(BuildMetricsEvent event) { + this.event = event; + } + } + + private BuildMetricsEventListener buildMetricsEventListener = new BuildMetricsEventListener(); + + @Override + protected BlazeRuntime.Builder getRuntimeBuilder() throws Exception { + return super.getRuntimeBuilder() + .addBlazeModule(new MetricsModule()) + .addBlazeModule(buildMetricsEventListener); + } + + @Before + public void writeTrivialFooTarget() throws Exception { + write( + "foo/BUILD", + "genrule(", + " name = 'foo',", + " outs = ['dir'],", + " cmd = '/bin/mkdir $(location dir)',", + " srcs = [],", + ")"); + } + + @Test + public void testActionsCreated() throws Exception { + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getActionSummary().getActionsCreated()).isGreaterThan(0L); + } + + @Test + public void testActionsCreatedIsZeroOnSecondBuild() throws Exception { + buildTarget("//foo:foo"); + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getActionSummary().getActionsCreated()).isEqualTo(0); + } + + @Test + public void testActionsExecuted() throws Exception { + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getActionSummary().getActionsExecuted()).isGreaterThan(0L); + } + + @Test + public void testActionsExecutedIsZeroOnSecondBuild() throws Exception { + buildTarget("//foo:foo"); + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getActionSummary().getActionsExecuted()).isEqualTo(0); + } + + @Test + public void buildGraphAndArtifactMetrics() throws Exception { + write( + "a/BUILD", + "genrule(name = 'a', srcs = ['//b:b', '//b:c'], outs = ['a.out'], cmd = 'cat $(SRCS) >" + + " $@')"); + write( + "b/BUILD", + "genrule(name = 'b', srcs = ['b.in', 'c.in'], outs = ['b.out'], cmd = 'cat $(SRCS) > $@')", + "genrule(name = 'c', srcs = ['c.in', 'c2.in'], outs = ['c.out'], cmd = 'cat $(SRCS) >" + + " $@')"); + if (OS.getCurrent() == OS.WINDOWS) { + // On Windows we have \r\n line endings while on other platforms only \n. So make the file one + // byte shorter on Windows so that the byte counts below match. + write("b/b.in", "1234"); + write("b/c.in", "1"); + } else { + write("b/b.in", "12345"); + write("b/c.in", "12"); + } + createSymlink("c.in", "b/c2.in"); + write( + "e/BUILD", + "alias(name = 'facade', actual = ':e.out')", + "genrule(name = 'e', srcs = ['e.in'], outs = ['e.out'], cmd = 'cat $(SRCS) > $@')"); + if (OS.getCurrent() == OS.WINDOWS) { + // On Windows we have \r\n line endings while on other platforms only \n. So make the file one + // byte shorter on Windows so that the byte counts below match. + write("e/e.in", "ab"); + } else { + write("e/e.in", "abc"); + } + + // Do one build of a target in a standalone package. Gets us a baseline for analysis/execution. + buildTarget("//e:facade"); + BuildGraphMetrics buildGraphMetrics = + buildMetricsEventListener.event.getBuildMetrics().getBuildGraphMetrics(); + int actionLookupValueCount = buildGraphMetrics.getActionLookupValueCount(); + // All these numbers should be big, but just want a basic check. + assertThat(actionLookupValueCount).isGreaterThan(0); + assertThat(buildGraphMetrics.getActionLookupValueCountNotIncludingAspects()) + .isEqualTo(actionLookupValueCount); + int actionCount = buildGraphMetrics.getActionCount(); + assertThat(actionCount).isGreaterThan(0); + assertThat(buildGraphMetrics.getActionCountNotIncludingAspects()).isEqualTo(actionCount); + assertThat(buildGraphMetrics) + .comparingExpectedFieldsOnly() + .isEqualTo( + BuildGraphMetrics.newBuilder() + .setOutputFileConfiguredTargetCount(1) + .setOtherConfiguredTargetCount(1) + .build()); + int outputArtifactCount = buildGraphMetrics.getOutputArtifactCount(); + assertThat(outputArtifactCount).isGreaterThan(0); + int graphSize = buildGraphMetrics.getPostInvocationSkyframeNodeCount(); + assertThat(graphSize).isGreaterThan(0); + ArtifactMetrics artifactMetrics = + buildMetricsEventListener.event.getBuildMetrics().getArtifactMetrics(); + assertThat(artifactMetrics.getSourceArtifactsRead().getSizeInBytes()).isGreaterThan(0L); + assertThat(artifactMetrics.getOutputArtifactsSeen()) + .isEqualTo(ArtifactMetrics.FilesMetric.newBuilder().setSizeInBytes(4L).setCount(3).build()); + assertThat(artifactMetrics.getOutputArtifactsFromActionCache().getCount()).isEqualTo(0); + assertThat(artifactMetrics.getTopLevelArtifacts()) + .isEqualTo(ArtifactMetrics.FilesMetric.newBuilder().setSizeInBytes(4L).setCount(1).build()); + // Adjust for the "alias", "input" and "output" configured targets, which won't be in play + // later. + actionLookupValueCount -= 3; + + // Now do a build of a target with non-trivial transitive deps, and verify the metrics. Blaze + // won't redo analysis of dependencies or re-read their sources. + buildTarget("//a"); + + buildGraphMetrics = buildMetricsEventListener.event.getBuildMetrics().getBuildGraphMetrics(); + assertThat(buildGraphMetrics) + .comparingExpectedFieldsOnly() + .isEqualTo( + BuildGraphMetrics.newBuilder() + // Two dependencies and three source files for action lookup values. + .setActionLookupValueCount(5 + actionLookupValueCount) + .setActionCount(2 + actionCount) + .setInputFileConfiguredTargetCount(4) + .setOutputArtifactCount(2 + outputArtifactCount) + .build()); + + int newGraphSize = buildGraphMetrics.getPostInvocationSkyframeNodeCount(); + assertThat(newGraphSize).isGreaterThan(graphSize); + + assertThat(buildMetricsEventListener.event.getBuildMetrics().getArtifactMetrics()) + .ignoringFieldAbsence() + .isEqualTo( + ArtifactMetrics.newBuilder() + // 2 distinct artifacts of 6 and 3 bytes, with a symlink to the 3-byte one. + .setSourceArtifactsRead( + ArtifactMetrics.FilesMetric.newBuilder().setSizeInBytes(12).setCount(3)) + // b outputs 9 bytes, c outputs 6, a outputs 15, 30 total. + .setOutputArtifactsSeen( + ArtifactMetrics.FilesMetric.newBuilder().setSizeInBytes(30).setCount(3).build()) + .setTopLevelArtifacts( + ArtifactMetrics.FilesMetric.newBuilder().setSizeInBytes(15).setCount(1).build()) + .build()); + + // Do a null build. No useful analysis stats. + buildTarget("//a"); + + assertThat(buildMetricsEventListener.event.getBuildMetrics().getBuildGraphMetrics()) + .ignoringFieldAbsence() + .isEqualTo( + BuildGraphMetrics.newBuilder() + .setPostInvocationSkyframeNodeCount(newGraphSize) + .build()); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getArtifactMetrics()) + .ignoringFieldAbsence() + .isEqualTo(ArtifactMetrics.getDefaultInstance()); + + // Change a BUILD file and rebuild: no source artifacts read, but analysis stats present. + write( + "a/BUILD", + "genrule(name = 'a', srcs = ['//b:c', '//b:b'], outs = ['a.out'], cmd = 'cat $(SRCS) >" + + " $@')"); + buildTarget("//a"); + + assertThat(buildMetricsEventListener.event.getBuildMetrics().getBuildGraphMetrics()) + .isEqualTo( + BuildGraphMetrics.newBuilder() + .setActionLookupValueCount(5 + actionLookupValueCount) + .setActionLookupValueCountNotIncludingAspects(5 + actionLookupValueCount) + .setActionCount(2 + actionCount) + .setActionCountNotIncludingAspects(2 + actionCount) + .setInputFileConfiguredTargetCount(4) + .setOutputArtifactCount(2 + outputArtifactCount) + // ArtifactNestedSet node for stale nested set is still in graph, since it is + // technically still valid (even though nobody wants that nested set anymore). + .setPostInvocationSkyframeNodeCount(newGraphSize + 1) + .build()); + ArtifactMetrics.FilesMetric singleFileMetric = + ArtifactMetrics.FilesMetric.newBuilder().setSizeInBytes(15L).setCount(1).build(); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getArtifactMetrics()) + .ignoringFieldAbsence() + .isEqualTo( + ArtifactMetrics.newBuilder() + .setOutputArtifactsSeen(singleFileMetric) + .setTopLevelArtifacts(singleFileMetric) + .build()); + + // Change BUILD file back, but don't do a full build. + write( + "a/BUILD", + "genrule(name = 'a', srcs = ['//b:c', '//b:b'], outs = ['a.out'], cmd = 'cat $(SRCS) >" + + " $@')"); + addOptions("--nobuild"); + buildTarget("//a"); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getBuildGraphMetrics()) + .isEqualTo( + BuildGraphMetrics.newBuilder() + .setActionLookupValueCount(5 + actionLookupValueCount) + .setActionLookupValueCountNotIncludingAspects(5 + actionLookupValueCount) + .setActionCount(2 + actionCount) + .setActionCountNotIncludingAspects(2 + actionCount) + .setInputFileConfiguredTargetCount(4) + .setOutputArtifactCount(2 + outputArtifactCount) + .setPostInvocationSkyframeNodeCount(newGraphSize + 1) + .build()); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getArtifactMetrics()) + .ignoringFieldAbsence() + .isEqualTo(ArtifactMetrics.getDefaultInstance()); + + // Null --nobuild. + buildTarget("//a"); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getBuildGraphMetrics()) + .ignoringFieldAbsence() + .isEqualTo( + BuildGraphMetrics.newBuilder() + // Stale action execution nodes have been GC'ed. + .setPostInvocationSkyframeNodeCount(newGraphSize - 1) + .build()); + + // Do a null full build. Back to baseline. + addOptions("--build"); + buildTarget("//a"); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getBuildGraphMetrics()) + .ignoringFieldAbsence() + .isEqualTo( + BuildGraphMetrics.newBuilder() + // We now have three copies of the ArtifactNestedSetKey, since the re-analysis + // didn't re-use the old nested set. + .setPostInvocationSkyframeNodeCount(newGraphSize + 2) + .build()); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getArtifactMetrics()) + .ignoringFieldAbsence() + .isEqualTo( + ArtifactMetrics.newBuilder() + .setOutputArtifactsSeen(singleFileMetric) + .setOutputArtifactsFromActionCache(singleFileMetric) + .setTopLevelArtifacts(singleFileMetric) + .build()); + + // Change a source file. It and its symlink are both re-read. + write("b/c.in", "1234"); + buildTarget("//a"); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getBuildGraphMetrics()) + .ignoringFieldAbsence() + .isEqualTo( + BuildGraphMetrics.newBuilder() + // Analysis not re-triggered, even of the input file that was changed. + .setInputFileConfiguredTargetCount(0) + .setPostInvocationSkyframeNodeCount(newGraphSize + 2) + .build()); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getArtifactMetrics()) + .ignoringFieldAbsence() + .isEqualTo( + ArtifactMetrics.newBuilder() + .setSourceArtifactsRead( + ArtifactMetrics.FilesMetric.newBuilder() + .setSizeInBytes(OS.getCurrent() == OS.WINDOWS ? 6 : 10) + .setCount(OS.getCurrent() == OS.WINDOWS ? 1 : 2)) + .setOutputArtifactsSeen( + ArtifactMetrics.FilesMetric.newBuilder() + .setSizeInBytes(42L) + .setCount(3) + .build()) + .setTopLevelArtifacts( + ArtifactMetrics.FilesMetric.newBuilder() + .setSizeInBytes(21L) + .setCount(1) + .build()) + .build()); + } + + @Test + public void treeArtifactAndTopLevelMetrics() throws Exception { + write( + "foo/tree_artifact_rule.bzl", + "def _tree_artifact_files_impl(ctx):", + " directory = ctx.actions.declare_directory(ctx.attr.name + '_artifact')", + " ctx.actions.run_shell(", + " outputs = [directory],", + " command = 'cd %s && echo a > file1 && echo bcde > file2}' % (directory.path))", + " return [DefaultInfo(files = depset([directory]))]", + "def _several_outputs_impl(ctx):", + " file = ctx.actions.declare_file(ctx.attr.name + '_file')", + " ctx.actions.write(output = file, content = 'abc')", + " return [DefaultInfo(files = depset([file])),", + " OutputGroupInfo(dep_files = ctx.attr.dep[DefaultInfo].files)]", + "my_tree = rule(implementation = _tree_artifact_files_impl)", + "my_rule = rule(", + " implementation = _several_outputs_impl,", + " attrs = { 'dep': attr.label()},", + " )"); + write( + "foo/BUILD", + "load('//foo:tree_artifact_rule.bzl', 'my_rule', 'my_tree')", + "my_tree(name = 'tree')", + "my_rule(name = 'top', dep = ':tree')"); + // Null build to populate silly things like fake build-info artifact. + buildTarget(); + addOptions("--output_groups=+dep_files"); + buildTarget("//foo:top"); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getArtifactMetrics()) + .ignoringFieldAbsence() + .isEqualTo( + ArtifactMetrics.newBuilder() + .setOutputArtifactsSeen( + ArtifactMetrics.FilesMetric.newBuilder() + .setSizeInBytes(10L) + .setCount(3) + .build()) + .setTopLevelArtifacts( + ArtifactMetrics.FilesMetric.newBuilder() + .setSizeInBytes(10L) + .setCount(3) + .build()) + .build()); + } + + @Test + public void testTargetCounts() throws Exception { + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getTargetMetrics().getTargetsConfigured()).isGreaterThan(0L); + assertThat(buildMetrics.getTargetMetrics().getTargetsConfiguredNotIncludingAspects()) + .isGreaterThan(0L); + } + + @Test + public void testTargetsCountsAreZeroOnSecondBuild() throws Exception { + buildTarget("//foo:foo"); + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getTargetMetrics().getTargetsLoaded()).isEqualTo(0); + assertThat(buildMetrics.getTargetMetrics().getTargetsConfigured()).isEqualTo(0); + } + + @Test + public void aspectLoadedMetric() throws Exception { + write( + "foo/foo.bzl", + "def _aspect_impl(target, ctx):", + " outfile = ctx.actions.declare_file(ctx.rule.attr.name + 'aspect.out')", + " ctx.actions.run_shell(", + " outputs = [outfile],", + " command = 'echo \"1\" > ' + outfile.path,", + " )", + " return [OutputGroupInfo(files = [outfile])]", + "", + "def _impl(ctx):", + " return []", + "", + "rule_aspect = aspect(implementation = _aspect_impl, attr_aspects = ['deps'])", + "", + "aspected = rule(", + " implementation = _impl,", + " attrs = { 'deps': attr.label_list(aspects = [rule_aspect]) })"); + write( + "foo/BUILD", + "load('//foo:foo.bzl', 'aspected')", + "aspected(name = 'top', deps = [':dep'])", + "aspected(name = 'dep')"); + buildTarget("//foo:top"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + // 2 additional: aspect and workspace status action. + assertThat(buildMetrics.getTargetMetrics().getTargetsConfigured()) + .isEqualTo(2L + buildMetrics.getTargetMetrics().getTargetsConfiguredNotIncludingAspects()); + assertThat(buildMetrics.getActionSummary().getActionsCreated()) + .isEqualTo(2L + buildMetrics.getActionSummary().getActionsCreatedNotIncludingAspects()); + // Traversing the Skyframe graph doesn't hit the workspace status action. + assertThat(buildMetrics.getBuildGraphMetrics().getActionLookupValueCount()) + .isEqualTo( + 1L + + buildMetrics + .getBuildGraphMetrics() + .getActionLookupValueCountNotIncludingAspects()); + assertThat(buildMetrics.getBuildGraphMetrics().getActionCount()) + .isEqualTo(1L + buildMetrics.getBuildGraphMetrics().getActionCountNotIncludingAspects()); + + // Analyzing a new target makes the aspect drop out of the target metric, but the build graph + // metric still knows about it. + write("bar/BUILD", "genrule(name = 'bar', outs = ['out'], cmd = 'touch $@')"); + buildTarget("//foo:top", "//bar:bar"); + buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getTargetMetrics().getTargetsConfigured()) + .isEqualTo(buildMetrics.getTargetMetrics().getTargetsConfiguredNotIncludingAspects()); + assertThat(buildMetrics.getActionSummary().getActionsCreated()) + .isEqualTo(buildMetrics.getActionSummary().getActionsCreatedNotIncludingAspects()); + assertThat(buildMetrics.getBuildGraphMetrics().getActionLookupValueCount()) + .isEqualTo( + 1L + + buildMetrics + .getBuildGraphMetrics() + .getActionLookupValueCountNotIncludingAspects()); + assertThat(buildMetrics.getBuildGraphMetrics().getActionCount()) + .isEqualTo(1L + buildMetrics.getBuildGraphMetrics().getActionCountNotIncludingAspects()); + } + + @Test + public void testPackagesLoaded() throws Exception { + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getPackageMetrics().getPackagesLoaded()).isGreaterThan(0L); + } + + @Test + public void testPackagesLoadedIsZeroOnSecondBuild() throws Exception { + buildTarget("//foo:foo"); + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getPackageMetrics().getPackagesLoaded()).isEqualTo(0); + } + + @Test + public void testAnalysisTimeInMs() throws Exception { + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getTimingMetrics().getAnalysisPhaseTimeInMs()).isGreaterThan(0); + } + + @Test + public void testUsedHeapSizePostBuild() throws Exception { + // TODO(bazel-team): Fix recording used heap size on Windows. + Assume.assumeTrue(OS.getCurrent() != OS.WINDOWS); + addOptions("--memory_profile=/dev/null"); + + // The options from above do not get added to the initial command environment, + // so it has to be recreated here. + runtimeWrapper.initializeOptionsParser(); + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getMemoryMetrics().getUsedHeapSizePostBuild()).isGreaterThan(0L); + // Note that we cannot test peak heap size here since the tiny builds that we do here don't + // trigger a full GC. + } + + @Test + public void testUsedHeapSizePostBuildCollectionOffByDefault() throws Exception { + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getMemoryMetrics().getUsedHeapSizePostBuild()).isEqualTo(0); + assertThat(buildMetrics.getMemoryMetrics().getPeakPostGcHeapSize()).isEqualTo(0); + } + + @Test + public void testWallTimePostBuild() throws Exception { + buildTarget("//foo:foo"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + assertThat(buildMetrics.getTimingMetrics().getWallTimeInMs()).isGreaterThan(0); + } + + @Test + public void cumulativeMetrics() throws Exception { + buildTarget("//foo:foo"); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getCumulativeMetrics()) + .isEqualTo(CumulativeMetrics.newBuilder().setNumAnalyses(1).setNumBuilds(1).build()); + + addOptions("--nobuild"); + buildTarget("//foo:foo"); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getCumulativeMetrics()) + .isEqualTo(CumulativeMetrics.newBuilder().setNumAnalyses(2).setNumBuilds(1).build()); + + addOptions("--build", "--noanalyze"); + buildTarget("//foo:foo"); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getCumulativeMetrics()) + .isEqualTo(CumulativeMetrics.newBuilder().setNumAnalyses(2).setNumBuilds(1).build()); + + write( + "foo/BUILD", + "genrule(", + " name = 'foo',", + " outs = ['dir'],", + " srcs = ['//noexist:noexist'],", + " cmd = '/bin/mkdir $(location dir)',", + ")"); + + addOptions("--analyze"); + assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:foo")); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getCumulativeMetrics()) + .isEqualTo(CumulativeMetrics.newBuilder().setNumAnalyses(3).setNumBuilds(1).build()); + + write( + "foo/BUILD", + "genrule(", + " name = 'foo',", + " outs = ['dir'],", + " cmd = '/bin/false',", + ")"); + + assertThrows(BuildFailedException.class, () -> buildTarget("//foo:foo")); + assertThat(buildMetricsEventListener.event.getBuildMetrics().getCumulativeMetrics()) + .isEqualTo(CumulativeMetrics.newBuilder().setNumAnalyses(4).setNumBuilds(2).build()); + } + + @Test + public void testActionData() throws Exception { + write( + "bar/BUILD", + "genrule(name='bar', srcs=[':dep1',':dep2'], outs=['out'], cmd='touch $@')", + "genrule(name='dep1', outs=['out1'], cmd='touch $@')", + "genrule(name='dep2', outs=['out2'], cmd='touch $@')"); + buildTarget("//bar"); + BuildMetrics buildMetrics = buildMetricsEventListener.event.getBuildMetrics(); + List<ActionData> actionDataList = buildMetrics.getActionSummary().getActionDataList(); + assertThat(actionDataList).hasSize(2); + assertThat(actionDataList.get(0).getMnemonic()).isEqualTo("Genrule"); + assertThat(actionDataList.get(0).getActionsExecuted()).isEqualTo(3); + + assertThat(actionDataList.get(1).getMnemonic()).isEqualTo("DummyBuildInfoAction"); + assertThat(actionDataList.get(1).getActionsExecuted()).isEqualTo(1); + + for (ActionData actionData : actionDataList) { + assertThat(actionData.getFirstStartedMs()).isAtMost(actionData.getLastEndedMs()); + } + } +}