Compile function SRP_Stopwatch(Service, Param1, Param2, Param3)

/***********************************************************************************************************************

    This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
    permission from SRP Computer Solutions, Inc.

    Name        :   SRP_Stopwatch

    Description :   Service module for benchmarking.

    Notes       :   SRP_Stopwatch simplifies benchmarking by tracking multiple benchmarks and displaying them for you at
                    the end. You need nothing other than the source code below. There are no dependencies on any other
                    DLLs, OCXs, or RDKs. It uses only that which ships with OpenInsight.

    Parameters  :
        Service     [in] -- The stopwatch service.
        Param1      [in] -- The service specific parameter.
        Ans        [out] -- Response from the service, usually benchmark results.

    Metadata:
        @@DEFINE_SERVICES_SIGNATURE(@SERVICE, @PARAMS)

    History     :   (Date, Initials, Notes)
        01/22/13    krf     Original programmer
        05/09/16    krf     Updated code to allow Start/Stop to be called multiple times for same name, accumulating a
                            total time.
        11/30/16    dmb     Fix support for single character stopwatch names in the GetAll and Reset services by
                            changing the GE operator to GT.
        11/30/16    dmb     If the Name argument is empty, use "<default>". This makes it easier to create a simple
                            one-off stopwatch.
        08/17/17    krf     Added Count and Average related services
        08/30/17    krf     Fixed formatting issues; added secret TEST service

***********************************************************************************************************************/

If Assigned(Service) else Service = ""
If Assigned(Param1) else Param1 = ""
Ans = ""

Declare function SRP_Stopwatch, GetTickCount, Max, Index
Declare subroutine Msg

// global common
Common /SRPStopwatch/ Names@, Total@

Convert @Lower.Case to @Upper.Case in Service

// benchmark specific commons (Param1 = Name for these services)
Locate Service in "GETAVERAGE,GETBENCHMARK,GETCOUNT,GETDATA,SHOW,START,STOP,TEST" using "," setting Pos then
    Name = Param1
    If Name EQ '' then
        Name = "<default>"
    end
    If Len(Name) then
        
        // Load the commons
        StopWatchName = "SRP_Stopwatch_":Name
        Common //StopWatchName// StartTime@, ElapsedTime@, Count@
        
        // Branch to the service
        Begin Case
            Case Service _EQC "GETAVERAGE"      ; GoSub GetAverage
            Case Service _EQC "GETBENCHMARK"    ; GoSub GetBenchmark
            Case Service _EQC "GETCOUNT"        ; GoSub GetCount
            Case Service _EQC "GETDATA"         ; GoSub GetData
            Case Service _EQC "SHOW"            ; GoSub Show
            Case Service _EQC "START"           ; GoSub Start
            Case Service _EQC "STOP"            ; GoSub Stop
            Case Service _EQC "TEST"            ; GoSub Test
        End Case
        
    end
end else
        
    // Non-benchmark specific services
    Begin Case
        Case Service _EQC "GETALL"              ; GoSub GetAll
        Case Service _EQC "GETTOTALBENCHMARK"   ; GoSub GetTotalBenchmark
        Case Service _EQC "RESET"               ; GoSub Reset
        Case Service _EQC "SHOWALL"             ; GoSub ShowAll
    End Case
        
end

Return Ans


///////////////////////////////////////////////////////////////////////////////////////////////////
// SERVICES
///////////////////////////////////////////////////////////////////////////////////////////////////


//-------------------------------------------------------------------------------------------------
// Returns the elapsed time for a benchmark.
// @@DEFINE_SERVICE(GetAll)
//-------------------------------------------------------------------------------------------------
GetAll:

    // Loop through all the benchmarks and get their results
    LenNames = Len(Names@)
    If LenNames then
        Benchmarks = ""
        Pos = 1
        Loop
        Until Pos GT LenNames
            Name = Names@[Pos, @FM]
            Pos = Col2() + 1
            Benchmarks<-1> = Name:@VM:SRP_Stopwatch("GetData", Name, @VM)
        Repeat
        Ans = Benchmarks
    end

