Page 1 of 1

ElapsedMilliseconds/GetTickCount wrap

Posted: Mon Dec 15, 2008 9:38 pm
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?

Posted: Mon Dec 15, 2008 9:53 pm
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.

Posted: Tue Dec 16, 2008 12:00 am
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.

Posted: Tue Dec 16, 2008 12:49 am
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...

Posted: Tue Dec 16, 2008 5:46 am
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

Posted: Tue Dec 16, 2008 12:20 pm
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"

Posted: Tue Dec 16, 2008 12:41 pm
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. :)

Posted: Tue Dec 16, 2008 12:44 pm
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.

Posted: Tue Dec 16, 2008 12:49 pm
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.

Posted: Tue Dec 16, 2008 12:54 pm
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.