unit DbgTimer;

(*                                                                             *
  Name:      DbgTimer                                                          *
                                                                               *
  Objective: Simplified use of precision timer/stopwatch especially for        *
             program development/testing/debugging with Lazarus/FPC.           *
                                                                               *
  Author:    Ruediger Walter <rue.walter@web.de>                               *
                                                                               *
  Version:   0.9.0-4 / 2014-10-01                                              *
                                                                               *
  Features:  DbgTimer provides a specific and simplified use of EpikTimer,     *
             especially designed for developing and analyzing time-critical    *
             sections and loops in Free Pascal programs/units using the        *
             Lazarus IDE.                                                      *
             For this purpose EpikTimer ist the appropriate tool. Please       *
             keep in mind, that Epiktimer is a virtual stop-watch, while       *
             the LCL-own TTimer (or TFPTimer) are virtual alarm-clocks         *
             (respectively virtual metronomes). Hence EpicTimer doesn't        *
             consume own computing ressources during the measured loop and     *
             has not to deal with tick-event notification overhead.            *
             DbgTimer avoids the incorporation of the EpikTimer LCL component  *
             into the project and can be removed easily afterwards. There's    *
             no need to install the EpikTimer package into the Lazarus IDE,    *
             it only requires the EpikTimer unit to be placed into the         *
             FPC/Lazarus searchpath. For further information on EpikTimer      *
             please take a look at http://wiki.freepascal.org/EpikTimer        *
                                                                               *
  Usage:     1) Put "DbgTimer" into the uses-part of the FPC/Lazarus-unit      *
                you want to explore.                                           *
                                                                               *
             2) There are only two procedure/function calls: "DebugTimerStart" *
                and "DebugTimerStop". Just place them around the section or    *
                loop you want to investigate, like this:                       *
                >   DebugTimerStart;                                           *
                >   for i := 0 to FFiles.Count - 1 do ParseFile(i);            *
                >   DebugTimerStop;                                            *
                >   NextInstruction;           // Place a Breakpoint here      *
                                                                               *
                "DebugTimerStart" will reset and start the virtual stopwatch,  *
                while "DebugTimerStop" (surprisingly) stops it and calculates  *
                the results.                                                   *
                                                                               *
                If you want to know the per cycle time consumption, you can    *
                call "DebugTimerStop" with an integer parameter instead:       *
                >   DebugTimerStop({Number of Cycles, in this case:} i+1);     *
                                                                               *
                Usually you might use "DebugTimerStop" like a procedure        *
                without reading the function result, but if you need the       *
                numerical (Extended type) value of the total time used (in     *
                seconds) for some further calculations, you can use its        *
                function result:                                               *
                >   MyLameDucksDawdleTime := DebugTimerStop;                   *
                                                                               *
                If you want to pause and continue without reset, you can       *
                call DebugTimerStart with the "NoReset" parameter instead:     *
                >   DebugTimerStop;                                            *
                >   DoSomethingIrrelevant;                                     *
                >   DebugTimerStart(NoReset);                                  *
                But don't forget to do a (resetting) DebugTimerStart at first. *
                To pause can be useful if you want to extract a particular     *
                sequence, e.g. a nested loop. But keep in mind: The smaller    *
                your time fragment, the less accurate your measure and the     *
                larger the burden of additional jumps and system calls will    *
                be. Thus adding lots of nanosecond intervals will not make     *
                much sense (see also "Limits").                                *
                                                                               *
             3) Go to the DbgTimer unit (e.g. by placing the cursor on         *
                "DebugTimerStart" and doing ALT+Up), click on the              *
                "DebugTimeTotal" and "DebugTimePerCycle" variables and         *
                add them to the Lazarus IDE watch list (Strg+F5).              *
                                                                               *
             4) Now put a breakpoint to the next line BELOW "DebugTimerStop".  *
                When running your program, the debugger will pause the         *
                execution after time measuring and you can read the values     *
                in the watch list, like this:                                  *
                >   DebugTimeTotal       '0.217390 s'                          *
                >   DebugTimePerCycle    '1.302 ms   (167 Cycles)'             *
                                                                               *
             5) To remove DbgTimer when the work is done just kick "DbgTimer"  *
                out of the "uses"-line, the compiler will help you to find     *
                all residues of your examination.                              *
                                                                               *
  Limits:    EpikTimer is a really nice virtual stopwatch and DebugTimer might *
             be a useful tool to optimize your source code (I hope at least).  *
             But you should have an idea about the limits of time measuring    *
             on the computer. It's very useless to stare at the amazing nano-  *
             seconds measuring abilities of the latest, best, most expensive,  *
             most accurate processor family while working on a multitasking,   *
             non-realtime operating system. If you press the stop-button e.g.  *
             via a key stroke, your virtual stopwatch won't stop immediately,  *
             but at the time your OS decides to execute the stop instruction.  *
             An average Linux computer typically needs 1-3 milliseconds to     *
             respond if another task is in charge at that moment. In return    *
             the execution of your program will be frequently interrupted by   *
             some task (or thread) switching. This is not due to a lack of     *
             accuracy of the CPU or of EpikTimer - those are simply the rules  *
             of your operating system. Thus measuring very short periods will  *
             decrease the likelihood of interrupts, but might bring the        *
             measure near the accuracy limits of the CPU, and it will          *
             increase the impact of scattered interruptions - the deviations   *
             will increase. And vice versa.                                    *
             That's why your results will always vary a little bit. Measuring  *
             time periods > 10 ms will produce reasonably reliable results     *
             (mostly), but you should never rely on a single measure. If you   *
             need very high accuracy when fine-tuning some code snippets,      *
             you'd better test them in loops > 10 seconds in order to level    *
             out random running conditions.                                    *
                                                                               *
  Credits:   Many thanks to Tom Lisjac, Felipe Monteiro de Carvalho, Marcel    *
             Minderhoud and Graeme Geldenhuys for developing, maintaining      *
             and providing EpikTimer. Actually this tiny unit here is nearly   *
             exclusively their merit. DbgTimer only provides a specific use    *
             of EpikTimer, so the real work was done by Tom, Felipe, Marcel    *
             and Graeme.                                                       *
                                                                               *
  Copyright (C) 2014 by Ruediger Walter <rue.walter@web.de>                    *
                                                                               *
  This library is licensed on the same Modifyed LGPL as Free Pascal RTL and    *
  Lazarus LCL. See the file COPYING.FPC, included in your FPC/Lazarus          *
  distribution, for details about the copyright.                               *
                                                                               *
  This program is distributed in the hope that it will be useful, but WITHOUT  *
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or        *
  FITNESS FOR A PARTICULAR PURPOSE.                                            *)