return


//-------------------------------------------------------------------------------------------------
// Returns the average elapsed time for a benchmark.
// @@DEFINE_SERVICE(GetAverage, Name)
//-------------------------------------------------------------------------------------------------
GetAverage:

    If Len(StartTime@) then
        If Len(ElapsedTime@) then
            Count = If Count@ GT 0 then Count@ else 1
            WorkingTime = Int(ElapsedTime@ / Count)
            GoSub FormatWorkingTime
        end
    end

return


//-------------------------------------------------------------------------------------------------
// Returns the elapsed time for a benchmark.
// @@DEFINE_SERVICE(GetBenchmark, Name)
//-------------------------------------------------------------------------------------------------
GetBenchmark:

    If Len(StartTime@) then
        If Len(ElapsedTime@) then
            WorkingTime = ElapsedTime@
            GoSub FormatWorkingTime
        end
    end

return


//-------------------------------------------------------------------------------------------------
// Returns the average elapsed time for a benchmark.
// @@DEFINE_SERVICE(GetAverage, Name)
//-------------------------------------------------------------------------------------------------
GetCount:

    If Len(StartTime@) then
        If Len(ElapsedTime@) then
            Ans = Count@
        end
    end

return


//-------------------------------------------------------------------------------------------------
// Returns the elapsed time for a benchmark.
// @@DEFINE_SERVICE(GetData, Name, Delim)
//-------------------------------------------------------------------------------------------------
GetData:

    // The delimiter
    Delim = If Assigned(Param2) then Param2 else @FM

    // Format and return the data
    WorkingTime = ElapsedTime@
    GoSub FormatWorkingTime
    Benchmark = Ans
    Count = If Count@ GT 0 then Count@ else 1
    WorkingTime = Int(ElapsedTime@ / Count)
    GoSub FormatWorkingTime
    Average = Ans
    Ans = Benchmark : Delim : Count : Delim : Average

return


//-------------------------------------------------------------------------------------------------
// Returns the elapsed time for all benchmarks.
// @@DEFINE_SERVICE(GetTotalBenchmark)
//-------------------------------------------------------------------------------------------------
GetTotalBenchmark:

    If Len(Total@) then
        WorkingTime = Total@
        GoSub FormatWorkingTime
    end

return


//-------------------------------------------------------------------------------------------------
// Resets the stopwatch. This should be called at the beginning of a benchmark.
// @@DEFINE_SERVICE(Reset)
//-------------------------------------------------------------------------------------------------
Reset:

    // Release all the commons, if any
    LenNames = Len(Names@)
    If LenNames then
        Pos = 1
        Loop
        Until Pos GT LenNames
            Name = Names@[Pos, @FM]
            Pos = Col2() + 1
            StopWatchName = "SRP_Stopwatch_":Name
            FreeCommon StopWatchName
        Repeat
    end
    
    // Clear the names
    Names@ = ""
    Total@ = 0
    
return


//-------------------------------------------------------------------------------------------------
// Shows the benchmark in a message box
// @@DEFINE_SERVICE(Show, Name)
//-------------------------------------------------------------------------------------------------
Show:

    GoSub GetData

    Text = ""
    Benchmark = Ans<1>
    Count = Ans<2>
    Average = Ans<3>
    
    // Format the benchmark
    If (Index(Benchmark, "00h", 1) EQ 0) else
        Benchmark[1, 4] = ""
    end
    If (Index(Benchmark, "00m", 1) EQ 0) else
        Benchmark[1, 4] = ""
    end
    If (Index(Benchmark, "00s", 1) EQ 0) else
        Benchmark[1, 4] = ""
    end
    Text = Name:" : ":Benchmark
    
    // Format the count and average
    If Count GT 1 then
        If (Index(Average, "00h", 1) EQ 0) else
            Average[1, 4] = ""
        end
        If (Index(Average, "00m", 1) EQ 0) else
            Average[1, 4] = ""
        end
        If (Index(Average, "00s", 1) EQ 0) else
            Average[1, 4] = ""
        end
        Text := " : ":Count:" times : ":Average:" avg"
    end
    
    // Show it
    MsgStruct = ""
    MsgStruct<1>  = Text
    MsgStruct<12> = "SRP Stopwatch Results"
    MsgStruct<18> = "Consolas":@SVM:"-13"
    Msg(@Window, MsgStruct)
    
    Ans = 1

