unit EpikTimer;

{ Name: EpikTimer
  Description: Precision timer/stopwatch component for Lazarus/FPC
  Author: Tom Lisjac <netdxr@gmail.com>
  Started on: June 24, 2003
  Features:
    Dual selectable timebases: Default:System (uSec timeofday or "now" in Win32)
                               Optional: Pentium Time Stamp Counter.
    Default timebase should work on most Unix systems of any architecture.
    Timebase correlation locks time stamp counter accuracy to system clock.
    Timers can be started, stopped, paused and resumed.
    Unlimited number of timers can be implemented with one component.
    Low resources required: 25 bytes per timer; No CPU overhead.
    Internal call overhead compensation.
    System sleep function
    Designed to support multiple operating systems and Architectures
    Designed to support other hardware tick sources
            
  Credits: Thanks to Martin Waldenburg for a lot of great ideas for using
           the Pentium's RDTSC instruction in wmFastTime and QwmFastTime.
}

{ Copyright (C) 2003-2006 by Tom Lisjac <netdxr@gmail.com>,
   Felipe Monteiro de Carvalho and Marcel Minderhoud

  This library is licensed on the same Modifyed LGPL as Free Pascal RTL and LCL are

  Please contact the author if you'd like to use this component but the Modifyed LGPL
  doesn't work with your project licensing.

  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.
  
  Contributor(s):
  
  * Felipe Monteiro de Carvalho (felipemonteiro.carvalho@gmail.com)
  
  * Marcel Minderhoud
  
}
{
 Known Issues

   - Tested on Linux but no other Lazarus/FPC supported Unix platforms
   - If system doesn't have microsecond system clock resolution, the component
     falls back to a single gated measurement of the hardware tick frequency via
     nanosleep. This usually results in poor absolute accuracy due large amounts
     of jitter in nanosleep... but for typical short term measurements, this
     shouldn't be a problem.

}

{$IFDEF FPC}
  {$MODE DELPHI}{$H+}
{$ENDIF}

{$IFNDEF FPC}
  {$DEFINE Windows}
{$ENDIF}

{$IFDEF Win32}
  {$DEFINE Windows}
{$ENDIF}

interface

uses
{$IFDEF Windows}
  Windows, MMSystem,
{$ELSE}
  unix, unixutil,
{$ENDIF}
  Classes, SysUtils, dateutils;

Const
  DefaultSystemTicksPerSecond = 1000000; //Divisor for microsecond resolution
  { HW Tick frequency falls back to gated measurement if the initial system
    clock measurement is outside this range plus or minus.}
  SystemTicksNormalRangeLimit = 100000;

