aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java15
-rw-r--r--src/main/java/com/google/devtools/build/lib/runtime/SpawnStats.java106
-rw-r--r--src/test/java/com/google/devtools/build/lib/runtime/SpawnStatsTest.java160
-rw-r--r--src/test/shell/bazel/BUILD6
-rwxr-xr-xsrc/test/shell/bazel/bazel_spawnstats_test.sh85
5 files changed, 372 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java b/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java
index 927355d9a9..4d2cdcef78 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/BuildSummaryStatsModule.java
@@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.devtools.build.lib.actions.ActionKeyContext;
+import com.google.devtools.build.lib.actions.ActionResultReceivedEvent;
import com.google.devtools.build.lib.buildeventstream.BuildToolLogs;
import com.google.devtools.build.lib.buildtool.BuildRequest;
import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
@@ -50,11 +51,14 @@ public class BuildSummaryStatsModule extends BlazeModule {
private boolean enabled;
private boolean discardActions;
+ private SpawnStats spawnStats;
+
@Override
public void beforeCommand(CommandEnvironment env) {
this.reporter = env.getReporter();
this.eventBus = env.getEventBus();
this.actionKeyContext = env.getSkyframeExecutor().getActionKeyContext();
+ this.spawnStats = new SpawnStats();
eventBus.register(this);
}
@@ -63,6 +67,7 @@ public class BuildSummaryStatsModule extends BlazeModule {
this.criticalPathComputer = null;
this.eventBus = null;
this.reporter = null;
+ this.spawnStats = null;
}
@Override
@@ -81,6 +86,11 @@ public class BuildSummaryStatsModule extends BlazeModule {
}
@Subscribe
+ public void actionResultReceived(ActionResultReceivedEvent event) {
+ spawnStats.countActionResult(event.getActionResult());
+ }
+
+ @Subscribe
public void buildComplete(BuildCompleteEvent event) {
try {
// We might want to make this conditional on a flag; it can sometimes be a bit of a nuisance.
@@ -115,6 +125,11 @@ public class BuildSummaryStatsModule extends BlazeModule {
}
reporter.handle(Event.info(Joiner.on(", ").join(items)));
+
+ String spawnSummary = spawnStats.getSummary();
+ reporter.handle(Event.info(spawnSummary));
+ statistics.add(Pair.of("process stats", ByteString.copyFromUtf8(spawnSummary)));
+
reporter.post(new BuildToolLogs(statistics, ImmutableList.of()));
} finally {
criticalPathComputer = null;
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/SpawnStats.java b/src/main/java/com/google/devtools/build/lib/runtime/SpawnStats.java
new file mode 100644
index 0000000000..dcb68ca8a9
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/runtime/SpawnStats.java
@@ -0,0 +1,106 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.runtime;
+
+import com.google.common.collect.ConcurrentHashMultiset;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multiset;
+import com.google.devtools.build.lib.actions.ActionResult;
+import com.google.devtools.build.lib.actions.SpawnResult;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import javax.annotation.concurrent.ThreadSafe;
+
+/** Collects results from SpawnResult. */
+@ThreadSafe
+class SpawnStats {
+ private final ConcurrentHashMultiset<String> runners = ConcurrentHashMultiset.create();
+ private static final ImmutableList<String> REPORT_FIRST = ImmutableList.of("remote cache hit");
+
+ public void countActionResult(ActionResult actionResult) {
+ for (SpawnResult r : actionResult.spawnResults()) {
+ countRunnerName(r.getRunnerName());
+ }
+ }
+
+ public void countRunnerName(String runner) {
+ runners.add(runner);
+ }
+
+ private static class ResultString {
+ StringBuilder result = new StringBuilder();
+ String firstRunner;
+ int spawnsCount = 0;
+ int runnersNum = 0;
+
+ public int spawnsCount() {
+ return spawnsCount;
+ }
+
+ public void add(String name, int count) {
+ spawnsCount += count;
+ runnersNum += 1;
+
+ if (runnersNum == 1) {
+ firstRunner = name;
+ }
+
+ if (result.length() > 0) {
+ result.append(", ");
+ }
+ result.append(count);
+ result.append(" ");
+ result.append(name);
+ }
+
+ @Override
+ public String toString() {
+ if (runnersNum == 0) {
+ return "";
+ }
+ if (runnersNum == 1) {
+ return ", " + firstRunner;
+ }
+ return ": " + result;
+ }
+ }
+
+ /*
+ * Returns a human-readable summary of spawns counted.
+ */
+ public String getSummary() {
+ ResultString result = new ResultString();
+
+ // First report cache results.
+ for (String s : REPORT_FIRST) {
+ int count = runners.setCount(s, 0);
+ if (count > 0) {
+ result.add(s, count);
+ }
+ }
+
+ // Sort the rest alphabetically
+ ArrayList<Multiset.Entry<String>> list = new ArrayList<>(runners.entrySet());
+ Collections.sort(list, Comparator.comparing(e -> e.getElement()));
+
+ for (Multiset.Entry<String> e : list) {
+ result.add(e.getElement(), e.getCount());
+ }
+
+ int total = result.spawnsCount();
+ return total + " process" + (total == 1 ? "" : "es") + result + ".";
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/SpawnStatsTest.java b/src/test/java/com/google/devtools/build/lib/runtime/SpawnStatsTest.java
new file mode 100644
index 0000000000..968108db64
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/runtime/SpawnStatsTest.java
@@ -0,0 +1,160 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.runtime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.actions.ActionResult;
+import com.google.devtools.build.lib.actions.SpawnResult;
+import java.util.ArrayList;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Testing SpawnStats */
+@RunWith(JUnit4.class)
+public final class SpawnStatsTest {
+
+ SpawnStats stats;
+
+ @Before
+ public void setUp() {
+ stats = new SpawnStats();
+ }
+
+ @Test
+ public void emptySet() {
+ assertThat(stats.getSummary()).isEqualTo("0 processes.");
+ }
+
+ @Test
+ public void one() {
+ stats.countRunnerName("foo");
+ assertThat(stats.getSummary()).isEqualTo("1 process, foo.");
+ }
+
+ @Test
+ public void oneRemote() {
+ stats.countRunnerName("remote cache hit");
+ assertThat(stats.getSummary()).isEqualTo("1 process, remote cache hit.");
+ }
+
+ @Test
+ public void two() {
+ stats.countRunnerName("foo");
+ stats.countRunnerName("foo");
+ assertThat(stats.getSummary()).isEqualTo("2 processes, foo.");
+ }
+
+ @Test
+ public void order() {
+ stats.countRunnerName("a");
+ stats.countRunnerName("b");
+ stats.countRunnerName("b");
+ stats.countRunnerName("c");
+ stats.countRunnerName("c");
+ stats.countRunnerName("c");
+ assertThat(stats.getSummary()).isEqualTo("6 processes: 1 a, 2 b, 3 c.");
+ }
+
+ @Test
+ public void reverseOrder() {
+ stats.countRunnerName("a");
+ stats.countRunnerName("a");
+ stats.countRunnerName("a");
+ stats.countRunnerName("b");
+ stats.countRunnerName("b");
+ stats.countRunnerName("c");
+ assertThat(stats.getSummary()).isEqualTo("6 processes: 3 a, 2 b, 1 c.");
+ }
+
+ @Test
+ public void cacheFirst() {
+ stats.countRunnerName("a");
+ stats.countRunnerName("a");
+ stats.countRunnerName("a");
+ stats.countRunnerName("b");
+ stats.countRunnerName("remote cache hit");
+ stats.countRunnerName("b");
+ stats.countRunnerName("c");
+ assertThat(stats.getSummary()).isEqualTo("7 processes: 1 remote cache hit, 3 a, 2 b, 1 c.");
+ }
+
+ private final SpawnResult rA =
+ new SpawnResult.Builder().setStatus(SpawnResult.Status.SUCCESS).setRunnerName("abc").build();
+ private final SpawnResult rB =
+ new SpawnResult.Builder().setStatus(SpawnResult.Status.SUCCESS).setRunnerName("cde").build();
+
+ @Test
+ public void actionOneSpawn() {
+
+ ArrayList<SpawnResult> spawns = new ArrayList<>();
+ spawns.add(rA);
+
+ stats.countActionResult(ActionResult.create(spawns));
+ assertThat(stats.getSummary()).isEqualTo("1 process, abc.");
+ }
+
+ @Test
+ public void actionManySpawn() {
+ // Different spawns with the same runner count as one action
+
+ ArrayList<SpawnResult> spawns = new ArrayList<>();
+ spawns.add(rA);
+ spawns.add(rA);
+ spawns.add(rA);
+
+ stats.countActionResult(ActionResult.create(spawns));
+ assertThat(stats.getSummary()).isEqualTo("3 processes, abc.");
+ }
+
+ @Test
+ public void actionManySpawnMixed() {
+ // Different spawns mixed runners
+
+ ArrayList<SpawnResult> spawns = new ArrayList<>();
+ spawns.add(rA);
+ spawns.add(rA);
+ spawns.add(rB);
+
+ stats.countActionResult(ActionResult.create(spawns));
+ assertThat(stats.getSummary()).isEqualTo("3 processes: 2 abc, 1 cde.");
+ }
+
+ @Test
+ public void actionManyActionsMixed() {
+ // Five actions:
+ // abc
+ // abc, abc
+ // abc, abc, cde
+ // abc, abc, cde
+ // abc, abc, cde
+
+ ArrayList<SpawnResult> spawns = new ArrayList<>();
+ spawns.add(rA);
+ stats.countActionResult(ActionResult.create(spawns));
+
+ spawns.add(rA);
+ stats.countActionResult(ActionResult.create(spawns));
+
+ spawns.add(rB);
+ stats.countActionResult(ActionResult.create(spawns));
+ stats.countActionResult(ActionResult.create(spawns));
+ stats.countActionResult(ActionResult.create(spawns));
+
+ assertThat(stats.getSummary()).isEqualTo("12 processes: 9 abc, 3 cde.");
+ }
+}
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD
index d2c79390ad..9aab738386 100644
--- a/src/test/shell/bazel/BUILD
+++ b/src/test/shell/bazel/BUILD
@@ -154,6 +154,12 @@ sh_test(
)
sh_test(
+ name = "bazel_spawnstats_test",
+ srcs = ["bazel_spawnstats_test.sh"],
+ data = [":test-deps"],
+)
+
+sh_test(
name = "bazel_coverage_test",
srcs = ["bazel_coverage_test.sh"],
data = [":test-deps"],
diff --git a/src/test/shell/bazel/bazel_spawnstats_test.sh b/src/test/shell/bazel/bazel_spawnstats_test.sh
new file mode 100755
index 0000000000..958296f47f
--- /dev/null
+++ b/src/test/shell/bazel/bazel_spawnstats_test.sh
@@ -0,0 +1,85 @@
+#!/bin/bash
+#
+# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Test lightweight spawn stats generation in Bazel
+#
+
+set -eu
+
+# Load the test setup defined in the parent directory
+CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source "${CURRENT_DIR}/../integration_test_setup.sh" \
+ || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
+
+function set_up() {
+ cat > BUILD <<EOF
+genrule(
+ name = "foo",
+ cmd = "echo hello > \$@",
+ outs = ["foo.txt"],
+)
+EOF
+}
+
+function test_order() {
+ # Ensure the new stats are printed before Build completed
+ bazel build :foo 2>&1 | tee ${TEST_log} | sed -n '/process/,$p' | grep "Build complete" || fail "Expected \"process\" to be followed by \"Build completed\""
+}
+
+# Single execution of Bazel
+function statistics_single() {
+ flags=$1 # flags to pass to Bazel
+ expect=$2 # string to expect
+
+ echo "Starting single run for $flags $expect" &> $TEST_log
+ output=`bazel build :foo $flags 2>&1 | tee ${TEST_log} | grep " process" | tr -d '\r'`
+
+ if ! [[ $output =~ ${expect} ]]; then
+ fail "bazel ${flags}: Want |${expect}|, got |${output}| "
+ fi
+
+ echo "Done $flags $expect" &> $TEST_log
+}
+
+function test_local() {
+ statistics_single "--spawn_strategy=local" ", local"
+}
+
+function test_local_sandbox() {
+ if [[ "$PLATFORM" == "linux" ]]; then
+ statistics_single "--spawn_strategy=linux-sandbox" ", linux-sandbox"
+ fi
+}
+
+# We are correctly resetting the counts
+function test_repeat() {
+ flags="--spawn_strategy=local"
+ statistics_single $flags ", local"
+ bazel clean $flags
+ statistics_single $flags ", local"
+}
+
+# Locally cached results are not yet displayed
+function test_localcache() {
+ flags="--spawn_strategy=local"
+ # We are correctly resetting the counts
+ statistics_single $flags ", local"
+ statistics_single $flags "0 processes."
+}
+
+run_suite "bazel statistics tests"
+
+