camel-processor 0.62.0

Message processors for rust-camel
Documentation
use std::hint::black_box;

use camel_api::{
    Exchange, FilterPredicate, Message, OutcomePipeline, OutcomeSegment, PipelineOutcome,
    PredicateSource, Value,
};
use camel_processor::FilterSegment;
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};

#[derive(Clone)]
struct Passthrough;

impl OutcomePipeline for Passthrough {
    fn clone_box(&self) -> Box<dyn OutcomePipeline> {
        Box::new(self.clone())
    }
    fn run<'a>(
        &'a mut self,
        exchange: Exchange,
    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = PipelineOutcome> + Send + 'a>> {
        Box::pin(async move { PipelineOutcome::Completed(exchange) })
    }
}

fn filter_segment() -> FilterSegment {
    FilterSegment {
        predicate: PredicateSource::Sync(FilterPredicate::new(|ex: &Exchange| {
            ex.input
                .body
                .as_text()
                .map(|text| text.contains("target"))
                .unwrap_or(false)
        })),
        body: OutcomeSegment::new(Box::new(Passthrough)),
    }
}

fn filter_segment_header() -> FilterSegment {
    FilterSegment {
        predicate: PredicateSource::Sync(FilterPredicate::new(|ex: &Exchange| {
            ex.input.header("pass").is_some()
        })),
        body: OutcomeSegment::new(Box::new(Passthrough)),
    }
}

fn bench_filter_hit_rates(c: &mut Criterion) {
    let mut group = c.benchmark_group("filter/hit_rate");
    let rt = tokio::runtime::Runtime::new().unwrap();

    for hit_rate in [0, 50, 100] {
        let segment = filter_segment();
        let mut idx = 0usize;

        group.bench_with_input(BenchmarkId::new("hit_rate", hit_rate), &hit_rate, |b, _| {
            b.to_async(&rt).iter(|| {
                let mut segment = segment.clone();
                let current = idx;
                idx = idx.wrapping_add(1);
                let body = match hit_rate {
                    0 => format!("miss-{current}"),
                    50 => {
                        if black_box(current.is_multiple_of(2)) {
                            format!("target-match-{current}")
                        } else {
                            format!("miss-{current}")
                        }
                    }
                    100 => format!("target-match-{current}"),
                    _ => unreachable!(),
                };
                let ex = Exchange::new(Message::new(black_box(body)));
                async move { black_box(segment.run(ex).await) }
            })
        });
    }
    group.finish();
}

fn bench_filter_throughput(c: &mut Criterion) {
    let mut group = c.benchmark_group("filter/throughput");
    group.throughput(Throughput::Elements(100));
    let rt = tokio::runtime::Runtime::new().unwrap();

    let segment = filter_segment_header();

    group.bench_function("100_messages", |b| {
        let payloads: Vec<String> = (0..100).map(|i| format!("msg-{i}")).collect();
        b.to_async(&rt).iter(|| {
            let segment = segment.clone();
            let payloads = payloads.clone();
            async move {
                for payload in payloads {
                    let mut segment = segment.clone();
                    let mut ex = Exchange::new(Message::new(payload));
                    ex.input.set_header("pass", Value::String("yes".into()));
                    black_box(segment.run(ex).await);
                }
            }
        })
    });
    group.finish();
}

criterion_group!(benches, bench_filter_hit_rates, bench_filter_throughput);
criterion_main!(benches);