At one point, I wanted to figure out what was slowing down a program that I largely had not written. I wanted to be able to time multiple nested pieces, and multiple sequential pieces, all at the same time. I wanted the timing to be pretty accurate, which meant using the Stopwatch object and also meant that the code had to have little time impact on its own, and I wanted it to be cheap. Naturally, you can buy commercial profilers, and there are some in some versions of VB. I didn't have access to either of those when I wrote this, and, quite frankly, the ease of use is such that I still rather like it.

Here's the code. Explanation will follow:

Code:
Public Class ProfileTimer
    Private mStop As Stopwatch
    Private mRegister As System.Collections.Generic.List(Of TimeHolder)
    Private mIsRunning As Boolean
    Private mLock As Boolean

    Public Shared TheTime As New ProfileTimer

    Private Sub New()
        mStop = New Stopwatch
        mRegister = New System.Collections.Generic.List(Of TimeHolder)
        mIsRunning = False
        mLock = False
    End Sub

#Region "Properties"

    Public ReadOnly Property GetString(ByVal index As Integer) As String
        Get
            If index >= 0 AndAlso index < mRegister.Count Then
                Return mRegister(index).TokenName & ": " & (mRegister(index).StopTime - mRegister(index).StartTime).ToString & "ms"
            Else
                Return ""
            End If
        End Get
    End Property

    Public ReadOnly Property Count() As Integer
        Get
            Return mRegister.Count
        End Get
    End Property

    Public ReadOnly Property GetString(ByVal tokn As String) As String
        Get
            Dim n As Integer = GetIndex(tokn)
            If n >= 0 Then
                Return mRegister(n).TokenName & ": " & (mRegister(n).StopTime - mRegister(n).StartTime).ToString & "ms"
            Else
                Return ""
            End If
        End Get
    End Property

    Public ReadOnly Property GetString() As String
        Get
            If mRegister.Count = 0 Then
                Return "No Timing"
            Else
                Dim x As Integer
                Dim sb As New System.Text.StringBuilder

                For x = 0 To mRegister.Count - 1
                    sb.Append(mRegister(x).TokenName & ": " & (mRegister(x).StopTime - mRegister(x).StartTime).ToString & "ms" & Environment.NewLine)
                Next

                Return sb.ToString
            End If
        End Get
    End Property

    Public Property Lock() As Boolean
        Get
            Return mLock
        End Get
        Set(ByVal value As Boolean)
            mLock = value
        End Set
    End Property

#End Region

#Region "Methods"

    Public Sub StopAll()
        Dim x As Integer
        Dim th As TimeHolder

        For x = 0 To mRegister.Count - 1
            If mRegister(x).StopTime = -1 Then
                th = mRegister(x)
                th.StopTime = mStop.ElapsedMilliseconds
                mRegister(x) = th
            End If
        Next
    End Sub

    Private Function StopTrack(ByVal tokn As String) As Long
        Dim n As Integer = GetIndex(tokn)
        Dim th As TimeHolder

        If n >= 0 Then
            th = mRegister(n)
            th.StopTime = mStop.ElapsedMilliseconds
            mRegister(n) = th
            Return mRegister(n).StopTime - mRegister(n).StartTime
        End If
    End Function

    Private Sub StartTrack(ByVal tokn As String)
        mRegister.Add(New TimeHolder(tokn, GetTick))
    End Sub

    Public Sub Track(ByVal tokn As String)
        Dim n As Integer = GetIndex(tokn)
        If n = -1 Then
            StartTrack(tokn)
        ElseIf mRegister(n).StopTime >= 0 Then
            If Not Lock Then
                mRegister.RemoveAt(n)
                StartTrack(tokn)
            End If
        Else
            StopTrack(tokn)
        End If
    End Sub

    Private Function GetTick() As Long
        If mIsRunning Then
            Return mStop.ElapsedMilliseconds
        Else
            mIsRunning = True
            mStop.Start()
            Return 0
        End If
    End Function

    Private Function GetIndex(ByVal tokn As String) As Integer
        Dim x As Integer

        For x = 0 To mRegister.Count - 1
            If mRegister(x).TokenName = tokn Then
                Return x
            End If
        Next
        Return -1
    End Function

#End Region
End Class

Public Structure TimeHolder
    Implements IEquatable(Of String)

    Public StartTime As Long
    Public StopTime As Long
    Public TokenName As String

    Public Sub New(ByVal tokn As String, ByVal sTime As Long)
        StartTime = sTime
        TokenName = tokn
        StopTime = -1
    End Sub

    Public Function Equals1(ByVal other As String) As Boolean Implements System.IEquatable(Of String).Equals
        Return (other = TokenName)
    End Function
End Structure
There is little to this that needs much explanation. The whole thing is a Singleton class with one significant method: Track.

To use the class, copy it somewhere in the project. You access the Singleton via: ProfileTimer.TheTime

A timed segment is called a track, and each track has a 'name', which can be any string. To start timing a segment, call Track() while passing in a name. Calling Track a second time with the same name will stop the segment. Therefore, you can write code like this:
Code:
Public Sub Foo()
 
 'Start track "a" to time the whole loop.
 ProfileTimer.TheTime.Track("a")
 For x as Integer = 0 To 1000
  'Start track "b" to time just this call.
  ProfileTimer.TheTime.Track("b")
  Bar()
  'Stop track "b".
  ProfileTimer.TheTime.Track("b")
 Next
End Sub
That's a pretty silly example, because track "b" will just be starting and stopping with each iteration, while track "a" is really timing almost the same thing, but it does give some idea of what can be done with the timer. A more complete example would be tedious.

At any time, you can call StopAll to stop all running tracks, and you can call one of the GetString properties to see the result. If you pass in no arguments to GetString, it will return the time for each track, with one track per line. Alternatively, you can get the time for a single track by passing in the track name, or by passing in the index into the track list.

There is some other functionality in there about locking and unlocking tracks. Frankly, I wrote this so long ago that I don't remember how that works, but it has to do with stopping and starting tracks, and I have never actually used it. The rest works well. That part....well, who knows, and frankly, who cares. Excess functionality with little utility.