Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ wasm = [
serde = [
"dep:serde",
"ipnet/serde",
"serde/rc",
]
native-tls = [
"oneio/native-tls",
Expand Down
26 changes: 26 additions & 0 deletions benches/internals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ pub fn criterion_benchmark(c: &mut Criterion) {
})
});

c.bench_function("updates into_route_iter", |b| {
b.iter(|| {
let mut reader = black_box(&updates[..]);

BgpkitParser::from_reader(&mut reader)
.into_route_iter()
.take(RECORD_LIMIT)
.for_each(|x| {
black_box(x);
});
})
});

c.bench_function("updates into_raw_record_iter", |b| {
b.iter(|| {
let mut reader = black_box(&updates[..]);
Expand Down Expand Up @@ -143,6 +156,19 @@ pub fn criterion_benchmark(c: &mut Criterion) {
})
});

c.bench_function("rib into_route_iter", |b| {
b.iter(|| {
let mut reader = black_box(&rib_dump[..]);

BgpkitParser::from_reader(&mut reader)
.into_route_iter()
.take(RECORD_LIMIT)
.for_each(|x| {
black_box(x);
});
})
});

c.bench_function("rib into_raw_record_iter", |b| {
b.iter(|| {
let mut reader = black_box(&rib_dump[..]);
Expand Down
144 changes: 144 additions & 0 deletions examples/route_level_parsing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use bgpkit_parser::{BgpkitParser, Filterable};
use std::time::Instant;

/// This example demonstrates the lightweight route-level parser (`into_route_iter()`)
/// which provides significantly faster processing when you only need basic route
/// information (prefix, AS path, peer metadata) without full BGP attributes.
///
/// Performance characteristics:
/// - Updates files: ~10-15% faster (fewer attributes to skip)
/// - RIB dump files: ~50-70% faster (many attributes per route)
///
/// Use `into_route_iter()` when you need:
/// - prefix, AS path, peer IP/AS, timestamp
/// - Fast scanning/filtering of large datasets
/// - No need for communities, MED, next-hop, local-pref, etc.
///
/// Use `into_elem_iter()` when you need:
/// - Full BGP attributes (communities, MED, next-hop, local-pref, etc.)
/// - Community-based filtering
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();

// Example 1: Download and parse an updates file using route iterator
log::info!("=== Example 1: Route-level parsing ===");

let url = "http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2";

let start = Instant::now();
let parser = BgpkitParser::new(url).unwrap();

let mut route_count = 0;
for route in parser.into_route_iter().take(1000) {
if route_count < 3 {
// Show first few routes
log::info!(
"Route {}: {} via AS{} (peer: {})",
route_count + 1,
route.prefix,
route.peer_asn,
route.peer_ip
);
if let Some(ref path) = route.as_path {
log::info!(" AS Path: {}", path);
}
}
route_count += 1;
}
let route_time = start.elapsed();
log::info!(
"Route-level parsing: {} routes in {:.3}s",
route_count,
route_time.as_secs_f64()
);

// Example 2: Compare with element-level parsing
log::info!("\n=== Example 2: Element-level parsing (full attributes) ===");

let start = Instant::now();
let parser = BgpkitParser::new(url).unwrap();

let mut elem_count = 0;
for elem in parser.into_elem_iter().take(1000) {
if elem_count < 3 {
log::info!(
"Element {}: {} via AS{} (next-hop: {:?})",
elem_count + 1,
elem.prefix,
elem.peer_asn,
elem.next_hop
);
if let Some(ref communities) = elem.communities {
log::info!(" Communities: {:?}", communities);
}
}
elem_count += 1;
}
let elem_time = start.elapsed();
log::info!(
"Element-level parsing: {} elements in {:.3}s",
elem_count,
elem_time.as_secs_f64()
);

// Example 3: Filtering with route elements
log::info!("\n=== Example 3: Filtering route elements ===");

let parser = BgpkitParser::new(url).unwrap();
// Filter for routes from peer AS49788 (seen in the output above)
let filter = bgpkit_parser::Filter::new("peer_asn", "49788").unwrap();

let mut filtered_count = 0;
for route in parser.into_route_iter().take(1000) {
if route.match_filter(&filter) {
filtered_count += 1;
if filtered_count <= 3 {
log::info!(
"Matched filter (peer_asn=49788): {} from AS{}",
route.prefix,
route.peer_asn
);
}
}
}
log::info!(
"Total routes matching filter (first 1000): {}",
filtered_count
);

// Example 4: Demonstrate AS path filtering
log::info!("\n=== Example 4: AS path filtering ===");

let parser = BgpkitParser::new(url).unwrap();
// Filter for routes with AS1299 somewhere in the path
let as_path_filter = bgpkit_parser::Filter::new("as_path", "1299").unwrap();

let mut as_path_matches = 0;
for route in parser.into_route_iter().take(1000) {
if route.match_filter(&as_path_filter) {
as_path_matches += 1;
if as_path_matches <= 3 {
log::info!(
"AS Path contains 1299: {} - path: {:?}",
route.prefix,
route.as_path.as_ref().map(|p| p.to_string())
);
}
}
}
log::info!(
"Total routes with AS1299 in path (first 1000): {}",
as_path_matches
);

log::info!("\n=== Summary ===");
log::info!(
"Route-level: {:.3}s | Element-level: {:.3}s",
route_time.as_secs_f64(),
elem_time.as_secs_f64()
);
log::info!("");
log::info!("Performance gain is most significant for RIB dumps with many attributes.");
log::info!("For update files, the difference is smaller since there are fewer attributes.");
log::info!("Note: Community filters are NOT supported with route elements.");
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,7 @@ pub mod parser;
pub mod wasm;

pub use models::BgpElem;
pub use models::BgpRouteElem;
pub use models::MrtRecord;
#[cfg(feature = "parser")]
pub use parser::*;
27 changes: 27 additions & 0 deletions src/models/bgp/elem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::Arc;

// TODO(jmeggitt): BgpElem can be converted to an enum. Apply this change during performance PR.

Expand Down Expand Up @@ -156,6 +157,32 @@ pub struct BgpElem {
pub deprecated: Option<Vec<AttrRaw>>,
}

/// Lightweight per-prefix route element.
///
/// This struct is intended for fast scans that only need route identity,
/// peer metadata, timestamp, and AS path. Use [`BgpElem`] when you need the
/// full set of BGP attributes.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BgpRouteElem {
/// The timestamp of the item in floating-point format.
pub timestamp: f64,
/// The element type of an item.
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub elem_type: ElemType,
/// The IP address of the peer associated with the item.
pub peer_ip: IpAddr,
/// The peer ASN of the item.
pub peer_asn: Asn,
/// The network prefix of the item.
pub prefix: NetworkPrefix,
/// The optional path representation of the item.
///
/// Route-level parsing shares the same AS path across all announced
/// prefixes from a single message.
pub as_path: Option<Arc<AsPath>>,
}

impl Eq for BgpElem {}

impl PartialOrd<Self> for BgpElem {
Expand Down
Loading
Loading