blob: 3ceeaa17606f774e51ca8850d82c254bb97a5619 [file] [log] [blame]
// Copyright 2014 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 com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.Reporter;
import com.google.devtools.build.lib.profiler.JsonProfile;
import com.google.devtools.build.lib.profiler.output.PhaseText;
import com.google.devtools.build.lib.profiler.statistics.CriticalPathStatistics;
import com.google.devtools.build.lib.runtime.BlazeCommand;
import com.google.devtools.build.lib.runtime.BlazeCommandResult;
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.server.FailureDetails;
import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
import com.google.devtools.build.lib.server.FailureDetails.ProfileCommand.Code;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.common.options.Converters;
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.OptionsParsingResult;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
/** Command line wrapper for analyzing Blaze build profiles. */
@Command(
name = "analyze-profile",
options = {ProfileCommand.ProfileOptions.class},
shortDescription = "Analyzes build profile data.",
help = "resource:analyze-profile.txt",
allowResidue = true,
completion = "path",
mustRunInWorkspace = false
)
public final class ProfileCommand implements BlazeCommand {
public static class DumpConverter extends Converters.StringSetConverter {
public DumpConverter() {
super("text", "raw");
}
}
public static class ProfileOptions extends OptionsBase {
@Option(
name = "dump",
abbrev = 'd',
converter = DumpConverter.class,
defaultValue = "null",
documentationCategory = OptionDocumentationCategory.LOGGING,
effectTags = {OptionEffectTag.AFFECTS_OUTPUTS},
help =
"output full profile data dump either in human-readable 'text' format or"
+ " script-friendly 'raw' format.")
public String dumpMode;
}
/**
* Note that this is just a basic check whether the file is zlib compressed.
*
* <p>Other checks (e.g. the magic bytes of the binary profile file) are done later.
*/
private static boolean isOldBinaryProfile(File profile) {
try (InputStream inputStream = new FileInputStream(profile)) {
byte[] magicBytes = new byte[2];
int numBytesRead = inputStream.read(magicBytes);
if (numBytesRead == 2
&& magicBytes[0] == (byte) 0x78
&& (magicBytes[1] == (byte) 0x01
|| magicBytes[1] == (byte) 0x9C
|| magicBytes[1] == (byte) 0xDA)) {
return true;
}
} catch (Exception e) {
// silently ignore any exception
}
return false;
}
@Override
public BlazeCommandResult exec(final CommandEnvironment env, OptionsParsingResult options) {
ProfileOptions profileOptions = options.getOptions(ProfileOptions.class);
String dumpMode = profileOptions.dumpMode;
try (PrintStream out = getOutputStream(env)) {
Reporter reporter = env.getReporter();
reporter.handle(
Event.warn(
"This information is intended for consumption by Bazel developers"
+ " only, and may change at any time. Script against it at your own risk"));
for (String name : options.getResidue()) {
Path profileFile = env.getWorkingDirectory().getRelative(name);
if (isOldBinaryProfile(profileFile.getPathFile())) {
String message =
"The old binary profile format is deprecated."
+ " Use the JSON trace profile instead.";
reporter.handle(Event.error(message));
return createFailureResult(message, Code.OLD_BINARY_FORMAT_UNSUPPORTED);
} else {
try {
if (dumpMode != null) {
reporter.handle(
Event.warn(
"--dump has not been implemented yet for the JSON profile, ignoring."));
}
JsonProfile jsonProfile = new JsonProfile(profileFile.getPathFile());
JsonProfile.BuildMetadata buildMetadata = jsonProfile.getBuildMetadata();
if (buildMetadata != null) {
reporter.handle(
Event.info(
"Profile created on "
+ buildMetadata.date()
+ ", build ID: "
+ buildMetadata.buildId()
+ ", output base: "
+ buildMetadata.outputBase()));
}
new PhaseText(
out,
jsonProfile.getPhaseSummaryStatistics(),
new CriticalPathStatistics(jsonProfile.getTraceEvents()))
.print();
} catch (IOException e) {
String message = "Failed to analyze profile file(s): " + e.getMessage();
reporter.handle(Event.error(message));
return createFailureResult(message, Code.FILE_READ_FAILURE);
}
}
}
}
return BlazeCommandResult.success();
}
private static PrintStream getOutputStream(CommandEnvironment env) {
return new PrintStream(
new BufferedOutputStream(env.getReporter().getOutErr().getOutputStream()), false);
}
private static BlazeCommandResult createFailureResult(String message, Code detailedCode) {
return BlazeCommandResult.failureDetail(
FailureDetail.newBuilder()
.setMessage(message)
.setProfileCommand(FailureDetails.ProfileCommand.newBuilder().setCode(detailedCode))
.build());
}
}