1/* 2 Title: Time functions. 3 Author: Dave Matthews, Cambridge University Computer Laboratory 4 5 Copyright (c) 2000 6 Cambridge University Technical Services Limited 7 8 Further development copyright David C.J. Matthews 2011,12,16 9 10 This library is free software; you can redistribute it and/or 11 modify it under the terms of the GNU Lesser General Public 12 License version 2.1 as published by the Free Software Foundation. 13 14 This library is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 Lesser General Public License for more details. 18 19 You should have received a copy of the GNU Lesser General Public 20 License along with this library; if not, write to the Free Software 21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 23*/ 24 25#ifdef HAVE_CONFIG_H 26#include "config.h" 27#elif defined(_WIN32) 28#include "winconfig.h" 29#else 30#error "No configuration file" 31#endif 32 33#ifdef HAVE_STDLIB_H 34#include <stdlib.h> 35#endif 36 37#ifdef HAVE_LOCALE_H 38#include <locale.h> 39#endif 40 41#ifdef HAVE_SYS_PARAM_H 42#include <sys/param.h> 43#endif 44 45#ifdef HAVE_TIME_H 46#include <time.h> 47#endif 48 49#ifdef HAVE_SYS_TIMES_H 50#include <sys/times.h> 51#endif 52 53#ifdef HAVE_SYS_TIME_H 54#include <sys/time.h> 55#endif 56 57#ifdef HAVE_SYS_RESOURCE_H 58#include <sys/resource.h> 59#endif 60 61#ifdef HAVE_SYS_TYPES_H 62#include <sys/types.h> 63#endif 64 65#ifdef HAVE_SYS_STAT_H 66#include <sys/stat.h> 67#endif 68 69#ifdef HAVE_SYS_SIGNAL_H 70#include <sys/signal.h> 71#endif 72 73#ifdef HAVE_ERRNO_H 74#include <errno.h> 75#endif 76 77#ifdef HAVE_UNISTD_H 78#include <unistd.h> 79#endif 80 81#ifdef HAVE_STRING_H 82#include <string.h> 83#endif 84 85#ifdef HAVE_LIMITS_H 86#include <limits.h> 87#endif 88 89#ifdef HAVE_ASSERT_H 90#include <assert.h> 91#define ASSERT(x) assert(x) 92#else 93#define ASSERT(x) 0 94#endif 95 96#ifdef HAVE_STDIO_H 97#include <stdio.h> 98#endif 99 100#ifdef HAVE_WINDOWS_H 101#include <windows.h> 102#endif 103 104#include <limits> 105// Windows headers define min/max macros, which messes up trying to use std::numeric_limits<T>::min/max() 106#ifdef min 107#undef min 108#endif 109#ifdef max 110#undef max 111#endif 112 113#include "locking.h" 114#include "globals.h" 115#include "arb.h" 116#include "run_time.h" 117#include "sys.h" 118#include "timing.h" 119#include "polystring.h" 120#include "save_vec.h" 121#include "rts_module.h" 122#include "processes.h" 123#include "heapsizing.h" 124#include "rtsentry.h" 125 126extern "C" { 127 POLYEXTERNALSYMBOL POLYUNSIGNED PolyTimingGeneral(PolyObject *threadId, PolyWord code, PolyWord arg); 128} 129 130#if (defined(_WIN32) && ! defined(__CYGWIN__)) 131/* Windows file times are 64-bit numbers representing times in 132 tenths of a microsecond. */ 133#define TICKS_PER_MICROSECOND 10 134 135#ifdef __GNUC__ 136#define SECSSINCE1601 11644473600LL 137#else 138#define SECSSINCE1601 11644473600 139#endif 140 141#else 142/* For Unix return times in microseconds. */ 143#define TICKS_PER_MICROSECOND 1 144#endif 145 146/* 147 The original Poly timing functions used a variety of timing bases 148 (e.g. seconds, tenths of a second). The old functions have been 149 retained but the intention is to phase them out in favour of new 150 functions. Most of these are handled through the timing_dispatch 151 function. 152 153 The intention behind the timing functions is to make use of the 154 arbitrary precision arithmetic to allow for a wider range of dates 155 than the usual mktime range of 1970 to 2036. We also want to handle 156 more accurate timing than per second or per microsecond where the 157 operating system provides it. 158*/ 159 160#if (defined(_WIN32) && ! defined(__CYGWIN__)) 161static FILETIME startTime; 162#define StrToLL _strtoi64 163#else 164static struct timeval startTime; 165#define StrToLL strtoll 166#endif 167 168#if(!(defined(HAVE_GMTIME_R) && defined(HAVE_LOCALTIME_R))) 169// gmtime and localtime are not re-entrant so if we don't have the 170// re-entrant versions we need to use a lock. 171static PLock timeLock("Timing"); 172#endif 173 174#define XSTR(X) STR(X) 175#define STR(X) #X 176 177static Handle timing_dispatch_c(TaskData *taskData, Handle args, Handle code) 178{ 179 unsigned c = get_C_unsigned(taskData, code->Word()); 180 switch (c) 181 { 182 case 0: /* Get ticks per microsecond. */ 183 return Make_arbitrary_precision(taskData, TICKS_PER_MICROSECOND); 184 case 1: /* Return time since the time base. */ 185 { 186#if (defined(_WIN32) && ! defined(__CYGWIN__)) 187 FILETIME ft; 188 GetSystemTimeAsFileTime(&ft); 189 return Make_arb_from_Filetime(taskData, ft); 190#else 191 struct timeval tv; 192 if (gettimeofday(&tv, NULL) != 0) 193 raise_syscall(taskData, "gettimeofday failed", errno); 194 return Make_arb_from_pair_scaled(taskData, tv.tv_sec, tv.tv_usec, 1000000); 195#endif 196 } 197 case 2: /* Return the base year. This is the year which corresponds to 198 zero in the timing sequence. */ 199#if (defined(_WIN32) && ! defined(__CYGWIN__)) 200 return Make_arbitrary_precision(taskData, 1601); 201#else 202 return Make_arbitrary_precision(taskData, 1970); 203#endif 204 205 case 3: /* In both Windows and Unix the time base is 1st of January 206 in the base year. This function is provided just in case 207 we are running on a system with a different base. It 208 returns the number of seconds after 1st January of the 209 base year that corresponds to zero of the time base. */ 210 return Make_arbitrary_precision(taskData, 0); 211 212 case 4: /* Return the time offset which applied/will apply at the 213 specified time (in seconds). */ 214 { 215 int localoff = 0; 216 time_t theTime; 217 int day = 0; 218#if (defined(HAVE_GMTIME_R) || defined(HAVE_LOCALTIME_R)) 219 struct tm result; 220#endif 221#if (defined(_WIN32) && ! defined(__CYGWIN__)) 222 /* Although the offset is in seconds it is since 1601. */ 223 FILETIME ftSeconds; // Not really a file-time because it's a number of seconds. 224 getFileTimeFromArb(taskData, args, &ftSeconds); /* May raise exception. */ 225 ULARGE_INTEGER liTime; 226 liTime.HighPart = ftSeconds.dwHighDateTime; 227 liTime.LowPart = ftSeconds.dwLowDateTime; 228 theTime = (long)(liTime.QuadPart - SECSSINCE1601); 229#else 230 theTime = get_C_long(taskData, DEREFWORD(args)); /* May raise exception. */ 231#endif 232 233 { 234#ifdef HAVE_GMTIME_R 235 struct tm *loctime = gmtime_r(&theTime, &result); 236#else 237 PLocker lock(&timeLock); 238 struct tm *loctime = gmtime(&theTime); 239#endif 240 if (loctime == NULL) raise_exception0(taskData, EXC_size); 241 localoff = (loctime->tm_hour*60 + loctime->tm_min)*60 + loctime->tm_sec; 242 day = loctime->tm_yday; 243 } 244 245 { 246 247#ifdef HAVE_LOCALTIME_R 248 struct tm *loctime = localtime_r(&theTime, &result); 249#else 250 PLocker lock(&timeLock); 251 struct tm *loctime = localtime(&theTime); 252#endif 253 if (loctime == NULL) raise_exception0(taskData, EXC_size); 254 localoff -= (loctime->tm_hour*60 + loctime->tm_min)*60 + loctime->tm_sec; 255 if (loctime->tm_yday != day) 256 { 257 // Different day - have to correct it. We can assume that there 258 // is at most one day to correct. 259 if (day == loctime->tm_yday+1 || (day == 0 && loctime->tm_yday >= 364)) 260 localoff += 24*60*60; 261 else localoff -= 24*60*60; 262 } 263 } 264 265 return Make_arbitrary_precision(taskData, localoff); 266 } 267 268 case 5: /* Find out if Summer Time (daylight saving) was/will be in effect. */ 269 { 270 time_t theTime; 271#if (defined(_WIN32) && ! defined(__CYGWIN__)) 272 FILETIME ftSeconds; // Not really a file-time because it's a number of seconds. 273 getFileTimeFromArb(taskData, args, &ftSeconds); /* May raise exception. */ 274 ULARGE_INTEGER liTime; 275 liTime.HighPart = ftSeconds.dwHighDateTime; 276 liTime.LowPart = ftSeconds.dwLowDateTime; 277 theTime = (long)(liTime.QuadPart - SECSSINCE1601); 278#else 279 theTime = get_C_long(taskData, DEREFWORD(args)); /* May raise exception. */ 280#endif 281 int isDst = 0; 282#ifdef HAVE_LOCALTIME_R 283 struct tm result; 284 struct tm *loctime = localtime_r(&theTime, &result); 285 isDst = loctime->tm_isdst; 286#else 287 { 288 PLocker lock(&timeLock); 289 struct tm *loctime = localtime(&theTime); 290 if (loctime == NULL) raise_exception0(taskData, EXC_size); 291 isDst = loctime->tm_isdst; 292 } 293#endif 294 return Make_arbitrary_precision(taskData, isDst); 295 } 296 297 case 6: /* Call strftime. It would be possible to do much of this in 298 ML except that it requires the current locale. */ 299 { 300 struct tm time; 301 char *format, buff[2048]; 302 Handle resString; 303 /* Get the format string. */ 304 format = Poly_string_to_C_alloc(DEREFHANDLE(args)->Get(0)); 305 306 /* Copy the time information. */ 307 time.tm_year = get_C_int(taskData, DEREFHANDLE(args)->Get(1)) - 1900; 308 time.tm_mon = get_C_int(taskData, DEREFHANDLE(args)->Get(2)); 309 time.tm_mday = get_C_int(taskData, DEREFHANDLE(args)->Get(3)); 310 time.tm_hour = get_C_int(taskData, DEREFHANDLE(args)->Get(4)); 311 time.tm_min = get_C_int(taskData, DEREFHANDLE(args)->Get(5)); 312 time.tm_sec = get_C_int(taskData, DEREFHANDLE(args)->Get(6)); 313 time.tm_wday = get_C_int(taskData, DEREFHANDLE(args)->Get(7)); 314 time.tm_yday = get_C_int(taskData, DEREFHANDLE(args)->Get(8)); 315 time.tm_isdst = get_C_int(taskData, DEREFHANDLE(args)->Get(9)); 316#if (defined(_WIN32) && ! defined(__CYGWIN__)) 317 _tzset(); /* Make sure we set the current locale. */ 318#else 319 setlocale(LC_TIME, ""); 320#endif 321 /* It would be better to dynamically allocate the string rather 322 than use a fixed size but Unix unlike Windows does not distinguish 323 between an error in the input and the buffer being too small. */ 324 if (strftime(buff, sizeof(buff), format, &time) <= 0) 325 { 326 /* Error */ 327 free(format); 328 raise_exception0(taskData, EXC_size); 329 } 330 resString = taskData->saveVec.push(C_string_to_Poly(taskData, buff)); 331 free(format); 332 return resString; 333 } 334 335 case 7: /* Return User CPU time since the start. */ 336 { 337#if (defined(_WIN32) && ! defined(__CYGWIN__)) 338 FILETIME ut, ct, et, kt; 339 if (! GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) 340 raise_syscall(taskData, "GetProcessTimes failed", GetLastError()); 341 return Make_arb_from_Filetime(taskData, ut); 342#else 343 struct rusage rusage; 344 if (getrusage(RUSAGE_SELF, &rusage) != 0) 345 raise_syscall(taskData, "getrusage failed", errno); 346 return Make_arb_from_pair_scaled(taskData, rusage.ru_utime.tv_sec, 347 rusage.ru_utime.tv_usec, 1000000); 348#endif 349 } 350 351 case 8: /* Return System CPU time since the start. */ 352 { 353#if (defined(_WIN32) && ! defined(__CYGWIN__)) 354 FILETIME ct, et, kt, ut; 355 if (! GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) 356 raise_syscall(taskData, "GetProcessTimes failed", GetLastError()); 357 return Make_arb_from_Filetime(taskData, kt); 358#else 359 struct rusage rusage; 360 if (getrusage(RUSAGE_SELF, &rusage) != 0) 361 raise_syscall(taskData, "getrusage failed", errno); 362 return Make_arb_from_pair_scaled(taskData, rusage.ru_stime.tv_sec, 363 rusage.ru_stime.tv_usec, 1000000); 364#endif 365 } 366 367 case 9: /* Return GC time since the start. */ 368 return gHeapSizeParameters.getGCUtime(taskData); 369 370 case 10: /* Return real time since the start. */ 371 { 372#if (defined(_WIN32) && ! defined(__CYGWIN__)) 373 FILETIME ft; 374 GetSystemTimeAsFileTime(&ft); 375 subFiletimes(&ft, &startTime); 376 return Make_arb_from_Filetime(taskData, ft); 377#else 378 struct timeval tv; 379 if (gettimeofday(&tv, NULL) != 0) 380 raise_syscall(taskData, "gettimeofday failed", errno); 381 subTimevals(&tv, &startTime); 382 return Make_arb_from_pair_scaled(taskData, tv.tv_sec, tv.tv_usec, 1000000); 383#endif 384 } 385 386 /* These next two are used only in the Posix structure. */ 387 case 11: /* Return User CPU time used by child processes. */ 388 { 389#if (defined(_WIN32) && ! defined(__CYGWIN__)) 390 return Make_arbitrary_precision(taskData, 0); 391#else 392 struct rusage rusage; 393 if (getrusage(RUSAGE_CHILDREN, &rusage) != 0) 394 raise_syscall(taskData, "getrusage failed", errno); 395 return Make_arb_from_pair_scaled(taskData, rusage.ru_utime.tv_sec, 396 rusage.ru_utime.tv_usec, 1000000); 397#endif 398 } 399 400 case 12: /* Return System CPU time used by child processes. */ 401 { 402#if (defined(_WIN32) && ! defined(__CYGWIN__)) 403 return Make_arbitrary_precision(taskData, 0); 404#else 405 struct rusage rusage; 406 if (getrusage(RUSAGE_CHILDREN, &rusage) != 0) 407 raise_syscall(taskData, "getrusage failed", errno); 408 return Make_arb_from_pair_scaled(taskData, rusage.ru_stime.tv_sec, 409 rusage.ru_stime.tv_usec, 1000000); 410#endif 411 } 412 413 case 13: /* Return GC system time since the start. */ 414 return gHeapSizeParameters.getGCStime(taskData); 415 416 default: 417 { 418 char msg[100]; 419 sprintf(msg, "Unknown timing function: %d", c); 420 raise_exception_string(taskData, EXC_Fail, msg); 421 return 0; 422 } 423 } 424} 425 426// General interface to timing. Ideally the various cases will be made into 427// separate functions. 428POLYUNSIGNED PolyTimingGeneral(PolyObject *threadId, PolyWord code, PolyWord arg) 429{ 430 TaskData *taskData = TaskData::FindTaskForId(threadId); 431 ASSERT(taskData != 0); 432 taskData->PreRTSCall(); 433 Handle reset = taskData->saveVec.mark(); 434 Handle pushedCode = taskData->saveVec.push(code); 435 Handle pushedArg = taskData->saveVec.push(arg); 436 Handle result = 0; 437 438 try { 439 result = timing_dispatch_c(taskData, pushedArg, pushedCode); 440 } catch (...) { } // If an ML exception is raised 441 442 taskData->saveVec.reset(reset); 443 taskData->PostRTSCall(); 444 if (result == 0) return TAGGED(0).AsUnsigned(); 445 else return result->Word().AsUnsigned(); 446} 447 448#ifdef HAVE_WINDOWS_H 449void addFiletimes(FILETIME *result, const FILETIME *x) 450{ 451 ULARGE_INTEGER liA, liB; 452 liA.LowPart = result->dwLowDateTime; 453 liA.HighPart = result->dwHighDateTime; 454 liB.LowPart = x->dwLowDateTime; 455 liB.HighPart = x->dwHighDateTime; 456 liA.QuadPart += liB.QuadPart; 457 result->dwLowDateTime = liA.LowPart; 458 result->dwHighDateTime = liA.HighPart; 459} 460 461void subFiletimes(FILETIME *result, const FILETIME *x) 462{ 463 ULARGE_INTEGER liA, liB; 464 liA.LowPart = result->dwLowDateTime; 465 liA.HighPart = result->dwHighDateTime; 466 liB.LowPart = x->dwLowDateTime; 467 liB.HighPart = x->dwHighDateTime; 468 liA.QuadPart -= liB.QuadPart; 469 result->dwLowDateTime = liA.LowPart; 470 result->dwHighDateTime = liA.HighPart; 471} 472 473float filetimeToSeconds(const FILETIME *x) 474{ 475 ULARGE_INTEGER ul; 476 ul.LowPart = x->dwLowDateTime; 477 ul.HighPart = x->dwHighDateTime; 478 return (float)ul.QuadPart / (float)1.0E7; 479} 480 481void FileTimeTime::fromSeconds(unsigned u) 482{ 483 ULARGE_INTEGER li; 484 li.QuadPart = (ULONGLONG)u * TICKS_PER_MICROSECOND * 1000000; 485 t.dwLowDateTime = li.LowPart; 486 t.dwHighDateTime = li.HighPart; 487} 488 489void FileTimeTime::add(const FileTimeTime &f) 490{ 491 addFiletimes(&t, &f.t); 492} 493 494void FileTimeTime::sub(const FileTimeTime &f) 495{ 496 subFiletimes(&t, &f.t); 497} 498 499float FileTimeTime::toSeconds(void) 500{ 501 return filetimeToSeconds(&t); 502} 503 504#endif 505 506#ifdef HAVE_SYS_TIME_H 507void addTimevals(struct timeval *result, const struct timeval *x) 508{ 509 long uSecs = result->tv_usec + x->tv_usec; 510 result->tv_sec += x->tv_sec; 511 if (uSecs >= 1000000) { result->tv_sec++; uSecs -= 1000000; } 512 result->tv_usec = uSecs; 513} 514 515void subTimevals(struct timeval *result, const struct timeval *x) 516{ 517 long uSecs = result->tv_usec - x->tv_usec; 518 result->tv_sec -= x->tv_sec; 519 if (uSecs < 0) { result->tv_sec--; uSecs += 1000000; } 520 result->tv_usec = uSecs; 521} 522 523float timevalToSeconds(const struct timeval *x) 524{ 525 return (float)x->tv_sec + (float)x->tv_usec / 1.0E6; 526} 527 528void TimeValTime::add(const TimeValTime &f) 529{ 530 addTimevals(&t, &f.t); 531} 532 533void TimeValTime::sub(const TimeValTime &f) 534{ 535 subTimevals(&t, &f.t); 536} 537 538#endif 539 540 541struct _entrypts timingEPT[] = 542{ 543 { "PolyTimingGeneral", (polyRTSFunction)&PolyTimingGeneral}, 544 545 { NULL, NULL} // End of list. 546}; 547 548class Timing: public RtsModule 549{ 550public: 551 virtual void Init(void); 552}; 553 554// Declare this. It will be automatically added to the table. 555static Timing timingModule; 556 557void Timing::Init(void) 558{ 559#if (defined(_WIN32) && ! defined(__CYGWIN__)) 560 // Record an initial time of day to use as the basis of real timing 561 GetSystemTimeAsFileTime(&startTime); 562#else 563 gettimeofday(&startTime, NULL); 564#endif 565} 566 567time_t getBuildTime(void) 568{ 569 char *source_date_epoch = getenv("SOURCE_DATE_EPOCH"); 570 if (source_date_epoch) { 571 errno = 0; 572 char *endptr; 573 long long epoch = StrToLL(source_date_epoch, &endptr, 10); 574 if ((errno == ERANGE && (epoch == LLONG_MIN || epoch == LLONG_MAX)) || (errno != 0 && epoch == 0)) { 575 fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: " XSTR(StrToLL) ": %s\n", strerror(errno)); 576 goto err; 577 } 578 if (endptr == source_date_epoch) { 579 fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: No digits were found: %s\n", endptr); 580 goto err; 581 } 582 if (*endptr != '\0') { 583 fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: Trailing garbage: %s\n", endptr); 584 goto err; 585 } 586 if (epoch < (long long)std::numeric_limits<time_t>::min()) { 587 fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: value must be greater than or equal to: %lld but was found to be: %lld\n", (long long)std::numeric_limits<time_t>::min(), epoch); 588 goto err; 589 } 590 if (epoch > (long long)std::numeric_limits<time_t>::max()) { 591 fprintf(stderr, "Environment variable $SOURCE_DATE_EPOCH: value must be smaller than or equal to: %lld but was found to be: %lld\n", (long long)std::numeric_limits<time_t>::max(), epoch); 592 goto err; 593 } 594 return (time_t) epoch; 595 } 596err: 597 return time(NULL); 598} 599