return


//-------------------------------------------------------------------------------------------------
// Shows the results for all benchmarks in a message box
// @@DEFINE_SERVICE(ShowAll)
//-------------------------------------------------------------------------------------------------
ShowAll:

    GoSub GetAll

    Text = ""
    LenAns = Len(Ans)
    NumBenchmarks = DCount(Ans, @FM)
    If LenAns then

        // Loop throught the results to determine the best way to format
        MaxNameLen = 5
        MaxCountLen = 1
        HasHours = 0
        HasMinutes = 0
        HasSeconds = 0
        HasCounts = 0
        HasAveHours = 0
        HasAveMinutes = 0
        HasAveSeconds = 0
        Pos = 1
        Loop
        Until Pos GE LenAns
            Result = Ans[Pos, @FM]
            Pos = Col2() + 1
            Name = Result[1, @VM]
            Time = Result[Col2() + 1, @VM]
            Count = Result[Col2() + 1, @VM]
            Average = Result[Col2() + 1, @VM]
            MaxNameLen = Max(MaxNameLen, Len(Name))
            If HasHours else
                HasHours = (Index(Time, "00h", 1) EQ 0)
            end
            If HasMinutes else
                HasMinutes = (Index(Time, "00m", 1) EQ 0)
            end
            If HasSeconds else
                HasSeconds = (Index(Time, "00s", 1) EQ 0)
            end
            //If Count GT 1 OR HasCounts EQ 1 then
                HasCounts = 1
                MaxCountLen = Max(MaxCountLen, Len(Count))
                If HasAveHours else
                    HasAveHours = (Index(Average, "00h", 1) EQ 0)
                end
                If HasAveMinutes else
                    HasAveMinutes = (Index(Average, "00m", 1) EQ 0)
                end
                If HasAveSeconds else
                    HasAveSeconds = (Index(Average, "00s", 1) EQ 0)
                end
            //end
        Repeat
        
        // Check how long the total time will be
        If NumBenchmarks GT 1 then
            TotalTime = SRP_Stopwatch("GetTotalBenchmark")
            If HasHours else
                HasHours = (Index(TotalTime, "00h", 1) EQ 0)
            end
            If HasMinutes else
                HasMinutes = (Index(TotalTime, "00m", 1) EQ 0)
            end
            If HasSeconds else
                HasSeconds = (Index(TotalTime, "00s", 1) EQ 0)
            end
        end
        
        // Loop through the results to build the message text
        MaxLine = 0
        Pos = 1
        Loop
        Until Pos GE LenAns
            Result = Ans[Pos, @FM]
            Pos = Col2() + 1
            Name = Result[1, @VM]
            Time = Result[Col2() + 1, @VM]
            Count = Result[Col2() + 1, @VM]
            Average = Result[Col2() + 1, @VM]
            If HasHours then
                Swap "00h" with "   " in Time
            end else
                Time[1, 4] = ""
            end
            If HasMinutes then
                Swap "00m" with "   " in Time
            end else
                Time[1, 4] = ""
            end
            If HasSeconds then
                Swap "00s" with "   " in Time
            end else
                Time[1, 4] = ""
            end
            Line = Fmt(Name, "L( )#":(MaxNameLen + 1)):": ":Time
            If HasCounts then
                If HasAveHours then
                    Swap "00h" with "   " in Average
                end else
                    Average[1, 4] = ""
                end
                If HasAveMinutes then
                    Swap "00m" with "   " in Average
                end else
                    Average[1, 4] = ""
                end
                If HasAveSeconds then
                    Swap "00s" with "   " in Average
                end else
                    Average[1, 4] = ""
                end
                Line := " : ":Fmt(Count, "R( )#":MaxCountLen):" times : ":Average:" avg"
            end                
            MaxLine = Max(MaxLine, Len(Line))
            Text := Line:"|"
        Repeat
        
        If NumBenchmarks GT 1 then
            
            // Divider line
            Text := Str("-", MaxLine):"|"
            
            // Total line
            If HasHours then
                Swap "00h" with "   " in TotalTime
            end else
                TotalTime[1, 4] = ""
            end
            If HasMinutes then
                Swap "00m" with "   " in TotalTime
            end else
                TotalTime[1, 4] = ""
            end
            If HasSeconds then
                Swap "00s" with "   " in TotalTime
            end else
                TotalTime[1, 4] = ""
            end
            Text := Fmt("Total", "L( )#":(MaxNameLen + 1)):": ":TotalTime
            
        end else
            Text[-1, 1] = ""
        end
        
    end
    
    // Show it
    If Len(Text) then
        MsgStruct = ""
        MsgStruct<1>  = Text
        MsgStruct<12> = "SRP Stopwatch Results"
        MsgStruct<18> = "Consolas":@SVM:"-13"
        Msg(@Window, MsgStruct)
        Ans = 1
    end else
        Ans = 0
    end

