| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! High-level benchmarks. |
| //! |
| //! This module contains microbenchmarks for the Netstack3 Core, built on top |
| //! of Criterion. |
| |
| // Enable dead code warnings for benchmarks (disabled in `lib.rs`). |
| #![warn(dead_code, unused_imports, unused_macros)] |
| |
| use alloc::vec; |
| #[cfg(debug_assertions)] |
| use assert_matches::assert_matches; |
| |
| use net_types::{ip::Ipv4, Witness as _}; |
| use packet::{Buf, InnerPacketBuilder, Serializer}; |
| use packet_formats::{ |
| ethernet::{ |
| testutil::{ |
| ETHERNET_DST_MAC_BYTE_OFFSET, ETHERNET_HDR_LEN_NO_TAG, ETHERNET_MIN_BODY_LEN_NO_TAG, |
| ETHERNET_SRC_MAC_BYTE_OFFSET, |
| }, |
| EtherType, EthernetFrameBuilder, |
| }, |
| ip::IpProto, |
| ipv4::{ |
| testutil::{IPV4_CHECKSUM_OFFSET, IPV4_MIN_HDR_LEN, IPV4_TTL_OFFSET}, |
| Ipv4PacketBuilder, |
| }, |
| }; |
| |
| use crate::{ |
| device::{ |
| ethernet::{EthernetLinkDevice, RecvEthernetFrameMeta}, |
| DeviceId, |
| }, |
| state::StackStateBuilder, |
| testutil::{ |
| benchmarks::{black_box, Bencher}, |
| CtxPairExt as _, FakeEventDispatcherBuilder, FAKE_CONFIG_V4, |
| }, |
| }; |
| |
| // NOTE: Extra tests that are too expensive to run during benchmarks can be |
| // added by gating them on the `debug_assertions` configuration option. This |
| // option is disabled when running `cargo check`, but enabled when running |
| // `cargo test`. |
| |
| // Benchmark the minimum possible time to forward an IPv4 packet by stripping |
| // out all interesting computation. We have the simplest possible setup - a |
| // forwarding table with a single entry, and a single device - and we receive an |
| // IPv4 packet frame which we expect will be parsed and forwarded without |
| // requiring any new buffers to be allocated. |
| fn bench_forward_minimum<B: Bencher>(b: &mut B, frame_size: usize) { |
| let (mut ctx, idx_to_device_id) = FakeEventDispatcherBuilder::from_config(FAKE_CONFIG_V4) |
| .build_with(StackStateBuilder::default()); |
| |
| let eth_device = idx_to_device_id[0].clone(); |
| let device: DeviceId<_> = eth_device.clone().into(); |
| crate::device::testutil::set_forwarding_enabled::<_, Ipv4>(&mut ctx, &device, true); |
| |
| assert!( |
| frame_size |
| >= ETHERNET_HDR_LEN_NO_TAG |
| + core::cmp::max(ETHERNET_MIN_BODY_LEN_NO_TAG, IPV4_MIN_HDR_LEN) |
| ); |
| let body = vec![0; frame_size - (ETHERNET_HDR_LEN_NO_TAG + IPV4_MIN_HDR_LEN)]; |
| const TTL: u8 = 64; |
| let mut buf = body |
| .into_serializer() |
| .encapsulate(Ipv4PacketBuilder::new( |
| // Use the remote IP as the destination so that we decide to |
| // forward. |
| FAKE_CONFIG_V4.remote_ip, |
| FAKE_CONFIG_V4.remote_ip, |
| TTL, |
| IpProto::Udp.into(), |
| )) |
| .encapsulate(EthernetFrameBuilder::new( |
| FAKE_CONFIG_V4.remote_mac.get(), |
| FAKE_CONFIG_V4.local_mac.get(), |
| EtherType::Ipv4, |
| ETHERNET_HDR_LEN_NO_TAG, |
| )) |
| .serialize_vec_outer() |
| .unwrap(); |
| |
| let buf = buf.as_mut(); |
| let range = 0..buf.len(); |
| |
| // Store a copy of the checksum to re-write it later. |
| let ipv4_checksum = [ |
| buf[ETHERNET_HDR_LEN_NO_TAG + IPV4_CHECKSUM_OFFSET], |
| buf[ETHERNET_HDR_LEN_NO_TAG + IPV4_CHECKSUM_OFFSET + 1], |
| ]; |
| |
| b.iter(|| { |
| black_box(ctx.core_api().device::<EthernetLinkDevice>().receive_frame( |
| black_box(RecvEthernetFrameMeta { device_id: eth_device.clone() }), |
| black_box(Buf::new(&mut buf[..], range.clone())), |
| )); |
| |
| #[cfg(debug_assertions)] |
| { |
| assert_matches!(&ctx.bindings_ctx.take_ethernet_frames()[..], [_frame]); |
| } |
| |
| // Since we modified the buffer in-place, it now has the wrong source |
| // and destination MAC addresses and IP TTL/CHECKSUM. We reset them to |
| // their original values as efficiently as we can to avoid affecting the |
| // results of the benchmark. |
| (&mut buf[ETHERNET_SRC_MAC_BYTE_OFFSET..ETHERNET_SRC_MAC_BYTE_OFFSET + 6]) |
| .copy_from_slice(&FAKE_CONFIG_V4.remote_mac.bytes()[..]); |
| (&mut buf[ETHERNET_DST_MAC_BYTE_OFFSET..ETHERNET_DST_MAC_BYTE_OFFSET + 6]) |
| .copy_from_slice(&FAKE_CONFIG_V4.local_mac.bytes()[..]); |
| let ipv4_buf = &mut buf[ETHERNET_HDR_LEN_NO_TAG..]; |
| ipv4_buf[IPV4_TTL_OFFSET] = TTL; |
| ipv4_buf[IPV4_CHECKSUM_OFFSET..IPV4_CHECKSUM_OFFSET + 2] |
| .copy_from_slice(&ipv4_checksum[..]); |
| }); |
| } |
| |
| bench!(bench_forward_minimum_64, |b| bench_forward_minimum(b, 64)); |
| bench!(bench_forward_minimum_128, |b| bench_forward_minimum(b, 128)); |
| bench!(bench_forward_minimum_256, |b| bench_forward_minimum(b, 256)); |
| bench!(bench_forward_minimum_512, |b| bench_forward_minimum(b, 512)); |
| bench!(bench_forward_minimum_1024, |b| bench_forward_minimum(b, 1024)); |
| |
| #[cfg(benchmark)] |
| /// Returns a benchmark group for all Netstack3 Core microbenchmarks. |
| pub fn get_benchmark() -> criterion::Benchmark { |
| // TODO(https://fxbug.dev/42051624) Find an automatic way to add benchmark |
| // functions to the `Criterion::Benchmark`, ideally as part of `bench!`. |
| let mut b = criterion::Benchmark::new("ForwardIpv4/64", bench_forward_minimum_64); |
| b = b.with_function("ForwardIpv4/128", bench_forward_minimum_128); |
| b = b.with_function("ForwardIpv4/256", bench_forward_minimum_256); |
| b = b.with_function("ForwardIpv4/512", bench_forward_minimum_512); |
| b = b.with_function("ForwardIpv4/1024", bench_forward_minimum_1024); |
| |
| // Add additional microbenchmarks defined elsewhere. |
| crate::data_structures::token_bucket::tests::add_benches(b) |
| } |