You've already forked lazarus-ccr
Adds epiktimer
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@453 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
802
components/epiktimer/epiktimer.pas
Normal file
802
components/epiktimer/epiktimer.pas
Normal file
@@ -0,0 +1,802 @@
|
||||
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}
|
||||
baseunix, unix, unixutil,
|
||||
{$ENDIF}
|
||||
{$IFDEF FPC}
|
||||
LResources,
|
||||
{$ENDIF}
|
||||
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, 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;
|
||||
|
||||
procedure Register;
|
||||
|
||||
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;
|
||||
|
||||
(* * * * * * * * * * * Register Component * * * * * * * * * * * *)
|
||||
|
||||
procedure Register;
|
||||
begin
|
||||
RegisterComponents('System', [TEpikTimer]);
|
||||
end;
|
||||
|
||||
Initialization
|
||||
|
||||
{$IFDEF FPC}
|
||||
{$I epiktimer.lrs}
|
||||
{$ENDIF}
|
||||
|
||||
end.
|
||||
|
Reference in New Issue
Block a user