/*
* Copyright 2015-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.jvm.java.tracing;
import com.facebook.buck.jvm.java.plugin.api.BuckJavacTaskListener;
import com.facebook.buck.jvm.java.plugin.api.TaskEventMirror;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
/**
* A {@link BuckJavacTaskListener} that traces all events to a {@link JavacPhaseTracer}. The event
* stream coming from the compiler is a little dirty, so this class cleans it up minimally before
* tracing.
*/
public class TracingTaskListener implements BuckJavacTaskListener {
private final JavacPhaseTracer tracing;
private final TraceCleaner traceCleaner;
@Nullable private final BuckJavacTaskListener inner;
public TracingTaskListener(JavacPhaseTracer tracing, @Nullable BuckJavacTaskListener next) {
inner = next;
this.tracing = tracing;
traceCleaner = new TraceCleaner(tracing);
}
@Override
public synchronized void started(TaskEventMirror e) {
// Chain start events before tracing, so that the tracing most closely tracks the actual event
// time
if (inner != null) {
inner.started(e);
}
switch (e.getKind()) {
case PARSE:
tracing.beginParse(getFileName(e));
break;
case ENTER:
traceCleaner.startEnter(e.getSourceFile().getName());
break;
case ANNOTATION_PROCESSING_ROUND:
tracing.beginAnnotationProcessingRound();
break;
case ANALYZE:
traceCleaner.startAnalyze(getFileName(e), getTypeName(e));
break;
case GENERATE:
tracing.beginGenerate(getFileName(e), getTypeName(e));
break;
case ANNOTATION_PROCESSING:
case COMPILATION:
default:
// The tracing doesn't care about these events
break;
}
}
@Override
public synchronized void finished(TaskEventMirror e) {
// See comment in started() about testability.
switch (e.getKind()) {
case PARSE:
tracing.endParse();
break;
case ENTER:
traceCleaner.finishEnter();
break;
case ANNOTATION_PROCESSING_ROUND:
tracing.endAnnotationProcessingRound();
break;
case ANALYZE:
traceCleaner.finishAnalyze(getFileName(e), getTypeName(e));
break;
case GENERATE:
tracing.endGenerate();
break;
case ANNOTATION_PROCESSING:
case COMPILATION:
default:
// The tracing doesn't care about these events
break;
}
// Chain finished events after tracing, so that the tracing most closely tracks the actual
// event time
if (inner != null) {
inner.finished(e);
}
}
/** Cleans up the slightly dirty aspects of the event stream. */
static class TraceCleaner {
private final JavacPhaseTracer tracing;
private int enterCount = 0;
private List<String> enteredFiles = new ArrayList<>();
private int analyzeCount = 0;
public TraceCleaner(JavacPhaseTracer tracing) {
this.tracing = tracing;
}
/**
* Generally the compiler enters all compilation units at once. It fires start events for all of
* them, does the work, then fires finish events for all of them. There are a couple of issues
* with this from a tracing perspective:
*
* <ul>
* <li>It generates a ton of slices
* <li>The finish events are in the same order as the start ones (we'd expect them to be in
* reverse order if this were true tracing)
* </ul>
*
* To clean this up into a trace-friendly event stream, we coalesce all the enter events for a
* given round into one.
*
* @see #finishEnter()
*/
void startEnter(String filename) {
if (enterCount == 0) {
tracing.beginEnter();
}
enterCount += 1;
enteredFiles.add(filename);
}
/** @see #startEnter(String) */
void finishEnter() {
enterCount -= 1;
if (enterCount == 0) {
tracing.endEnter(enteredFiles);
enteredFiles.clear();
}
}
/**
* There are some cases in which a finish analyze event can be received without a corresponding
* start. Rather than output malformed trace data, we detect that case and synthesize a start
* event.
*
* @see #finishAnalyze(String, String)
*/
void startAnalyze(@Nullable String filename, @Nullable String typename) {
analyzeCount += 1;
tracing.beginAnalyze(filename, typename);
}
/** @see #startAnalyze(String, String) */
void finishAnalyze(@Nullable String filename, @Nullable String typename) {
if (analyzeCount > 0) {
analyzeCount -= 1;
tracing.endAnalyze();
} else {
tracing.beginAnalyze(filename, typename);
tracing.endAnalyze();
}
}
}
@Nullable
private static String getFileName(TaskEventMirror e) {
if (e.getSourceFile() == null) {
return null;
}
return e.getSourceFile().toString();
}
@Nullable
private static String getTypeName(TaskEventMirror e) {
if (e.getTypeElement() == null) {
return null;
}
return e.getTypeElement().getQualifiedName().toString();
}
}