Add aquery summary output formatter. For executed actions we already have a summary in the BEP, but we didn't have one for created actions. PiperOrigin-RevId: 384648645
diff --git a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java index d43add6..9d95713 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java +++ b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphQueryEnvironment.java
@@ -175,6 +175,8 @@ StreamedOutputHandler.OutputType.JSON, actionFilters), new ActionGraphTextOutputFormatterCallback( + eventHandler, aqueryOptions, out, skyframeExecutor, accessor, actionFilters), + new ActionGraphSummaryOutputFormatterCallback( eventHandler, aqueryOptions, out, skyframeExecutor, accessor, actionFilters)); }
diff --git a/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphSummaryOutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphSummaryOutputFormatterCallback.java new file mode 100644 index 0000000..e7b0031 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/query2/aquery/ActionGraphSummaryOutputFormatterCallback.java
@@ -0,0 +1,145 @@ +// 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.query2.aquery; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; +import com.google.devtools.build.lib.actions.ActionOwner; +import com.google.devtools.build.lib.analysis.AspectValue; +import com.google.devtools.build.lib.analysis.ConfiguredTargetValue; +import com.google.devtools.build.lib.buildeventstream.BuildEvent; +import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos; +import com.google.devtools.build.lib.events.ExtendedEventHandler; +import com.google.devtools.build.lib.packages.AspectDescriptor; +import com.google.devtools.build.lib.query2.engine.QueryEnvironment.TargetAccessor; +import com.google.devtools.build.lib.skyframe.RuleConfiguredTargetValue; +import com.google.devtools.build.lib.skyframe.SkyframeExecutor; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +/** Output callback for aquery, prints a human readable summary. */ +class ActionGraphSummaryOutputFormatterCallback extends AqueryThreadsafeCallback { + + private final AqueryActionFilter actionFilters; + private final Map<String, Integer> mnemonicToCount = new HashMap<>(); + private final Map<String, Integer> configurationToCount = new HashMap<>(); + private final Map<String, Integer> execPlatformToCount = new HashMap<>(); + private final Map<String, Integer> aspectToCount = new HashMap<>(); + + ActionGraphSummaryOutputFormatterCallback( + ExtendedEventHandler eventHandler, + AqueryOptions options, + OutputStream out, + SkyframeExecutor skyframeExecutor, + TargetAccessor<ConfiguredTargetValue> accessor, + AqueryActionFilter actionFilters) { + super(eventHandler, options, out, skyframeExecutor, accessor); + this.actionFilters = actionFilters; + } + + @Override + public String getName() { + return "summary"; + } + + @Override + public void processOutput(Iterable<ConfiguredTargetValue> partialResult) + throws IOException, InterruptedException { + // Enabling includeParamFiles should enable includeCommandline by default. + options.includeCommandline |= options.includeParamFiles; + + for (ConfiguredTargetValue configuredTargetValue : partialResult) { + if (!(configuredTargetValue instanceof RuleConfiguredTargetValue)) { + // We have to include non-rule values in the graph to visit their dependencies, but they + // don't have any actions to print out. + continue; + } + for (ActionAnalysisMetadata action : + ((RuleConfiguredTargetValue) configuredTargetValue).getActions()) { + processAction(action); + } + if (options.useAspects) { + for (AspectValue aspectValue : accessor.getAspectValues(configuredTargetValue)) { + for (ActionAnalysisMetadata action : aspectValue.getActions()) { + processAction(action); + } + } + } + } + } + + private void processAction(ActionAnalysisMetadata action) throws InterruptedException { + if (!AqueryUtils.matchesAqueryFilters(action, actionFilters)) { + return; + } + + mnemonicToCount.merge(action.getMnemonic(), 1, Integer::sum); + ActionOwner actionOwner = action.getOwner(); + if (actionOwner != null) { + BuildEvent configuration = actionOwner.getConfiguration(); + BuildEventStreamProtos.Configuration configProto = + configuration.asStreamProto(/*context=*/ null).getConfiguration(); + configurationToCount.merge(configProto.getMnemonic(), 1, Integer::sum); + + if (actionOwner.getExecutionPlatform() != null) { + execPlatformToCount.merge( + actionOwner.getExecutionPlatform().label().toString(), 1, Integer::sum); + } + + // In the case of aspect-on-aspect, AspectDescriptors are listed in + // topological order of the dependency graph. + // e.g. [A -> B] would imply that aspect A is applied on top of aspect B. + ImmutableList<AspectDescriptor> aspectDescriptors = + actionOwner.getAspectDescriptors().reverse(); + if (!aspectDescriptors.isEmpty()) { + aspectDescriptors.forEach( + aspectDescriptor -> + aspectToCount.merge(aspectDescriptor.getAspectClass().getName(), 1, Integer::sum)); + } + } + } + + @Override + public void close(boolean failFast) throws InterruptedException, IOException { + if (failFast) { + return; + } + + int totalActions = mnemonicToCount.values().stream().mapToInt(v -> v).sum(); + if (totalActions == 0) { + printStream.println("No actions matched."); + } else { + printStream.println(totalActions + " total action" + (totalActions == 1 ? "" : "s") + "."); + } + + printSummary(mnemonicToCount, "Mnemonics:"); + printSummary(configurationToCount, "Configurations:"); + printSummary(execPlatformToCount, "Execution Platforms:"); + printSummary(aspectToCount, "Aspects:"); + } + + private void printSummary(Map<String, Integer> actionsCount, String s) { + if (!actionsCount.isEmpty()) { + printStream.println(); + printStream.println(s); + actionsCount.entrySet().stream() + .sorted(Comparator.comparingInt(Entry::getValue)) + .forEach(entry -> printStream.println(" " + entry.getKey() + ": " + entry.getValue())); + } + } +}
diff --git a/src/test/shell/integration/aquery_test.sh b/src/test/shell/integration/aquery_test.sh index 59bf5d4..f51ee77 100755 --- a/src/test/shell/integration/aquery_test.sh +++ b/src/test/shell/integration/aquery_test.sh
@@ -1381,10 +1381,31 @@ bazel clean bazel build --experimental_aquery_dump_after_build_format=text --experimental_aquery_dump_after_build_output_file="$TEST_TMPDIR/foo.out" "//$pkg:foo" \ - &> "$TEST_log" && fail "Expected success" + &> "$TEST_log" && fail "Expected failure" expect_log "--skyframe_state must be used with --output=proto\|textproto\|jsonproto. Invalid aquery output format: text" } +function test_summary_output() { + local pkg="${FUNCNAME[0]}" + mkdir -p "$pkg" || fail "mkdir -p $pkg" + cat > "$pkg/BUILD" <<'EOF' +genrule( + name = "foo", + srcs = ["in"], + outs = ["out"], + cmd = "touch $(OUTS)", +) +EOF + touch "$pkg/in" + + bazel aquery --output=summary "//$pkg:all" \ + &> "$TEST_log" || fail "Expected success" + expect_log "1 total action" + expect_log "Genrule: 1" + # Not matching the full string here since it's OS dependent. + expect_log "-fastbuild: 1" +} + # Usage: assert_matches expected_pattern actual function assert_matches() { [[ "$2" =~ $1 ]] || fail "Expected to match '$1', was: $2"