interface

uses
  Classes, SysUtils, Math, EpikTimer;

type
  TDebugTimerStartOption = (NoReset);

procedure DebugTimerStart; overload;
procedure DebugTimerStart(ResetOption : TDebugTimerStartOption); overload;

function  DebugTimerStop: Extended; overload;
function  DebugTimerStop(NumCycles: integer): Extended; overload;

var
  DebugTimeTotal,                           // Add those variables to the
  DebugTimePerCycle : String;               // Lazarus IDE watch list (Strg+F5)


implementation

var
  DT : TEpikTimer;

const
  Precision = 4;                               // Number of significant digits;


function SecondsToString(t : Extended): String;
var
  n, n2 : integer;
begin
  if t < IntPower(10, -9) then Result := 'Invalid period < 1 ns'
  else begin
    n := 0;
    while t < IntPower(10, n) do dec(n);            // Upper significant digit
    while t > IntPower(10, n+1) do inc(n);
    n2 := n;
    if n >= 0 then Result := 's'                   // Select Range
      else if n < -6 then begin t := t * 1000000000; Result := 'ns'; inc(n, 9); end
        else if n < -3 then begin t := t * 1000000; Result := 'µs'; inc(n, 6); end
          else begin t := t * 1000; Result := 'ms'; inc(n, 3); end;
    n := Precision - (n + 1);
    if (n < 0) or (n2 < -6) then n := 0;            // Too big or in ns-Range?
    Result := format('%.'+IntToStr(n)+'f',[t]) + ' ' + Result;
  end;
end;

procedure DebugTimerStart;
begin
  DT.Clear;
  DT.Start;
end;

procedure DebugTimerStart(ResetOption : TDebugTimerStartOption);
begin
  if ResetOption = NoReset then DT.Start;   // There is only "NoReset", but the
end;                                        // compiler will bleat if ignored

function DebugTimerStop(NumCycles: integer): Extended;
begin
  DT.Stop;
  Result := DT.Elapsed;
  DebugTimeTotal := SecondsToString(Result);
  if NumCycles <= 0 then
       DebugTimePerCycle := 'Number of cycles not specified'
  else DebugTimePerCycle := SecondsToString(Result / NumCycles) +
                            '   (' + IntToStr(NumCycles) + ' Cycles)';
end;

function DebugTimerStop: Extended;
begin
  Result := DebugTimerStop(0);
end;


initialization
  DT := TEpikTimer.Create(nil);

finalization
  DT.Free;

end.

