Files
unison/src/core/model/show-progress.cc
2022-10-14 14:13:12 +00:00

288 lines
7.9 KiB
C++

/*
* Copyright (c) 2017 Lawrence Livermore National Laboratory
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Author: Gustavo Carneiro <gjc@inescporto.pt>
* Author: Peter D. Barnes, Jr. <pdbarnes@llnl.gov>
*/
/**
* \file
* \ingroup core
* ns3::ShowProgress implementation.
*/
#include "show-progress.h"
#include "event-id.h"
#include "log.h"
#include "nstime.h"
#include "simulator.h"
#include <iomanip>
namespace ns3
{
NS_LOG_COMPONENT_DEFINE("ShowProgress");
/* static */
const int64x64_t ShowProgress::HYSTERESIS = 1.414;
/* static */
const int64x64_t ShowProgress::MAXGAIN = 2.0;
ShowProgress::ShowProgress(const Time interval /* = Seconds (1.0) */,
std::ostream& os /* = std::cout */)
: m_timer(),
m_stamp(),
m_elapsed(),
m_interval(interval),
m_vtime(Time(1)),
m_event(),
m_eventCount(0),
m_printer(DefaultTimePrinter),
m_os(&os),
m_verbose(false),
m_repCount(0)
{
NS_LOG_FUNCTION(this << interval);
ScheduleCheckProgress();
Start();
}
ShowProgress::~ShowProgress()
{
Stop();
}
void
ShowProgress::SetInterval(const Time interval)
{
NS_LOG_FUNCTION(this << interval);
const int64x64_t ratio = interval / m_interval;
m_interval = interval;
// If we aren't at the initial value assume we have a reasonable
// update time m_vtime, so we should rescale it
if (m_vtime > Time(1))
{
m_vtime = m_vtime * ratio;
}
Simulator::Cancel(m_event);
Start();
} // ShowProgress::SetInterval
void
ShowProgress::SetTimePrinter(TimePrinter lp)
{
NS_LOG_FUNCTION(this << lp);
m_printer = lp;
}
void
ShowProgress::SetVerbose(bool verbose)
{
NS_LOG_FUNCTION(this << verbose);
m_verbose = verbose;
}
void
ShowProgress::SetStream(std::ostream& os)
{
m_os = &os;
}
void
ShowProgress::ScheduleCheckProgress()
{
NS_LOG_FUNCTION(this);
m_event = Simulator::Schedule(m_vtime, &ShowProgress::CheckProgress, this);
m_timer.Start();
} // ShowProgress::ScheduleCheckProgress
void
ShowProgress::GiveFeedback(uint64_t nEvents, int64x64_t ratio, int64x64_t speed)
{
// Save stream state
auto precision = m_os->precision();
auto flags = m_os->flags();
m_os->setf(std::ios::fixed, std::ios::floatfield);
if (m_verbose)
{
(*m_os) << std::right << std::setw(5) << m_repCount << std::left
<< (ratio > (1.0 / HYSTERESIS) ? "-->" : " ") << std::setprecision(9)
<< " [del: " << m_elapsed.As(Time::S) << "/ int: " << m_interval.As(Time::S)
<< " = rat: " << ratio
<< (ratio > HYSTERESIS ? " dn" : (ratio < 1.0 / HYSTERESIS ? " up" : " --"))
<< ", vt: " << m_vtime.As(Time::S) << "] ";
}
// Print the current time
(*m_printer)(*m_os);
(*m_os) << " (" << std::setprecision(3) << std::setw(8) << speed.GetDouble() << "x real time) "
<< nEvents << " events processed" << std::endl
<< std::flush;
// Restore stream state
m_os->precision(precision);
m_os->flags(flags);
} // ShowProgress::GiveFeedback
void
ShowProgress::CheckProgress()
{
// Get elapsed wall clock time
m_elapsed += MilliSeconds(m_timer.End());
NS_LOG_FUNCTION(this << m_elapsed);
// Don't do anything unless the elapsed time is positive.
if (m_elapsed <= Time(0))
{
m_vtime = m_vtime * MAXGAIN;
++m_repCount;
ScheduleCheckProgress();
return;
}
// Speed: how fast are we compared to real time
const int64x64_t speed = m_vtime / m_elapsed;
// Ratio: how much real time did we use,
// compared to reporting interval target
const int64x64_t ratio = m_elapsed / m_interval;
// Elapsed event count
uint64_t events = Simulator::GetEventCount();
uint64_t nEvents = events - m_eventCount;
/**
* \internal Update algorithm
*
* We steer \c m_vtime to obtain updates approximately every
* \c m_interval in wall clock time. To smooth things out a little
* we impose a hysteresis band around \c m_interval where we
* don't change \c m_vtime. To avoid too rapid movements
* chasing spikes or dips in execution rate, we bound the
* change in \c m_vtime to a maximum factor.
*
* In mathematical terms, we compute the ratio of elapsed wall clock time
* compared to the target reporting interval:
* \f[ ratio = \frac{elapsed}{target interval)} \f]
*
* Graphically, the windows in ratio value and the corresponding
* updates to \c m_vtime are sketched in this figure:
* \verbatim
^
|
ratio | vtime update
|
|
| /= MAXGAIN
|
MAXGAIN -|-------------- /= min (ratio, MAXGAIN)
|
| /= ratio
|
HYSTERESIS -|=============================================
|
|
|
1 -| No change
|
|
|
1/ HYSTERESIS -|==============================================
|
| *= 1 / ratio
|
1/ MAXGAIN -|--------------- *= min (1 / ratio, MAXGAIN)
|
| *= MAXGAIN
|
\endverbatim
*
* As indicated, when ratio is outside the hysteresis band
* it amounts to multiplying \c m_vtime by the min/max of the ratio
* with the appropriate MAXGAIN factor.
*
* Finally, some experimentation suggests we further dampen
* movement between HYSTERESIS and MAXGAIN, so we only apply
* half the ratio. This reduces "hunting" for a stable update
* period.
*
* \todo Evaluate if simple exponential averaging would be
* more effective, simpler.
*/
if (ratio > HYSTERESIS)
{
int64x64_t f = 1 + (ratio - 1) / 2;
if (ratio > MAXGAIN)
{
f = MAXGAIN;
}
m_vtime = m_vtime / f;
}
else if (ratio < 1.0 / HYSTERESIS)
{
int64x64_t f = 1 + (1 / ratio - 1) / 2;
if (1 / ratio > MAXGAIN)
{
f = MAXGAIN;
}
m_vtime = m_vtime * f;
}
// Only give feedback if ratio is at least as big as 1/HYSTERESIS
if (ratio > (1.0 / HYSTERESIS))
{
GiveFeedback(nEvents, ratio, speed);
m_elapsed = Time(0);
m_eventCount = events;
}
else
{
NS_LOG_LOGIC("skipping update: " << ratio);
// enable this line for debugging, with --verbose
// GiveFeedback (nEvents, ratio, speed);
}
++m_repCount;
// And do it again
ScheduleCheckProgress();
} // ShowProgress::CheckProgress
void
ShowProgress::Start()
{
m_stamp.Stamp();
(*m_os) << "Start wall clock: " << m_stamp.ToString() << std::endl;
} // ShowProgress::Start
void
ShowProgress::Stop()
{
m_stamp.Stamp();
(*m_os) << "End wall clock: " << m_stamp.ToString()
<< "\nElapsed wall clock: " << m_stamp.GetInterval() << "s" << std::endl;
} // ShowProgress::Stop
} // namespace ns3