Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

2111 lines (1777 sloc) 57.822 kb
/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1999-2015. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
/*
* Support routines for the time
*/
/* #define ERTS_TIME_CORRECTION_PRINT */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sys.h"
#include "erl_vm.h"
#include "global.h"
static erts_smp_mtx_t erts_timeofday_mtx;
static erts_smp_mtx_t erts_get_time_mtx;
static SysTimes t_start; /* Used in elapsed_time_both */
static ErtsMonotonicTime prev_wall_clock_elapsed; /* Used in wall_clock_elapsed_time_both */
static ErtsMonotonicTime previous_now; /* Used in get_now */
static ErtsMonitor *time_offset_monitors = NULL;
static Uint no_time_offset_monitors = 0;
#ifdef DEBUG
static int time_sup_initialized = 0;
#endif
#define ERTS_MONOTONIC_TIME_KILO \
((ErtsMonotonicTime) 1000)
#define ERTS_MONOTONIC_TIME_MEGA \
(ERTS_MONOTONIC_TIME_KILO*ERTS_MONOTONIC_TIME_KILO)
#define ERTS_MONOTONIC_TIME_GIGA \
(ERTS_MONOTONIC_TIME_MEGA*ERTS_MONOTONIC_TIME_KILO)
#define ERTS_MONOTONIC_TIME_TERA \
(ERTS_MONOTONIC_TIME_GIGA*ERTS_MONOTONIC_TIME_KILO)
static void
schedule_send_time_offset_changed_notifications(ErtsMonotonicTime new_offset);
/*
* NOTE! ERTS_MONOTONIC_TIME_START *need* to be a multiple
* of ERTS_MONOTONIC_TIME_UNIT.
*/
#if ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT
#ifdef ARCH_32
/*
* Want to use a big-num of arity 2 as long as possible (584 years
* in the nano-second time unit case).
*/
#define ERTS_MONOTONIC_TIME_START \
(((((((ErtsMonotonicTime) 1) << 32)-1) \
/ ERTS_MONOTONIC_TIME_UNIT) \
* ERTS_MONOTONIC_TIME_UNIT) \
+ ERTS_MONOTONIC_TIME_UNIT)
#else /* ARCH_64 */
#if ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT <= 10*1000*1000
/*
* Using micro second time unit or lower. Start at zero since
* time will remain an immediate for a very long time anyway
* (1827 years in the 10 micro second case)...
*/
#define ERTS_MONOTONIC_TIME_START ((ErtsMonotonicTime) 0)
#else /* ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT > 1000*1000 */
/*
* Want to use an immediate as long as possible (36 years in the
* nano-second time unit case).
*/
#define ERTS_MONOTONIC_TIME_START \
((((ErtsMonotonicTime) MIN_SMALL) \
/ ERTS_MONOTONIC_TIME_UNIT) \
* ERTS_MONOTONIC_TIME_UNIT)
#endif /* ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT > 1000*1000 */
#endif /* ARCH_64 */
#define ERTS_MONOTONIC_OFFSET_NATIVE \
(ERTS_MONOTONIC_TIME_START - ERTS_MONOTONIC_TIME_UNIT)
#define ERTS_MONOTONIC_OFFSET_NSEC \
ERTS_MONOTONIC_TO_NSEC__(ERTS_MONOTONIC_OFFSET_NATIVE)
#define ERTS_MONOTONIC_OFFSET_USEC \
ERTS_MONOTONIC_TO_USEC__(ERTS_MONOTONIC_OFFSET_NATIVE)
#define ERTS_MONOTONIC_OFFSET_MSEC \
ERTS_MONOTONIC_TO_MSEC__(ERTS_MONOTONIC_OFFSET_NATIVE)
#define ERTS_MONOTONIC_OFFSET_SEC \
ERTS_MONOTONIC_TO_SEC__(ERTS_MONOTONIC_OFFSET_NATIVE)
#else /* ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT */
/*
* Initialized in erts_init_time_sup()...
*/
#define ERTS_MONOTONIC_TIME_START (time_sup.r.o.start)
#define ERTS_MONOTONIC_OFFSET_NATIVE (time_sup.r.o.start_offset.native)
#define ERTS_MONOTONIC_OFFSET_NSEC (time_sup.r.o.start_offset.nsec)
#define ERTS_MONOTONIC_OFFSET_USEC (time_sup.r.o.start_offset.usec)
#define ERTS_MONOTONIC_OFFSET_MSEC (time_sup.r.o.start_offset.msec)
#define ERTS_MONOTONIC_OFFSET_SEC (time_sup.r.o.start_offset.sec)
#endif /* ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT */
struct time_sup_read_only__ {
ErtsMonotonicTime (*get_time)(void);
int correction;
ErtsTimeWarpMode warp_mode;
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
ErtsMonotonicTime moffset;
int os_monotonic_time_disable;
char *os_monotonic_time_func;
char *os_monotonic_time_clock_id;
int os_monotonic_time_locked;
Uint64 os_monotonic_time_resolution;
Uint64 os_monotonic_time_extended;
#endif
char *os_system_time_func;
char *os_system_time_clock_id;
int os_system_time_locked;
Uint64 os_system_time_resolution;
Uint64 os_system_time_extended;
#if !ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT
ErtsMonotonicTime start;
struct {
ErtsMonotonicTime native;
ErtsMonotonicTime nsec;
ErtsMonotonicTime usec;
ErtsMonotonicTime msec;
ErtsMonotonicTime sec;
} start_offset;
#endif
struct {
ErtsMonotonicTime large_diff;
ErtsMonotonicTime small_diff;
} adj;
};
typedef struct {
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
ErtsMonotonicTime drift; /* Correction for os monotonic drift */
#endif
ErtsMonotonicTime error; /* Correction for error between system times */
} ErtsMonotonicCorrection;
typedef struct {
ErtsMonotonicTime erl_mtime;
ErtsMonotonicTime os_mtime;
ErtsMonotonicCorrection correction;
} ErtsMonotonicCorrectionInstance;
#define ERTS_DRIFT_INTERVALS 5
typedef struct {
struct {
struct {
ErtsMonotonicTime sys;
ErtsMonotonicTime mon;
} diff;
struct {
ErtsMonotonicTime sys;
ErtsMonotonicTime mon;
} time;
} intervals[ERTS_DRIFT_INTERVALS];
struct {
ErtsMonotonicTime sys;
ErtsMonotonicTime mon;
} acc;
int ix;
int dirty_counter;
} ErtsMonotonicDriftData;
typedef struct {
ErtsMonotonicCorrectionInstance prev;
ErtsMonotonicCorrectionInstance curr;
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
ErtsMonotonicDriftData drift;
#endif
ErtsMonotonicTime last_check;
int short_check_interval;
} ErtsMonotonicCorrectionData;
struct time_sup_infrequently_changed__ {
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
struct {
erts_smp_rwmtx_t rwmtx;
ErlTimer timer;
ErtsMonotonicCorrectionData cdata;
} parmon;
ErtsMonotonicTime minit;
#endif
int finalized_offset;
ErtsSystemTime sinit;
ErtsMonotonicTime not_corrected_moffset;
erts_atomic64_t offset;
};
struct time_sup_frequently_changed__ {
ErtsMonotonicTime last_not_corrected_time;
};
static struct {
union {
struct time_sup_read_only__ o;
char align__[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(struct time_sup_read_only__))];
} r;
union {
struct time_sup_infrequently_changed__ c;
char align__[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(struct time_sup_infrequently_changed__))];
} inf;
union {
struct time_sup_frequently_changed__ c;
char align__[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(struct time_sup_frequently_changed__))];
} f;
} time_sup erts_align_attribute(ERTS_CACHE_LINE_SIZE);
ErtsTimeSupData erts_time_sup__ erts_align_attribute(ERTS_CACHE_LINE_SIZE);
/*
* erts_get_approx_time() returns an *approximate* time
* in seconds. NOTE that this time may jump backwards!!!
*/
erts_approx_time_t
erts_get_approx_time(void)
{
ErtsSystemTime stime = erts_os_system_time();
return (erts_approx_time_t) ERTS_MONOTONIC_TO_SEC(stime);
}
static ERTS_INLINE void
init_time_offset(ErtsMonotonicTime offset)
{
erts_atomic64_init_nob(&time_sup.inf.c.offset, (erts_aint64_t) offset);
}
static ERTS_INLINE void
set_time_offset(ErtsMonotonicTime offset)
{
erts_atomic64_set_relb(&time_sup.inf.c.offset, (erts_aint64_t) offset);
}
static ERTS_INLINE ErtsMonotonicTime
get_time_offset(void)
{
return (ErtsMonotonicTime) erts_atomic64_read_acqb(&time_sup.inf.c.offset);
}
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
/*
* Time correction adjustments made due to
* error between Erlang system time and OS
* system time:
* - Large adjustment ~1%
* - Small adjustment ~0.05%
*/
#define ERTS_TCORR_ERR_UNIT 2048
#define ERTS_TCORR_ERR_LARGE_ADJ 20
#define ERTS_TCORR_ERR_SMALL_ADJ 1
#define ERTS_INIT_SHORT_INTERVAL_COUNTER 10
#define ERTS_LONG_TIME_CORRECTION_CHECK ERTS_SEC_TO_MONOTONIC(60)
#define ERTS_SHORT_TIME_CORRECTION_CHECK ERTS_SEC_TO_MONOTONIC(15)
#define ERTS_TIME_DRIFT_MAX_ADJ_DIFF ERTS_USEC_TO_MONOTONIC(50)
#define ERTS_TIME_DRIFT_MIN_ADJ_DIFF ERTS_USEC_TO_MONOTONIC(5)
static ERTS_INLINE ErtsMonotonicTime
calc_corrected_erl_mtime(ErtsMonotonicTime os_mtime,
ErtsMonotonicCorrectionInstance *cip,
ErtsMonotonicTime *os_mdiff_p)
{
ErtsMonotonicTime erl_mtime, diff = os_mtime - cip->os_mtime;
ERTS_TIME_ASSERT(diff >= 0);
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
diff += (cip->correction.drift*diff)/ERTS_MONOTONIC_TIME_UNIT;
#endif
erl_mtime = cip->erl_mtime;
erl_mtime += diff;
erl_mtime += cip->correction.error*(diff/ERTS_TCORR_ERR_UNIT);
if (os_mdiff_p)
*os_mdiff_p = diff;
return erl_mtime;
}
static ErtsMonotonicTime get_corrected_time(void)
{
ErtsMonotonicTime os_mtime;
ErtsMonotonicCorrectionData cdata;
ErtsMonotonicCorrectionInstance *cip;
erts_smp_rwmtx_rlock(&time_sup.inf.c.parmon.rwmtx);
os_mtime = erts_os_monotonic_time();
cdata = time_sup.inf.c.parmon.cdata;
erts_smp_rwmtx_runlock(&time_sup.inf.c.parmon.rwmtx);
if (os_mtime >= cdata.curr.os_mtime)
cip = &cdata.curr;
else {
if (os_mtime < cdata.prev.os_mtime)
erl_exit(ERTS_ABORT_EXIT,
"OS monotonic time stepped backwards\n");
cip = &cdata.prev;
}
return calc_corrected_erl_mtime(os_mtime, cip, NULL);
}
#ifdef ERTS_TIME_CORRECTION_PRINT
static ERTS_INLINE void
print_correction(int change,
ErtsMonotonicTime sdiff,
ErtsMonotonicTime old_ecorr,
ErtsMonotonicTime old_dcorr,
ErtsMonotonicTime new_ecorr,
ErtsMonotonicTime new_dcorr,
Uint tmo)
{
ErtsMonotonicTime usec_sdiff;
if (sdiff < 0)
usec_sdiff = -1*ERTS_MONOTONIC_TO_USEC(-1*sdiff);
else
usec_sdiff = ERTS_MONOTONIC_TO_USEC(sdiff);
if (!change)
fprintf(stderr,
"sdiff = %lld usec : [ec=%lld ppm, dc=%lld ppb] : "
"tmo = %lld msec\r\n",
(long long) usec_sdiff,
(long long) (1000000*old_ecorr) / ERTS_TCORR_ERR_UNIT,
(long long) (1000000000*old_dcorr) / ERTS_MONOTONIC_TIME_UNIT,
(long long) tmo);
else
fprintf(stderr,
"sdiff = %lld usec : [ec=%lld ppm, dc=%lld ppb] "
"-> [ec=%lld ppm, dc=%lld ppb] : tmo = %lld msec\r\n",
(long long) usec_sdiff,
(long long) (1000000*old_ecorr) / ERTS_TCORR_ERR_UNIT,
(long long) (1000000000*old_dcorr) / ERTS_MONOTONIC_TIME_UNIT,
(long long) (1000000*new_ecorr) / ERTS_TCORR_ERR_UNIT,
(long long) (1000000000*new_dcorr) / ERTS_MONOTONIC_TIME_UNIT,
(long long) tmo);
}
#endif
static void
check_time_correction(void *unused)
{
#ifndef ERTS_TIME_CORRECTION_PRINT
# define ERTS_PRINT_CORRECTION
#else
# ifdef ERTS_HAVE_CORRECTED_OS_MONOTONIC
# define ERTS_PRINT_CORRECTION \
print_correction(set_new_correction, \
sdiff, \
cip->correction.error, \
0, \
new_correction.error, \
0, \
timeout)
# else
# define ERTS_PRINT_CORRECTION \
print_correction(set_new_correction, \
sdiff, \
cip->correction.error, \
cip->correction.drift, \
new_correction.error, \
new_correction.drift, \
timeout)
# endif
#endif
ErtsMonotonicCorrectionData cdata;
ErtsMonotonicCorrection new_correction;
ErtsMonotonicCorrectionInstance *cip;
ErtsMonotonicTime mdiff, sdiff, os_mtime, erl_mtime, os_stime,
erl_stime, time_offset;
Uint timeout;
int set_new_correction, begin_short_intervals = 0;
erts_smp_rwmtx_rlock(&time_sup.inf.c.parmon.rwmtx);
ASSERT(time_sup.inf.c.finalized_offset);
erts_os_times(&os_mtime, &os_stime);
cdata = time_sup.inf.c.parmon.cdata;
erts_smp_rwmtx_runlock(&time_sup.inf.c.parmon.rwmtx);
if (os_mtime < cdata.curr.os_mtime)
erl_exit(ERTS_ABORT_EXIT,
"OS monotonic time stepped backwards\n");
cip = &cdata.curr;
erl_mtime = calc_corrected_erl_mtime(os_mtime, cip, &mdiff);
time_offset = get_time_offset();
erl_stime = erl_mtime + time_offset;
sdiff = erl_stime - os_stime;
new_correction = cip->correction;
if (time_sup.r.o.warp_mode == ERTS_MULTI_TIME_WARP_MODE
&& (sdiff < -2*time_sup.r.o.adj.small_diff
|| 2*time_sup.r.o.adj.small_diff < sdiff)) {
/* System time diff exeeded limits; change time offset... */
time_offset -= sdiff;
sdiff = 0;
set_time_offset(time_offset);
schedule_send_time_offset_changed_notifications(time_offset);
begin_short_intervals = 1;
if (cdata.curr.correction.error == 0)
set_new_correction = 0;
else {
set_new_correction = 1;
new_correction.error = 0;
}
}
else if (cdata.curr.correction.error == 0) {
if (sdiff < -time_sup.r.o.adj.small_diff) {
set_new_correction = 1;
if (sdiff < -time_sup.r.o.adj.large_diff)
new_correction.error = ERTS_TCORR_ERR_LARGE_ADJ;
else
new_correction.error = ERTS_TCORR_ERR_SMALL_ADJ;
}
else if (sdiff > time_sup.r.o.adj.small_diff) {
set_new_correction = 1;
if (sdiff > time_sup.r.o.adj.large_diff)
new_correction.error = -ERTS_TCORR_ERR_LARGE_ADJ;
else
new_correction.error = -ERTS_TCORR_ERR_SMALL_ADJ;
}
else {
set_new_correction = 0;
}
}
else if (cdata.curr.correction.error > 0) {
if (sdiff < 0) {
if (cdata.curr.correction.error == ERTS_TCORR_ERR_LARGE_ADJ
|| -time_sup.r.o.adj.large_diff <= sdiff)
set_new_correction = 0;
else {
new_correction.error = ERTS_TCORR_ERR_LARGE_ADJ;
set_new_correction = 1;
}
}
else if (sdiff > time_sup.r.o.adj.small_diff) {
set_new_correction = 1;
if (sdiff > time_sup.r.o.adj.large_diff)
new_correction.error = -ERTS_TCORR_ERR_LARGE_ADJ;
else
new_correction.error = -ERTS_TCORR_ERR_SMALL_ADJ;
}
else {
set_new_correction = 1;
new_correction.error = 0;
}
}
else /* if (cdata.curr.correction.error < 0) */ {
if (0 < sdiff) {
if (cdata.curr.correction.error == -ERTS_TCORR_ERR_LARGE_ADJ
|| sdiff <= time_sup.r.o.adj.large_diff)
set_new_correction = 0;
else {
new_correction.error = -ERTS_TCORR_ERR_LARGE_ADJ;
set_new_correction = 1;
}
set_new_correction = 0;
}
else if (sdiff < -time_sup.r.o.adj.small_diff) {
set_new_correction = 1;
if (sdiff < -time_sup.r.o.adj.large_diff)
new_correction.error = ERTS_TCORR_ERR_LARGE_ADJ;
else
new_correction.error = ERTS_TCORR_ERR_SMALL_ADJ;
}
else {
set_new_correction = 1;
new_correction.error = 0;
}
}
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
{
ErtsMonotonicDriftData *ddp = &time_sup.inf.c.parmon.cdata.drift;
int ix = ddp->ix;
ErtsMonotonicTime mtime_diff, old_os_mtime;
old_os_mtime = ddp->intervals[ix].time.mon;
mtime_diff = os_mtime - old_os_mtime;
if (mtime_diff >= ERTS_SEC_TO_MONOTONIC(10)) {
ErtsMonotonicTime drift_adj, drift_adj_diff, old_os_stime,
stime_diff, mtime_acc, stime_acc, avg_drift_adj;
old_os_stime = ddp->intervals[ix].time.sys;
mtime_acc = ddp->acc.mon;
stime_acc = ddp->acc.sys;
avg_drift_adj = (((stime_acc - mtime_acc)*ERTS_MONOTONIC_TIME_UNIT)
/ mtime_acc);
mtime_diff = os_mtime - old_os_mtime;
stime_diff = os_stime - old_os_stime;
drift_adj = (((stime_diff - mtime_diff)*ERTS_MONOTONIC_TIME_UNIT)
/ mtime_diff);
ix++;
if (ix >= ERTS_DRIFT_INTERVALS)
ix = 0;
mtime_acc -= ddp->intervals[ix].diff.mon;
mtime_acc += mtime_diff;
stime_acc -= ddp->intervals[ix].diff.sys;
stime_acc += stime_diff;
ddp->intervals[ix].diff.mon = mtime_diff;
ddp->intervals[ix].diff.sys = stime_diff;
ddp->intervals[ix].time.mon = os_mtime;
ddp->intervals[ix].time.sys = os_stime;
ddp->ix = ix;
ddp->acc.mon = mtime_acc;
ddp->acc.sys = stime_acc;
drift_adj_diff = avg_drift_adj - drift_adj;
if (drift_adj_diff < -ERTS_TIME_DRIFT_MAX_ADJ_DIFF
|| ERTS_TIME_DRIFT_MAX_ADJ_DIFF < drift_adj_diff) {
ddp->dirty_counter = ERTS_DRIFT_INTERVALS;
begin_short_intervals = 1;
}
else {
if (ddp->dirty_counter <= 0) {
drift_adj = ((stime_acc - mtime_acc)
*ERTS_MONOTONIC_TIME_UNIT) / mtime_acc;
}
if (ddp->dirty_counter >= 0) {
if (ddp->dirty_counter == 0) {
/* Force set new drift correction... */
set_new_correction = 1;
}
ddp->dirty_counter--;
}
drift_adj_diff = drift_adj - new_correction.drift;
if (drift_adj_diff) {
if (drift_adj_diff > ERTS_TIME_DRIFT_MAX_ADJ_DIFF)
drift_adj_diff = ERTS_TIME_DRIFT_MAX_ADJ_DIFF;
else if (drift_adj_diff < -ERTS_TIME_DRIFT_MAX_ADJ_DIFF)
drift_adj_diff = -ERTS_TIME_DRIFT_MAX_ADJ_DIFF;
new_correction.drift += drift_adj_diff;
if (drift_adj_diff < -ERTS_TIME_DRIFT_MIN_ADJ_DIFF
|| ERTS_TIME_DRIFT_MIN_ADJ_DIFF < drift_adj_diff) {
set_new_correction = 1;
}
}
}
}
}
#endif
begin_short_intervals |= set_new_correction;
if (begin_short_intervals) {
time_sup.inf.c.parmon.cdata.short_check_interval
= ERTS_INIT_SHORT_INTERVAL_COUNTER;
}
else if ((os_mtime - time_sup.inf.c.parmon.cdata.last_check
>= ERTS_SHORT_TIME_CORRECTION_CHECK - ERTS_MONOTONIC_TIME_UNIT)
&& time_sup.inf.c.parmon.cdata.short_check_interval > 0) {
time_sup.inf.c.parmon.cdata.short_check_interval--;
}
time_sup.inf.c.parmon.cdata.last_check = os_mtime;
if (new_correction.error == 0)
timeout = ERTS_MONOTONIC_TO_MSEC(ERTS_LONG_TIME_CORRECTION_CHECK);
else {
ErtsMonotonicTime ecorr = new_correction.error;
if (sdiff < 0)
sdiff = -1*sdiff;
if (ecorr < 0)
ecorr = -1*ecorr;
if (sdiff > ecorr*(ERTS_LONG_TIME_CORRECTION_CHECK/ERTS_TCORR_ERR_UNIT))
timeout = ERTS_MONOTONIC_TO_MSEC(ERTS_LONG_TIME_CORRECTION_CHECK);
else {
timeout = ERTS_MONOTONIC_TO_MSEC((ERTS_TCORR_ERR_UNIT*sdiff)/ecorr);
if (timeout < 10)
timeout = 10;
}
}
if (timeout > ERTS_MONOTONIC_TO_MSEC(ERTS_SHORT_TIME_CORRECTION_CHECK)
&& time_sup.inf.c.parmon.cdata.short_check_interval) {
timeout = ERTS_MONOTONIC_TO_MSEC(ERTS_SHORT_TIME_CORRECTION_CHECK);
}
ERTS_PRINT_CORRECTION;
if (set_new_correction) {
erts_smp_rwmtx_rwlock(&time_sup.inf.c.parmon.rwmtx);
os_mtime = erts_os_monotonic_time();
/* Save previous correction instance */
time_sup.inf.c.parmon.cdata.prev = *cip;
/*
* Current correction instance begin when
* OS monotonic time has increased one unit.
*/
os_mtime++;
/*
* Erlang monotonic time corresponding to
* next OS monotonic time using previous
* correction.
*/
erl_mtime = calc_corrected_erl_mtime(os_mtime, cip, NULL);
/*
* Save new current correction instance.
*/
time_sup.inf.c.parmon.cdata.curr.erl_mtime = erl_mtime;
time_sup.inf.c.parmon.cdata.curr.os_mtime = os_mtime;
time_sup.inf.c.parmon.cdata.curr.correction = new_correction;
erts_smp_rwmtx_rwunlock(&time_sup.inf.c.parmon.rwmtx);
}
erts_set_timer(&time_sup.inf.c.parmon.timer,
check_time_correction,
NULL,
NULL,
timeout);
#undef ERTS_PRINT_CORRECTION
}
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
static void
init_check_time_correction(void *unused)
{
ErtsMonotonicDriftData *ddp;
ErtsMonotonicTime old_mtime, old_stime, mtime, stime, mtime_diff,
stime_diff;
int ix;
ddp = &time_sup.inf.c.parmon.cdata.drift;
ix = ddp->ix;
old_mtime = ddp->intervals[0].time.mon;
old_stime = ddp->intervals[0].time.sys;
erts_os_times(&mtime, &stime);
mtime_diff = mtime - old_mtime;
stime_diff = stime - old_stime;
if (100*stime_diff < 80*mtime_diff || 120*mtime_diff < 100*stime_diff ) {
/* Had a system time leap... pretend no drift... */
stime_diff = mtime_diff;
}
/*
* We use old time values in order to trigger
* a drift adjustment, and repeat this interval
* in all slots...
*/
for (ix = 0; ix < ERTS_DRIFT_INTERVALS; ix++) {
ddp->intervals[ix].diff.mon = mtime_diff;
ddp->intervals[ix].diff.sys = stime_diff;
ddp->intervals[ix].time.mon = old_mtime;
ddp->intervals[ix].time.sys = old_stime;
}
ddp->acc.sys = stime_diff*ERTS_DRIFT_INTERVALS;
ddp->acc.mon = mtime_diff*ERTS_DRIFT_INTERVALS;
ddp->ix = 0;
ddp->dirty_counter = ERTS_DRIFT_INTERVALS;
check_time_correction(NULL);
}
#endif
static ErtsMonotonicTime
finalize_corrected_time_offset(ErtsSystemTime *stimep)
{
ErtsMonotonicTime os_mtime;
ErtsMonotonicCorrectionData cdata;
ErtsMonotonicCorrectionInstance *cip;
erts_smp_rwmtx_rlock(&time_sup.inf.c.parmon.rwmtx);
erts_os_times(&os_mtime, stimep);
cdata = time_sup.inf.c.parmon.cdata;
erts_smp_rwmtx_runlock(&time_sup.inf.c.parmon.rwmtx);
if (os_mtime < cdata.curr.os_mtime)
erl_exit(ERTS_ABORT_EXIT,
"OS monotonic time stepped backwards\n");
cip = &cdata.curr;
return calc_corrected_erl_mtime(os_mtime, cip, NULL);
}
static void
late_init_time_correction(void)
{
if (time_sup.inf.c.finalized_offset) {
erts_init_timer(&time_sup.inf.c.parmon.timer);
erts_set_timer(&time_sup.inf.c.parmon.timer,
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
init_check_time_correction,
#else
check_time_correction,
#endif
NULL,
NULL,
ERTS_MONOTONIC_TO_MSEC(ERTS_SHORT_TIME_CORRECTION_CHECK));
}
}
#endif /* ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT */
static ErtsMonotonicTime get_not_corrected_time(void)
{
ErtsMonotonicTime stime, mtime;
erts_smp_mtx_lock(&erts_get_time_mtx);
stime = erts_os_system_time();
mtime = stime - time_sup.inf.c.not_corrected_moffset;
if (mtime >= time_sup.f.c.last_not_corrected_time)
time_sup.f.c.last_not_corrected_time = mtime;
else {
mtime = time_sup.f.c.last_not_corrected_time;
if (time_sup.r.o.warp_mode == ERTS_MULTI_TIME_WARP_MODE) {
ErtsMonotonicTime new_offset = stime - mtime;
new_offset = ERTS_MONOTONIC_TO_USEC(new_offset);
new_offset = ERTS_USEC_TO_MONOTONIC(new_offset);
if (time_sup.inf.c.not_corrected_moffset != new_offset) {
time_sup.inf.c.not_corrected_moffset = new_offset;
set_time_offset(new_offset);
schedule_send_time_offset_changed_notifications(new_offset);
}
}
}
ASSERT(stime == mtime + time_sup.inf.c.not_corrected_moffset);
erts_smp_mtx_unlock(&erts_get_time_mtx);
return mtime;
}
int erts_check_time_adj_support(int time_correction,
ErtsTimeWarpMode time_warp_mode)
{
if (!time_correction)
return 1;
/* User wants time correction */
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
return !time_sup.r.o.os_monotonic_time_disable;
#else
return 0;
#endif
}
int
erts_has_time_correction(void)
{
return time_sup.r.o.correction;
}
void erts_init_sys_time_sup(void)
{
ErtsSysInitTimeResult sys_init_time_res
= ERTS_SYS_INIT_TIME_RESULT_INITER;
sys_init_time(&sys_init_time_res);
erts_time_sup__.r.o.monotonic_time_unit
= sys_init_time_res.os_monotonic_time_unit;
#ifndef SYS_CLOCK_RESOLUTION
erts_time_sup__.r.o.clktck_resolution
= sys_init_time_res.sys_clock_resolution;
erts_time_sup__.r.o.clktck_resolution *= 1000;
#endif
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
time_sup.r.o.os_monotonic_time_disable
= !sys_init_time_res.have_os_monotonic_time;
time_sup.r.o.os_monotonic_time_func
= sys_init_time_res.os_monotonic_time_info.func;
time_sup.r.o.os_monotonic_time_clock_id
= sys_init_time_res.os_monotonic_time_info.clock_id;
time_sup.r.o.os_monotonic_time_locked
= sys_init_time_res.os_monotonic_time_info.locked_use;
time_sup.r.o.os_monotonic_time_resolution
= sys_init_time_res.os_monotonic_time_info.resolution;
time_sup.r.o.os_monotonic_time_extended
= sys_init_time_res.os_monotonic_time_info.extended;
#endif
time_sup.r.o.os_system_time_func
= sys_init_time_res.os_system_time_info.func;
time_sup.r.o.os_system_time_clock_id
= sys_init_time_res.os_system_time_info.clock_id;
time_sup.r.o.os_system_time_locked
= sys_init_time_res.os_system_time_info.locked_use;
time_sup.r.o.os_system_time_resolution
= sys_init_time_res.os_system_time_info.resolution;
}
int
erts_init_time_sup(int time_correction, ErtsTimeWarpMode time_warp_mode)
{
ErtsMonotonicTime resolution;
#if !ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT
ErtsMonotonicTime abs_native_offset, native_offset;
#endif
ASSERT(ERTS_MONOTONIC_TIME_MIN < ERTS_MONOTONIC_TIME_MAX);
erts_smp_mtx_init(&erts_timeofday_mtx, "timeofday");
erts_smp_mtx_init(&erts_get_time_mtx, "get_time");
time_sup.r.o.correction = time_correction;
time_sup.r.o.warp_mode = time_warp_mode;
if (time_warp_mode == ERTS_SINGLE_TIME_WARP_MODE)
time_sup.inf.c.finalized_offset = 0;
else
time_sup.inf.c.finalized_offset = ~0;
#if !ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT
#ifdef ARCH_32
time_sup.r.o.start = ((((ErtsMonotonicTime) 1) << 32)-1);
time_sup.r.o.start /= ERTS_MONOTONIC_TIME_UNIT;
time_sup.r.o.start *= ERTS_MONOTONIC_TIME_UNIT;
time_sup.r.o.start += ERTS_MONOTONIC_TIME_UNIT;
native_offset = time_sup.r.o.start - ERTS_MONOTONIC_TIME_UNIT;
native_offset = native_offset;
#else /* ARCH_64 */
if (ERTS_MONOTONIC_TIME_UNIT <= 10*1000*1000) {
time_sup.r.o.start = 0;
native_offset = -ERTS_MONOTONIC_TIME_UNIT;
abs_native_offset = ERTS_MONOTONIC_TIME_UNIT;
}
else {
time_sup.r.o.start = ((ErtsMonotonicTime) MIN_SMALL);
time_sup.r.o.start /= ERTS_MONOTONIC_TIME_UNIT;
time_sup.r.o.start *= ERTS_MONOTONIC_TIME_UNIT;
native_offset = time_sup.r.o.start - ERTS_MONOTONIC_TIME_UNIT;
abs_native_offset = -1*native_offset;
}
#endif
time_sup.r.o.start_offset.native = (time_sup.r.o.start
- ERTS_MONOTONIC_TIME_UNIT);
time_sup.r.o.start_offset.nsec = (ErtsMonotonicTime)
erts_time_unit_conversion((Uint64) abs_native_offset,
(Uint32) ERTS_MONOTONIC_TIME_UNIT,
(Uint32) 1000*1000*1000);
time_sup.r.o.start_offset.usec = (ErtsMonotonicTime)
erts_time_unit_conversion((Uint64) abs_native_offset,
(Uint32) ERTS_MONOTONIC_TIME_UNIT,
(Uint32) 1000*1000);
time_sup.r.o.start_offset.msec = (ErtsMonotonicTime)
erts_time_unit_conversion((Uint64) abs_native_offset,
(Uint32) ERTS_MONOTONIC_TIME_UNIT,
(Uint32) 1000);
time_sup.r.o.start_offset.sec = (ErtsMonotonicTime)
erts_time_unit_conversion((Uint64) abs_native_offset,
(Uint32) ERTS_MONOTONIC_TIME_UNIT,
(Uint32) 1);
if (native_offset < 0) {
time_sup.r.o.start_offset.nsec *= -1;
time_sup.r.o.start_offset.usec *= -1;
time_sup.r.o.start_offset.msec *= -1;
time_sup.r.o.start_offset.sec *= -1;
}
#endif
resolution = time_sup.r.o.os_system_time_resolution;
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
if (resolution > time_sup.r.o.os_monotonic_time_resolution)
resolution = time_sup.r.o.os_monotonic_time_resolution;
#endif
time_sup.r.o.adj.large_diff = erts_time_sup__.r.o.monotonic_time_unit;
time_sup.r.o.adj.large_diff *= 50;
time_sup.r.o.adj.large_diff /= resolution;
if (time_sup.r.o.adj.large_diff < ERTS_USEC_TO_MONOTONIC(500))
time_sup.r.o.adj.large_diff = ERTS_USEC_TO_MONOTONIC(500);
time_sup.r.o.adj.small_diff = time_sup.r.o.adj.large_diff/10;
#ifdef ERTS_TIME_CORRECTION_PRINT
fprintf(stderr, "start = %lld\n\r", (long long) ERTS_MONOTONIC_TIME_START);
fprintf(stderr, "native offset = %lld\n\r", (long long) ERTS_MONOTONIC_OFFSET_NATIVE);
fprintf(stderr, "nsec offset = %lld\n\r", (long long) ERTS_MONOTONIC_OFFSET_NSEC);
fprintf(stderr, "usec offset = %lld\n\r", (long long) ERTS_MONOTONIC_OFFSET_USEC);
fprintf(stderr, "msec offset = %lld\n\r", (long long) ERTS_MONOTONIC_OFFSET_MSEC);
fprintf(stderr, "sec offset = %lld\n\r", (long long) ERTS_MONOTONIC_OFFSET_SEC);
fprintf(stderr, "large diff = %lld usec\r\n",
(long long) ERTS_MONOTONIC_TO_USEC(time_sup.r.o.adj.large_diff));
fprintf(stderr, "small diff = %lld usec\r\n",
(long long) ERTS_MONOTONIC_TO_USEC(time_sup.r.o.adj.small_diff));
#endif
if (ERTS_MONOTONIC_TIME_UNIT < ERTS_CLKTCK_RESOLUTION)
ERTS_INTERNAL_ERROR("Too small monotonic time time unit");
#ifndef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
time_sup.r.o.correction = 0;
#else
if (time_sup.r.o.os_monotonic_time_disable)
time_sup.r.o.correction = 0;
if (time_sup.r.o.correction) {
ErtsMonotonicCorrectionData *cdatap;
erts_smp_rwmtx_opt_t rwmtx_opts = ERTS_SMP_RWMTX_OPT_DEFAULT_INITER;
ErtsMonotonicTime offset;
erts_os_times(&time_sup.inf.c.minit,
&time_sup.inf.c.sinit);
time_sup.r.o.moffset = -1*time_sup.inf.c.minit;
offset = time_sup.inf.c.sinit;
offset -= ERTS_MONOTONIC_TIME_UNIT;
init_time_offset(offset);
rwmtx_opts.type = ERTS_SMP_RWMTX_TYPE_EXTREMELY_FREQUENT_READ;
rwmtx_opts.lived = ERTS_SMP_RWMTX_LONG_LIVED;
erts_smp_rwmtx_init_opt(&time_sup.inf.c.parmon.rwmtx,
&rwmtx_opts, "get_corrected_time");
cdatap = &time_sup.inf.c.parmon.cdata;
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
cdatap->drift.intervals[0].time.sys = time_sup.inf.c.sinit;
cdatap->drift.intervals[0].time.mon = time_sup.inf.c.minit;
cdatap->curr.correction.drift = 0;
#endif
cdatap->curr.correction.error = 0;
cdatap->curr.erl_mtime = ERTS_MONOTONIC_TIME_UNIT;
cdatap->curr.os_mtime = time_sup.inf.c.minit;
cdatap->last_check = time_sup.inf.c.minit;
cdatap->short_check_interval = ERTS_INIT_SHORT_INTERVAL_COUNTER;
cdatap->prev = cdatap->curr;
time_sup.r.o.get_time = get_corrected_time;
}
else
#endif
{
ErtsMonotonicTime stime, offset;
time_sup.r.o.get_time = get_not_corrected_time;
stime = time_sup.inf.c.sinit = erts_os_system_time();
offset = stime - ERTS_MONOTONIC_TIME_UNIT;
time_sup.inf.c.not_corrected_moffset = offset;
init_time_offset(offset);
time_sup.f.c.last_not_corrected_time = 0;
}
prev_wall_clock_elapsed = 0;
previous_now = ERTS_MONOTONIC_TO_USEC(get_time_offset());
#ifdef DEBUG
time_sup_initialized = 1;
#endif
return ERTS_CLKTCK_RESOLUTION/1000;
}
void
erts_late_init_time_sup(void)
{
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
/* Timer wheel must be initialized */
if (time_sup.r.o.get_time == get_corrected_time)
late_init_time_correction();
#endif
erts_late_sys_init_time();
}
ErtsTimeWarpMode erts_time_warp_mode(void)
{
return time_sup.r.o.warp_mode;
}
ErtsTimeOffsetState erts_time_offset_state(void)
{
switch (time_sup.r.o.warp_mode) {
case ERTS_NO_TIME_WARP_MODE:
return ERTS_TIME_OFFSET_FINAL;
case ERTS_SINGLE_TIME_WARP_MODE:
if (time_sup.inf.c.finalized_offset)
return ERTS_TIME_OFFSET_FINAL;
return ERTS_TIME_OFFSET_PRELIMINARY;
case ERTS_MULTI_TIME_WARP_MODE:
return ERTS_TIME_OFFSET_VOLATILE;
default:
ERTS_INTERNAL_ERROR("Invalid time warp mode");
return ERTS_TIME_OFFSET_VOLATILE;
}
}
/*
* erts_finalize_time_offset() will only change time offset
* the first time it is called when the emulator has been
* started in "single time warp" mode. Returns previous
* state:
* * ERTS_TIME_OFFSET_PRELIMINARY - Finalization performed
* * ERTS_TIME_OFFSET_FINAL - Already finialized; nothing changed
* * ERTS_TIME_OFFSET_VOLATILE - Not supported, either in
* * no correction mode (or multi time warp mode; not yet implemented).
*/
ErtsTimeOffsetState
erts_finalize_time_offset(void)
{
switch (time_sup.r.o.warp_mode) {
case ERTS_NO_TIME_WARP_MODE:
return ERTS_TIME_OFFSET_FINAL;
case ERTS_MULTI_TIME_WARP_MODE:
return ERTS_TIME_OFFSET_VOLATILE;
case ERTS_SINGLE_TIME_WARP_MODE: {
ErtsTimeOffsetState res = ERTS_TIME_OFFSET_FINAL;
erts_smp_mtx_lock(&erts_get_time_mtx);
if (!time_sup.inf.c.finalized_offset) {
ErtsMonotonicTime mtime, new_offset;
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
if (!time_sup.r.o.correction)
#endif
{
ErtsMonotonicTime stime = erts_os_system_time();
mtime = stime - time_sup.inf.c.not_corrected_moffset;
if (mtime >= time_sup.f.c.last_not_corrected_time) {
time_sup.f.c.last_not_corrected_time = mtime;
new_offset = time_sup.inf.c.not_corrected_moffset;
}
else {
mtime = time_sup.f.c.last_not_corrected_time;
ASSERT(time_sup.inf.c.not_corrected_moffset != stime - mtime);
new_offset = stime - mtime;
time_sup.inf.c.not_corrected_moffset = new_offset;
}
}
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
else {
ErtsSystemTime stime;
mtime = finalize_corrected_time_offset(&stime);
new_offset = stime - mtime;
}
#endif
new_offset = ERTS_MONOTONIC_TO_USEC(new_offset);
new_offset = ERTS_USEC_TO_MONOTONIC(new_offset);
set_time_offset(new_offset);
schedule_send_time_offset_changed_notifications(new_offset);
time_sup.inf.c.finalized_offset = ~0;
res = ERTS_TIME_OFFSET_PRELIMINARY;
}
erts_smp_mtx_unlock(&erts_get_time_mtx);
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
if (res == ERTS_TIME_OFFSET_PRELIMINARY
&& time_sup.r.o.get_time == get_corrected_time) {
late_init_time_correction();
}
#endif
return res;
}
default:
ERTS_INTERNAL_ERROR("Invalid time warp mode");
return ERTS_TIME_OFFSET_VOLATILE;
}
}
/* info functions */
void
elapsed_time_both(UWord *ms_user, UWord *ms_sys,
UWord *ms_user_diff, UWord *ms_sys_diff)
{
UWord prev_total_user, prev_total_sys;
UWord total_user, total_sys;
SysTimes now;
sys_times(&now);
total_user = (now.tms_utime * 1000) / SYS_CLK_TCK;
total_sys = (now.tms_stime * 1000) / SYS_CLK_TCK;
if (ms_user != NULL)
*ms_user = total_user;
if (ms_sys != NULL)
*ms_sys = total_sys;
erts_smp_mtx_lock(&erts_timeofday_mtx);
prev_total_user = (t_start.tms_utime * 1000) / SYS_CLK_TCK;
prev_total_sys = (t_start.tms_stime * 1000) / SYS_CLK_TCK;
t_start = now;
erts_smp_mtx_unlock(&erts_timeofday_mtx);
if (ms_user_diff != NULL)
*ms_user_diff = total_user - prev_total_user;
if (ms_sys_diff != NULL)
*ms_sys_diff = total_sys - prev_total_sys;
}
/* wall clock routines */
void
wall_clock_elapsed_time_both(UWord *ms_total, UWord *ms_diff)
{
ErtsMonotonicTime now, elapsed;
erts_smp_mtx_lock(&erts_timeofday_mtx);
now = time_sup.r.o.get_time();
elapsed = ERTS_MONOTONIC_TO_MSEC(now);
*ms_total = (UWord) elapsed;
*ms_diff = (UWord) (elapsed - prev_wall_clock_elapsed);
prev_wall_clock_elapsed = elapsed;
erts_smp_mtx_unlock(&erts_timeofday_mtx);
}
/* get current time */
void
get_time(int *hour, int *minute, int *second)
{
time_t the_clock;
struct tm *tm;
#ifdef HAVE_LOCALTIME_R
struct tm tmbuf;
#endif
the_clock = time((time_t *)0);
#ifdef HAVE_LOCALTIME_R
tm = localtime_r(&the_clock, &tmbuf);
#else
tm = localtime(&the_clock);
#endif
*hour = tm->tm_hour;
*minute = tm->tm_min;
*second = tm->tm_sec;
}
/* get current date */
void
get_date(int *year, int *month, int *day)
{
time_t the_clock;
struct tm *tm;
#ifdef HAVE_LOCALTIME_R
struct tm tmbuf;
#endif
the_clock = time((time_t *)0);
#ifdef HAVE_LOCALTIME_R
tm = localtime_r(&the_clock, &tmbuf);
#else
tm = localtime(&the_clock);
#endif
*year = tm->tm_year + 1900;
*month = tm->tm_mon +1;
*day = tm->tm_mday;
}
/* get localtime */
void
get_localtime(int *year, int *month, int *day,
int *hour, int *minute, int *second)
{
time_t the_clock;
struct tm *tm;
#ifdef HAVE_LOCALTIME_R
struct tm tmbuf;
#endif
the_clock = time((time_t *)0);
#ifdef HAVE_LOCALTIME_R
localtime_r(&the_clock, (tm = &tmbuf));
#else
tm = localtime(&the_clock);
#endif
*year = tm->tm_year + 1900;
*month = tm->tm_mon +1;
*day = tm->tm_mday;
*hour = tm->tm_hour;
*minute = tm->tm_min;
*second = tm->tm_sec;
}
/* get universaltime */
void
get_universaltime(int *year, int *month, int *day,
int *hour, int *minute, int *second)
{
time_t the_clock;
struct tm *tm;
#ifdef HAVE_GMTIME_R
struct tm tmbuf;
#endif
the_clock = time((time_t *)0);
#ifdef HAVE_GMTIME_R
gmtime_r(&the_clock, (tm = &tmbuf));
#else
tm = gmtime(&the_clock);
#endif
*year = tm->tm_year + 1900;
*month = tm->tm_mon +1;
*day = tm->tm_mday;
*hour = tm->tm_hour;
*minute = tm->tm_min;
*second = tm->tm_sec;
}
/* days in month = 1, 2, ..., 12 */
static const int mdays[14] = {0, 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
#define IN_RANGE(a,x,b) (((a) <= (x)) && ((x) <= (b)))
#define is_leap_year(y) (((((y) % 4) == 0) && \
(((y) % 100) != 0)) || \
(((y) % 400) == 0))
/* This is the earliest year we are sure to be able to handle
on all platforms w/o problems */
#define BASEYEAR 1902
/* A more "clever" mktime
* return 1, if successful
* return -1, if not successful
*/
static int erl_mktime(time_t *c, struct tm *tm) {
time_t clock;
clock = mktime(tm);
if (clock != -1) {
*c = clock;
return 1;
}
/* in rare occasions mktime returns -1
* when a correct value has been entered
*
* decrease seconds with one second
* if the result is -2, epochs should be -1
*/
tm->tm_sec = tm->tm_sec - 1;
clock = mktime(tm);
tm->tm_sec = tm->tm_sec + 1;
*c = -1;
if (clock == -2) {
return 1;
}
return -1;
}
/*
* gregday
*
* Returns the number of days since Jan 1, 1600, if year is
* greater of equal to 1600 , and month [1-12] and day [1-31]
* are within range. Otherwise it returns -1.
*/
static time_t gregday(int year, int month, int day)
{
Sint ndays = 0;
Sint gyear, pyear, m;
/* number of days in previous years */
gyear = year - 1600;
if (gyear > 0) {
pyear = gyear - 1;
ndays = (pyear/4) - (pyear/100) + (pyear/400) + pyear*365 + 366;
}
/* number of days in all months preceeding month */
for (m = 1; m < month; m++)
ndays += mdays[m];
/* Extra day if leap year and March or later */
if (is_leap_year(year) && (month > 2))
ndays++;
ndays += day - 1;
return (time_t) (ndays - 135140); /* 135140 = Jan 1, 1970 */
}
#define SECONDS_PER_MINUTE (60)
#define SECONDS_PER_HOUR (60 * SECONDS_PER_MINUTE)
#define SECONDS_PER_DAY (24 * SECONDS_PER_HOUR)
int seconds_to_univ(Sint64 time, Sint *year, Sint *month, Sint *day,
Sint *hour, Sint *minute, Sint *second) {
Sint y,mi;
Sint days = time / SECONDS_PER_DAY;
Sint secs = time % SECONDS_PER_DAY;
Sint tmp;
if (secs < 0) {
days--;
secs += SECONDS_PER_DAY;
}
tmp = secs % SECONDS_PER_HOUR;
*hour = secs / SECONDS_PER_HOUR;
*minute = tmp / SECONDS_PER_MINUTE;
*second = tmp % SECONDS_PER_MINUTE;
days += 719468;
y = (10000*((Sint64)days) + 14780) / 3652425;
tmp = days - (365 * y + y/4 - y/100 + y/400);
if (tmp < 0) {
y--;
tmp = days - (365*y + y/4 - y/100 + y/400);
}
mi = (100 * tmp + 52)/3060;
*month = (mi + 2) % 12 + 1;
*year = y + (mi + 2) / 12;
*day = tmp - (mi * 306 + 5)/10 + 1;
return 1;
}
int univ_to_seconds(Sint year, Sint month, Sint day, Sint hour, Sint minute, Sint second, Sint64 *time) {
Sint days;
if (!(IN_RANGE(1600, year, INT_MAX - 1) &&
IN_RANGE(1, month, 12) &&
IN_RANGE(1, day, (mdays[month] +
(month == 2
&& (year % 4 == 0)
&& (year % 100 != 0 || year % 400 == 0)))) &&
IN_RANGE(0, hour, 23) &&
IN_RANGE(0, minute, 59) &&
IN_RANGE(0, second, 59))) {
return 0;
}
days = gregday(year, month, day);
*time = SECONDS_PER_DAY;
*time *= days; /* don't try overflow it, it hurts */
*time += SECONDS_PER_HOUR * hour;
*time += SECONDS_PER_MINUTE * minute;
*time += second;
return 1;
}
#if defined(HAVE_TIME2POSIX) && defined(HAVE_DECL_TIME2POSIX) && \
!HAVE_DECL_TIME2POSIX
extern time_t time2posix(time_t);
#endif
int
local_to_univ(Sint *year, Sint *month, Sint *day,
Sint *hour, Sint *minute, Sint *second, int isdst)
{
time_t the_clock;
struct tm *tm, t;
#ifdef HAVE_GMTIME_R
struct tm tmbuf;
#endif
if (!(IN_RANGE(BASEYEAR, *year, INT_MAX - 1) &&
IN_RANGE(1, *month, 12) &&
IN_RANGE(1, *day, (mdays[*month] +
(*month == 2
&& (*year % 4 == 0)
&& (*year % 100 != 0 || *year % 400 == 0)))) &&
IN_RANGE(0, *hour, 23) &&
IN_RANGE(0, *minute, 59) &&
IN_RANGE(0, *second, 59))) {
return 0;
}
t.tm_year = *year - 1900;
t.tm_mon = *month - 1;
t.tm_mday = *day;
t.tm_hour = *hour;
t.tm_min = *minute;
t.tm_sec = *second;
t.tm_isdst = isdst;
/* the nature of mktime makes this a bit interesting,
* up to four mktime calls could happen here
*/
if (erl_mktime(&the_clock, &t) < 0) {
if (isdst) {
/* If this is a timezone without DST and the OS (correctly)
refuses to give us a DST time, we simulate the Linux/Solaris
behaviour of giving the same data as if is_dst was not set. */
t.tm_isdst = 0;
if (erl_mktime(&the_clock, &t) < 0) {
/* Failed anyway, something else is bad - will be a badarg */
return 0;
}
} else {
/* Something else is the matter, badarg. */
return 0;
}
}
#ifdef HAVE_TIME2POSIX
the_clock = time2posix(the_clock);
#endif
#ifdef HAVE_GMTIME_R
tm = gmtime_r(&the_clock, &tmbuf);
#else
tm = gmtime(&the_clock);
#endif
if (!tm) {
return 0;
}
*year = tm->tm_year + 1900;
*month = tm->tm_mon +1;
*day = tm->tm_mday;
*hour = tm->tm_hour;
*minute = tm->tm_min;
*second = tm->tm_sec;
return 1;
}
#if defined(HAVE_POSIX2TIME) && defined(HAVE_DECL_POSIX2TIME) && \
!HAVE_DECL_POSIX2TIME
extern time_t posix2time(time_t);
#endif
int
univ_to_local(Sint *year, Sint *month, Sint *day,
Sint *hour, Sint *minute, Sint *second)
{
time_t the_clock;
struct tm *tm;
#ifdef HAVE_LOCALTIME_R
struct tm tmbuf;
#endif
if (!(IN_RANGE(BASEYEAR, *year, INT_MAX - 1) &&
IN_RANGE(1, *month, 12) &&
IN_RANGE(1, *day, (mdays[*month] +
(*month == 2
&& (*year % 4 == 0)
&& (*year % 100 != 0 || *year % 400 == 0)))) &&
IN_RANGE(0, *hour, 23) &&
IN_RANGE(0, *minute, 59) &&
IN_RANGE(0, *second, 59))) {
return 0;
}
the_clock = *second + 60 * (*minute + 60 * (*hour + 24 *
gregday(*year, *month, *day)));
#ifdef HAVE_POSIX2TIME
/*
* Addition from OpenSource - affects FreeBSD.
* No valid test case /PaN
*
* leap-second correction performed
* if system is configured so;
* do nothing if not
* See FreeBSD 6.x and 7.x
* /usr/src/lib/libc/stdtime/localtime.c
* for the details
*/
the_clock = posix2time(the_clock);
#endif
#ifdef HAVE_LOCALTIME_R
tm = localtime_r(&the_clock, &tmbuf);
#else
tm = localtime(&the_clock);
#endif
if (tm) {
*year = tm->tm_year + 1900;
*month = tm->tm_mon +1;
*day = tm->tm_mday;
*hour = tm->tm_hour;
*minute = tm->tm_min;
*second = tm->tm_sec;
return 1;
}
return 0;
}
/* get a timestamp */
void
get_now(Uint* megasec, Uint* sec, Uint* microsec)
{
ErtsMonotonicTime now_megasec, now_sec, now, mtime, time_offset;
mtime = time_sup.r.o.get_time();
time_offset = get_time_offset();
now = ERTS_MONOTONIC_TO_USEC(mtime + time_offset);
erts_smp_mtx_lock(&erts_timeofday_mtx);
/* Make sure now time is later than last time */
if (now <= previous_now)
now = previous_now + 1;
previous_now = now;
erts_smp_mtx_unlock(&erts_timeofday_mtx);
now_megasec = now / ERTS_MONOTONIC_TIME_TERA;
now_sec = now / ERTS_MONOTONIC_TIME_MEGA;
*megasec = (Uint) now_megasec;
*sec = (Uint) (now_sec - now_megasec*ERTS_MONOTONIC_TIME_MEGA);
*microsec = (Uint) (now - now_sec*ERTS_MONOTONIC_TIME_MEGA);
ASSERT(((ErtsMonotonicTime) *megasec)*ERTS_MONOTONIC_TIME_TERA
+ ((ErtsMonotonicTime) *sec)*ERTS_MONOTONIC_TIME_MEGA
+ ((ErtsMonotonicTime) *microsec) == now);
}
ErtsMonotonicTime
erts_get_monotonic_time(void)
{
return time_sup.r.o.get_time();
}
void
get_sys_now(Uint* megasec, Uint* sec, Uint* microsec)
{
ErtsSystemTime stime = erts_os_system_time();
ErtsSystemTime ms, s, us;
us = ERTS_MONOTONIC_TO_USEC(stime);
s = us / (1000*1000);
ms = s / (1000*1000);
*megasec = (Uint) ms;
*sec = (Uint) (s - ms*(1000*1000));
*microsec = (Uint) (us - s*(1000*1000));
}
#ifdef HAVE_ERTS_NOW_CPU
void erts_get_now_cpu(Uint* megasec, Uint* sec, Uint* microsec) {
SysCpuTime t;
SysTimespec tp;
sys_get_proc_cputime(t, tp);
*microsec = (Uint)(tp.tv_nsec / 1000);
t = (tp.tv_sec / 1000000);
*megasec = (Uint)(t % 1000000);
*sec = (Uint)(tp.tv_sec % 1000000);
}
#endif
#include "big.h"
void
erts_monitor_time_offset(Eterm id, Eterm ref)
{
erts_smp_mtx_lock(&erts_get_time_mtx);
erts_add_monitor(&time_offset_monitors, MON_TIME_OFFSET, ref, id, NIL);
no_time_offset_monitors++;
erts_smp_mtx_unlock(&erts_get_time_mtx);
}
int
erts_demonitor_time_offset(Eterm ref)
{
int res;
ErtsMonitor *mon;
ASSERT(is_internal_ref(ref));
erts_smp_mtx_lock(&erts_get_time_mtx);
mon = erts_remove_monitor(&time_offset_monitors, ref);
if (!mon)
res = 0;
else {
ASSERT(no_time_offset_monitors > 0);
no_time_offset_monitors--;
res = 1;
}
erts_smp_mtx_unlock(&erts_get_time_mtx);
if (res)
erts_destroy_monitor(mon);
return res;
}
typedef struct {
Eterm pid;
Eterm ref;
Eterm heap[REF_THING_SIZE];
} ErtsTimeOffsetMonitorInfo;
typedef struct {
Uint ix;
ErtsTimeOffsetMonitorInfo *to_mon_info;
} ErtsTimeOffsetMonitorContext;
static void
save_time_offset_monitor(ErtsMonitor *mon, void *vcntxt)
{
ErtsTimeOffsetMonitorContext *cntxt;
Eterm *from_hp, *to_hp;
Uint mix;
int hix;
cntxt = (ErtsTimeOffsetMonitorContext *) vcntxt;
mix = (cntxt->ix)++;
cntxt->to_mon_info[mix].pid = mon->pid;
to_hp = &cntxt->to_mon_info[mix].heap[0];
ASSERT(is_internal_ref(mon->ref));
from_hp = internal_ref_val(mon->ref);
ASSERT(thing_arityval(*from_hp) + 1 == REF_THING_SIZE);
for (hix = 0; hix < REF_THING_SIZE; hix++)
to_hp[hix] = from_hp[hix];
cntxt->to_mon_info[mix].ref
= make_internal_ref(&cntxt->to_mon_info[mix].heap[0]);
}
static void
send_time_offset_changed_notifications(void *new_offsetp)
{
ErtsMonotonicTime new_offset;
ErtsTimeOffsetMonitorInfo *to_mon_info = NULL; /* Shut up faulty warning */
Uint no_monitors;
char *tmp = NULL;
#ifdef ARCH_64
new_offset = (ErtsMonotonicTime) new_offsetp;
#else
new_offset = *((ErtsMonotonicTime *) new_offsetp);
erts_free(ERTS_ALC_T_NEW_TIME_OFFSET, new_offsetp);
#endif
new_offset -= ERTS_MONOTONIC_OFFSET_NATIVE;
erts_smp_mtx_lock(&erts_get_time_mtx);
no_monitors = no_time_offset_monitors;
if (no_monitors) {
ErtsTimeOffsetMonitorContext cntxt;
Uint alloc_sz;
/* Monitor info array size */
alloc_sz = no_monitors*sizeof(ErtsTimeOffsetMonitorInfo);
/* + template max size */
alloc_sz += 6*sizeof(Eterm); /* 5-tuple */
alloc_sz += ERTS_MAX_SINT64_HEAP_SIZE*sizeof(Eterm); /* max offset size */
tmp = erts_alloc(ERTS_ALC_T_TMP, alloc_sz);
to_mon_info = (ErtsTimeOffsetMonitorInfo *) tmp;
cntxt.ix = 0;
cntxt.to_mon_info = to_mon_info;
erts_doforall_monitors(time_offset_monitors,
save_time_offset_monitor,
&cntxt);
ASSERT(cntxt.ix == no_monitors);
}
erts_smp_mtx_unlock(&erts_get_time_mtx);
if (no_monitors) {
Eterm *hp, *patch_refp, new_offset_term, message_template;
Uint mix, hsz;
/* Make message template */
hp = (Eterm *) (tmp + no_monitors*sizeof(ErtsTimeOffsetMonitorInfo));
hsz = 6; /* 5-tuple */
hsz += REF_THING_SIZE;
hsz += ERTS_SINT64_HEAP_SIZE(new_offset);
if (IS_SSMALL(new_offset))
new_offset_term = make_small(new_offset);
else
new_offset_term = erts_sint64_to_big(new_offset, &hp);
message_template = TUPLE5(hp,
am_CHANGE,
THE_NON_VALUE, /* Patch point for ref */
am_time_offset,
am_clock_service,
new_offset_term);
patch_refp = &hp[2];
ASSERT(*patch_refp == THE_NON_VALUE);
for (mix = 0; mix < no_monitors; mix++) {
Process *rp = erts_proc_lookup(to_mon_info[mix].pid);
if (rp) {
Eterm ref = to_mon_info[mix].ref;
ErtsProcLocks rp_locks = ERTS_PROC_LOCK_LINK;
erts_smp_proc_lock(rp, ERTS_PROC_LOCK_LINK);
if (erts_lookup_monitor(ERTS_P_MONITORS(rp), ref)) {
ErlHeapFragment *bp;
ErlOffHeap *ohp;
Eterm message;
hp = erts_alloc_message_heap(hsz, &bp, &ohp, rp, &rp_locks);
*patch_refp = ref;
ASSERT(hsz == size_object(message_template));
message = copy_struct(message_template, hsz, &hp, ohp);
erts_queue_message(rp, &rp_locks, bp, message, NIL
#ifdef USE_VM_PROBES
, NIL
#endif
);
}
erts_smp_proc_unlock(rp, rp_locks);
}
}
erts_free(ERTS_ALC_T_TMP, tmp);
}
}
static void
schedule_send_time_offset_changed_notifications(ErtsMonotonicTime new_offset)
{
#ifdef ARCH_64
void *new_offsetp = (void *) new_offset;
ASSERT(sizeof(void *) == sizeof(ErtsMonotonicTime));
#else
void *new_offsetp = erts_alloc(ERTS_ALC_T_NEW_TIME_OFFSET,
sizeof(ErtsMonotonicTime));
*((ErtsMonotonicTime *) new_offsetp) = new_offset;
#endif
erts_schedule_misc_aux_work(1,
send_time_offset_changed_notifications,
new_offsetp);
}
static ERTS_INLINE Eterm
make_time_val(Process *c_p, ErtsMonotonicTime time_val)
{
Sint64 val = (Sint64) time_val;
Eterm *hp;
Uint sz;
if (IS_SSMALL(val))
return make_small(val);
sz = ERTS_SINT64_HEAP_SIZE(val);
hp = HAlloc(c_p, sz);
return erts_sint64_to_big(val, &hp);
}
Eterm
erts_get_monotonic_start_time(struct process *c_p)
{
return make_time_val(c_p, ERTS_MONOTONIC_TIME_START);
}
static Eterm
bld_monotonic_time_source(Uint **hpp, Uint *szp, Sint64 os_mtime)
{
#ifndef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
return NIL;
#else
int i = 0;
Eterm k[6];
Eterm v[6];
if (time_sup.r.o.os_monotonic_time_disable)
return NIL;
k[i] = erts_bld_atom(hpp, szp, "function");
v[i++] = erts_bld_atom(hpp, szp,
time_sup.r.o.os_monotonic_time_func);
if (time_sup.r.o.os_monotonic_time_clock_id) {
k[i] = erts_bld_atom(hpp, szp, "clock_id");
v[i++] = erts_bld_atom(hpp, szp,
time_sup.r.o.os_monotonic_time_clock_id);
}
k[i] = erts_bld_atom(hpp, szp, "resolution");
v[i++] = erts_bld_uint64(hpp, szp,
time_sup.r.o.os_monotonic_time_resolution);
k[i] = erts_bld_atom(hpp, szp, "extended");
v[i++] = time_sup.r.o.os_monotonic_time_extended ? am_yes : am_no;
k[i] = erts_bld_atom(hpp, szp, "parallel");
v[i++] = time_sup.r.o.os_monotonic_time_locked ? am_no : am_yes;
k[i] = erts_bld_atom(hpp, szp, "time");
v[i++] = erts_bld_sint64(hpp, szp, os_mtime);
return erts_bld_2tup_list(hpp, szp, (Sint) i, k, v);
#endif
}
Eterm
erts_monotonic_time_source(struct process *c_p)
{
Uint hsz = 0;
Eterm *hp = NULL;
Sint64 os_mtime = 0;
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
if (!time_sup.r.o.os_monotonic_time_disable)
os_mtime = (Sint64) erts_os_monotonic_time();
#endif
bld_monotonic_time_source(NULL, &hsz, os_mtime);
if (hsz)
hp = HAlloc(c_p, hsz);
return bld_monotonic_time_source(&hp, NULL, os_mtime);
}
static Eterm
bld_system_time_source(Uint **hpp, Uint *szp, Sint64 os_stime)
{
int i = 0;
Eterm k[5];
Eterm v[5];
k[i] = erts_bld_atom(hpp, szp, "function");
v[i++] = erts_bld_atom(hpp, szp,
time_sup.r.o.os_system_time_func);
if (time_sup.r.o.os_system_time_clock_id) {
k[i] = erts_bld_atom(hpp, szp, "clock_id");
v[i++] = erts_bld_atom(hpp, szp,
time_sup.r.o.os_system_time_clock_id);
}
k[i] = erts_bld_atom(hpp, szp, "resolution");
v[i++] = erts_bld_uint64(hpp, szp,
time_sup.r.o.os_system_time_resolution);
k[i] = erts_bld_atom(hpp, szp, "parallel");
v[i++] = am_yes;
k[i] = erts_bld_atom(hpp, szp, "time");
v[i++] = erts_bld_sint64(hpp, szp, os_stime);
return erts_bld_2tup_list(hpp, szp, (Sint) i, k, v);
}
Eterm
erts_system_time_source(struct process *c_p)
{
Uint hsz = 0;
Eterm *hp = NULL;
Sint64 os_stime = (Sint64) erts_os_system_time();
bld_system_time_source(NULL, &hsz, os_stime);
if (hsz)
hp = HAlloc(c_p, hsz);
return bld_system_time_source(&hp, NULL, os_stime);
}
#include "bif.h"
static ERTS_INLINE Eterm
time_unit_conversion(Process *c_p, Eterm term, ErtsMonotonicTime val, ErtsMonotonicTime muloff)
{
ErtsMonotonicTime result;
BIF_RETTYPE ret;
if (val < 0)
goto trap_to_erlang_code;
/* Convert to common user specified time units */
switch (term) {
case am_seconds:
case make_small(1):
result = ERTS_MONOTONIC_TO_SEC(val) + muloff*ERTS_MONOTONIC_OFFSET_SEC;
ERTS_BIF_PREP_RET(ret, make_time_val(c_p, result));
break;
case am_milli_seconds:
case make_small(1000):
result = ERTS_MONOTONIC_TO_MSEC(val) + muloff*ERTS_MONOTONIC_OFFSET_MSEC;
ERTS_BIF_PREP_RET(ret, make_time_val(c_p, result));
break;
case am_micro_seconds:
case make_small(1000*1000):
result = ERTS_MONOTONIC_TO_USEC(val) + muloff*ERTS_MONOTONIC_OFFSET_USEC;
ERTS_BIF_PREP_RET(ret, make_time_val(c_p, result));
break;
#ifdef ARCH_64
case am_nano_seconds:
case make_small(1000*1000*1000):
result = ERTS_MONOTONIC_TO_NSEC(val) + muloff*ERTS_MONOTONIC_OFFSET_NSEC;
ERTS_BIF_PREP_RET(ret, make_time_val(c_p, result));
break;
#endif
default: {
Eterm value, native_res;
#ifndef ARCH_64
Sint user_res;
if (term == am_nano_seconds)
goto to_nano_seconds;
if (term_to_Sint(term, &user_res)) {
if (user_res == 1000*1000*1000) {
to_nano_seconds:
result = (ERTS_MONOTONIC_TO_NSEC(val)
+ muloff*ERTS_MONOTONIC_OFFSET_NSEC);
ERTS_BIF_PREP_RET(ret, make_time_val(c_p, result));
break;
}
if (user_res <= 0)
goto badarg;
}
#else
if (is_small(term)) {
if (signed_val(term) <= 0)
goto badarg;
}
#endif
else if (is_big(term)) {
if (big_sign(term))
goto badarg;
}
else {
badarg:
ERTS_BIF_PREP_ERROR(ret, c_p, BADARG);
break;
}
trap_to_erlang_code:
/* Do it in erlang code instead; pass along values to use... */
value = make_time_val(c_p, val + muloff*ERTS_MONOTONIC_OFFSET_NATIVE);
native_res = make_time_val(c_p, ERTS_MONOTONIC_TIME_UNIT);
ERTS_BIF_PREP_TRAP3(ret, erts_convert_time_unit_trap, c_p,
value, native_res, term);
break;
}
}
return ret;
}
/* Built in functions */
BIF_RETTYPE monotonic_time_0(BIF_ALIST_0)
{
ErtsMonotonicTime mtime = time_sup.r.o.get_time();
mtime += ERTS_MONOTONIC_OFFSET_NATIVE;
BIF_RET(make_time_val(BIF_P, mtime));
}
BIF_RETTYPE monotonic_time_1(BIF_ALIST_1)
{
BIF_RET(time_unit_conversion(BIF_P, BIF_ARG_1, time_sup.r.o.get_time(), 1));
}
BIF_RETTYPE system_time_0(BIF_ALIST_0)
{
ErtsMonotonicTime mtime, offset;
mtime = time_sup.r.o.get_time();
offset = get_time_offset();
BIF_RET(make_time_val(BIF_P, mtime + offset));
}
BIF_RETTYPE system_time_1(BIF_ALIST_0)
{
ErtsMonotonicTime mtime, offset;
mtime = time_sup.r.o.get_time();
offset = get_time_offset();
BIF_RET(time_unit_conversion(BIF_P, BIF_ARG_1, mtime + offset, 0));
}
BIF_RETTYPE erts_internal_time_unit_0(BIF_ALIST_0)
{
BIF_RET(make_time_val(BIF_P, ERTS_MONOTONIC_TIME_UNIT));
}
BIF_RETTYPE time_offset_0(BIF_ALIST_0)
{
ErtsMonotonicTime time_offset = get_time_offset();
time_offset -= ERTS_MONOTONIC_OFFSET_NATIVE;
BIF_RET(make_time_val(BIF_P, time_offset));
}
BIF_RETTYPE time_offset_1(BIF_ALIST_1)
{
BIF_RET(time_unit_conversion(BIF_P, BIF_ARG_1, get_time_offset(), -1));
}
BIF_RETTYPE timestamp_0(BIF_ALIST_0)
{
Eterm *hp, res;
ErtsMonotonicTime stime, mtime, all_sec, offset;
Uint mega_sec, sec, micro_sec;
mtime = time_sup.r.o.get_time();
offset = get_time_offset();
stime = ERTS_MONOTONIC_TO_USEC(mtime + offset);
all_sec = stime / ERTS_MONOTONIC_TIME_MEGA;
mega_sec = (Uint) (stime / ERTS_MONOTONIC_TIME_TERA);
sec = (Uint) (all_sec - (((ErtsMonotonicTime) mega_sec)
* ERTS_MONOTONIC_TIME_MEGA));
micro_sec = (Uint) (stime - all_sec*ERTS_MONOTONIC_TIME_MEGA);
ASSERT(((ErtsMonotonicTime) mega_sec)*ERTS_MONOTONIC_TIME_TERA
+ ((ErtsMonotonicTime) sec)*ERTS_MONOTONIC_TIME_MEGA
+ micro_sec == stime);
/*
* Mega seconds is the only value that potentially
* ever could be a bignum. However, that wont happen
* during at least the next 4 million years...
*
* (System time will also have wrapped in the
* 64-bit integer before we get there...)
*/
ASSERT(IS_USMALL(0, mega_sec));
ASSERT(IS_USMALL(0, sec));
ASSERT(IS_USMALL(0, micro_sec));
hp = HAlloc(BIF_P, 4);
res = TUPLE3(hp,
make_small(mega_sec),
make_small(sec),
make_small(micro_sec));
BIF_RET(res);
}
BIF_RETTYPE os_system_time_0(BIF_ALIST_0)
{
ErtsSystemTime stime = erts_os_system_time();
BIF_RET(make_time_val(BIF_P, stime));
}
BIF_RETTYPE os_system_time_1(BIF_ALIST_0)
{
ErtsSystemTime stime = erts_os_system_time();
BIF_RET(time_unit_conversion(BIF_P, BIF_ARG_1, stime, 0));
}
Jump to Line
Something went wrong with that request. Please try again.