1 module mecca.platform.os.linux.time;
2 
3 version (linux):
4 package(mecca):
5 
6 struct Timer
7 {
8 @nogc:
9 
10     import core.sys.posix.signal : siginfo_t;
11     import core.sys.posix.time : timer_t;
12     import core.time : Duration;
13 
14     import mecca.platform.os.linux : OSSignal;
15 
16     alias Callback = extern (C) void function();
17 
18     private
19     {
20         Duration interval;
21         Callback callback;
22 
23         OSSignal hangDetectorSig;
24         timer_t hangDetectorTimerId;
25     }
26 
27     this(Duration interval, Callback callback) nothrow
28     in
29     {
30         assert(callback !is null);
31     }
32     do
33     {
34         this.interval = interval;
35         this.callback = callback;
36     }
37 
38     void start() @trusted
39     {
40         import core.sys.posix.signal : SA_ONSTACK,
41             SA_RESTART, SA_SIGINFO, sigaction, sigaction_t, sigevent, signal,
42             SIGRTMIN, SIG_DFL;
43 
44         import core.sys.posix.time : CLOCK_MONOTONIC, itimerspec, timer_create,
45             timer_delete, timer_settime;
46 
47         import mecca.lib.exception : DBG_ASSERT, ASSERT, errnoEnforceNGC;
48         import mecca.log : INFO;
49         import mecca.platform.os.linux : gettid;
50 
51         hangDetectorSig = cast(OSSignal)SIGRTMIN;
52         scope(failure) hangDetectorSig = OSSignal.init;
53 
54         sigaction_t sa;
55         sa.sa_flags = SA_RESTART | SA_ONSTACK | SA_SIGINFO;
56         sa.sa_sigaction = cast(typeof(sa.sa_sigaction)) callback;
57         errnoEnforceNGC(sigaction(hangDetectorSig, &sa, null) == 0, "sigaction() for registering hang detector signal failed");
58         scope(failure) signal(hangDetectorSig, SIG_DFL);
59 
60         enum SIGEV_THREAD_ID = 4;
61         // SIGEV_THREAD_ID (Linux-specific)
62         // As  for  SIGEV_SIGNAL, but the signal is targeted at the thread whose ID is given in sigev_notify_thread_id,
63         // which must be a thread in the same process as the caller.  The sigev_notify_thread_id field specifies a kernel
64         // thread ID, that is, the value returned by clone(2) or gettid(2).  This flag is intended only for use by
65         // threading libraries.
66 
67         sigevent sev;
68         sev.sigev_notify = SIGEV_THREAD_ID;
69         sev.sigev_signo = hangDetectorSig;
70         sev.sigev_value.sival_ptr = &hangDetectorTimerId;
71         sev._sigev_un._tid = gettid();
72 
73         errnoEnforceNGC(timer_create(CLOCK_MONOTONIC, &sev, &hangDetectorTimerId) == 0,
74                 "timer_create for hang detector");
75         ASSERT!"hangDetectorTimerId is null"(hangDetectorTimerId !is timer_t.init);
76         scope(failure) timer_delete(hangDetectorTimerId);
77 
78         itimerspec its;
79 
80         interval.split!("seconds", "nsecs")(its.it_value.tv_sec, its.it_value.tv_nsec);
81         its.it_interval = its.it_value;
82         INFO!"Hang detector will wake up every %s seconds and %s nsecs"(its.it_interval.tv_sec, its.it_interval.tv_nsec);
83 
84         errnoEnforceNGC(timer_settime(hangDetectorTimerId, 0, &its, null) == 0, "timer_settime");
85     }
86 
87     void cancel() @trusted nothrow
88     {
89         import core.sys.posix.signal : signal, SIG_DFL;
90         import core.sys.posix.time : timer_delete;
91 
92         if (hangDetectorSig is OSSignal.SIGNONE)
93             return; // Hang detector was not initialized
94 
95         timer_delete(hangDetectorTimerId);
96         signal(hangDetectorSig, SIG_DFL);
97         hangDetectorSig = OSSignal.init;
98     }
99 
100     bool isSet() const pure @safe nothrow
101     {
102         return hangDetectorSig != OSSignal.SIGNONE;
103     }
104 }
105 
106 auto calculateCycles()
107 {
108     import core.sys.posix.signal : timespec;
109     import core.sys.posix.time: clock_gettime, CLOCK_MONOTONIC, nanosleep;
110 
111     import std.exception: enforce, errnoEnforce;
112     import std.file: readText;
113     import std..string: indexOf;
114     import std.typecons: tuple;
115 
116     import mecca.platform.x86: readTSC;
117 
118     enforce(readText("/proc/cpuinfo").indexOf("constant_tsc") >= 0,
119         "constant_tsc not supported");
120 
121     timespec sleepTime = timespec(0, 200_000_000);
122     timespec t0, t1;
123 
124     auto rc1 = clock_gettime(CLOCK_MONOTONIC, &t0);
125     auto cyc0 = readTSC();
126     auto rc2 = nanosleep(&sleepTime, null);
127     auto rc3 = clock_gettime(CLOCK_MONOTONIC, &t1);
128     auto cyc1 = readTSC();
129 
130     errnoEnforce(rc1 == 0, "clock_gettime");
131     errnoEnforce(rc2 == 0, "nanosleep");   // we hope we won't be interrupted by a signal here
132     errnoEnforce(rc3 == 0, "clock_gettime");
133 
134     const nsecs = (t1.tv_sec - t0.tv_sec) * 1_000_000_000UL +
135         (t1.tv_nsec  - t0.tv_nsec);
136     const cyclesPerSecond = cast(long)((cyc1 - cyc0) / (nsecs / 1E9));
137     const cyclesPerMsec = cyclesPerSecond / 1_000;
138     const cyclesPerUsec = cyclesPerSecond / 1_000_000;
139 
140     return tuple!("perSecond", "perMsec", "perUsec")(
141         cyclesPerSecond, cyclesPerMsec, cyclesPerUsec);
142 }