use crate::error::{BenchError, Result};
use crate::scenarios::BenchmarkScenario;
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CloudProvider {
S3,
Gcs,
Azure,
}
pub struct S3ReadScenario {
bucket: String,
key: String,
#[allow(dead_code)]
region: String,
range_requests: bool,
chunk_size: Option<usize>,
}
impl S3ReadScenario {
pub fn new<S1, S2, S3>(bucket: S1, key: S2, region: S3) -> Self
where
S1: Into<String>,
S2: Into<String>,
S3: Into<String>,
{
Self {
bucket: bucket.into(),
key: key.into(),
region: region.into(),
range_requests: false,
chunk_size: None,
}
}
pub fn with_range_requests(mut self, chunk_size: usize) -> Self {
self.range_requests = true;
self.chunk_size = Some(chunk_size);
self
}
}
impl BenchmarkScenario for S3ReadScenario {
fn name(&self) -> &str {
"s3_read"
}
fn description(&self) -> &str {
"Benchmark S3 object read performance"
}
fn setup(&mut self) -> Result<()> {
if self.bucket.is_empty() {
return Err(BenchError::InvalidConfiguration(
"Bucket name cannot be empty".to_string(),
));
}
if self.key.is_empty() {
return Err(BenchError::InvalidConfiguration(
"Object key cannot be empty".to_string(),
));
}
Ok(())
}
fn execute(&mut self) -> Result<()> {
#[cfg(feature = "cloud")]
{
}
#[cfg(not(feature = "cloud"))]
{
return Err(BenchError::missing_dependency("oxigdal-cloud", "cloud"));
}
Ok(())
}
fn teardown(&mut self) -> Result<()> {
Ok(())
}
}
pub struct S3WriteScenario {
bucket: String,
key: String,
#[allow(dead_code)]
region: String,
#[allow(dead_code)]
data_size: usize,
multipart: bool,
part_size: Option<usize>,
cleanup: bool,
}
impl S3WriteScenario {
pub fn new<S1, S2, S3>(bucket: S1, key: S2, region: S3, data_size: usize) -> Self
where
S1: Into<String>,
S2: Into<String>,
S3: Into<String>,
{
Self {
bucket: bucket.into(),
key: key.into(),
region: region.into(),
data_size,
multipart: false,
part_size: None,
cleanup: true,
}
}
pub fn with_multipart(mut self, part_size: usize) -> Self {
self.multipart = true;
self.part_size = Some(part_size);
self
}
pub fn with_cleanup(mut self, cleanup: bool) -> Self {
self.cleanup = cleanup;
self
}
}
impl BenchmarkScenario for S3WriteScenario {
fn name(&self) -> &str {
"s3_write"
}
fn description(&self) -> &str {
"Benchmark S3 object write performance"
}
fn setup(&mut self) -> Result<()> {
if self.bucket.is_empty() {
return Err(BenchError::InvalidConfiguration(
"Bucket name cannot be empty".to_string(),
));
}
if self.key.is_empty() {
return Err(BenchError::InvalidConfiguration(
"Object key cannot be empty".to_string(),
));
}
Ok(())
}
fn execute(&mut self) -> Result<()> {
#[cfg(feature = "cloud")]
{
}
#[cfg(not(feature = "cloud"))]
{
return Err(BenchError::missing_dependency("oxigdal-cloud", "cloud"));
}
Ok(())
}
fn teardown(&mut self) -> Result<()> {
#[cfg(feature = "cloud")]
{
if self.cleanup {
}
}
Ok(())
}
}
pub struct CachingScenario {
#[allow(dead_code)]
provider: CloudProvider,
#[allow(dead_code)]
bucket: String,
#[allow(dead_code)]
key: String,
cache_dir: PathBuf,
cache_size_mb: usize,
access_pattern: CacheAccessPattern,
}
#[derive(Debug, Clone, Copy)]
pub enum CacheAccessPattern {
Sequential,
Random,
Repeated,
}
impl CachingScenario {
pub fn new<S1, S2, P>(provider: CloudProvider, bucket: S1, key: S2, cache_dir: P) -> Self
where
S1: Into<String>,
S2: Into<String>,
P: Into<PathBuf>,
{
Self {
provider,
bucket: bucket.into(),
key: key.into(),
cache_dir: cache_dir.into(),
cache_size_mb: 100,
access_pattern: CacheAccessPattern::Sequential,
}
}
pub fn with_cache_size(mut self, size_mb: usize) -> Self {
self.cache_size_mb = size_mb;
self
}
pub fn with_access_pattern(mut self, pattern: CacheAccessPattern) -> Self {
self.access_pattern = pattern;
self
}
}
impl BenchmarkScenario for CachingScenario {
fn name(&self) -> &str {
"cloud_caching"
}
fn description(&self) -> &str {
"Benchmark cloud storage caching performance"
}
fn setup(&mut self) -> Result<()> {
std::fs::create_dir_all(&self.cache_dir)?;
Ok(())
}
fn execute(&mut self) -> Result<()> {
#[cfg(feature = "cloud")]
{
}
#[cfg(not(feature = "cloud"))]
{
return Err(BenchError::missing_dependency("oxigdal-cloud", "cloud"));
}
Ok(())
}
fn teardown(&mut self) -> Result<()> {
if self.cache_dir.exists() {
let _ = std::fs::remove_dir_all(&self.cache_dir);
}
Ok(())
}
}
pub struct PrefetchScenario {
#[allow(dead_code)]
provider: CloudProvider,
#[allow(dead_code)]
bucket: String,
keys: Vec<String>,
prefetch_count: usize,
parallel_requests: usize,
}
impl PrefetchScenario {
pub fn new<S>(provider: CloudProvider, bucket: S, keys: Vec<String>) -> Self
where
S: Into<String>,
{
Self {
provider,
bucket: bucket.into(),
keys,
prefetch_count: 5,
parallel_requests: 4,
}
}
pub fn with_prefetch_count(mut self, count: usize) -> Self {
self.prefetch_count = count;
self
}
pub fn with_parallel_requests(mut self, count: usize) -> Self {
self.parallel_requests = count;
self
}
}
impl BenchmarkScenario for PrefetchScenario {
fn name(&self) -> &str {
"cloud_prefetch"
}
fn description(&self) -> &str {
"Benchmark cloud storage prefetch performance"
}
fn setup(&mut self) -> Result<()> {
if self.keys.is_empty() {
return Err(BenchError::InvalidConfiguration(
"No keys provided for prefetch benchmark".to_string(),
));
}
Ok(())
}
fn execute(&mut self) -> Result<()> {
#[cfg(feature = "cloud")]
{
}
#[cfg(not(feature = "cloud"))]
{
return Err(BenchError::missing_dependency("oxigdal-cloud", "cloud"));
}
Ok(())
}
fn teardown(&mut self) -> Result<()> {
Ok(())
}
}
pub struct RangeRequestScenario {
#[allow(dead_code)]
provider: CloudProvider,
#[allow(dead_code)]
bucket: String,
#[allow(dead_code)]
key: String,
range_sizes: Vec<usize>,
}
impl RangeRequestScenario {
pub fn new<S1, S2>(provider: CloudProvider, bucket: S1, key: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
Self {
provider,
bucket: bucket.into(),
key: key.into(),
range_sizes: vec![
64 * 1024, 256 * 1024, 1024 * 1024, 4 * 1024 * 1024, ],
}
}
pub fn with_range_sizes(mut self, sizes: Vec<usize>) -> Self {
self.range_sizes = sizes;
self
}
}
impl BenchmarkScenario for RangeRequestScenario {
fn name(&self) -> &str {
"range_requests"
}
fn description(&self) -> &str {
"Benchmark different range request sizes"
}
fn setup(&mut self) -> Result<()> {
Ok(())
}
fn execute(&mut self) -> Result<()> {
#[cfg(feature = "cloud")]
{
}
#[cfg(not(feature = "cloud"))]
{
return Err(BenchError::missing_dependency("oxigdal-cloud", "cloud"));
}
Ok(())
}
fn teardown(&mut self) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_s3_read_scenario_creation() {
let scenario = S3ReadScenario::new("my-bucket", "test.tif", "us-east-1")
.with_range_requests(1024 * 1024);
assert_eq!(scenario.name(), "s3_read");
assert!(scenario.range_requests);
assert_eq!(scenario.chunk_size, Some(1024 * 1024));
}
#[test]
fn test_s3_write_scenario_creation() {
let scenario =
S3WriteScenario::new("my-bucket", "output.tif", "us-east-1", 10 * 1024 * 1024)
.with_multipart(5 * 1024 * 1024)
.with_cleanup(false);
assert_eq!(scenario.name(), "s3_write");
assert!(scenario.multipart);
assert!(!scenario.cleanup);
}
#[test]
fn test_caching_scenario_creation() {
let scenario =
CachingScenario::new(CloudProvider::S3, "my-bucket", "test.tif", "/tmp/cache")
.with_cache_size(200)
.with_access_pattern(CacheAccessPattern::Random);
assert_eq!(scenario.name(), "cloud_caching");
assert_eq!(scenario.cache_size_mb, 200);
}
#[test]
fn test_prefetch_scenario_creation() {
let keys = vec!["file1.tif".to_string(), "file2.tif".to_string()];
let scenario = PrefetchScenario::new(CloudProvider::S3, "my-bucket", keys)
.with_prefetch_count(10)
.with_parallel_requests(8);
assert_eq!(scenario.name(), "cloud_prefetch");
assert_eq!(scenario.prefetch_count, 10);
assert_eq!(scenario.parallel_requests, 8);
}
#[test]
fn test_range_request_scenario_creation() {
let scenario = RangeRequestScenario::new(CloudProvider::S3, "my-bucket", "test.tif")
.with_range_sizes(vec![128 * 1024, 512 * 1024]);
assert_eq!(scenario.name(), "range_requests");
assert_eq!(scenario.range_sizes.len(), 2);
}
}