Skip to content
73 changes: 62 additions & 11 deletions bench/benches/userland.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

// Copyright 2024 Oxide Computer Company
// Copyright 2026 Oxide Computer Company

//! Userland packet parsing and processing microbenchmarks.

Expand All @@ -21,6 +21,7 @@ use opte_bench::packet::Dhcp6;
use opte_bench::packet::Icmp4;
use opte_bench::packet::Icmp6;
use opte_bench::packet::ParserKind;
use opte_bench::packet::SlowpathEvict;
use opte_bench::packet::TestCase;
use opte_bench::packet::ULP_FAST_PATH;
use opte_bench::packet::ULP_SLOW_PATH;
Expand All @@ -29,6 +30,9 @@ use oxide_vpc::api::IpAddr;
use oxide_vpc::api::Ipv4Addr;
use oxide_vpc::api::Ipv6Addr;
use oxide_vpc::api::SourceFilter;
use rand::SeedableRng;
use rand::distr::Bernoulli;
use rand::distr::Distribution;
use std::collections::BTreeSet;
use std::hint::black_box;

Expand All @@ -44,11 +48,12 @@ pub fn block<M: MeasurementInfo>(c: &mut Criterion<M>, do_parse: bool) {
Box::new(Icmp6),
Box::new(ULP_FAST_PATH),
Box::new(ULP_SLOW_PATH),
Box::new(SlowpathEvict),
];

for experiment in all_tests {
for case in experiment.test_cases() {
if do_parse {
if experiment.do_parse_benchmark() && do_parse {
test_parse(c, &**experiment, &*case);
}
test_handle(c, &**experiment, &*case);
Expand Down Expand Up @@ -151,6 +156,7 @@ pub fn test_handle<M: MeasurementInfo>(
));

let parser = case.parse_with();
let can_fail = experiment.allow_failure();
c.bench_with_input(
BenchmarkId::from_parameter(case.instance_name()),
&case,
Expand All @@ -174,19 +180,23 @@ pub fn test_handle<M: MeasurementInfo>(
GenericUlp {},
)
.unwrap();
port.port.process(dir, black_box(pkt)).unwrap()
port.port.process(dir, black_box(pkt))
}
Out => {
let pkt = Packet::parse_outbound(
pkt_m.iter_mut(),
GenericUlp {},
)
.unwrap();
port.port.process(dir, black_box(pkt)).unwrap()
port.port.process(dir, black_box(pkt))
}
};
assert!(!matches!(res, ProcessResult::Drop { .. }));
if let Modified(spec) = res {

if !can_fail {
assert!(res.is_ok());
}

if let Ok(Modified(spec)) = res {
black_box(spec.apply(pkt_m));
}
}
Expand All @@ -198,19 +208,23 @@ pub fn test_handle<M: MeasurementInfo>(
VpcParser {},
)
.unwrap();
port.port.process(dir, black_box(pkt)).unwrap()
port.port.process(dir, black_box(pkt))
}
Out => {
let pkt = Packet::parse_outbound(
pkt_m.iter_mut(),
VpcParser {},
)
.unwrap();
port.port.process(dir, black_box(pkt)).unwrap()
port.port.process(dir, black_box(pkt))
}
};
assert!(!matches!(res, ProcessResult::Drop { .. }));
if let Modified(spec) = res {

if !can_fail {
assert!(res.is_ok());
}

if let Ok(Modified(spec)) = res {
black_box(spec.apply(pkt_m));
}
}
Expand Down Expand Up @@ -325,7 +339,44 @@ fn source_filter_allows(c: &mut Criterion) {
group.finish();
}

