
Caos Monkey, mas para teste de áudio e vídeo (webRTC e UDP)
Plataforma de engenharia do caos distribuída para teste de carga em sistemas de videoconferência. Simula mais de 1500 participantes WebRTC com streams H.264/Opus e injeta picos de caos na rede para validar a resiliência do sistema sob condições degradadas.
Pipeline de Processamento de Mídia:
Plano de Controle:
Pool de Participantes:
participant_id % total_partitions = partition_idAuto-Configuração Kubernetes:
orchestrator-3 → PARTITION_ID=3base_port + (partition_id × 10000) + participant_indexCadeia de Relé UDP (apenas Kubernetes):
Pods Orchestrator (10×) → UDP :5000 → Pod udp-relay (Python)
→ TCP com prefixo de comprimento :5001 → kubectl port-forward 15001:5001
→ tools/udp-relay (Go) → UDP :5002 → Seu Receptor
Infraestrutura WebRTC:
Integração com Cliente:
Pilha de Observabilidade (Opcional):
/metrics de todos os pods do orchestrator a cada 5sprometheus.io/scrape: "true"Cada participante virtual gera streams de mídia reais:
Cinco tipos de pico simulam condições reais de rede:
Os picos são distribuídos ao longo da duração do teste usando estratégias configuráveis:
Implantações Kubernetes usam particionamento de participantes para escalonamento horizontal:
participant_id % total_partitions == partition_idbase_port + (partition_id * 10000) + participant_indexMelhor para: Desenvolvimento, depuração, testes de pequena escala (1-100 participantes)
# Iniciar orchestrator
go run cmd/main.go
# Em outro terminal: Iniciar receptor UDP
go run examples/go/udp_receiver.go 5002
# Editar config/config.json para definir num_participants: 10
# Executar teste de caos
go run tools/chaos-test/main.go -config config/config.json
O que acontece:
:8080127.0.0.1:5002Configuração (config/config.json):
{
"base_url": "http://localhost:8080",
"media_path": "public/rick-roll.mp4",
"num_participants": 10,
"duration_seconds": 300,
"spikes": {
"count": 20,
"interval_seconds": 5,
"types": { "rtp_packet_loss": {...}, "network_jitter": {...} }
},
"spike_distribution": {
"strategy": "random",
"min_spacing_seconds": 5,
"jitter_percent": 15
}
}
Melhor para: Testes isolados, CI/CD, testes de médio porte (100-500 participantes)
Pré-requisitos:
docker-compose instalado# Construir e iniciar container orchestrator
./scripts/start_everything.sh build
# Em outro terminal: Iniciar receptor UDP
go run examples/go/udp_receiver.go 5002
# Editar config/config.json para definir num_participants: 100
# Executar teste de caos (direcionado ao container)
go run tools/chaos-test/main.go -config config/config.json
Limites de Recursos (editar docker-compose.yaml):
services:
orchestrator:
deploy:
resources:
limits:
cpus: "14.0"
memory: 6G # Aumentar para mais participantes
Guia de Dimensionamento:
| Memória Docker | Máx. Participantes | Núcleos CPU |
|---|---|---|
| 8 GB | ~100 | 4 |
| 16 GB | ~250 | 8 |
| 24 GB | ~400 | 12 |
| 32 GB | ~500 | 14 |
Melhor para: Testes de grande escala (500-1500 participantes), escalonamento horizontal, validação de produção
Pré-requisitos:
# Nix fornece: Go, Docker, kubectl, kind, ffmpeg
nix develop
# Ou usar direnv para ativação automática
echo "use flake" > .envrc
direnv allow
# Implantação automática com configurações ideais (detecta recursos do sistema)
./scripts/start_everything.sh run -config config/config.json
# Ou especificar arquivos de mídia personalizados
./scripts/start_everything.sh run --media=path/to/video.mp4 -config config/config.json
O que acontece:
kubectl port-forward para o relé UDPOpção A: Receptor UDP (Recomendado para Kubernetes)
# Recebe stream agregado de todos os 1500 participantes
go run ./examples/go/udp_receiver.go 5002
Opção B: Receptor WebRTC (Vários Participantes)
# Conecta-se a até 150 participantes via WebRTC
go run ./examples/go/webrtc_receiver.go http://localhost:8080 <test_id> 150
Fluxo da Arquitetura:
1500 Participantes em 10 pods
→ Cada pod: 150 participantes
→ Partição por participant_id % 10
→ Todos enviam UDP para udp-relay:5000
→ Relé UDP agrega → TCP :5001
→ kubectl port-forward 15001:5001
→ Relé local converte TCP → UDP :5002
→ Seu receptor obtém todos os 1500 streams
Nota: O script start_everything.sh configura automaticamente:
# Construir e carregar imagem
docker build -t chaos-monkey-orchestrator:latest .
kind load docker-image chaos-monkey-orchestrator:latest
# Implantar
kubectl apply -f k8s/orchestrator/orchestrator.yaml
kubectl apply -f k8s/udp-relay/udp-relay.yaml
# Aguardar pods
kubectl wait --for=condition=ready pod -l app=orchestrator --timeout=300s
# Port-forward do relé UDP
kubectl port-forward udp-relay 15001:5001 &
# Iniciar relé local TCP→UDP
go run tools/udp-relay/main.go &
# Em outro terminal: Iniciar receptor
go run ./examples/go/udp_receiver.go 5002
# Em outro terminal: Executar teste de caos
go run tools/chaos-test/main.go -config config/config.json
# Excluir recursos Kubernetes
./scripts/cleanup.sh
# Ou excluir todo o cluster
kind delete cluster --name av-chaos-monkey
# Compilar para Linux x86_64 (mais comum)
nix build .#packages.x86_64-linux.av-chaos-monkey
# Compilar para ARM64 (Raspberry Pi, AWS Graviton)
nix build .#packages.aarch64-linux.av-chaos-monkey
# Compilar para macOS Intel
nix build .#packages.x86_64-darwin.av-chaos-monkey
# Compilar para macOS Apple Silicon
nix build .#packages.aarch64-darwin.av-chaos-monkey
# Localização do binário
./result/bin/main
# Criar teste
POST /api/v1/test/create
{
"test_id": "optional_id",
"num_participants": 100,
"video": {...},
"audio": {...},
"duration_seconds": 600,
"spikes": [...],
"spike_distribution": {
"strategy": "even",
"min_spacing_seconds": 5,
"jitter_percent": 15
}
}
# Iniciar teste
POST /api/v1/test/{test_id}/start
# Obter métricas
GET /api/v1/test/{test_id}/metrics
# Parar teste
POST /api/v1/test/{test_id}/stop
# Obter oferta SDP
GET /api/v1/test/{test_id}/sdp/{participant_id}
# Definir resposta SDP
POST /api/v1/test/{test_id}/sdp/{participant_id}
{"sdp_answer": "v=0..."}
# Injetar pico
POST /api/v1/test/{test_id}/spike
{
"spike_id": "unique_id",
"type": "rtp_packet_loss",
"duration_seconds": 30,
"participant_ids": [1001, 1002],
"params": {"loss_percentage": "15"}
}
| Tipo | Parâmetros | Efeito |
|---|---|---|
rtp_packet_loss | loss_percentage (0-100) | Descarta pacotes na camada RTP |
network_jitter | base_latency_ms, jitter_std_dev_ms | Adiciona variação de atraso |
bitrate_reduce | new_bitrate_kbps | Limita a codificação de vídeo |
frame_drop | drop_percentage (0-100) | Pula quadros de vídeo |
bandwidth_limit | bandwidth_kbps | Limita a taxa total de transferência |
{
"spike_distribution": {
"strategy": "even",
"min_spacing_seconds": 5,
"jitter_percent": 15,
"respect_min_offset": true
}
}
# Receptor fornecido com análise RTP
go run examples/go/udp_receiver.go 5002
Saída:
Ouvindo pacotes RTP na porta UDP 0.0.0.0:5002
Pacote #100 de 127.0.0.1:xxxxx:
ID do Participante: 1001
Tipo de Payload: 96 (vídeo H.264)
Sequência: 1234
Timestamp: 90000
SSRC: 1001000
Tamanho do Payload: 1200 bytes
═══════════════════════════════════════════════════════════
ESTATÍSTICAS DE PACOTES
═══════════════════════════════════════════════════════════
Duração: 60s
Total de Pacotes: 180000 (3000 pkt/s)
Total de Bytes: 450 MB (60 Mbps)
Detalhamento por Tipo de Mídia:
Vídeo (H.264): 120000 pacotes (66.7%)
Áudio (Opus): 60000 pacotes (33.3%)
Streams Únicos (SSRCs): 1500
Participantes Únicos: 1500
# Participante único
go run ./examples/go/webrtc_receiver.go http://localhost:8080 <test_id>
# Vários participantes (até 150)
go run ./examples/go/webrtc_receiver.go http://localhost:8080 <test_id> 150
# Exemplo com ID de teste real
go run ./examples/go/webrtc_receiver.go http://localhost:8080 chaos_test_1770831684 150
Nota: WebRTC requer conexões 1:1. Para Kubernetes, use o receptor UDP que agrega todos os participantes automaticamente.
Formato do Pacote RTP:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | número de sequência |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| identificador de sincronização (SSRC) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ID de Extensão=1 | Comprimento=4 | ID do Participante (uint32) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload H.264/Opus |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Tipos de Payload:
96: Vídeo H.264 (RFC 6184)111: Áudio Opus (RFC 7587)Extração do ID do Participante:
// Bit de extensão definido?
if (packet[0] & 0x10) != 0 {
offset := 12 + int(packet[0]&0x0F)*4 // Pular CSRC
extID := binary.BigEndian.Uint16(packet[offset:])
if extID == 1 {
participantID := binary.LittleEndian.Uint32(packet[offset+4:])
}
}
| Participantes | Memória | CPU | Largura de Banda |
|---|---|---|---|
| 100 | 2GB | 2 núcleos | 250 Mbps |
| 500 | 6GB | 8 núcleos | 1.2 Gbps |
| 1000 | 12GB | 16 núcleos | 2.5 Gbps |
| 1500 | 18GB | 24 núcleos | 3.7 Gbps |
Por participante (1280x720@30fps + Opus):
# Exposto no endpoint /metrics
av_chaos_monkey_participants_total
av_chaos_monkey_packets_sent_total
av_chaos_monkey_bytes_sent_total
av_chaos_monkey_spikes_active
av_chaos_monkey_packet_loss_percent
av_chaos_monkey_jitter_ms
# Modo Docker: Iniciar pilha de monitoramento
docker-compose --profile monitoring up
# Modo Kubernetes: Implantar monitoramento
kubectl apply -f k8s/monitoring/prometheus-rbac.yaml
kubectl apply -f k8s/monitoring/prometheus.yaml
kubectl apply -f k8s/monitoring/grafana.yaml
# Acessar Grafana
# Docker: http://localhost:3000
# Kubernetes: http://localhost:30030 (NodePort)
# Credenciais padrão: admin/admin
# Acessar Prometheus
# Docker: http://localhost:9091
# Kubernetes: http://localhost:30090 (NodePort)
Descoberta Automática Kubernetes:
prometheus.io/scrape: "true"/metrics de todos os pods a cada 5s# Obter métricas do teste
curl http://localhost:8080/api/v1/test/{test_id}/metrics | jq
# Saída
{
"aggregate": {
"total_frames_sent": 45000,
"total_packets_sent": 180000,
"total_bitrate_kbps": 250000,
"avg_jitter_ms": 12.5,
"avg_packet_loss": 2.3,
"avg_mos_score": 4.1
}
}
# Verificar configuração do destino UDP
kubectl logs orchestrator-0 | grep "UDP transmission enabled"
# Verificar se o relé UDP está em execução
kubectl get pod udp-relay
# Verificar port-forward
ps aux | grep "kubectl port-forward"
# Testar conectividade UDP
nc -u -z localhost 5002
# Verificar servidor TURN
kubectl get svc coturn-lb
# Verificar candidatos ICE
kubectl logs orchestrator-0 | grep "ICE"
# Testar conectividade TURN
turnutils_uclient -v -u webrtc -w webrtc123 <turn-server>:3478
# Verificar contagem de participantes por pod
kubectl exec orchestrator-0 -- curl -s http://localhost:8080/api/v1/test/{test_id}/metrics | jq '.participants | length'
# Reduzir participantes ou aumentar número de pods
go run tools/k8s-start/main.go -replicas 10 -participants 1000
# Aumentar memória Docker (Docker Desktop)
# Configurações → Recursos → Memória → 16GB
Um único socket UDP não consegue lidar com mais de 3000 streams concorrentes sem estouro do buffer do kernel. Soluções:
setsockopt(SO_RCVBUF, 8MB)Licença BSD 3-Clause
Contribuições são bem-vindas! Áreas principais: