From b0cff82c6b91f99d2962f10d8d61d64853296e36 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Thu, 13 Feb 2020 22:29:14 +0100 Subject: [PATCH] [Windows] increase precision of boot_time() and proc create_time() (#1693) --- HISTORY.rst | 2 + psutil/_psutil_common.c | 38 ++++++++++++++++ psutil/_psutil_common.h | 2 + psutil/_psutil_windows.c | 71 ++++++------------------------ psutil/arch/windows/process_info.c | 11 +++-- 5 files changed, 60 insertions(+), 64 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 44d887e38..539c6fd4b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -19,6 +19,8 @@ XXXX-XX-XX - 1679_: [Windows] net_connections() and Process.connections() are 10% faster. - 1681_: [Linux] disk_partitions() now also shows swap partitions. - 1686_: [Windows] added support for PyPy on Windows. +- 1693_: [Windows] boot_time() and Process.create_time() now have the precision + of a micro second (before the precision was of a second). **Bug fixes** diff --git a/psutil/_psutil_common.c b/psutil/_psutil_common.c index 028e48e00..07578edaa 100644 --- a/psutil/_psutil_common.c +++ b/psutil/_psutil_common.c @@ -352,6 +352,7 @@ psutil_set_winver() { return 0; } + int psutil_load_globals() { if (psutil_loadlibs() != 0) @@ -362,4 +363,41 @@ psutil_load_globals() { InitializeCriticalSection(&PSUTIL_CRITICAL_SECTION); return 0; } + + +/* + * Convert the hi and lo parts of a FILETIME structure or a LARGE_INTEGER + * to a UNIX time. + * A FILETIME contains a 64-bit value representing the number of + * 100-nanosecond intervals since January 1, 1601 (UTC). + * A UNIX time is the number of seconds that have elapsed since the + * UNIX epoch, that is the time 00:00:00 UTC on 1 January 1970. + */ +static double +_to_unix_time(ULONGLONG hiPart, ULONGLONG loPart) { + ULONGLONG ret; + + // 100 nanosecond intervals since January 1, 1601. + ret = hiPart << 32; + ret += loPart; + // Change starting time to the Epoch (00:00:00 UTC, January 1, 1970). + ret -= 116444736000000000ull; + // Convert nano secs to secs. + return (double) ret / 10000000ull; +} + + +double +psutil_FiletimeToUnixTime(FILETIME ft) { + return _to_unix_time((ULONGLONG)ft.dwHighDateTime, + (ULONGLONG)ft.dwLowDateTime); + +} + + +double +psutil_LargeIntegerToUnixTime(LARGE_INTEGER li) { + return _to_unix_time((ULONGLONG)li.HighPart, + (ULONGLONG)li.LowPart); +} #endif // PSUTIL_WINDOWS diff --git a/psutil/_psutil_common.h b/psutil/_psutil_common.h index 975432069..34c428c05 100644 --- a/psutil/_psutil_common.h +++ b/psutil/_psutil_common.h @@ -134,4 +134,6 @@ int psutil_setup(void); PVOID psutil_GetProcAddress(LPCSTR libname, LPCSTR procname); PVOID psutil_GetProcAddressFromLib(LPCSTR libname, LPCSTR procname); PVOID psutil_SetFromNTStatusErr(NTSTATUS Status, const char *syscall); + double psutil_FiletimeToUnixTime(FILETIME ft); + double psutil_LargeIntegerToUnixTime(LARGE_INTEGER li); #endif diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index f94057070..64592103d 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -79,30 +79,13 @@ psutil_get_num_cpus(int fail_on_err) { */ static PyObject * psutil_boot_time(PyObject *self, PyObject *args) { - ULONGLONG uptime; - time_t pt; + ULONGLONG upTime; FILETIME fileTime; - ULONGLONG ll; GetSystemTimeAsFileTime(&fileTime); - /* - HUGE thanks to: - http://johnstewien.spaces.live.com/blog/cns!E6885DB5CEBABBC8!831.entry - - This function converts the FILETIME structure to the 32 bit - Unix time structure. - The time_t is a 32-bit value for the number of seconds since - January 1, 1970. A FILETIME is a 64-bit for the number of - 100-nanosecond periods since January 1, 1601. Convert by - subtracting the number of 100-nanosecond period between 01-01-1970 - and 01-01-1601, from time_t the divide by 1e+7 to get to the same - base granularity. - */ - ll = (((ULONGLONG) - (fileTime.dwHighDateTime)) << 32) + fileTime.dwLowDateTime; - pt = (time_t)((ll - 116444736000000000ull) / 10000000ull); - uptime = GetTickCount64() / 1000ull; - return Py_BuildValue("K", pt - uptime); + // Number of milliseconds that have elapsed since the system was started. + upTime = GetTickCount64() / 1000ull; + return Py_BuildValue("d", psutil_FiletimeToUnixTime(fileTime) - upTime); } @@ -320,10 +303,10 @@ psutil_proc_cpu_times(PyObject *self, PyObject *args) { */ return Py_BuildValue( "(dd)", - (double)(ftUser.dwHighDateTime * 429.4967296 + \ - ftUser.dwLowDateTime * 1e-7), - (double)(ftKernel.dwHighDateTime * 429.4967296 + \ - ftKernel.dwLowDateTime * 1e-7) + (double)(ftUser.dwHighDateTime * HI_T + \ + ftUser.dwLowDateTime * LO_T), + (double)(ftKernel.dwHighDateTime * HI_T + \ + ftKernel.dwLowDateTime * LO_T) ); } @@ -335,7 +318,6 @@ psutil_proc_cpu_times(PyObject *self, PyObject *args) { static PyObject * psutil_proc_create_time(PyObject *self, PyObject *args) { DWORD pid; - long long unix_time; HANDLE hProcess; FILETIME ftCreate, ftExit, ftKernel, ftUser; @@ -363,34 +345,7 @@ psutil_proc_create_time(PyObject *self, PyObject *args) { } CloseHandle(hProcess); - - /* - // Make sure the process is not gone as OpenProcess alone seems to be - // unreliable in doing so (it seems a previous call to p.wait() makes - // it unreliable). - // This check is important as creation time is used to make sure the - // process is still running. - ret = GetExitCodeProcess(hProcess, &exitCode); - CloseHandle(hProcess); - if (ret != 0) { - if (exitCode != STILL_ACTIVE) - return NoSuchProcess("GetExitCodeProcess"); - } - else { - // Ignore access denied as it means the process is still alive. - // For all other errors, we want an exception. - if (GetLastError() != ERROR_ACCESS_DENIED) - return PyErr_SetFromWindowsErr(0); - } - */ - - // Convert the FILETIME structure to a Unix time. - // It's the best I could find by googling and borrowing code here - // and there. The time returned has a precision of 1 second. - unix_time = ((LONGLONG)ftCreate.dwHighDateTime) << 32; - unix_time += ftCreate.dwLowDateTime - 116444736000000000LL; - unix_time /= 10000000; - return Py_BuildValue("d", (double)unix_time); + return Py_BuildValue("d", psutil_FiletimeToUnixTime(ftCreate)); } @@ -844,10 +799,10 @@ psutil_proc_threads(PyObject *self, PyObject *args) { py_tuple = Py_BuildValue( "kdd", te32.th32ThreadID, - (double)(ftUser.dwHighDateTime * 429.4967296 + \ - ftUser.dwLowDateTime * 1e-7), - (double)(ftKernel.dwHighDateTime * 429.4967296 + \ - ftKernel.dwLowDateTime * 1e-7)); + (double)(ftUser.dwHighDateTime * HI_T + \ + ftUser.dwLowDateTime * LO_T), + (double)(ftKernel.dwHighDateTime * HI_T + \ + ftKernel.dwLowDateTime * LO_T)); if (!py_tuple) goto error; if (PyList_Append(py_retlist, py_tuple)) diff --git a/psutil/arch/windows/process_info.c b/psutil/arch/windows/process_info.c index d8104c818..73a699127 100644 --- a/psutil/arch/windows/process_info.c +++ b/psutil/arch/windows/process_info.c @@ -578,7 +578,8 @@ psutil_get_environ(DWORD pid) { * fills the structure with various process information in one shot * by using NtQuerySystemInformation. * We use this as a fallback when faster functions fail with access - * denied. This is slower because it iterates over all processes. + * denied. This is slower because it iterates over all processes + * but it doesn't require any privilege (also work for PID 0). * On success return 1, else 0 with Python exception already set. */ int @@ -669,7 +670,7 @@ psutil_proc_info(PyObject *self, PyObject *args) { ULONG ctx_switches = 0; double user_time; double kernel_time; - long long create_time; + double create_time; PyObject *py_retlist; if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) @@ -692,9 +693,7 @@ psutil_proc_info(PyObject *self, PyObject *args) { create_time = 0; } else { - create_time = ((LONGLONG)process->CreateTime.HighPart) << 32; - create_time += process->CreateTime.LowPart - 116444736000000000LL; - create_time /= 10000000; + create_time = psutil_LargeIntegerToUnixTime(process->CreateTime); } py_retlist = Py_BuildValue( @@ -707,7 +706,7 @@ psutil_proc_info(PyObject *self, PyObject *args) { ctx_switches, // num ctx switches user_time, // cpu user time kernel_time, // cpu kernel time - (double)create_time, // create time + create_time, // create time (int)process->NumberOfThreads, // num threads // IO counters process->ReadOperationCount.QuadPart, // io rcount