loadpace 0.1.0-rc.4

Adaptive client-side load balancing with bounded backpressure
Documentation
use loadpace::{
    EndpointConfig, Gradient2Config, LatencyEstimatorConfig, ProbeSchedule, SimulatedEndpoint,
    SimulationConfig, simulate,
};
use std::time::Duration;

fn endpoint(queue_capacity: usize, service_time: Duration) -> SimulatedEndpoint {
    SimulatedEndpoint {
        config: EndpointConfig {
            queue_capacity,
            max_inflight: 16,
            latency: LatencyEstimatorConfig {
                initial_rtt: service_time,
                short_alpha: 1.0,
                long_alpha: 1.0,
                min_rtt: service_time,
                baseline_window: Duration::from_secs(60),
            },
            gradient: Gradient2Config {
                initial_concurrency: 1.0,
                max_concurrency: 100.0,
                ..Gradient2Config::default()
            },
            probe_schedule: ProbeSchedule::default(),
        },
        workers: 1,
        service_time,
    }
}

#[test]
fn simulator_keeps_offered_load_bounded_by_the_endpoint_horizon() {
    let report = simulate(SimulationConfig {
        duration: Duration::from_millis(300),
        offered_rate: 100.0,
        endpoints: vec![endpoint(2, Duration::from_millis(100))],
        seed: 7,
    });

    assert!(report.offered > report.accepted);
    assert!(report.backpressured > 0);
    assert!(report.max_queued <= 2);
    assert!(report.accepted - report.dispatched <= 2);
}

#[test]
fn simulator_uses_predicted_cost_to_shift_work_to_a_faster_endpoint() {
    let report = simulate(SimulationConfig {
        duration: Duration::from_secs(2),
        offered_rate: 20.0,
        endpoints: vec![
            endpoint(4, Duration::from_millis(10)),
            endpoint(4, Duration::from_millis(100)),
        ],
        seed: 42,
    });

    assert!(report.endpoints[0].dispatched > report.endpoints[1].dispatched);
    assert!(report.endpoints[0].snapshot.expected_rtt < report.endpoints[1].snapshot.expected_rtt);
}

#[test]
fn simulator_models_worker_capacity_and_queueing_latency() {
    let report = simulate(SimulationConfig {
        duration: Duration::from_secs(2),
        offered_rate: 500.0,
        endpoints: vec![SimulatedEndpoint {
            config: EndpointConfig::default().queue_capacity(4),
            workers: 2,
            service_time: Duration::from_millis(10),
        }],
        seed: 11,
    });

    assert!(report.completed > 100);
    assert!(report.endpoints[0].snapshot.expected_rtt > Duration::from_millis(10));
    assert!(report.max_queued <= 4);
}

#[test]
fn simulator_makes_progress_above_timer_precision() {
    let report = simulate(SimulationConfig {
        duration: Duration::ZERO,
        offered_rate: 1e20,
        endpoints: vec![endpoint(1, Duration::from_millis(1))],
        seed: 13,
    });

    assert_eq!(report.offered, 1);
    assert_eq!(report.accepted, 1);
}

#[test]
fn simulation_defaults_are_a_runnable_baseline() {
    let config = SimulationConfig::default();

    assert_eq!(config.duration, Duration::from_secs(10));
    assert_eq!(config.offered_rate, 1.0);
    assert_eq!(config.endpoints.len(), 1);
    assert_eq!(config.endpoints[0].workers, 1);
    assert_eq!(config.endpoints[0].service_time, Duration::from_millis(50));
}

#[test]
#[should_panic(expected = "offered rate is too low")]
fn simulator_rejects_an_unrepresentably_low_offered_rate() {
    simulate(SimulationConfig {
        offered_rate: 1e-30,
        ..SimulationConfig::default()
    });
}