utilities: Update time/r methods for LINUX

Default to MONOTONIC clock for timer methods to avoid falling victim to
clock corrections.  Changed signatures from accepting pointers since
this is not needed an complicates calls and Java/JS/Python bindings.

    * Switched from nanosleep to clock_nanosleep to allow developers to
      provide a clock for LINUX
    * Default upm_clock_init to CLOCK_MONOTONIC
    * Updated logic to calculating delay and elapsed to be more readable
    * Added ns flavors for completeness
    * Refactored all upm_* delay/timer methods
    * Added #else for preprocessor cases w/o an #else
    * Added test for AQI
    * Added test fixture with logic to identify a minimum delay time
      which is used as a metric for testing all delay methods
    * Much more lenient unit testing of delays to minimize false CI
      failures

Signed-off-by: Noel Eck <noel.eck@intel.com>
This commit is contained in:
Noel Eck 2018-05-01 10:56:27 -07:00
parent be46240b8c
commit db3c73cbd1
10 changed files with 290 additions and 165 deletions

View File

@ -456,8 +456,7 @@ upm_result_t mcp2515_set_opmode(const mcp2515_context dev,
<< _MCP2515_CANSTAT_OPMODE_SHIFT);
bool done = false;
upm_clock_t clock;
upm_clock_init(&clock);
upm_clock_t clock = upm_clock_init();
do
{
@ -623,8 +622,7 @@ upm_result_t mcp2515_transmit_buffer(const mcp2515_context dev,
return UPM_SUCCESS;
// now spin with timeout waiting for it to be transmitted
upm_clock_t clock;
upm_clock_init(&clock);
upm_clock_t clock = upm_clock_init();
bool done = false;
do

View File

@ -99,7 +99,7 @@ ppd42ns_dust_data ppd42ns_get_data(const ppd42ns_context dev)
unsigned int low_pulse_occupancy = 0;
upm_clock_init(&max_loop_time);
max_loop_time = upm_clock_init();
do {
low_pulse_occupancy += ppd42ns_pulse_in(dev, 0);
@ -132,11 +132,10 @@ static uint32_t ppd42ns_pulse_in(const ppd42ns_context dev,
assert(dev != NULL);
// we run for no more than 1 second at a time
upm_clock_t max_time;
upm_clock_t pulse_time;
uint32_t total_pulse_time = 0;
upm_clock_init(&max_time);
upm_clock_t max_time = upm_clock_init();
bool pin_level;
bool is_timing = false;
@ -146,7 +145,7 @@ static uint32_t ppd42ns_pulse_in(const ppd42ns_context dev,
if (!is_timing && pin_level == high_low_value)
{
// level is desired level, but not currently timing
upm_clock_init(&pulse_time);
pulse_time = upm_clock_init();
is_timing = true;
}
else if (is_timing && pin_level != high_low_value)

View File

@ -317,8 +317,7 @@ RN2903_RESPONSE_T rn2903_waitfor_response(const rn2903_context dev,
memset(dev->resp_data, 0, RN2903_MAX_BUFFER);
dev->resp_len = 0;
upm_clock_t clock;
upm_clock_init(&clock);
upm_clock_t clock = upm_clock_init();
uint32_t elapsed = 0;
do

View File

@ -206,8 +206,7 @@ upm_result_t speaker_emit(const speaker_context dev, unsigned int freq,
if (speaker_set_frequency(dev, freq))
return UPM_ERROR_OPERATION_FAILED;
upm_clock_t clock;
upm_clock_init(&clock);
upm_clock_t clock = upm_clock_init();
mraa_pwm_enable(dev->pwm, 1);
while (upm_elapsed_ms(&clock) < emit_ms)

View File

@ -265,8 +265,7 @@ int uartat_command_with_response(const uartat_context dev,
{
memset(resp, 0, resp_len);
upm_clock_t clock;
upm_clock_init(&clock);
upm_clock_t clock = upm_clock_init();
size_t idx = 0;
@ -320,8 +319,7 @@ bool uartat_command_waitfor(const uartat_context dev, const char *cmd,
memset(resp, 0, resp_len);
upm_clock_t clock;
upm_clock_init(&clock);
upm_clock_t clock = upm_clock_init();
size_t idx = 0;

View File

@ -26,31 +26,42 @@
*/
#ifndef _POSIX_C_SOURCE
// We need at least 199309L for nanosleep()
// We need at least 199309L for clock_nanosleep()
# define _POSIX_C_SOURCE 200809L
#endif
#include <assert.h>
#include <time.h>
#include <errno.h>
#include "upm_platform.h"
#include "upm_utilities.h"
// https://www3.epa.gov/airnow/aqi-technical-assistance-document-may2016.pdf
static struct aqi {
float clow;
float chigh;
int llow;
int lhigh;
} aqi[] = {
{0.0, 12.0, 0, 50},
{12.1, 35.4, 51, 100},
{35.5, 55.4, 101, 150},
{55.5, 150.4, 151, 200},
{150.5, 250.4, 201, 300},
{250.5, 350.4, 301, 400},
{350.5, 500.4, 401, 500},
};
void upm_delay(unsigned int time)
/**
* Calculate the delta of two upm_clock_t values as
* delta = finish - start
*
* @param finish Ending upm_clock_t time
* @param start Beginning upm_clock_t time
* @return Time in nanoseconds
*/
static uint64_t _delta_ns(const upm_clock_t* finish, const upm_clock_t* start)
{
uint64_t delta;
assert((finish != NULL) && (start != NULL) && "_delta_ns, arguments cannot be NULL");
#if defined(UPM_PLATFORM_ZEPHYR)
delta = SYS_CLOCK_HW_CYCLES_TO_NS64(*finish - *start);
#elif defined(UPM_PLATFORM_LINUX)
delta = (finish->tv_sec * 1000000000UL + finish->tv_nsec) -
(start->tv_sec * 1000000000UL + start->tv_nsec);
#else
#error "Unknown platform, valid platforms are {UPM_PLATFORM_ZEPHYR, UPM_PLATFORM_LINUX}"
#endif
return delta;
}
void upm_delay(uint32_t time)
{
/* Return if time == 0 */
if (!time)
@ -58,17 +69,11 @@ void upm_delay(unsigned int time)
#if defined(UPM_PLATFORM_LINUX)
struct timespec delay_time;
delay_time.tv_sec = time;
delay_time.tv_nsec = 0;
// The advantage over sleep(3) here is that it will not use
// an alarm signal or handler.
upm_clock_t delay_time = {time, 0};
// here we spin until the delay is complete - detecting signals
// and continuing where we left off
while (nanosleep(&delay_time, &delay_time) && errno == EINTR)
; // loop
while (clock_nanosleep(CLOCK_MONOTONIC, 0, &delay_time, &delay_time) == EINTR);
#elif defined(UPM_PLATFORM_ZEPHYR)
# if KERNEL_VERSION_MAJOR == 1 && KERNEL_VERSION_MINOR >= 6
@ -88,10 +93,12 @@ void upm_delay(unsigned int time)
# endif
#else
#error "Unknown platform, valid platforms are {UPM_PLATFORM_ZEPHYR, UPM_PLATFORM_LINUX}"
#endif
}
void upm_delay_ms(unsigned int time)
void upm_delay_ms(uint32_t time)
{
/* Return if time == 0 */
if (!time)
@ -99,14 +106,11 @@ void upm_delay_ms(unsigned int time)
#if defined(UPM_PLATFORM_LINUX)
struct timespec delay_time;
upm_clock_t delay_time = {time / 1000, (time % 1000) * 1000000UL};
delay_time.tv_sec = time / 1000;
delay_time.tv_nsec = (time % 1000) * 1000000;
// here we spin until the delay is complete - detecting signals
// and continuing where we left off
while (nanosleep(&delay_time, &delay_time) && errno == EINTR)
; // loop
while (clock_nanosleep(CLOCK_MONOTONIC, 0, &delay_time, &delay_time) == EINTR);
#elif defined(UPM_PLATFORM_ZEPHYR)
# if KERNEL_VERSION_MAJOR == 1 && KERNEL_VERSION_MINOR >= 6
@ -125,10 +129,12 @@ void upm_delay_ms(unsigned int time)
nano_timer_test(&timer, TICKS_UNLIMITED);
# endif
#else
#error "Unknown platform, valid platforms are {UPM_PLATFORM_ZEPHYR, UPM_PLATFORM_LINUX}"
#endif
}
void upm_delay_us(unsigned int time)
void upm_delay_us(uint32_t time)
{
/* Return if time == 0 */
if (!time)
@ -136,24 +142,19 @@ void upm_delay_us(unsigned int time)
#if defined(UPM_PLATFORM_LINUX)
struct timespec delay_time;
upm_clock_t delay_time = {time / 1000000, (time % 1000000) * 1000};
delay_time.tv_sec = time / 1000000;
delay_time.tv_nsec = (time % 1000000) * 1000;
// here we spin until the delay is complete - detecting signals
// and continuing where we left off
while (nanosleep(&delay_time, &delay_time) && errno == EINTR)
; // loop
while (clock_nanosleep(CLOCK_MONOTONIC, 0, &delay_time, &delay_time) == EINTR);
#elif defined(UPM_PLATFORM_ZEPHYR)
# if KERNEL_VERSION_MAJOR == 1 && KERNEL_VERSION_MINOR >= 6
// we will use a upm_clock to do microsecond timings here as k_timer has
// only a millisecond resolution. So we init a clock and spin.
upm_clock_t timer;
upm_clock_init(&timer);
while (upm_elapsed_us(&timer) < time)
; // spin
upm_clock_t timer = upm_clock_init();
while (upm_elapsed_us(&timer) < time); // spin
# else
@ -165,109 +166,129 @@ void upm_delay_us(unsigned int time)
# endif
#else
#error "Unknown platform, valid platforms are {UPM_PLATFORM_ZEPHYR, UPM_PLATFORM_LINUX}"
#endif
}
void upm_clock_init(upm_clock_t *clock)
void upm_delay_ns(uint64_t time)
{
/* Return if time == 0 */
if (!time)
return;
#if defined(UPM_PLATFORM_LINUX)
gettimeofday(clock, NULL);
upm_clock_t delay_time = {time / 1000000000UL, time % 1000000000UL};
// here we spin until the delay is complete - detecting signals
// and continuing where we left off
while (clock_nanosleep(CLOCK_MONOTONIC, 0, &delay_time, &delay_time) == EINTR);
#elif defined(UPM_PLATFORM_ZEPHYR)
*clock = sys_cycle_get_32();
# if KERNEL_VERSION_MAJOR == 1 && KERNEL_VERSION_MINOR >= 6
// we will use a upm_clock to do microsecond timings here as k_timer has
// only a millisecond resolution. So we init a clock and spin.
upm_clock_t timer = upm_clock_init();
while (upm_elapsed_ns(&timer) < time); // spin
# else
struct nano_timer timer;
void *timer_data[1];
nano_timer_init(&timer, timer_data);
nano_timer_start(&timer, time + 1);
nano_timer_test(&timer, TICKS_UNLIMITED);
# endif
#else
#error "Unknown platform, valid platforms are {UPM_PLATFORM_ZEPHYR, UPM_PLATFORM_LINUX}"
#endif
}
uint32_t upm_elapsed_ms(upm_clock_t *clock)
upm_clock_t upm_clock_init(void)
{
upm_clock_t clock = {0};
#if defined(UPM_PLATFORM_LINUX)
struct timeval elapsed, now;
uint32_t elapse;
// get current time
gettimeofday(&now, NULL);
struct timeval startTime = *clock;
// compute the delta since startTime
if( (elapsed.tv_usec = now.tv_usec - startTime.tv_usec) < 0 )
{
elapsed.tv_usec += 1000000;
elapsed.tv_sec = now.tv_sec - startTime.tv_sec - 1;
}
else
{
elapsed.tv_sec = now.tv_sec - startTime.tv_sec;
}
elapse = (uint32_t)((elapsed.tv_sec * 1000) + (elapsed.tv_usec / 1000));
// never return 0
if (elapse == 0)
elapse = 1;
return elapse;
clock_gettime(CLOCK_MONOTONIC, &clock);
#elif defined(UPM_PLATFORM_ZEPHYR)
uint32_t now = sys_cycle_get_32();
uint32_t elapsed =
(uint32_t)(SYS_CLOCK_HW_CYCLES_TO_NS64(now - *clock)/(uint64_t)1000000);
if (elapsed == 0)
elapsed = 1;
return elapsed;
clock = sys_cycle_get_32();
#else
#error "Unknown platform, valid platforms are {UPM_PLATFORM_ZEPHYR, UPM_PLATFORM_LINUX}"
#endif
return clock;
}
uint32_t upm_elapsed_us(upm_clock_t *clock)
uint64_t upm_elapsed_ms(const upm_clock_t *clock)
{
assert((clock != NULL) && "upm_elapsed_ms, clock cannot be NULL");
upm_clock_t now = {0};
#if defined(UPM_PLATFORM_LINUX)
struct timeval elapsed, now;
uint32_t elapse;
// get current time
gettimeofday(&now, NULL);
struct timeval startTime = *clock;
// compute the delta since startTime
if( (elapsed.tv_usec = now.tv_usec - startTime.tv_usec) < 0 )
{
elapsed.tv_usec += 1000000;
elapsed.tv_sec = now.tv_sec - startTime.tv_sec - 1;
}
else
{
elapsed.tv_sec = now.tv_sec - startTime.tv_sec;
}
elapse = (uint32_t)((elapsed.tv_sec * 1000000) + elapsed.tv_usec);
// never return 0
if (elapse == 0)
elapse = 1;
return elapse;
clock_gettime(CLOCK_MONOTONIC, &now);
#elif defined(UPM_PLATFORM_ZEPHYR)
uint32_t now = sys_cycle_get_32();
uint32_t elapsed =
(uint32_t)(SYS_CLOCK_HW_CYCLES_TO_NS64(now - *clock)/(uint64_t)1000);
// never return 0
if (elapsed == 0)
elapsed = 1;
return elapsed;
now = sys_cycle_get_32();
#else
#error "Unknown platform, valid platforms are {UPM_PLATFORM_ZEPHYR, UPM_PLATFORM_LINUX}"
#endif
return _delta_ns(&now, clock)/1000000;
}
uint64_t upm_elapsed_us(const upm_clock_t *clock)
{
assert((clock != NULL) && "upm_elapsed_us, clock cannot be NULL");
upm_clock_t now = {0};
#if defined(UPM_PLATFORM_LINUX)
clock_gettime(CLOCK_MONOTONIC, &now);
#elif defined(UPM_PLATFORM_ZEPHYR)
now = sys_cycle_get_32();
#else
#error "Unknown platform, valid platforms are {UPM_PLATFORM_ZEPHYR, UPM_PLATFORM_LINUX}"
#endif
return _delta_ns(&now, clock)/1000;
}
uint64_t upm_elapsed_ns(const upm_clock_t *clock)
{
assert((clock != NULL) && "upm_elapsed_ns, clock cannot be NULL");
upm_clock_t now = {0};
#if defined(UPM_PLATFORM_LINUX)
clock_gettime(CLOCK_MONOTONIC, &now);
#elif defined(UPM_PLATFORM_ZEPHYR)
now = sys_cycle_get_32();
#else
#error "Unknown platform, valid platforms are {UPM_PLATFORM_ZEPHYR, UPM_PLATFORM_LINUX}"
#endif
return _delta_ns(&now, clock);
}
// https://www3.epa.gov/airnow/aqi-technical-assistance-document-may2016.pdf
static struct aqi {
float clow;
float chigh;
int llow;
int lhigh;
} aqi[] = {
{0.0, 12.0, 0, 50},
{12.1, 35.4, 51, 100},
{35.5, 55.4, 101, 150},
{55.5, 150.4, 151, 200},
{150.5, 250.4, 201, 300},
{250.5, 350.4, 301, 400},
{350.5, 500.4, 401, 500},
};
int upm_ugm3_to_aqi (double ugm3)
{
int i;

View File

@ -40,7 +40,7 @@ extern "C" {
#include <unistd.h>
#include <sys/time.h>
typedef struct timeval upm_clock_t;
typedef struct timespec upm_clock_t;
#endif /* UPM_PLATFORM_LINUX */
#if defined(UPM_PLATFORM_ZEPHYR)
@ -58,38 +58,55 @@ typedef struct timeval upm_clock_t;
#define PRINT printk
#endif
typedef uint32_t upm_clock_t;
typedef uint64_t upm_clock_t;
#endif /* UPM_PLATFORM_ZEPHYR */
/**
* Delay for a number of seconds
* Delay for a number of seconds (s)
*
* @param time The number of seconds to delay for
*/
void upm_delay(unsigned int time);
void upm_delay(uint32_t time);
/**
* Delay for a number of milliseconds
* Delay for a number of milliseconds (ms)
*
* @param time The number of milliseconds to delay for
*/
void upm_delay_ms(unsigned int time);
void upm_delay_ms(uint32_t time);
/**
* Delay for a number of microseconds
* Delay for a number of microseconds (us)
*
* @param time The number of microseconds to delay for
*/
void upm_delay_us(unsigned int time);
void upm_delay_us(uint32_t time);
/**
* Delay for a number of nanoseconds (ns)
*
* Note, sub-microsecond accurate time on *nix is generally not available OOB
* and high resolution times are also not supported on all HW architectures.
*
* @param time The number of nanoseconds to delay for
*/
void upm_delay_ns(uint64_t time);
/**
* Initialize a clock. This can be used with upm_elapsed_ms() and
* upm_elapsed_us() for measuring a duration.
*
* @param clock The upm_clock_t to initialize to the current time
* For *nix operating systems, this initializes a MONOTONIC clock.
*
* Example:
* upm_clock_t start = upm_clock_init();
* ... do stuff ...
* uint64_t delta_ns = upm_elapsed_us(&start);
*
* @return The upm_clock_t initialized to the current time
*/
void upm_clock_init(upm_clock_t *clock);
upm_clock_t upm_clock_init(void);
/**
* Return the elapsed time in milliseconds since upm_init_clock() was
@ -99,7 +116,7 @@ void upm_clock_init(upm_clock_t *clock);
* @return the number of milliseconds elapsed since upm_init_clock()
* was called on the clock parameter.
*/
uint32_t upm_elapsed_ms(upm_clock_t *clock);
uint64_t upm_elapsed_ms(const upm_clock_t *clock);
/**
* Return the elapsed time in microseconds since upm_init_clock() was
@ -109,7 +126,20 @@ uint32_t upm_elapsed_ms(upm_clock_t *clock);
* @return the number of microseconds elapsed since upm_init_clock()
* was called on the clock parameter.
*/
uint32_t upm_elapsed_us(upm_clock_t *clock);
uint64_t upm_elapsed_us(const upm_clock_t *clock);
/**
* Return the elapsed time in nanoseconds since upm_init_clock() was
* last called.
*
* Note, sub-microsecond accurate time on *nix is generally not available OOB
* and high resolution times are also not supported on all HW architectures.
*
* @param clock A upm_clock_t initialized by upm_init_clock()
* @return the number of nanoseconds elapsed since upm_init_clock()
* was called on the clock parameter.
*/
uint64_t upm_elapsed_ns(const upm_clock_t *clock);
/**
* Return the AQI (based on EPA standards) using the ugm3 value

View File

@ -88,7 +88,7 @@ void wfs_init_clock(const wfs_context dev)
{
assert(dev != NULL);
upm_clock_init(&dev->clock);
dev->clock = upm_clock_init();
}
uint32_t wfs_get_millis(const wfs_context dev)

View File

@ -131,7 +131,7 @@ int ZFM20::writeCmdPacket(uint8_t *pkt, int len)
void ZFM20::initClock()
{
upm_clock_init(&m_clock);
m_clock = upm_clock_init();
}
uint32_t ZFM20::getMillis()

View File

@ -22,11 +22,29 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <chrono>
#include <thread>
#include "gtest/gtest.h"
#include "upm_utilities.h"
#include "upm_utilities.hpp"
/* Average over AVG_CNT iterations */
#define AVG_CNT 5
/* Specify a delay for all tests under 1s */
#define ms_50 std::chrono::milliseconds(50)
/* Specify a +/- value for all tests under 1s. Since the delay methods in
* non-realtime operating systems can vary greatly, use a lenient value for
* testing these methods. */
#define time_range std::chrono::milliseconds(5)
/* Helper defines */
#define to_ms std::chrono::duration_cast<std::chrono::milliseconds>
#define to_us std::chrono::duration_cast<std::chrono::microseconds>
#define to_ns std::chrono::duration_cast<std::chrono::nanoseconds>
/* Utilities test fixture */
class utilities_unit : public ::testing::Test
{
@ -44,44 +62,107 @@ class utilities_unit : public ::testing::Test
virtual void TearDown() {}
};
/* Sanity check on min_delay_ns */
TEST_F(utilities_unit, min_delay_LT_500us)
{
/* Determine a rough-average for the minimum delay using chrono */
std::chrono::nanoseconds min_delay_ns = std::chrono::nanoseconds::zero();
for (int i = 0; i < AVG_CNT; i++)
{
auto start = std::chrono::steady_clock::now();
std::this_thread::sleep_for(std::chrono::nanoseconds(1));
auto end = std::chrono::steady_clock::now();
min_delay_ns += to_ns(end-start);
}
min_delay_ns /= AVG_CNT;
ASSERT_LT(to_us(min_delay_ns).count(), 500);
}
/* Test the second delay method */
TEST_F(utilities_unit, test_upm_delay)
{
upm_clock_t clock;
upm_clock_init(&clock);
upm_clock_t clock = upm_clock_init();
/* Test a corner case */
upm_delay(0);
/* +- check for 0s */
EXPECT_EQ(upm_elapsed_ms(&clock), 0);
clock = upm_clock_init();
upm_delay(1);
/* +- check for 1s +/- 1ms */
ASSERT_NEAR(upm_elapsed_ms(&clock), 1000, 1);
/* +- check near 1s */
EXPECT_NEAR(upm_elapsed_ms(&clock), 1000, time_range.count());
}
/* Test the millisecond delay method */
TEST_F(utilities_unit, test_upm_delay_ms)
{
upm_clock_t clock;
upm_clock_init(&clock);
upm_clock_t clock = upm_clock_init();
/* Test a corner case */
upm_delay_ms(0);
upm_delay_ms(50);
/* +- check for 50ms +/- 1ms */
ASSERT_NEAR(upm_elapsed_ms(&clock), 50, 1);
/* +- check for 0ms */
EXPECT_EQ(upm_elapsed_ms(&clock), 0);
clock = upm_clock_init();
upm_delay_ms(ms_50.count() * AVG_CNT);
/* +- check near 50ms */
EXPECT_NEAR(upm_elapsed_ms(&clock)/AVG_CNT, ms_50.count(), time_range.count());
}
/* Test the microsecond delay method */
TEST_F(utilities_unit, test_upm_delay_us)
{
upm_clock_t clock;
upm_clock_init(&clock);
upm_clock_t clock = upm_clock_init();
/* Test a corner case */
upm_delay_us(0);
upm_delay_us(1000);
/* +- check for 1000us +/- 150us */
ASSERT_NEAR(upm_elapsed_us(&clock), 1000, 150);
/* +- check for 0us +/- 100us */
EXPECT_NEAR(upm_elapsed_us(&clock), 0, 100);
clock = upm_clock_init();
upm_delay_us(to_us(ms_50).count() * AVG_CNT);
/* +- check near 50ms */
EXPECT_NEAR(upm_elapsed_us(&clock)/AVG_CNT, to_us(ms_50).count(),
to_us(time_range).count());
}
/* Test the nanosecond delay method */
TEST_F(utilities_unit, test_upm_delay_ns)
{
upm_clock_t clock = upm_clock_init();
/* Test a corner case */
upm_delay_ns(0);
/* +- check for 0us +/- 100us */
EXPECT_NEAR(upm_elapsed_ns(&clock), 0, 100000);
clock = upm_clock_init();
upm_delay_ns(to_ns(ms_50).count() * AVG_CNT);
/* +- check near 50ms */
EXPECT_NEAR(upm_elapsed_ns(&clock)/AVG_CNT, to_ns(ms_50).count(),
to_ns(time_range).count());
}
/* Test the max us delay (default to disabled) */
TEST_F(utilities_unit, DISABLED_test_upm_delay_us_max)
{
upm_clock_t clock = upm_clock_init();
upm_delay_us(4294967295);
EXPECT_NEAR(upm_elapsed_us(&clock), 4294967295, 150);
}
/* Test the Air Quality Index method */
TEST_F(utilities_unit, test_upm_ugm3_to_aqi)
{
EXPECT_EQ(upm_ugm3_to_aqi(10), 41);
}