feat: add e2e tests, perf benchmarks, and infrastructure improvements

- messages/bench_test.go: serialization benchmarks (msgpack map vs struct vs protobuf)
- clients/clients_test.go: HTTP client tests with pooling verification (20 tests)
- natsutil/natsutil_test.go: encode/decode roundtrip + binary data tests
- handler/handler_test.go: handler dispatch tests + benchmark
- config/config.go: live reload via fsnotify + RWMutex getter methods
- clients/clients.go: SharedTransport + sync.Pool buffer pooling
- messages/messages.go: typed structs with msgpack+json tags
- messages/proto/: protobuf schema + generated code

Benchmark baseline (ChatRequest roundtrip):
  MsgpackMap:    2949 ns/op, 36 allocs
  MsgpackStruct: 2030 ns/op, 13 allocs (31% faster, 64% fewer allocs)
  Protobuf:       793 ns/op,  8 allocs (73% faster, 78% fewer allocs)
This commit is contained in:
2026-02-20 06:44:37 -05:00
parent d321c9852b
commit 35912d5844
12 changed files with 4260 additions and 391 deletions

515
messages/bench_test.go Normal file
View File

@@ -0,0 +1,515 @@
// Package messages benchmarks compare three serialization strategies:
//
// 1. msgpack map[string]any — the old approach (dynamic, no types)
// 2. msgpack typed struct — the new approach (compile-time safe, short keys)
// 3. protobuf — optional future migration
//
// Run with:
//
// go test -bench=. -benchmem -count=5 ./messages/... | tee bench.txt
// # optional: go install golang.org/x/perf/cmd/benchstat@latest && benchstat bench.txt
package messages
import (
"testing"
"time"
"github.com/vmihailenco/msgpack/v5"
"google.golang.org/protobuf/proto"
pb "git.daviestechlabs.io/daviestechlabs/handler-base/messages/proto"
)
// ────────────────────────────────────────────────────────────────────────────
// Test fixtures — equivalent data across all three encodings
// ────────────────────────────────────────────────────────────────────────────
// chatRequestMap is the legacy map[string]any representation.
func chatRequestMap() map[string]any {
return map[string]any{
"request_id": "req-abc-123",
"user_id": "user-42",
"message": "What is the capital of France?",
"query": "",
"premium": true,
"enable_rag": true,
"enable_reranker": true,
"enable_streaming": false,
"top_k": 10,
"collection": "documents",
"enable_tts": false,
"system_prompt": "You are a helpful assistant.",
"response_subject": "ai.chat.response.req-abc-123",
}
}
// chatRequestStruct is the typed struct representation.
func chatRequestStruct() ChatRequest {
return ChatRequest{
RequestID: "req-abc-123",
UserID: "user-42",
Message: "What is the capital of France?",
Premium: true,
EnableRAG: true,
EnableReranker: true,
TopK: 10,
Collection: "documents",
SystemPrompt: "You are a helpful assistant.",
ResponseSubject: "ai.chat.response.req-abc-123",
}
}
// chatRequestProto is the protobuf representation.
func chatRequestProto() *pb.ChatRequest {
return &pb.ChatRequest{
RequestId: "req-abc-123",
UserId: "user-42",
Message: "What is the capital of France?",
Premium: true,
EnableRag: true,
EnableReranker: true,
TopK: 10,
Collection: "documents",
SystemPrompt: "You are a helpful assistant.",
ResponseSubject: "ai.chat.response.req-abc-123",
}
}
// voiceResponseMap is a voice response with a 16 KB audio payload.
func voiceResponseMap() map[string]any {
return map[string]any{
"request_id": "vr-001",
"response": "The capital of France is Paris.",
"audio": make([]byte, 16384),
"transcription": "What is the capital of France?",
}
}
func voiceResponseStruct() VoiceResponse {
return VoiceResponse{
RequestID: "vr-001",
Response: "The capital of France is Paris.",
Audio: make([]byte, 16384),
Transcription: "What is the capital of France?",
}
}
func voiceResponseProto() *pb.VoiceResponse {
return &pb.VoiceResponse{
RequestId: "vr-001",
Response: "The capital of France is Paris.",
Audio: make([]byte, 16384),
Transcription: "What is the capital of France?",
}
}
// ttsChunkMap simulates a streaming audio chunk (~32 KB).
func ttsChunkMap() map[string]any {
return map[string]any{
"session_id": "tts-sess-99",
"chunk_index": 3,
"total_chunks": 12,
"audio_b64": string(make([]byte, 32768)), // old: base64 string
"is_last": false,
"timestamp": time.Now().Unix(),
"sample_rate": 24000,
}
}
func ttsChunkStruct() TTSAudioChunk {
return TTSAudioChunk{
SessionID: "tts-sess-99",
ChunkIndex: 3,
TotalChunks: 12,
Audio: make([]byte, 32768), // new: raw bytes
IsLast: false,
Timestamp: time.Now().Unix(),
SampleRate: 24000,
}
}
func ttsChunkProto() *pb.TTSAudioChunk {
return &pb.TTSAudioChunk{
SessionId: "tts-sess-99",
ChunkIndex: 3,
TotalChunks: 12,
Audio: make([]byte, 32768),
IsLast: false,
Timestamp: time.Now().Unix(),
SampleRate: 24000,
}
}
// ────────────────────────────────────────────────────────────────────────────
// Wire-size comparison (run once, printed by TestWireSize)
// ────────────────────────────────────────────────────────────────────────────
func TestWireSize(t *testing.T) {
tests := []struct {
name string
mapData any
structVal any
protoMsg proto.Message
}{
{"ChatRequest", chatRequestMap(), chatRequestStruct(), chatRequestProto()},
{"VoiceResponse", voiceResponseMap(), voiceResponseStruct(), voiceResponseProto()},
{"TTSAudioChunk", ttsChunkMap(), ttsChunkStruct(), ttsChunkProto()},
}
for _, tt := range tests {
mapBytes, _ := msgpack.Marshal(tt.mapData)
structBytes, _ := msgpack.Marshal(tt.structVal)
protoBytes, _ := proto.Marshal(tt.protoMsg)
t.Logf("%-16s map=%5d B struct=%5d B proto=%5d B (struct saves %.0f%%, proto saves %.0f%%)",
tt.name,
len(mapBytes), len(structBytes), len(protoBytes),
100*(1-float64(len(structBytes))/float64(len(mapBytes))),
100*(1-float64(len(protoBytes))/float64(len(mapBytes))),
)
}
}
// ────────────────────────────────────────────────────────────────────────────
// Encode benchmarks
// ────────────────────────────────────────────────────────────────────────────
func BenchmarkEncode_ChatRequest_MsgpackMap(b *testing.B) {
data := chatRequestMap()
b.ResetTimer()
for b.Loop() {
msgpack.Marshal(data)
}
}
func BenchmarkEncode_ChatRequest_MsgpackStruct(b *testing.B) {
data := chatRequestStruct()
b.ResetTimer()
for b.Loop() {
msgpack.Marshal(data)
}
}
func BenchmarkEncode_ChatRequest_Protobuf(b *testing.B) {
data := chatRequestProto()
b.ResetTimer()
for b.Loop() {
proto.Marshal(data)
}
}
func BenchmarkEncode_VoiceResponse_MsgpackMap(b *testing.B) {
data := voiceResponseMap()
b.ResetTimer()
for b.Loop() {
msgpack.Marshal(data)
}
}
func BenchmarkEncode_VoiceResponse_MsgpackStruct(b *testing.B) {
data := voiceResponseStruct()
b.ResetTimer()
for b.Loop() {
msgpack.Marshal(data)
}
}
func BenchmarkEncode_VoiceResponse_Protobuf(b *testing.B) {
data := voiceResponseProto()
b.ResetTimer()
for b.Loop() {
proto.Marshal(data)
}
}
func BenchmarkEncode_TTSChunk_MsgpackMap(b *testing.B) {
data := ttsChunkMap()
b.ResetTimer()
for b.Loop() {
msgpack.Marshal(data)
}
}
func BenchmarkEncode_TTSChunk_MsgpackStruct(b *testing.B) {
data := ttsChunkStruct()
b.ResetTimer()
for b.Loop() {
msgpack.Marshal(data)
}
}
func BenchmarkEncode_TTSChunk_Protobuf(b *testing.B) {
data := ttsChunkProto()
b.ResetTimer()
for b.Loop() {
proto.Marshal(data)
}
}
// ────────────────────────────────────────────────────────────────────────────
// Decode benchmarks
// ────────────────────────────────────────────────────────────────────────────
func BenchmarkDecode_ChatRequest_MsgpackMap(b *testing.B) {
encoded, _ := msgpack.Marshal(chatRequestMap())
b.ResetTimer()
for b.Loop() {
var m map[string]any
msgpack.Unmarshal(encoded, &m)
}
}
func BenchmarkDecode_ChatRequest_MsgpackStruct(b *testing.B) {
encoded, _ := msgpack.Marshal(chatRequestStruct())
b.ResetTimer()
for b.Loop() {
var m ChatRequest
msgpack.Unmarshal(encoded, &m)
}
}
func BenchmarkDecode_ChatRequest_Protobuf(b *testing.B) {
encoded, _ := proto.Marshal(chatRequestProto())
b.ResetTimer()
for b.Loop() {
var m pb.ChatRequest
proto.Unmarshal(encoded, &m)
}
}
func BenchmarkDecode_VoiceResponse_MsgpackMap(b *testing.B) {
encoded, _ := msgpack.Marshal(voiceResponseMap())
b.ResetTimer()
for b.Loop() {
var m map[string]any
msgpack.Unmarshal(encoded, &m)
}
}
func BenchmarkDecode_VoiceResponse_MsgpackStruct(b *testing.B) {
encoded, _ := msgpack.Marshal(voiceResponseStruct())
b.ResetTimer()
for b.Loop() {
var m VoiceResponse
msgpack.Unmarshal(encoded, &m)
}
}
func BenchmarkDecode_VoiceResponse_Protobuf(b *testing.B) {
encoded, _ := proto.Marshal(voiceResponseProto())
b.ResetTimer()
for b.Loop() {
var m pb.VoiceResponse
proto.Unmarshal(encoded, &m)
}
}
func BenchmarkDecode_TTSChunk_MsgpackMap(b *testing.B) {
encoded, _ := msgpack.Marshal(ttsChunkMap())
b.ResetTimer()
for b.Loop() {
var m map[string]any
msgpack.Unmarshal(encoded, &m)
}
}
func BenchmarkDecode_TTSChunk_MsgpackStruct(b *testing.B) {
encoded, _ := msgpack.Marshal(ttsChunkStruct())
b.ResetTimer()
for b.Loop() {
var m TTSAudioChunk
msgpack.Unmarshal(encoded, &m)
}
}
func BenchmarkDecode_TTSChunk_Protobuf(b *testing.B) {
encoded, _ := proto.Marshal(ttsChunkProto())
b.ResetTimer()
for b.Loop() {
var m pb.TTSAudioChunk
proto.Unmarshal(encoded, &m)
}
}
// ────────────────────────────────────────────────────────────────────────────
// Roundtrip benchmarks (encode + decode)
// ────────────────────────────────────────────────────────────────────────────
func BenchmarkRoundtrip_ChatRequest_MsgpackMap(b *testing.B) {
data := chatRequestMap()
b.ResetTimer()
for b.Loop() {
enc, _ := msgpack.Marshal(data)
var dec map[string]any
msgpack.Unmarshal(enc, &dec)
}
}
func BenchmarkRoundtrip_ChatRequest_MsgpackStruct(b *testing.B) {
data := chatRequestStruct()
b.ResetTimer()
for b.Loop() {
enc, _ := msgpack.Marshal(data)
var dec ChatRequest
msgpack.Unmarshal(enc, &dec)
}
}
func BenchmarkRoundtrip_ChatRequest_Protobuf(b *testing.B) {
data := chatRequestProto()
b.ResetTimer()
for b.Loop() {
enc, _ := proto.Marshal(data)
var dec pb.ChatRequest
proto.Unmarshal(enc, &dec)
}
}
// ────────────────────────────────────────────────────────────────────────────
// Typed struct unit tests — verify roundtrip correctness
// ────────────────────────────────────────────────────────────────────────────
func TestRoundtrip_ChatRequest(t *testing.T) {
orig := chatRequestStruct()
data, err := msgpack.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var dec ChatRequest
if err := msgpack.Unmarshal(data, &dec); err != nil {
t.Fatal(err)
}
if dec.RequestID != orig.RequestID {
t.Errorf("RequestID = %q, want %q", dec.RequestID, orig.RequestID)
}
if dec.Message != orig.Message {
t.Errorf("Message = %q, want %q", dec.Message, orig.Message)
}
if dec.TopK != orig.TopK {
t.Errorf("TopK = %d, want %d", dec.TopK, orig.TopK)
}
if dec.Premium != orig.Premium {
t.Errorf("Premium = %v, want %v", dec.Premium, orig.Premium)
}
if dec.EffectiveQuery() != orig.Message {
t.Errorf("EffectiveQuery() = %q, want %q", dec.EffectiveQuery(), orig.Message)
}
}
func TestRoundtrip_VoiceResponse(t *testing.T) {
orig := voiceResponseStruct()
data, err := msgpack.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var dec VoiceResponse
if err := msgpack.Unmarshal(data, &dec); err != nil {
t.Fatal(err)
}
if dec.RequestID != orig.RequestID {
t.Errorf("RequestID mismatch")
}
if len(dec.Audio) != len(orig.Audio) {
t.Errorf("Audio len = %d, want %d", len(dec.Audio), len(orig.Audio))
}
if dec.Transcription != orig.Transcription {
t.Errorf("Transcription mismatch")
}
}
func TestRoundtrip_TTSAudioChunk(t *testing.T) {
orig := ttsChunkStruct()
data, err := msgpack.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var dec TTSAudioChunk
if err := msgpack.Unmarshal(data, &dec); err != nil {
t.Fatal(err)
}
if dec.SessionID != orig.SessionID {
t.Errorf("SessionID mismatch")
}
if dec.ChunkIndex != orig.ChunkIndex {
t.Errorf("ChunkIndex = %d, want %d", dec.ChunkIndex, orig.ChunkIndex)
}
if len(dec.Audio) != len(orig.Audio) {
t.Errorf("Audio len = %d, want %d", len(dec.Audio), len(orig.Audio))
}
if dec.SampleRate != orig.SampleRate {
t.Errorf("SampleRate = %d, want %d", dec.SampleRate, orig.SampleRate)
}
}
func TestRoundtrip_PipelineTrigger(t *testing.T) {
orig := PipelineTrigger{
RequestID: "pip-001",
Pipeline: "document-ingestion",
Parameters: map[string]any{"source": "s3://bucket/data"},
}
data, err := msgpack.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var dec PipelineTrigger
if err := msgpack.Unmarshal(data, &dec); err != nil {
t.Fatal(err)
}
if dec.Pipeline != orig.Pipeline {
t.Errorf("Pipeline = %q, want %q", dec.Pipeline, orig.Pipeline)
}
if dec.Parameters["source"] != orig.Parameters["source"] {
t.Errorf("Parameters[source] mismatch")
}
}
func TestRoundtrip_STTTranscription(t *testing.T) {
orig := STTTranscription{
SessionID: "stt-001",
Transcript: "hello world",
Sequence: 5,
IsPartial: false,
IsFinal: true,
Timestamp: time.Now().Unix(),
SpeakerID: "speaker-1",
HasVoiceActivity: true,
State: "listening",
}
data, err := msgpack.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var dec STTTranscription
if err := msgpack.Unmarshal(data, &dec); err != nil {
t.Fatal(err)
}
if dec.Transcript != orig.Transcript {
t.Errorf("Transcript = %q, want %q", dec.Transcript, orig.Transcript)
}
if dec.IsFinal != orig.IsFinal {
t.Error("IsFinal mismatch")
}
}
func TestRoundtrip_ErrorResponse(t *testing.T) {
orig := ErrorResponse{Error: true, Message: "something broke", Type: "InternalError"}
data, err := msgpack.Marshal(orig)
if err != nil {
t.Fatal(err)
}
var dec ErrorResponse
if err := msgpack.Unmarshal(data, &dec); err != nil {
t.Fatal(err)
}
if !dec.Error || dec.Message != "something broke" || dec.Type != "InternalError" {
t.Errorf("ErrorResponse roundtrip mismatch: %+v", dec)
}
}
func TestTimestamp(t *testing.T) {
ts := Timestamp()
now := time.Now().Unix()
if ts < now-1 || ts > now+1 {
t.Errorf("Timestamp() = %d, expected ~%d", ts, now)
}
}