test(gateway): freeze v8 qualification harness
Verify Data Plane / gateway (push) Failing after 3m12s
Verify Data Plane / gateway (push) Failing after 3m12s
This commit is contained in:
@@ -31,7 +31,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -39,7 +38,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
qualificationToolVersion = "versevdi-gateway-qualification/v7"
|
||||
qualificationToolVersion = "versevdi-gateway-qualification/v8"
|
||||
qualificationImpairmentQueuePackets = nativeApolloVideoQueuePackets
|
||||
qualificationImpairmentMaxPackets = 100_000
|
||||
qualificationImpairmentPacketCount = 10_000
|
||||
@@ -76,20 +75,39 @@ type qualificationPathTrace struct {
|
||||
PayloadPreserved bool
|
||||
}
|
||||
|
||||
type qualificationVideoBatchObservation struct {
|
||||
ProviderFrame uint32
|
||||
SourcePacket uint64
|
||||
PacketWithinFrame int
|
||||
StartedAfter time.Duration
|
||||
}
|
||||
|
||||
type qualificationPath struct {
|
||||
client *independentGatewayClient
|
||||
server *Server
|
||||
session *nativeApolloSession
|
||||
fixture *qualificationApolloFixture
|
||||
backend *qualificationTracingBackend
|
||||
process *qualificationGatewayProcess
|
||||
key []byte
|
||||
flow string
|
||||
frame uint32
|
||||
bootTrace atomic.Bool
|
||||
sourceUDP atomic.Uint64
|
||||
closeOnce sync.Once
|
||||
shutdown func()
|
||||
client *independentGatewayClient
|
||||
server *Server
|
||||
session *nativeApolloSession
|
||||
fixture *qualificationApolloFixture
|
||||
backend *qualificationTracingBackend
|
||||
process *qualificationGatewayProcess
|
||||
key []byte
|
||||
flow string
|
||||
frame uint32
|
||||
bootTrace atomic.Bool
|
||||
sourceUDP atomic.Uint64
|
||||
expectedSourceUDP atomic.Uint64
|
||||
closeOnce sync.Once
|
||||
shutdown func()
|
||||
}
|
||||
|
||||
type qualificationProcessingDiagnostics struct {
|
||||
ProcessedFrames int64
|
||||
SourceFrames uint32
|
||||
ExpectedWritesThroughLastFrame uint64
|
||||
FixtureSentPackets uint64
|
||||
SourceUDP uint64
|
||||
BatchHistory []qualificationVideoBatchObservation
|
||||
Gateway qualificationGatewayProcessSnapshot
|
||||
SnapshotError string
|
||||
}
|
||||
|
||||
type qualificationImpairmentProfile struct {
|
||||
@@ -414,23 +432,29 @@ func (b *qualificationTracingBackend) session(sessionID string) *nativeApolloSes
|
||||
}
|
||||
|
||||
type qualificationApolloFixture struct {
|
||||
sessionID string
|
||||
management *httptest.Server
|
||||
stream net.Listener
|
||||
control *net.UDPConn
|
||||
audio *net.UDPConn
|
||||
video *net.UDPConn
|
||||
videoRemote atomic.Pointer[net.UDPAddr]
|
||||
key atomic.Pointer[[]byte]
|
||||
keyReady chan []byte
|
||||
failures chan error
|
||||
closed atomic.Bool
|
||||
closeOnce sync.Once
|
||||
sentPackets atomic.Uint64
|
||||
work protocol.ProviderSessionWork
|
||||
videoPaceMu sync.Mutex
|
||||
videoNext time.Time
|
||||
observeVideoBatch func(int, time.Time)
|
||||
sessionID string
|
||||
management *httptest.Server
|
||||
stream net.Listener
|
||||
control *net.UDPConn
|
||||
audio *net.UDPConn
|
||||
video *net.UDPConn
|
||||
videoRemote atomic.Pointer[net.UDPAddr]
|
||||
key atomic.Pointer[[]byte]
|
||||
keyReady chan []byte
|
||||
failures chan error
|
||||
closed atomic.Bool
|
||||
closeOnce sync.Once
|
||||
sentPackets atomic.Uint64
|
||||
work protocol.ProviderSessionWork
|
||||
videoPaceMu sync.Mutex
|
||||
videoNext time.Time
|
||||
beforeVideoBatch func(context.Context, int) error
|
||||
beforeVideoFirstWrite func(context.Context, int) error
|
||||
observeVideoBatch func(int, time.Time)
|
||||
videoBatchMu sync.Mutex
|
||||
videoBatchEpoch time.Time
|
||||
videoBatchCount uint64
|
||||
videoBatches [128]qualificationVideoBatchObservation
|
||||
|
||||
controlImpairmentMu sync.Mutex
|
||||
controlRTT time.Duration
|
||||
@@ -438,6 +462,30 @@ type qualificationApolloFixture struct {
|
||||
controlRandom uint64
|
||||
}
|
||||
|
||||
func (f *qualificationApolloFixture) videoBatchHistory() []qualificationVideoBatchObservation {
|
||||
f.videoBatchMu.Lock()
|
||||
defer f.videoBatchMu.Unlock()
|
||||
count := min(f.videoBatchCount, uint64(len(f.videoBatches)))
|
||||
result := make([]qualificationVideoBatchObservation, 0, count)
|
||||
for index := f.videoBatchCount - count; index < f.videoBatchCount; index++ {
|
||||
result = append(result, f.videoBatches[index%uint64(len(f.videoBatches))])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *qualificationApolloFixture) recordVideoBatch(providerFrame uint32, sourcePacket uint64, packetWithinFrame int, started time.Time) {
|
||||
f.videoBatchMu.Lock()
|
||||
if f.videoBatchEpoch.IsZero() {
|
||||
f.videoBatchEpoch = started
|
||||
}
|
||||
f.videoBatches[f.videoBatchCount%uint64(len(f.videoBatches))] = qualificationVideoBatchObservation{
|
||||
ProviderFrame: providerFrame, SourcePacket: sourcePacket,
|
||||
PacketWithinFrame: packetWithinFrame, StartedAfter: started.Sub(f.videoBatchEpoch),
|
||||
}
|
||||
f.videoBatchCount++
|
||||
f.videoBatchMu.Unlock()
|
||||
}
|
||||
|
||||
func newQualificationApolloFixture(t *testing.T, serverTLS, clientTLS *tls.Config, sessionID string, profile qualificationMediaProfile) *qualificationApolloFixture {
|
||||
t.Helper()
|
||||
fixture := &qualificationApolloFixture{sessionID: sessionID, keyReady: make(chan []byte, 1), failures: make(chan error, 8)}
|
||||
@@ -749,7 +797,7 @@ func (f *qualificationApolloFixture) serveMedia(socket *net.UDPConn, video bool)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *qualificationApolloFixture) sendVideo(ctx context.Context, packets [][]byte) error {
|
||||
func (f *qualificationApolloFixture) sendVideo(ctx context.Context, providerFrame uint32, packets [][]byte) error {
|
||||
for f.videoRemote.Load() == nil {
|
||||
select {
|
||||
case err := <-f.failures:
|
||||
@@ -770,21 +818,37 @@ func (f *qualificationApolloFixture) sendVideo(ctx context.Context, packets [][]
|
||||
|
||||
f.videoPaceMu.Lock()
|
||||
defer f.videoPaceMu.Unlock()
|
||||
frameStart := time.Now()
|
||||
if f.videoNext.After(frameStart) {
|
||||
frameStart = f.videoNext
|
||||
}
|
||||
framePackets := 0
|
||||
for batchStart := 0; batchStart < len(packets); batchStart += batchSize {
|
||||
batchEnd := min(batchStart+batchSize, len(packets))
|
||||
due := frameStart.Add(qualificationApolloVideoOffset(framePackets, packetsPerMillisecond))
|
||||
if err := qualificationWaitContext(ctx, due); err != nil {
|
||||
sourceStart := f.sentPackets.Load()
|
||||
if err := qualificationWaitContext(ctx, f.videoNext); err != nil {
|
||||
return err
|
||||
}
|
||||
if f.observeVideoBatch != nil {
|
||||
f.observeVideoBatch(framePackets, time.Now())
|
||||
if f.beforeVideoBatch != nil {
|
||||
if err := f.beforeVideoBatch(ctx, framePackets); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, packet := range packets[batchStart:batchEnd] {
|
||||
if f.beforeVideoFirstWrite != nil {
|
||||
if err := f.beforeVideoFirstWrite(ctx, framePackets); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
firstPacket := packets[batchStart]
|
||||
if len(firstPacket) != len(packets[0]) {
|
||||
return ErrProviderMalformed
|
||||
}
|
||||
if _, err := f.video.WriteToUDP(firstPacket, remote); err != nil {
|
||||
return err
|
||||
}
|
||||
batchStarted := time.Now()
|
||||
f.sentPackets.Add(1)
|
||||
f.recordVideoBatch(providerFrame, sourceStart, framePackets, batchStarted)
|
||||
if f.observeVideoBatch != nil {
|
||||
f.observeVideoBatch(framePackets, batchStarted)
|
||||
}
|
||||
for _, packet := range packets[batchStart+1 : batchEnd] {
|
||||
if len(packet) != len(packets[0]) {
|
||||
return ErrProviderMalformed
|
||||
}
|
||||
@@ -795,8 +859,8 @@ func (f *qualificationApolloFixture) sendVideo(ctx context.Context, packets [][]
|
||||
}
|
||||
currentBatch := batchEnd - batchStart
|
||||
framePackets += currentBatch
|
||||
f.videoNext = batchStarted.Add(qualificationApolloVideoOffset(currentBatch, packetsPerMillisecond))
|
||||
}
|
||||
f.videoNext = frameStart.Add(qualificationApolloVideoOffset(framePackets, packetsPerMillisecond))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1145,15 +1209,31 @@ func (p *qualificationPath) emit(t *testing.T, payload []byte) (qualificationPat
|
||||
}
|
||||
p.frame++
|
||||
packets := qualificationSourceVideoPackets(t, p.key, p.frame, payload)
|
||||
p.expectedSourceUDP.Add(uint64(len(packets)))
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
if err := p.fixture.sendVideo(ctx, packets); err != nil {
|
||||
if err := p.fixture.sendVideo(ctx, p.frame, packets); err != nil {
|
||||
return qualificationPathTrace{}, err
|
||||
}
|
||||
p.sourceUDP.Add(uint64(len(packets)))
|
||||
return qualificationPathTrace{}, nil
|
||||
}
|
||||
|
||||
func (p *qualificationPath) processingDiagnostics(processed int64) qualificationProcessingDiagnostics {
|
||||
diagnostics := qualificationProcessingDiagnostics{
|
||||
ProcessedFrames: processed, SourceFrames: p.frame,
|
||||
ExpectedWritesThroughLastFrame: p.expectedSourceUDP.Load(),
|
||||
FixtureSentPackets: p.fixture.sentPackets.Load(), SourceUDP: p.sourceUDP.Load(),
|
||||
BatchHistory: p.fixture.videoBatchHistory(),
|
||||
}
|
||||
var err error
|
||||
diagnostics.Gateway, err = p.process.snapshot()
|
||||
if err != nil {
|
||||
diagnostics.SnapshotError = err.Error()
|
||||
}
|
||||
return diagnostics
|
||||
}
|
||||
|
||||
func (p *qualificationPath) receivePayload(parent context.Context) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(parent, 2*time.Second)
|
||||
defer cancel()
|
||||
@@ -1711,14 +1791,21 @@ func qualificationMaximumDeliveryBytes(deliveries []qualificationDeliverySample,
|
||||
return maximum
|
||||
}
|
||||
|
||||
func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile, rawPath string) (qualificationProcessingSummary, error) {
|
||||
func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile, rawPath string) (summary qualificationProcessingSummary, err error) {
|
||||
t.Helper()
|
||||
payload := qualificationFramePayload(profile, 0)
|
||||
if len(payload) < 4 {
|
||||
return qualificationProcessingSummary{}, errors.New("qualification payload too small")
|
||||
}
|
||||
path := newQualificationProcessingPath(t, profile, qualificationFramePacerKbps(profile))
|
||||
defer path.Close()
|
||||
var processed int64
|
||||
defer func() {
|
||||
if err != nil {
|
||||
diagnostics := path.processingDiagnostics(processed)
|
||||
err = fmt.Errorf("%w; diagnostics=%#v", err, diagnostics)
|
||||
}
|
||||
path.Close()
|
||||
}()
|
||||
if err := runQualificationProcessWarmup(t, path, profile, payload); err != nil {
|
||||
return qualificationProcessingSummary{}, err
|
||||
}
|
||||
@@ -1773,15 +1860,13 @@ func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile,
|
||||
}
|
||||
receivedDone <- nil
|
||||
}()
|
||||
var processed int64
|
||||
var nextRelease time.Time
|
||||
payloadDigest := sha256.New()
|
||||
for processed < targetFrames {
|
||||
select {
|
||||
case receiveErr := <-receivedDone:
|
||||
if receiveErr != nil {
|
||||
snapshot, _ := path.process.snapshot()
|
||||
return qualificationProcessingSummary{}, fmt.Errorf("%w; gateway snapshot=%#v", receiveErr, snapshot)
|
||||
return qualificationProcessingSummary{}, receiveErr
|
||||
}
|
||||
return qualificationProcessingSummary{}, errors.New("qualification processing receiver ended early")
|
||||
default:
|
||||
@@ -1799,8 +1884,7 @@ func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile,
|
||||
processed++
|
||||
}
|
||||
if err := <-receivedDone; err != nil {
|
||||
snapshot, _ := path.process.snapshot()
|
||||
return qualificationProcessingSummary{}, fmt.Errorf("%w; gateway snapshot=%#v", err, snapshot)
|
||||
return qualificationProcessingSummary{}, err
|
||||
}
|
||||
qualificationWaitUntil(started.Add(profile.Duration))
|
||||
actualDuration := time.Since(started)
|
||||
@@ -1827,7 +1911,7 @@ func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile,
|
||||
if err != nil {
|
||||
return qualificationProcessingSummary{}, err
|
||||
}
|
||||
summary, err := summarizeQualificationSamples(samples)
|
||||
summary, err = summarizeQualificationSamples(samples)
|
||||
if err != nil {
|
||||
return qualificationProcessingSummary{}, err
|
||||
}
|
||||
@@ -1950,12 +2034,7 @@ func runQualificationWarmup(t *testing.T, path *qualificationPath, profile quali
|
||||
}
|
||||
|
||||
func qualificationRuntimeSample(started time.Time) qualificationResourceSample {
|
||||
var usage syscall.Rusage
|
||||
cpuSeconds := -1.0
|
||||
if syscall.Getrusage(syscall.RUSAGE_SELF, &usage) == nil {
|
||||
cpuSeconds = float64(usage.Utime.Sec+usage.Stime.Sec) +
|
||||
float64(usage.Utime.Usec+usage.Stime.Usec)/1_000_000
|
||||
}
|
||||
cpuSeconds := qualificationProcessCPUSeconds()
|
||||
samples := []runtimemetrics.Sample{
|
||||
{Name: "/memory/classes/heap/objects:bytes"},
|
||||
{Name: "/sched/goroutines:goroutines"},
|
||||
|
||||
Reference in New Issue
Block a user