type

  TickType = Int64; // Global declaration for all tick processing routines
  
  FormatPrecision = 1..12; // Number of decimal places in elapsed text format
  
  // Component powers up in System mode to provide some cross-platform safety.
  TickSources = (SystemTimebase, HardwareTimebase); // add others if desired

  (* * * * * * * * * * * Timebase declarations  * * * * * * * * * * *)
  
  { There are two timebases currently implemented in this component but others
    can be added by declaring them as "TickSources", adding a TimebaseData
    variable to the Private area of TEpikTimer and providing a "Ticks" routine
    that returns the current counter value.
    
    Timebases are "calibrated" during initialization by taking samples of the
    execution times of the SystemSleep and Ticks functions measured with in the
    tick period of the selected timebase. At runtime, these values are retrieved
    and used to remove the call overhead to the best degree possible.
    
    System latency is always present and contributes "jitter" to the edges of
    the sample measurements. This is especially true if a microsecond system
    clock isn't detected on the host system and a fallback gated measurement
    (based on nanosleep in Linux and sleep in Win32) is used to determine the
    timebase frequency. This is sufficient for short term measurements where
    high resolution comparisons are desired... but over a long measurement
    period, the hardware and system wall clock will diverge significantly.
    
    If a microsecond system clock is found, timebase correlation is used to
    synchronize the hardware counter and system clock. This is described below.
  }

  TickCallFunc = function: Ticktype; // Ticks interface function
  
  // Contains timebase overhead compensation factors in ticks for each timebase
  TimebaseCalibrationParameters = record
    FreqCalibrated: Boolean; // Indicates that the tickfrequency has been calibrated
    OverheadCalibrated: Boolean; // Indicates that all call overheads have been calibrated
    TicksIterations: Integer; // number of iterations to use when measuring ticks overhead
    SleepIterations: Integer; // number of iterations to use when measuring SystemSleep overhead
    FreqIterations: Integer;  // number of iterations to use when measuring ticks frequency
    FrequencyGateTimeMS: Integer;  // gate time to use when measuring ticks frequency
   end;

  // This record defines the Timebase context
  TimebaseData = record
    CalibrationParms: TimebaseCalibrationParameters; // Calibration data for this timebase
    TicksFrequency: TickType; // Tick frequency of this timebase
    TicksOverhead: Ticktype;  // Ticks call overhead in TicksFrequency for this timebase
    SleepOverhead: Ticktype;   // SystemSleep all overhead in TicksFrequency for this timebase
    Ticks: TickCallFunc; // all methods get their ticks from this function when selected
  end;
  
  TimeBaseSelector = ^TimebaseData;
  
  (*  * * * * * * * * * * Timebase Correlation  * * * * * * * * * * *)

  { The TimeBaseCorrelation record stores snapshot samples of both the system
    ticks (the source of known accuracy) and the hardware tick source (the
    source of high measurement resolution). An initial sample is taken at power
    up. The CorrelationMode property sets where and when updates are acquired.

    When an update snapshot is acquired, the differences between it and the
    startup value can be used to calculate the hardware clock frequency with
    high precision from the accuracy of the accumulated system clocks. The
    longer time that elapses between startup and a call to "CorrelateTimebases",
    the better the accuracy will be. On a 1.6 Ghz P4, it only takes a few
    seconds to achieve measurement certainty down to a few Hertz.

    Of course this system is only as good as your system clock accuracy, so
    it's a good idea to periodically sync it with NTP or against another source
    of known accuracy if you want to maximize the long term of the timers. }

  TimebaseCorrelationData = record
    SystemTicks: TickType;
    HWTicks: TickType;
  end;

  // If the Correlation property is set to automatic, an update sample is taken
  // anytime the user calls Start or Elapsed. If in manual, the correlation
  // update is only done when "CorrelateTimebases" is called. Doing updates
  // with every call adds a small amount of overhead... and after the first few
  // minutes of operation, there won't be very much correcting to do!

  CorrelationModes=(Manual, OnTimebaseSelect, OnGetElapsed);
  
  (* * * * * * * * * * * Timer Data record structure  * * * * * * * * * * *)
  
  // This is the timer data context. There is an internal declaration of this
  // record and overloaded methods if you only want to use the component for a
  // single timer... or you can declare multiple TimerData records in your
  // program and create as many instances as you want with only a single
  // component on the form. See the "Stopwatch" methods in the TEpikTimer class.
  
  // Each timers points to the timebase that started it... so you can mix system
  // and hardware timers in the same application.

  TimerData = record
    Running:Boolean; // Timer is currently running
    TimebaseUsed:TimeBaseSelector; // keeps timer aligned with the source that started it.
    StartTime:TickType; // Ticks sample when timer was started
    TotalTicks:TickType; // Total ticks... for snapshotting and pausing
  end;
  
  TEpikTimer= class(TComponent)
    private
      BuiltInTimer:TimerData; // Used to provide a single built-in timer;
      FHWTickSupportAvailable:Boolean; // True if hardware tick support is available
      FHWCapabilityDataAvailable:Boolean; // True if hardware tick support is available
      FHWTicks:TimeBaseData;     // The hardware timebase
      FSystemTicks:TimeBaseData; // The system timebase
      FSelectedTimebase:TimeBaseSelector; // Pointer to selected database
      
      FTimeBaseSource: TickSources; // use hardware or system timebase
      FWantDays: Boolean; // true if days are to be displayed in string returns
      FWantMS: Boolean; // True to display milliseconds in string formatted calls
      FSPrecision: FormatPrecision; // number of digits to display in string calls
      FMicrosecondSystemClockAvailable:Boolean; // true if system has microsecond clock
      
      StartupCorrelationSample:TimebaseCorrelationData; // Starting ticks correlation snapshot
      UpdatedCorrelationSample:TimebaseCorrelationData; // Snapshot of last correlation sample
      FCorrelationMode: CorrelationModes; // mode to control when correlation updates are performed
    protected
      function GetSelectedTimebase: TimebaseData;
      procedure SetSelectedTimebase(const AValue: TimebaseData);
      procedure SetTimebaseSource(const AValue: TickSources); //setter for TB
      Procedure GetCorrelationSample(Var CorrelationData:TimeBaseCorrelationData);
    public
      {                       Stopwatch emulation routines
        These routines behave exactly like a conventional stopwatch with start,
        stop, elapsed (lap) and clear methods. The timers can be started,
        stopped and resumed. The Elapsed routines provide a "lap" time analog.

        The methods are overloaded to make it easy to simply use the component's
        BuiltInTimer as a single timer... or to declare your own TimerData records
        in order to implement unlimited numbers of timers using a single component
        on the form. The timers are very resource efficient because they consume
        no CPU overhead and only require about 25 bytes of memory.
      }

      // Stops and resets the timer
      procedure Clear; overload;// Call this routine to use the built-in timer record
      procedure Clear(Var T:TimerData); overload; // pass your TimerData record to this one

      //Start or resume a stopped timer
      procedure Start; overload;
      procedure Start(Var T:TimerData); overload;

      //Stop or pause a timer
      procedure Stop; overload;
      procedure Stop(Var T:TimerData); overload;

      //Return elapsed time in seconds as an extended type
      function Elapsed:Extended; overload;
      function Elapsed(var T: TimerData):Extended; overload;

      //Return a string in Day:Hour:Minute:Second format. Milliseconds can be
      //optionally appended via the WantMilliseconds property
      function ElapsedDHMS:String; overload;
      function ElapsedDHMS(var T: TimerData):String; overload;

      //Return a string in the format of seconds.milliseconds
      function ElapsedStr:String; overload;
      function ElapsedStr(var T:TimerData):String; overload;

      function WallClockTime:String; // Return time of day string from system time

      //Overhead compensated system sleep to provide a best possible precision delay
      function SystemSleep(Milliseconds: Integer):integer; Virtual;

      //Diagnostic taps for development and fine grained timebase adjustment
      property HWTimebase: TimeBaseData read FHWTicks write FHWTicks; // The hardware timebase
      property SysTimebase: TimebaseData read FSystemTicks write FSystemTicks;
      function GetHardwareTicks:TickType; // return raw tick value from hardware source
      function GetSystemTicks:Ticktype;   // Return system tick value(in microseconds of Epoch time)
      function GetTimebaseCorrelation:TickType;
      function CalibrateCallOverheads(Var TimeBase:TimebaseData) : Integer; Virtual;
      function CalibrateTickFrequency(Var TimeBase:TimebaseData): Integer; Virtual;

      property MicrosecondSystemClockAvailable:Boolean read FMicrosecondSystemClockAvailable;
      property SelectedTimebase:TimebaseSelector read FSelectedTimebase write FSelectedTimebase;
      property HWTickSupportAvailable:Boolean read FHWTickSupportAvailable;
      property HWCapabilityDataAvailable:Boolean read FHWCapabilityDataAvailable;
      procedure CorrelateTimebases; // Manually call to do timebase correlation snapshot and update

      constructor Create(AOwner:TComponent); Override;
      destructor Destroy; Override;
    Published
      property StringPrecision: FormatPrecision read FSPrecision write FSPrecision;
      property WantMilliseconds: Boolean read FWantMS write FWantMS;
      property WantDays: Boolean read FWantDays write FWantDays;
      property TimebaseSource: TickSources read FTimeBaseSource write SetTimebaseSource;
      property CorrelationMode:CorrelationModes read FCorrelationMode write FCorrelationMode;
  end;


