This project implements precise RTP Round-Trip Latency measurements in an ARI service according to a specific architecture. It provides high-performance ARI service for accurate RTP latency measurement with SLA monitoring capabilities.
- Architecture
- Components
- Environment Variables
- System Requirements
- Quick Start
- Running Tests
- Docker Usage
- Monitoring
- Latency Measurement
- Configuration
- Scripts
- Troubleshooting
┌─────────────┐ RTP ┌─────────────┐ RTP ┌─────────────┐
│ Asterisk │ ────────► │ ARI Service │ ────────► │ Echo Server │
│ │ │ │ │ │
│ - SIP calls │ │ - Parse RTP │ │ - Min delay │
│ - RTP gen │ │ - Track seq │ │ - Pacing │
└─────────────┘ │ - Measure │ └─────────────┘
▲ │ latency │ │
│ │ - SLA check │ │
│ └─────────────┘ │
│ ▲ │
│ │ │
└─────────────────────────┼───────────────────────────┘
Round-trip
Measurement
- ARI Service (
cmd/ari-service/main.go) - ARI client, StasisStart/End handling, externalMedia, bridge, channel manager - RTP Worker (
internal/rtp/worker.go) - Concurrent UDP worker with channel-based processing, RTP parsing, send/receive, sequence number correlation, packet pacing - Metrics (
internal/metrics/hist.go) - RTT histogram, p50/p95/p99, counters - Echo Server (
cmd/echo/main.go) - Separate external process for echo loopback with TS pacing - Load Test (
cmd/load_test_new/main.go) - Originate N calls, print results
The project uses environment variables for configuration. These can be set in a .env file or exported directly.
# Asterisk ARI Configuration (for REST API calls from your service TO Asterisk)
ASTERISK_HOST=localhost
ASTERISK_PORT=8088
ASTERISK_USERNAME=ari
ASTERISK_PASSWORD=ari
ASTERISK_APP_NAME=ari-service
# ARI Configuration (for WebSocket authentication - Asterisk connecting TO your service)
ARI_URL=localhost:8088
ARI_USER=asterisk
ARI_PASS=asterisk
APP_NAME=ari-app
# RTP Configuration
BIND_IP=0.0.0.0
PORT_RANGE=21000-31000
# Echo Server Configuration
ECHO_HOST=localhost
ECHO_PORT=4000
# Metrics
METRICS_INTERVAL_SEC=5
# Load Test Configuration
LOAD_TEST_CONCURRENT_CALLS=10
LOAD_TEST_DURATION_SECONDS=60
LOAD_TEST_CALL_DURATION_SECONDS=30
LOAD_TEST_ENDPOINT=Local/echo@ari-context
LOAD_TEST_REPORT=reports/load_test_report.json
# Service Configuration
SERVICE_BIND_IP=0.0.0.0
SERVICE_PORT=9090- Go 1.16+
- Docker and Docker Compose (for Docker setup)
- Asterisk with ARI enabled (for local setup)
- Proper UDP buffer tuning (sysctl settings recommended)
The easiest way to run this project is using Docker:
# Build and start the Docker container
docker-compose up -d
# Wait for services to initialize (about 10 seconds)
sleep 10
# Check if services are running
docker-compose exec asterisk curl -s http://localhost:9090/health
# Run the load test
docker-compose exec asterisk bash -c "cd /app && ./run.sh"
# View metrics
docker-compose exec asterisk curl -s http://localhost:9090/metrics | jq '.'
# Stop services
docker-compose downTo run locally without Docker:
# Export environment variables
export ASTERISK_HOST=localhost
export ASTERISK_PORT=8088
export ASTERISK_USERNAME=ari
export ASTERISK_PASSWORD=ari
export ARI_URL=localhost:8088
export ARI_USER=asterisk
export ARI_PASS=asterisk
export APP_NAME=ari-app
export BIND_IP=0.0.0.0
export PORT_RANGE=21000-31000
export ECHO_HOST=localhost
export ECHO_PORT=4000
export METRICS_INTERVAL_SEC=5
# Build all components
go build -o bin/ari-service ./cmd/ari-service
go build -o bin/echo-server ./cmd/echo
go build -o bin/load-test-new ./cmd/load_test_new
# Start ARI service
./bin/ari-service &
# Start echo server as separate process
./bin/echo-server &
# Run load test
./bin/load-test-new --count=10 --duration-ms=60000The run.sh script provides a complete workflow for running tests:
# Run regular load test with default configuration (10 calls, 60s duration, 30s call duration)
./run.sh
# Run production test (50 calls, 300s duration, 60s call duration)
./run.sh prod
# Run 100 calls test (100 calls, 600s duration, 1800s call duration)
./run.sh 100callThe run_simple.sh script allows for custom parameters:
# Run with custom parameters
./run_simple.sh --count=30 --duration-ms=30000 --delay-between-ms=100
# Run with different configuration
./run_simple.sh --count=100 --duration-ms=60000 --delay-between-ms=50The run_continuous.sh script maintains a constant number of concurrent calls:
# Run continuous load test with default configuration (10 concurrent calls, 30s call duration)
./run_continuous.sh
# Run with custom parameters
./run_continuous.sh --concurrent-calls=50 --call-duration-seconds=60 --delay-between-calls-ms=200
# Run with different configuration
./run_continuous.sh --concurrent-calls=100 --call-duration-seconds=120 --delay-between-calls-ms=100The continuous load test will:
- Maintain the specified number of concurrent calls
- Automatically start new calls when existing ones complete
- Run indefinitely until stopped with Ctrl+C
- Report metrics at regular intervals
You can also run components manually:
# Start services
./bin/ari-service &
./bin/echo-server &
# Run load test with specific parameters
./bin/load-test-new \
--count=10 \
--duration-ms=60000 \
--delay-between-ms=100# Build the Docker image
docker-compose build# Start services in background
docker-compose up -d
# View logs
docker-compose logs -f
# Execute commands in the container
docker-compose exec asterisk bash
# Stop services
docker-compose down# Run the default test configuration
docker-compose exec asterisk bash -c "cd /app && ./run.sh"
# Run with custom configuration by updating .env file first
docker-compose exec asterisk bash -c "cd /app && ./run_simple.sh --count=50 --duration-ms=120000"Environment variables can be set in multiple ways when using Docker:
- In docker-compose.yml: Define environment variables directly
- In .env file: Variables will be automatically loaded
- At runtime: Pass variables when running docker-compose
Example of setting variables at runtime:
LOAD_TEST_CONCURRENT_CALLS=50 LOAD_TEST_DURATION_SECONDS=300 docker-compose up -dThe ARI service exposes metrics via HTTP endpoints:
- Health check:
curl http://localhost:9090/health - Metrics:
curl http://localhost:9090/metrics
Example metrics output:
{
"total_channels": 12,
"active_channels": 12,
"total_latencies": 33682,
"p50_latency": 11,
"p95_latency": 17,
"p99_latency": 19,
"max_latency": 106,
"avg_latency": 11.414820972626329,
"late_ratio": 0.9991392359977442,
"packet_loss_ratio": 0,
"timestamp": "2025-09-01T09:30:15.979076055Z"
}The system measures RTP round-trip latency using the following approach:
-
StasisStart: Answer → externalMedia (both, ulaw, udp, rtp, external_host=BIND_IP:PORT) → bridge(mixing) + add(client, externalMedia)
-
One UDP worker per channel (concurrent model):
- From Asterisk: Parse RTP (12 bytes), paced send → echo, RecordSend(seq)
- From echo: Parse, GetLatency(seq), RTT calculation, send → Asterisk (from the same local port)
-
MVP Metrics: p50/p95/p99/max RTT, drops by Seq (echo→you)
-
Teardown on StasisEnd: Stop worker, close socket, return port to pool, clean state
| Variable | Description | Default Value |
|---|---|---|
ASTERISK_HOST |
Asterisk host for REST API calls | localhost |
ASTERISK_PORT |
Asterisk port for REST API calls | 8088 |
ASTERISK_USERNAME |
Username for REST API calls | ari |
ASTERISK_PASSWORD |
Password for REST API calls | ari |
ARI_URL |
ARI WebSocket URL | localhost:8088 |
ARI_USER |
Username for WebSocket auth | asterisk |
ARI_PASS |
Password for WebSocket auth | asterisk |
APP_NAME |
ARI application name | ari-app |
BIND_IP |
IP to bind RTP ports | 0.0.0.0 |
PORT_RANGE |
Range of ports for RTP | 21000-31000 |
ECHO_HOST |
Echo server host | localhost |
ECHO_PORT |
Echo server port | 4000 |
METRICS_INTERVAL_SEC |
Metrics reporting interval | 5 |
LOAD_TEST_CONCURRENT_CALLS |
Number of concurrent calls | 10 |
LOAD_TEST_DURATION_SECONDS |
Test duration in seconds | 60 |
LOAD_TEST_CALL_DURATION_SECONDS |
Call duration in seconds | 30 |
LOAD_TEST_ENDPOINT |
Endpoint to call | Local/echo@ari-context |
SERVICE_PORT |
Port for internal service | 9090 |
For production use, increase UDP buffer sizes:
# Add to /etc/sysctl.conf
net.core.rmem_max = 268435456 # 256MB
net.core.wmem_max = 268435456 # 256MB
net.core.rmem_default = 262144 # 256KB
net.core.wmem_default = 262144 # 256KB
net.ipv4.udp_rmem_min = 262144 # 256KB
net.ipv4.udp_wmem_min = 262144 # 256KB
# Apply settings
sudo sysctl -pFor high-performance operation, increase file descriptor limits:
# Add to /etc/security/limits.conf
* soft nofile 65536
* hard nofile 65536
# Or run with:
ulimit -n 65536The project includes several utility scripts:
run.sh- Complete workflow script with different test configurationsrun_simple.sh- Simple script with customizable parametersrun_continuous.sh- Continuous load test that maintains constant concurrent callstest.sh- Basic test scriptcleanup.sh- Cleanup script for logs and reports
run_comprehensive_test.sh- Comprehensive testing scriptrun_extended_test.sh- Extended testing with multiple scenariostest_env_configurations.sh- Test different environment configurationstest_sla.sh- SLA validation testing
demo_monitoring.sh- Demonstration of monitoring capabilitiesmonitor_resources.sh- Resource monitoring scriptanalyze_test_results.sh- Test result analysis
run_docker_env.sh- Run tests with Docker environment variablesrun_docker_test.sh- Docker testing scriptrun_docker_tests.sh- Multiple Docker tests
- Services not starting: Check Docker logs with
docker-compose logs - Connection refused: Ensure ports are not blocked by firewall
- High latency: Check network connectivity and system resources
- Packet loss: Increase UDP buffer sizes and check network infrastructure
# Check service health
curl http://localhost:9090/health
# View detailed logs
tail -f logs/ari-service.log
tail -f logs/echo-server.log
# Check system resources
docker-compose exec asterisk top- Increase UDP buffers: Apply sysctl optimizations
- Adjust file limits: Increase ulimit for file descriptors
- CPU affinity: Pin processes to specific CPU cores
- Network tuning: Optimize network interface settings
MIT License - see LICENSE file for details.# ari-echo