开发者

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.)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