/*********************************************************************
* Software License Agreement (AGPL-3 License)
*
* OpenViBE SDK Test Software
* Based on OpenViBE V1.1.0, Copyright (C) Inria, 2006-2015
* Copyright (C) Inria, 2015-2017,V1.0
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3,
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.
* If not, see .
*/
#include
#include
#include
#include
#include
#include
#include "openvibe/CTime.hpp"
#include "system/ovCTime.h"
#include "ovtAssert.h"
//
// \note This test should be improved. Some improvements could be:
// - Compare clock results to gold standard
// - True testing of monotonic state
// - Stress tests on longer period
//
// \brief Calibrate sleep function to estimate the extra time not spent at sleeping
uint64_t calibrateSleep(const size_t nSample, bool (*sleepFunction)(uint64_t), uint64_t (*timeFunction)())
{
uint64_t maxTime = 0;
for (size_t i = 0; i < nSample; ++i)
{
const uint64_t preTime = timeFunction();
sleepFunction(0);
const uint64_t processingTime = timeFunction() - preTime;
if (processingTime > maxTime) { maxTime = processingTime; }
}
return maxTime;
}
// \brief Record sleep function precision
std::vector testSleep(const std::vector& sleepTimes, bool (*sleepFunction)(uint64_t), uint64_t (*timeFunction)())
{
std::vector effectiveSleepTimes;
for (auto time : sleepTimes)
{
const uint64_t preTime = timeFunction();
sleepFunction(time);
effectiveSleepTimes.push_back(timeFunction() - preTime);
}
return effectiveSleepTimes;
}
// \brief Return a warning count that is incremented when sleep function did not meet following requirements:
// - sleep enough time
// - sleep less than the expected time + delta
size_t assessSleepTestResult(const std::vector& expected, const std::vector& result, const uint64_t delta, const uint64_t epsilon)
{
size_t warningCount = 0;
for (size_t i = 0; i < expected.size(); ++i)
{
if (result[i] + epsilon < expected[i] || result[i] > (expected[i] + delta + epsilon))
{
std::cerr << "WARNING: Failure to sleep the right amount of time: [expected|result] = "
<< OpenViBE::CTime(expected[i]) << "|" << OpenViBE::CTime(result[i]) << std::endl;
warningCount++;
}
}
return warningCount;
}
// \brief Record clock function data (spin test taken from OpenViBE). Return a tuple with:
// - bool = monotonic state
// - std::vector = all the cumulative steps
std::tuple> testClock(const uint64_t samplePeriod, const unsigned sampleCountGuess, uint64_t (*timeFunction)())
{
std::vector cumulativeSteps;
cumulativeSteps.reserve(sampleCountGuess);
bool monotonic = true;
const uint64_t startTime = timeFunction();
uint64_t nowTime = startTime;
uint64_t previousTime = nowTime;
while (nowTime - startTime < samplePeriod)
{
nowTime = timeFunction();
if (nowTime > previousTime) { cumulativeSteps.push_back(nowTime - previousTime); }
else if (nowTime < previousTime)
{
monotonic = false;
break;
}
previousTime = nowTime;
}
return std::make_tuple(monotonic, cumulativeSteps);
}
// \brief Compute jitter measurements for 32:32 time. Return a tuple with:
// - double = mean
// - double = max deviation from mean
// - double = RMSE
std::tuple assessTimeClock(const std::vector& measurements)
{
double jitterMax = 0.0;
double jitterMSE = 0.0;
double mean = 0.0;
// compute mean
for (auto& data : measurements)
{
// convert data
auto seconds = data >> 32;
auto microseconds = ((data & 0xFFFFFFFFLL) * 1000000LL) >> 32;
std::chrono::microseconds chronoData = std::chrono::seconds(seconds) + std::chrono::microseconds(microseconds);
mean += double(chronoData.count()) / (1000 * measurements.size());
}
// compute deviations
for (auto& data : measurements)
{
// convert data
auto seconds = data >> 32;
auto microseconds = ((data & 0xFFFFFFFFLL) * 1000000LL) >> 32;
std::chrono::microseconds chronoData = std::chrono::seconds(seconds) + std::chrono::microseconds(microseconds);
const double deviation = std::abs(double(chronoData.count()) / 1000 - mean);
jitterMSE += std::pow(deviation, 2) / measurements.size();
if (deviation - jitterMax > std::numeric_limits::epsilon()) { jitterMax = deviation; }
}
return std::make_tuple(mean, jitterMax, std::sqrt(jitterMSE));
}
int uoTimeTest(int /*argc*/, char* /*argv*/[])
{
// This is a very theoretical test. But if it returns false, we can
// assume that a steady clock is not available on the test
// platform. If it returns true, it just means that the internal clock
// says it is steady...
OVT_ASSERT(System::Time::isClockSteady(), "Failure to retrieve a steady clock");
// Same as above.
// The test is set to 1ms at it is a requirement for OpenViBE clocks
OVT_ASSERT(System::Time::checkResolution(1), "Failure to check for resolution");
// A stress test to check no overflow happens
OVT_ASSERT(System::Time::checkResolution(std::numeric_limits::max()), "Failure to check for resolution");
//
// zSleep() function test
//
const std::vector expectedSleepData = {
0x80000000LL, 0x40000000LL, 0x20000000LL, 0x10000000LL,
0x80000000LL, 0x40000000LL, 0x20000000LL, 0x10000000LL,
0x08000000LL, 0x04000000LL, 0x02000000LL, 0x01000000LL,
0x08000000LL, 0x04000000LL, 0x02000000LL, 0x01000000LL
};
// calibrate sleep function
const auto deltaTime = calibrateSleep(1000, System::Time::zsleep, System::Time::zgetTime);
std::cout << "INFO: Delta time for zsleep calibration = " << OpenViBE::CTime(deltaTime) << std::endl;
const auto resultSleepData = testSleep(expectedSleepData, System::Time::zsleep, System::Time::zgetTime);
OVT_ASSERT(resultSleepData.size() == expectedSleepData.size(), "Failure to run zsleep tests");
const size_t warningCount = assessSleepTestResult(expectedSleepData, resultSleepData, deltaTime, OpenViBE::CTime(0.005).time());
// relax this threshold in case there is some recurrent problems
// according to the runtime environment
OVT_ASSERT(warningCount <= 2, "Failure to zsleep the right amount of time");
//
// zGetTime() function test
//
// the sample count guess was found in an empiric way
auto resultGetTimeData = testClock(OpenViBE::CTime(0.5).time(), 500000, System::Time::zgetTime);
OVT_ASSERT(std::get<0>(resultGetTimeData), "Failure in zgetTime() test: the clock is not monotonic");
auto clockMetrics = assessTimeClock(std::get<1>(resultGetTimeData));
std::cout << "INFO: Sample count for getTime() = " << std::get<1>(resultGetTimeData).size() << std::endl;
std::cout << "INFO: Mean step in ms for getTime() = " << std::get<0>(clockMetrics) << std::endl;
std::cout << "INFO: Max deviation in ms for getTime() = " << std::get<1>(clockMetrics) << std::endl;
std::cout << "INFO: RMSE in ms for getTime() = " << std::get<2>(clockMetrics) << std::endl;
// We expect at least 1ms resolution
const double resolutionDelta = std::get<0>(clockMetrics) - 1;
OVT_ASSERT(resolutionDelta <= std::numeric_limits::epsilon(), "Failure in zgetTime() test: the clock resolution does not match requirements");
return EXIT_SUCCESS;
}