implementation

(* * * * * * * * * * * * * * Timebase Section  * * * * * * * * * * * * *)
{
  There are two tick sources defined in this section. The first uses a hardware
  source which, in this case, is the Pentium's internal 64 Time Stamp Counter.
  The second source (the default) uses the given environment's most precision
  "timeofday" system call so it can work across OS platforms and architectures.
  
  The hardware timer's accuracy depends on the frequency of the timebase tick
  source that drives it... in other words, how many of the timebase's ticks
  there are in a second. This frequency is measured by capturing a sample of the
  timebase ticks for a known period against a source of known accuracy. There
  are two ways to do this.
  
  The first is to capture a large sample of ticks from both the unknown and
  known timing sources. Then the frequency of the unknown tick stream can be
  calculated by: UnknownSampleTicks / (KnownSampleTicks / KnownTickFrequency).
  Over a short period of time, this can provide a precise synchronization
  mechanism that effectively locks the measurements taken with the high
  resolution source to the known accuracy of the system clock.

  The first method depends on the existance of an accurate system time source of
  microsecond resolution. If the host system doesn't provide this, the second
  fallback method is to gate the unknown tick stream by a known time. This isn't
  as good because it usually involves calling a system "delay" routine that
  usually has a lot of overhead "jitter" and non-deterministic behavior. This
  approach is usable, however, for short term, high resolution comparisons where
  absolute accuracy isn't important.
}

