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