Walk the control buffer by hand instead of allocating a message list per frame
This commit is contained in:
@@ -80,21 +80,31 @@ func enableRxTimestamps(fd int) error {
|
|||||||
// means the mac did not produce a stamp.
|
// means the mac did not produce a stamp.
|
||||||
const scmTimestampingLen = 3 * int(unsafe.Sizeof(unix.Timespec{}))
|
const scmTimestampingLen = 3 * int(unsafe.Sizeof(unix.Timespec{}))
|
||||||
|
|
||||||
|
// A control message's payload starts at its header rounded up to a pointer, and
|
||||||
|
// the next starts at its length rounded up the same way.
|
||||||
|
const (
|
||||||
|
cmsgAlignMask = unix.SizeofPtr - 1
|
||||||
|
cmsgDataOff = (unix.SizeofCmsghdr + cmsgAlignMask) &^ cmsgAlignMask
|
||||||
|
)
|
||||||
|
|
||||||
|
// Walked by hand rather than through ParseSocketControlMessage, which allocates
|
||||||
|
// per call on a path that runs for every frame received.
|
||||||
func hwTimestamp(oob []byte) (int64, bool) {
|
func hwTimestamp(oob []byte) (int64, bool) {
|
||||||
msgs, err := unix.ParseSocketControlMessage(oob)
|
for off := 0; off+unix.SizeofCmsghdr <= len(oob); {
|
||||||
if err != nil {
|
h := (*unix.Cmsghdr)(unsafe.Pointer(&oob[off]))
|
||||||
|
n := int(h.Len)
|
||||||
|
if n < unix.SizeofCmsghdr || off+n > len(oob) {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
for _, m := range msgs {
|
if h.Level == unix.SOL_SOCKET && h.Type == unix.SCM_TIMESTAMPING {
|
||||||
if m.Header.Level != unix.SOL_SOCKET || m.Header.Type != unix.SCM_TIMESTAMPING {
|
if cmsgDataOff+scmTimestampingLen > n {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(m.Data) < scmTimestampingLen {
|
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
ts := (*[3]unix.Timespec)(unsafe.Pointer(&m.Data[0]))
|
ts := (*[3]unix.Timespec)(unsafe.Pointer(&oob[off+cmsgDataOff]))
|
||||||
ns := ts[2].Nano()
|
ns := ts[2].Nano()
|
||||||
return ns, ns != 0
|
return ns, ns != 0
|
||||||
}
|
}
|
||||||
|
off += (n + cmsgAlignMask) &^ cmsgAlignMask
|
||||||
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func putCmsg(buf []byte, off int, level, typ int32, dataLen int) int {
|
||||||
|
h := (*unix.Cmsghdr)(unsafe.Pointer(&buf[off]))
|
||||||
|
h.SetLen(unix.CmsgLen(dataLen))
|
||||||
|
h.Level = level
|
||||||
|
h.Type = typ
|
||||||
|
return off + unix.CmsgSpace(dataLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func putStamp(buf []byte, off int, raw unix.Timespec) int {
|
||||||
|
next := putCmsg(buf, off, unix.SOL_SOCKET, unix.SCM_TIMESTAMPING, scmTimestampingLen)
|
||||||
|
ts := (*[3]unix.Timespec)(unsafe.Pointer(&buf[off+cmsgDataOff]))
|
||||||
|
ts[2] = raw
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHwTimestampReadsRawHardwareClock(t *testing.T) {
|
||||||
|
buf := make([]byte, cmsgLen)
|
||||||
|
n := putStamp(buf, 0, unix.Timespec{Sec: 5, Nsec: 7})
|
||||||
|
|
||||||
|
got, ok := hwTimestamp(buf[:n])
|
||||||
|
if !ok || got != 5e9+7 {
|
||||||
|
t.Errorf("hwTimestamp = %d, %v; want 5000000007, true", got, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHwTimestampSkipsOtherMessages(t *testing.T) {
|
||||||
|
buf := make([]byte, cmsgLen)
|
||||||
|
off := putCmsg(buf, 0, unix.SOL_SOCKET, unix.SCM_RIGHTS, 4)
|
||||||
|
n := putStamp(buf, off, unix.Timespec{Sec: 1, Nsec: 2})
|
||||||
|
|
||||||
|
got, ok := hwTimestamp(buf[:n])
|
||||||
|
if !ok || got != 1e9+2 {
|
||||||
|
t.Errorf("hwTimestamp = %d, %v; want 1000000002, true", got, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHwTimestampRejectsZeroStamp(t *testing.T) {
|
||||||
|
buf := make([]byte, cmsgLen)
|
||||||
|
n := putStamp(buf, 0, unix.Timespec{})
|
||||||
|
|
||||||
|
if got, ok := hwTimestamp(buf[:n]); ok {
|
||||||
|
t.Errorf("hwTimestamp = %d, %v; want a zero stamp refused", got, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHwTimestampWithoutAStamp(t *testing.T) {
|
||||||
|
buf := make([]byte, cmsgLen)
|
||||||
|
n := putCmsg(buf, 0, unix.SOL_SOCKET, unix.SCM_RIGHTS, 4)
|
||||||
|
|
||||||
|
if _, ok := hwTimestamp(buf[:n]); ok {
|
||||||
|
t.Error("a buffer carrying no timestamp yielded one")
|
||||||
|
}
|
||||||
|
if _, ok := hwTimestamp(nil); ok {
|
||||||
|
t.Error("an empty buffer yielded a timestamp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHwTimestampRefusesBadLengths(t *testing.T) {
|
||||||
|
buf := make([]byte, cmsgLen)
|
||||||
|
n := putStamp(buf, 0, unix.Timespec{Sec: 1})
|
||||||
|
if _, ok := hwTimestamp(buf[:n-1]); ok {
|
||||||
|
t.Error("a message running past the buffer yielded a timestamp")
|
||||||
|
}
|
||||||
|
|
||||||
|
short := make([]byte, cmsgLen)
|
||||||
|
putCmsg(short, 0, unix.SOL_SOCKET, unix.SCM_TIMESTAMPING, scmTimestampingLen)
|
||||||
|
(*unix.Cmsghdr)(unsafe.Pointer(&short[0])).SetLen(unix.SizeofCmsghdr - 1)
|
||||||
|
if _, ok := hwTimestamp(short); ok {
|
||||||
|
t.Error("a message shorter than its own header yielded a timestamp")
|
||||||
|
}
|
||||||
|
|
||||||
|
trunc := make([]byte, cmsgLen)
|
||||||
|
tn := putCmsg(trunc, 0, unix.SOL_SOCKET, unix.SCM_TIMESTAMPING, scmTimestampingLen/3)
|
||||||
|
if _, ok := hwTimestamp(trunc[:tn]); ok {
|
||||||
|
t.Error("a truncated stamp yielded a timestamp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkHwTimestamp(b *testing.B) {
|
||||||
|
buf := make([]byte, cmsgLen)
|
||||||
|
n := putStamp(buf, 0, unix.Timespec{Sec: 5, Nsec: 7})
|
||||||
|
oob := buf[:n]
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
for b.Loop() {
|
||||||
|
if _, ok := hwTimestamp(oob); !ok {
|
||||||
|
b.Fatal("no timestamp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user