File Coverage

lib/POSIX/RT/Timer.xs
Criterion Covered Total %
statement 93 127 73.2
branch 63 138 45.6
condition n/a
subroutine n/a
pod n/a
total 156 265 58.8


line stmt bran cond sub pod time code
1             /*
2             * This software is copyright (c) 2010 by Leon Timmermans .
3             *
4             * This is free software; you can redistribute it and/or modify it under
5             * the same terms as perl itself.
6             *
7             */
8              
9             #define PERL_NO_GET_CONTEXT
10             #define PERL_REENTR_API 1
11              
12             #include "EXTERN.h"
13             #include "perl.h"
14             #include "XSUB.h"
15             #define NEED_mg_findext
16             #include "ppport.h"
17              
18             #include
19             #include
20              
21             #define die_sys(format) Perl_croak(aTHX_ format, strerror(errno))
22              
23             typedef struct { const char* key; STRLEN key_length; clockid_t value; } map[];
24              
25             static map clocks = {
26             { STR_WITH_LEN("realtime") , CLOCK_REALTIME }
27             #ifdef CLOCK_REALTIME_COARSE
28             , { STR_WITH_LEN("realtime_coarse"), CLOCK_REALTIME_COARSE }
29             #endif
30             #ifdef CLOCK_REALTIME_ALARM
31             , { STR_WITH_LEN("realtime_alarm"), CLOCK_REALTIME_ALARM }
32             #endif
33             #ifdef CLOCK_MONOTONIC
34             , { STR_WITH_LEN("monotonic"), CLOCK_MONOTONIC }
35             #elif defined CLOCK_HIGHRES
36             , { STR_WITH_LEN("monotonic"), CLOCK_HIGHRES }
37             #endif
38             #ifdef CLOCK_MONOTONIC_RAW
39             , { STR_WITH_LEN("monotonic_raw"), CLOCK_MONOTONIC_RAW }
40             #endif
41             #ifdef CLOCK_MONOTONIC_COARSE
42             , { STR_WITH_LEN("monotonic_coarse"), CLOCK_MONOTONIC_COARSE }
43             #endif
44             #ifdef CLOCK_PROCESS_CPUTIME_ID
45             , { STR_WITH_LEN("process"), CLOCK_PROCESS_CPUTIME_ID }
46             #elif defined CLOCK_PROF
47             , { STR_WITH_LEN("process"), CLOCK_PROF }
48             #endif
49             #ifdef CLOCK_THREAD_CPUTIME_ID
50             , { STR_WITH_LEN("thread"), CLOCK_THREAD_CPUTIME_ID }
51             #endif
52             #ifdef CLOCK_UPTIME
53             , { STR_WITH_LEN("uptime"), CLOCK_UPTIME }
54             #endif
55             #ifdef CLOCK_BOOTTIME
56             , { STR_WITH_LEN("boottime"), CLOCK_BOOTTIME }
57             #endif
58             #ifdef CLOCK_BOOTTIME_ALARM
59             , { STR_WITH_LEN("boottime_alarm"), CLOCK_BOOTTIME_ALARM }
60             #endif
61             #ifdef CLOCK_VIRTUAL
62             , { STR_WITH_LEN("virtual"), CLOCK_VIRTUAL }
63             #endif
64             #ifdef CLOCK_TAI
65             , { STR_WITH_LEN("tai"), CLOCK_TAI }
66             #endif
67             };
68              
69 4           static clockid_t S_get_clockid(pTHX_ SV* clock_name) {
70             int i;
71             STRLEN length;
72 4 50         const char* clock_ptr = SvPV(clock_name, length);
73 7 50         for (i = 0; i < sizeof clocks / sizeof *clocks; ++i) {
74 7 100         if (clocks[i].key_length == length && strEQ(clock_ptr, clocks[i].key))
    50          
75 4           return clocks[i].value;
76             }
77 0           Perl_croak(aTHX_ "No such timer '%s' known", clock_name);
78             }
79             #define get_clockid(name) S_get_clockid(aTHX_ name)
80              
81             #define NANO_SECONDS 1000000000
82              
83             static NV timespec_to_nv(struct timespec* time) {
84 1           return time->tv_sec + time->tv_nsec / (double)NANO_SECONDS;
85             }
86              
87             static void nv_to_timespec(NV input, struct timespec* output) {
88 11           output->tv_sec = (time_t) floor(input);
89 6           output->tv_nsec = (long) ((input - output->tv_sec) * NANO_SECONDS);
90             }
91              
92 4           static int timer_destroy(pTHX_ SV* var, MAGIC* magic) {
93 4 50         if (timer_delete(*(timer_t*)magic->mg_ptr))
94 0           die_sys("Can't delete timer: %s");
95 4           }
96              
97             static const MGVTBL timer_magic = { NULL, NULL, NULL, NULL, timer_destroy };
98              
99 2           static MAGIC* S_get_magic(pTHX_ SV* ref, const char* funcname, const MGVTBL* vtbl) {
100             SV* value;
101             MAGIC* magic;
102 1 50         if (!SvROK(ref) || !(value = SvRV(ref)) || !SvMAGICAL(value) || (magic = mg_findext(value, PERL_MAGIC_ext, vtbl)) == NULL)
    50          
    50          
    50          
103 0           Perl_croak(aTHX_ "Could not %s: this variable is not a timer", funcname);
104 1           return magic;
105             }
106             #define get_magic(ref, funcname, vtbl) S_get_magic(aTHX_ ref, funcname, vtbl)
107             #define get_timer(ref, funcname) (*(timer_t*)get_magic(ref, funcname, &timer_magic)->mg_ptr)
108              
109 28           static clockid_t S_get_clock(pTHX_ SV* ref, const char* funcname) {
110             SV* value;
111 14 50         if (!SvROK(ref) || !(value = SvRV(ref)))
    50          
112 0           Perl_croak(aTHX_ "Could not %s: this variable is not a clock", funcname);
113 14 50         return SvUV(value);
114             }
115             #define get_clock(ref, func) S_get_clock(aTHX_ ref, func)
116              
117             #if defined(SIGEV_THREAD_ID) && defined(SYS_gettid)
118             #include
119             #define gettid() syscall(SYS_gettid)
120             #ifndef sigev_notify_thread_id
121             #define sigev_notify_thread_id _sigev_un._tid
122             #endif
123             #endif
124              
125 4           static SV* S_create_clock(pTHX_ clockid_t clockid, SV* class) {
126             SV *tmp, *retval;
127 4           tmp = newSViv(clockid);
128 4           retval = newRV_noinc(tmp);
129 4           sv_bless(retval, gv_stashsv(class, 0));
130 4           SvREADONLY_on(tmp);
131 4           return retval;
132             }
133             #define create_clock(clockid, class) S_create_clock(aTHX_ clockid, class)
134              
135             #if defined(_POSIX_CLOCK_SELECTION) && _POSIX_CLOCK_SELECTION >= 0
136 5           static int my_clock_nanosleep(pTHX_ clockid_t clockid, int flags, const struct timespec* request, struct timespec* remain) {
137             int ret;
138 5           ret = clock_nanosleep(clockid, flags, request, remain);
139 5 100         if (ret != 0) {
140 2           errno = ret;
141 2 50         if (ret != EINTR)
142 0           die_sys("Could not sleep: %s");
143             }
144 5           return ret;
145             }
146             #endif
147              
148             #define clock_nanosleep(clockid, flags, request, remain) my_clock_nanosleep(aTHX_ clockid, flags, request, remain)
149              
150             #if defined(USE_ITHREADS) && defined(_POSIX_THREAD_CPUTIME) && _POSIX_THREAD_CPUTIME >= 0
151             static pthread_t* S_get_pthread(pTHX_ SV* thread_handle) {
152             SV* tmp;
153             pthread_t* ret;
154             dSP;
155             ENTER;
156             SAVETMPS;
157             PUSHMARK(SP);
158             PUSHs(thread_handle);
159             PUTBACK;
160             call_method("_handle", G_SCALAR);
161             SPAGAIN;
162             tmp = POPs;
163             ret = INT2PTR(pthread_t* ,SvUV(tmp));
164             FREETMPS;
165             LEAVE;
166             return ret;
167             }
168             #define get_pthread(handle) S_get_pthread(aTHX_ handle)
169             #endif
170              
171             #define undef &PL_sv_undef
172              
173             typedef struct _timer_init {
174             clockid_t clockid;
175             IV signo;
176             IV ident;
177             struct itimerspec itimer;
178             int flags;
179             } timer_init;
180              
181 4           static void S_timer_args(pTHX_ timer_init* para, SV** begin, Size_t items) {
182             int i;
183 15 100         for(i = 0; i < items; i += 2) {
184             const char* current;
185             STRLEN curlen;
186 11           SV *key = begin[i], *value = begin[i+1];
187 11 50         current = SvPV(key, curlen);
188 11 100         if (curlen == 5) {
189 6 100         if (strEQ(current, "clock"))
190 1 50         para->clockid = SvROK(value) ? get_clock(value, "create timer") : get_clockid(value);
191 5 100         else if (strEQ(current, "value"))
192 4 50         nv_to_timespec(SvNV(value), ¶->itimer.it_value);
193 1 50         else if (strEQ(current, "ident"))
194 1 50         para->ident = SvIV(value);
195             else
196             goto fail;
197             }
198 5 100         else if (curlen == 6 && strEQ(current, "signal"))
    50          
199 4 50         para->signo = (SvIOK(value) || looks_like_number(value)) ? SvIV(value) : whichsig(SvPV_nolen(value));
    0          
    50          
    0          
200 1 50         else if (curlen == 8) {
201 1 50         if (strEQ(current, "interval"))
202 1 50         nv_to_timespec(SvNV(value), ¶->itimer.it_interval);
203 0 0         else if (strEQ(current, "absolute"))
204 0           para->flags |= TIMER_ABSTIME;
205             else
206             goto fail;
207             }
208             else
209 0           fail: Perl_croak(aTHX_ "Unknown option '%s'", current);
210             }
211 4           }
212             #define timer_args(para, begin, items) S_timer_args(aTHX_ para, begin, items)
213              
214 4           static SV* S_timer_instantiate(pTHX_ timer_init* para, const char* class, Size_t classlength) {
215             timer_t* timer;
216 4           struct sigevent event = { 0 };
217             SV *tmp, *retval;
218             MAGIC* mg;
219              
220 4 50         if (para->signo < 0)
221 0           Perl_croak(aTHX_ "No valid signal was given");
222              
223             #ifdef gettid
224             event.sigev_notify = SIGEV_THREAD_ID;
225             event.sigev_notify_thread_id = gettid();
226             #else
227             event.sigev_notify = SIGEV_SIGNAL;
228             #endif
229 4           event.sigev_signo = para->signo;
230 4           event.sigev_value.sival_int = para->ident;
231              
232 4           Newx(timer, 1, timer_t);
233              
234 4 50         if (timer_create(para->clockid, &event, timer) < 0) {
235 0           Safefree(timer);
236 0           die_sys("Couldn't create timer: %s");
237             }
238 4 50         if (timer_settime(*timer, para->flags, ¶->itimer, NULL) < 0)
239 0           die_sys("Couldn't set_time: %s");
240              
241 4           tmp = newSV(0);
242 4           retval = sv_2mortal(sv_bless(newRV_noinc(tmp), gv_stashpvn(class, classlength, 0)));
243 4           SvREADONLY_on(tmp);
244              
245 4           mg = sv_magicext(tmp, NULL, PERL_MAGIC_ext, &timer_magic, (const char*)timer, 0);
246 4           mg->mg_len = sizeof *timer;
247 4           return retval;
248             }
249             #define timer_instantiate(para, class, classlen) S_timer_instantiate(aTHX_ para, class, classlen)
250              
251             MODULE = POSIX::RT::Timer PACKAGE = POSIX::RT::Timer
252              
253             PROTOTYPES: DISABLED
254              
255             void new(class, ...)
256             SV* class;
257             PREINIT:
258             const char* class_str;
259             Size_t length;
260             PPCODE:
261 3 50         class_str = SvPV(class, length);
262 3           timer_init para = { CLOCK_REALTIME, 0, 0, 0, 0};
263 3           timer_args(¶, SP + 2, items - 1);
264 3           PUSHs(timer_instantiate(¶, class_str, length));
265              
266             UV
267             handle(self)
268             SV* self;
269             CODE:
270 0           RETVAL = (UV)get_timer(self, "id");
271             OUTPUT:
272             RETVAL
273              
274             void
275             get_timeout(self)
276             SV* self;
277             PREINIT:
278             timer_t timer;
279             struct itimerspec value;
280             PPCODE:
281 0           timer = get_timer(self, "get_timeout");
282 0 0         if (timer_gettime(timer, &value) == -1)
283 0           die_sys("Couldn't get_time: %s");
284 0 0         mXPUSHn(timespec_to_nv(&value.it_value));
285 0 0         if (GIMME_V == G_ARRAY)
    0          
