aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/profiler/Profiler.java
diff options
context:
space:
mode:
authorGravatar Han-Wen Nienhuys <hanwen@google.com>2015-02-25 16:45:20 +0100
committerGravatar Han-Wen Nienhuys <hanwen@google.com>2015-02-25 16:45:20 +0100
commitd08b27fa9701fecfdb69e1b0d1ac2459efc2129b (patch)
tree5d50963026239ca5aebfb47ea5b8db7e814e57c8 /src/main/java/com/google/devtools/build/lib/profiler/Profiler.java
Update from Google.
-- MOE_MIGRATED_REVID=85702957
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/profiler/Profiler.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/profiler/Profiler.java871
1 files changed, 871 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/profiler/Profiler.java b/src/main/java/com/google/devtools/build/lib/profiler/Profiler.java
new file mode 100644
index 0000000000..d59284880f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/profiler/Profiler.java
@@ -0,0 +1,871 @@
+// Copyright 2014 Google Inc. 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.collect.Iterables;
+import com.google.devtools.build.lib.util.Clock;
+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.PriorityQueue;
+import java.util.Queue;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+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 - commented out to avoid cyclic dependency with lib.util package
+public final class Profiler {
+ 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.
+ static final int VERSION = 0x03;
+
+ // EOF marker. Must be < 0.
+ 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();
+
+ /**
+ * A task that was very slow.
+ */
+ public 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 - commented out to avoid cyclic dependency with lib.util.
+ 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 - commented out to avoid cyclic dependency with lib.util.
+ 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).toString());
+ 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 - commented out to avoid cyclic dependency with lib.util.
+ private 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 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<SlowTask>(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 Profiler() {
+ for (ProfilerTask task : ProfilerTask.values()) {
+ if (task.slowestInstancesCount != 0) {
+ slowestTasks[task.ordinal()] = new SlowestTaskAggregator(task.slowestInstancesCount);
+ }
+ }
+ }
+
+ 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() {
+ 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 " + startTime);
+ if (duration < 0) {
+ // See note in Clock#nanoTime, which is used by Profiler#nanoTimeMaybe.
+ duration = 0;
+ }
+
+ TaskData parent = taskStack.peek();
+ if (parent != null) {
+ parent.aggregateChild(type, duration);
+ }
+ if (wasTaskSlowEnoughToRecord(type, duration)) {
+ TaskData data = taskStack.create(startTime, type, object);
+ data.duration = duration;
+ if (out != null) {
+ taskQueue.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();
+ // Do not use Preconditions class below due to the very expensive
+ // toString() calls used in the message.
+ if (data.type != type) {
+ throw new IllegalStateException("Inconsistent Profiler.completeTask() call for the "
+ + type + " task.\n " + 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);
+ }
+ }
+
+}