/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1997-2013. 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%
*/
/*
* Purpose: System-dependent time functions.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "sys.h"
#include "assert.h"
#include "erl_os_monotonic_time_extender.h"
#include "erl_time.h"
#define LL_LITERAL(X) ERTS_I64_LITERAL(X)
/******************* Routines for time measurement *********************/
#define EPOCH_JULIAN_DIFF LL_LITERAL(11644473600)
#define TICKS_PER_SECOND LL_LITERAL(10000000)
#define SECONDS_PER_DAY LL_LITERAL(86400)
#define ULI_TO_FILETIME(ft,ull) \
do { \
(ft).dwLowDateTime = (ull).LowPart; \
(ft).dwHighDateTime = (ull).HighPart; \
} while (0)
#define FILETIME_TO_ULI(ull,ft) \
do { \
(ull).LowPart = (ft).dwLowDateTime; \
(ull).HighPart = (ft).dwHighDateTime; \
} while (0)
#define EPOCH_TO_FILETIME(ft, epoch) \
do { \
ULARGE_INTEGER ull; \
ull.QuadPart = (((epoch) + EPOCH_JULIAN_DIFF) * TICKS_PER_SECOND); \
ULI_TO_FILETIME(ft,ull); \
} while(0)
#define FILETIME_TO_EPOCH(epoch, ft) \
do { \
ULARGE_INTEGER ull; \
FILETIME_TO_ULI(ull,ft); \
(epoch) = ((ull.QuadPart / TICKS_PER_SECOND) - EPOCH_JULIAN_DIFF); \
} while(0)
/* Getting timezone information is a heavy operation, so we want to do this
only once */
static TIME_ZONE_INFORMATION static_tzi;
static int have_static_tzi = 0;
static int days_in_month[2][13] = {
{0,31,28,31,30,31,30,31,31,30,31,30,31},
{0,31,29,31,30,31,30,31,31,30,31,30,31}};
#define ERTS_GET_TICK_COUNT_TIME_UNIT_SHIFT 10
/*
* erts_os_monotonic_time()
*/
struct sys_time_internal_state_read_only__ {
ULONGLONG (WINAPI *pGetTickCount64)(void);
BOOL (WINAPI *pQueryPerformanceCounter)(LARGE_INTEGER *);
Sint32 pcf;
int using_get_tick_count_time_unit;
};
struct sys_time_internal_state_read_mostly__ {
ErtsOsMonotonicTimeExtendState os_mtime_xtnd;
};
struct sys_time_internal_state_write_freq__ {
erts_smp_mtx_t mtime_mtx;
ULONGLONG wrap;
ULONGLONG last_tick_count;
};
__declspec(align(ASSUMED_CACHE_LINE_SIZE)) struct {
union {
struct sys_time_internal_state_read_only__ o;
char align__[(((sizeof(struct sys_time_internal_state_read_only__) - 1)
/ ASSUMED_CACHE_LINE_SIZE) + 1)
* ASSUMED_CACHE_LINE_SIZE];
} r;
union {
struct sys_time_internal_state_read_mostly__ m;
char align__[(((sizeof(struct sys_time_internal_state_read_mostly__) - 1)
/ ASSUMED_CACHE_LINE_SIZE) + 1)
* ASSUMED_CACHE_LINE_SIZE];
} wr;
union {
struct sys_time_internal_state_write_freq__ f;
char align__[(((sizeof(struct sys_time_internal_state_write_freq__) - 1)
/ ASSUMED_CACHE_LINE_SIZE) + 1)
* ASSUMED_CACHE_LINE_SIZE];
} w;
} internal_state;
__declspec(align(ASSUMED_CACHE_LINE_SIZE)) ErtsSysTimeData__ erts_sys_time_data__;
static ErtsMonotonicTime
os_monotonic_time_qpc(void)
{
LARGE_INTEGER pc;
if (!(*internal_state.r.o.pQueryPerformanceCounter)(&pc))
erl_exit(ERTS_ABORT_EXIT, "QueryPerformanceCounter() failed\n");
return (ErtsMonotonicTime) pc.QuadPart;
}
static Uint32
get_tick_count(void)
{
return (Uint32) GetTickCount();
}
static ErtsMonotonicTime
os_monotonic_time_gtc32(void)
{
Uint32 ticks = (Uint32) GetTickCount();
ERTS_CHK_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd,
tick_count);
return ERTS_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd,
ticks) << 10;
}
static ErtsMonotonicTime
os_monotonic_time_gtc64(void)
{
ULONGLONG ticks = (*internal_state.r.o.pGetTickCount64)();
return (ErtsMonotonicTime) ticks << 10;
}
static ErtsSysHrTime
sys_hrtime_qpc(void)
{
LARGE_INTEGER pc;
if (!(*internal_state.r.o.pQueryPerformanceCounter)(&pc))
erl_exit(ERTS_ABORT_EXIT, "QueryPerformanceCounter() failed\n");
ASSERT(pc.QuadPart > 0);
return (ErtsSysHrTime) erts_time_unit_conversion((Uint64) pc.QuadPart,
internal_state.r.o.pcf,
(Uint32) 1000*1000*1000);
}
static ErtsSysHrTime
sys_hrtime_gtc32(void)
{
ErtsSysHrTime time;
Uint32 ticks = (Uint32) GetTickCount();
ERTS_CHK_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd,
tick_count);
time = (ErtsSysHrTime) ERTS_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd,
ticks);
time *= (ErtsSysHrTime) (1000 * 1000);
return time;
}
static ErtsSysHrTime
sys_hrtime_gtc64(void)
{
ErtsSysHrTime time = (*internal_state.r.o.pGetTickCount64)();
time *= (ErtsSysHrTime) (1000*1000);
return time;
}
/*
* Init
*/
void
sys_init_time(ErtsSysInitTimeResult *init_resp)
{
ErtsMonotonicTime (*os_mtime_func)(void);
ErtsSysHrTime (*sys_hrtime_func)(void) = NULL;
ErtsMonotonicTime time_unit;
char kernel_dll_name[] = "kernel32";
HMODULE module;
init_resp->os_monotonic_time_info.clock_id = NULL;
module = GetModuleHandle(kernel_dll_name);
if (!module) {
get_tick_count:
erts_smp_mtx_init(&internal_state.w.f.mtime_mtx,
"os_monotonic_time");
internal_state.w.f.wrap = 0;
internal_state.w.f.last_tick_count = 0;
init_resp->os_monotonic_time_info.func = "GetTickCount";
init_resp->os_monotonic_time_info.locked_use = 1;
/* 10-16 ms resolution according to MicroSoft documentation */
init_resp->os_monotonic_time_info.resolution = 100; /* 10 ms */
time_unit = (ErtsMonotonicTime) 1000;
time_unit <<= ERTS_GET_TICK_COUNT_TIME_UNIT_SHIFT;
internal_state.r.o.using_get_tick_count_time_unit = 1;
os_mtime_func = os_monotonic_time_gtc32;
init_resp->os_monotonic_time_info.extended = 1;
erts_init_os_monotonic_time_extender(&internal_state.wr.m.os_mtime_xtnd,
get_tick_count,
60*60*24*7); /* Check once a week */
if (!sys_hrtime_func)
sys_hrtime_func = sys_hrtime_gtc32;
}
else {
int major, minor, build;
os_version(&major, &minor, &build);
if (major < 6) {
get_tick_count64:
internal_state.r.o.pGetTickCount64
= ((ULONGLONG (WINAPI *)(void))
GetProcAddress(module, "GetTickCount64"));
if (!internal_state.r.o.pGetTickCount64)
goto get_tick_count;
init_resp->os_monotonic_time_info.func = "GetTickCount64";
init_resp->os_monotonic_time_info.locked_use = 0;
/* 10-16 ms resolution according to MicroSoft documentation */
init_resp->os_monotonic_time_info.resolution = 100; /* 10 ms */
time_unit = (ErtsMonotonicTime) 1000;
time_unit <<= ERTS_GET_TICK_COUNT_TIME_UNIT_SHIFT;
internal_state.r.o.using_get_tick_count_time_unit = 1;
os_mtime_func = os_monotonic_time_gtc64;
if (!sys_hrtime_func)
sys_hrtime_func = sys_hrtime_gtc64;
}
else { /* Vista or newer... */
LARGE_INTEGER pf;
BOOL (WINAPI *QPF)(LARGE_INTEGER *);
QPF = ((BOOL (WINAPI *)(LARGE_INTEGER *))
GetProcAddress(module, "QueryPerformanceFrequency"));
if (!QPF)
goto get_tick_count64;
if (!(*QPF)(&pf))
goto get_tick_count64;
internal_state.r.o.pQueryPerformanceCounter
= ((BOOL (WINAPI *)(LARGE_INTEGER *))
GetProcAddress(module, "QueryPerformanceCounter"));
if (!internal_state.r.o.pQueryPerformanceCounter)
goto get_tick_count64;
if (pf.QuadPart < (((LONGLONG) 1) << 32)) {
internal_state.r.o.pcf = (Uint32) pf.QuadPart;
sys_hrtime_func = sys_hrtime_qpc;
}
/*
* We only use QueryPerformanceCounter() for
* os-monotonic-time if its frequency is equal
* to, or larger than GHz in order to ensure
* that the user wont be able to observe faulty
* order between values retrieved on different threads.
*/
if (pf.QuadPart < (LONGLONG) 1000*1000*1000)
goto get_tick_count64;
init_resp->os_monotonic_time_info.func = "QueryPerformanceCounter";
init_resp->os_monotonic_time_info.locked_use = 0;
time_unit = (ErtsMonotonicTime) pf.QuadPart;
internal_state.r.o.using_get_tick_count_time_unit = 0;
init_resp->os_monotonic_time_info.resolution = time_unit;
os_mtime_func = os_monotonic_time_qpc;
}
}
erts_sys_time_data__.r.o.os_monotonic_time = os_mtime_func;
init_resp->os_monotonic_time_unit = time_unit;
init_resp->have_os_monotonic_time = 1;
init_resp->sys_clock_resolution = 1;
init_resp->os_system_time_info.func = "GetSystemTime";
init_resp->os_system_time_info.clock_id = NULL;
init_resp->os_system_time_info.resolution = 100;
init_resp->os_system_time_info.locked_use = 0;
if(GetTimeZoneInformation(&static_tzi) &&
static_tzi.StandardDate.wMonth != 0 &&
static_tzi.DaylightDate.wMonth != 0) {
have_static_tzi = 1;
}
}
void
erts_late_sys_init_time(void)
{
if (erts_sys_time_data__.r.o.os_monotonic_time == os_monotonic_time_gtc32)
erts_late_init_os_monotonic_time_extender(&internal_state.wr.m.os_mtime_xtnd);
}
/* Returns a switchtimes for DST as UTC filetimes given data from a
TIME_ZONE_INFORMATION, see sys_localtime_r for usage. */
static void
get_dst_switchtime(DWORD year,
SYSTEMTIME dstinfo, LONG bias,
FILETIME *utc_switchtime)
{
DWORD occu;
DWORD weekday,wday_1st;
DWORD day, days_in;
FILETIME tmp,tmp2;
ULARGE_INTEGER ull;
int leap_year = 0;
if (dstinfo.wYear != 0) {
/* A year specific transition, in which case the data in the structure
is already properly set for a specific year. Compare year
with parameter and see if they correspond, in that case generate a
filetime directly, otherwise set the filetime to 0 */
if (year != dstinfo.wYear) {
utc_switchtime->dwLowDateTime = utc_switchtime->dwHighDateTime = 0;
return;
}
} else {
occu = dstinfo.wDay;
weekday = dstinfo.wDayOfWeek;
dstinfo.wDayOfWeek = 0;
dstinfo.wDay = 1;
dstinfo.wYear = year;
SystemTimeToFileTime(&dstinfo,&tmp);
ull.LowPart = tmp.dwLowDateTime;
ull.HighPart = tmp.dwHighDateTime;
ull.QuadPart /= (TICKS_PER_SECOND*SECONDS_PER_DAY); /* Julian Day */
wday_1st = (DWORD) ((ull.QuadPart + LL_LITERAL(1)) % LL_LITERAL(7));
day = (weekday >= wday_1st) ?
weekday - wday_1st + 1 :
weekday - wday_1st + 8;
--occu;
if (((dstinfo.wYear % 4) == 0 && (dstinfo.wYear % 100) > 0) ||
((dstinfo.wYear % 400) == 0)) {
leap_year = 1;
}
days_in = days_in_month[leap_year][dstinfo.wMonth];
while (occu > 0 && (day + 7 <= days_in)) {
--occu;
day += 7;
}
dstinfo.wDay = day;
}
SystemTimeToFileTime(&dstinfo,&tmp);
/* correct for bias */
ull.LowPart = tmp.dwLowDateTime;
ull.HighPart = tmp.dwHighDateTime;
ull.QuadPart += (((LONGLONG) bias) * LL_LITERAL(60) * TICKS_PER_SECOND);
utc_switchtime->dwLowDateTime = ull.LowPart;
utc_switchtime->dwHighDateTime = ull.HighPart;
return;
}
/* This function gives approximately the correct year from a FILETIME
Around the actual new year, it may return the wrong value, but that's OK
as DST never switches around new year. */
static DWORD
approx_year(FILETIME ft)
{
ULARGE_INTEGER ull;
FILETIME_TO_ULI(ull,ft);
ull.QuadPart /= LL_LITERAL(1000);
ull.QuadPart /= SECONDS_PER_DAY;
ull.QuadPart /= LL_LITERAL(3652425);
ull.QuadPart += 1601;
return (DWORD) ull.QuadPart;
}
struct tm *
sys_localtime_r(time_t *epochs, struct tm *ptm)
{
FILETIME ft,lft;
SYSTEMTIME st;
if ((((*epochs) + EPOCH_JULIAN_DIFF) * TICKS_PER_SECOND) < 0LL) {
fprintf(stderr,"1\r\n"); fflush(stderr);
return NULL;
}
EPOCH_TO_FILETIME(ft,*epochs);
ptm->tm_isdst = 0;
if (have_static_tzi) {
FILETIME dst_start, dst_stop;
ULARGE_INTEGER ull;
DWORD year = approx_year(ft);
get_dst_switchtime(year,static_tzi.DaylightDate,
static_tzi.Bias+static_tzi.StandardBias,&dst_start);
get_dst_switchtime(year,static_tzi.StandardDate,
static_tzi.Bias+static_tzi.StandardBias+
static_tzi.DaylightBias,
&dst_stop);
FILETIME_TO_ULI(ull,ft);
if (CompareFileTime(&ft,&dst_start) >= 0 &&
CompareFileTime(&ft,&dst_stop) < 0) {
ull.QuadPart -=
((LONGLONG) static_tzi.Bias+static_tzi.StandardBias+
static_tzi.DaylightBias) *
LL_LITERAL(60) * TICKS_PER_SECOND;
ptm->tm_isdst = 1;
} else {
ull.QuadPart -=
((LONGLONG) static_tzi.Bias+static_tzi.StandardBias)
* LL_LITERAL(60) * TICKS_PER_SECOND;
}
ULI_TO_FILETIME(ft,ull);
} else {
if (!FileTimeToLocalFileTime(&ft,&lft)) {
return NULL;
}
ft = lft;
}
if (!FileTimeToSystemTime(&ft,&st)) {
return NULL;
}
ptm->tm_year = (int) st.wYear - 1900;
ptm->tm_mon = (int) st.wMonth - 1;
ptm->tm_mday = (int) st.wDay;
ptm->tm_hour = (int) st.wHour;
ptm->tm_min = (int) st.wMinute;
ptm->tm_sec = (int) st.wSecond;
ptm->tm_wday = (int) st.wDayOfWeek;
{
int yday = ptm->tm_mday - 1;
int m = ptm->tm_mon;
int leap_year = 0;
if (((st.wYear % 4) == 0 && (st.wYear % 100) > 0) ||
((st.wYear % 400) == 0)) {
leap_year = 1;
}
while (m > 0) {
yday +=days_in_month[leap_year][m];
--m;
}
ptm->tm_yday = yday;
}
return ptm;
}
struct tm *
sys_gmtime_r(time_t *epochs, struct tm *ptm)
{
FILETIME ft;
SYSTEMTIME st;
if ((((*epochs) + EPOCH_JULIAN_DIFF) * TICKS_PER_SECOND) < 0LL) {
return NULL;
}
EPOCH_TO_FILETIME(ft,*epochs);
if (!FileTimeToSystemTime(&ft,&st)) {
return NULL;
}
ptm->tm_year = (int) st.wYear - 1900;
ptm->tm_mon = (int) st.wMonth - 1;
ptm->tm_mday = (int) st.wDay;
ptm->tm_hour = (int) st.wHour;
ptm->tm_min = (int) st.wMinute;
ptm->tm_sec = (int) st.wSecond;
ptm->tm_wday = (int) st.wDayOfWeek;
ptm->tm_isdst = 0;
{
int yday = ptm->tm_mday - 1;
int m = ptm->tm_mon;
int leap_year = 0;
if (((st.wYear % 4) == 0 && (st.wYear % 100) > 0) ||
((st.wYear % 400) == 0)) {
leap_year = 1;
}
while (m > 0) {
yday +=days_in_month[leap_year][m];
--m;
}
ptm->tm_yday = yday;
}
return ptm;
}
time_t
sys_mktime(struct tm *ptm)
{
FILETIME ft;
SYSTEMTIME st;
int dst = 0;
time_t epochs;
memset(&st,0,sizeof(st));
/* Convert relevant parts of truct tm to SYSTEMTIME */
st.wYear = (USHORT) (ptm->tm_year + 1900);
st.wMonth = (USHORT) (ptm->tm_mon + 1);
st.wDay = (USHORT) ptm->tm_mday;
st.wHour = (USHORT) ptm->tm_hour;
st.wMinute = (USHORT) ptm->tm_min;
st.wSecond = (USHORT) ptm->tm_sec;
SystemTimeToFileTime(&st,&ft);
/* ft is now some kind of local file time, but it may be wrong depending
on what is in the tm_dst field. We need to manually convert it to
UTC before turning it into epochs */
if (have_static_tzi) {
FILETIME dst_start, dst_stop;
ULARGE_INTEGER ull_start,ull_stop,ull_ft;
FILETIME_TO_ULI(ull_ft,ft);
/* Correct everything except DST */
ull_ft.QuadPart += (static_tzi.Bias+static_tzi.StandardBias)
* LL_LITERAL(60) * TICKS_PER_SECOND;
/* Determine if DST is active */
if (ptm->tm_isdst >= 0) {
dst = ptm->tm_isdst;
} else if (static_tzi.DaylightDate.wMonth != 0){
/* This is how windows mktime does it, meaning it does not
take nonexisting local times into account */
get_dst_switchtime(st.wYear,static_tzi.DaylightDate,
static_tzi.Bias+static_tzi.StandardBias,
&dst_start);
get_dst_switchtime(st.wYear,static_tzi.StandardDate,
static_tzi.Bias+static_tzi.StandardBias+
static_tzi.DaylightBias,
&dst_stop);
FILETIME_TO_ULI(ull_start,dst_start);
FILETIME_TO_ULI(ull_stop,dst_stop);
if ((ull_ft.QuadPart >= ull_start.QuadPart) &&
(ull_ft.QuadPart < ull_stop.QuadPart)) {
/* We are in DST */
dst = 1;
}
}
/* Correct for DST */
if (dst) {
ull_ft.QuadPart += static_tzi.DaylightBias *
LL_LITERAL(60) * TICKS_PER_SECOND;
}
epochs = ((ull_ft.QuadPart / TICKS_PER_SECOND) - EPOCH_JULIAN_DIFF);
} else {
/* No DST, life is easy... */
FILETIME lft;
LocalFileTimeToFileTime(&ft,&lft);
FILETIME_TO_EPOCH(epochs,lft);
}
/* Normalize the struct tm */
sys_localtime_r(&epochs,ptm);
return epochs;
}
void
sys_gettimeofday(SysTimeval *tv)
{
SYSTEMTIME t;
FILETIME ft;
ULARGE_INTEGER ull;
GetSystemTime(&t);
SystemTimeToFileTime(&t, &ft);
FILETIME_TO_ULI(ull,ft);
tv->tv_usec = (long) ((ull.QuadPart / LL_LITERAL(10)) %
LL_LITERAL(1000000));
tv->tv_sec = (long) ((ull.QuadPart / LL_LITERAL(10000000)) -
EPOCH_JULIAN_DIFF);
}
ErtsSystemTime
erts_os_system_time(void)
{
SYSTEMTIME t;
FILETIME ft;
ULARGE_INTEGER ull;
ErtsSystemTime stime;
GetSystemTime(&t);
SystemTimeToFileTime(&t, &ft);
FILETIME_TO_ULI(ull,ft);
/* now in 100 ns units */
stime = (ErtsSystemTime) ull.QuadPart;
stime -= (((ErtsSystemTime) EPOCH_JULIAN_DIFF)
* ((ErtsSystemTime) (10*1000*1000)));
stime /= (ErtsSystemTime) (10*1000);
if (internal_state.r.o.using_get_tick_count_time_unit) {
stime <<= ERTS_GET_TICK_COUNT_TIME_UNIT_SHIFT;
return stime;
}
return ((ErtsSystemTime)
erts_time_unit_conversion(stime,
(Uint32) 1000,
(Uint32) ERTS_MONOTONIC_TIME_UNIT));
}
clock_t
sys_times(SysTimes *buffer) {
clock_t kernel_ticks = (GetTickCount() /
(1000 / SYS_CLK_TCK)) & 0x7FFFFFFF;
FILETIME dummy;
LONGLONG user;
LONGLONG system;
buffer->tms_utime = buffer->tms_stime = buffer->tms_cutime =
buffer->tms_cstime = 0;
if (GetProcessTimes(GetCurrentProcess(), &dummy, &dummy,
(FILETIME *) &system, (FILETIME *) &user) == 0)
return kernel_ticks;
system /= (LONGLONG)(10000000 / SYS_CLK_TCK);
user /= (LONGLONG)(10000000 / SYS_CLK_TCK);
buffer->tms_utime = (clock_t) (user & LL_LITERAL(0x7FFFFFFF));
buffer->tms_stime = (clock_t) (system & LL_LITERAL(0x7FFFFFFF));
return kernel_ticks;
}