(* * * * * * * * Start of i386 Hardware specific code  * * * * * * *)

{$IFDEF CPUI386}
{ Some references for this section can be found at:
      http://www.sandpile.org/ia32/cpuid.htm
      http://www.sandpile.org/ia32/opc_2.htm
      http://www.sandpile.org/ia32/msr.htm
}

// Pentium specific... push and pop the flags and check for CPUID availability
function HasHardwareCapabilityData: Boolean;
begin
  asm
   PUSHFD
   POP    EAX
   MOV    EDX,EAX
   XOR    EAX,$200000
   PUSH   EAX
   POPFD
   PUSHFD
   POP    EAX
   XOR    EAX,EDX
   JZ     @EXIT
   MOV    AL,TRUE
   @EXIT:
  end;
end;

function HasHardwareTickCounter: Boolean;
  var FeatureFlags: Longword;
  begin
    FeatureFlags:=0;
    asm
      PUSH   EBX
      XOR    EAX,EAX
      DW     $A20F
      POP    EBX
      CMP    EAX,1
      JL     @EXIT
      XOR    EAX,EAX
      MOV    EAX,1
      PUSH   EBX
      DW     $A20F
      MOV    FEATUREFLAGS,EDX
      POP    EBX
      @EXIT:
    end;
    Result := (FeatureFlags and $10) <> 0;
  end;

// Execute the Pentium's RDTSC instruction to access the counter value.
function HardwareTicks: TickType; assembler; asm DW 0310FH end;

(* * * * * * * * End of i386 Hardware specific code  * * * * * * *)


// These are here for architectures that don't have a precision hardware
// timing source. They'll return zeros for overhead values. The timers
// will work but there won't be any error compensation for long
// term accuracy.
{$ELSE} // add other architectures and hardware specific tick sources here
function HasHardwareCapabilityData: Boolean; begin Result:=False end;
function HasHardwareTickCounter: Boolean; begin Result:=false end;
function HardwareTicks:TickType; begin result:=0 end;
{$ENDIF}

function NullHardwareTicks:TickType; begin Result:=0 end;

// Return microsecond normalized time source for a given platform.
// This should be sync'able to an external time standard (via NTP, for example).
function SystemTicks: TickType;
{$IFDEF Windows}
begin
  QueryPerformanceCounter(Result);
  //Result := Int64(TimeStampToMSecs(DateTimeToTimeStamp(Now)) * 1000) // an alternative Win32 timebase
{$ELSE}
var t : timeval;
begin
  fpgettimeofday(@t,nil);
   // Build a 64 bit microsecond tick from the seconds and microsecond longints
  Result := (TickType(t.tv_sec) * 1000000) + t.tv_usec;
{$ENDIF}
end;

function TEpikTimer.SystemSleep(Milliseconds: Integer):Integer;
{$IFDEF Windows}

begin
  Sleep(Milliseconds);
  Result := 0;
end;

{$ELSE}

  {$IFDEF CPUX86_64}

begin
  Sleep(Milliseconds);
  Result := 0;
end;

  {$ELSE}

var
  timerequested, timeremaining: timespec;
begin
  // This is not a very accurate or stable gating source... but it's the
  // only one that's available for making short term measurements.
  timerequested.tv_sec:=Milliseconds div 1000;
  timerequested.tv_nsec:=(Milliseconds mod 1000) * 1000000;
  Result := fpnanosleep(@timerequested, @timeremaining) // returns 0 if ok
end;

  {$ENDIF}

{$ENDIF}

function TEpikTimer.GetHardwareTicks: TickType;
begin
  Result:=FHWTicks.Ticks();
end;

function TEpikTimer.GetSystemTicks: Ticktype;
begin
  Result:=FSystemTicks.Ticks();
end;

