fix: 压测客户端改为 MQTT 5 并完成 hello 与收发统计

This commit is contained in:
Nixevol
2026-09-30 16:24:31 +08:00
parent 09fb544b7d
commit c72d1f0427
11 changed files with 1398 additions and 259 deletions
+147 -37
View File
@@ -4,72 +4,182 @@ import (
"bytes"
"io"
"net"
"os"
"strings"
"testing"
"time"
"git.asio.asia/nixevol/NixMsg/test/harness"
"github.com/mochi-mqtt/server/v2/packets"
)
// 极简 MQTT broker:读 CONNECT,回 CONNACK accepted,保持连接。
func startFakeMQTTBroker(t *testing.T) (addr string, closeFn func()) {
func TestEncodeConnectMQTT5(t *testing.T) {
raw, err := encodeConnect("ep-1", "password1234")
if err != nil {
t.Fatal(err)
}
if raw[0]>>4 != packets.Connect {
t.Fatalf("type=0x%02x", raw[0])
}
rem, n, err := decodeRemainingLength(raw[1:])
if err != nil {
t.Fatal(err)
}
body := raw[1+n:]
pk := packets.Packet{
ProtocolVersion: mqttProtocolLevel5,
FixedHeader: packets.FixedHeader{Type: packets.Connect, Remaining: rem},
}
if err := pk.ConnectDecode(body); err != nil {
t.Fatal(err)
}
if pk.ProtocolVersion != mqttProtocolLevel5 {
t.Fatalf("protocol version %d", pk.ProtocolVersion)
}
if !pk.Connect.Clean {
t.Fatal("CleanStart=false")
}
if pk.Connect.Keepalive != mqttKeepaliveSec {
t.Fatalf("keepalive=%d", pk.Connect.Keepalive)
}
if pk.Connect.ClientIdentifier != "ep-1" {
t.Fatalf("client id %q", pk.Connect.ClientIdentifier)
}
if string(pk.Connect.Username) != "ep-1" {
t.Fatalf("username %q", pk.Connect.Username)
}
if string(pk.Connect.Password) != "password1234" {
t.Fatalf("password mismatch")
}
if !pk.Connect.UsernameFlag || !pk.Connect.PasswordFlag {
t.Fatal("username/password flag")
}
if !bytes.Contains(raw, []byte{0, 4, 'M', 'Q', 'T', 'T', mqttProtocolLevel5}) {
t.Fatal("CONNECT 不是 MQTT 5")
}
}
func TestConnackReasonMQTT5(t *testing.T) {
reason, err := connackReason([]byte{0x20, 0x03, 0x00, 0x00, 0x00})
if err != nil || reason != 0 {
t.Fatalf("mqtt5 connack: %d %v", reason, err)
}
reason, err = connackReason([]byte{0x20, 0x02, 0x00, 0x87})
if err != nil || reason != 0x87 {
t.Fatalf("mqtt311-style: %d %v", reason, err)
}
}
func TestPercentiles(t *testing.T) {
empty := calcPercentiles(nil)
if empty.N != 0 {
t.Fatalf("empty n=%d", empty.N)
}
one := calcPercentiles([]float64{7})
if one.P50 != 7 || one.P95 != 7 || one.P99 != 7 {
t.Fatalf("%+v", one)
}
vals := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
p := calcPercentiles(vals)
if p.P50 != 5 || p.P95 != 10 || p.P99 != 10 {
t.Fatalf("%+v", p)
}
}
func TestEndpointIDs(t *testing.T) {
ids, err := EndpointIDs("qb", 3)
if err != nil {
t.Fatal(err)
}
if strings.Join(ids, ",") != "qb0000,qb0001,qb0002" {
t.Fatalf("%v", ids)
}
}
func TestMQTT5ConnectFakeBroker(t *testing.T) {
addr, stop := startFakeMQTT5(t)
defer stop()
mc, err := harness.DialMQTTTCP(addr, 2*time.Second)
if err != nil {
t.Fatal(err)
}
defer func() { _ = mc.Close() }()
pkt, err := encodeConnect("ep1", "password1234")
if err != nil {
t.Fatal(err)
}
if err := mc.Send(pkt); err != nil {
t.Fatal(err)
}
ack, err := mc.Recv()
if err != nil {
t.Fatal(err)
}
reason, err := connackReason(ack)
if err != nil || reason != 0 {
t.Fatalf("reason=%d err=%v ack=%x", reason, err, ack)
}
}
func startFakeMQTT5(t *testing.T) (addr string, closeFn func()) {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
done := make(chan struct{})
go func() {
for {
conn, err := ln.Accept()
if err != nil {
select {
case <-done:
return
default:
return
}
return
}
go func(c net.Conn) {
defer func() { _ = c.Close() }()
_ = c.SetDeadline(time.Now().Add(5 * time.Second))
buf := make([]byte, 256)
buf := make([]byte, 2048)
n, err := c.Read(buf)
if err != nil || n < 2 || buf[0] != 0x10 {
if err != nil || n < 10 || buf[0] != 0x10 {
return
}
// CONNACK: type 0x20, remaining 2, flags 0, rc 0
_, _ = c.Write([]byte{0x20, 0x02, 0x00, 0x00})
if !bytes.Contains(buf[:n], []byte{0, 4, 'M', 'Q', 'T', 'T', mqttProtocolLevel5}) {
return
}
_, _ = c.Write([]byte{0x20, 0x03, 0x00, 0x00, 0x00})
_ = c.SetDeadline(time.Time{})
_, _ = io.Copy(io.Discard, c)
}(conn)
}
}()
return ln.Addr().String(), func() {
close(done)
_ = ln.Close()
}
return ln.Addr().String(), func() { _ = ln.Close() }
}
func TestMQTTBenchConnectN(t *testing.T) {
addr, stop := startFakeMQTTBroker(t)
defer stop()
func TestLocalLoadReport(t *testing.T) {
if os.Getenv("NIXMSG_LOAD_VERIFY") == "" {
t.Skip("本地 50×20/s×1m:设置 NIXMSG_LOAD_VERIFY=1")
}
srv, err := harness.Start(harness.Options{})
if err != nil {
t.Fatal(err)
}
defer func() { _ = srv.Stop() }()
var out bytes.Buffer
b := &MQTTBench{Addr: addr, ClientIDPrefix: "q-t-", Timeout: 2 * time.Second}
defer b.Close()
if err := b.ConnectN(3, &out); err != nil {
t.Fatalf("ConnectN: %v", err)
cfg := Config{
HTTPBase: srv.HTTPBase,
AdminPass: srv.AdminPassword,
Password: "password1234",
Prefix: "vr",
N: 50,
Rate: 20,
Duration: time.Minute,
GroupSize: 50,
Scenario: "all",
}
if b.Alive() != 3 {
t.Fatalf("alive=%d", b.Alive())
rep, err := Run(cfg)
if err != nil {
t.Fatal(err)
}
got := out.String()
if !bytes.Contains(out.Bytes(), []byte("mqtt connections: 3")) {
t.Fatalf("output missing count: %q", got)
}
}
func TestMQTTBenchRequiresAddr(t *testing.T) {
b := &MQTTBench{}
if err := b.ConnectN(1, nil); err == nil {
t.Fatal("expected error")
if rep.SubmitMs.N == 0 {
t.Fatalf("empty report:\n%s", rep.Format())
}
t.Log(rep.Format())
}