github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/lib/hrtime/time_windows.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  // High-resolution time for Windows.
     5  
     6  package hrtime
     7  
     8  // References:
     9  // https://github.com/golang/go/issues/31160
    10  // https://github.com/golang/go/issues/31528
    11  // https://go-review.googlesource.com/c/go/+/227499/1/src/testing/time_windows.go
    12  // https://go-review.googlesource.com/c/sys/+/515915
    13  // https://github.com/golang/go/blob/master/src/runtime/os_windows.go#L447
    14  // https://github.com/golang/go/blob/9b6e9f0c8c66355c0f0575d808b32f52c8c6d21c/src/runtime/os_windows.go#L381
    15  // https://learn.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps
    16  
    17  import (
    18  	"errors"
    19  	"sync/atomic"
    20  	"time"
    21  	"unsafe"
    22  
    23  	"golang.org/x/sys/windows"
    24  )
    25  
    26  // Way 1: Update the Windows time resolution to 1ms.
    27  
    28  var (
    29  	windowsHighResolutionEnabled = atomic.Bool{}
    30  )
    31  
    32  func SetTimeResolutionTo1ms() {
    33  	if windowsHighResolutionEnabled.Load() {
    34  		return
    35  	}
    36  	if err := windows.TimeBeginPeriod(1); err != nil {
    37  		panic(err)
    38  	}
    39  	windowsHighResolutionEnabled.Store(true)
    40  }
    41  
    42  func ResetTimeResolutionFrom1ms() error {
    43  	if !windowsHighResolutionEnabled.Load() {
    44  		return nil
    45  	}
    46  	if err := windows.TimeEndPeriod(1); err != nil {
    47  		return err
    48  	}
    49  	windowsHighResolutionEnabled.Store(false)
    50  	return nil
    51  }
    52  
    53  // Way 2: Use the Windows high-resolution performance counter API.
    54  
    55  var (
    56  	// Load windows dynamic link library.
    57  	kernel32 = windows.NewLazyDLL("kernel32.dll")
    58  	// Find windows dynamic link library functions.
    59  	procQPF = kernel32.NewProc("QueryPerformanceFrequency")
    60  	procQPC = kernel32.NewProc("QueryPerformanceCounter")
    61  )
    62  
    63  // https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancefrequency
    64  // BOOL QueryPerformanceFrequency(
    65  //
    66  //	[out] LARGE_INTEGER *lpFrequency
    67  //
    68  // );
    69  func getFrequency() (int64, bool) {
    70  	var freq int64
    71  	r1, _, err := procQPF.Call(uintptr(unsafe.Pointer(&freq)))
    72  	if err != nil && !errors.Is(err, windows.SEVERITY_SUCCESS) {
    73  		panic(err)
    74  	}
    75  	return freq, r1 == 1
    76  }
    77  
    78  // https://learn.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter
    79  // BOOL QueryPerformanceCounter(
    80  //
    81  //	[out] LARGE_INTEGER *lpPerformanceCount
    82  //
    83  // );
    84  // In multi-cores CPU, the counter may not be monotonic.
    85  // 1. Hardware differences: The counter is not guaranteed to be consistent across different hardware.
    86  // 2. CPU cache-line consistency: The counter is not guaranteed to be consistent across different CPU cores.
    87  // 3. Interrupt delays: The counter is not guaranteed to be consistent across different CPU cores.
    88  // 4. (Power management) Dynamic Voltage and Frequency Scaling (DVFS): The counter is not guaranteed to be consistent across different CPU cores.
    89  // 5. OS scheduling (CPU Core-to-Core): The counter is not guaranteed to be consistent across different CPU cores.
    90  // 6. Some BIOS issues in multi-cores CPU's: The counter is not guaranteed to be consistent across different CPU cores.
    91  // The way to fetch precious counter-number is running on a single-core CPU.
    92  func getCounter() (int64, bool) {
    93  	var counter int64
    94  	r1, _, err := procQPC.Call(uintptr(unsafe.Pointer(&counter)))
    95  	if err != nil && !errors.Is(err, windows.SEVERITY_SUCCESS) {
    96  		panic(err)
    97  	}
    98  	return counter, r1 == 1
    99  }
   100  
   101  var (
   102  	baseProcFreq          int64
   103  	baseProcCounter       int64
   104  	fallbackLowResolution atomic.Bool
   105  )
   106  
   107  func SetFallbackLowResolution(flag bool) {
   108  	fallbackLowResolution.Swap(flag)
   109  }
   110  
   111  func ClockInit() {
   112  	SetTimeResolutionTo1ms()
   113  	appStartTime = time.Now().In(loadTZLocation(TimeZoneOffset(atomic.LoadInt32(&defaultTimezoneOffset))))
   114  
   115  	var ok bool
   116  	if baseProcCounter, ok = getCounter(); !ok {
   117  		fallbackLowResolution.Store(true)
   118  	}
   119  	if baseProcFreq, ok = getFrequency(); !fallbackLowResolution.Load() && !ok || baseProcFreq <= 0 {
   120  		fallbackLowResolution.Store(true)
   121  	}
   122  }
   123  
   124  func now() time.Time {
   125  	nano := appStartTime.UnixNano() + MonotonicElapsed().Nanoseconds()
   126  	return time.UnixMilli(time.Duration(nano).Milliseconds())
   127  }
   128  
   129  func NowIn(offset TimeZoneOffset) time.Time {
   130  	if fallbackLowResolution.Load() {
   131  		return time.Now().In(loadTZLocation(offset))
   132  	}
   133  	return now().In(loadTZLocation(offset))
   134  }
   135  
   136  func NowInDefaultTZ() time.Time {
   137  	return NowIn(TimeZoneOffset(atomic.LoadInt32(&defaultTimezoneOffset)))
   138  }
   139  
   140  func NowInUTC() time.Time {
   141  	return NowIn(TzUtc0Offset)
   142  }
   143  
   144  // MonotonicElapsed returns the time elapsed since the program started.
   145  // Note: Not a very precise implementation.
   146  func MonotonicElapsed() time.Duration {
   147  	if fallbackLowResolution.Load() {
   148  		return time.Since(appStartTime)
   149  	}
   150  	currentCounter, _ := getCounter()
   151  	return time.Duration(currentCounter-baseProcCounter) * time.Second / (time.Duration(baseProcFreq) * time.Nanosecond)
   152  }
   153  
   154  func Since(beginTime time.Time) time.Duration {
   155  	if fallbackLowResolution.Load() {
   156  		return time.Since(beginTime)
   157  	}
   158  	n := NowInDefaultTZ()
   159  	return time.Duration(n.Nanosecond() - beginTime.In(loadTZLocation(TimeZoneOffset(DefaultTimezoneOffset()))).Nanosecond())
   160  }
   161  
   162  var (
   163  	SdkClock     = &sdkClockTime{}
   164  	WindowsClock = &windowsClockTime{}
   165  )
   166  
   167  type sdkClockTime struct{}
   168  
   169  func (s *sdkClockTime) NowIn(offset TimeZoneOffset) time.Time {
   170  	SetFallbackLowResolution(true)
   171  	return NowIn(offset)
   172  }
   173  
   174  func (s *sdkClockTime) NowInDefaultTZ() time.Time {
   175  	SetFallbackLowResolution(true)
   176  	return s.NowIn(TimeZoneOffset(atomic.LoadInt32(&defaultTimezoneOffset)))
   177  }
   178  
   179  func (s *sdkClockTime) NowInUTC() time.Time {
   180  	SetFallbackLowResolution(true)
   181  	return s.NowIn(TzUtc0Offset)
   182  }
   183  
   184  func (s *sdkClockTime) MonotonicElapsed() time.Duration {
   185  	SetFallbackLowResolution(true)
   186  	return MonotonicElapsed()
   187  }
   188  
   189  func (s *sdkClockTime) Since(beginTime time.Time) time.Duration {
   190  	SetFallbackLowResolution(true)
   191  	return Since(beginTime)
   192  }
   193  
   194  type windowsClockTime struct{}
   195  
   196  func (w *windowsClockTime) NowIn(offset TimeZoneOffset) time.Time {
   197  	SetFallbackLowResolution(false)
   198  	return NowIn(offset)
   199  }
   200  
   201  func (w *windowsClockTime) NowInDefaultTZ() time.Time {
   202  	SetFallbackLowResolution(false)
   203  	return w.NowIn(TimeZoneOffset(atomic.LoadInt32(&defaultTimezoneOffset)))
   204  }
   205  
   206  func (w *windowsClockTime) NowInUTC() time.Time {
   207  	SetFallbackLowResolution(false)
   208  	return w.NowIn(TzUtc0Offset)
   209  }
   210  
   211  func (w *windowsClockTime) MonotonicElapsed() time.Duration {
   212  	SetFallbackLowResolution(false)
   213  	return MonotonicElapsed()
   214  }
   215  
   216  func (w *windowsClockTime) Since(beginTime time.Time) time.Duration {
   217  	SetFallbackLowResolution(false)
   218  	return Since(beginTime)
   219  }