How to measure program execution time (within the program), not counting things like pauses during debugging?
One of the common problems I have when debugging is that anything time-dependent fails whenever I pause the program. For example, if you terminate a network connection after 1 minute of no activity from the other side, pausing the debugger for a minute will kill the connection.
I already encountered a similar issue for testing, where I wanted long periods of time to go by. My solution there was to introduce an IClock interface with ElapsedTime and Wait methods, so for example the program would pass in a RealTimeClock but testing would pass in a ManualClock.
What I want to do is make a DebugClock that pauses when the debugger pauses.
The straightforward idea is to use a periodic time开发者_运维百科r and detect when it goes way over its period and ignore that timespan, but this is not very accurate/precise and also requires managing the timer carefully. I was wondering if there was a better way.
This is my self-answer 'naive' solution that I would like to improve upon. I'm putting it as an answer to avoid bogging down the question with code.
Public Class ProgramClock
Implements IClock
'''<summary>Used to check for pauses. Only a singleton because it uses a periodic callback.</summary>'
Private Class BackingClockSingleton
Private Shared ReadOnly PausePeriod As TimeSpan = 5.Seconds
Private Shared ReadOnly TickPeriod As TimeSpan = 3.Seconds
'''<summary>Checked periodically to catch overly long periods.</summary>'
'''<remarks>Stored as a weak reference to allow cleanup when there are no ProgramClock instances justifying the periodic timer usage.</remarks>'
Private Shared _backClock As WeakReference
Private Shared _lastElapsedTime As TimeSpan
Private Shared _lostTime As TimeSpan
Private Shared ReadOnly _lock As New Object()
Public Shared Function GetElapsedTime() As TimeSpan
Return PokeElapsedTime(scheduleNextPoke:=False).Value
End Function
Private Shared Function PokeElapsedTime(ByVal scheduleNextPoke As Boolean) As TimeSpan?
SyncLock _lock
Dim clock = DirectCast(_backClock.Target, IClock)
If clock Is Nothing Then Return Nothing
Dim t = clock.ElapsedTime
Dim dt = t - _lastElapsedTime
_lastElapsedTime = t
If dt > PausePeriod Then _lostTime += dt
If scheduleNextPoke Then
clock.AsyncWait(TickPeriod).ContinueWithAction(Sub() PokeElapsedTime(scheduleNextPoke:=True))
End If
Return t - _lostTime
End SyncLock
End Function
Public Shared Function AsyncWaitUntil(ByVal t As TimeSpan) As Task
Contract.Ensures(Contract.Result(Of Task)() IsNot Nothing)
SyncLock _lock
Dim clock = DirectCast(_backClock.Target, IClock)
If clock Is Nothing Then Throw New Exceptions.InvalidStateException("Attempted to wait without a backing clock.")
Return clock.AsyncWaitUntil(t)
End SyncLock
End Function
Public Shared Function GetBackingClockReferenceToHold() As Object
Contract.Ensures(Contract.Result(Of Object)() IsNot Nothing)
SyncLock _lock
Dim clock = DirectCast(_backClock.Target, IClock)
If clock Is Nothing Then
_lostTime = 0.Seconds
_lastElapsedTime = 0.Seconds
clock = New SystemClock()
_backClock = New WeakReference(clock)
PokeElapsedTime(scheduleNextPoke:=True)
End If
Return clock
End SyncLock
End Function
End Class
Private ReadOnly _backingClockReference As Object
Private ReadOnly _initialElapsedTime As TimeSpan
<ContractInvariantMethod()> Private Sub ObjectInvariant()
Contract.Invariant(_backingClockReference IsNot Nothing)
End Sub
Public Sub New()
Me._backingClockReference = BackingClockSingleton.GetBackingClockReferenceToHold()
Me._initialElapsedTime = BackingClockSingleton.GetElapsedTime()
End Sub
Public Function AsyncWaitUntil(ByVal time As TimeSpan) As Task Implements IClock.AsyncWaitUntil
Return BackingClockSingleton.AsyncWaitUntil(time + _initialElapsedTime)
End Function
Public ReadOnly Property ElapsedTime As TimeSpan Implements IClock.ElapsedTime
Get
Return BackingClockSingleton.GetElapsedTime() - _initialElapsedTime
End Get
End Property
End Class
I'd simply do this.
class DebugClock implements Runnable {
private volatile int secondsElapsed = 0;
public void run() {
try {
while( true ) {
Thread.sleep( 1000 );
secondsElapsed++;
}
} catch( InterruptedException ex ) {}
}
}
It's not particularly precise but when you start suspending threads, you have to wave precision timing goodbye anyway. (Not to mention that Java isn't that precise with timing at the best of times.)
精彩评论