criterion_group!(wall, parse_and_process, source_filter_allows);
fn periodic_cleanup<M: MeasurementInfo>(c: &mut Criterion<M>) {
let expt = SlowpathEvict;
for case in expt.test_cases() {
let port = case.create_port().unwrap();
for p_expire in [0.0, 0.1, 0.25, 0.5] {
let mut c = c.benchmark_group(format!(
"cleanup/{}/P{}",
M::label(),
p_expire
));
let mut rng =
rand::rngs::StdRng::seed_from_u64(0x01de_097e_7e57_0712);
let dist = Bernoulli::new(p_expire).unwrap();

c.bench_with_input(
BenchmarkId::from_parameter(case.instance_name()),
&case,
|b, _i| {
b.iter_batched(
|| {
case.pre_handle(&port);
port.port.inject_expiry(|| dist.sample(&mut rng));
},
|_| black_box(port.port.expire_flows()),
criterion::BatchSize::LargeInput,
)
},
);
}
}
}

criterion_group!(
wall,
parse_and_process,
source_filter_allows,
periodic_cleanup
);
criterion_group!(
name = alloc;
config = new_crit(Allocs);
Expand Down
18 changes: 4 additions & 14 deletions bench/src/kbench/workload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use super::*;
use measurement::Instrumentation;

#[allow(dead_code)]
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub enum IperfMode {
#[default]
ClientSend,
ServerSend,
// TODO: need an updated illumos package.
Expand All @@ -28,15 +29,10 @@ impl std::fmt::Display for IperfMode {
}
}

impl Default for IperfMode {
fn default() -> Self {
Self::ClientSend
}
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub enum IperfProto {
#[default]
Tcp,
Udp {
/// Target bandwidth in MiB/s.
Expand All @@ -59,12 +55,6 @@ impl std::fmt::Display for IperfProto {
}
}

impl Default for IperfProto {
fn default() -> Self {
Self::Tcp
}
}

#[derive(Debug, Clone)]
pub struct IperfConfig {
pub instrumentation: Instrumentation,
Expand Down
165 changes: 156 additions & 9 deletions bench/src/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ use opte_test_utils::icmp::gen_icmpv6_echo;
use opte_test_utils::icmp::generate_ndisc;
use opte_test_utils::*;
use std::collections::BTreeMap;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;

pub type TestCase = (MsgBlk, Direction);

Expand All @@ -42,6 +44,16 @@ pub trait BenchPacket {

/// Return a list of discrete scenarios
fn test_cases(&self) -> Vec<Box<dyn BenchPacketInstance>>;

/// Are `generate`d packets worth benchmarking for parser performance?
fn do_parse_benchmark(&self) -> bool {
true
}

/// Is packet processing allowed to fail due to table size constraints?
fn allow_failure(&self) -> bool {
false
}
}

/// An individual packet to time the parse/process timing of.
Expand Down Expand Up @@ -73,13 +85,9 @@ pub struct UlpProcess {
pub const ULP_FAST_PATH: UlpProcess = UlpProcess { fast_path: true };
pub const ULP_SLOW_PATH: UlpProcess = UlpProcess { fast_path: false };

impl BenchPacket for UlpProcess {
fn packet_label(&self) -> &'static str {
if self.fast_path { "ULP-FastPath" } else { "ULP-SlowPath" }
}

fn test_cases(&self) -> Vec<Box<dyn BenchPacketInstance>> {
let ip_cfg = IpCfg::DualStack {
impl UlpProcess {
fn cfg() -> IpCfg {
IpCfg::DualStack {
ipv4: Ipv4Cfg {
vpc_subnet: "172.30.0.0/22".parse().unwrap(),
private_ip: "172.30.0.5".parse().unwrap(),
Expand Down Expand Up @@ -110,9 +118,17 @@ impl BenchPacket for UlpProcess {
attached_subnets: BTreeMap::default(),
transit_ips: BTreeMap::default(),
},
};
}
}
}

impl BenchPacket for UlpProcess {
fn packet_label(&self) -> &'static str {
if self.fast_path { "ULP-FastPath" } else { "ULP-SlowPath" }
}

let cfg = g1_cfg2(ip_cfg);
fn test_cases(&self) -> Vec<Box<dyn BenchPacketInstance>> {
let cfg = g1_cfg2(UlpProcess::cfg());

itertools::iproduct!(
[IpVariant::V4, IpVariant::V6],
Expand Down Expand Up @@ -334,6 +350,137 @@ impl BenchPacketInstance for UlpProcessInstance {
}
}

pub struct SlowpathEvict;

impl BenchPacket for SlowpathEvict {
fn packet_label(&self) -> &'static str {
"Eviction"
}

fn test_cases(&self) -> Vec<Box<dyn BenchPacketInstance>> {
let cfg = g1_cfg2(UlpProcess::cfg());
[1 << 10, 1 << 15, 1 << 19, 1 << 20]
.into_iter()
.map(|n| {
Box::new(AllSynInstance {
index: 0.into(),
capacity: n.try_into().unwrap(),
cfg: cfg.clone(),
}) as Box<dyn BenchPacketInstance>
})
.collect()
}

fn do_parse_benchmark(&self) -> bool {
false
}

fn allow_failure(&self) -> bool {
true
}
}

#[derive(Debug)]
pub struct AllSynInstance {
index: AtomicU64,
capacity: NonZeroU32,

cfg: VpcCfg,
}

impl BenchPacketInstance for AllSynInstance {
fn create_port(&self) -> Option<PortAndVps> {
let mut g1 =
oxide_net_setup("g1_port", &self.cfg, None, Some(self.capacity));
g1.port.start();
set!(g1, "port_state=running");

Some(g1)
}

fn parse_with(&self) -> ParserKind {
ParserKind::OxideVpc
}

fn pre_handle(&self, port: &PortAndVps) {
while port.port.num_flows("firewall", Direction::In)
< self.capacity.get() - 1
{
let (mut pkt, dir) = self.generate();
let pkt = parse_inbound(&mut pkt, VpcParser {}).unwrap();
match port.port.process(dir, pkt) {
Ok(_) => {}
Err(opte::engine::port::ProcessError::Layer(
opte::engine::layer::LayerError::FlowTableFull { .. },
))
| Err(opte::engine::port::ProcessError::FlowTableFull {
..
}) => break,
e => panic!("unexpected err condition {e:?}"),
}
}
}

fn instance_name(&self) -> String {
format!("{}", self.capacity)
}

fn generate(&self) -> (MsgBlk, Direction) {
let my_index = self.index.fetch_add(1, Ordering::Relaxed);

// SYN packets (or small UDP) are the easiest way to prod at
// UFT expiry behaviour.
let src_port = (my_index / u64::from(u16::MAX)) as u16;
let dst_port = (my_index % u64::from(u16::MAX)) as u16;

let body = &[][..];

let eth = Ethernet {
destination: self.cfg.guest_mac,
source: BS_MAC_ADDR,
ethertype: Ethertype::IPV4,
};

let tcp = UlpRepr::Tcp(Tcp {
source: src_port,
destination: dst_port,
flags: TcpFlags::SYN,
sequence: 1234,
acknowledgement: 3456,
window_size: 1,
..Default::default()
});

let ip = L3Repr::Ipv4(Ipv4 {
source: Ipv4Addr::from_const([172, 30, 0, 6]),
destination: self.cfg.ipv4().private_ip,
protocol: IngotIpProto::TCP,
total_len: (Ipv4::MINIMUM_LENGTH + (&tcp, &body).packet_length())
as u16,
..Default::default()
});

let guest_phys = TestIpPhys {
ip: self.cfg.phys_ip,
mac: self.cfg.guest_mac,
vni: self.cfg.vni,
};

let partner_phys = TestIpPhys {
ip: Ipv6Addr::from([
0xFD00, 0x0000, 0x00F7, 0x0116, 0x0000, 0x0000, 0x0000, 0x0001,
]),
mac: ox_vpc_mac([0xF0, 0x00, 0x66]),
vni: self.cfg.vni,
};

(
encap(ulp_pkt(eth, ip, tcp, body), partner_phys, guest_phys),
Direction::In,
)
}
}

pub struct Dhcp4;

impl BenchPacket for Dhcp4 {
Expand Down
Loading