fix: tx-debug-udp 日志的写入以及发送日志调试
This commit is contained in:
@@ -11,22 +11,32 @@ import (
|
||||
)
|
||||
|
||||
// UDPConn 是对 UDP 连接的轻量封装。
|
||||
// server 侧:共享同一个 net.UDPConn,Send 时通过 peerAddr 指定对端地址。
|
||||
// peer 侧:独立的 net.UDPConn,已通过 Dial 连接到 server,Send 直接写即可。
|
||||
// server 侧共享一个 net.UDPConn,通过 SendTo 指定目标地址;
|
||||
// peer 侧使用已连接的 net.UDPConn,直接 Send 即可。
|
||||
type UDPConn struct {
|
||||
conn *net.UDPConn
|
||||
peerAddr *net.UDPAddr // server 侧为对端地址;peer 侧为 nil(连接模式下直接 Write)
|
||||
raw syscall.RawConn // 底层 syscall 句柄,用于 Linux socket timestamping
|
||||
peerAddr *net.UDPAddr
|
||||
raw syscall.RawConn
|
||||
|
||||
logger latencylog.Logger
|
||||
txTimestampDebugLogger TXTimestampDebugLogger
|
||||
nodeRole string // 日志中记录的节点角色,例如 "server" 或 "peer"
|
||||
nodeID string // 日志中记录的节点 ID
|
||||
writeMu sync.Mutex // 保护 Send 的互斥锁
|
||||
txPacketSeq uint32
|
||||
pendingTX map[uint32]udpTXPendingRecord
|
||||
nodeRole string
|
||||
nodeID string
|
||||
writeMu sync.Mutex
|
||||
closeOnce sync.Once
|
||||
closeErr error
|
||||
}
|
||||
|
||||
type udpTXPendingRecord struct {
|
||||
msg protocol.Message
|
||||
sendCallIndex int
|
||||
bytesWritten int
|
||||
expectedTXID uint32
|
||||
observedTimestamps map[string]int64
|
||||
}
|
||||
|
||||
// UDPOption 用于为 UDPConn 注入可选行为。
|
||||
type UDPOption func(*UDPConn)
|
||||
|
||||
@@ -39,7 +49,7 @@ func WithUDPLogger(logger latencylog.Logger, nodeRole, nodeID string) UDPOption
|
||||
}
|
||||
}
|
||||
|
||||
// WithUDPTXTimestampDebugLogger 为 UDP 连接注入可选的 TX errqueue 调试日志器。
|
||||
// WithUDPTXTimestampDebugLogger 为 UDP 连接注入 TX errqueue 调试日志器。
|
||||
func WithUDPTXTimestampDebugLogger(logger TXTimestampDebugLogger) UDPOption {
|
||||
return func(conn *UDPConn) {
|
||||
conn.txTimestampDebugLogger = logger
|
||||
@@ -47,13 +57,12 @@ func WithUDPTXTimestampDebugLogger(logger TXTimestampDebugLogger) UDPOption {
|
||||
}
|
||||
|
||||
// NewUDPConn 创建 UDP transport 连接封装。
|
||||
// peerAddr 为 nil 时表示 peer 侧已连接模式(conn 已 Dial 到 server)。
|
||||
// peerAddr 非 nil 时表示 server 侧,Send 时需要指定目标地址。
|
||||
func NewUDPConn(conn *net.UDPConn, peerAddr *net.UDPAddr, opts ...UDPOption) (*UDPConn, error) {
|
||||
udpConn := &UDPConn{
|
||||
conn: conn,
|
||||
peerAddr: peerAddr,
|
||||
logger: latencylog.NoopLogger{},
|
||||
conn: conn,
|
||||
peerAddr: peerAddr,
|
||||
logger: latencylog.NoopLogger{},
|
||||
pendingTX: make(map[uint32]udpTXPendingRecord),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
@@ -72,39 +81,34 @@ func NewUDPConn(conn *net.UDPConn, peerAddr *net.UDPAddr, opts ...UDPOption) (*U
|
||||
}
|
||||
|
||||
// Send 将一条协议消息编码为 UDP 数据报并发送。
|
||||
// 多个 goroutine 可以并发调用,内部会串行化写入。
|
||||
func (c *UDPConn) Send(msg protocol.Message) error {
|
||||
c.writeMu.Lock()
|
||||
defer c.writeMu.Unlock()
|
||||
latencylog.LogMessageEvent(c.logger, c.nodeRole, c.nodeID, latencylog.EventSendHandoffBegin, msg)
|
||||
|
||||
latencylog.LogMessageEvent(c.logger, c.nodeRole, c.nodeID, latencylog.EventSendHandoffBegin, msg)
|
||||
if err := c.sendMessageLinux(msg); err != nil {
|
||||
return fmt.Errorf("transport: udp send message: %w", err)
|
||||
}
|
||||
|
||||
latencylog.LogMessageEvent(c.logger, c.nodeRole, c.nodeID, latencylog.EventSendHandoffEnd, msg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendTo 将一条协议消息编码为 UDP 数据报并发送到指定地址。
|
||||
// 主要用于 server 侧向特定 peer 发送消息。
|
||||
func (c *UDPConn) SendTo(msg protocol.Message, addr *net.UDPAddr) error {
|
||||
c.writeMu.Lock()
|
||||
defer c.writeMu.Unlock()
|
||||
latencylog.LogMessageEvent(c.logger, c.nodeRole, c.nodeID, latencylog.EventSendHandoffBegin, msg)
|
||||
|
||||
latencylog.LogMessageEvent(c.logger, c.nodeRole, c.nodeID, latencylog.EventSendHandoffBegin, msg)
|
||||
if err := c.sendMessageToLinux(msg, addr); err != nil {
|
||||
return fmt.Errorf("transport: udp send message to %s: %w", addr, err)
|
||||
}
|
||||
|
||||
latencylog.LogMessageEvent(c.logger, c.nodeRole, c.nodeID, latencylog.EventSendHandoffEnd, msg)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Receive 从 UDP 连接读取一条完整协议消息。
|
||||
// 返回解码后的消息和来源地址(peer 侧来源地址始终为 server 地址)。
|
||||
func (c *UDPConn) Receive() (protocol.Message, *net.UDPAddr, error) {
|
||||
msg, addr, err := c.receiveMessageLinux()
|
||||
if err != nil {
|
||||
@@ -115,7 +119,6 @@ func (c *UDPConn) Receive() (protocol.Message, *net.UDPAddr, error) {
|
||||
}
|
||||
|
||||
// ReceiveLoop 持续从 UDP 连接读取消息并交给 handler 处理。
|
||||
// handler 的第二个参数是消息来源地址。
|
||||
func (c *UDPConn) ReceiveLoop(handler func(protocol.Message, *net.UDPAddr) error) error {
|
||||
for {
|
||||
msg, addr, err := c.Receive()
|
||||
@@ -130,8 +133,6 @@ func (c *UDPConn) ReceiveLoop(handler func(protocol.Message, *net.UDPAddr) error
|
||||
}
|
||||
|
||||
// Close 关闭底层 UDP 连接,保证重复调用安全。
|
||||
// 注意:server 侧多个 UDPConn 共享同一个 net.UDPConn 时,
|
||||
// 只应由 UDPHub 负责关闭底层连接,不应通过此方法关闭。
|
||||
func (c *UDPConn) Close() error {
|
||||
c.closeOnce.Do(func() {
|
||||
c.closeErr = c.conn.Close()
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"omnisocketgo/cmd/internal/protocol"
|
||||
)
|
||||
|
||||
// UDP 接收缓冲区大小,足以容纳 MaxFrameSize 加上协议头。
|
||||
// UDP 接收缓冲区需要容纳完整 datagram 和协议头。
|
||||
const udpReceiveBufferSize = protocol.MaxFrameSize + 1024
|
||||
|
||||
// initUDPLinuxTimestamping 拿到底层 fd,并打开 Linux timestamping。
|
||||
@@ -26,7 +26,6 @@ func (c *UDPConn) initUDPLinuxTimestamping() error {
|
||||
return fmt.Errorf("transport: udp missing syscall conn")
|
||||
}
|
||||
|
||||
// UDP 不需要 OPT_ID_TCP,使用标准的 OPT_ID 即可。
|
||||
flagCandidates := []int{
|
||||
linuxSOFTimestampingTXSched |
|
||||
linuxSOFTimestampingTXSoftware |
|
||||
@@ -68,21 +67,7 @@ func (c *UDPConn) sendMessageLinux(msg protocol.Message) error {
|
||||
return fmt.Errorf("protocol: encode message: %w", err)
|
||||
}
|
||||
|
||||
readIndex := 0
|
||||
c.drainPendingUDPTXTimestampEvents(msg, linuxTXTimestampPhasePreSendDrain, &readIndex)
|
||||
|
||||
if c.peerAddr != nil {
|
||||
if err := c.udpSendTo(payload, c.peerAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := c.udpSend(payload); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.collectAndLogUDPTXTimestampEvents(msg, &readIndex)
|
||||
return nil
|
||||
return c.sendUDPPayloadLinux(msg, payload, c.peerAddr)
|
||||
}
|
||||
|
||||
// sendMessageToLinux 编码消息并通过 UDP 发送到指定地址,采集 TX 时间戳。
|
||||
@@ -92,22 +77,65 @@ func (c *UDPConn) sendMessageToLinux(msg protocol.Message, addr *net.UDPAddr) er
|
||||
return fmt.Errorf("protocol: encode message: %w", err)
|
||||
}
|
||||
|
||||
readIndex := 0
|
||||
c.drainPendingUDPTXTimestampEvents(msg, linuxTXTimestampPhasePreSendDrain, &readIndex)
|
||||
return c.sendUDPPayloadLinux(msg, payload, addr)
|
||||
}
|
||||
|
||||
if err := c.udpSendTo(payload, addr); err != nil {
|
||||
func (c *UDPConn) sendUDPPayloadLinux(msg protocol.Message, payload []byte, addr *net.UDPAddr) error {
|
||||
readIndex := 0
|
||||
|
||||
// pre-send drain 可能读到上一条消息晚到的 errqueue 事件,
|
||||
// 这里必须先清掉,并且按 ee_data 归还给原消息,不能污染当前消息。
|
||||
c.drainPendingUDPTXTimestampEvents(linuxTXTimestampPhasePreSendDrain, &readIndex)
|
||||
|
||||
chunk := c.newUDPSendChunk(len(payload))
|
||||
var err error
|
||||
if addr != nil {
|
||||
err = c.udpSendTo(payload, addr)
|
||||
} else {
|
||||
err = c.udpSend(payload)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.collectAndLogUDPTXTimestampEvents(msg, &readIndex)
|
||||
c.commitUDPSend(msg, chunk)
|
||||
c.collectAndLogUDPTXTimestampEvents(msg, chunk, &readIndex)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *UDPConn) newUDPSendChunk(payloadLen int) txSendChunk {
|
||||
return txSendChunk{
|
||||
SendCallIndex: 0,
|
||||
FrameOffsetStart: 0,
|
||||
FrameOffsetEnd: payloadLen - 1,
|
||||
BytesWritten: payloadLen,
|
||||
ExpectedTXID: c.txPacketSeq,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UDPConn) commitUDPSend(msg protocol.Message, chunk txSendChunk) {
|
||||
// Linux 对 UDP datagram 的 ee_data 是按包递增的 ID。
|
||||
// 这里把 ID 和原始消息元数据绑定起来,后续 drain 到的晚到事件才能记回原消息。
|
||||
if c.txTimestampDebugLogger != nil {
|
||||
c.pendingTX[chunk.ExpectedTXID] = udpTXPendingRecord{
|
||||
msg: msg,
|
||||
sendCallIndex: chunk.SendCallIndex,
|
||||
bytesWritten: chunk.BytesWritten,
|
||||
expectedTXID: chunk.ExpectedTXID,
|
||||
observedTimestamps: make(map[string]int64, 2),
|
||||
}
|
||||
c.logUDPTXSendChunkDebugRecord(msg, chunk)
|
||||
}
|
||||
|
||||
c.txPacketSeq++
|
||||
}
|
||||
|
||||
// udpSend 通过已连接的 UDP socket 发送数据。
|
||||
func (c *UDPConn) udpSend(payload []byte) error {
|
||||
if c.raw != nil {
|
||||
return c.udpSendmsgRaw(payload, nil)
|
||||
}
|
||||
|
||||
_, err := c.conn.Write(payload)
|
||||
return err
|
||||
}
|
||||
@@ -120,6 +148,7 @@ func (c *UDPConn) udpSendTo(payload []byte, addr *net.UDPAddr) error {
|
||||
return c.udpSendmsgRaw(payload, sa)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := c.conn.WriteToUDP(payload, addr)
|
||||
return err
|
||||
}
|
||||
@@ -213,14 +242,13 @@ func (c *UDPConn) udpRecvmsgRaw() ([]byte, *net.UDPAddr, int64, error) {
|
||||
return nil, nil, 0, opErr
|
||||
}
|
||||
|
||||
addr := sockaddrToUDPAddr(from)
|
||||
return buf[:n], addr, rxTimeNS, nil
|
||||
return buf[:n], sockaddrToUDPAddr(from), rxTimeNS, nil
|
||||
}
|
||||
}
|
||||
|
||||
// collectAndLogUDPTXTimestampEvents 采集并记录 UDP 发送的 TX 时间戳事件。
|
||||
func (c *UDPConn) collectAndLogUDPTXTimestampEvents(msg protocol.Message, readIndex *int) {
|
||||
timestamps := c.collectUDPTXTimestampEvents(msg, readIndex)
|
||||
func (c *UDPConn) collectAndLogUDPTXTimestampEvents(msg protocol.Message, chunk txSendChunk, readIndex *int) {
|
||||
timestamps := c.collectUDPTXTimestampEvents(msg, chunk, readIndex)
|
||||
|
||||
if ts, ok := timestamps[latencylog.EventATXSched]; ok {
|
||||
latencylog.LogMessageEventAt(c.logger, c.nodeRole, c.nodeID, latencylog.EventATXSched, ts, msg)
|
||||
@@ -231,13 +259,13 @@ func (c *UDPConn) collectAndLogUDPTXTimestampEvents(msg protocol.Message, readIn
|
||||
}
|
||||
|
||||
// collectUDPTXTimestampEvents 在 errqueue 中等待 TX 时间戳。
|
||||
func (c *UDPConn) collectUDPTXTimestampEvents(msg protocol.Message, readIndex *int) map[string]int64 {
|
||||
func (c *UDPConn) collectUDPTXTimestampEvents(msg protocol.Message, chunk txSendChunk, readIndex *int) map[string]int64 {
|
||||
if c.raw == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(linuxTXTimestampWaitTimeout)
|
||||
timestamps := make(map[string]int64, 2)
|
||||
observed := make([]observedTXTimestampEvent, 0, 4)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
event, err := c.recvUDPTXTimestampOnce()
|
||||
@@ -251,25 +279,31 @@ func (c *UDPConn) collectUDPTXTimestampEvents(msg protocol.Message, readIndex *i
|
||||
if event.EventName == "" || event.TSUnixNano <= 0 {
|
||||
continue
|
||||
}
|
||||
*readIndex++
|
||||
|
||||
if isBusinessTXTimestampEventName(event.EventName) {
|
||||
if _, exists := timestamps[event.EventName]; !exists {
|
||||
timestamps[event.EventName] = event.TSUnixNano
|
||||
}
|
||||
}
|
||||
observed = append(observed, observedTXTimestampEvent{
|
||||
Phase: linuxTXTimestampPhasePostSendCollect,
|
||||
ReadIndex: *readIndex,
|
||||
Event: event,
|
||||
})
|
||||
c.recordUDPPendingEvent(event)
|
||||
*readIndex = *readIndex + 1
|
||||
|
||||
if hasCompleteTXTimestampPair(timestamps) {
|
||||
selection := selectTXTimestampEvents(observed, chunk.ExpectedTXID, true)
|
||||
if selection.HasEvent && selection.SelectedID == chunk.ExpectedTXID && hasCompleteTXTimestampPair(selection.Timestamps) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.drainPendingUDPTXTimestampEvents(msg, linuxTXTimestampPhasePostSelectDrain, readIndex)
|
||||
return timestamps
|
||||
selection := selectTXTimestampEvents(observed, chunk.ExpectedTXID, true)
|
||||
c.logObservedUDPTXTimestampEvents(msg, chunk, observed, selection)
|
||||
c.releaseCompletedUDPPendingFromObserved(observed)
|
||||
c.drainPendingUDPTXTimestampEvents(linuxTXTimestampPhasePostSelectDrain, readIndex)
|
||||
c.releaseCompletedUDPPending(chunk.ExpectedTXID)
|
||||
return selection.Timestamps
|
||||
}
|
||||
|
||||
// drainPendingUDPTXTimestampEvents 清空 errqueue 中残留的时间戳事件。
|
||||
func (c *UDPConn) drainPendingUDPTXTimestampEvents(msg protocol.Message, phase string, readIndex *int) {
|
||||
func (c *UDPConn) drainPendingUDPTXTimestampEvents(phase string, readIndex *int) {
|
||||
if c.raw == nil {
|
||||
return
|
||||
}
|
||||
@@ -277,12 +311,27 @@ func (c *UDPConn) drainPendingUDPTXTimestampEvents(msg protocol.Message, phase s
|
||||
for {
|
||||
event, err := c.recvUDPTXTimestampOnce()
|
||||
if err != nil {
|
||||
if isWouldBlock(err) {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
if event.EventName == "" || event.TSUnixNano <= 0 {
|
||||
continue
|
||||
}
|
||||
*readIndex++
|
||||
|
||||
complete := c.recordUDPPendingEvent(event)
|
||||
if msg, chunks, ok := c.lookupUDPPendingDebugContext(event.EEData); ok {
|
||||
c.logUDPTXErrqueueDebugRecord(msg, chunks, observedTXTimestampEvent{
|
||||
Phase: phase,
|
||||
ReadIndex: *readIndex,
|
||||
Event: event,
|
||||
}, false)
|
||||
if complete {
|
||||
delete(c.pendingTX, event.EEData)
|
||||
}
|
||||
}
|
||||
*readIndex = *readIndex + 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,6 +361,155 @@ func (c *UDPConn) recvUDPTXTimestampOnce() (txTimestampEvent, error) {
|
||||
return event, nil
|
||||
}
|
||||
|
||||
func (c *UDPConn) recordUDPPendingEvent(event txTimestampEvent) bool {
|
||||
if !isBusinessTXTimestampEventName(event.EventName) {
|
||||
return false
|
||||
}
|
||||
|
||||
record, ok := c.pendingTX[event.EEData]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if record.observedTimestamps == nil {
|
||||
record.observedTimestamps = make(map[string]int64, 2)
|
||||
}
|
||||
if existing, exists := record.observedTimestamps[event.EventName]; !exists || event.TSUnixNano < existing {
|
||||
record.observedTimestamps[event.EventName] = event.TSUnixNano
|
||||
}
|
||||
c.pendingTX[event.EEData] = record
|
||||
|
||||
return hasCompleteTXTimestampPair(record.observedTimestamps)
|
||||
}
|
||||
|
||||
func (c *UDPConn) releaseCompletedUDPPending(txID uint32) {
|
||||
record, ok := c.pendingTX[txID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if hasCompleteTXTimestampPair(record.observedTimestamps) {
|
||||
delete(c.pendingTX, txID)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UDPConn) releaseCompletedUDPPendingFromObserved(observed []observedTXTimestampEvent) {
|
||||
seen := make(map[uint32]struct{}, len(observed))
|
||||
for _, entry := range observed {
|
||||
if _, ok := seen[entry.Event.EEData]; ok {
|
||||
continue
|
||||
}
|
||||
c.releaseCompletedUDPPending(entry.Event.EEData)
|
||||
seen[entry.Event.EEData] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UDPConn) lookupUDPPendingDebugContext(txID uint32) (protocol.Message, []txSendChunk, bool) {
|
||||
record, ok := c.pendingTX[txID]
|
||||
if !ok {
|
||||
return protocol.Message{}, nil, false
|
||||
}
|
||||
|
||||
chunk := txSendChunk{
|
||||
SendCallIndex: record.sendCallIndex,
|
||||
FrameOffsetStart: 0,
|
||||
FrameOffsetEnd: record.bytesWritten - 1,
|
||||
BytesWritten: record.bytesWritten,
|
||||
ExpectedTXID: record.expectedTXID,
|
||||
}
|
||||
return record.msg, []txSendChunk{chunk}, true
|
||||
}
|
||||
|
||||
func (c *UDPConn) logObservedUDPTXTimestampEvents(msg protocol.Message, chunk txSendChunk, observed []observedTXTimestampEvent, selection txTimestampSelection) {
|
||||
if len(observed) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
currentChunks := []txSendChunk{chunk}
|
||||
for _, entry := range observed {
|
||||
recordMsg := msg
|
||||
recordChunks := currentChunks
|
||||
if pendingMsg, pendingChunks, ok := c.lookupUDPPendingDebugContext(entry.Event.EEData); ok {
|
||||
recordMsg = pendingMsg
|
||||
recordChunks = pendingChunks
|
||||
}
|
||||
|
||||
// 理想情况应命中本次 expectedTXID;如果等待窗口里只看到了更高的 ee_data,
|
||||
// 就退回到本轮实际观察到的最新事件,至少保留调试和定位线索。
|
||||
selected := selection.HasEvent &&
|
||||
entry.Event.EEData == selection.SelectedID &&
|
||||
isBusinessTXTimestampEventName(entry.Event.EventName) &&
|
||||
selection.Timestamps[entry.Event.EventName] == entry.Event.TSUnixNano
|
||||
c.logUDPTXErrqueueDebugRecord(recordMsg, recordChunks, entry, selected)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UDPConn) logUDPTXSendChunkDebugRecord(msg protocol.Message, chunk txSendChunk) {
|
||||
if c.txTimestampDebugLogger == nil {
|
||||
return
|
||||
}
|
||||
|
||||
sendCallIndex := chunk.SendCallIndex
|
||||
frameOffsetStart := chunk.FrameOffsetStart
|
||||
frameOffsetEnd := chunk.FrameOffsetEnd
|
||||
bytesWritten := chunk.BytesWritten
|
||||
expectedTXID := chunk.ExpectedTXID
|
||||
|
||||
record := c.newUDPTXTimestampDebugRecord(msg)
|
||||
record.RecordType = txTimestampDebugRecordTypeSendChunk
|
||||
record.SendCallIndex = &sendCallIndex
|
||||
record.FrameOffsetStart = &frameOffsetStart
|
||||
record.FrameOffsetEnd = &frameOffsetEnd
|
||||
record.BytesWritten = &bytesWritten
|
||||
record.ExpectedTXID = &expectedTXID
|
||||
c.logUDPTXTimestampDebugRecord(record)
|
||||
}
|
||||
|
||||
func (c *UDPConn) logUDPTXErrqueueDebugRecord(msg protocol.Message, chunks []txSendChunk, observed observedTXTimestampEvent, selected bool) {
|
||||
if c.txTimestampDebugLogger == nil {
|
||||
return
|
||||
}
|
||||
|
||||
readIndex := observed.ReadIndex
|
||||
tsUnixNano := observed.Event.TSUnixNano
|
||||
eeInfo := observed.Event.EEInfo
|
||||
eeData := observed.Event.EEData
|
||||
selectedForLatency := selected
|
||||
|
||||
record := c.newUDPTXTimestampDebugRecord(msg)
|
||||
record.RecordType = txTimestampDebugRecordTypeErrqueueEvent
|
||||
record.Phase = observed.Phase
|
||||
record.ReadIndex = &readIndex
|
||||
record.EventName = observed.Event.EventName
|
||||
record.TSUnixNano = &tsUnixNano
|
||||
record.EEInfo = &eeInfo
|
||||
record.EEData = &eeData
|
||||
record.SelectedForLatency = &selectedForLatency
|
||||
if matchedSendCallIndex, ok := matchTXTimestampEventToSendChunk(observed.Event.EEData, chunks); ok {
|
||||
record.MatchedSendCallIndex = &matchedSendCallIndex
|
||||
}
|
||||
c.logUDPTXTimestampDebugRecord(record)
|
||||
}
|
||||
|
||||
func (c *UDPConn) newUDPTXTimestampDebugRecord(msg protocol.Message) TXTimestampDebugRecord {
|
||||
return TXTimestampDebugRecord{
|
||||
NodeRole: c.nodeRole,
|
||||
NodeID: c.nodeID,
|
||||
MessageType: msg.Type,
|
||||
MessageID: msg.ID,
|
||||
From: msg.From,
|
||||
To: msg.To,
|
||||
FileName: msg.FileName,
|
||||
BodySize: len(msg.Body),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UDPConn) logUDPTXTimestampDebugRecord(record TXTimestampDebugRecord) {
|
||||
if c.txTimestampDebugLogger == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = c.txTimestampDebugLogger.LogTXTimestampDebugRecord(record)
|
||||
}
|
||||
|
||||
// udpAddrToSockaddr 将 net.UDPAddr 转换为 syscall.Sockaddr。
|
||||
func udpAddrToSockaddr(addr *net.UDPAddr) syscall.Sockaddr {
|
||||
if ip4 := addr.IP.To4(); ip4 != nil {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"omnisocketgo/cmd/internal/protocol"
|
||||
)
|
||||
|
||||
// TestUDPLinuxTimestampingRecordsKernelEvents 验证 UDP 在 Linux 上能正确采集内核时间戳。
|
||||
func TestUDPLinuxTimestampingRecordsKernelEvents(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -45,7 +44,6 @@ func TestUDPLinuxTimestampingRecordsKernelEvents(t *testing.T) {
|
||||
senderLogger := &recordingLogger{}
|
||||
receiverLogger := &recordingLogger{}
|
||||
|
||||
// 创建 server 侧监听
|
||||
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveUDPAddr() error = %v", err)
|
||||
@@ -65,7 +63,6 @@ func TestUDPLinuxTimestampingRecordsKernelEvents(t *testing.T) {
|
||||
}
|
||||
t.Cleanup(func() { _ = receiver.Close() })
|
||||
|
||||
// 创建 peer 侧连接
|
||||
peerRaw, err := net.DialUDP("udp", nil, serverRaw.LocalAddr().(*net.UDPAddr))
|
||||
if err != nil {
|
||||
t.Fatalf("DialUDP() error = %v", err)
|
||||
@@ -103,3 +100,149 @@ func TestUDPLinuxTimestampingRecordsKernelEvents(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUDPLinuxTimestampingDebugLoggerCapturesDatagramAndErrqueueEvents(t *testing.T) {
|
||||
debugLogger := &recordingTXTimestampDebugLogger{}
|
||||
senderLogger := &recordingLogger{}
|
||||
receiverLogger := &recordingLogger{}
|
||||
|
||||
serverAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveUDPAddr() error = %v", err)
|
||||
}
|
||||
serverRaw, err := net.ListenUDP("udp", serverAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("ListenUDP() error = %v", err)
|
||||
}
|
||||
receiver, err := NewUDPConn(
|
||||
serverRaw,
|
||||
nil,
|
||||
WithUDPLogger(receiverLogger, latencylog.NodeRolePeer, "peer-b"),
|
||||
)
|
||||
if err != nil {
|
||||
_ = serverRaw.Close()
|
||||
t.Fatalf("NewUDPConn(receiver) error = %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = receiver.Close() })
|
||||
|
||||
peerRaw, err := net.DialUDP("udp", nil, serverRaw.LocalAddr().(*net.UDPAddr))
|
||||
if err != nil {
|
||||
t.Fatalf("DialUDP() error = %v", err)
|
||||
}
|
||||
sender, err := NewUDPConn(
|
||||
peerRaw,
|
||||
nil,
|
||||
WithUDPLogger(senderLogger, latencylog.NodeRolePeer, "peer-a"),
|
||||
WithUDPTXTimestampDebugLogger(debugLogger),
|
||||
)
|
||||
if err != nil {
|
||||
_ = peerRaw.Close()
|
||||
t.Fatalf("NewUDPConn(sender) error = %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _ = sender.Close() })
|
||||
|
||||
msg := protocol.Message{
|
||||
Type: protocol.MessageTypeText,
|
||||
ID: 99,
|
||||
From: "peer-a",
|
||||
To: "peer-b",
|
||||
Body: []byte("hello udp debug"),
|
||||
}
|
||||
|
||||
sendErr := make(chan error, 1)
|
||||
go func() {
|
||||
sendErr <- sender.Send(msg)
|
||||
}()
|
||||
|
||||
got, _, err := receiver.Receive()
|
||||
if err != nil {
|
||||
t.Fatalf("Receive() error = %v", err)
|
||||
}
|
||||
if err := <-sendErr; err != nil {
|
||||
t.Fatalf("Send() error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, msg) {
|
||||
t.Fatalf("message mismatch: got %+v want %+v", got, msg)
|
||||
}
|
||||
|
||||
assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSched, msg.ID)
|
||||
assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSoftware, msg.ID)
|
||||
assertHasEvent(t, receiverLogger.Events(), latencylog.EventBRXSoftware, msg.ID)
|
||||
|
||||
sendChunkRecords := debugRecordsByType(debugLogger.Records(), txTimestampDebugRecordTypeSendChunk)
|
||||
errqueueRecords := debugRecordsByType(debugLogger.Records(), txTimestampDebugRecordTypeErrqueueEvent)
|
||||
if len(sendChunkRecords) == 0 {
|
||||
t.Fatal("send_chunk debug records = 0, want at least 1")
|
||||
}
|
||||
if len(errqueueRecords) == 0 {
|
||||
t.Fatal("errqueue_event debug records = 0, want at least 1")
|
||||
}
|
||||
|
||||
finalChunkRecord := sendChunkRecords[len(sendChunkRecords)-1]
|
||||
if finalChunkRecord.ExpectedTXID == nil {
|
||||
t.Fatal("final send_chunk expected_tx_id = nil, want non-nil")
|
||||
}
|
||||
finalExpectedTXID := *finalChunkRecord.ExpectedTXID
|
||||
|
||||
selectedRecords := selectedErrqueueRecords(errqueueRecords)
|
||||
if len(selectedRecords) == 0 {
|
||||
t.Fatal("selected errqueue debug records = 0, want at least 1")
|
||||
}
|
||||
|
||||
highestObservedID := uint32(0)
|
||||
haveHighestObservedID := false
|
||||
haveExactFinalID := false
|
||||
for _, record := range errqueueRecords {
|
||||
if record.EEData == nil {
|
||||
continue
|
||||
}
|
||||
if !haveHighestObservedID || *record.EEData > highestObservedID {
|
||||
highestObservedID = *record.EEData
|
||||
haveHighestObservedID = true
|
||||
}
|
||||
if *record.EEData == finalExpectedTXID && isBusinessTXTimestampRecord(record) {
|
||||
haveExactFinalID = true
|
||||
}
|
||||
}
|
||||
if !haveHighestObservedID {
|
||||
t.Fatal("highestObservedID missing, want at least one ee_data")
|
||||
}
|
||||
|
||||
wantSelectedID := highestObservedID
|
||||
if haveExactFinalID {
|
||||
wantSelectedID = finalExpectedTXID
|
||||
}
|
||||
for _, record := range selectedRecords {
|
||||
if record.EEData == nil {
|
||||
t.Fatalf("selected record missing ee_data: %+v", record)
|
||||
}
|
||||
if *record.EEData != wantSelectedID {
|
||||
t.Fatalf("selected ee_data = %d, want %d", *record.EEData, wantSelectedID)
|
||||
}
|
||||
}
|
||||
|
||||
selectedByEventName := make(map[string]int64, len(selectedRecords))
|
||||
for _, record := range selectedRecords {
|
||||
if record.TSUnixNano == nil {
|
||||
t.Fatalf("selected record missing timestamp: %+v", record)
|
||||
}
|
||||
selectedByEventName[record.EventName] = *record.TSUnixNano
|
||||
}
|
||||
|
||||
senderEventsByName := make(map[string]int64)
|
||||
for _, event := range senderLogger.Events() {
|
||||
if event.MessageID != msg.ID {
|
||||
continue
|
||||
}
|
||||
if !isBusinessTXTimestampEventName(event.Event) {
|
||||
continue
|
||||
}
|
||||
senderEventsByName[event.Event] = event.TsUnixNano
|
||||
}
|
||||
|
||||
for eventName, selectedTS := range selectedByEventName {
|
||||
if senderEventsByName[eventName] != selectedTS {
|
||||
t.Fatalf("sender latency event %s = %d, want %d from selected debug record", eventName, senderEventsByName[eventName], selectedTS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user