add support for auth and other improvements
This commit is contained in:
		@@ -7,7 +7,6 @@ publish = false # This is a package of examples, not meant to be published
 | 
			
		||||
[dependencies]
 | 
			
		||||
# Local Rhailib crates
 | 
			
		||||
rhai_client = { path = "../src/client" }
 | 
			
		||||
worker = { path = "../src/worker" }
 | 
			
		||||
 | 
			
		||||
# External dependencies
 | 
			
		||||
rhai = "1.18.0"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
use log::{info, error, debug};
 | 
			
		||||
use rhai::Engine;
 | 
			
		||||
use rhai_client::{RhaiClient, RhaiClientError}; // RhaiTaskDetails is not directly used
 | 
			
		||||
use worker_lib::spawn_rhai_worker;
 | 
			
		||||
use rhai_client::{RhaiClient, RhaiClientError}; // RhaiTaskDetails is now used for its fields
 | 
			
		||||
use rhailib_worker::spawn_rhai_worker;
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
use tokio::sync::mpsc;
 | 
			
		||||
use serde_json::Value;
 | 
			
		||||
use uuid::Uuid; // Added for generating task_id
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
@@ -53,28 +53,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
 | 
			
		||||
    // 5. Submit script and await result using the new mechanism
 | 
			
		||||
    let task_timeout = Duration::from_secs(10);
 | 
			
		||||
    let client_rpc_id: Option<Value> = Some(serde_json::json!({ "demo_request_id": "reply_queue_test_001" }));
 | 
			
		||||
    let task_id = Uuid::new_v4().to_string(); // Generate a unique task_id
 | 
			
		||||
 | 
			
		||||
    info!("Submitting script to circle '{}' and awaiting result...", circle_name);
 | 
			
		||||
    info!("Submitting script to circle '{}' with task_id '{}' and awaiting result...", circle_name, task_id);
 | 
			
		||||
    info!("Script: {}", script_to_run);
 | 
			
		||||
 | 
			
		||||
    match client
 | 
			
		||||
        .submit_script_and_await_result(
 | 
			
		||||
            circle_name,
 | 
			
		||||
            task_id.clone(), // Pass the generated task_id
 | 
			
		||||
            script_to_run.to_string(),
 | 
			
		||||
            client_rpc_id,
 | 
			
		||||
            task_timeout,
 | 
			
		||||
            // poll_interval is no longer needed
 | 
			
		||||
            None // public_key
 | 
			
		||||
        )
 | 
			
		||||
        .await
 | 
			
		||||
    {
 | 
			
		||||
        Ok(details) => {
 | 
			
		||||
            info!("Task completed successfully!");
 | 
			
		||||
            info!("Task {} completed successfully!", details.task_id);
 | 
			
		||||
            debug!("Full Task Details: {:#?}", details);
 | 
			
		||||
            // The task_id is not part of the returned RhaiTaskDetails struct.
 | 
			
		||||
            // We could modify the client to return (task_id, details) if needed,
 | 
			
		||||
            // but for this demo, we'll just log the content of the returned details.
 | 
			
		||||
            info!("Received details for script: {}", details.script);
 | 
			
		||||
            // The task_id is now part of the returned RhaiTaskDetails struct.
 | 
			
		||||
            info!("Received details for task_id: {}, script: {}", details.task_id, details.script);
 | 
			
		||||
            info!("Status: {}", details.status);
 | 
			
		||||
            if let Some(output) = details.output {
 | 
			
		||||
                info!("Output: {}", output); // Expected: 42
 | 
			
		||||
@@ -89,7 +87,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            error!("An error occurred while awaiting task result: {}", e);
 | 
			
		||||
            // The specific error can be inspected if needed, e.g., for timeout
 | 
			
		||||
            if let RhaiClientError::Timeout(task_id) = e {
 | 
			
		||||
            if let RhaiClientError::Timeout(returned_task_id) = e {
 | 
			
		||||
                // Note: 'task_id' here is the one from the error, which should match the one we sent.
 | 
			
		||||
                info!("Task {} timed out.", returned_task_id);
 | 
			
		||||
                info!("Task {} timed out.", task_id);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								examples/end_to_end/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								examples/end_to_end/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
# End-to-End Authorization Demo
 | 
			
		||||
 | 
			
		||||
This example demonstrates an end-to-end scenario involving a custom Rhai engine, `rhailib_worker`, and `rhai_client` to showcase how authorization based on `CALLER_PUBLIC_KEY` can be implemented.
 | 
			
		||||
 | 
			
		||||
## Overview
 | 
			
		||||
 | 
			
		||||
1.  **Custom Rhai Engine**: A Rhai engine is created, and a custom function `check_permission(caller_pk: String)` is registered. This function returns different messages based on the `caller_pk` provided.
 | 
			
		||||
2.  **Rhai Worker (`rhailib_worker`)**: A worker is spawned with this custom engine. The worker is configured with its own `CIRCLE_PUBLIC_KEY` (e.g., "auth_worker_circle").
 | 
			
		||||
3.  **Rhai Client (`rhai_client`)**: The client is used to submit a script (`auth_script.rhai`) to the worker.
 | 
			
		||||
4.  **Authorization Script (`auth_script.rhai`)**: This script calls the `check_permission` function, passing the `CALLER_PUBLIC_KEY` (which is automatically injected into the script's scope by the worker based on the client's submission).
 | 
			
		||||
5.  **Demonstration**: The `main.rs` program submits the script twice, using two different `CALLER_PUBLIC_KEY`s ("admin_pk" and "user_pk"), and shows that the script produces different results based on the authorization logic in `check_permission`.
 | 
			
		||||
 | 
			
		||||
This example illustrates how the `rhailib` components can work together to build systems where script execution is controlled and authorized based on the identity of the calling client.
 | 
			
		||||
 | 
			
		||||
## Running the Example
 | 
			
		||||
 | 
			
		||||
Assuming you have Redis running and accessible at `redis://127.0.0.1/`:
 | 
			
		||||
 | 
			
		||||
Run the example from the `rhailib` root directory:
 | 
			
		||||
```bash
 | 
			
		||||
cargo run --example end_to_end_auth_demo
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
You should see output indicating the results of the script execution for both the "admin_pk" and "user_pk" callers.
 | 
			
		||||
							
								
								
									
										6
									
								
								examples/end_to_end/auth_script.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								examples/end_to_end/auth_script.rhai
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
// auth_script.rhai
 | 
			
		||||
// This script calls a custom registered function 'check_permission'
 | 
			
		||||
// and passes the CALLER_PUBLIC_KEY to it.
 | 
			
		||||
// CALLER_PUBLIC_KEY is injected into the script's scope by the rhailib_worker.
 | 
			
		||||
 | 
			
		||||
check_permission(CALLER_PUBLIC_KEY)
 | 
			
		||||
							
								
								
									
										136
									
								
								examples/end_to_end/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								examples/end_to_end/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
			
		||||
use rhai::{Engine, EvalAltResult};
 | 
			
		||||
use rhai_client::RhaiClient;
 | 
			
		||||
use rhailib_worker::spawn_rhai_worker;
 | 
			
		||||
use std::{fs, path::Path, time::Duration};
 | 
			
		||||
use tokio::sync::mpsc;
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
// Custom Rhai function for authorization
 | 
			
		||||
// It takes the caller's public key as an argument.
 | 
			
		||||
fn check_permission(caller_pk: String) -> Result<String, Box<EvalAltResult>> {
 | 
			
		||||
    log::info!("check_permission called with PK: {}", caller_pk);
 | 
			
		||||
    if caller_pk == "admin_pk" {
 | 
			
		||||
        Ok("Access Granted: Welcome Admin!".to_string())
 | 
			
		||||
    } else if caller_pk == "user_pk" {
 | 
			
		||||
        Ok("Limited Access: Welcome User!".to_string())
 | 
			
		||||
    } else {
 | 
			
		||||
        Ok(format!("Access Denied: Unknown public key '{}'", caller_pk))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
 | 
			
		||||
 | 
			
		||||
    let redis_url = "redis://127.0.0.1/";
 | 
			
		||||
    let worker_circle_pk = "auth_worker_circle".to_string();
 | 
			
		||||
 | 
			
		||||
    // 1. Create a Rhai engine and register custom functionality
 | 
			
		||||
    let mut engine = Engine::new();
 | 
			
		||||
    engine.register_fn("check_permission", check_permission);
 | 
			
		||||
    log::info!("Custom 'check_permission' function registered with Rhai engine.");
 | 
			
		||||
 | 
			
		||||
    // 2. Spawn the Rhai worker
 | 
			
		||||
    let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
 | 
			
		||||
    let worker_handle = tokio::spawn(spawn_rhai_worker(
 | 
			
		||||
        0, // worker_id
 | 
			
		||||
        worker_circle_pk.clone(),
 | 
			
		||||
        engine,
 | 
			
		||||
        redis_url.to_string(),
 | 
			
		||||
        shutdown_rx,
 | 
			
		||||
        false, // use_sentinel
 | 
			
		||||
    ));
 | 
			
		||||
    log::info!("Rhai worker spawned for circle: {}", worker_circle_pk);
 | 
			
		||||
 | 
			
		||||
    // Give the worker a moment to start up
 | 
			
		||||
    tokio::time::sleep(Duration::from_secs(1)).await;
 | 
			
		||||
 | 
			
		||||
    // 3. Create a Rhai client
 | 
			
		||||
    let client = RhaiClient::new(redis_url)?;
 | 
			
		||||
    log::info!("Rhai client created.");
 | 
			
		||||
 | 
			
		||||
    // 4. Load the Rhai script content
 | 
			
		||||
    let script_path_str = "examples/end_to_end/auth_script.rhai"; // Relative to Cargo.toml / rhailib root
 | 
			
		||||
    let script_content = match fs::read_to_string(script_path_str) {
 | 
			
		||||
        Ok(content) => content,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Failed to read script file '{}': {}", script_path_str, e);
 | 
			
		||||
            // Attempt to read from an alternative path if run via `cargo run --example`
 | 
			
		||||
            // where current dir might be the crate root.
 | 
			
		||||
            let alt_script_path = Path::new(file!()).parent().unwrap().join("auth_script.rhai");
 | 
			
		||||
            log::info!("Attempting alternative script path: {:?}", alt_script_path);
 | 
			
		||||
            fs::read_to_string(&alt_script_path)?
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    log::info!("Loaded script content from '{}'", script_path_str);
 | 
			
		||||
 | 
			
		||||
    // Define different caller public keys
 | 
			
		||||
    let admin_caller_pk = "admin_pk".to_string();
 | 
			
		||||
    let user_caller_pk = "user_pk".to_string();
 | 
			
		||||
    let unknown_caller_pk = "unknown_pk".to_string();
 | 
			
		||||
 | 
			
		||||
    let callers = vec![
 | 
			
		||||
        ("Admin", admin_caller_pk),
 | 
			
		||||
        ("User", user_caller_pk),
 | 
			
		||||
        ("Unknown", unknown_caller_pk),
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    for (caller_name, caller_pk) in callers {
 | 
			
		||||
        let task_id = Uuid::new_v4().to_string();
 | 
			
		||||
        log::info!(
 | 
			
		||||
            "Submitting script for caller '{}' (PK: {}) with task_id: {}",
 | 
			
		||||
            caller_name,
 | 
			
		||||
            caller_pk,
 | 
			
		||||
            task_id
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        match client
 | 
			
		||||
            .submit_script_and_await_result(
 | 
			
		||||
                &worker_circle_pk,
 | 
			
		||||
                task_id.clone(),        // task_id (UUID) first
 | 
			
		||||
                script_content.clone(), // script_content second
 | 
			
		||||
                Duration::from_secs(10),
 | 
			
		||||
                Some(caller_pk.clone()), // This is the CALLER_PUBLIC_KEY
 | 
			
		||||
            )
 | 
			
		||||
            .await
 | 
			
		||||
        {
 | 
			
		||||
            Ok(details) => {
 | 
			
		||||
                log::info!(
 | 
			
		||||
                    "Task {} for caller '{}' (PK: {}) completed. Status: {}, Output: {:?}, Error: {:?}",
 | 
			
		||||
                    task_id,
 | 
			
		||||
                    caller_name,
 | 
			
		||||
                    caller_pk,
 | 
			
		||||
                    details.status,
 | 
			
		||||
                    details.output,
 | 
			
		||||
                    details.error
 | 
			
		||||
                );
 | 
			
		||||
                // Basic assertion for expected output
 | 
			
		||||
                if caller_pk == "admin_pk" {
 | 
			
		||||
                    assert_eq!(details.output, Some("Access Granted: Welcome Admin!".to_string()));
 | 
			
		||||
                } else if caller_pk == "user_pk" {
 | 
			
		||||
                    assert_eq!(details.output, Some("Limited Access: Welcome User!".to_string()));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!(
 | 
			
		||||
                    "Task {} for caller '{}' (PK: {}) failed: {}",
 | 
			
		||||
                    task_id,
 | 
			
		||||
                    caller_name,
 | 
			
		||||
                    caller_pk,
 | 
			
		||||
                    e
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        tokio::time::sleep(Duration::from_millis(100)).await; // Small delay between submissions
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 5. Shutdown the worker (optional, could also let it run until program exits)
 | 
			
		||||
    log::info!("Signaling worker to shutdown...");
 | 
			
		||||
    let _ = shutdown_tx.send(()).await;
 | 
			
		||||
    if let Err(e) = worker_handle.await {
 | 
			
		||||
        log::error!("Worker task panicked or encountered an error: {:?}", e);
 | 
			
		||||
    }
 | 
			
		||||
    log::info!("Worker shutdown complete.");
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
use rhai::Engine;
 | 
			
		||||
use rhai_client::RhaiClient; // To submit tasks
 | 
			
		||||
use uuid::Uuid; // For generating task_id
 | 
			
		||||
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
use tokio::time::sleep;
 | 
			
		||||
use worker_lib::spawn_rhai_worker;
 | 
			
		||||
use rhailib_worker::spawn_rhai_worker;
 | 
			
		||||
 | 
			
		||||
// Custom function for Rhai
 | 
			
		||||
fn add(a: i64, b: i64) -> i64 {
 | 
			
		||||
@@ -48,13 +49,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    log::info!("Submitting math script to 'math_circle' and awaiting result...");
 | 
			
		||||
    
 | 
			
		||||
    let timeout_duration = Duration::from_secs(10);
 | 
			
		||||
 | 
			
		||||
    let task_id = Uuid::new_v4().to_string();
 | 
			
		||||
 | 
			
		||||
    match client.submit_script_and_await_result(
 | 
			
		||||
        "math_circle",
 | 
			
		||||
        script_content.to_string(),
 | 
			
		||||
        None,
 | 
			
		||||
        timeout_duration
 | 
			
		||||
        task_id, // Pass the generated task_id
 | 
			
		||||
        timeout_duration,
 | 
			
		||||
        None
 | 
			
		||||
    ).await {
 | 
			
		||||
        Ok(details) => {
 | 
			
		||||
            log::info!("Math Worker Example: Task finished. Status: {}, Output: {:?}, Error: {:?}",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
use rhai::Engine;
 | 
			
		||||
use rhai_client::RhaiClient; // To submit tasks
 | 
			
		||||
use uuid::Uuid; // For generating task_id
 | 
			
		||||
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
use tokio::time::sleep;
 | 
			
		||||
use worker_lib::spawn_rhai_worker;
 | 
			
		||||
use rhailib_worker::spawn_rhai_worker;
 | 
			
		||||
 | 
			
		||||
// Custom function for Rhai
 | 
			
		||||
fn reverse_string(s: String) -> String {
 | 
			
		||||
@@ -48,13 +49,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    log::info!("Submitting string script to 'string_circle' and awaiting result...");
 | 
			
		||||
 | 
			
		||||
    let timeout_duration = Duration::from_secs(10);
 | 
			
		||||
 | 
			
		||||
    let task_id = Uuid::new_v4().to_string();
 | 
			
		||||
 | 
			
		||||
    match client.submit_script_and_await_result(
 | 
			
		||||
        "string_circle",
 | 
			
		||||
        script_content.to_string(),
 | 
			
		||||
        None,
 | 
			
		||||
        timeout_duration
 | 
			
		||||
        task_id, // Pass the generated task_id
 | 
			
		||||
        timeout_duration,
 | 
			
		||||
        None
 | 
			
		||||
    ).await {
 | 
			
		||||
        Ok(details) => {
 | 
			
		||||
            log::info!("String Worker Example: Task finished. Status: {}, Output: {:?}, Error: {:?}",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,52 +0,0 @@
 | 
			
		||||
use worker_lib::spawn_rhai_worker;
 | 
			
		||||
use rhai::Engine;
 | 
			
		||||
use tokio::sync::mpsc;
 | 
			
		||||
use tokio::signal;
 | 
			
		||||
use log::info;
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    // Initialize the logger
 | 
			
		||||
    env_logger::init();
 | 
			
		||||
 | 
			
		||||
    let redis_url = "redis://127.0.0.1/";
 | 
			
		||||
    let circle_name = "default".to_string();
 | 
			
		||||
    let mut engine = Engine::new(); // Create a new, simple Rhai engine
 | 
			
		||||
 | 
			
		||||
    // Register a simple 'ping' function for the readiness check.
 | 
			
		||||
    engine.register_fn("ping", || -> String {
 | 
			
		||||
        "pong".to_string()
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Create a channel for the shutdown signal
 | 
			
		||||
    let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
 | 
			
		||||
 | 
			
		||||
    info!("Spawning Rhai worker for circle: {}", circle_name);
 | 
			
		||||
 | 
			
		||||
    // Spawn the worker
 | 
			
		||||
    let worker_handle = spawn_rhai_worker(
 | 
			
		||||
        1, // circle_id
 | 
			
		||||
        circle_name.clone(),
 | 
			
		||||
        engine,
 | 
			
		||||
        redis_url.to_string(),
 | 
			
		||||
        shutdown_rx,
 | 
			
		||||
        false, // preserve_tasks
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    info!("Worker spawned. Press Ctrl+C to shut down.");
 | 
			
		||||
 | 
			
		||||
    // Wait for Ctrl+C
 | 
			
		||||
    signal::ctrl_c().await?;
 | 
			
		||||
 | 
			
		||||
    info!("Ctrl+C received. Sending shutdown signal to worker.");
 | 
			
		||||
    let _ = shutdown_tx.send(()).await;
 | 
			
		||||
 | 
			
		||||
    // Wait for the worker to finish
 | 
			
		||||
    if let Err(e) = worker_handle.await? {
 | 
			
		||||
        eprintln!("Worker process finished with an error: {:?}", e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    info!("Worker has shut down gracefully.");
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
@@ -1,133 +0,0 @@
 | 
			
		||||
//! Demo script showing how to run the hybrid performance benchmark
 | 
			
		||||
//! 
 | 
			
		||||
//! This example demonstrates:
 | 
			
		||||
//! 1. Starting workers programmatically
 | 
			
		||||
//! 2. Running the Lua batch script
 | 
			
		||||
//! 3. Collecting and displaying statistics
 | 
			
		||||
 | 
			
		||||
use rhailib::{RedisStatsCollector, WorkerManager, clear_redis_test_data, check_redis_connection};
 | 
			
		||||
use redis::{Client, Commands};
 | 
			
		||||
use std::fs;
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
const REDIS_URL: &str = "redis://localhost:6379";
 | 
			
		||||
const CIRCLE_NAME: &str = "demo_circle";
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
    env_logger::init();
 | 
			
		||||
    
 | 
			
		||||
    println!("🚀 Rhailib Hybrid Performance Benchmark Demo");
 | 
			
		||||
    println!("============================================");
 | 
			
		||||
    
 | 
			
		||||
    // Check Redis connection
 | 
			
		||||
    println!("📡 Checking Redis connection...");
 | 
			
		||||
    check_redis_connection(REDIS_URL)?;
 | 
			
		||||
    println!("✅ Redis connection successful");
 | 
			
		||||
    
 | 
			
		||||
    // Clear any existing test data
 | 
			
		||||
    println!("🧹 Clearing existing test data...");
 | 
			
		||||
    clear_redis_test_data(REDIS_URL)?;
 | 
			
		||||
    println!("✅ Test data cleared");
 | 
			
		||||
    
 | 
			
		||||
    // Load Lua script
 | 
			
		||||
    println!("📜 Loading Lua batch script...");
 | 
			
		||||
    let lua_script = fs::read_to_string("scripts/run_rhai_batch.lua")?;
 | 
			
		||||
    println!("✅ Lua script loaded ({} bytes)", lua_script.len());
 | 
			
		||||
    
 | 
			
		||||
    // Start workers
 | 
			
		||||
    println!("👷 Starting 2 worker processes...");
 | 
			
		||||
    let mut worker_manager = WorkerManager::new();
 | 
			
		||||
    worker_manager.start_workers(2, CIRCLE_NAME, REDIS_URL)?;
 | 
			
		||||
    worker_manager.wait_for_workers_ready(Duration::from_secs(3))?;
 | 
			
		||||
    println!("✅ Workers started and ready");
 | 
			
		||||
    
 | 
			
		||||
    // Connect to Redis
 | 
			
		||||
    let redis_client = Client::open(REDIS_URL)?;
 | 
			
		||||
    let mut conn = redis_client.get_connection()?;
 | 
			
		||||
    
 | 
			
		||||
    // Execute batch workload
 | 
			
		||||
    println!("🎯 Submitting batch of 100 tasks...");
 | 
			
		||||
    let batch_id = format!("demo_batch_{}", chrono::Utc::now().timestamp_millis());
 | 
			
		||||
    let simple_script = "let x = 42; x * 2";
 | 
			
		||||
    
 | 
			
		||||
    let start_time = std::time::Instant::now();
 | 
			
		||||
    
 | 
			
		||||
    let result: redis::Value = redis::cmd("EVAL")
 | 
			
		||||
        .arg(&lua_script)
 | 
			
		||||
        .arg(0) // No keys
 | 
			
		||||
        .arg(CIRCLE_NAME)
 | 
			
		||||
        .arg(100) // task count
 | 
			
		||||
        .arg(simple_script)
 | 
			
		||||
        .arg(&batch_id)
 | 
			
		||||
        .query(&mut conn)?;
 | 
			
		||||
    
 | 
			
		||||
    let submission_time = start_time.elapsed();
 | 
			
		||||
    println!("✅ Batch submitted in {:?}", submission_time);
 | 
			
		||||
    
 | 
			
		||||
    // Parse result
 | 
			
		||||
    if let redis::Value::Data(data) = result {
 | 
			
		||||
        let response: serde_json::Value = serde_json::from_slice(&data)?;
 | 
			
		||||
        println!("📊 Batch info: {}", serde_json::to_string_pretty(&response)?);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Wait for completion and collect statistics
 | 
			
		||||
    println!("⏳ Waiting for batch completion...");
 | 
			
		||||
    let stats_collector = RedisStatsCollector::new(REDIS_URL)?;
 | 
			
		||||
    
 | 
			
		||||
    let completed = stats_collector.wait_for_batch_completion(
 | 
			
		||||
        &batch_id,
 | 
			
		||||
        100,
 | 
			
		||||
        Duration::from_secs(30),
 | 
			
		||||
    )?;
 | 
			
		||||
    
 | 
			
		||||
    if !completed {
 | 
			
		||||
        println!("⚠️  Batch did not complete within timeout");
 | 
			
		||||
        return Ok(());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    println!("✅ Batch completed!");
 | 
			
		||||
    
 | 
			
		||||
    // Collect and display statistics
 | 
			
		||||
    println!("📈 Collecting performance statistics...");
 | 
			
		||||
    let timings = stats_collector.collect_batch_timings(&batch_id)?;
 | 
			
		||||
    let stats = stats_collector.calculate_stats(&timings);
 | 
			
		||||
    
 | 
			
		||||
    println!("\n📊 PERFORMANCE RESULTS");
 | 
			
		||||
    println!("======================");
 | 
			
		||||
    println!("Total tasks:       {}", stats.total_tasks);
 | 
			
		||||
    println!("Completed tasks:   {}", stats.completed_tasks);
 | 
			
		||||
    println!("Failed tasks:      {}", stats.failed_tasks);
 | 
			
		||||
    println!("Error rate:        {:.2}%", stats.error_rate);
 | 
			
		||||
    println!("Throughput:        {:.2} tasks/second", stats.throughput_tps);
 | 
			
		||||
    println!("Batch duration:    {:.2} ms", stats.batch_duration_ms);
 | 
			
		||||
    println!("\nLatency Statistics:");
 | 
			
		||||
    println!("  Min:             {:.2} ms", stats.latency_stats.min_ms);
 | 
			
		||||
    println!("  Max:             {:.2} ms", stats.latency_stats.max_ms);
 | 
			
		||||
    println!("  Mean:            {:.2} ms", stats.latency_stats.mean_ms);
 | 
			
		||||
    println!("  Median:          {:.2} ms", stats.latency_stats.median_ms);
 | 
			
		||||
    println!("  P95:             {:.2} ms", stats.latency_stats.p95_ms);
 | 
			
		||||
    println!("  P99:             {:.2} ms", stats.latency_stats.p99_ms);
 | 
			
		||||
    println!("  Std Dev:         {:.2} ms", stats.latency_stats.std_dev_ms);
 | 
			
		||||
    
 | 
			
		||||
    // Show some individual task timings
 | 
			
		||||
    println!("\n🔍 Sample Task Timings (first 10):");
 | 
			
		||||
    for (i, timing) in timings.iter().take(10).enumerate() {
 | 
			
		||||
        println!("  Task {}: {} -> {} ({:.2}ms, status: {})", 
 | 
			
		||||
            i + 1,
 | 
			
		||||
            timing.task_id,
 | 
			
		||||
            timing.status,
 | 
			
		||||
            timing.latency_ms,
 | 
			
		||||
            timing.status
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Cleanup
 | 
			
		||||
    println!("\n🧹 Cleaning up...");
 | 
			
		||||
    stats_collector.cleanup_batch_data(&batch_id)?;
 | 
			
		||||
    worker_manager.shutdown()?;
 | 
			
		||||
    println!("✅ Cleanup complete");
 | 
			
		||||
    
 | 
			
		||||
    println!("\n🎉 Demo completed successfully!");
 | 
			
		||||
    
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user