procedure TEpikTimer.SetTimebaseSource(const AValue: TickSources);

  procedure UseSystemTimer;
  begin
    FTimeBaseSource := SystemTimebase;
    SelectedTimebase := @FSystemTicks;
  end;

begin
  case AValue of
    HardwareTimebase:
      try
        if HWTickSupportAvailable then
          begin
            SelectedTimebase:=@FHWTicks;
            FTimeBaseSource:=HardwareTimebase;
            If CorrelationMode<>Manual then CorrelateTimebases
          end
      except // If HW init fails, fall back to system tick source
        UseSystemTimer
      end;
    SystemTimeBase: UseSystemTimer
  end
end;

function TEpikTimer.GetSelectedTimebase: TimebaseData;
begin
  Result := FSelectedTimebase^;
end;

procedure TEpikTimer.SetSelectedTimebase(const AValue: TimebaseData);
begin
  FSelectedTimebase^ := AValue;
end;

(* * * * * * * * * * Time measurement core routines * * * * * * * * * *)

procedure TEpikTimer.Clear(var T: TimerData);
begin
  with T do
    begin
      Running:=False; StartTime:=0; TotalTicks:=0; TimeBaseUsed:=FSelectedTimebase
    end;
end;

procedure TEpikTimer.Start(var T: TimerData);
begin
  if not T.running then
    With FSelectedTimebase^ do
    begin
      T.StartTime:=Ticks()-TicksOverhead;
      T.TimebaseUsed:=FSelectedTimebase;
      T.Running:=True
    end
end;

procedure TEpikTimer.Stop(var T: TimerData);
  Var CurTicks:TickType;
Begin
  if T.Running then
    With T.TimebaseUsed^ do
    Begin
      CurTicks:=Ticks()-TicksOverhead; // Back out the call overhead
      T.TotalTicks:=(CurTicks - T.Starttime)+T.TotalTicks; T.Running:=false
    end
end;

function TEpikTimer.Elapsed(var T: TimerData): Extended;
var
  CurTicks: TickType;
begin
  With T.TimebaseUsed^ do
    if T.Running then
      Begin

        CurTicks:=Ticks()-TicksOverhead; // Back out the call overhead
        If CorrelationMode>OnTimebaseSelect then CorrelateTimebases;

        Result := ((CurTicks - T.Starttime)+T.TotalTicks) / TicksFrequency
      End
    Else Result := T.TotalTicks / TicksFrequency;
end;

(* * * * * * * * * * Output formatting routines  * * * * * * * * * *)

function TEpikTimer.ElapsedDHMS(var T: TimerData): String;
var
  Tmp, MS: extended;
  D, H, M, S: Integer;
  P, SM: string;
begin
  Tmp := Elapsed(T);
  P := inttostr(FSPrecision);
  MS := frac(Tmp); SM:=format('%0.'+P+'f',[MS]); delete(SM,1,1);
  D := trunc(Tmp / 84600); Tmp:=Trunc(tmp) mod 84600;
  H := trunc(Tmp / 3600); Tmp:=Trunc(Tmp) mod 3600;
  M := Trunc(Tmp / 60); S:=(trunc(Tmp) mod 60);
  If FWantDays then
    Result := format('%2.3d:%2.2d:%2.2d:%2.2d',[D,H,M,S])
  else
    Result := format('%2.2d:%2.2d:%2.2d',[H,M,S]);
  If FWantMS then Result:=Result+SM;
end;

function TEpikTimer.ElapsedStr(var T: TimerData): string;
begin
  Result := format('%.'+inttostr(FSPrecision)+'f',[Elapsed(T)]);
end;

function TEpikTimer.WallClockTime: string;
var
  Y, D, M, hour, min, sec, ms, us: Word;
{$IFNDEF Windows}
  t: timeval;
{$ENDIF}
begin
{$IFDEF Windows}
  DecodeDatetime(Now, Y, D, M, Hour, min, Sec, ms);
  us:=0;
{$ELSE}
  // "Now" doesn't report milliseconds on Linux... appears to be broken.
  // I opted for this approach which also provides microsecond precision.
  fpgettimeofday(@t,nil);
  EpochToLocal(t.tv_sec, Y, M, D, hour, min, sec);
  ms:=t.tv_usec div 1000; us:=t.tv_usec mod 1000;
{$ENDIF}
  Result:='';
  If FWantDays then
    Result := Format('%4.4d/%2.2d/%2.2d-',[Y,M,D]);
  Result := Result + Format('%2.2d:%2.2d:%2.2d',[hour,min,sec]);
  If FWantMS then
    Result := Result + Format('.%3.3d%3.3d',[ms,us])
