blob: f56afc8f9bf370c83a694a0fcd0acaa6b744fed9 [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.profiler;
import static com.google.devtools.build.lib.profiler.ProfilerTask.TASK_COUNT;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.clock.Clock;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
import com.google.devtools.build.lib.profiler.PredicateBasedStatRecorder.RecorderAndPredicate;
import com.google.devtools.build.lib.profiler.StatRecorder.VfsHeuristics;
import com.google.devtools.build.lib.util.VarInt;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
/**
* Blaze internal profiler. Provides facility to report various Blaze tasks and store them
* (asynchronously) in the file for future analysis.
*
* <p>Implemented as singleton so any caller should use Profiler.instance() to obtain reference.
*
* <p>Internally, profiler uses two data structures - ThreadLocal task stack to track nested tasks
* and single ConcurrentLinkedQueue to gather all completed tasks.
*
* <p>Also, due to the nature of the provided functionality (instrumentation of all Blaze
* components), build.lib.profiler package will be used by almost every other Blaze package, so
* special attention should be paid to avoid any dependencies on the rest of the Blaze code,
* including build.lib.util and build.lib.vfs. This is important because build.lib.util and
* build.lib.vfs contain Profiler invocations and any dependency on those two packages would create
* circular relationship.
*
* <p>All gathered instrumentation data will be stored in the file. Please, note, that while file
* format is described here it is considered internal and can change at any time. For scripting,
* using blaze analyze-profile --dump=raw would be more robust and stable solution.
*
* <p>
*
* <pre>
* Profiler file consists of the deflated stream with following overall structure:
* HEADER
* TASK_TYPE_TABLE
* TASK_RECORD...
* EOF_MARKER
*
* HEADER:
* int32: magic token (Profiler.MAGIC)
* int32: version format (Profiler.VERSION)
* string: file comment
*
* TASK_TYPE_TABLE:
* int32: number of type names below
* string... : type names. Each of the type names is assigned id according to
* their position in this table starting from 0.
*
* TASK_RECORD:
* int32 size: size of the encoded task record
* byte[size] encoded_task_record:
* varint64: thread id - as was returned by Thread.getId()
* varint32: task id - starting from 1.
* varint32: parent task id for subtasks or 0 for root tasks
* varint64: start time in ns, relative to the Profiler.start() invocation
* varint64: task duration in ns
* byte: task type id (see TASK_TYPE_TABLE)
* varint32: description string index incremented by 1 (>0) or 0 this is
* a first occurrence of the description string
* AGGREGATED_STAT...: remainder of the field (if present) represents
* aggregated stats for that task
* string: *optional* description string, will appear only if description
* string index above was 0. In that case this string will be
* assigned next sequential id so every unique description string
* will appear in the file only once - after that it will be
* referenced by id.
*
* AGGREGATE_STAT:
* byte: stat type
* varint32: total number of subtask invocations
* varint64: cumulative duration of subtask invocations in ns.
*
* EOF_MARKER:
* int64: -1 - please note that this corresponds to the thread id in the
* TASK_RECORD which is always > 0
* </pre>
*
* @see ProfilerTask enum for recognized task types.
*/
@ThreadSafe
public final class Profiler {
private static final Logger logger = Logger.getLogger(Profiler.class.getName());
public static final int MAGIC = 0x11223344;
// File version number. Note that merely adding new record types in
// the ProfilerTask does not require bumping version number as long as original
// enum values are not renamed or deleted.
public static final int VERSION = 0x03;
// EOF marker. Must be < 0.
public static final int EOF_MARKER = -1;
// Profiler will check for gathered data and persist all of it in the
// separate thread every SAVE_DELAY ms.
private static final int SAVE_DELAY = 2000; // ms
/**
* The profiler (a static singleton instance). Inactive by default.
*/
private static final Profiler instance = new Profiler();
private static final int HISTOGRAM_BUCKETS = 20;
/** A task that was very slow. */
public static final class SlowTask implements Comparable<SlowTask> {
final long durationNanos;
final Object object;
ProfilerTask type;
private SlowTask(TaskData taskData) {
this.durationNanos = taskData.duration;
this.object = taskData.object;
this.type = taskData.type;
}
@Override
public int compareTo(SlowTask other) {
long delta = durationNanos - other.durationNanos;
if (delta < 0) { // Very clumsy
return -1;
} else if (delta > 0) {
return 1;
} else {
return 0;
}
}
public long getDurationNanos() {
return durationNanos;
}
public String getDescription() {
return toDescription(object);
}
public ProfilerTask getType() {
return type;
}
}
/**
* Container for the single task record.
* Should never be instantiated directly - use TaskStack.create() instead.
*
* Class itself is not thread safe, but all access to it from Profiler
* methods is.
*/
@ThreadCompatible
private final class TaskData {
final long threadId;
final long startTime;
long duration = 0L;
final int id;
final int parentId;
int[] counts; // number of invocations per ProfilerTask type
long[] durations; // time spend in the task per ProfilerTask type
final ProfilerTask type;
final Object object;
TaskData(long startTime, TaskData parent,
ProfilerTask eventType, Object object) {
threadId = Thread.currentThread().getId();
counts = null;
durations = null;
id = taskId.incrementAndGet();
parentId = (parent == null ? 0 : parent.id);
this.startTime = startTime;
this.type = eventType;
this.object = Preconditions.checkNotNull(object);
}
/**
* Aggregates information about an *immediate* subtask.
*/
public void aggregateChild(ProfilerTask type, long duration) {
int index = type.ordinal();
if (counts == null) {
// one entry for each ProfilerTask type
counts = new int[TASK_COUNT];
durations = new long[TASK_COUNT];
}
counts[index]++;
durations[index] += duration;
}
@Override
public String toString() {
return "Thread " + threadId + ", task " + id + ", type " + type + ", " + object;
}
}
/**
* Tracks nested tasks for each thread.
*
* java.util.ArrayDeque is the most efficient stack implementation in the
* Java Collections Framework (java.util.Stack class is older synchronized
* alternative). It is, however, used here strictly for LIFO operations.
* However, ArrayDeque is 1.6 only. For 1.5 best approach would be to utilize
* ArrayList and emulate stack using it.
*/
@ThreadSafe
private final class TaskStack extends ThreadLocal<List<TaskData>> {
@Override
public List<TaskData> initialValue() {
return new ArrayList<>();
}
public TaskData peek() {
List<TaskData> list = get();
if (list.isEmpty()) {
return null;
}
return list.get(list.size() - 1);
}
public TaskData pop() {
List<TaskData> list = get();
return list.remove(list.size() - 1);
}
public boolean isEmpty() {
return get().isEmpty();
}
public void push(ProfilerTask eventType, Object object) {
get().add(create(clock.nanoTime(), eventType, object));
}
public TaskData create(long startTime, ProfilerTask eventType, Object object) {
return new TaskData(startTime, peek(), eventType, object);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(
"Current task stack for thread " + Thread.currentThread().getName() + ":\n");
List<TaskData> list = get();
for (int i = list.size() - 1; i >= 0; i--) {
builder.append(list.get(i));
builder.append("\n");
}
return builder.toString();
}
}
private static String toDescription(Object object) {
return (object instanceof Describable)
? ((Describable) object).describe()
: object.toString();
}
/**
* Implements datastore for object description indices. Intended to be used only by the
* Profiler.save() method.
*/
@ThreadCompatible
private static final class ObjectDescriber {
private Map<Object, Integer> descMap = new IdentityHashMap<>(2000);
private int indexCounter = 0;
ObjectDescriber() { }
int getDescriptionIndex(Object object) {
Integer index = descMap.get(object);
return (index != null) ? index : -1;
}
String getDescription(Object object) {
String description = toDescription(object);
Integer oldIndex = descMap.put(object, indexCounter++);
// Do not use Preconditions class below due to the rather expensive
// toString() calls used in the message.
if (oldIndex != null) {
throw new IllegalStateException(" Object '" + description + "' @ "
+ System.identityHashCode(object) + " already had description index "
+ oldIndex + " while assigning index " + descMap.get(object));
} else if (description.length() > 20000) {
// Note size 64k byte limitation in DataOutputStream#writeUTF().
description = description.substring(0, 20000);
}
return description;
}
boolean isUnassigned(int index) {
return (index < 0);
}
}
/**
* Aggregator class that keeps track of the slowest tasks of the specified type.
*
* <p><code>priorityQueues</p> is sharded so that all threads need not compete for the same
* lock if they do the same operation at the same time. Access to the individual queues is
* synchronized on the queue objects themselves.
*/
private static final class SlowestTaskAggregator {
private static final int SHARDS = 16;
private final int size;
@SuppressWarnings({"unchecked", "rawtypes"})
private final PriorityQueue<SlowTask>[] priorityQueues = new PriorityQueue[SHARDS];
SlowestTaskAggregator(int size) {
this.size = size;
for (int i = 0; i < SHARDS; i++) {
priorityQueues[i] = new PriorityQueue<>(size + 1);
}
}
// @ThreadSafe
void add(TaskData taskData) {
PriorityQueue<SlowTask> queue =
priorityQueues[(int) (Thread.currentThread().getId() % SHARDS)];
synchronized (queue) {
if (queue.size() == size) {
// Optimization: check if we are faster than the fastest element. If we are, we would
// be the ones to fall off the end of the queue, therefore, we can safely return early.
if (queue.peek().getDurationNanos() > taskData.duration) {
return;
}
queue.add(new SlowTask(taskData));
queue.remove();
} else {
queue.add(new SlowTask(taskData));
}
}
}
// @ThreadSafe
void clear() {
for (int i = 0; i < SHARDS; i++) {
PriorityQueue<SlowTask> queue = priorityQueues[i];
synchronized (queue) {
queue.clear();
}
}
}
// @ThreadSafe
Iterable<SlowTask> getSlowestTasks() {
// This is slow, but since it only happens during the end of the invocation, it's OK
PriorityQueue<SlowTask> merged = new PriorityQueue<>(size * SHARDS);
for (int i = 0; i < SHARDS; i++) {
PriorityQueue<SlowTask> queue = priorityQueues[i];
synchronized (queue) {
merged.addAll(queue);
}
}
while (merged.size() > size) {
merged.remove();
}
return merged;
}
}
/**
* Which {@link ProfilerTask}s are profiled.
*/
public enum ProfiledTaskKinds {
/**
* Do not profile anything.
*
* <p>Performance is best with this case, but we lose critical path analysis and slowest
* operation tracking.
*/
NONE {
@Override
boolean isProfiling(ProfilerTask type) {
return false;
}
},
/**
* Profile on a few, known-to-be-slow tasks.
*
* <p>Performance is somewhat decreased in comparison to {@link #NONE}, but we still track the
* slowest operations (VFS).
*/
SLOWEST {
@Override
boolean isProfiling(ProfilerTask type) {
return type.collectsSlowestInstances();
}
},
/**
* Profile all tasks.
*
* <p>This is in use when {@code --profile} is specified.
*/
ALL {
@Override
boolean isProfiling(ProfilerTask type) {
return true;
}
};
/** Whether the Profiler collects data for the given task type. */
abstract boolean isProfiling(ProfilerTask type);
}
private Clock clock;
private ProfiledTaskKinds profiledTaskKinds;
private volatile long profileStartTime = 0L;
private volatile boolean recordAllDurations = false;
private AtomicInteger taskId = new AtomicInteger();
private TaskStack taskStack;
private Queue<TaskData> taskQueue;
private DataOutputStream out;
private Timer timer;
private IOException saveException;
private ObjectDescriber describer;
@SuppressWarnings("unchecked")
private final SlowestTaskAggregator[] slowestTasks =
new SlowestTaskAggregator[ProfilerTask.values().length];
private final StatRecorder[] tasksHistograms = new StatRecorder[ProfilerTask.values().length];
private Profiler() {
initHistograms();
for (ProfilerTask task : ProfilerTask.values()) {
if (task.slowestInstancesCount != 0) {
slowestTasks[task.ordinal()] = new SlowestTaskAggregator(task.slowestInstancesCount);
}
}
}
private void initHistograms() {
for (ProfilerTask task : ProfilerTask.values()) {
if (task.isVfs()) {
Map<String, ? extends Predicate<? super String>> vfsHeuristics =
VfsHeuristics.vfsTypeHeuristics;
List<RecorderAndPredicate> recorders = new ArrayList<>(vfsHeuristics.size());
for (Entry<String, ? extends Predicate<? super String>> e : vfsHeuristics.entrySet()) {
recorders.add(new RecorderAndPredicate(
new SingleStatRecorder(task + " " + e.getKey(), HISTOGRAM_BUCKETS), e.getValue()));
}
tasksHistograms[task.ordinal()] = new PredicateBasedStatRecorder(recorders);
} else {
tasksHistograms[task.ordinal()] = new SingleStatRecorder(task, HISTOGRAM_BUCKETS);
}
}
}
public ImmutableList<StatRecorder> getTasksHistograms() {
return ImmutableList.copyOf(tasksHistograms);
}
public static Profiler instance() {
return instance;
}
/**
* Returns the nanoTime of the current profiler instance, or an arbitrary
* constant if not active.
*/
public static long nanoTimeMaybe() {
if (instance.isActive()) {
return instance.clock.nanoTime();
}
return -1;
}
/**
* Enable profiling.
*
* <p>Subsequent calls to beginTask/endTask will be recorded
* in the provided output stream. Please note that stream performance is
* extremely important and buffered streams should be utilized.
*
* @param profiledTaskKinds which kinds of {@link ProfilerTask}s to track
* @param stream output stream to store profile data. Note: passing unbuffered stream object
* reference may result in significant performance penalties
* @param comment a comment to insert in the profile data
* @param recordAllDurations iff true, record all tasks regardless of their duration; otherwise
* some tasks may get aggregated if they finished quick enough
* @param clock a {@code BlazeClock.instance()}
* @param execStartTimeNanos execution start time in nanos obtained from {@code clock.nanoTime()}
*/
public synchronized void start(ProfiledTaskKinds profiledTaskKinds, OutputStream stream,
String comment, boolean recordAllDurations, Clock clock, long execStartTimeNanos)
throws IOException {
Preconditions.checkState(!isActive(), "Profiler already active");
taskStack = new TaskStack();
taskQueue = new ConcurrentLinkedQueue<>();
describer = new ObjectDescriber();
this.profiledTaskKinds = profiledTaskKinds;
this.clock = clock;
// sanity check for current limitation on the number of supported types due
// to using enum.ordinal() to store them instead of EnumSet for performance reasons.
Preconditions.checkState(TASK_COUNT < 256,
"The profiler implementation supports only up to 255 different ProfilerTask values.");
// reset state for the new profiling session
taskId.set(0);
this.recordAllDurations = recordAllDurations;
this.saveException = null;
if (stream != null) {
this.timer = new Timer("ProfilerTimer", true);
// Wrapping deflater stream in the buffered stream proved to reduce CPU consumption caused by
// the save() method. Values for buffer sizes were chosen by running small amount of tests
// and identifying point of diminishing returns - but I have not really tried to optimize
// them.
this.out = new DataOutputStream(new BufferedOutputStream(new DeflaterOutputStream(
stream, new Deflater(Deflater.BEST_SPEED, false), 65536), 262144));
this.out.writeInt(MAGIC); // magic
this.out.writeInt(VERSION); // protocol_version
this.out.writeUTF(comment);
// ProfileTask.values() method sorts enums using their ordinal() value, so
// there there is no need to store ordinal() value for each entry.
this.out.writeInt(TASK_COUNT);
for (ProfilerTask type : ProfilerTask.values()) {
this.out.writeUTF(type.toString());
}
// Start save thread
timer.schedule(new TimerTask() {
@Override public void run() { save(); }
}, SAVE_DELAY, SAVE_DELAY);
} else {
this.out = null;
}
// activate profiler
profileStartTime = execStartTimeNanos;
}
public synchronized Iterable<SlowTask> getSlowestTasks() {
List<Iterable<SlowTask>> slowestTasksByType = new ArrayList<>();
for (SlowestTaskAggregator aggregator : slowestTasks) {
if (aggregator != null) {
slowestTasksByType.add(aggregator.getSlowestTasks());
}
}
return Iterables.concat(slowestTasksByType);
}
/**
* Disable profiling and complete profile file creation.
* Subsequent calls to beginTask/endTask will no longer
* be recorded in the profile.
*/
public synchronized void stop() throws IOException {
if (saveException != null) {
throw saveException;
}
if (!isActive()) {
return;
}
// Log a final event to update the duration of ProfilePhase.FINISH.
logEvent(ProfilerTask.INFO, "Finishing");
save();
clear();
for (SlowestTaskAggregator aggregator : slowestTasks) {
if (aggregator != null) {
aggregator.clear();
}
}
if (saveException != null) {
throw saveException;
}
if (out != null) {
out.writeInt(EOF_MARKER);
out.close();
out = null;
}
}
/**
* Returns true iff profiling is currently enabled.
*/
public boolean isActive() {
return profileStartTime != 0L;
}
public boolean isProfiling(ProfilerTask type) {
return profiledTaskKinds.isProfiling(type);
}
/**
* Saves all gathered information from taskQueue queue to the file.
* Method is invoked internally by the Timer-based thread and at the end of
* profiling session.
*/
private synchronized void save() {
if (out == null) {
return;
}
try {
// Allocate the sink once to avoid GC
ByteBuffer sink = ByteBuffer.allocate(1024);
while (!taskQueue.isEmpty()) {
sink.clear();
TaskData data = taskQueue.poll();
VarInt.putVarLong(data.threadId, sink);
VarInt.putVarInt(data.id, sink);
VarInt.putVarInt(data.parentId, sink);
VarInt.putVarLong(data.startTime - profileStartTime, sink);
VarInt.putVarLong(data.duration, sink);
// To save space (and improve performance), convert all description
// strings to the canonical object and use IdentityHashMap to assign
// unique numbers for each string.
int descIndex = describer.getDescriptionIndex(data.object);
VarInt.putVarInt(descIndex + 1, sink); // Add 1 to avoid encoding negative values.
// Save types using their ordinal() value
sink.put((byte) data.type.ordinal());
// Save aggregated data stats.
if (data.counts != null) {
for (int i = 0; i < TASK_COUNT; i++) {
if (data.counts[i] > 0) {
sink.put((byte) i); // aggregated type ordinal value
VarInt.putVarInt(data.counts[i], sink);
VarInt.putVarLong(data.durations[i], sink);
}
}
}
this.out.writeInt(sink.position());
this.out.write(sink.array(), 0, sink.position());
if (describer.isUnassigned(descIndex)) {
this.out.writeUTF(describer.getDescription(data.object));
}
}
this.out.flush();
} catch (IOException e) {
saveException = e;
clear();
try {
out.close();
} catch (IOException e2) {
// ignore it
}
}
}
private synchronized void clear() {
initHistograms();
profileStartTime = 0L;
if (timer != null) {
timer.cancel();
timer = null;
}
taskStack = null;
taskQueue = null;
describer = null;
// Note that slowest task aggregator are not cleared here because clearing happens
// periodically over the course of a command invocation.
}
/**
* Unless --record_full_profiler_data is given we drop small tasks and add their time to the
* parents duration.
*/
private boolean wasTaskSlowEnoughToRecord(ProfilerTask type, long duration) {
return (recordAllDurations || duration >= type.minDuration);
}
/**
* Adds task directly to the main queue bypassing task stack. Used for simple
* tasks that are known to not have any subtasks.
*
* @param startTime task start time (obtained through {@link Profiler#nanoTimeMaybe()})
* @param duration task duration
* @param type task type
* @param object object associated with that task. Can be String object that
* describes it.
*/
private void logTask(long startTime, long duration, ProfilerTask type, Object object) {
Preconditions.checkNotNull(object);
Preconditions.checkState(startTime > 0, "startTime was %s", startTime);
if (duration < 0) {
// See note in Clock#nanoTime, which is used by Profiler#nanoTimeMaybe.
duration = 0;
}
tasksHistograms[type.ordinal()].addStat((int) TimeUnit.NANOSECONDS.toMillis(duration), object);
// Store instance fields as local variables so they are not nulled out from under us by #clear.
TaskStack localStack = taskStack;
Queue<TaskData> localQueue = taskQueue;
if (localStack == null || localQueue == null) {
// Variables have been nulled out by #clear in between the check the caller made and this
// point in the code. Probably due to an asynchronous crash.
logger.severe("Variables null in profiler for " + type + ", probably due to async crash");
return;
}
TaskData parent = localStack.peek();
if (parent != null) {
parent.aggregateChild(type, duration);
}
if (wasTaskSlowEnoughToRecord(type, duration)) {
TaskData data = localStack.create(startTime, type, object);
data.duration = duration;
if (out != null) {
localQueue.add(data);
}
SlowestTaskAggregator aggregator = slowestTasks[type.ordinal()];
if (aggregator != null) {
aggregator.add(data);
}
}
}
/**
* Used externally to submit simple task (one that does not have any subtasks).
* Depending on the minDuration attribute of the task type, task may be
* just aggregated into the parent task and not stored directly.
*
* @param startTime task start time (obtained through {@link
* Profiler#nanoTimeMaybe()})
* @param type task type
* @param object object associated with that task. Can be String object that
* describes it.
*/
public void logSimpleTask(long startTime, ProfilerTask type, Object object) {
if (isActive() && isProfiling(type)) {
logTask(startTime, clock.nanoTime() - startTime, type, object);
}
}
/**
* Used externally to submit simple task (one that does not have any
* subtasks). Depending on the minDuration attribute of the task type, task
* may be just aggregated into the parent task and not stored directly.
*
* <p>Note that start and stop time must both be acquired from the same clock
* instance.
*
* @param startTime task start time
* @param stopTime task stop time
* @param type task type
* @param object object associated with that task. Can be String object that
* describes it.
*/
public void logSimpleTask(long startTime, long stopTime, ProfilerTask type, Object object) {
if (isActive() && isProfiling(type)) {
logTask(startTime, stopTime - startTime, type, object);
}
}
/**
* Used externally to submit simple task (one that does not have any
* subtasks). Depending on the minDuration attribute of the task type, task
* may be just aggregated into the parent task and not stored directly.
*
* @param startTime task start time (obtained through {@link
* Profiler#nanoTimeMaybe()})
* @param duration the duration of the task
* @param type task type
* @param object object associated with that task. Can be String object that
* describes it.
*/
public void logSimpleTaskDuration(long startTime, long duration, ProfilerTask type,
Object object) {
if (isActive() && isProfiling(type)) {
logTask(startTime, duration, type, object);
}
}
/**
* Used to log "events" - tasks with zero duration.
*/
public void logEvent(ProfilerTask type, Object object) {
if (isActive() && isProfiling(type)) {
logTask(clock.nanoTime(), 0, type, object);
}
}
/**
* Records the beginning of the task specified by the parameters. This method
* should always be followed by completeTask() invocation to mark the end of
* task execution (usually ensured by try {} finally {} block). Failure to do
* so will result in task stack corruption.
*
* Use of this method allows to support nested task monitoring. For tasks that
* are known to not have any subtasks, logSimpleTask() should be used instead.
*
* @param type predefined task type - see ProfilerTask for available types.
* @param object object associated with that task. Can be String object that
* describes it.
*/
public void startTask(ProfilerTask type, Object object) {
// ProfilerInfo.allTasksById is supposed to be an id -> Task map, but it is in fact a List,
// which means that we cannot drop tasks to which we had already assigned ids. Therefore,
// non-leaf tasks must not have a minimum duration. However, we don't quite consistently
// enforce this, and Blaze only works because we happen not to add child tasks to those parent
// tasks that have a minimum duration.
Preconditions.checkNotNull(object);
if (isActive() && isProfiling(type)) {
taskStack.push(type, object);
}
}
/**
* Records the end of the task and moves tasks from the thread-local stack to
* the main queue. Will validate that given task type matches task at the top
* of the stack.
*
* @param type task type.
*/
public void completeTask(ProfilerTask type) {
if (isActive() && isProfiling(type)) {
long endTime = clock.nanoTime();
TaskData data = taskStack.pop();
Preconditions.checkState(
data.type == type,
"Inconsistent Profiler.completeTask() call: should have been %s but got %s (%s, %s)",
data.type,
type,
data,
taskStack);
data.duration = endTime - data.startTime;
if (data.parentId > 0) {
taskStack.peek().aggregateChild(data.type, data.duration);
}
boolean shouldRecordTask = wasTaskSlowEnoughToRecord(type, data.duration);
if (out != null && (shouldRecordTask || data.counts != null)) {
taskQueue.add(data);
}
if (shouldRecordTask) {
SlowestTaskAggregator aggregator = slowestTasks[type.ordinal()];
if (aggregator != null) {
aggregator.add(data);
}
}
}
}
/**
* Convenience method to log phase marker tasks.
*/
public void markPhase(ProfilePhase phase) {
MemoryProfiler.instance().markPhase(phase);
if (isActive() && isProfiling(ProfilerTask.PHASE)) {
Preconditions.checkState(taskStack.isEmpty(), "Phase tasks must not be nested");
logEvent(ProfilerTask.PHASE, phase.description);
}
}
/**
* Convenience method to log spawn tasks.
*
* TODO(bazel-team): Right now method expects single string of the spawn action
* as task description (usually either argv[0] or a name of the main executable
* in case of complex shell commands). Maybe it should accept Command object
* and create more user friendly description.
*/
public void logSpawn(long startTime, String arg0) {
if (isActive() && isProfiling(ProfilerTask.SPAWN)) {
logTask(startTime, clock.nanoTime() - startTime, ProfilerTask.SPAWN, arg0);
}
}
}