diff --git a/.gitignore b/.gitignore index 1b82a02..70b22f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ bin/* inbox/* -*.jsonl \ No newline at end of file +*.jsonl +*.html diff --git a/cmd/internal/latencylog/summary.go b/cmd/internal/latencylog/summary.go index 7d6429a..89a1bba 100644 --- a/cmd/internal/latencylog/summary.go +++ b/cmd/internal/latencylog/summary.go @@ -31,14 +31,17 @@ type Summary struct { BodySize int `json:"body_size"` //消息体大小(字节数) Timestamps map[string]int64 `json:"timestamps"` //事件时间戳,key 是事件名称,value 是 UnixNano 时间戳 - AProcessingLatencyNS *int64 `json:"a_processing_latency_ns,omitempty"` // A 处理时延:A_TX_SCHED - A_APP_PREP_BEGIN - AQueueLatencyNS *int64 `json:"a_queue_latency_ns,omitempty"` // A 排队时延:A_TX_SOFTWARE - A_TX_SCHED - ABTransportPropagationNS *int64 `json:"a_b_transport_propagation_ns,omitempty"` // A-B 传输+传播时延近似:B_APP_RECV - A_TX_SOFTWARE - BKernelReceivePathLatencyNS *int64 `json:"b_kernel_receive_path_latency_ns,omitempty"` // B 内核接收路径近似:B_APP_RECV - B_RX_SOFTWARE - BProcessingLatencyNS *int64 `json:"b_processing_latency_ns,omitempty"` // B 处理时延:B_PERSIST_END - B_APP_RECV - EndToEndLatencyNS *int64 `json:"end_to_end_latency_ns,omitempty"` // 端到端时延:B_PERSIST_END - A_APP_PREP_BEGIN - ApproxRTTNS *int64 `json:"approx_rtt_ns,omitempty"` // 近似 RTT:首条反向应答的 B_APP_RECV - 当前请求的 A_TX_SOFTWARE - MissingTimestamps []string `json:"missing_timestamps,omitempty"` // 缺失的时间戳列表,包含 requiredTimestampNames 中但在原始事件中没有的事件名称 + AProcessingLatencyNS *int64 `json:"a_processing_latency_ns,omitempty"` // A 处理时延:A_TX_SCHED - A_APP_PREP_BEGIN + AQueueLatencyNS *int64 `json:"a_queue_latency_ns,omitempty"` // A 排队时延:A_TX_SOFTWARE - A_TX_SCHED + ABTransportPropagationNS *int64 `json:"a_b_transport_propagation_ns,omitempty"` // A-B 传输+传播时延近似:B_APP_RECV - A_TX_SOFTWARE + BKernelReceivePathLatencyNS *int64 `json:"b_kernel_receive_path_latency_ns,omitempty"` // B 内核接收路径近似:B_APP_RECV - B_RX_SOFTWARE + BProcessingLatencyNS *int64 `json:"b_processing_latency_ns,omitempty"` // B 处理时延:B_PERSIST_END - B_APP_RECV + EndToEndLatencyNS *int64 `json:"end_to_end_latency_ns,omitempty"` // 端到端时延:B_PERSIST_END - A_APP_PREP_BEGIN + AProcessingBitrateBPS *float64 `json:"a_processing_bitrate_bps,omitempty"` // A 处理阶段近似比特率:(BodySize * 8) / A 处理时延(秒) + ABTransportPropagationBitrateBPS *float64 `json:"a_b_transport_propagation_bitrate_bps,omitempty"` // A-B 传输+传播阶段近似比特率:(BodySize * 8) / A-B 传输+传播时延(秒) + EndToEndBitrateBPS *float64 `json:"end_to_end_bitrate_bps,omitempty"` // 端到端近似比特率:(BodySize * 8) / 端到端时延(秒) + ApproxRTTNS *int64 `json:"approx_rtt_ns,omitempty"` // 近似 RTT:首条反向应答的 B_APP_RECV - 当前请求的 A_TX_SOFTWARE + MissingTimestamps []string `json:"missing_timestamps,omitempty"` // 缺失的时间戳列表,包含 requiredTimestampNames 中但在原始事件中没有的事件名称 } // LoadEventsFromFiles 从JSONL 原始日志文件中加载事件。 @@ -213,6 +216,10 @@ func completeSummary(summary *Summary) { if value := subtractIfPresent(summary.Timestamps, EventBPersistEnd, EventAAppPrepBegin); value != nil { summary.EndToEndLatencyNS = value } + + summary.AProcessingBitrateBPS = calculateBitrateBPS(summary.BodySize, summary.AProcessingLatencyNS) + summary.ABTransportPropagationBitrateBPS = calculateBitrateBPS(summary.BodySize, summary.ABTransportPropagationNS) + summary.EndToEndBitrateBPS = calculateBitrateBPS(summary.BodySize, summary.EndToEndLatencyNS) } type routeKey struct { @@ -340,6 +347,16 @@ func subtractSummaryTimestamps(endSummary *Summary, endName string, beginSummary return &value } +// 除法函数,如果 bodySize <= 0 或 latencyNS 不存在或 <= 0,则返回 nil;否则返回 bodySize / latencyNS 的结果。 +func calculateBitrateBPS(bodySize int, latencyNS *int64) *float64 { + if bodySize <= 0 || latencyNS == nil || *latencyNS <= 0 { + return nil + } + + value := float64(bodySize) * 8 * 1_000_000_000 / float64(*latencyNS) + return &value +} + // 判断事件是否是业务相关的时延事件(其中一项) func IsBusinessEvent(event Event) bool { switch event.Event { diff --git a/cmd/internal/latencylog/summary_chart.go b/cmd/internal/latencylog/summary_chart.go index 30b7559..a37a4c2 100644 --- a/cmd/internal/latencylog/summary_chart.go +++ b/cmd/internal/latencylog/summary_chart.go @@ -131,6 +131,23 @@ const summaryChartHTMLTemplate = ` font-size: 13px; margin-bottom: 12px; } + .ratio-list { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin: -2px 0 12px; + } + .ratio-pill { + display: inline-flex; + align-items: center; + padding: 6px 10px; + border-radius: 999px; + border: 1px solid var(--border); + background: #f7f9fc; + color: var(--text); + font-size: 12px; + line-height: 1.2; + } .bar { height: 24px; display: flex; @@ -216,6 +233,13 @@ const summaryChartHTMLTemplate = `
{{.Subtitle}}
{{.ApproxRTT}}
+ {{if .RatioMetrics}} +
+ {{range .RatioMetrics}} + {{.Label}} {{.Value}} + {{end}} +
+ {{end}}
{{range .Segments}}
@@ -265,6 +289,7 @@ type summaryChartRow struct { EndToEnd string ApproxRTT string MissingTimestamps string + RatioMetrics []summaryChartValue Segments []summaryChartSegment } @@ -275,6 +300,11 @@ type summaryChartSegment struct { WidthPercent float64 } +type summaryChartValue struct { + Label string + Value string +} + type summaryChartSegmentMetric struct { label string value *int64 @@ -366,6 +396,24 @@ func buildSummaryChartRow(summary Summary) summaryChartRow { row.ApproxRTT = fmt.Sprintf("Approx RTT: %s", formatLatencyNS(*summary.ApproxRTTNS)) } + ratioMetrics := []struct { + label string + value *float64 + }{ + {label: "A processing bitrate", value: summary.AProcessingBitrateBPS}, + {label: "A-B transport + propagation bitrate", value: summary.ABTransportPropagationBitrateBPS}, + {label: "End-to-end bitrate", value: summary.EndToEndBitrateBPS}, + } + for _, metric := range ratioMetrics { + if metric.value == nil || *metric.value <= 0 { + continue + } + row.RatioMetrics = append(row.RatioMetrics, summaryChartValue{ + Label: metric.label, + Value: formatBitrateBPS(*metric.value), + }) + } + if summary.EndToEndLatencyNS == nil || *summary.EndToEndLatencyNS <= 0 { return row } @@ -444,3 +492,7 @@ func buildSummaryChartSubtitle(summary Summary) string { func formatLatencyNS(ns int64) string { return fmt.Sprintf("%.3f ms", float64(ns)/1_000_000) } + +func formatBitrateBPS(bitsPerSecond float64) string { + return fmt.Sprintf("%.3f Mb/s", bitsPerSecond/1_000_000) +} diff --git a/cmd/internal/latencylog/summary_chart_test.go b/cmd/internal/latencylog/summary_chart_test.go index be466ac..d9f41ec 100644 --- a/cmd/internal/latencylog/summary_chart_test.go +++ b/cmd/internal/latencylog/summary_chart_test.go @@ -15,20 +15,26 @@ func TestWriteSummariesHTMLChart(t *testing.T) { transport := int64(40_000_000) bProcessing := int64(30_000_000) endToEnd := int64(100_000_000) + aProcessingBitrate := float64(5) * 8 * 1_000_000_000 / float64(aProcessing) + transportBitrate := float64(5) * 8 * 1_000_000_000 / float64(transport) + endToEndBitrate := float64(5) * 8 * 1_000_000_000 / float64(endToEnd) summaries := []Summary{ { - MessageType: protocol.MessageTypeText, - MessageID: 7, - From: "peer-a", - To: "peer-b", - BodySize: 5, - AProcessingLatencyNS: &aProcessing, - AQueueLatencyNS: &aQueue, - ABTransportPropagationNS: &transport, - BProcessingLatencyNS: &bProcessing, - EndToEndLatencyNS: &endToEnd, - ApproxRTTNS: &endToEnd, + MessageType: protocol.MessageTypeText, + MessageID: 7, + From: "peer-a", + To: "peer-b", + BodySize: 5, + AProcessingLatencyNS: &aProcessing, + AQueueLatencyNS: &aQueue, + ABTransportPropagationNS: &transport, + BProcessingLatencyNS: &bProcessing, + EndToEndLatencyNS: &endToEnd, + AProcessingBitrateBPS: &aProcessingBitrate, + ABTransportPropagationBitrateBPS: &transportBitrate, + EndToEndBitrateBPS: &endToEndBitrate, + ApproxRTTNS: &endToEnd, }, { MessageType: protocol.MessageTypeFile, @@ -58,6 +64,9 @@ func TestWriteSummariesHTMLChart(t *testing.T) { "peer-a -> peer-b | 5 bytes", "End-to-end: 100.000 ms", "Approx RTT: 100.000 ms", + "A processing bitrate 0.002 Mb/s", + "A-B transport + propagation bitrate 0.001 Mb/s", + "End-to-end bitrate 0.000 Mb/s", "A processing 20.000 ms", "A-B transport + propagation 40.000 ms", "file #8 (payload.bin)", diff --git a/cmd/internal/latencylog/summary_test.go b/cmd/internal/latencylog/summary_test.go index e8ac2f8..cb4bc8b 100644 --- a/cmd/internal/latencylog/summary_test.go +++ b/cmd/internal/latencylog/summary_test.go @@ -13,13 +13,13 @@ import ( func TestSummarizeEventsComputesLatencyMetrics(t *testing.T) { events := []Event{ - {TsUnixNano: 100, Event: EventAAppPrepBegin, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b"}, - {TsUnixNano: 120, Event: EventATXSched, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b"}, - {TsUnixNano: 140, Event: EventATXSoftware, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b"}, - {TsUnixNano: 180, Event: EventBRXSoftware, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b"}, - {TsUnixNano: 220, Event: EventBAppRecv, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b"}, - {TsUnixNano: 230, Event: EventBPersistBegin, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b"}, - {TsUnixNano: 260, Event: EventBPersistEnd, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b"}, + {TsUnixNano: 100, Event: EventAAppPrepBegin, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b", BodySize: 320}, + {TsUnixNano: 120, Event: EventATXSched, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b", BodySize: 320}, + {TsUnixNano: 140, Event: EventATXSoftware, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b", BodySize: 320}, + {TsUnixNano: 180, Event: EventBRXSoftware, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b", BodySize: 320}, + {TsUnixNano: 220, Event: EventBAppRecv, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b", BodySize: 320}, + {TsUnixNano: 230, Event: EventBPersistBegin, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b", BodySize: 320}, + {TsUnixNano: 260, Event: EventBPersistEnd, MessageType: protocol.MessageTypeText, MessageID: 1, From: "peer-a", To: "peer-b", BodySize: 320}, } summaries := SummarizeEvents(events) @@ -46,6 +46,15 @@ func TestSummarizeEventsComputesLatencyMetrics(t *testing.T) { if got := ptrValue(summary.EndToEndLatencyNS); got != 160 { t.Fatalf("EndToEndLatencyNS = %d, want 160", got) } + if got := ptrValueFloat(summary.AProcessingBitrateBPS); got != 128_000_000_000 { + t.Fatalf("AProcessingBitrateBPS = %v, want 128000000000", got) + } + if got := ptrValueFloat(summary.ABTransportPropagationBitrateBPS); got != 32_000_000_000 { + t.Fatalf("ABTransportPropagationBitrateBPS = %v, want 32000000000", got) + } + if got := ptrValueFloat(summary.EndToEndBitrateBPS); got != 16_000_000_000 { + t.Fatalf("EndToEndBitrateBPS = %v, want 16000000000", got) + } if got := summary.Timestamps[EventBRXSoftware]; got != 180 { t.Fatalf("timestamps[%q] = %d, want 180", EventBRXSoftware, got) } @@ -128,12 +137,12 @@ func TestLoadAndWriteSummaryFiles(t *testing.T) { }) for _, event := range []Event{ - {TsUnixNano: 100, Event: EventAAppPrepBegin, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b"}, - {TsUnixNano: 120, Event: EventATXSched, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b"}, - {TsUnixNano: 140, Event: EventATXSoftware, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b"}, - {TsUnixNano: 180, Event: EventBRXSoftware, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b"}, - {TsUnixNano: 220, Event: EventBAppRecv, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b"}, - {TsUnixNano: 260, Event: EventBPersistEnd, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b"}, + {TsUnixNano: 100, Event: EventAAppPrepBegin, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b", BodySize: 320}, + {TsUnixNano: 120, Event: EventATXSched, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b", BodySize: 320}, + {TsUnixNano: 140, Event: EventATXSoftware, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b", BodySize: 320}, + {TsUnixNano: 180, Event: EventBRXSoftware, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b", BodySize: 320}, + {TsUnixNano: 220, Event: EventBAppRecv, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b", BodySize: 320}, + {TsUnixNano: 260, Event: EventBPersistEnd, MessageType: protocol.MessageTypeText, MessageID: 3, From: "peer-a", To: "peer-b", BodySize: 320}, } { if err := rawLogger.LogEvent(event); err != nil { t.Fatalf("LogEvent() error = %v", err) @@ -174,6 +183,9 @@ func TestLoadAndWriteSummaryFiles(t *testing.T) { if got := ptrValue(summary.EndToEndLatencyNS); got != 160 { t.Fatalf("EndToEndLatencyNS = %d, want 160", got) } + if got := ptrValueFloat(summary.EndToEndBitrateBPS); got != 16_000_000_000 { + t.Fatalf("EndToEndBitrateBPS = %v, want 16000000000", got) + } } func ptrValue(value *int64) int64 { @@ -182,3 +194,10 @@ func ptrValue(value *int64) int64 { } return *value } + +func ptrValueFloat(value *float64) float64 { + if value == nil { + return 0 + } + return *value +} diff --git a/cmd/internal/transport/tcp_linux.go b/cmd/internal/transport/tcp_linux.go index 6578bf1..5641cc8 100644 --- a/cmd/internal/transport/tcp_linux.go +++ b/cmd/internal/transport/tcp_linux.go @@ -15,11 +15,11 @@ import ( ) const ( - linuxTimestampControlBufferSize = 2560 // 控制消息缓冲区。 - linuxSocketWriteBufferSize = 10 * 1024 * 1024 // 请求把 socket 发送缓冲区调到 10 MiB。 - linuxTXTimestampWaitTimeout = 250 * time.Millisecond // 等待 TX 时间戳的上限。 - linuxTXTimestampPollInterval = time.Millisecond // 轮询 errqueue 的间隔。 - linuxDataPollInterval = time.Millisecond // 轮询普通收发的间隔。 + linuxTimestampControlBufferSize = 2048 // 控制消息缓冲区。 + linuxSocketWriteBufferSize = 10 * 1024 * 1024 // 请求把 socket 发送缓冲区调到 10 MiB。 + linuxTXTimestampWaitTimeout = 5000 * time.Millisecond // 等待 TX 时间戳的上限。 + linuxTXTimestampPollInterval = time.Millisecond // 轮询 errqueue 的间隔。 + linuxDataPollInterval = time.Millisecond // 轮询普通收发的间隔。 linuxSOTimestampingNew = 0x41 linuxSCMTimestampingNew = linuxSOTimestampingNew diff --git a/latencysummary b/latencysummary deleted file mode 100755 index e8ecfe9..0000000 Binary files a/latencysummary and /dev/null differ