1 /**
2  * CPU time used by system or process.
3  * Authors:
4  *  $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov).
5  * License:
6  *  $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
7  * Copyright:
8  *  Roman Chistokhodov 2015
9  *
10  * Note: Every function may throw on fail ($(B ErrnoException) on Linux, $(B WindowsException) on Windows).
11  */
12 
13 module resusage.cpu;
14 import resusage.common;
15 
16 import std.exception;
17 
18 ///Base interface for cpu watchers
19 interface CPUWatcher
20 {
21     ///CPU time currently used, in percents.
22     @safe double current();
23 }
24 
25 version(Windows)
26 {
27     private import core.stdc.string : memcpy;
28 
29     private {
30         alias HANDLE PDH_HQUERY;
31         alias HANDLE PDH_HCOUNTER;
32         alias LONG PDH_STATUS;
33 
34         struct PDH_FMT_COUNTERVALUE
35         {
36             DWORD CStatus;
37             static union DUMMYUNIONNAME
38             {
39                 LONG     longValue;
40                 double   doubleValue;
41                 LONGLONG largeValue;
42                 LPCSTR   AnsiStringValue;
43                 LPCWSTR  WideStringValue;
44             }
45             DUMMYUNIONNAME dummyUnion;
46         };
47 
48         extern(Windows) @nogc PDH_STATUS openQueryDummy(
49             const(wchar)* szDataSource,
50             DWORD_PTR dwUserData,
51             PDH_HQUERY *phQuery) @system nothrow { return 0; }
52 
53         extern(Windows) @nogc PDH_STATUS addCounterDummy(
54             PDH_HQUERY Query,
55             const(wchar)* szFullCounterPath,
56             DWORD_PTR dwUserData,
57             PDH_HCOUNTER *phCounter) @system nothrow { return 0; }
58 
59         extern(Windows) @nogc PDH_STATUS queryDataDummy(
60             PDH_HQUERY hQuery) @system nothrow { return 0; }
61 
62         extern(Windows) @nogc PDH_STATUS formattedValueDummy(
63             PDH_HCOUNTER hCounter,
64             DWORD dwFormat,
65             LPDWORD lpdwType,
66             PDH_FMT_COUNTERVALUE* pValue) @system nothrow { return 0; }
67 
68         alias typeof(&openQueryDummy) func_PdhOpenQuery;
69         alias typeof(&addCounterDummy) func_PdhAddCounter;
70         alias typeof(&queryDataDummy) func_PdhCollectQueryData;
71         alias typeof(&formattedValueDummy) func_PdhGetFormattedCounterValue;
72 
73         __gshared func_PdhOpenQuery PdhOpenQuery;
74         __gshared func_PdhAddCounter PdhAddCounter;
75         __gshared func_PdhCollectQueryData PdhCollectQueryData;
76         __gshared func_PdhGetFormattedCounterValue PdhGetFormattedCounterValue;
77 
78         __gshared DWORD pdhError;
79 
80         enum PDH_FMT_DOUBLE = 0x00000200;
81     }
82 
83     shared static this()
84     {
85         debug import std.stdio : writeln;
86 
87         HMODULE pdhLib = LoadLibraryA("Pdh");
88         if (pdhLib) {
89             PdhOpenQuery = cast(func_PdhOpenQuery) GetProcAddress(pdhLib, "PdhOpenQueryW");
90             PdhAddCounter = cast(func_PdhAddCounter) GetProcAddress(pdhLib, "PdhAddEnglishCounterW");
91 
92             /*
93             PdhAddEnglishCounterW is defined only since Windows Vista or Windows Server 2008.
94             Load locale-dependent PdhAddCounter on older Windows versions and hope that user has english locale.
95             */
96             if (PdhAddCounter is null) {
97                 PdhAddCounter = cast(func_PdhAddCounter) GetProcAddress(pdhLib, "PdhAddCounterW");
98                 debug writeln("Warning: resusage will use PdhAddCounter, because could not find PdhAddEnglishCounter");
99             }
100 
101             PdhCollectQueryData = cast(func_PdhCollectQueryData) GetProcAddress(pdhLib, "PdhCollectQueryData");
102             PdhGetFormattedCounterValue = cast(func_PdhGetFormattedCounterValue) GetProcAddress(pdhLib, "PdhGetFormattedCounterValue");
103         }
104 
105         if (!isPdhLoaded()) {
106             pdhError = GetLastError();
107         }
108     }
109 
110     private @nogc @trusted bool isPdhLoaded() {
111         return PdhOpenQuery && PdhAddCounter && PdhCollectQueryData && PdhGetFormattedCounterValue;
112     }
113 
114     private struct PlatformSystemCPUWatcher
115     {
116         @trusted void initialize() {
117             if (!isPdhLoaded()) {
118                 throw new WindowsException(pdhError, "Pdh.dll is not loaded");
119             }
120 
121             wenforce(PdhOpenQuery(null, 0, &cpuQuery) == ERROR_SUCCESS, "Could not query pdh data");
122             wenforce(PdhAddCounter(cpuQuery, "\\Processor(_Total)\\% Processor Time"w.ptr, 0, &cpuTotal) == ERROR_SUCCESS, "Could not add pdh counter");
123             wenforce(PdhCollectQueryData(cpuQuery) == ERROR_SUCCESS, "Could not collect pdh query data");
124         }
125 
126         @trusted double current()
127         {
128             PDH_FMT_COUNTERVALUE counterVal;
129             wenforce(PdhCollectQueryData(cpuQuery) == ERROR_SUCCESS, "Could not collect pdh query data");
130             wenforce(PdhGetFormattedCounterValue(cpuTotal, PDH_FMT_DOUBLE, null, &counterVal) == ERROR_SUCCESS, "Could not format pdh counter data");
131             return counterVal.dummyUnion.doubleValue;
132         }
133 
134     private:
135         PDH_HQUERY cpuQuery;
136         PDH_HCOUNTER cpuTotal;
137     }
138 
139     private @trusted void timesHelper(int pid, ref ULARGE_INTEGER now, ref ULARGE_INTEGER sys, ref ULARGE_INTEGER user)
140     {
141         HANDLE handle = openProcess(pid);
142         scope(exit) CloseHandle(handle);
143 
144         FILETIME ftime, fsys, fuser;
145         GetSystemTimeAsFileTime(&ftime);
146         memcpy(&now, &ftime, FILETIME.sizeof);
147 
148         GetProcessTimes(handle, &ftime, &ftime, &fsys, &fuser);
149         memcpy(&sys, &fsys, FILETIME.sizeof);
150         memcpy(&user, &fuser, FILETIME.sizeof);
151     }
152 
153     private struct PlatformProcessCPUWatcher
154     {
155         @trusted void initialize(int procId) {
156             pid = procId;
157             timesHelper(pid, lastCPU, lastSysCPU, lastUserCPU);
158         }
159 
160         @trusted void initialize(HANDLE procHandle) {
161             pid = GetProcessId(procHandle);
162             timesHelper(pid, lastCPU, lastSysCPU, lastUserCPU);
163         }
164 
165         @trusted void initialize() {
166             pid = thisProcessID();
167             timesHelper(pid, lastCPU, lastSysCPU, lastUserCPU);
168         }
169 
170         @trusted double current()
171         {
172             ULARGE_INTEGER now, sys, user;
173             timesHelper(pid, now, sys, user);
174 
175             double percent = (sys.QuadPart - lastSysCPU.QuadPart) + (user.QuadPart - lastUserCPU.QuadPart);
176             percent /= (now.QuadPart - lastCPU.QuadPart);
177 
178             lastCPU = now;
179             lastUserCPU = user;
180             lastSysCPU = sys;
181 
182             return percent * 100;
183         }
184 
185         @trusted int processID() const nothrow {
186             return pid;
187         }
188 
189     private:
190         int pid;
191         ULARGE_INTEGER lastCPU, lastUserCPU, lastSysCPU;
192     }
193 } else version(linux) {
194     private @trusted void readProcStat(ref ulong totalUser, ref ulong totalUserLow, ref ulong totalSys, ref ulong totalIdle)
195     {
196         FILE* f = errnoEnforce(fopen("/proc/stat", "r"));
197         scope(exit) fclose(f);
198         errnoEnforce(fscanf(f, "cpu %llu %llu %llu %llu", &totalUser, &totalUserLow, &totalSys, &totalIdle) == 4);
199     }
200 
201     private struct PlatformSystemCPUWatcher
202     {
203         @trusted void initialize() {
204             readProcStat(lastTotalUser, lastTotalUserLow, lastTotalSys, lastTotalIdle);
205         }
206 
207         @trusted double current()
208         {
209             ulong totalUser, totalUserLow, totalSys, totalIdle;
210             readProcStat(totalUser, totalUserLow, totalSys, totalIdle);
211 
212             double percent;
213 
214             if (totalUser < lastTotalUser || totalUserLow < lastTotalUserLow ||
215                 totalSys < lastTotalSys || totalIdle < lastTotalIdle){
216                 //Overflow detection. Just skip this value.
217                 return lastPercent;
218             } else {
219                 auto total = (totalUser - lastTotalUser) + (totalUserLow - lastTotalUserLow) + (totalSys - lastTotalSys);
220                 percent = total;
221                 total += (totalIdle - lastTotalIdle);
222                 percent /= total;
223                 percent *= 100;
224             }
225 
226             lastTotalUser = totalUser;
227             lastTotalUserLow = totalUserLow;
228             lastTotalSys = totalSys;
229             lastTotalIdle = totalIdle;
230 
231             lastPercent = percent;
232             return percent;
233         }
234 
235     private:
236         ulong lastTotalUser, lastTotalUserLow, lastTotalSys, lastTotalIdle;
237         double lastPercent;
238     }
239 } else version(FreeBSD) {
240     private struct PlatformSystemCPUWatcher
241     {
242         @trusted void initialize() {
243 
244         }
245 
246         @trusted double current()
247         {
248             return 0.0;
249         }
250 
251     private:
252 
253     }
254 } else version(OSX) {
255     private struct PlatformSystemCPUWatcher
256     {
257         @trusted void initialize() {
258 
259         }
260 
261         @trusted double current()
262         {
263             return 0.0;
264         }
265 
266     private:
267 
268     }
269 
270     private struct PlatformProcessCPUWatcher
271     {
272         @trusted void initialize(int pid) {
273             _pid = pid;
274         }
275 
276         @trusted void initialize() {
277             _pid = thisProcessID;
278         }
279 
280         @trusted double current()
281         {
282             return 0.0;
283         }
284 
285         @trusted int processID() const nothrow {
286             return _pid;
287         }
288 
289     private:
290         int _pid;
291     }
292 }
293 
294 static if (is(typeof({import core.sys.posix.time : CLOCK_MONOTONIC;})))
295 {
296     private import core.sys.posix.time;
297     private import core.time;
298     private import core.sys.posix.unistd;
299     private import core.stdc.errno;
300     extern(C) @nogc int clock_getcpuclockid(pid_t pid, clockid_t *clock_id) nothrow;
301 
302     private auto getClockTime(clockid_t clockId)
303     {
304         timespec spec;
305         auto result = clock_gettime(clockId, &spec);
306         if (result != 0) {
307             if (errno == EINVAL) {
308                 throw new ErrnoException("clock_gettime failed, the watched process probably died");
309             } else {
310                 throw new ErrnoException("clock_gettime");
311             }
312         } else {
313             return dur!"seconds"(spec.tv_sec) + dur!"nsecs"(spec.tv_nsec);
314         }
315     }
316 
317     private struct PlatformProcessCPUWatcher
318     {
319         @trusted void initialize(int pid) {
320             _pid = pid;
321             int result = clock_getcpuclockid(pid, &clockId);
322             if (result == 0) {
323                 lastCPUTime = getClockTime(clockId);
324             } else {
325                 errno = result;
326                 errnoEnforce(false, "clock_getcpuclockid");
327             }
328             lastTime = getClockTime(CLOCK_MONOTONIC);
329         }
330 
331         @trusted void initialize() {
332             static if (is(typeof({import core.sys.posix.time : CLOCK_PROCESS_CPUTIME_ID;}))) {
333                 import core.sys.posix.time : CLOCK_PROCESS_CPUTIME_ID;
334                 _pid = getpid();
335                 clockId = CLOCK_PROCESS_CPUTIME_ID;
336                 lastCPUTime = getClockTime(clockId);
337                 lastTime = getClockTime(CLOCK_MONOTONIC);
338             } else {
339                 initialize(getpid());
340             }
341         }
342 
343         @trusted double current()
344         {
345             auto nowTime = getClockTime(CLOCK_MONOTONIC);
346             auto nowCPUTime = getClockTime(clockId);
347             double percent;
348 
349             if (nowTime < lastTime || nowCPUTime < lastCPUTime) {
350                 //Overflow detection. Just skip this value.
351                 return lastPercent;
352             } else {
353                 percent = (nowCPUTime - lastCPUTime).total!"hnsecs";
354                 percent /= (nowTime - lastTime).total!"hnsecs";
355                 percent *= 100;
356             }
357             lastTime = nowTime;
358             lastCPUTime = nowCPUTime;
359             lastPercent = percent;
360             return percent;
361         }
362 
363         @trusted int processID() const nothrow {
364             return _pid;
365         }
366 
367     private:
368         int _pid;
369         double lastPercent;
370         clockid_t clockId;
371         Duration lastTime;
372         Duration lastCPUTime;
373     }
374 }
375 
376 ///System CPU watcher.
377 final class SystemCPUWatcher : CPUWatcher
378 {
379     /**
380      * Watch system.
381      */
382     @safe this() {
383         _watcher.initialize();
384     }
385 
386     /**
387      * CPU time used by all processes in the system, in percents.
388      * This returns the average value amongst all CPUs, thus can't exceed 100.
389      */
390     @safe override double current() {
391         return _watcher.current();
392     }
393 
394 private:
395     PlatformSystemCPUWatcher _watcher;
396 }
397 
398 ///CPU watcher for single process.
399 final class ProcessCPUWatcher : CPUWatcher
400 {
401     /**
402      * Watch process by id.
403      * Params:
404      * pid = ID number of process. Can be process handle on Windows.
405      */
406     @safe this(int pid) {
407         _watcher.initialize(pid);
408     }
409 
410     version(Windows) {
411         @safe this(HANDLE procHandle) {
412             _watcher.initialize(procHandle);
413         }
414     }
415 
416     ///ditto, but watch this process.
417     @safe this() {
418         _watcher.initialize();
419     }
420     /**
421      * CPU time used by underlying process, in percents between 0 and 100.
422      */
423     @safe override double current() {
424         import std.parallelism : totalCPUs;
425         return _watcher.current() / totalCPUs;
426     }
427 
428     /**
429      * CPU time used by underlying process. The value range depends on CPU count.
430      * E.g. on the system with 4 CPUs it will be between 0 and 400 (considering all units are available for the process).
431      */
432     @safe double currentTotal() {
433         return _watcher.current();
434     }
435 
436     ///The process ID number.
437     @safe int processID() const nothrow {
438         return _watcher.processID();
439     }
440 
441 private:
442     PlatformProcessCPUWatcher _watcher;
443 }