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 }