Skip to content

Commit 4a476a5

Browse files
committed
Improve Windows time support by using QueryPerformanceCounter and
GetSystemTimePreciseAsFileTime.
1 parent 883393c commit 4a476a5

24 files changed

+395
-125
lines changed

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,18 @@ See [CHANGES.md](CHANGES.md).
326326

327327
_Planned for v1.0.0..._
328328

329-
- Improve Windows time accuracy:
330-
- Use QueryPerformanceCounter for monotonic clock
331-
- Use GetSystemTimePreciseAsFileTime for wall clock
332-
- Replace all references to time.Time with irtt.Time
333-
- Remove server processing time calculation hack
329+
- Solidify TimeSource, Time and new Windows timer support:
330+
- Add --timesrc to client and server
331+
- Fall back to Go functions as necessary for older Windows versions
332+
- Make sure all calls to TimeSource.Now pass in only needed clocks
333+
- Find a better way to log warnings than fmt.Fprintf(os.Stderr) in timesrc_win.go
334+
- Rename Time.Mono to Monotonic, or others from Monotonic to Mono for
335+
consistency
336+
- Improve diagnostic commands:
337+
- Change bench command to output in columns
338+
- Rename sleep command to timer and add --timesrc, --sleep, --timer and --tcomp
339+
- Rename timer command to resolution and add --timesrc
340+
- Rename clock command to drift and add --timesrc
334341
- Improve output flexibility:
335342
- Allow specifying a format string for text output with optional units for times
336343
- Add format abbreviations for CSV, space delimited, etc.

