2022-12-06 22:53:37 +00:00
/***
Copyright ( C ) 2022 J Reece Wilson ( a / k / a " Reece " ) . All rights reserved .
File : ThroughputCalculator . hpp
Date : 2022 - 12 - 06
Author : Reece
* * */
# pragma once
namespace Aurora : : Utility
{
struct ThroughputCalculator
{
// call me arbitrarily
double inline OnUpdate ( AuUInt uUnit )
2022-12-08 19:34:15 +00:00
{
OnTick ( ) ;
2022-12-06 22:53:37 +00:00
this - > uTotal + = uUnit ;
this - > uTotalLifetime + = uUnit ;
return this - > dCurFreq ;
}
// or:
void inline AddData ( AuUInt uUnit )
{
this - > uTotal + = uUnit ;
this - > uTotalLifetime + = uUnit ;
}
void inline AddSampleSampleTick ( ) // at around 1 tick/sec
{
OnTick ( ) ;
}
// then call me arbitrarily:
double inline GetEstimatedHertz ( ) const
{
auto uNow = Aurora : : Time : : SteadyClockNS ( ) ;
auto uDelta = uNow - this - > uLast ;
// we cannot do anything on frame zero
if ( ! this - > dCurFreq )
{
return 0 ;
}
static const auto kOneSecond = AuMSToNS < AuUInt64 > ( AuSToMS < AuUInt64 > ( 1 ) ) ;
auto dDeltaWeight = ( ( double ) uDelta / ( double ) kOneSecond ) ;
// is overshooting over 1s?
if ( uDelta > kOneSecond )
{
if ( uDelta > this - > uLastDelta )
{
// we're in uncharted territory. delaying til frame +1 and overshooting by +1 should be fine.
return 0 ;
}
// # otherwise return constant last tick freqency of this->dCurFreq
//return this->dCurFreq;
// ## dNextFactor: we are at-least the amount of bytes in pending frame since last tick, over the time period of at least 1 second
return AuMax < double > ( /*dNextFactor: (effectively unit/time)*/ double ( this - > uTotal ) / dDeltaWeight , /* ordinarily a large unit-frame within the tick wouldn't jitter the throughput bc OnTick would normalized the value.
however , once over a second , it makes sense to account for pending bytes / ` this - > uTotal ` / time into frame as a baseline .
if we ' re under a second since the last tick , we can simply extrapolate from the normalized frequency , to
the current frames ` this - > uTotal ` / uDelta , in a " linear " manner . ok , not so linear , ubytes can change , but it should
give us realistic smoothed out bandwidth statistics . it ' s more like two fractions of time where one is inverted .
see : the last expression
*/
this - > dCurFreq /* use the normalized frequency. a single small frame shouldn't significantly jitter the throughput. */ ) ;
}
// otherwise return live frequency...
if ( uDelta > this - > uLastDelta ) // last normalized this->dCurFreq, if not current frame if suddenly peaking
{
return this - > dCurFreq ;
return AuMax < double > ( /*dNextFactor: (effectively unit/time)*/ double ( this - > uTotal ) / dDeltaWeight , this - > dCurFreq ) ;
}
else // fractional lerp
{
//return this->dCurFreq;
double dNextFactor = double ( this - > uTotal ) * dDeltaWeight ;
// ...by calculating the weight of the current tick in terms of delta between now and the previous frame.
// the bit we don't know will be the last frames throughput freqency added to the current throughput * delta.
// combined, we get a transition to this->uTotal whose starting point is accelerated by this->dCurFreq
return ( dNextFactor ) +
( this - > dCurFreq * ( double ( 1.f ) - dDeltaWeight ) /*last frame units/s extrapolated forward by the unit of time that hasn't passed*/ ) ;
}
}
AuUInt64 inline GetTotalStats ( )
{
return this - > uTotalLifetime ;
}
AuUInt64 inline GetLastFrameTimeSteady ( )
{
return this - > uLast ;
}
AuInt64 inline GetLastFrameTimeWall ( )
{
return this - > uLastWall ;
}
private :
void inline OnTick ( )
{
auto uNow = Aurora : : Time : : SteadyClockNS ( ) ;
auto uDelta = uNow - this - > uLast ;
this - > uLastDelta = uDelta ;
this - > uLast = uNow ;
this - > uLastWall = Aurora : : Time : : CurrentClockMS ( ) ;
auto dAbsMax = double ( this - > uTotal ) / ( double ( uDelta ) / ( double ) AuMSToNS < AuUInt64 > ( AuSToMS < AuUInt64 > ( 1 ) ) ) ;
auto dAbsMin = this - > dCurFreq * ( double ) 0.5f ;
dAbsMax = ( dAbsMax * 0.5 ) + ( dAbsMin * 0.5 ) ; // without this, our numbers are too noisey and far ahead of ThroughputCalculator.hpp.buffered.
// in either scenario, ThroughputCalculator.hpp.buffered matches system specs on IO tests more closely but it still massively overshoots.
// adding this one line of normalization gets this approximation down to system monitoring utilities somewhat accurately.
// we still overshoot. the buffered variant could beat us at some point, but this original implementation seems to be much more lightweight.
// id question if its worth a switch over. i think this is close enough & justifys its cheapness
this - > dCurFreq = AuMax ( dAbsMin , dAbsMax ) ;
this - > uTotal = 0 ;
}
AuUInt64 uTotal { } ;
AuUInt64 uTotalLifetime { } ;
AuUInt64 uLastDelta { } ;
AuInt64 uLastWall { } ;
AuUInt64 uLast { } ;
double dCurFreq { } ;
} ;
}