end;

(* * * Overloaded methods to use the component's internal timer data * * *)

procedure TEpikTimer.Clear; begin Clear(BuiltInTimer) end;
procedure TEpikTimer.Start; begin Start(BuiltInTimer) end;
procedure TEpikTimer.Stop;  Begin Stop(BuiltInTimer) End;
function  TEpikTimer.Elapsed: Extended; begin Result:=Elapsed(BuiltInTimer) end;
function  TEpikTimer.ElapsedStr: String; Begin Result:=ElapsedStr(BuiltInTimer) end;
function  TEpikTimer.ElapsedDHMS: String; begin Result:=ElapsedDHMS(BuiltInTimer) end;

(* * * * * * * * * * Timebase calibration section  * * * * * * * * * *)

// Set up compensation for call overhead to the Ticks and SystemSleep functions.
// The Timebase record contains Calibration parameters to be used for each
// timebase source. These have to be unique as the output of this measurement
// is measured in "ticks"... which are different periods for each timebase.

function TEpikTimer.CalibrateCallOverheads(Var Timebase:TimebaseData):Integer;
var i:Integer; St,Fin,Total:TickType;
begin
  with Timebase, Timebase.CalibrationParms do
  begin
    Total:=0; Result:=1;
    for I:=1 to TicksIterations do // First get the base tick getting overhead
      begin
        St:=Ticks(); Fin:=Ticks();
        Total:=Total+(Fin-St); // dump the first sample
      end;
    TicksOverhead:=Total div TicksIterations;
    Total:=0;
    For I:=1 to SleepIterations do
    Begin
      St:=Ticks();
      if SystemSleep(0)<>0 then exit;
      Fin:=Ticks();
      Total:=Total+((Fin-St)-TicksOverhead);
    End;
    SleepOverhead:=Total div SleepIterations;
    OverheadCalibrated:=True; Result:=0
  End
end;

// CalibrateTickFrequency is a fallback in case a microsecond resolution system
// clock isn't found. It's still important because the long term accuracy of the
// timers will depend on the determination of the tick frequency... in other words,
// the number of ticks it takes to make a second. If this measurement isn't
// accurate, the counters will proportionately drift over time.
//
// The technique used here is to gate a sample of the tick stream with a known
// time reference which, in this case, is nanosleep. There is a *lot* of jitter
// in a nanosleep call so an attempt is made to compensate for some of it here.

function TEpikTimer.CalibrateTickFrequency(Var Timebase:TimebaseData):Integer;
var
  i: Integer;
  Total, SS, SE: TickType;
  ElapsedTicks, SampleTime: Extended;
begin
  With Timebase, Timebase.CalibrationParms do
  Begin
    Result:=1; //maintain unitialized default in case something goes wrong.
    Total:=0;
    For i:=1 to FreqIterations do
      begin
        SS:=Ticks();
        SystemSleep(FrequencyGateTimeMS);
        SE:=Ticks();
        Total:=Total+((SE-SS)-(SleepOverhead+TicksOverhead))
      End;
    //doing the floating point conversion allows SampleTime parms of < 1 second
    ElapsedTicks:=Total div FreqIterations;
    SampleTime:=FrequencyGateTimeMS;

    TicksFrequency:=Trunc( ElapsedTicks / (SampleTime / 1000));

    FreqCalibrated:=True;
  end;
end;

// Grab a snapshot of the system and hardware tick sources... as quickly as
// possible and with overhead compensation. These samples will be used to
// correct the accuracy of the hardware tick frequency source when precision
// long term measurements are desired.
procedure TEpikTimer.GetCorrelationSample(var CorrelationData: TimeBaseCorrelationData);
Var
  TicksHW, TicksSys: TickType;
  THW, TSYS: TickCallFunc;
begin
  THW:=FHWTicks.Ticks; TSYS:=FSystemTicks.Ticks;
  TicksHW:=THW(); TicksSys:=TSYS();
  With CorrelationData do
    Begin
      SystemTicks:= TicksSys-FSystemTicks.TicksOverhead;
      HWTicks:=TicksHW-FHWTicks.TicksOverhead;
    End
end;

(* * * * * * * * * * Timebase correlation section  * * * * * * * * * *)

{ Get another snapshot of the system and hardware tick sources and compute a
  corrected value for the hardware frequency. In a short amount of time, the
  microsecond system clock accumulates enough ticks to perform a *very*
  accurate frequency measurement of the typically picosecond time stamp counter. }

Function TEpikTimer.GetTimebaseCorrelation:TickType;
Var
  HWDiff, SysDiff, Corrected: Extended;
begin
  If HWtickSupportAvailable then
    Begin
      GetCorrelationSample(UpdatedCorrelationSample);
      HWDiff:=UpdatedCorrelationSample.HWTicks-StartupCorrelationSample.HWTicks;
      SysDiff:=UpdatedCorrelationSample.SystemTicks-StartupCorrelationSample.SystemTicks;
      Corrected:=HWDiff / (SysDiff / DefaultSystemTicksPerSecond);
      Result:=trunc(Corrected)
    End
  else result:=0
end;

{ If an accurate reference is available, update the TicksFrequency of the
  hardware timebase. }
procedure TEpikTimer.CorrelateTimebases;
begin
  If MicrosecondSystemClockAvailable and HWTickSupportAvailable then
    FHWTicks.TicksFrequency:=GetTimebaseCorrelation
end;

(* * * * * * * * Initialization: Constructor and Destructor  * * * * * * *)

constructor TEpikTimer.Create(AOwner: TComponent);

  Procedure InitTimebases;
  Begin

    { Tick frequency rates are different for the system and HW timebases so we
      need to store calibration data in the period format of each one. }
    FSystemTicks.Ticks:=@SystemTicks; // Point to Ticks routine
    With FSystemTicks.CalibrationParms do
      Begin
        FreqCalibrated:=False;
        OverheadCalibrated:=False;
        TicksIterations:=5;
        SleepIterations:=10;
        FrequencyGateTimeMS:=100;
        FreqIterations:=1;
      End;

    // Initialize the HW tick source data
    FHWCapabilityDataAvailable:=False;
    FHWTickSupportAvailable:=False;
    FHWTicks.Ticks:=@NullHardwareTicks; // returns a zero if no HW support
    FHWTicks.TicksFrequency:=1;
    With FHWTicks.CalibrationParms do
      Begin
        FreqCalibrated:=False;
        OverheadCalibrated:=False;
        TicksIterations:=10;
        SleepIterations:=20;
        FrequencyGateTimeMS:=150;
        FreqIterations:=1;
      End;

    if HasHardwareCapabilityData then
      Begin
        FHWCapabilityDataAvailable:=True;
        If HasHardwareTickCounter then
          Begin
            FHWTicks.Ticks:=@HardwareTicks;
            FHWTickSupportAvailable:=CalibrateCallOverheads(FHWTicks)=0
          End
      end;
         
    CalibrateCallOverheads(FSystemTicks);
    CalibrateTickFrequency(FSystemTicks);

    // Overheads are set... get starting timestamps for long term calibration runs
    GetCorrelationSample(StartupCorrelationSample);
    With FSystemTicks do
      If (TicksFrequency>(DefaultSystemTicksPerSecond-SystemTicksNormalRangeLimit)) and
        (TicksFrequency<(DefaultSystemTicksPerSecond+SystemTicksNormalRangeLimit)) then
        Begin // We've got a good microsecond system clock
          FSystemTicks.TicksFrequency:=DefaultSystemTicksPerSecond; // assume it's pure
          FMicrosecondSystemClockAvailable:=True;
          If FHWTickSupportAvailable then
            Begin
              SystemSleep(FHWTicks.CalibrationParms.FrequencyGateTimeMS); // rough gate
              CorrelateTimebases
            End
        end
      else
        Begin
          FMicrosecondSystemClockAvailable:=False;
          If FHWTickSupportAvailable then
            CalibrateTickFrequency(FHWTicks) // sloppy but usable fallback calibration
        End;
 End;

begin
  inherited Create(AOwner);
  StringPrecision:=6; FWantMS:=True; FWantDays:=True;
  InitTimebases;
  CorrelationMode:=OnTimebaseSelect;
  // Default is the safe, cross-platform but less precise system timebase
  TimebaseSource:=SystemTimebase;
  Clear(BuiltInTimer)
end;

destructor TEpikTimer.Destroy;
begin
  inherited Destroy;
  // here in case we need to clean something up in a later version
end;

end.

