/*
* Copyright 2013-present Facebook, Inc.
*
* 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.facebook.buck.event.listener;
import com.facebook.buck.artifact_cache.ArtifactCacheConnectEvent;
import com.facebook.buck.artifact_cache.ArtifactCacheEvent;
import com.facebook.buck.event.ActionGraphEvent;
import com.facebook.buck.event.ArtifactCompressionEvent;
import com.facebook.buck.event.BuckEvent;
import com.facebook.buck.event.BuckEventListener;
import com.facebook.buck.event.ChromeTraceEvent;
import com.facebook.buck.event.CommandEvent;
import com.facebook.buck.event.CompilerPluginDurationEvent;
import com.facebook.buck.event.InstallEvent;
import com.facebook.buck.event.RuleKeyCalculationEvent;
import com.facebook.buck.event.SimplePerfEvent;
import com.facebook.buck.event.StartActivityEvent;
import com.facebook.buck.event.UninstallEvent;
import com.facebook.buck.io.PathListing;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.json.ParseBuckFileEvent;
import com.facebook.buck.jvm.java.AnnotationProcessingEvent;
import com.facebook.buck.jvm.java.tracing.JavacPhaseEvent;
import com.facebook.buck.log.CommandThreadFactory;
import com.facebook.buck.log.InvocationInfo;
import com.facebook.buck.log.Logger;
import com.facebook.buck.model.BuildId;
import com.facebook.buck.parser.ParseEvent;
import com.facebook.buck.rules.BuildEvent;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleEvent;
import com.facebook.buck.rules.TestSummaryEvent;
import com.facebook.buck.step.StepEvent;
import com.facebook.buck.timing.Clock;
import com.facebook.buck.util.BestCompressionGZIPOutputStream;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.ObjectMappers;
import com.facebook.buck.util.Optionals;
import com.facebook.buck.util.ProcessResourceConsumption;
import com.facebook.buck.util.concurrent.MostExecutors;
import com.facebook.buck.util.perf.PerfStatsTracking;
import com.facebook.buck.util.perf.ProcessTracker;
import com.facebook.buck.util.unit.SizeUnit;
import com.fasterxml.jackson.core.JsonGenerator;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CaseFormat;
import com.google.common.base.Joiner;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.eventbus.Subscribe;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Optional;
import java.util.TimeZone;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/** Logs events to a json file formatted to be viewed in Chrome Trace View (chrome://tracing). */
public class ChromeTraceBuildListener implements BuckEventListener {
private static final LoadingCache<String, String> CONVERTED_EVENT_ID_CACHE =
CacheBuilder.newBuilder()
.weakValues()
.build(
new CacheLoader<String, String>() {
@Override
public String load(String key) throws Exception {
return CaseFormat.UPPER_CAMEL
.converterTo(CaseFormat.LOWER_UNDERSCORE)
.convert(key)
.intern();
}
});
private static final Logger LOG = Logger.get(ChromeTraceBuildListener.class);
private static final int TIMEOUT_SECONDS = 30;
private final ProjectFilesystem projectFilesystem;
private final Clock clock;
private final ThreadLocal<SimpleDateFormat> dateFormat;
private final Path tracePath;
private final OutputStream traceStream;
private final JsonGenerator jsonGenerator;
private final InvocationInfo invocationInfo;
private final ChromeTraceBuckConfig config;
private final ExecutorService outputExecutor;
public ChromeTraceBuildListener(
ProjectFilesystem projectFilesystem,
InvocationInfo invocationInfo,
Clock clock,
ChromeTraceBuckConfig config)
throws IOException {
this(projectFilesystem, invocationInfo, clock, Locale.US, TimeZone.getDefault(), config);
}
@VisibleForTesting
ChromeTraceBuildListener(
ProjectFilesystem projectFilesystem,
InvocationInfo invocationInfo,
Clock clock,
final Locale locale,
final TimeZone timeZone,
ChromeTraceBuckConfig config)
throws IOException {
this.invocationInfo = invocationInfo;
this.projectFilesystem = projectFilesystem;
this.clock = clock;
this.dateFormat =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd.HH-mm-ss", locale);
dateFormat.setTimeZone(timeZone);
return dateFormat;
}
};
this.config = config;
this.outputExecutor =
MostExecutors.newSingleThreadExecutor(new CommandThreadFactory(getClass().getName()));
TracePathAndStream tracePathAndStream = createPathAndStream(invocationInfo);
this.tracePath = tracePathAndStream.getPath();
this.traceStream = tracePathAndStream.getStream();
this.jsonGenerator = ObjectMappers.createGenerator(this.traceStream);
this.jsonGenerator.writeStartArray();
addProcessMetadataEvent();
}
private void addProcessMetadataEvent() {
submitTraceEvent(
new ChromeTraceEvent(
"buck",
"process_name",
ChromeTraceEvent.Phase.METADATA,
/* processId */ 0,
/* threadId */ 0,
/* microTime */ 0,
/* microThreadUserTime */ 0,
ImmutableMap.of("name", "buck")));
}
@VisibleForTesting
void deleteOldTraces() {
if (!projectFilesystem.exists(invocationInfo.getLogDirectoryPath())) {
return;
}
Path traceDirectory =
projectFilesystem.getPathForRelativePath(invocationInfo.getLogDirectoryPath());
try {
for (Path path :
PathListing.listMatchingPathsWithFilters(
traceDirectory,
"build.*.trace",
PathListing.GET_PATH_MODIFIED_TIME,
PathListing.FilterMode.EXCLUDE,
Optional.of(config.getMaxTraces()),
Optional.empty())) {
projectFilesystem.deleteFileAtPath(path);
}
} catch (IOException e) {
LOG.error(e, "Couldn't list paths in trace directory %s", traceDirectory);
}
}
private TracePathAndStream createPathAndStream(InvocationInfo invocationInfo) {
String filenameTime = dateFormat.get().format(new Date(clock.currentTimeMillis()));
String traceName =
String.format("build.%s.%s.trace", filenameTime, invocationInfo.getBuildId());
if (config.getCompressTraces()) {
traceName = traceName + ".gz";
}
Path tracePath = invocationInfo.getLogDirectoryPath().resolve(traceName);
try {
projectFilesystem.createParentDirs(tracePath);
OutputStream stream = projectFilesystem.newFileOutputStream(tracePath);
if (config.getCompressTraces()) {
stream = new BestCompressionGZIPOutputStream(stream, true);
}
return new TracePathAndStream(tracePath, stream);
} catch (IOException e) {
throw new HumanReadableException(e, "Unable to write trace file: " + e);
}
}
@Override
public void outputTrace(BuildId buildId) {
try {
LOG.debug("Writing Chrome trace to %s", tracePath);
outputExecutor.shutdown();
try {
if (!outputExecutor.awaitTermination(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
LOG.warn("Failed to log buck trace %s. Trace might be corrupt", tracePath);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
jsonGenerator.writeEndArray();
jsonGenerator.close();
traceStream.close();
uploadTraceIfConfigured(buildId);
String symlinkName = config.getCompressTraces() ? "build.trace.gz" : "build.trace";
Path symlinkPath = projectFilesystem.getBuckPaths().getLogDir().resolve(symlinkName);
projectFilesystem.createSymLink(
projectFilesystem.resolve(symlinkPath), projectFilesystem.resolve(tracePath), true);
deleteOldTraces();
} catch (IOException e) {
throw new HumanReadableException(e, "Unable to write trace file: " + e);
}
}
@Subscribe
public void commandStarted(CommandEvent.Started started) {
writeChromeTraceEvent(
"buck",
started.getCommandName(),
ChromeTraceEvent.Phase.BEGIN,
ImmutableMap.of("command_args", Joiner.on(' ').join(started.getArgs())),
started);
}
@Subscribe
public void commandFinished(CommandEvent.Finished finished) {
writeChromeTraceEvent(
"buck",
finished.getCommandName(),
ChromeTraceEvent.Phase.END,
ImmutableMap.of(
"command_args", Joiner.on(' ').join(finished.getArgs()),
"daemon", Boolean.toString(finished.isDaemon())),
finished);
}
@Subscribe
public void buildStarted(BuildEvent.Started started) {
writeChromeTraceEvent(
"buck", "build", ChromeTraceEvent.Phase.BEGIN, ImmutableMap.of(), started);
}
@Subscribe
public synchronized void buildFinished(BuildEvent.Finished finished) {
writeChromeTraceEvent("buck", "build", ChromeTraceEvent.Phase.END, ImmutableMap.of(), finished);
}
@Subscribe
public void ruleStarted(BuildRuleEvent.Started started) {
BuildRule buildRule = started.getBuildRule();
writeChromeTraceEvent(
"buck",
buildRule.getFullyQualifiedName(),
ChromeTraceEvent.Phase.BEGIN,
ImmutableMap.of(),
started);
}
@Subscribe
public void ruleFinished(BuildRuleEvent.Finished finished) {
writeChromeTraceEvent(
"buck",
finished.getBuildRule().getFullyQualifiedName(),
ChromeTraceEvent.Phase.END,
ImmutableMap.of(
"cache_result", finished.getCacheResult().toString().toLowerCase(),
"success_type", finished.getSuccessType().map(Object::toString).orElse("failed")),
finished);
}
@Subscribe
public void ruleResumed(BuildRuleEvent.Resumed resumed) {
BuildRule buildRule = resumed.getBuildRule();
writeChromeTraceEvent(
"buck",
buildRule.getFullyQualifiedName(),
ChromeTraceEvent.Phase.BEGIN,
ImmutableMap.of("rule_key", resumed.getRuleKey()),
resumed);
}
@Subscribe
public void ruleSuspended(BuildRuleEvent.Suspended suspended) {
// Because RuleKeyCalculationEvent.Finished is a subclass of BuildRuleEvent.Suspended, both
// events get issued. If we wrote the trace events in the order they come in on the event bus,
// we'd create an incorrect trace where the rule section ends before the rule_key_calc section
// it encloses. Instead, when we see a BuildRuleEvent.Suspended that is also a
// RuleKeyCalculationEvent.Finished, we let ruleKeyCalculationFinished log them both in the
// correct order. TODO(jkeljo): Fix this in a less hacky way.
if (suspended instanceof RuleKeyCalculationEvent.Finished) {
return;
}
writeRuleSuspended(suspended);
}
private void writeRuleSuspended(BuildRuleEvent.Suspended suspended) {
BuildRule buildRule = suspended.getBuildRule();
writeChromeTraceEvent(
"buck",
buildRule.getFullyQualifiedName(),
ChromeTraceEvent.Phase.END,
ImmutableMap.of("rule_key", suspended.getRuleKey()),
suspended);
}
@Subscribe
public void stepStarted(StepEvent.Started started) {
writeChromeTraceEvent(
"buck",
started.getShortStepName(),
ChromeTraceEvent.Phase.BEGIN,
ImmutableMap.of(),
started);
}
@Subscribe
public void stepFinished(StepEvent.Finished finished) {
writeChromeTraceEvent(
"buck",
finished.getShortStepName(),
ChromeTraceEvent.Phase.END,
ImmutableMap.of(
"description", finished.getDescription(),
"exit_code", Integer.toString(finished.getExitCode())),
finished);
}
@Subscribe
public void parseStarted(ParseEvent.Started started) {
writeChromeTraceEvent(
"buck", "parse", ChromeTraceEvent.Phase.BEGIN, ImmutableMap.of(), started);
}
@Subscribe
public void parseFinished(ParseEvent.Finished finished) {
writeChromeTraceEvent(
"buck",
"parse",
ChromeTraceEvent.Phase.END,
ImmutableMap.of("targets", Joiner.on(",").join(finished.getBuildTargets())),
finished);
}
@Subscribe
public void simplePerfEvent(SimplePerfEvent perfEvent) {
ChromeTraceEvent.Phase phase = null;
switch (perfEvent.getEventType()) {
case STARTED:
phase = ChromeTraceEvent.Phase.BEGIN;
break;
case FINISHED:
phase = ChromeTraceEvent.Phase.END;
break;
case UPDATED:
phase = ChromeTraceEvent.Phase.IMMEDIATE;
break;
}
if (phase == null) {
throw new IllegalStateException("Unsupported perf event type: " + perfEvent.getEventType());
}
try {
writeChromeTraceEvent(
"buck",
CONVERTED_EVENT_ID_CACHE.get(perfEvent.getEventId().getValue().intern()),
phase,
ImmutableMap.copyOf(Maps.transformValues(perfEvent.getEventInfo(), Object::toString)),
perfEvent);
} catch (ExecutionException e) {
LOG.warn("Unable to log perf event " + perfEvent, e);
}
}
@Subscribe
public void parseBuckFileStarted(ParseBuckFileEvent.Started started) {
writeChromeTraceEvent(
"buck",
"parse_file",
ChromeTraceEvent.Phase.BEGIN,
ImmutableMap.of("path", started.getBuckFilePath().toString()),
started);
}
@Subscribe
public void parseBuckFileFinished(ParseBuckFileEvent.Finished finished) {
writeChromeTraceEvent(
"buck",
"parse_file",
ChromeTraceEvent.Phase.END,
ImmutableMap.of(
"path",
finished.getBuckFilePath().toString(),
"num_rules",
Integer.toString(finished.getNumRules()),
"processed_bytes",
Long.toString(finished.getProcessedBytes()),
"python_profile",
finished.getProfile().orElse("")),
finished);
}
@Subscribe
public void actionGraphStarted(ActionGraphEvent.Started started) {
writeChromeTraceEvent(
"buck", "action_graph", ChromeTraceEvent.Phase.BEGIN, ImmutableMap.of(), started);
}
@Subscribe
public void actionGraphFinished(ActionGraphEvent.Finished finished) {
writeChromeTraceEvent(
"buck", "action_graph", ChromeTraceEvent.Phase.END, ImmutableMap.of(), finished);
}
@Subscribe
public void actionGraphCacheHit(ActionGraphEvent.Cache.Hit hit) {
writeChromeTraceEvent(
"buck",
"action_graph_cache",
ChromeTraceEvent.Phase.IMMEDIATE,
ImmutableMap.of("hit", "true"),
hit);
}
@Subscribe
public void actionGraphCacheMiss(ActionGraphEvent.Cache.Miss miss) {
writeChromeTraceEvent(
"buck",
"action_graph_cache",
ChromeTraceEvent.Phase.IMMEDIATE,
ImmutableMap.of("hit", "false", "cacheWasEmpty", String.valueOf(miss.cacheWasEmpty)),
miss);
}
@Subscribe
public void installStarted(InstallEvent.Started started) {
writeChromeTraceEvent(
"buck", "install", ChromeTraceEvent.Phase.BEGIN, ImmutableMap.of(), started);
}
@Subscribe
public void installFinished(InstallEvent.Finished finished) {
writeChromeTraceEvent(
"buck",
"install",
ChromeTraceEvent.Phase.END,
ImmutableMap.of(
"target", finished.getBuildTarget().getFullyQualifiedName(),
"success", Boolean.toString(finished.isSuccess())),
finished);
}
@Subscribe
public void startActivityStarted(StartActivityEvent.Started started) {
writeChromeTraceEvent(
"buck", "start_activity", ChromeTraceEvent.Phase.BEGIN, ImmutableMap.of(), started);
}
@Subscribe
public void startActivityFinished(StartActivityEvent.Finished finished) {
writeChromeTraceEvent(
"buck",
"start_activity",
ChromeTraceEvent.Phase.END,
ImmutableMap.of(
"target", finished.getBuildTarget().getFullyQualifiedName(),
"activity_name", finished.getActivityName(),
"success", Boolean.toString(finished.isSuccess())),
finished);
}
@Subscribe
public void uninstallStarted(UninstallEvent.Started started) {
writeChromeTraceEvent(
"buck", "uninstall", ChromeTraceEvent.Phase.BEGIN, ImmutableMap.of(), started);
}
@Subscribe
public void uninstallFinished(UninstallEvent.Finished finished) {
writeChromeTraceEvent(
"buck",
"uninstall",
ChromeTraceEvent.Phase.END,
ImmutableMap.of(
"package_name", finished.getPackageName(),
"success", Boolean.toString(finished.isSuccess())),
finished);
}
@Subscribe
public void artifactCacheEventStarted(ArtifactCacheEvent.Started started) {
writeChromeTraceEvent(
"buck",
started.getCategory(),
ChromeTraceEvent.Phase.BEGIN,
ImmutableMap.of("rule_key", Joiner.on(", ").join(started.getRuleKeys())),
started);
}
@Subscribe
public void artifactCacheEventFinished(ArtifactCacheEvent.Finished finished) {
ImmutableMap.Builder<String, String> argumentsBuilder =
ImmutableMap.<String, String>builder()
.put("success", Boolean.toString(finished.isSuccess()))
.put("rule_key", Joiner.on(", ").join(finished.getRuleKeys()));
Optionals.putIfPresent(
finished.getCacheResult().map(Object::toString), "cache_result", argumentsBuilder);
writeChromeTraceEvent(
"buck",
finished.getCategory(),
ChromeTraceEvent.Phase.END,
argumentsBuilder.build(),
finished);
}
@Subscribe
public void artifactCompressionStarted(ArtifactCompressionEvent.Started started) {
writeArtifactCompressionEvent(started, ChromeTraceEvent.Phase.BEGIN);
}
@Subscribe
public void artifactCompressionFinished(ArtifactCompressionEvent.Finished finished) {
writeArtifactCompressionEvent(finished, ChromeTraceEvent.Phase.END);
}
public void writeArtifactCompressionEvent(
ArtifactCompressionEvent event, ChromeTraceEvent.Phase phase) {
writeChromeTraceEvent(
"buck",
event.getCategory(),
phase,
ImmutableMap.of("rule_key", Joiner.on(", ").join(event.getRuleKeys())),
event);
}
@Subscribe
public void artifactConnectStarted(ArtifactCacheConnectEvent.Started started) {
writeChromeTraceEvent(
"buck", "artifact_connect", ChromeTraceEvent.Phase.BEGIN, ImmutableMap.of(), started);
}
@Subscribe
public void artifactConnectFinished(ArtifactCacheConnectEvent.Finished finished) {
writeChromeTraceEvent(
"buck", "artifact_connect", ChromeTraceEvent.Phase.END, ImmutableMap.of(), finished);
}
@Subscribe
public void javacPhaseStarted(JavacPhaseEvent.Started started) {
writeChromeTraceEvent(
"javac",
started.getPhase().toString(),
ChromeTraceEvent.Phase.BEGIN,
started.getArgs(),
started);
}
@Subscribe
public void javacPhaseFinished(JavacPhaseEvent.Finished finished) {
writeChromeTraceEvent(
"javac",
finished.getPhase().toString(),
ChromeTraceEvent.Phase.END,
finished.getArgs(),
finished);
}
@Subscribe
public void annotationProcessingStarted(AnnotationProcessingEvent.Started started) {
writeChromeTraceEvent(
started.getAnnotationProcessorName(),
started.getCategory(),
ChromeTraceEvent.Phase.BEGIN,
ImmutableMap.of(),
started);
}
@Subscribe
public void annotationProcessingFinished(AnnotationProcessingEvent.Finished finished) {
writeChromeTraceEvent(
finished.getAnnotationProcessorName(),
finished.getCategory(),
ChromeTraceEvent.Phase.END,
ImmutableMap.of(),
finished);
}
@Subscribe
public void compilerPluginDurationEventStarted(CompilerPluginDurationEvent.Started started) {
writeChromeTraceEvent(
started.getPluginName(),
started.getDurationName(),
ChromeTraceEvent.Phase.BEGIN,
started.getArgs(),
started);
}
@Subscribe
public void compilerPluginDurationEventFinished(CompilerPluginDurationEvent.Finished finished) {
writeChromeTraceEvent(
finished.getPluginName(),
finished.getDurationName(),
ChromeTraceEvent.Phase.END,
finished.getArgs(),
finished);
}
@Subscribe
public void memoryPerfStats(PerfStatsTracking.MemoryPerfStatsEvent memory) {
writeChromeTraceEvent(
"perf",
"memory",
ChromeTraceEvent.Phase.COUNTER,
ImmutableMap.<String, String>builder()
.put(
"used_memory_mb",
Long.toString(
SizeUnit.BYTES.toMegabytes(
memory.getTotalMemoryBytes() - memory.getFreeMemoryBytes())))
.put(
"free_memory_mb",
Long.toString(SizeUnit.BYTES.toMegabytes(memory.getFreeMemoryBytes())))
.put(
"total_memory_mb",
Long.toString(SizeUnit.BYTES.toMegabytes(memory.getTotalMemoryBytes())))
.put(
"max_memory_mb",
Long.toString(SizeUnit.BYTES.toMegabytes(memory.getMaxMemoryBytes())))
.put(
"time_spent_in_gc_sec",
Long.toString(TimeUnit.MILLISECONDS.toSeconds(memory.getTimeSpentInGcMs())))
.putAll(
memory
.getCurrentMemoryBytesUsageByPool()
.entrySet()
.stream()
.map(
e ->
Maps.immutableEntry(
"pool_" + e.getKey() + "_mb",
Long.toString(SizeUnit.BYTES.toMegabytes(e.getValue()))))
.collect(Collectors.toList()))
.build(),
memory);
}
@Subscribe
public void processResourceConsumption(ProcessTracker.ProcessResourceConsumptionEvent event) {
Optional<ProcessResourceConsumption> resourceConsumption = event.getResourceConsumption();
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
builder.put("executable", event.getExecutableName());
if (event.getContext().isPresent()) {
builder.put("context", event.getContext().get().toString());
}
if (resourceConsumption.isPresent()) {
ProcessResourceConsumption res = resourceConsumption.get();
builder.put("mem_size_mb", Long.toString(SizeUnit.BYTES.toMegabytes(res.getMemSize())));
builder.put(
"mem_resident_mb", Long.toString(SizeUnit.BYTES.toMegabytes(res.getMemResident())));
builder.put("cpu_real_ms", Long.toString(res.getCpuReal()));
builder.put("cpu_user_ms", Long.toString(res.getCpuUser()));
builder.put("cpu_sys_ms", Long.toString(res.getCpuSys()));
builder.put("bytes_read_mb", Long.toString(SizeUnit.BYTES.toMegabytes(res.getIoBytesRead())));
builder.put(
"bytes_written_mb", Long.toString(SizeUnit.BYTES.toMegabytes(res.getIoBytesWritten())));
}
writeChromeTraceEvent(
"perf", "process", ChromeTraceEvent.Phase.COUNTER, builder.build(), event);
}
@Subscribe
public void testStartedEvent(TestSummaryEvent.Started started) {
writeChromeTraceEvent(
"buck",
"test",
ChromeTraceEvent.Phase.BEGIN,
ImmutableMap.of(
"test_case_name", started.getTestCaseName(),
"test_name", started.getTestName()),
started);
}
@Subscribe
public void testFinishedEvent(TestSummaryEvent.Finished finished) {
writeChromeTraceEvent(
"buck",
"test",
ChromeTraceEvent.Phase.END,
ImmutableMap.of(
"test_case_name", finished.getTestCaseName(),
"test_name", finished.getTestName()),
finished);
}
@Subscribe
public void ruleKeyCalculationStarted(RuleKeyCalculationEvent.Started started) {
writeChromeTraceEvent(
"buck", started.getCategory(), ChromeTraceEvent.Phase.BEGIN, ImmutableMap.of(), started);
}
@Subscribe
public void ruleKeyCalculationFinished(RuleKeyCalculationEvent.Finished finished) {
writeChromeTraceEvent(
"buck", finished.getCategory(), ChromeTraceEvent.Phase.END, ImmutableMap.of(), finished);
if (finished instanceof BuildRuleEvent.Suspended) {
writeRuleSuspended((BuildRuleEvent.Suspended) finished);
}
}
private void writeChromeTraceEvent(
String category,
String name,
ChromeTraceEvent.Phase phase,
ImmutableMap<String, String> arguments,
final BuckEvent event) {
final ChromeTraceEvent chromeTraceEvent =
new ChromeTraceEvent(
category,
name,
phase,
0,
event.getThreadId(),
TimeUnit.NANOSECONDS.toMicros(event.getNanoTime()),
TimeUnit.NANOSECONDS.toMicros(event.getThreadUserNanoTime()),
arguments);
submitTraceEvent(chromeTraceEvent);
}
@SuppressWarnings("PMD.EmptyCatchBlock")
private void submitTraceEvent(final ChromeTraceEvent chromeTraceEvent) {
@SuppressWarnings("unused")
Future<?> unused =
outputExecutor.submit(
() -> {
try {
ObjectMappers.WRITER.writeValue(jsonGenerator, chromeTraceEvent);
} catch (IOException e) {
// Swallow any failures to write.
}
return null;
});
}
private void uploadTraceIfConfigured(BuildId buildId) {
Optional<URI> traceUploadUri = config.getTraceUploadUri();
if (!traceUploadUri.isPresent()) {
return;
}
Path fullPath = projectFilesystem.resolve(tracePath);
Path logFile =
projectFilesystem.resolve(
invocationInfo.getLogDirectoryPath().resolve("upload-build-trace.log"));
LOG.debug("Uploading build trace in the background. Upload will log to %s", logFile);
try {
String[] args = {
"java",
"-cp",
System.getenv("BUCK_CLASSPATH"),
"com.facebook.buck.util.trace.uploader.Main",
"--buildId",
buildId.toString(),
"--traceFilePath",
fullPath.toString(),
"--baseUrl",
traceUploadUri.get().toString(),
"--log",
logFile.toString()
};
Runtime.getRuntime().exec(args);
} catch (IOException e) {
LOG.error(e, e.getMessage());
}
}
private static class TracePathAndStream {
private final Path path;
private final OutputStream stream;
public TracePathAndStream(Path path, OutputStream stream) {
this.path = path;
this.stream = stream;
}
public Path getPath() {
return path;
}
public OutputStream getStream() {
return stream;
}
}
}