cconfig.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type ClientConfig struct {
1919
DF DF
2020
TTL int
2121
Timer Timer
22+
TimeSource TimeSource
2223
Waiter Waiter
2324
Filler Filler
2425
FillOne bool
@@ -47,6 +48,7 @@ func NewClientConfig() *ClientConfig {
4748
DF: DefaultDF,
4849
TTL: DefaultTTL,
4950
Timer: DefaultTimer,
51+
TimeSource: DefaultTimeSource,
5052
Waiter: DefaultWait,
5153
ThreadLock: DefaultThreadLock,
5254
}
@@ -85,6 +87,7 @@ func (c *ClientConfig) MarshalJSON() ([]byte, error) {
8587
DF DF `json:"df"`
8688
TTL int `json:"ttl"`
8789
Timer string `json:"timer"`
90+
TimeSource string `json:"time_source"`
8891
Waiter string `json:"waiter"`
8992
Filler string `json:"filler"`
9093
FillOne bool `json:"fill_one"`
@@ -101,6 +104,7 @@ func (c *ClientConfig) MarshalJSON() ([]byte, error) {
101104
DF: c.DF,
102105
TTL: c.TTL,
103106
Timer: c.Timer.String(),
107+
TimeSource: c.TimeSource.String(),
104108
Waiter: c.Waiter.String(),
105109
Filler: fstr,
106110
FillOne: c.FillOne,

client.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ func (c *Client) Run(ctx context.Context) (r *Result, err error) {
9999
}
100100

101101
// create recorder
102-
if c.rec, err = newRecorder(pcount(c.Duration, c.Interval), c.Handler); err != nil {
102+
if c.rec, err = newRecorder(pcount(c.Duration, c.Interval), c.TimeSource,
103+
c.Handler); err != nil {
103104
return
104105
}
105106

@@ -308,7 +309,7 @@ func (c *Client) send(ctx context.Context) error {
308309
p.updateHMAC()
309310

310311
// record the start time of the test and calculate the end
311-
t := time.Now()
312+
t := c.TimeSource.Now(BothClocks)
312313
c.rec.Start = t
313314
end := c.rec.Start.Add(c.Duration)
314315

@@ -346,8 +347,8 @@ func (c *Client) send(ctx context.Context) error {
346347
p.updateHMAC()
347348

348349
// set the current base interval we're at
349-
tnext := c.rec.Start.Add(
350-
c.Interval * (time.Now().Sub(c.rec.Start) / c.Interval))
350+
tnext := c.rec.Start.Add(c.Interval *
351+
(c.TimeSource.Now(Monotonic).Sub(c.rec.Start) / c.Interval))
351352

352353
// if we're under half-way to the next interval, sleep until the next
353354
// interval, but if we're over half-way, sleep until the interval after
@@ -358,17 +359,17 @@ func (c *Client) send(ctx context.Context) error {
358359
tnext = tnext.Add(2 * c.Interval)
359360
}
360361

361-
// break if tnext if after the end of the test
362+
// break if tnext is after the end of the test
362363
if !tnext.Before(end) {
363364
break
364365
}
365366

366367
// calculate sleep duration
367-
tsleep := time.Now()
368+
tsleep := c.TimeSource.Now(Monotonic)
368369
dsleep := tnext.Sub(tsleep)
369370

370371
// sleep
371-
t, err = c.Timer.Sleep(ctx, tsleep, dsleep)
372+
t, err = c.Timer.Sleep(ctx, c.TimeSource, tsleep, dsleep)
372373
if err != nil {
373374
return err
374375
}

code_string.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

conn.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ type nconn struct {
2424
dscpSupport bool
2525
ttl int
2626
df DF
27+
timeSource TimeSource
2728
}
2829

29-
func (n *nconn) init(conn *net.UDPConn, ipVer IPVersion) {
30+
func (n *nconn) init(conn *net.UDPConn, ipVer IPVersion, ts TimeSource) {
3031
n.conn = conn
3132
n.ipVer = ipVer
3233
n.df = DFDefault
34+
n.timeSource = ts
3335

3436
// create x/net conns for socket options
3537
if n.ipVer&IPv4 != 0 {
@@ -149,7 +151,7 @@ func dial(ctx context.Context, cfg *ClientConfig) (cc *cconn, err error) {
149151

150152
// create cconn
151153
cc = &cconn{nconn: &nconn{}, cfg: cfg}
152-
cc.init(conn, cfg.IPVersion)
154+
cc.init(conn, cfg.IPVersion, cfg.TimeSource)
153155

154156
// open connection to server
155157
err = cc.open(ctx)
@@ -254,8 +256,8 @@ func (c *cconn) send(p *packet) (err error) {
254256
}
255257
var n int
256258
n, err = c.conn.Write(p.bytes())
257-
p.tsent = time.Now()
258-
p.trcvd = time.Time{}
259+
p.tsent = c.timeSource.Now(BothClocks)
260+
p.trcvd = Time{}
259261
if err != nil {
260262
return
261263
}
@@ -268,8 +270,8 @@ func (c *cconn) send(p *packet) (err error) {
268270
func (c *cconn) receive(p *packet) (err error) {
269271
var n int
270272
n, err = c.conn.Read(p.readTo())
271-
p.trcvd = time.Now()
272-
p.tsent = time.Time{}
273+
p.trcvd = c.timeSource.Now(BothClocks)
274+
p.tsent = Time{}
273275
p.dscp = 0
274276
if err != nil {
275277
return
@@ -334,30 +336,31 @@ type lconn struct {
334336
}
335337

336338
// listen creates an lconn by listening on a UDP address.
337-
func listen(laddr *net.UDPAddr, setSrcIP bool) (l *lconn, err error) {
339+
func listen(laddr *net.UDPAddr, setSrcIP bool, ts TimeSource) (l *lconn, err error) {
338340
ipVer := IPVersionFromUDPAddr(laddr)
339341
var conn *net.UDPConn
340342
conn, err = net.ListenUDP(ipVer.udpNetwork(), laddr)
341343
if err != nil {
342344
return
343345
}
344346
l = &lconn{nconn: &nconn{}, setSrcIP: setSrcIP && laddr.IP.IsUnspecified()}
345-
l.init(conn, ipVer)
347+
l.init(conn, ipVer, ts)
346348
return
347349
}
348350

349351
// listenAll creates lconns on multiple addresses, with separate lconns for IPv4
350352
// and IPv6, so that socket options can be set correctly, which is not possible
351353
// with a dual stack conn.
352-
func listenAll(ipVer IPVersion, addrs []string, setSrcIP bool) (lconns []*lconn, err error) {
354+
func listenAll(ipVer IPVersion, addrs []string, setSrcIP bool,
355+
ts TimeSource) (lconns []*lconn, err error) {
353356
laddrs, err := resolveListenAddrs(addrs, ipVer)
354357
if err != nil {
355358
return
356359
}
357360
lconns = make([]*lconn, 0, 16)
358361
for _, laddr := range laddrs {
359362
var l *lconn
360-
l, err = listen(laddr, setSrcIP)
363+
l, err = listen(laddr, setSrcIP, ts)
361364
if err != nil {
362365
return
363366
}
@@ -385,8 +388,8 @@ func (l *lconn) send(p *packet) (err error) {
385388
l.cm6.Src = p.srcIP
386389
n, err = l.ip6conn.WriteTo(p.bytes(), &l.cm6, p.raddr)
387390
}
388-
p.tsent = time.Now()
389-
p.trcvd = time.Time{}
391+
p.tsent = l.timeSource.Now(BothClocks)
392+
p.trcvd = Time{}
390393
if err != nil {
391394
return
392395
}
@@ -428,8 +431,8 @@ func (l *lconn) receive(p *packet) (err error) {
428431
}
429432
p.srcIP = nil
430433
p.dscp = 0
431-
p.trcvd = time.Now()
432-
p.tsent = time.Time{}
434+
p.trcvd = l.timeSource.Now(BothClocks)
435+
p.tsent = Time{}
433436
if err != nil {
434437
return
435438
}

defaults.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ var DefaultWait = &WaitMaxRTT{time.Duration(4) * time.Second, 3}
5353
// DefaultTimer is the default timer implementation, CompTimer.
5454
var DefaultTimer = NewCompTimer(DefaultCompTimerAverage)
5555

56+
// DefaultTimeSource is the default TimeSource implementation (WindowsTimeSource
57+
// for Windows and GoTimeSource for everything else).
58+
var DefaultTimeSource = NewDefaultTimeSource()
59+
5660
// DefaultFillPattern is the default fill pattern.
5761
var DefaultFillPattern = []byte("irtt")
5862

doc/irtt-client.1

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,8 @@ time\-to\-live (https://en.wikipedia.org/wiki/Time_to_live) value
770770
\f[I]timer\f[] the timer used: simple, comp, hybrid or busy (irtt client
771771
\-\-timer flag)
772772
.IP \[bu] 2
773+
\f[I]time_source\f[] the time source used: go or windows
774+
.IP \[bu] 2
773775
\f[I]waiter\f[] the waiter used: fixed duration, multiple of RTT or
774776
multiple of max RTT (irtt client \f[I]\-\-wait\f[] flag)
775777
.IP \[bu] 2
@@ -804,7 +806,10 @@ statistics for the results
804806
.nf
805807
\f[C]
806808
"stats":\ {
807-
\ \ \ \ "start_time":\ "2017\-10\-16T21:05:23.502719056+02:00",
809+
\ \ \ \ "start_time":\ {
810+
\ \ \ \ \ \ \ \ "wall":\ 1528621979787034330,
811+
\ \ \ \ \ \ \ \ "monotonic":\ 5136247
812+
\ \ \ \ },
808813
\ \ \ \ "send_call":\ {
809814
\ \ \ \ \ \ \ \ "total":\ 79547,
810815
\ \ \ \ \ \ \ \ "n":\ 3,
@@ -943,7 +948,8 @@ following attributes:
943948
.PP
944949
The regular attributes in \f[I]stats\f[] are as follows:
945950
.IP \[bu] 2
946-
\f[I]start_time\f[] the start time of the test, in TZ format
951+
\f[I]start_time\f[] the start time of the test (see \f[I]round_trips\f[]
952+
Notes for descriptions of \f[I]wall\f[] and \f[I]monotonic\f[] values)
947953
.IP \[bu] 2
948954
\f[I]send_call\f[] a duration stats object for the call time when
949955
sending packets
@@ -1144,8 +1150,8 @@ each round\-trip is a single request to / reply from the server
11441150
January 1, 1970 UTC
11451151
.PP
11461152
\f[B]Note:\f[] \f[I]monotonic\f[] values are the number of nanoseconds
1147-
since the start of the test for the client, and since start of the
1148-
process for the server
1153+
since some arbitrary point in time, so can only be relied on to measure
1154+
duration
11491155
.IP \[bu] 2
11501156
\f[I]seqno\f[] the sequence number
11511157
.IP \[bu] 2

doc/irtt-client.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,7 @@ <h2 id="config">config</h2>
611611
<li><em>df</em> the do-not-fragment setting (0 == OS default, 1 == false, 2 == true)</li>
612612
<li><em>ttl</em> the IP <a href="https://en.wikipedia.org/wiki/Time_to_live">time-to-live</a> value</li>
613613
<li><em>timer</em> the timer used: simple, comp, hybrid or busy (irtt client --timer flag)</li>
614+
<li><em>time_source</em> the time source used: go or windows</li>
614615
<li><em>waiter</em> the waiter used: fixed duration, multiple of RTT or multiple of max RTT (irtt client <em>--wait</em> flag)</li>
615616
<li><em>filler</em> the packet filler used: none, rand or pattern (irtt client <em>--fill</em> flag)</li>
616617
<li><em>fill_one</em> whether to fill only once and repeat for all packets (irtt client <em>--fill-one</em> flag)</li>
@@ -625,7 +626,10 @@ <h2 id="config">config</h2>
625626
<h2 id="stats">stats</h2>
626627
<p>statistics for the results</p>
627628
<pre><code>&quot;stats&quot;: {
628-
&quot;start_time&quot;: &quot;2017-10-16T21:05:23.502719056+02:00&quot;,
629+
&quot;start_time&quot;: {
630+
&quot;wall&quot;: 1528621979787034330,
631+
&quot;monotonic&quot;: 5136247
632+
},
629633
&quot;send_call&quot;: {
630634
&quot;total&quot;: 79547,
631635
&quot;n&quot;: 3,
@@ -751,7 +755,7 @@ <h2 id="stats">stats</h2>
751755
</ul>
752756
<p>The regular attributes in <em>stats</em> are as follows:</p>
753757
<ul>
754-
<li><em>start_time</em> the start time of the test, in TZ format</li>
758+
<li><em>start_time</em> the start time of the test (see <em>round_trips</em> Notes for descriptions of <em>wall</em> and <em>monotonic</em> values)</li>
755759
<li><em>send_call</em> a duration stats object for the call time when sending packets</li>
756760
<li><em>timer_error</em> a duration stats object for the observed sleep time error</li>
757761
<li><em>rtt</em> a duration stats object for the round-trip time</li>
@@ -873,7 +877,7 @@ <h2 id="round_trips">round_trips</h2>
873877
}
874878
]</code></pre>
875879
<p><strong>Note:</strong> <em>wall</em> values are from Go’s <em>time.Time.UnixNano()</em>, the number of nanoseconds elapsed since January 1, 1970 UTC</p>
876-
<p><strong>Note:</strong> <em>monotonic</em> values are the number of nanoseconds since the start of the test for the client, and since start of the process for the server</p>
880+
<p><strong>Note:</strong> <em>monotonic</em> values are the number of nanoseconds since some arbitrary point in time, so can only be relied on to measure duration</p>
877881
<ul>
878882
<li><em>seqno</em> the sequence number</li>
879883
<li><em>lost</em> the lost status of the packet, which can be one of <em>false</em>, <em>true</em>, <em>true_down</em> or <em>true_up</em>. The <em>true_down</em> and <em>true_up</em> values are only possible if the <em>ReceivedStats</em> parameter includes <em>ReceivedStatsWindow</em> (irtt client <em>--stats</em> flag). Even then, if it could not be determined whether the packet was lost upstream or downstream, the value <em>true</em> is used.</li>

doc/irtt-client.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ the configuration used for the test
355355
- *df* the do-not-fragment setting (0 == OS default, 1 == false, 2 == true)
356356
- *ttl* the IP [time-to-live](https://en.wikipedia.org/wiki/Time_to_live) value
357357
- *timer* the timer used: simple, comp, hybrid or busy (irtt client \--timer flag)
358+
- *time_source* the time source used: go or windows
358359
- *waiter* the waiter used: fixed duration, multiple of RTT or multiple of max RTT
359360
(irtt client *\--wait* flag)
360361
- *filler* the packet filler used: none, rand or pattern (irtt client *\--fill*
@@ -377,7 +378,10 @@ statistics for the results
377378

378379
```
379380
"stats": {
380-
"start_time": "2017-10-16T21:05:23.502719056+02:00",
381+
"start_time": {
382+
"wall": 1528621979787034330,
383+
"monotonic": 5136247
384+
},
381385
"send_call": {
382386
"total": 79547,
383387
"n": 3,
@@ -507,7 +511,8 @@ nanosecond duration values and has the following attributes:
507511

508512
The regular attributes in *stats* are as follows:
509513

510-
- *start_time* the start time of the test, in TZ format
514+
- *start_time* the start time of the test (see *round_trips* Notes for
515+
descriptions of *wall* and *monotonic* values)
511516
- *send_call* a duration stats object for the call time when sending packets
512517
- *timer_error* a duration stats object for the observed sleep time error
513518
- *rtt* a duration stats object for the round-trip time
@@ -663,8 +668,8 @@ each round-trip is a single request to / reply from the server
663668
**Note:** *wall* values are from Go's *time.Time.UnixNano()*, the number of nanoseconds
664669
elapsed since January 1, 1970 UTC
665670

666-
**Note:** *monotonic* values are the number of nanoseconds since the start of the test for
667-
the client, and since start of the process for the server
671+
**Note:** *monotonic* values are the number of nanoseconds since some arbitrary
672+
point in time, so can only be relied on to measure duration
668673

669674
- *seqno* the sequence number
670675
- *lost* the lost status of the packet, which can be one of *false*, *true*,

doc/irtt.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ send delay and IPDV.
8989

9090
# BUGS
9191

92-
- Windows has severe timer issues that may make measurements unreliable
9392
- Windows is unable to set DSCP values for IPv6.
9493
- Windows is unable to set the source IP address, so `--set-src-ip` may not be used
9594
on the server.

0 commit comments

Comments
 (0)