feat: dagrunner, initial version
This commit is contained in:
122 files changed
+23101
No files matched your search
@@ -0,0 +1,464 @@
|
||||
//! # Debug Tracing and Task Naming
|
||||
//!
|
||||
//! This example demonstrates how to add debug metadata and tracing information to tasks
|
||||
//! for better observability in complex DAG workflows.
|
||||
//!
|
||||
//! ## What You'll Learn
|
||||
//! - How to add task names and IDs for debugging
|
||||
//! - How to implement correlation IDs for distributed tracing
|
||||
//! - How to track task execution timing and lifecycle events
|
||||
//! - How to build traceable task wrappers
|
||||
//!
|
||||
//! ## Debugging Patterns
|
||||
//!
|
||||
//! **1. Named Tasks** - Add human-readable names to tasks
|
||||
//! ```rust
|
||||
//! struct TaskMetadata {
|
||||
//! id: String,
|
||||
//! name: String,
|
||||
//! start_time: Option<Instant>,
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! **2. Correlation IDs** - Track requests across task boundaries
|
||||
//! ```rust
|
||||
//! struct TraceContext {
|
||||
//! correlation_id: String,
|
||||
//! parent_task: Option<String>,
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! **3. Execution Metrics** - Measure task performance
|
||||
//! ```rust
|
||||
//! struct TaskTrace {
|
||||
//! name: String,
|
||||
//! duration_ms: u64,
|
||||
//! status: &'static str,
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Workflow Diagram
|
||||
//! ```text
|
||||
//! TRACE: order-12345
|
||||
//! ┌──────────────────┐
|
||||
//! │ ValidateOrder │ [order-12345] Start: 0ms
|
||||
//! │ ID: task-001 │──────► Status: Success
|
||||
//! └──────────────────┘ Duration: 10ms
|
||||
//! │
|
||||
//! ├──────────────────┬──────────────────┐
|
||||
//! ▼ ▼ ▼
|
||||
//! ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||||
//! │ CheckInventory │ │ ValidatePayment │ │ CalculateShipping│
|
||||
//! │ ID: task-002 │ │ ID: task-003 │ │ ID: task-004 │
|
||||
//! └──────────────────┘ └──────────────────┘ └──────────────────┘
|
||||
//! [order-12345] [order-12345] [order-12345]
|
||||
//! Duration: 45ms Duration: 120ms Duration: 15ms
|
||||
//! │ │ │
|
||||
//! └──────────────────┴──────────────────┘
|
||||
//! ▼
|
||||
//! ┌──────────────────┐
|
||||
//! │ FulfillOrder │ [order-12345]
|
||||
//! │ ID: task-005 │ Duration: 30ms
|
||||
//! └──────────────────┘
|
||||
//! ```
|
||||
//!
|
||||
//! ## Key Concepts
|
||||
//! - **Observability**: Make task execution visible for debugging
|
||||
//! - **Correlation IDs**: Track a request through multiple tasks
|
||||
//! - **Structured logging**: Consistent task metadata across logs
|
||||
//! - **Performance metrics**: Measure task execution time
|
||||
//! - **Task lifecycle**: Track start, completion, and errors
|
||||
//!
|
||||
//! ## Use Cases
|
||||
//! - Debugging complex workflows in production
|
||||
//! - Performance analysis and bottleneck identification
|
||||
//! - Distributed tracing in microservices
|
||||
//! - SLA monitoring and alerting
|
||||
//! - Audit logging for compliance
|
||||
//!
|
||||
//! ## Running This Example
|
||||
//! ```bash
|
||||
//! cargo run --example debug_tracing
|
||||
//! ```
|
||||
//!
|
||||
//! ## Expected Output
|
||||
//! ```text
|
||||
//! === Debug Tracing Example ===
|
||||
//!
|
||||
//! Starting order processing for order-12345
|
||||
//!
|
||||
//! [order-12345] [task-001] ValidateOrder: Starting
|
||||
//! [order-12345] [task-001] ValidateOrder: Completed in 10ms - Success
|
||||
//!
|
||||
//! [order-12345] [task-002] CheckInventory: Starting
|
||||
//! [order-12345] [task-003] ValidatePayment: Starting
|
||||
//! [order-12345] [task-004] CalculateShipping: Starting
|
||||
//! [order-12345] [task-002] CheckInventory: Completed in 45ms - Success
|
||||
//! [order-12345] [task-004] CalculateShipping: Completed in 15ms - Success
|
||||
//! [order-12345] [task-003] ValidatePayment: Completed in 120ms - Success
|
||||
//!
|
||||
//! [order-12345] [task-005] FulfillOrder: Starting
|
||||
//! [order-12345] [task-005] FulfillOrder: Completed in 30ms - Success
|
||||
//!
|
||||
//! === Execution Trace Summary ===
|
||||
//! Correlation ID: order-12345
|
||||
//! Total Tasks: 5
|
||||
//! Total Duration: 220ms
|
||||
//! Success: 5, Failed: 0
|
||||
//!
|
||||
//! Task Breakdown:
|
||||
//! ValidateOrder: 10ms
|
||||
//! CheckInventory: 45ms
|
||||
//! ValidatePayment: 120ms (slowest)
|
||||
//! CalculateShipping: 15ms
|
||||
//! FulfillOrder: 30ms
|
||||
//! ```
|
||||
|
||||
use dagx::{task, DagRunner, Task};
|
||||
use std::time::Instant;
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
||||
// Stateful task that tracks execution metadata for debugging
|
||||
struct TracedTask {
|
||||
task_id: String,
|
||||
task_name: String,
|
||||
correlation_id: String,
|
||||
work_duration_ms: u64,
|
||||
start_time: Option<Instant>,
|
||||
}
|
||||
|
||||
impl TracedTask {
|
||||
fn new(
|
||||
task_id: impl Into<String>,
|
||||
task_name: impl Into<String>,
|
||||
correlation_id: impl Into<String>,
|
||||
work_duration_ms: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
task_id: task_id.into(),
|
||||
task_name: task_name.into(),
|
||||
correlation_id: correlation_id.into(),
|
||||
work_duration_ms,
|
||||
start_time: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[task]
|
||||
impl TracedTask {
|
||||
async fn run(&mut self) -> Result<TaskTrace, String> {
|
||||
// Record start
|
||||
let start = Instant::now();
|
||||
self.start_time = Some(start);
|
||||
|
||||
println!(
|
||||
"[{}] [{}] {}: Starting",
|
||||
self.correlation_id, self.task_id, self.task_name
|
||||
);
|
||||
|
||||
// Simulate work
|
||||
sleep(Duration::from_millis(self.work_duration_ms)).await;
|
||||
|
||||
// Record completion
|
||||
let duration_ms = start.elapsed().as_millis() as u64;
|
||||
|
||||
println!(
|
||||
"[{}] [{}] {}: Completed in {}ms - Success",
|
||||
self.correlation_id, self.task_id, self.task_name, duration_ms
|
||||
);
|
||||
|
||||
Ok(TaskTrace {
|
||||
task_id: self.task_id.clone(),
|
||||
task_name: self.task_name.clone(),
|
||||
correlation_id: self.correlation_id.clone(),
|
||||
duration_ms,
|
||||
status: "Success",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Task that can fail, demonstrating error tracing
|
||||
struct TracedTaskWithError {
|
||||
task_id: String,
|
||||
task_name: String,
|
||||
correlation_id: String,
|
||||
should_fail: bool,
|
||||
}
|
||||
|
||||
impl TracedTaskWithError {
|
||||
fn new(
|
||||
task_id: impl Into<String>,
|
||||
task_name: impl Into<String>,
|
||||
correlation_id: impl Into<String>,
|
||||
should_fail: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
task_id: task_id.into(),
|
||||
task_name: task_name.into(),
|
||||
correlation_id: correlation_id.into(),
|
||||
should_fail,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[task]
|
||||
impl TracedTaskWithError {
|
||||
async fn run(&mut self) -> Result<TaskTrace, TaskTrace> {
|
||||
let start = Instant::now();
|
||||
|
||||
println!(
|
||||
"[{}] [{}] {}: Starting",
|
||||
self.correlation_id, self.task_id, self.task_name
|
||||
);
|
||||
|
||||
sleep(Duration::from_millis(50)).await;
|
||||
|
||||
let duration_ms = start.elapsed().as_millis() as u64;
|
||||
|
||||
if self.should_fail {
|
||||
println!(
|
||||
"[{}] [{}] {}: Failed in {}ms - Validation Error",
|
||||
self.correlation_id, self.task_id, self.task_name, duration_ms
|
||||
);
|
||||
|
||||
Err(TaskTrace {
|
||||
task_id: self.task_id.clone(),
|
||||
task_name: self.task_name.clone(),
|
||||
correlation_id: self.correlation_id.clone(),
|
||||
duration_ms,
|
||||
status: "Failed",
|
||||
})
|
||||
} else {
|
||||
println!(
|
||||
"[{}] [{}] {}: Completed in {}ms - Success",
|
||||
self.correlation_id, self.task_id, self.task_name, duration_ms
|
||||
);
|
||||
|
||||
Ok(TaskTrace {
|
||||
task_id: self.task_id.clone(),
|
||||
task_name: self.task_name.clone(),
|
||||
correlation_id: self.correlation_id.clone(),
|
||||
duration_ms,
|
||||
status: "Success",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Task trace data structure
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct TaskTrace {
|
||||
task_id: String,
|
||||
task_name: String,
|
||||
correlation_id: String,
|
||||
duration_ms: u64,
|
||||
status: &'static str,
|
||||
}
|
||||
|
||||
// Task that aggregates traces from multiple upstream tasks
|
||||
struct TraceAggregator {
|
||||
correlation_id: String,
|
||||
}
|
||||
|
||||
impl TraceAggregator {
|
||||
fn new(correlation_id: impl Into<String>) -> Self {
|
||||
Self {
|
||||
correlation_id: correlation_id.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[task]
|
||||
impl TraceAggregator {
|
||||
async fn run(
|
||||
&mut self,
|
||||
t1: &Result<TaskTrace, String>,
|
||||
t2: &Result<TaskTrace, String>,
|
||||
t3: &Result<TaskTrace, String>,
|
||||
) -> TraceSummary {
|
||||
let traces = vec![t1, t2, t3];
|
||||
|
||||
let mut summary = TraceSummary {
|
||||
correlation_id: self.correlation_id.clone(),
|
||||
task_count: traces.len(),
|
||||
total_duration_ms: 0,
|
||||
success_count: 0,
|
||||
failed_count: 0,
|
||||
task_details: Vec::new(),
|
||||
};
|
||||
|
||||
for trace_result in traces {
|
||||
match trace_result {
|
||||
Ok(trace) => {
|
||||
summary.success_count += 1;
|
||||
summary.total_duration_ms += trace.duration_ms;
|
||||
summary.task_details.push(trace.clone());
|
||||
}
|
||||
Err(e) => {
|
||||
summary.failed_count += 1;
|
||||
eprintln!("[{}] Task failed: {}", self.correlation_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
summary
|
||||
}
|
||||
}
|
||||
|
||||
// Summary of traced execution
|
||||
#[derive(Clone, Debug)]
|
||||
struct TraceSummary {
|
||||
correlation_id: String,
|
||||
task_count: usize,
|
||||
total_duration_ms: u64,
|
||||
success_count: usize,
|
||||
failed_count: usize,
|
||||
task_details: Vec<TaskTrace>,
|
||||
}
|
||||
|
||||
impl TraceSummary {
|
||||
fn print(&self) {
|
||||
println!("\n=== Execution Trace Summary ===");
|
||||
println!("Correlation ID: {}", self.correlation_id);
|
||||
println!("Total Tasks: {}", self.task_count);
|
||||
println!("Total Duration: {}ms", self.total_duration_ms);
|
||||
println!(
|
||||
"Success: {}, Failed: {}",
|
||||
self.success_count, self.failed_count
|
||||
);
|
||||
|
||||
if !self.task_details.is_empty() {
|
||||
println!("\nTask Breakdown:");
|
||||
let max_duration = self
|
||||
.task_details
|
||||
.iter()
|
||||
.map(|t| t.duration_ms)
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
for trace in &self.task_details {
|
||||
let slowest_tag = if trace.duration_ms == max_duration {
|
||||
" (slowest)"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
println!(
|
||||
" {}: {}ms{}",
|
||||
trace.task_name, trace.duration_ms, slowest_tag
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Debug Tracing Example ===\n");
|
||||
|
||||
// Example 1: Successful order processing with full tracing
|
||||
println!("Example 1: Successful order processing\n");
|
||||
{
|
||||
let correlation_id = "order-12345";
|
||||
println!("Starting order processing for {}\n", correlation_id);
|
||||
|
||||
let dag = DagRunner::new();
|
||||
|
||||
// Root task: Validate order
|
||||
let _validate = dag.add_task(TracedTask::new(
|
||||
"task-001",
|
||||
"ValidateOrder",
|
||||
correlation_id,
|
||||
10,
|
||||
));
|
||||
|
||||
// Parallel validation tasks (independent, no dependencies)
|
||||
let inventory = dag.add_task(TracedTask::new(
|
||||
"task-002",
|
||||
"CheckInventory",
|
||||
correlation_id,
|
||||
45,
|
||||
));
|
||||
|
||||
let payment = dag.add_task(TracedTask::new(
|
||||
"task-003",
|
||||
"ValidatePayment",
|
||||
correlation_id,
|
||||
120,
|
||||
));
|
||||
|
||||
let shipping = dag.add_task(TracedTask::new(
|
||||
"task-004",
|
||||
"CalculateShipping",
|
||||
correlation_id,
|
||||
15,
|
||||
));
|
||||
|
||||
// Aggregate traces
|
||||
let summary = dag
|
||||
.add_task(TraceAggregator::new(correlation_id))
|
||||
.depends_on((&inventory, &payment, &shipping));
|
||||
|
||||
// Run the DAG
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Print summary
|
||||
dag.get(summary).unwrap().print();
|
||||
}
|
||||
|
||||
println!("\n");
|
||||
|
||||
// Example 2: Order with failed payment
|
||||
println!("Example 2: Order with failed payment\n");
|
||||
{
|
||||
let correlation_id = "order-67890";
|
||||
println!("Starting order processing for {}\n", correlation_id);
|
||||
|
||||
let dag = DagRunner::new();
|
||||
|
||||
let _validate = dag.add_task(TracedTaskWithError::new(
|
||||
"task-101",
|
||||
"ValidateOrder",
|
||||
correlation_id,
|
||||
false,
|
||||
));
|
||||
|
||||
let _inventory = dag.add_task(TracedTaskWithError::new(
|
||||
"task-102",
|
||||
"CheckInventory",
|
||||
correlation_id,
|
||||
false,
|
||||
));
|
||||
|
||||
// This task will fail
|
||||
let payment = dag.add_task(TracedTaskWithError::new(
|
||||
"task-103",
|
||||
"ValidatePayment",
|
||||
correlation_id,
|
||||
true, // will fail
|
||||
));
|
||||
|
||||
dag.run(|fut| {
|
||||
tokio::spawn(fut);
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Check results
|
||||
match dag.get(payment).unwrap() {
|
||||
Ok(_) => println!("\nPayment validation succeeded"),
|
||||
Err(trace) => {
|
||||
println!("\n[{}] Payment validation failed:", correlation_id);
|
||||
println!(" Task ID: {}", trace.task_id);
|
||||
println!(" Duration: {}ms", trace.duration_ms);
|
||||
println!(" Status: {}", trace.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n=== Debug Tracing Example Complete ===");
|
||||
}
|
||||
Reference in new issue
Block a user