return


//-------------------------------------------------------------------------------------------------
// Starts a benchmark.
// @@DEFINE_SERVICE(Start, Name)
//-------------------------------------------------------------------------------------------------
Start:

    // Add the benchmark name if it doesn't exist
    Locate Name in Names@ using @FM setting Pos else
        Names@<-1> = Name
        ElapsedTime@ = 0
        Count@ = 0
    end
    
    StartTime@ = GetTickCount()
    
return


//-------------------------------------------------------------------------------------------------
// Stops a benchmark.
// @@DEFINE_SERVICE(Stop, Name)
//-------------------------------------------------------------------------------------------------
Stop:

    CurrElapsedTime = GetTickCount() - StartTime@
    ElapsedTime@ += CurrElapsedTime
    Total@ += CurrElapsedTime
    Count@ += 1
    
return


//-------------------------------------------------------------------------------------------------
// Let's me add test data, so I'm not going to advertise this service. Param2 is the elapsed time,
// and Param3 is the count for the given name.
//-------------------------------------------------------------------------------------------------
Test:

    // Add the benchmark name if it doesn't exist
    Locate Name in Names@ using @FM setting Pos else
        Names@<-1> = Name
        ElapsedTime@ = Param2
        Count@ = Param3
        Total@ += Param2
    end
    
return


///////////////////////////////////////////////////////////////////////////////////////////////////
// INTERNAL GOSUBS
///////////////////////////////////////////////////////////////////////////////////////////////////

FormatWorkingTime:
    
    // Hours
    NumHours = Int(WorkingTime / 3600000)
    WorkingTime = Mod(WorkingTime, 3600000)
    
    // Minutes
    NumMinutes = Int(WorkingTime / 60000)
    WorkingTime = Mod(WorkingTime, 60000)
    
    // Seconds and Millseconds
    NumSeconds = Int(WorkingTime / 1000)
    NumMilliseconds = Mod(WorkingTime, 1000)
    
    // Format
    Ans = Fmt(NumHours, "R(0)#2"):"h ":Fmt(NumMinutes, "R(0)#2"):"m ":Fmt(NumSeconds, "R(0)#2"):"s ":Fmt(NumMilliseconds, "R(0)#3"):"ms"
        
return

