ElapsedMilliseconds/GetTickCount wrap

Just starting out? Need help? Post your questions and find answers here.
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

ElapsedMilliseconds/GetTickCount wrap

Post by PB »

Hi guys. One of my apps makes extensive use of ElapsedMilliseconds(),
which is a wrapper the GetTickCount_() API. But, as you might know,
it's a 16-bit value which becomes negative after 49.7 days.

At my workplace, our PCs are on 24/7. So now, these commands return
a negative value for me, making them useless. How can I truly know how
long the PC has been on, in milliseconds? The Win32.hlp file says:
Win32.hlp wrote:Windows NT: To obtain the time elapsed since the computer was started, look up the System Up Time counter in the performance data in the registry key HKEY_PERFORMANCE_DATA. The value returned is an 8 byte value.
But I have no idea where this is in the Registry (can't find it). Any ideas?
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post by Kaeru Gaman »

I think you do not need any 8byte or unsigned value to solve the problem,
you only need to take care of the overflow moment.

this is a snippet from my AnimProcs:

Code: Select all

ActTim = ElapsedMilliseconds()
If ActTim<0 And Anim(Nr)\msBf > 0
    Anim(Nr)\msBf = ActTim+Anim(Nr)\Dely
EndIf

If ActTim >= Anim(Nr)\msBf
    Anim(Nr)\msBf = ActTim+Anim(Nr)\Dely
    Animate = 1
EndIf
msBf is the Milliseconds-Buffer, Dely is the Delay to wait.
if the actual timer is less than zero and the buffered value greater than zero,
then the overflow moment just happened.
in this model I just restart the delay period by resetting the buffer to the actual negative timervalue.

you could also calculate the given difference, if you really interested in
keeping this single small period every 49 days as precise as the others.
oh... and have a nice day.
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Post by Rescator »

First of all the ticks/millisecs are a 32bit signed value.

Normally (mentioned in the SDK as well) you would do: (pseudocode)

old=ElapsedMilliseconds()
loop and other stuff here
ms=ElapsedMilliseconds()
timepassed=ms-old ;difference thus no wrapping, it IS limited to 47 something days worth of millisecs where it wraps to 0 again so you can't meassure beyond that.
end loop


But if you are only interested in uptime or similar info.
why not simply use Date() ?
Store the start date (32bit signed value in seconds since 1st Jan 1970 and should be ok until um 2038 something?).

you would have to do some simple math to turn those seconds into days, hours minutes and seconds but that is not too difficult.

Here's a procedure I use in GridStream player when displaying how long it's been connected to the stream, and also the uptime of the player.

Code: Select all

Procedure.s CalculateDays(startdate.i,enddate.i)
 Protected result$,seconds.i,minutes.i,hours.i,days.i
 seconds=enddate-startdate
 If seconds<>0
  minutes=seconds/60 : seconds-(minutes*60)
  hours=minutes/60 : minutes-(hours*60)
  days=hours/24 : hours-(days*24)
  If days>0
   result$=StrU(days,#PB_Long)+" day"
   If Not days=1
    result$+"s "
   Else
    result$+" "
   EndIf
  EndIf
  result$+RSet(StrU(hours,#PB_Long),2,"0")+":"
  result$+RSet(StrU(minutes,#PB_Long),2,"0")+":"
  result$+RSet(StrU(seconds,#PB_Long),2,"0")
 EndIf
 ProcedureReturn result$
EndProcedure

old=Date()
Delay(1000)
new=Date()
Debug CalculateDays(old,new)
I recommend following my example here for long measurements of time as seconds should be enough precision.
Millisecs are normally only needed in cases where you expect to track only a few days worth of time, usually just a few hours, as any longer than a few hours I always use Date() by habit anyway.
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post by Kaeru Gaman »

;difference thus no wrapping, it IS limited to 47 something days worth of millisecs where it wraps to 0 again so you can't meassure beyond that
... I just made a test, and now I wonder if there is no easier solution:

Code: Select all

msOld.l = $7FFFFFF0

Debug msOld

msNew.l = msOld + 50

Debug msNew

msDiff.l = msNew - msOld

Debug msDiff
as you can see, the difference is correct, it seems that the ASM code solves the overflow as it should.
(I bet CARRY is set after that.)

keeping that in mind, it should be possible to drive around the overflow without coding any complicated special tricks...
oh... and have a nice day.
breeze4me
Enthusiast
Enthusiast
Posts: 633
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Post by breeze4me »

I don't guarantee it will work correctly.

Code: Select all

#PDH_FMT_LONG     = $00000100
#PDH_FMT_DOUBLE   = $00000200
#PDH_FMT_LARGE    = $00000400
#PDH_FMT_NOSCALE  = $00001000
#PDH_FMT_1000     = $00002000
#PDH_FMT_NOCAP100 = $00008000

Structure PDH_FMT_COUNTERVALUE
  CStatus.l
  dummy.l
  StructureUnion
    l.l
    q.q
    d.d
  EndStructureUnion
EndStructure

Global data1.PDH_FMT_COUNTERVALUE

CompilerIf #PB_Compiler_Unicode
  Prototype PdhVbAddCounter(hQuery, CounterPath.p-ascii, *hCounter)
CompilerElse
  Prototype PdhVbAddCounter(hQuery, CounterPath.s, *hCounter)
CompilerEndIf
Prototype PdhGetFormattedCounterValue(hCounter, dwFormat, lpdwType, *Value)
Prototype PdhCollectQueryData(hQuery)
Prototype PdhVbOpenQuery(*hQuery)
Prototype PdhCloseQuery(hQuery)

If OpenLibrary(0, "pdh.dll")
  PdhVbOpenQuery.PdhVbOpenQuery = GetFunction(0, "PdhVbOpenQuery")
  PdhVbAddCounter.PdhVbAddCounter = GetFunction(0, "PdhVbAddCounter")
  PdhGetFormattedCounterValue.PdhGetFormattedCounterValue = GetFunction(0, "PdhGetFormattedCounterValue")
  PdhCollectQueryData.PdhCollectQueryData = GetFunction(0, "PdhCollectQueryData")
  PdhCloseQuery.PdhCloseQuery = GetFunction(0, "PdhCloseQuery")
  
  If PdhVbOpenQuery And PdhVbAddCounter And PdhGetFormattedCounterValue And PdhCollectQueryData And PdhCloseQuery
    If PdhVbOpenQuery(@hQuery) = #ERROR_SUCCESS
      If PdhVbAddCounter(hQuery, "\System\System Up Time", @hCounter) = #ERROR_SUCCESS
        For i = 0 To 10
          If PdhCollectQueryData(hQuery) = #ERROR_SUCCESS
            If PdhGetFormattedCounterValue(hCounter, #PDH_FMT_LARGE|#PDH_FMT_1000, 0, data1) = #ERROR_SUCCESS
            ;If PdhGetFormattedCounterValue(hCounter, #PDH_FMT_LARGE, 0, data1) = #ERROR_SUCCESS
              Debug data1\q
              Debug timeGetTime_()
              Debug ""
              Sleep_(1000)
            EndIf
          EndIf
        Next
      EndIf
      PdhCloseQuery(hQuery)
    EndIf
  EndIf
  CloseLibrary(0)
EndIf
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Post by PB »

All I'm trying to do is see if X amount of milliseconds has elapsed between
two events. For example, the user hits the Space bar. I want to know how
many milliseconds has elapsed since a second hit of the Space bar. Using
Date() is no good for that, and ElapsedMilliseconds() doesn't work on my
workplace PC due to negative results.

I finally just decided to use this, which works fine for my purposes, and
also shows why we need a line continuation character for the IDE. ;)

Code: Select all

Procedure.q GetTimeRef()
  GetLocalTime_(time.SYSTEMTIME)
  t$=FormatDate("%yyyy%mm%dd%hh%ii%ss",Date(time\wYear,time\wMonth,time\wDay,time\wHour,time\wMinute,time\wSecond))+RSet(Str(time\wMilliseconds),3,"0")
  ProcedureReturn Val(t$)
EndProcedure

s.q=GetTimeRef() : Debug s
Sleep_(1000)
f.q=GetTimeRef() : Debug f
Debug "Elapsed = "+Str(f-s)+" ms"
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Post by PB »

A question for Fred/Freak: when compiling on Vista, does ElapsedMilliseconds()
use GetTickCount64_() for the wrapper, which fixes this problem? I hope so. :)
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post by Kaeru Gaman »

PB wrote:ElapsedMilliseconds() doesn't work on my
workplace PC due to negative results.
look at my example above.
as I see it, you should be able to produce the correct result just by a simple subtraction.
oh... and have a nice day.
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Post by PB »

@Kaeru: I'm not at work now, but will definitely try it tomorrow, as I'd much
rather use a simple calc than my procedure. I kinda like my procedure too,
as it has a good visual representation of the moment in time it was taken.
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
User avatar
dhouston
Enthusiast
Enthusiast
Posts: 430
Joined: Tue Aug 21, 2007 2:44 pm
Location: USA (Cincinnati)
Contact:

Post by dhouston »

PB wrote:All I'm trying to do is see if X amount of milliseconds has elapsed between
two events. For example, the user hits the Space bar. I want to know how
many milliseconds has elapsed since a second hit of the Space bar. Using
Date() is no good for that, and ElapsedMilliseconds() doesn't work on my
workplace PC due to negative results.
In this case, dealing with the overflow is quite simple.

Code: Select all

MAX = overflow
Start = GetTickCount
If GetTickCount < Start
  result = MAX - Start + GetTickCount
Else
  result = GetTickCount - Start
End If
I don't recall if you have to worry about latency when using GetTickCount. If so, there is a precision timer available that can give you submillisecond accuracy.
Post Reply