feat: dagrunner, initial version

This commit is contained in:
swaits committed 2025-10-07 16:31:03 -07:00
commit 7412287ee4
122 files changed
+23101

No files matched your search

+66
View File
@@ -0,0 +1,66 @@
//! Configuration broadcast pattern benchmark - one config shared across many workers
use criterion::{BenchmarkId, Criterion};
use dagx::{task_fn, DagRunner};
use std::collections::HashMap;
use std::hint::black_box;
pub fn bench_config_broadcast(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("config_broadcast");
for worker_count in [10, 50, 100, 200].iter() {
group.bench_with_input(
BenchmarkId::new("config", worker_count),
worker_count,
|b, &workers| {
b.iter(|| {
rt.block_on(async {
let dag = DagRunner::new();
// Configuration source - framework wraps HashMap in Arc automatically
let config = dag.add_task(task_fn(|_: ()| async {
HashMap::from([
("max_connections".to_string(), "100".to_string()),
("timeout_ms".to_string(), "5000".to_string()),
("batch_size".to_string(), "50".to_string()),
("retry_count".to_string(), "3".to_string()),
("worker_threads".to_string(), "4".to_string()),
("buffer_size".to_string(), "8192".to_string()),
("compression".to_string(), "true".to_string()),
("log_level".to_string(), "info".to_string()),
])
}));
// Workers receive and process based on shared config
for worker_id in 0..workers {
dag.add_task(task_fn(move |cfg: HashMap<String, String>| async move {
// Simulate work based on configuration
let batch_size: usize = cfg
.get("batch_size")
.and_then(|s| s.parse().ok())
.unwrap_or(10);
let timeout: usize = cfg
.get("timeout_ms")
.and_then(|s| s.parse().ok())
.unwrap_or(1000);
// Simulate some computation
black_box(worker_id * batch_size + timeout / 100)
}))
.depends_on(&config);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await
.unwrap();
})
});
},
);
}
group.finish();
}
+105
View File
@@ -0,0 +1,105 @@
//! ETL (Extract, Transform, Load) pipeline benchmark
use criterion::{BenchmarkId, Criterion};
use dagx::{task_fn, DagRunner};
pub fn bench_etl_pipeline(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("etl_scaling");
// ETL pipeline needs more time for larger datasets (10k records needs ~12.3s, set to 15s for headroom)
group.measurement_time(std::time::Duration::from_secs(15));
for record_count in [1_000, 5_000, 10_000].iter() {
group.bench_with_input(
BenchmarkId::new("etl", record_count),
record_count,
|b, &count| {
b.iter(|| {
rt.block_on(async {
let dag = DagRunner::new();
// Extract: Load JSON-like data (framework wraps in Arc automatically)
let extract = dag.add_task(task_fn(move |_: ()| async move {
(0..count)
.map(|i| {
format!(
"{{\"id\":{},\"name\":\"User{}\",\"score\":{}}}",
i,
i,
i * 10
)
})
.collect::<Vec<_>>()
}));
// Transform: Three parallel transformations
// T1: Parse and validate
let validate = dag
.add_task(task_fn(move |data: Vec<String>| async move {
data.iter()
.filter(|s| {
s.contains("\"id\"")
&& s.contains("\"name\"")
&& s.contains("\"score\"")
})
.count()
}))
.depends_on(&extract);
// T2: Extract scores
let scores = dag
.add_task(task_fn(move |data: Vec<String>| async move {
data.iter()
.filter_map(|s| {
s.split("\"score\":").nth(1).and_then(|part| {
part.trim_end_matches('}').parse::<i32>().ok()
})
})
.collect::<Vec<_>>()
}))
.depends_on(&extract);
// T3: Extract names
let names = dag
.add_task(task_fn(move |data: Vec<String>| async move {
data.iter()
.filter_map(|s| {
s.split("\"name\":\"")
.nth(1)
.and_then(|part| part.split('"').next())
.map(String::from)
})
.collect::<Vec<_>>()
}))
.depends_on(&extract);
// Load: Aggregate all transformations
dag.add_task(task_fn(
move |(valid, scores, names): (usize, Vec<i32>, Vec<String>)| async move {
let avg_score =
scores.iter().sum::<i32>() / scores.len().max(1) as i32;
format!(
"Processed {} valid records, {} names, avg score: {}",
valid,
names.len(),
avg_score
)
},
))
.depends_on((&validate, &scores, &names));
dag.run(|fut| {
tokio::spawn(fut);
})
.await
.unwrap();
})
});
},
);
}
group.finish();
}
+53
View File
@@ -0,0 +1,53 @@
//! Memory efficiency benchmark with large data structures
use criterion::{BenchmarkId, Criterion};
use dagx::{task_fn, DagRunner};
use std::hint::black_box;
pub fn bench_memory_efficiency(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
let mut group = c.benchmark_group("memory_efficiency");
// Test with different data sizes (in MB)
for mb_size in [1, 5, 10].iter() {
group.bench_with_input(
BenchmarkId::new("large_data_20_consumers", format!("{}MB", mb_size)),
mb_size,
|b, &mb| {
b.iter(|| {
rt.block_on(async {
let dag = DagRunner::new();
let source = dag.add_task(task_fn(move |_: ()| async move {
// Create MB of data - framework wraps in Arc automatically
vec![0u8; mb * 1024 * 1024]
}));
// 20 consumers share the data via automatic Arc wrapping
for i in 0..20 {
dag.add_task(task_fn(move |data: Vec<u8>| async move {
// Process a small portion (data extracted from Arc)
let sample_sum: u32 = data
.iter()
.skip(i * 1000)
.take(1000)
.map(|&b| b as u32)
.sum();
black_box(sample_sum)
}))
.depends_on(&source);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await
.unwrap();
})
});
},
);
}
group.finish();
}
+61
View File
@@ -0,0 +1,61 @@
//! Mixed realistic patterns - fan-out, fan-in, and processing
use criterion::Criterion;
use dagx::{task_fn, DagRunner};
pub fn bench_mixed_patterns(c: &mut Criterion) {
let rt = tokio::runtime::Runtime::new().unwrap();
c.bench_function("mixed_patterns_realistic", |b| {
b.iter(|| {
rt.block_on(async {
let dag = DagRunner::new();
// Stage 1: Load data (framework wraps in Arc automatically)
let data = dag.add_task(task_fn(|_: ()| async {
(0..1000)
.map(|i| format!("Record-{:04}", i))
.collect::<Vec<_>>()
}));
// Stage 2: Fan-out for parallel analysis
let analysis1 = dag
.add_task(task_fn(|d: Vec<String>| async move {
d.iter().filter(|s| s.contains("00")).count()
}))
.depends_on(&data);
let analysis2 = dag
.add_task(task_fn(|d: Vec<String>| async move {
d.iter().map(|s| s.len()).sum::<usize>()
}))
.depends_on(&data);
let analysis3 = dag
.add_task(task_fn(|d: Vec<String>| async move { d.len() }))
.depends_on(&data);
// Stage 3: Fan-in to aggregate
let summary = dag
.add_task(task_fn(|(a1, a2, a3): (usize, usize, usize)| async move {
format!("Matches: {}, Total bytes: {}, Count: {}", a1, a2, a3)
}))
.depends_on((&analysis1, &analysis2, &analysis3));
// Stage 4: Fan-out final report to multiple destinations
for i in 0..3 {
dag.add_task(task_fn(move |report: String| async move {
format!("Destination {}: {}", i, report)
}))
.depends_on(summary);
}
dag.run(|fut| {
tokio::spawn(fut);
})
.await
.unwrap();
})
});
});
}
+6
View File
@@ -0,0 +1,6 @@
//! Realistic workload benchmarks
pub mod config_broadcast;
pub mod etl;
pub mod memory_efficiency;
pub mod mixed;