1
0

dispatch_bench_test.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package batch
  2. import (
  3. "testing"
  4. "github.com/slackhq/nebula/test"
  5. )
  6. // stagePackets builds the stagedPacket entries Commit would have produced, so dispatch benchmarks
  7. // bypass staging and the sort entirely.
  8. func stagePackets(pkts [][]byte) []stagedPacket {
  9. staged := make([]stagedPacket, len(pkts))
  10. for i, p := range pkts {
  11. pp := testPP(p)
  12. staged[i] = stagedPacket{
  13. pkt: p,
  14. key: SortKey{Epoch: 1, Counter: uint64(i + 1)},
  15. proto: pp.Protocol,
  16. fragAny: pp.FragAny,
  17. ipHdrLen: uint16(pp.IPHdrLen),
  18. }
  19. }
  20. return staged
  21. }
  22. func flushLanes(b *testing.B, m *MultiCoalescer) {
  23. b.Helper()
  24. if m.tcp != nil {
  25. if err := m.tcp.Flush(); err != nil {
  26. b.Fatal(err)
  27. }
  28. }
  29. if m.udp != nil {
  30. if err := m.udp.Flush(); err != nil {
  31. b.Fatal(err)
  32. }
  33. }
  34. if err := m.pt.Flush(); err != nil {
  35. b.Fatal(err)
  36. }
  37. }
  38. // runDispatchBench measures dispatch plus the per-batch lane flush: the post-sort half of the
  39. // batcher, which is where the production profile concentrates.
  40. func runDispatchBench(b *testing.B, pkts [][]byte, batchSize int) {
  41. b.Helper()
  42. m := NewMultiCoalescer(nopTunWriter{}, test.NewLogger())
  43. staged := stagePackets(pkts)
  44. b.ReportAllocs()
  45. b.SetBytes(int64(len(pkts[0])))
  46. b.ResetTimer()
  47. for i := 0; i < b.N; i++ {
  48. if err := m.dispatch(staged[i%len(staged)]); err != nil {
  49. b.Fatal(err)
  50. }
  51. if (i+1)%batchSize == 0 {
  52. flushLanes(b, m)
  53. }
  54. }
  55. b.StopTimer()
  56. flushLanes(b, m)
  57. }
  58. // BenchmarkDispatchSingleFlow is the bulk steady state: every packet past the seed appends.
  59. func BenchmarkDispatchSingleFlow(b *testing.B) {
  60. runDispatchBench(b, buildTCPv4BulkFlow(tcpCoalesceMaxSegs, 1200), tcpCoalesceMaxSegs)
  61. }
  62. // BenchmarkDispatchInterleaved16 stresses the openSlots map: 16 flows round-robined defeats the
  63. // lastSlot cache on every packet.
  64. func BenchmarkDispatchInterleaved16(b *testing.B) {
  65. pkts := buildTCPv4Interleaved(16, tcpCoalesceMaxSegs, 1200)
  66. runDispatchBench(b, pkts, len(pkts))
  67. }
  68. // BenchmarkDispatchAckHeavy alternates MSS data with pure ACKs on one flow — the RX shape of a
  69. // bidirectional transfer (the peer's data and its ACKs of our data share the tunnel direction).
  70. func BenchmarkDispatchAckHeavy(b *testing.B) {
  71. pay := make([]byte, 1200)
  72. var pkts [][]byte
  73. seq := uint32(1000)
  74. for range tcpCoalesceMaxSegs / 2 {
  75. pkts = append(pkts, buildTCPv4(seq, tcpAck, pay))
  76. seq += uint32(len(pay))
  77. pkts = append(pkts, buildTCPv4(seq, tcpAck, nil))
  78. }
  79. runDispatchBench(b, pkts, len(pkts))
  80. }
  81. // BenchmarkDispatchUDPFlow is the QUIC-ish bulk UDP shape.
  82. func BenchmarkDispatchUDPFlow(b *testing.B) {
  83. pay := make([]byte, 1200)
  84. pkts := make([][]byte, udpCoalesceMaxSegs)
  85. for i := range pkts {
  86. pkts[i] = buildUDPv4(2000, 443, pay)
  87. }
  88. runDispatchBench(b, pkts, len(pkts))
  89. }
  90. // BenchmarkDispatchSeedHeavy sets PSH on every packet so each one seeds and immediately closes
  91. // its own slot — the small-write RPC shape, and the upper bound on what the seed path (including
  92. // the parsedTCP-to-slot field transfer) can cost.
  93. func BenchmarkDispatchSeedHeavy(b *testing.B) {
  94. pay := make([]byte, 1200)
  95. pkts := make([][]byte, tcpCoalesceMaxSegs)
  96. seq := uint32(1000)
  97. for i := range pkts {
  98. pkts[i] = buildTCPv4(seq, tcpAckPsh, pay)
  99. seq += uint32(len(pay))
  100. }
  101. runDispatchBench(b, pkts, len(pkts))
  102. }