286 0 0         mXPUSHn(timespec_to_nv(&value.it_interval));
287              
288             void
289             set_timeout(self, new_value, new_interval = 0, abstime = 0)
290             SV* self;
291             NV new_value;
292             NV new_interval;
293             IV abstime;
294             PREINIT:
295             timer_t timer;
296             struct itimerspec new_itimer, old_itimer;
297             PPCODE:
298 1           timer = get_timer(self, "set_timeout");
299             nv_to_timespec(new_value, &new_itimer.it_value);
300             nv_to_timespec(new_interval, &new_itimer.it_interval);
301 1 50         if (timer_settime(timer, (abstime ? TIMER_ABSTIME : 0), &new_itimer, &old_itimer) == -1)
302 0           die_sys("Couldn't set_time: %s");
303 1 50         mXPUSHn(timespec_to_nv(&old_itimer.it_value));
304 1 50         if (GIMME_V == G_ARRAY)
    50          
305 0 0         mXPUSHn(timespec_to_nv(&old_itimer.it_interval));
306              
307             IV
308             get_overrun(self)
309             SV* self;
310             PREINIT:
311             timer_t timer;
312             CODE:
313 0           timer = get_timer(self, "get_overrun");
314 0           RETVAL = timer_getoverrun(timer);
315 0 0         if (RETVAL == -1)
316 0           die_sys("Couldn't get_overrun: %s");
317             OUTPUT:
318             RETVAL
319              
320             MODULE = POSIX::RT::Timer PACKAGE = POSIX::RT::Clock
321              
322             PROTOTYPES: DISABLED
323              
324             SV*
325             new(class, ...)
326             SV* class;
327             PREINIT:
328             clockid_t clockid;
329             CODE:
330 3 50         clockid = items > 1 ? get_clockid(ST(1)) : CLOCK_REALTIME;
331 3           RETVAL = create_clock(clockid, class);
332             OUTPUT:
333             RETVAL
334              
335             UV
336             handle(self)
337             SV* self;
338             CODE:
339 0           RETVAL = (UV)get_clock(self, "id");
340             OUTPUT:
341             RETVAL
342              
343             #if defined(_POSIX_CPUTIME) && _POSIX_CPUTIME >= 0
344             SV*
345             get_cpuclock(class, pid = undef)
346             SV* class;
347             SV* pid;
348             PREINIT:
349             clockid_t clockid;
350             CODE:
351 1 50         if (SvOK(pid) && SvROK(pid) && sv_derived_from(pid, "threads")) {
    0          
    0          
    50          
    0          
352             #if defined(USE_ITHREADS) && defined(_POSIX_THREAD_CPUTIME) && _POSIX_THREAD_CPUTIME >= 0
353             pthread_t* handle = get_pthread(pid);
354             if (pthread_getcpuclockid(*handle, &clockid) != 0)
355             die_sys("Could not get cpuclock");
356             #else
357 0           Perl_croak(aTHX_ "Can't get CPU time for threads");
358             #endif
359             }
360             else {
361 1 50         if (clock_getcpuclockid(SvOK(pid) ? SvIV(pid) : 0, &clockid) != 0)
    0          
    0          
    50          
    50          
362 0           die_sys("Could not get cpuclock");
363             }
364            
365 1           RETVAL = create_clock(clockid, class);
366             OUTPUT:
367             RETVAL
368              
369             #endif
370              
371             void
372             get_clocks(class)
373             SV* class;
374             PREINIT:
375             size_t i;
376             const size_t max = sizeof clocks / sizeof *clocks;
377             PPCODE:
378 12 100         for (i = 0; i < max; ++i)
379 11 50         mXPUSHp(clocks[i].key, clocks[i].key_length);
380 1           XSRETURN(max);
381              
382             NV
383             get_time(self)
384             SV* self;
385             PREINIT:
386             clockid_t clockid;
387             struct timespec time;
388             CODE:
389 8           clockid = get_clock(self, "get_time");
390 8 50         if (clock_gettime(clockid, &time) == -1)
391 0           die_sys("Couldn't get time: %s");
392             RETVAL = timespec_to_nv(&time);
393             OUTPUT:
394             RETVAL
395              
396             void
397             set_time(self, frac_time)
398             SV* self;
399             NV frac_time;
400             PREINIT:
401             clockid_t clockid;
402             struct timespec time;
403             CODE:
404 0           clockid = get_clock(self, "set_time");
405             nv_to_timespec(frac_time, &time);
406 0 0         if (clock_settime(clockid, &time) == -1)
407 0           die_sys("Couldn't set time: %s");
408              
409             NV
410             get_resolution(self)
411             SV* self;
412             PREINIT:
413             clockid_t clockid;
414             struct timespec time;
415             CODE:
416 1           clockid = get_clock(self, "get_resolution");
417 1 50         if (clock_getres(clockid, &time) == -1)
418 0           die_sys("Couldn't get resolution: %s");
419             RETVAL = timespec_to_nv(&time);
420             OUTPUT:
421             RETVAL
422              
423             void
424             timer(self, ...)
425             SV* self;
426             PPCODE:
427 1           timer_init para = { CLOCK_REALTIME, 0, 0, 0, 0};
428 1           timer_args(¶, SP + 2, items - 1);
429 1           para.clockid = get_clock(self, "timer");
430 1           PUSHs(timer_instantiate(¶, "POSIX::RT::Timer", 16));
431              
432             #if defined(_POSIX_CLOCK_SELECTION) && _POSIX_CLOCK_SELECTION >= 0
433             NV
434             sleep(self, frac_time, abstime = 0)
435             SV* self;
436             NV frac_time;
437             int abstime;
438             PREINIT:
439             clockid_t clockid;
440             struct timespec sleep_time, remain_time;
441             int flags;
442             CODE:
443 3           clockid = get_clock(self, "sleep");
444 3           flags = abstime ? TIMER_ABSTIME : 0;
445             nv_to_timespec(frac_time, &sleep_time);
446              
447 3 100         if (clock_nanosleep(clockid, flags, &sleep_time, &remain_time) == EINTR)
448 1 50         RETVAL = abstime ? frac_time : timespec_to_nv(&remain_time);
449             else
450             RETVAL = 0;
451             OUTPUT:
452             RETVAL
453              
454             NV
455             sleep_deeply(self, frac_time, abstime = 0)
456             SV* self;
457             NV frac_time;
458             int abstime;
459             PREINIT:
460             clockid_t clockid;
461             struct timespec sleep_time;
462             NV real_time;
463             CODE:
464 1           clockid = get_clock(self, "sleep_deeply");
465 1 50         if (abstime)
466             nv_to_timespec(frac_time, &sleep_time);
467             else {
468 1 50         if (clock_gettime(clockid, &sleep_time) == -1)
469 0           die_sys("Couldn't get time: %s");
470 1           nv_to_timespec(timespec_to_nv(&sleep_time) + frac_time, &sleep_time);
471             }
472 2 100         while (clock_nanosleep(clockid, TIMER_ABSTIME, &sleep_time, NULL) == EINTR);
473             RETVAL = 0;
474             OUTPUT:
475             RETVAL
476              
477             #endif