| // 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.Predicate; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.Iterables; | |
| 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.Clock; | |
| import com.google.devtools.build.lib.util.Preconditions; | |
| 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 - commented out to avoid cyclic dependency with lib.util package | |
| public final class Profiler { | |
| private static final Logger LOG = Logger.getLogger(Profiler.class.getName()); | |
| 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(); | |
| private static final int HISTOGRAM_BUCKETS = 20; | |
| /** | |
| * | |
| * 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)); | |
| 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<>(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. | |
| LOG.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(); | |
| // 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); | |
| } | |
| } | |
| } |