Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std::chrono wrappers for Windows times and timestamps #89

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

hackbunny
Copy link

New header <wil/chrono.h> defines std::chrono adapters for all fixed-frequency Windows timers and timestamps

General considerations

User mode-only for now, because I don't know if <chrono> is kernel mode-safe. If we could get confirmation of the safety of <chrono> from the compiler people, we could add similar wrappers for functions like KeQueryTickCount, KeQuerySystemTime, etc. Alternatively, we could fork the LLVM <chrono> into a wistd_chrono.h header

Where possible, functions have been made constexpr through __WI_LIBCPP_CONSTEXPR_AFTER_CXX11, so that it depends on the underlying constexpr-ness of <chrono> functions

I haven't implemented DOS/FAT timestamps because, frankly, I think they aren't worth the effort

The following timers have a variable frequency and can't have zero-overhead std::chrono adapters, so they aren't included for now:

  • QueryPerformanceCounter

  • The auxiliary counter... if there was a function to query it (what is the deal with it?)

  • The CPU timestamp register, as returned by intrinsics like __rdtsc or __rdtscp. This timer comes with additional considerations, such as:

    • Detecting availability: whether the architecture implements it, whether the sandbox/hypervisor/VM allows access to it, whether it has a stable frequency (e.g. constant or invariant TSC on Intel/AMD processors) etc.

    • Querying the frequency: hard (I tried! and I still don't have a good answer)

    • Instruction ordering: for example, __rdtsc is unordered, and needs a fence (_mm_mfence or _mm_lfence) for meaningful measurements, or alternatively __rdtscp can be used; but all solutions require an additional feature test... and even the feature test itself (__cpuid/__cpuidex) may, in turn, require a feature test

Processor cycle counters (e.g. QueryThreadCycleTime) are out of scope, since they aren't time-based at all

Testing

I don't know how to write test cases, but I could learn. All clock classes support a form of compile-time dependency injection, so writing tests for corner cases shouldn't be hard

Overview

Tick counter clocks

The following Clocks are defined:

  • tick_count_clock :

    • rep: DWORD

    • period: std::milli (1ms)

    • is_steady: true

    • now: wraps GetTickCount

  • (#if _WIN32_WINNT >= 0x0600) tick_count64_clock:

    • rep: ULONGLONG

    • period, is_steady, time_point: same as tick_count_clock

    • now: wraps GetTickCount64

Caveats

Unlike standard C++ clocks, these clocks have an unsigned rep. This is intentional: the value returned by GetTickCount has a limited range, and we don't want to incur in undefined behavior dealing with signed under/overflow

System time clocks

The following Clocks are defined:

  • system_time_clock:

    • rep: LONGLONG

    • period: std::ratio_multiply<std::hecto, std::nano> (100ns)

    • is_steady: false

    • now: wraps GetSystemTimeAsFileTime

    • to_filetime and from_filetime: conversion between time_point and raw FILETIMEs

    • to_system_clock and from_system_clock: conversion between time_point and std::chrono::system_clock::time_point

    • to_time_t, from_time_t, to_time32_t, from_time32_t, to_time64_t, from_time64_t: conversion between time_point (100ns, Windows epoch) and std::time_t/__time32_t/__time64_t (1s, UNIX epoch)

  • (#if _WIN32_WINNT >= _WIN32_WINNT_WIN8) precise_system_time_clock:

    • rep, period, time_point, is_steady: same as system_time_clock

    • to_filetime, from_filetime, to_system_clock, from_system_clock, to_time_t, from_time_t, to_time32_t, from_time32_t, to_time64_t, from_time64_t: same as system_time_clock

    • now: wraps GetSystemTimePreciseAsFileTime

  • high_precision_system_time_clock:

    • (#if _WIN32_WINNT >= _WIN32_WINNT_WIN8) alias for precise_system_time_clock

    • (#else) alias for system_time_clock

Caveats

to_filetime and from_filetime are implemented in-line, because including win32_helpers.h to use wil::filetime routines didn't seem worth it compared to the downsides:

  • win32_helpers.h pulls in extra dependencies and pollutes the namespace

  • wil::filetime routines aren't constexpr (nor constexpr-compatible)

In to_system_clock and from_system_clock, we non-portably assume std::chrono::system_clock::time_point starts from the UNIX epoch. However, this is true in all C++ runtime library implementations I know of and it will be a requirement starting from C++20

Interrupt time clocks

When targeting Windows 7 or later (#if _WIN32_WINNT >= 0x0601), the following Clock is defined:

  • unbiased_interrupt_time_clock:

    • rep: LONGLONG

    • period: std::ratio_multiply<std::hecto, std::nano> (100ns)

    • is_steady: true

    • now: wraps QueryUnbiasedInterruptTime

When targeting Windows 7 or later and using a Windows 10 or later SDK (#ifdef NTDDI_WIN10), the following Clocks are also defined:

  • interrupt_time_clock:

    • rep: LONGLONG

    • period: std::ratio_multiply<std::hecto, std::nano> (100ns)

    • is_steady: true

    • now: wraps QueryInterruptTime

  • precise_interrupt_time_clock:

    • rep, period, time_point, is_steady: same as interrupt_time_clock

    • now: wraps QueryInterruptTimePrecise

  • precise_unbiased_interrupt_time_clock:

    • rep, period, time_point, is_steady: same as unbiased_interrupt_time_clock

    • now: wraps QueryUnbiasedInterruptTimePrecise

Process and thread times

Types

The following types are defined:

  • cpu_time_duration: std::chrono::duration<LONGLONG, std::ratio_multiply<std::hecto, std::nano>>

  • cpu_time:

    enum class cpu_time
    {
        total,
        kernel,
        user,
    };
  • execution_times:

    struct execution_times
    {
        system_time_clock::time_point creation_time;
        system_time_clock::time_point exit_time;
        cpu_time_duration kernel_time;
        cpu_time_duration user_time;
    };
  • thread_times and process_times: aliases for execution_times

Functions

The following functions are defined:

  • template<class ErrorPolicy>
    thread_times get_thread_times(HANDLE thread = ::GetCurrentThread());
    
    thread_times get_thread_times(HANDLE thread = ::GetCurrentThread());

    Returns the thread_times for the thread identified by the thread handle. Defaults to the current thread.

    Wraps GetThreadTimes, handling errors according to ErrorPolicy. ErrorPolicy defaults to err_exception_policy when calling the non-template overload

  • template<class ErrorPolicy>
    cpu_time_duration get_thread_cpu_time(HANDLE thread = ::GetCurrentThread(), cpu_time kind = cpu_time::total);
    
    cpu_time_duration get_thread_cpu_time(HANDLE thread = ::GetCurrentThread(), cpu_time kind = cpu_time::total);

    Returns the CPU time usage specified by the kind argument for the thread identified by the thread handle. Defaults to the total (kernel + user) CPU time of the current thread.

    Wraps GetThreadTimes, handling errors according to ErrorPolicy. ErrorPolicy defaults to err_exception_policy when calling the non-template overload

  • template<class ErrorPolicy>
    process_times get_process_times(HANDLE process = ::GetCurrentProcess());
    
    process_times get_process_times(HANDLE process = ::GetCurrentProcess());

    Returns the process_times for the process identified by the process handle. Defaults to the current process.

    Wraps GetProcessTimes, handling errors according to ErrorPolicy. ErrorPolicy defaults to err_exception_policy when calling the non-template overload

  • template<class ErrorPolicy>
    cpu_time_duration get_process_cpu_time(HANDLE process = ::GetCurrentProcess(), cpu_time kind = cpu_time::total);
    
    cpu_time_duration get_process_cpu_time(HANDLE process = ::GetCurrentProcess(), cpu_time kind = cpu_time::total);

    Returns the CPU time usage specified by the kind argument for the process identified by the process handle. Defaults to the total (kernel + user) CPU time of the current process.

    Wraps GetProcessTimes, handling errors according to ErrorPolicy. ErrorPolicy defaults to err_exception_policy when calling the non-template overload

Clocks

The following Clocks are defined:

  • current_thread_cpu_time_clock:

    • rep, period: same as cpu_time_duration

    • duration: cpu_time_duration

    • now<ErrorPolicy>: wraps get_thread_cpu_time with default arguments and the specified ErrorPolicy, returning the total CPU time used by the current thread

    • now: wraps now, passing err_exception_policy as its ErrorPolicy

  • current_process_cpu_time_clock:

    • rep, period: same as cpu_time_duration

    • duration: cpu_time_duration

    • now<ErrorPolicy>: wraps get_process_cpu_time with default arguments and the specified ErrorPolicy, returning the total CPU time used by the current process

    • now: wraps now, passing err_exception_policy as its ErrorPolicy

Caveats

Only error policies that interrupt execution (e.g. err_failfast_policy, err_exception_policy) are meaningfully supported. Policies that return error codes will be effectively ignored, and errors will cause undefined values to be returned

CPU time clocks were only defined for the current process and current thread, so that they could be stateless classes. The Clock concept doesn't mandate statelessness, but it's unclear to me how statefulness conceptually interacts with time_point

CPU time clocks were only defined for total time, because I couldn't think of a good, unambiguous API for passing a cpu_time argument

@msftclas
Copy link

msftclas commented Sep 1, 2019

CLA assistant check
All CLA requirements met.

@hackbunny
Copy link
Author

How do I sign the CLA? I keep pressing "Sign in with GitHub to agree" and looping back to the same page

include/wil/chrono.h Outdated Show resolved Hide resolved
include/wil/chrono.h Outdated Show resolved Hide resolved
include/wil/chrono.h Outdated Show resolved Hide resolved
hackbunny added 3 commits September 2, 2019 21:26
[As requested in the comments to PR microsoft#89](microsoft#89 (comment))
Changed template parameter names from Pascal case to snake case, with _t suffixes where appropriate
Split function template parameters into a type parameter and a value parameter; as a result, the details::QueryUnbiasedInterruptTime wrapper is now unnecessary

[As requested in the comments to PR microsoft#89](microsoft#89 (comment))
@hackbunny
Copy link
Author

Done, done and done. I kept the definition of the rep typedef of details::tick_count_clock_impl class as a template parameter, instead of setting it to the return type get_tick_count_fn, because we might want to define it to a different type (e.g. a signed integer)

@hackbunny
Copy link
Author

How do I sign the CLA? I keep pressing "Sign in with GitHub to agree" and looping back to the same page

I still have issues with this, please advise

@sylveon
Copy link
Contributor

sylveon commented Sep 2, 2019

Perhaps your browser privacy settings, maybe try in another browser's clean profile

@dunhor
Copy link
Member

dunhor commented Sep 3, 2019

I still have issues with this, please advise

I'm not having issues either. Perhaps try with a different browser, disable ad blocker, try an incognito window, etc. to see if that helps out

@hackbunny
Copy link
Author

I still have issues with this, please advise

I'm not having issues either. Perhaps try with a different browser, disable ad blocker, try an incognito window, etc. to see if that helps out

Tried all of those, same issue. Who do I report issues with the CLA Assistant to?

@dunhor
Copy link
Member

dunhor commented Sep 3, 2019

From my understanding that page is to authorize a GitHub application. The page for managing GitHub applications for your profile has a link to https://help.github.com/en/articles/authorizing-oauth-apps, which might be helpful?

using period = std::milli;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<base_clock_t, duration>;
static constexpr bool const is_steady = true;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for integral type constexpr with const together is useless, so we can remove it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@sylveon
Copy link
Contributor

sylveon commented Oct 25, 2019 via email

Moved the optional constexpr specifiers of details::execution_times_from_filetimes and details::get_cpu_time to their appropriate places
Added the inline specifier to the inline functions that were missing it
Added clarifying comments to some #endifs
Don't mix typename and class in template parameter lists
Delimit the code regions for thread/process times functions and thread/process CPU time clocks
In the non-template overload of current_thread_cpu_time_clock::now, call the non-template overload of get_thread_cpu_time; in the non-template overload of current_process_cpu_time_clock::now, call the non-template overload get_process_cpu_time. This makes sure the two methods inherit the error policy of the underlying functions
constexpr implies const
@hackbunny
Copy link
Author

Alright, I removed the redundant consts, fixed a few syntax errors in some configurations, added a couple comments and refactored a couple minor things

Also I finally signed the CLA!

using period = std::ratio_multiply<std::hecto, std::nano>;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<base_clock_t, duration>;
static constexpr bool is_steady = true;
Copy link
Member

@dunhor dunhor Nov 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The phrasing from the documentation:

The unbiased interrupt-time count does not include time the system spends in sleep or hibernation.

Makes it sound like this should be false, at least for QueryUnbiasedInterruptTime . At least the standard makes it sound like it should be:

true if t1 <= t2 is always true and the time between clock ticks is constant, otherwise false.

return time_point{duration{ static_cast<rep>(filetime_to_int(ft)) }};
}

#if __WI_LIBCPP_STD_VER > 11
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can assume at least C++14

#pragma endregion

#pragma region Thread/process CPU clocks
struct current_thread_cpu_time_clock
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requires is_steady static member to satisfy Clock requirements

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with current_process_cpu_time_clock and possibly other I may have missed

@jonwis
Copy link
Member

jonwis commented Dec 19, 2020

Hey @hackbunny - are you still interested in pushing forward on this? I'm happy to apply @dunhor's feedback and push to completion.

@hackbunny
Copy link
Author

@jonwis please and thank you. Go ahead, I had completely forgotten about this.

@CookiePLMonster
Copy link
Contributor

Is there anything else this PR needs before it can be merged?

@soroshsabz
Copy link

I hope this PR is merge quickly :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants