| // Copyright 2015 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 com.google.common.base.Preconditions; | |
| import com.google.devtools.build.lib.clock.BlazeClock; | |
| import com.google.devtools.build.lib.clock.Clock; | |
| import java.time.Duration; | |
| import java.util.concurrent.TimeUnit; | |
| import java.util.concurrent.atomic.AtomicBoolean; | |
| import java.util.concurrent.atomic.AtomicReference; | |
| import java.util.logging.Logger; | |
| import javax.annotation.Nullable; | |
| /** | |
| * A convenient way to actively get access to timing information (e.g. for logging and/or | |
| * profiling purposes) with minimal boilerplate. The lack of boilerplate comes at a performance | |
| * cost; do not use {@link AutoProfiler} on performance critical code. | |
| * | |
| * <p>The intended usage is: | |
| * | |
| * <pre> | |
| * {@code | |
| * try (AutoProfiler p = AutoProfiler.logged("<description of your code>")) { | |
| * // Your code here. | |
| * } | |
| * } | |
| * </pre> | |
| * | |
| * <p>but if the try-with-resources pattern is too cumbersome, you can also do | |
| * | |
| * <pre> | |
| * {@code | |
| * AutoProfiler p = AutoProfiler.logged("<description of your code>"); | |
| * // Your code here. | |
| * long elapsedTimeNanos = p.completeAndGetElapsedTimeNanos(); | |
| * } | |
| * </pre> | |
| * | |
| * <p>An {@link AutoProfiler} can also automatically talk to the active {@link Profiler} instance: | |
| * | |
| * <pre> | |
| * {@code | |
| * try (AutoProfiler p = AutoProfiler.profiled("<description of your code>")) { | |
| * // Your code here. | |
| * } | |
| * } | |
| * </pre> | |
| */ | |
| public class AutoProfiler implements SilentCloseable { | |
| private static final AtomicReference<Clock> CLOCK_REF = new AtomicReference<>(null); | |
| private static final AtomicReference<LoggingElapsedTimeReceiverFactory> | |
| LOGGING_ELAPSED_TIME_RECEIVER_FACTORY_REF = new AtomicReference<>( | |
| LoggingElapsedTimeReceiver.FACTORY); | |
| private final ElapsedTimeReceiver elapsedTimeReceiver; | |
| private final long startTimeNanos; | |
| private final AtomicBoolean closed = new AtomicBoolean(false); | |
| private AutoProfiler(ElapsedTimeReceiver elapsedTimeReceiver, long startTimeNanos) { | |
| this.elapsedTimeReceiver = elapsedTimeReceiver; | |
| this.startTimeNanos = startTimeNanos; | |
| } | |
| /** A opaque receiver of elapsed time information. */ | |
| public interface ElapsedTimeReceiver { | |
| /** | |
| * Receives the elapsed time of the lifetime of an {@link AutoProfiler} instance. | |
| * | |
| * <p>Note that System#nanoTime isn't guaranteed to be non-decreasing, so implementations should | |
| * check for non-positive {@code elapsedTimeNanos} if they care about this sort of thing. | |
| */ | |
| void accept(long elapsedTimeNanos); | |
| } | |
| /** | |
| * Sets the clock to use. May be called before trying to call any of the other static methods; | |
| * otherwise {@link BlazeClock#instance} will be used. | |
| * | |
| * <p>Calling this more than once (e.g. after calling any of the other static methods) results in | |
| * unspecified behavior. | |
| */ | |
| public static void setClock(Clock clock) { | |
| CLOCK_REF.set(clock); | |
| } | |
| private static long nanoTime() { | |
| Clock clock = CLOCK_REF.get(); | |
| return clock != null ? clock.nanoTime() : BlazeClock.instance().nanoTime(); | |
| } | |
| /** | |
| * Factory to use for creating {@link ElapsedTimeReceiver} instances for servicing | |
| * {@link #logged} et al. | |
| */ | |
| public interface LoggingElapsedTimeReceiverFactory { | |
| ElapsedTimeReceiver create( | |
| String taskDescription, Logger logger, long minTimeForLogging, TimeUnit timeUnit); | |
| } | |
| /** | |
| * Inject a custom {@link LoggingElapsedTimeReceiverFactory} for servicing {@link #logged} et al. | |
| * | |
| * <p>Use this if you want custom logging behavior. | |
| */ | |
| public static void setLoggingElapsedTimeReceiverFactory( | |
| LoggingElapsedTimeReceiverFactory factory) { | |
| LOGGING_ELAPSED_TIME_RECEIVER_FACTORY_REF.set(factory); | |
| } | |
| /** | |
| * Returns an {@link AutoProfiler} that, when closed, logs the elapsed time in milliseconds to | |
| * the given {@link Logger}. | |
| * | |
| * <p>The returned {@link AutoProfiler} is thread-safe. | |
| */ | |
| public static AutoProfiler logged(String taskDescription, Logger logger) { | |
| return logged(taskDescription, logger, TimeUnit.MILLISECONDS); | |
| } | |
| /** | |
| * Returns an {@link AutoProfiler} that, when closed, logs the elapsed time in the given | |
| * {@link TimeUnit} to the given {@link Logger}. | |
| * | |
| * <p>The returned {@link AutoProfiler} is thread-safe. | |
| */ | |
| public static AutoProfiler logged(String taskDescription, Logger logger, TimeUnit timeUnit) { | |
| return create( | |
| LOGGING_ELAPSED_TIME_RECEIVER_FACTORY_REF | |
| .get() | |
| .create(taskDescription, logger, 0L, timeUnit)); | |
| } | |
| /** | |
| * Returns an {@link AutoProfiler} that, when closed, logs the elapsed time in milliseconds to the | |
| * given {@link Logger} if it is greater than {@code minTimeForLoggingInMilliseconds}. | |
| * | |
| * <p>The returned {@link AutoProfiler} is thread-safe. | |
| */ | |
| public static AutoProfiler logged( | |
| String taskDescription, Logger logger, long minTimeForLoggingInMilliseconds) { | |
| return create( | |
| LOGGING_ELAPSED_TIME_RECEIVER_FACTORY_REF | |
| .get() | |
| .create( | |
| taskDescription, logger, minTimeForLoggingInMilliseconds, TimeUnit.MILLISECONDS)); | |
| } | |
| /** | |
| * Returns an {@link AutoProfiler} that, when closed, records the elapsed time using {@link | |
| * Profiler}. | |
| * | |
| * <p>The returned {@link AutoProfiler} is thread-safe. | |
| */ | |
| public static AutoProfiler profiled(String description, ProfilerTask profilerTaskType) { | |
| return create(new ProfilingElapsedTimeReceiver(description, profilerTaskType)); | |
| } | |
| /** | |
| * Returns an {@link AutoProfiler} that, when closed, records the elapsed time using | |
| * {@link Profiler} and also logs it (in milliseconds) to the given {@link Logger}. | |
| * | |
| * <p>The returned {@link AutoProfiler} is thread-safe. | |
| */ | |
| public static AutoProfiler profiledAndLogged(String taskDescription, | |
| ProfilerTask profilerTaskType, Logger logger) { | |
| ElapsedTimeReceiver profilingReceiver = | |
| new ProfilingElapsedTimeReceiver(taskDescription, profilerTaskType); | |
| ElapsedTimeReceiver loggingReceiver = | |
| LOGGING_ELAPSED_TIME_RECEIVER_FACTORY_REF | |
| .get() | |
| .create(taskDescription, logger, 0L, TimeUnit.MILLISECONDS); | |
| return create(new SequencedElapsedTimeReceiver(profilingReceiver, loggingReceiver)); | |
| } | |
| /** | |
| * Returns an {@link AutoProfiler} that doesn't do anything when closed and so is only useful for | |
| * {@link #completeAndGetElapsedTimeNanos()}. | |
| */ | |
| public static AutoProfiler timed() { | |
| return create(NULL_RECEIVER); | |
| } | |
| /** | |
| * Returns an {@link AutoProfiler} that, when closed, invokes the given | |
| * {@link ElapsedTimeReceiver}. | |
| * | |
| * <p>The returned {@link AutoProfiler} is as thread-safe as the given | |
| * {@link ElapsedTimeReceiver} is. | |
| */ | |
| public static AutoProfiler create(ElapsedTimeReceiver elapsedTimeReceiver) { | |
| return new AutoProfiler(elapsedTimeReceiver, nanoTime()); | |
| } | |
| /** | |
| * Manually completes the profiling (useful to trigger the underlying action on completion). | |
| * | |
| * <p>At most one of {@link #complete}, {@link #completeAndGetElapsedTimeNanos} and {@link #close} | |
| * may be called. | |
| */ | |
| public void complete() { | |
| close(); | |
| } | |
| /** | |
| * Manually completes the profiling and returns the elapsed time in nanoseconds. | |
| * | |
| * <p>At most one of {@link #complete}, {@link #completeAndGetElapsedTimeNanos} and {@link #close} | |
| * may be called. | |
| */ | |
| public long completeAndGetElapsedTimeNanos() { | |
| long elapsedTimeNanos = nanoTime() - startTimeNanos; | |
| Preconditions.checkState(closed.compareAndSet(false, true)); | |
| elapsedTimeReceiver.accept(elapsedTimeNanos); | |
| return elapsedTimeNanos; | |
| } | |
| /** | |
| * Automatically completes the profiling. | |
| * | |
| * <p>At most one of {@link #complete}, {@link #completeAndGetElapsedTimeNanos} and {@link #close} | |
| * may be called. | |
| */ | |
| @Override | |
| public void close() { | |
| long elapsedTimeNanos = nanoTime() - startTimeNanos; | |
| Preconditions.checkState(closed.compareAndSet(false, true)); | |
| elapsedTimeReceiver.accept(elapsedTimeNanos); | |
| } | |
| private static final ElapsedTimeReceiver NULL_RECEIVER = new ElapsedTimeReceiver() { | |
| @Override | |
| public void accept(long elapsedTimeNanos) { | |
| } | |
| }; | |
| private static class SequencedElapsedTimeReceiver implements ElapsedTimeReceiver { | |
| private final ElapsedTimeReceiver firstReceiver; | |
| private final ElapsedTimeReceiver secondReceiver; | |
| private SequencedElapsedTimeReceiver(ElapsedTimeReceiver firstReceiver, | |
| ElapsedTimeReceiver secondReceiver) { | |
| this.firstReceiver = firstReceiver; | |
| this.secondReceiver = secondReceiver; | |
| } | |
| @Override | |
| public void accept(long elapsedTimeNanos) { | |
| firstReceiver.accept(elapsedTimeNanos); | |
| secondReceiver.accept(elapsedTimeNanos); | |
| } | |
| } | |
| /** {@link ElapsedTimeReceiver} that will not log a message if the time elapsed is too small. */ | |
| public abstract static class FilteringElapsedTimeReceiver implements ElapsedTimeReceiver { | |
| private final String taskDescription; | |
| private final TimeUnit timeUnit; | |
| private final long minNanosForLogging; | |
| protected FilteringElapsedTimeReceiver( | |
| String taskDescription, long minTimeForLogging, TimeUnit timeUnit) { | |
| this.taskDescription = taskDescription; | |
| this.timeUnit = timeUnit; | |
| this.minNanosForLogging = timeUnit.toNanos(minTimeForLogging); | |
| } | |
| protected abstract void log(String logMessage); | |
| /** | |
| * Returns a message about the amount of time spent doing a task if {@code elapsedTimeNanos} is | |
| * at least {@link #minNanosForLogging} and null otherwise. | |
| */ | |
| @Nullable | |
| private String loggingMessage(long elapsedTimeNanos) { | |
| if (elapsedTimeNanos >= minNanosForLogging) { | |
| return String.format( | |
| "Spent %d %s doing %s", | |
| timeUnit.convert(elapsedTimeNanos, TimeUnit.NANOSECONDS), | |
| timeUnit.toString().toLowerCase(), | |
| taskDescription); | |
| } else { | |
| return null; | |
| } | |
| } | |
| @Override | |
| public final void accept(long elapsedTimeNanos) { | |
| String logMessage = loggingMessage(elapsedTimeNanos); | |
| if (logMessage != null) { | |
| log(logMessage); | |
| } | |
| } | |
| } | |
| /** | |
| * {@link ElapsedTimeReceiver} that logs a message in {@link #accept} if the elapsed time is at | |
| * least a given threshold. | |
| */ | |
| public static class LoggingElapsedTimeReceiver extends FilteringElapsedTimeReceiver { | |
| private static final LoggingElapsedTimeReceiverFactory FACTORY = | |
| new LoggingElapsedTimeReceiverFactory() { | |
| @Override | |
| public ElapsedTimeReceiver create( | |
| String taskDescription, Logger logger, long minTimeForLogging, TimeUnit timeUnit) { | |
| return new LoggingElapsedTimeReceiver( | |
| taskDescription, logger, minTimeForLogging, timeUnit); | |
| } | |
| }; | |
| protected final Logger logger; | |
| protected LoggingElapsedTimeReceiver( | |
| String taskDescription, Logger logger, long minTimeForLogging, TimeUnit timeUnit) { | |
| super(taskDescription, minTimeForLogging, timeUnit); | |
| this.logger = logger; | |
| } | |
| @Override | |
| protected void log(String logMessage) { | |
| logger.info(logMessage); | |
| } | |
| } | |
| private static class ProfilingElapsedTimeReceiver implements ElapsedTimeReceiver { | |
| private final long startTimeNanos; | |
| private final String description; | |
| private final ProfilerTask profilerTaskType; | |
| private ProfilingElapsedTimeReceiver(String description, ProfilerTask profilerTaskType) { | |
| this.startTimeNanos = Profiler.nanoTimeMaybe(); | |
| this.description = description; | |
| this.profilerTaskType = profilerTaskType; | |
| } | |
| @Override | |
| public void accept(long elapsedTimeNanos) { | |
| if (elapsedTimeNanos > 0) { | |
| Profiler.instance() | |
| .logSimpleTaskDuration( | |
| startTimeNanos, Duration.ofNanos(elapsedTimeNanos), profilerTaskType, description); | |
| } | |
| } | |
| } | |
| } | |