-
Notifications
You must be signed in to change notification settings - Fork 96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Makes TimedPhase auto-closeable. #143
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Copyright 2017 Yahoo Inc. | ||
// Licensed under the terms of the Apache license. Please see LICENSE.md file distributed with this work for terms. | ||
package com.yahoo.bard.webservice.logging; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* Represents a phase that is timed. | ||
* TimedPhase is used to associate a Timer located in the registry with the exact duration of such a phase for a | ||
* specific request. Times are in nanoseconds. | ||
* <p> | ||
* Note: This class is NOT thread-safe. Timers are intended to be started once by one thread, and stopped once by | ||
* one thread (though those threads are not necessarily the same). | ||
*/ | ||
public class TimedPhase implements AutoCloseable { | ||
private static final Logger LOG = LoggerFactory.getLogger(TimedPhase.class); | ||
|
||
private final String name; | ||
private long start; | ||
private long duration; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I tried playing around with using Anyway, here's the relevant results of my exploration, in case they help: private static final long NOT_RUNNING = 0;
private final String name;
private final AtomicLong start = new AtomicLong();
private final AtomicLong duration = new AtomicLong();
/**
* Start the phase.
*
* @return This phase after being started
*/
public TimedPhase start() {
if (!start.compareAndSet(NOT_RUNNING, System.nanoTime())) {
LOG.warn("Tried to start timer that is already running: {}", name);
}
return this;
}
/**
* Stop the phase.
*/
public void stop() {
long started = start.getAndUpdate(ignoredCurrent -> NOT_RUNNING);
if (started == 0) {
LOG.warn("Tried to stop timer that has not been started: {}", name);
return;
}
duration.addAndGet(System.nanoTime() - started);
}
public long getDuration() {
return duration.get();
}
public boolean isStarted() {
return start.get() != 0;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm ok with making it thread safe, especially since many of these timers are stopped on a different thread than they're started (the request vs response thread). Rethinking the semantics is outside the scope of this PR though, I think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, after playing with it a little bit, I think I'm going to punt on this, largely because validating whether a timer is running outside of this class or not becomes a lot trickier in a multi-threaded scenario. |
||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param name Name of the phase | ||
*/ | ||
public TimedPhase(String name) { | ||
this.name = name; | ||
} | ||
|
||
/** | ||
* Start the phase. | ||
* | ||
* @return This phase after being started | ||
*/ | ||
public TimedPhase start() { | ||
if (isRunning()) { | ||
LOG.warn("Tried to start timer that is already running: {}", name); | ||
} else { | ||
start = System.nanoTime(); | ||
} | ||
return this; | ||
} | ||
|
||
/** | ||
* Stop the phase. | ||
* <p> | ||
* This method just stops the timer. It does not register the time with the {@link RequestLog}. To register | ||
* the timer, invoke {@link TimedPhase#registerTime()}. To do both with a single method call, see | ||
* {@link TimedPhase#close()} | ||
* | ||
* @see TimedPhase#registerTime() | ||
* @see TimedPhase#close() | ||
*/ | ||
public void stop() { | ||
if (!isRunning()) { | ||
LOG.warn("Tried to stop timer that has not been started: {}", name); | ||
return; | ||
} | ||
duration += System.nanoTime() - start; | ||
start = 0; | ||
} | ||
|
||
/** | ||
* Registers the duration of this timer with the RequestLog. | ||
* <p> | ||
* It is highly recommended that you {@link TimedPhase#stop()}} the timer first. Otherwise, the timings may | ||
* be inaccurate. To both stop and register the timer at once see {@link TimedPhase#close}. | ||
* | ||
* @see TimedPhase#stop() | ||
* @see TimedPhase#close() | ||
*/ | ||
public void registerTime() { | ||
RequestLog.registerTime(this); | ||
} | ||
|
||
/** | ||
* Return the duration of the timer in nanoseconds. | ||
* | ||
* @return The duration of the timer in nanoseconds | ||
*/ | ||
public long getDuration() { | ||
if (isRunning()) { | ||
LOG.warn("Timer '{}' is still running. Timings may be incorrect.", getName()); | ||
} | ||
return duration; | ||
} | ||
|
||
public String getName() { | ||
return name; | ||
} | ||
|
||
public TimeUnit getUnit() { | ||
return TimeUnit.NANOSECONDS; | ||
} | ||
|
||
public boolean isRunning() { | ||
return start != 0; | ||
} | ||
|
||
/** | ||
* Stops the timer, and registers the timer with the RequestLog. | ||
* <p> | ||
* This is primarily meant to be used by the try-with-resources block, which both stops the timer and registers it | ||
* with the RequestLog, though it can of course be called manually as well. If you want to stop the timer, but | ||
* don't want to register the timer just yet, then see {@link TimedPhase#stop}. | ||
* | ||
* @see TimedPhase#stop() | ||
* @see TimedPhase#registerTime() | ||
*/ | ||
@Override | ||
public void close() { | ||
stop(); | ||
registerTime(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we make "stop" convert to doing the registering, calling "close" is likely not needed, but "stop" will be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
close
has to do the registering anyway, because that's the method invoked when thetry-with-resources
block ends. So even if we change howstop
works, and hadclose
delegate tostop
, we still wouldn't need to change this invocation.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plus we shouldn't change this from
close
tostop
. In my opinion,RequestLog::stopTiming
should have the same semantics as what the try-with-resources block does (with the exception of the existence check that the try-with-resources doesn't have to worry about).