use std::sync::Arc;
use std::thread;
use std::time::Duration;
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
use enum_map::Enum;
use maplit::hashmap;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use serde::{Deserialize, Serialize};
use unleash_api_client::api::{Feature, Features, Strategy};
use unleash_api_client::client;
use unleash_api_client::context::Context;
use unleash_api_client::http::HttpClient;
#[allow(non_camel_case_types)]
#[derive(Debug, Deserialize, Serialize, Enum, Clone)]
enum UserFeatures {
Flexible0,
Flexible1,
Flexible2,
Flexible3,
Flexible4,
Flexible5,
Flexible6,
Flexible7,
Flexible8,
Flexible9,
Flexible10,
Flexible11,
Flexible12,
Flexible13,
Flexible14,
Flexible15,
Flexible16,
Flexible17,
Flexible18,
Flexible19,
Flexible20,
Flexible21,
Flexible22,
Flexible23,
Flexible24,
Flexible25,
Flexible26,
Flexible27,
Flexible28,
Flexible29,
Flexible30,
Flexible31,
Flexible32,
Flexible33,
Flexible34,
Flexible35,
Flexible36,
Flexible37,
Flexible38,
Flexible39,
Flexible40,
Flexible41,
Flexible42,
Flexible43,
Flexible44,
Flexible45,
Flexible46,
Flexible47,
Flexible48,
Flexible49,
Flexible50,
Flexible51,
Flexible52,
Flexible53,
Flexible54,
Flexible55,
Flexible56,
Flexible57,
Flexible58,
Flexible59,
Flexible60,
Flexible61,
Flexible62,
Flexible63,
Unknown0,
Unknown1,
Unknown2,
Unknown3,
Unknown4,
Unknown5,
Unknown6,
Unknown7,
Unknown8,
Unknown9,
Unknown10,
Unknown11,
Unknown12,
Unknown13,
Unknown14,
Unknown15,
Unknown16,
Unknown17,
Unknown18,
Unknown19,
Unknown20,
Unknown21,
Unknown22,
Unknown23,
Unknown24,
Unknown25,
Unknown26,
Unknown27,
Unknown28,
Unknown29,
Unknown30,
Unknown31,
Unknown32,
Unknown33,
Unknown34,
Unknown35,
Unknown36,
Unknown37,
Unknown38,
Unknown39,
Unknown40,
Unknown41,
Unknown42,
Unknown43,
Unknown44,
Unknown45,
Unknown46,
Unknown47,
Unknown48,
Unknown49,
Unknown50,
Unknown51,
Unknown52,
Unknown53,
Unknown54,
Unknown55,
Unknown56,
Unknown57,
Unknown58,
Unknown59,
Unknown60,
Unknown61,
Unknown62,
Unknown63,
}
fn client<C>(count: usize) -> client::Client<UserFeatures, C>
where
C: HttpClient + Default,
{
let client = client::ClientBuilder::default()
.enable_string_features()
.into_client::<UserFeatures, C>("notused", "app", "test", None)
.unwrap();
let mut features = vec![];
for i in 0..count {
let name = format!("Flexible{}", i);
features.push(Feature {
description: Some(name.clone()),
enabled: true,
created_at: None,
variants: None,
name,
strategies: vec![Strategy {
name: "flexibleRollout".into(),
parameters: Some(hashmap!["stickiness".into()=>"default".into(),
"groupId".into()=>"flexible".into(), "rollout".into()=>"33".into()]),
..Default::default()
}],
});
let name = format!("flexible{}", i);
features.push(Feature {
description: Some(name.clone()),
enabled: true,
created_at: None,
variants: None,
name,
strategies: vec![Strategy {
name: "flexibleRollout".into(),
parameters: Some(hashmap!["stickiness".into()=>"default".into(),
"groupId".into()=>"flexible".into(), "rollout".into()=>"33".into()]),
..Default::default()
}],
});
}
let f = Features {
version: 1,
features,
};
client.memoize(f.features).unwrap();
client
}
#[inline]
fn random_str() -> String {
thread_rng()
.sample_iter(&Alphanumeric)
.take(30)
.map(char::from)
.collect()
}
fn batch(c: &mut Criterion) {
cfg_if::cfg_if! {
if #[cfg(feature = "surf")] {
use surf::Client as HttpClient;
} else if #[cfg(feature = "reqwest")] {
use reqwest::Client as HttpClient;
} else if #[cfg(feature = "reqwest-11")] {
use reqwest_11::Client as HttpClient;
} else {
compile_error!("Cannot run test suite without a client enabled");
}
}
let _ = simple_logger::SimpleLogger::new()
.with_utc_timestamps()
.with_module_level("isahc::agent", log::LevelFilter::Off)
.with_module_level("tracing::span", log::LevelFilter::Off)
.with_module_level("tracing::span::active", log::LevelFilter::Off)
.with_level(log::LevelFilter::Warn)
.init();
let cpus = num_cpus::get();
let client = Arc::new(client::<HttpClient>(cpus));
let iterations = 50_000;
println!(
"Benchmarking across {} threads with {} iterations per thread",
cpus, iterations
);
let mut group = c.benchmark_group("batch");
group
.throughput(Throughput::Elements(iterations))
.warm_up_time(Duration::from_secs(15))
.measurement_time(Duration::from_secs(30));
group.bench_function("single thread(enum)", |b| {
b.iter(|| {
let context = Context {
user_id: Some(random_str()),
..Default::default()
};
for _ in 0..iterations {
client.is_enabled(UserFeatures::Flexible0, Some(&context), false);
}
})
});
group.bench_function("single thread(str)", |b| {
b.iter(|| {
let context = Context {
user_id: Some(random_str()),
..Default::default()
};
for _ in 0..iterations {
client.is_enabled_str("flexible0", Some(&context), false);
}
})
});
group
.throughput(Throughput::Elements(iterations * cpus as u64))
.sample_size(10);
group.bench_function("parallel same-feature(enum)", |b| {
b.iter(|| {
let mut threads = vec![];
for _cpu in 0..cpus {
let thread_client = client.clone();
let feature = UserFeatures::Flexible0;
let handle = thread::spawn(move || {
let context = Context {
user_id: Some(random_str()),
..Default::default()
};
for _ in 0..iterations {
thread_client.is_enabled(feature.clone(), Some(&context), false);
}
});
threads.push(handle);
}
for thread in threads {
thread.join().unwrap();
}
})
});
group.bench_function("parallel same-feature(str)", |b| {
b.iter(|| {
let mut threads = vec![];
for _cpu in 0..cpus {
let thread_client = client.clone();
let feature = "flexible0";
let handle = thread::spawn(move || {
let context = Context {
user_id: Some(random_str()),
..Default::default()
};
for _ in 0..iterations {
thread_client.is_enabled_str(feature, Some(&context), false);
}
});
threads.push(handle);
}
for thread in threads {
thread.join().unwrap();
}
})
});
group.bench_function("parallel distinct-features(enum)", |b| {
b.iter(|| {
let mut threads = vec![];
for cpu in 0..cpus {
let thread_client = client.clone();
let feature_str = format!("Flexible{}", cpu);
let feature = serde_plain::from_str::<UserFeatures>(&feature_str).unwrap();
let handle = thread::spawn(move || {
let context = Context {
user_id: Some(random_str()),
..Default::default()
};
for _ in 0..iterations {
thread_client.is_enabled(feature.clone(), Some(&context), false);
}
});
threads.push(handle);
}
for thread in threads {
thread.join().unwrap();
}
})
});
group.bench_function("parallel distinct-features(str)", |b| {
b.iter(|| {
let mut threads = vec![];
for cpu in 0..cpus {
let thread_client = client.clone();
let feature_str = format!("flexible{}", cpu);
let handle = thread::spawn(move || {
let context = Context {
user_id: Some(random_str()),
..Default::default()
};
for _ in 0..iterations {
thread_client.is_enabled_str(&feature_str, Some(&context), false);
}
});
threads.push(handle);
}
for thread in threads {
thread.join().unwrap();
}
})
});
group.bench_function("parallel unknown-features(enum)", |b| {
b.iter(|| {
let mut threads = vec![];
for cpu in 0..cpus {
let thread_client = client.clone();
let feature_str = format!("Unknown{}", cpu);
let feature = serde_plain::from_str::<UserFeatures>(&feature_str).unwrap();
let handle = thread::spawn(move || {
let context = Context {
user_id: Some(random_str()),
..Default::default()
};
for _ in 0..iterations {
thread_client.is_enabled(feature.clone(), Some(&context), false);
}
});
threads.push(handle);
}
for thread in threads {
thread.join().unwrap();
}
})
});
group.bench_function("parallel unknown-features(str)", |b| {
b.iter(|| {
let mut threads = vec![];
for cpu in 0..cpus {
let thread_client = client.clone();
let feature_str = format!("unknown{}", cpu);
let handle = thread::spawn(move || {
let context = Context {
user_id: Some(random_str()),
..Default::default()
};
for _ in 0..iterations {
thread_client.is_enabled_str(&feature_str, Some(&context), false);
}
});
threads.push(handle);
}
for thread in threads {
thread.join().unwrap();
}
})
});
group.finish();
}
fn single_call(c: &mut Criterion) {
let _ = simple_logger::SimpleLogger::new()
.with_utc_timestamps()
.with_module_level("isahc::agent", log::LevelFilter::Off)
.with_module_level("tracing::span", log::LevelFilter::Off)
.with_module_level("tracing::span::active", log::LevelFilter::Off)
.with_level(log::LevelFilter::Warn)
.init();
cfg_if::cfg_if! {
if #[cfg(feature = "surf")] {
use surf::Client as HttpClient;
} else if #[cfg(feature = "reqwest")] {
use reqwest::Client as HttpClient;
} else if #[cfg(feature = "reqwest-11")] {
use reqwest_11::Client as HttpClient;
} else {
compile_error!("Cannot run test suite without a client enabled");
}
}
let client = client::<HttpClient>(1);
let context = Context {
user_id: Some(random_str()),
..Default::default()
};
let mut group = c.benchmark_group("single_call");
group.throughput(Throughput::Elements(1));
group.bench_function("single_call(enum)", |b| {
b.iter(|| {
client.is_enabled(UserFeatures::Flexible0, Some(&context), false);
})
});
group.bench_function("single_call(str)", |b| {
b.iter(|| {
client.is_enabled_str("flexible0", Some(&context), false);
})
});
group.finish();
}
criterion_group!(benches, single_call, batch);
criterion_main!(benches);