-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclock_resolution_test.go
More file actions
123 lines (115 loc) · 4.45 KB
/
Copy pathclock_resolution_test.go
File metadata and controls
123 lines (115 loc) · 4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package netquality
import (
"context"
"runtime"
"testing"
"time"
"github.com/korya/netquality/server"
)
// observedResolution returns the smallest positive gap two successive readings
// of read can show — the granularity actually available to a caller. It gives
// up after budget so a pathologically coarse clock cannot hang the test.
func observedResolution(read func() time.Duration, budget time.Duration) (time.Duration, int) {
best, samples := time.Duration(0), 0
deadline := time.Now().Add(budget)
for i := 0; i < 2_000_000 && time.Now().Before(deadline); i++ {
if d := read(); d > 0 {
samples++
if best == 0 || d < best {
best = d
}
}
}
return best, samples
}
// TestProbeClockResolves guards LAT-10: the clock probe timings are measured
// with must resolve finely enough to see a probe.
//
// The bound is absolute but generous, because what observedResolution actually
// returns is max(clock resolution, cost of the code between two readings). On a
// nanosecond clock that is the call overhead, not the clock — which is why this
// does not compare the probe clock against time.Now outside Windows, where the
// two are the same clock reached through a different amount of work. A coarse
// clock is 1 ms at best and 15.625 ms at worst, so 100 us separates the two
// cases by more than two orders of magnitude without pinning either.
func TestProbeClockResolves(t *testing.T) {
mono, monoN := observedResolution(func() time.Duration {
a, b := monoNow(), monoNow()
return b.sub(a)
}, 200*time.Millisecond)
wall, wallN := observedResolution(func() time.Duration {
a, b := time.Now(), time.Now()
return b.Sub(a)
}, 200*time.Millisecond)
// The numbers this platform actually delivers. go test hides these unless
// the test fails or -v is set, which is the right trade: they are wanted
// exactly when an assertion below is arguing with the environment.
t.Logf("probe clock: %v over %d positive samples (high resolution: %v)", mono, monoN, monoHighResolution())
t.Logf("time.Now: %v over %d positive samples", wall, wallN)
if runtime.GOOS == "windows" {
// The only platform where the probe clock is not time.Now. If
// QueryPerformanceCounter is ever silently lost the run still works,
// but its jitter and percentiles stop meaning anything (LAT-10), so
// that must be loud here rather than skipped.
if !monoHighResolution() {
t.Error("QueryPerformanceCounter unavailable: probe timings fell back to the tick-quantised system clock")
return
}
// Orders of magnitude apart, so call overhead cannot explain it away.
if wall > 0 && mono >= wall {
t.Errorf("probe clock (%v) is no finer than the system clock (%v); QueryPerformanceCounter is not doing anything", mono, wall)
}
}
if mono == 0 {
t.Fatal("probe clock never advanced: no two readings differed")
}
// A probe on a fast path is tens of microseconds; a clock this coarse
// cannot honestly measure one.
if mono >= 100*time.Microsecond {
t.Errorf("probe clock resolution %v cannot time a probe", mono)
}
}
// TestProbeClockMonotonic: readings never go backwards, so sub can never
// produce a negative duration.
func TestProbeClockMonotonic(t *testing.T) {
prev := monoNow()
for i := 0; i < 100_000; i++ {
now := monoNow()
if d := now.sub(prev); d < 0 {
t.Fatalf("clock went backwards by %v at reading %d", d, i)
}
prev = now
}
if (instant{}).isZero() != true {
t.Error("the zero instant must report itself zero")
}
}
// TestCoarseClockWarns: when the high-resolution timer is unavailable the run
// still reports its numbers, and says how they were obtained (INV-3). The
// fallback is otherwise reachable only on a Windows box without QPC, so the
// clock seam stands in for one.
func TestCoarseClockWarns(t *testing.T) {
target, client := newTestServer(t, server.Options{})
fc := newFakeClock()
fc.coarse = true
go func() { // the phase loop is ticker-driven; keep it moving
for i := 0; i < 8; i++ {
time.Sleep(60 * time.Millisecond)
fc.tick()
}
}()
res, err := Run(context.Background(), target, Options{
HTTPClient: client, Directions: Download, IdleProbes: 2,
MaxDuration: 500 * time.Millisecond, MaxBytes: 1 << 40,
Stability: fastStability(), clock: fc,
})
if err != nil {
t.Fatal(err)
}
if !hasWarning(res, "high-resolution timer unavailable") {
t.Errorf("a coarse clock must be reported: %v", res.Warnings)
}
if res.Idle == nil || res.Download == nil {
t.Error("a coarse clock degrades the numbers; it must not remove them")
}
}