// Copyright 2015 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.commands; import static java.util.stream.Collectors.toList; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.PackageFactory; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.profiler.memory.AllocationTracker; import com.google.devtools.build.lib.profiler.memory.AllocationTracker.RuleBytes; import com.google.devtools.build.lib.runtime.BlazeCommand; import com.google.devtools.build.lib.runtime.BlazeCommandUtils; import com.google.devtools.build.lib.runtime.BlazeRuntime; import com.google.devtools.build.lib.runtime.BlazeWorkspace; import com.google.devtools.build.lib.runtime.Command; import com.google.devtools.build.lib.runtime.CommandEnvironment; import com.google.devtools.build.lib.skyframe.SkyframeExecutor; import com.google.devtools.build.lib.skyframe.SkyframeExecutor.RuleStat; import com.google.devtools.build.lib.util.ExitCode; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.common.options.EnumConverter; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionDocumentationCategory; import com.google.devtools.common.options.OptionEffectTag; import com.google.devtools.common.options.OptionsBase; import com.google.devtools.common.options.OptionsParser; import com.google.devtools.common.options.OptionsProvider; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; /** Implementation of the dump command. */ @Command( allowResidue = false, mustRunInWorkspace = false, options = {DumpCommand.DumpOptions.class}, help = "Usage: %{product} dump \n" + "Dumps the internal state of the %{product} server process. This command is provided " + "as an aid to debugging, not as a stable interface, so users should not try to " + "parse the output; instead, use 'query' or 'info' for this purpose.\n%{options}", name = "dump", shortDescription = "Dumps the internal state of the %{product} server process." ) public class DumpCommand implements BlazeCommand { /** * NB! Any changes to this class must be kept in sync with anyOutput variable * value in the {@link DumpCommand#exec(CommandEnvironment,OptionsProvider)} method below. */ public static class DumpOptions extends OptionsBase { @Option( name = "packages", defaultValue = "false", category = "verbosity", documentationCategory = OptionDocumentationCategory.OUTPUT_SELECTION, effectTags = {OptionEffectTag.BAZEL_MONITORING}, help = "Dump package cache content." ) public boolean dumpPackages; @Option( name = "vfs", defaultValue = "false", category = "verbosity", documentationCategory = OptionDocumentationCategory.OUTPUT_SELECTION, effectTags = {OptionEffectTag.BAZEL_MONITORING}, help = "Dump virtual filesystem cache content." ) public boolean dumpVfs; @Option( name = "action_cache", defaultValue = "false", category = "verbosity", documentationCategory = OptionDocumentationCategory.OUTPUT_SELECTION, effectTags = {OptionEffectTag.BAZEL_MONITORING}, help = "Dump action cache content." ) public boolean dumpActionCache; @Option( name = "rule_classes", defaultValue = "false", category = "verbosity", documentationCategory = OptionDocumentationCategory.OUTPUT_SELECTION, effectTags = {OptionEffectTag.BAZEL_MONITORING}, help = "Dump rule classes." ) public boolean dumpRuleClasses; @Option( name = "rules", defaultValue = "false", category = "verbosity", documentationCategory = OptionDocumentationCategory.OUTPUT_SELECTION, effectTags = {OptionEffectTag.BAZEL_MONITORING}, help = "Dump rules, including counts and memory usage (if memory is tracked)." ) public boolean dumpRules; @Option( name = "skylark_memory", defaultValue = "null", category = "verbosity", documentationCategory = OptionDocumentationCategory.OUTPUT_SELECTION, effectTags = {OptionEffectTag.BAZEL_MONITORING}, help = "Dumps a pprof-compatible memory profile to the specified path." + " To learn more please see pprof." ) public String skylarkMemory; @Option( name = "skyframe", defaultValue = "off", category = "verbosity", converter = SkyframeDumpEnumConverter.class, documentationCategory = OptionDocumentationCategory.OUTPUT_SELECTION, effectTags = {OptionEffectTag.BAZEL_MONITORING}, help = "Dump Skyframe graph: 'off', 'summary', or 'detailed'." ) public SkyframeDumpOption dumpSkyframe; } /** * Different ways to dump information about Skyframe. */ public enum SkyframeDumpOption { OFF, SUMMARY, DETAILED; } /** * Enum converter for SkyframeDumpOption. */ public static class SkyframeDumpEnumConverter extends EnumConverter { public SkyframeDumpEnumConverter() { super(SkyframeDumpOption.class, "Skyframe Dump option"); } } @Override public void editOptions(OptionsParser optionsParser) {} @Override public ExitCode exec(CommandEnvironment env, OptionsProvider options) { BlazeRuntime runtime = env.getRuntime(); DumpOptions dumpOptions = options.getOptions(DumpOptions.class); boolean anyOutput = dumpOptions.dumpPackages || dumpOptions.dumpVfs || dumpOptions.dumpActionCache || dumpOptions.dumpRuleClasses || dumpOptions.dumpRules || dumpOptions.skylarkMemory != null || (dumpOptions.dumpSkyframe != SkyframeDumpOption.OFF); if (!anyOutput) { Map categories = new HashMap<>(); categories.put("verbosity", "Options that control what internal state is dumped"); Collection> optionList = new ArrayList<>(); optionList.add(DumpOptions.class); env.getReporter().getOutErr().printErrLn(BlazeCommandUtils.expandHelpTopic( getClass().getAnnotation(Command.class).name(), getClass().getAnnotation(Command.class).help(), getClass(), optionList, categories, OptionsParser.HelpVerbosity.LONG, runtime.getProductName())); return ExitCode.ANALYSIS_FAILURE; } PrintStream out = new PrintStream(env.getReporter().getOutErr().getOutputStream()); try { out.println("Warning: this information is intended for consumption by developers"); out.println("only, and may change at any time. Script against it at your own risk!"); out.println(); boolean success = true; if (dumpOptions.dumpPackages) { env.getPackageManager().dump(out); out.println(); } if (dumpOptions.dumpVfs) { out.println("Filesystem cache"); FileSystemUtils.dump(env.getOutputBase().getFileSystem(), out); out.println(); } if (dumpOptions.dumpActionCache) { success &= dumpActionCache(env, out); out.println(); } if (dumpOptions.dumpRuleClasses) { dumpRuleClasses(runtime, out); out.println(); } if (dumpOptions.dumpRules) { dumpRuleStats(env.getBlazeWorkspace(), env.getSkyframeExecutor(), out); out.println(); } if (dumpOptions.skylarkMemory != null) { try { dumpSkylarkHeap(env.getBlazeWorkspace(), dumpOptions.skylarkMemory, out); } catch (IOException e) { env.getReporter().error(null, "Could not dump skylark memory", e); } } if (dumpOptions.dumpSkyframe != SkyframeDumpOption.OFF) { success &= dumpSkyframe( env.getSkyframeExecutor(), dumpOptions.dumpSkyframe == SkyframeDumpOption.SUMMARY, out); out.println(); } return success ? ExitCode.SUCCESS : ExitCode.ANALYSIS_FAILURE; } finally { out.flush(); } } private boolean dumpActionCache(CommandEnvironment env, PrintStream out) { try { env.getPersistentActionCache().dump(out); } catch (IOException e) { env.getReporter().handle(Event.error("Cannot dump action cache: " + e.getMessage())); return false; } return true; } private boolean dumpSkyframe(SkyframeExecutor executor, boolean summarize, PrintStream out) { executor.dump(summarize, out); return true; } private void dumpRuleClasses(BlazeRuntime runtime, PrintStream out) { PackageFactory factory = runtime.getPackageFactory(); List ruleClassNames = new ArrayList<>(factory.getRuleClassNames()); Collections.sort(ruleClassNames); for (String name : ruleClassNames) { if (name.startsWith("$")) { continue; } RuleClass ruleClass = factory.getRuleClass(name); out.print(ruleClass + "("); boolean first = true; for (Attribute attribute : ruleClass.getAttributes()) { if (attribute.isImplicit()) { continue; } if (first) { first = false; } else { out.print(", "); } out.print(attribute.getName()); } out.println(")"); } } private void dumpRuleStats(BlazeWorkspace workspace, SkyframeExecutor executor, PrintStream out) { List ruleStats = executor.getRuleStats(); if (ruleStats.isEmpty()) { out.print("No rules in Blaze server, please run a build command first."); return; } List rules = ruleStats.stream().filter(RuleStat::isRule).collect(toList()); List aspects = ruleStats.stream().filter(r -> !r.isRule()).collect(toList()); Map ruleBytes = new HashMap<>(); Map aspectBytes = new HashMap<>(); AllocationTracker allocationTracker = workspace.getAllocationTracker(); if (allocationTracker != null) { allocationTracker.getRuleMemoryConsumption(ruleBytes, aspectBytes); } printRuleStatsOfType(rules, "RULE", out, ruleBytes, allocationTracker != null); printRuleStatsOfType(aspects, "ASPECT", out, aspectBytes, allocationTracker != null); } private static void printRuleStatsOfType( List ruleStats, String type, PrintStream out, Map ruleToBytes, boolean bytesEnabled) { if (ruleStats.isEmpty()) { return; } ruleStats.sort(Comparator.comparing(RuleStat::getCount).reversed()); int longestName = ruleStats.stream().map(r -> r.getName().length()).max(Integer::compareTo).get(); int maxNameWidth = 30; int nameColumnWidth = Math.min(longestName, maxNameWidth); int numberColumnWidth = 10; int bytesColumnWidth = 13; int eachColumnWidth = 11; printWithPadding(out, type, nameColumnWidth); printWithPaddingBefore(out, "COUNT", numberColumnWidth); printWithPaddingBefore(out, "ACTIONS", numberColumnWidth); if (bytesEnabled) { printWithPaddingBefore(out, "BYTES", bytesColumnWidth); printWithPaddingBefore(out, "EACH", eachColumnWidth); } out.println(); for (RuleStat ruleStat : ruleStats) { printWithPadding( out, truncateName(ruleStat.getName(), ruleStat.isRule(), maxNameWidth), nameColumnWidth); printWithPaddingBefore(out, formatLong(ruleStat.getCount()), numberColumnWidth); printWithPaddingBefore(out, formatLong(ruleStat.getActionCount()), numberColumnWidth); if (bytesEnabled) { RuleBytes ruleBytes = ruleToBytes.get(ruleStat.getKey()); long bytes = ruleBytes != null ? ruleBytes.getBytes() : 0L; printWithPaddingBefore(out, formatLong(bytes), bytesColumnWidth); printWithPaddingBefore(out, formatLong(bytes / ruleStat.getCount()), eachColumnWidth); } out.println(); } out.println(); } private static String truncateName(String name, boolean isRule, int maxNameWidth) { // If this is an aspect, we'll chop off everything except the aspect name if (!isRule) { int dividerIndex = name.lastIndexOf('%'); if (dividerIndex >= 0) { name = name.substring(dividerIndex + 1); } } if (name.length() <= maxNameWidth) { return name; } int starti = name.length() - maxNameWidth + "...".length(); return "..." + name.substring(starti); } private static void printWithPadding(PrintStream out, String str, int columnWidth) { out.print(str); pad(out, columnWidth + 2, str.length()); } private static void printWithPaddingBefore(PrintStream out, String str, int columnWidth) { pad(out, columnWidth, str.length()); out.print(str); pad(out, 2, 0); } private static void pad(PrintStream out, int columnWidth, int consumed) { for (int i = 0; i < columnWidth - consumed; ++i) { out.print(' '); } } private static String formatLong(long number) { return String.format("%,d", number); } private void dumpSkylarkHeap(BlazeWorkspace workspace, String path, PrintStream out) throws IOException { AllocationTracker allocationTracker = workspace.getAllocationTracker(); if (allocationTracker == null) { out.println( "Cannot dump skylark heap without running in memory tracking mode. " + "Please refer to the user manual for the dump commnd " + "for information how to turn on memory tracking."); return; } out.println("Dumping skylark heap to: " + path); allocationTracker.dumpSkylarkAllocations(path); } }