prodash

repository·main·Indexed 19 days ago

https://github.com/gitoxidelabs/prodash

A Rust library for integrating high-performance progress reporting into concurrent applications. It provides a pragmatic API for tracking asynchronous and blocking tasks with multiple rendering modes, including a full Terminal User Interface (TUI) and a lightweight line-based renderer. Features include hierarchical task tracking (up to 6 levels), support for various terminal backends (crossterm, termion), and customizable unit formatting for bytes, durations, and human-readable counts.

Tokens
10.2K
Snippets
33
Records
45
Agent score
64%

What's inside prodash

  1. Overview of prodash

    main
    prodash is a library designed to integrate progress reporting into concurrent applications. It provides a pragmatic API for tracking progress and includes various renderers to display that progress, such as a full Terminal User Interface (TUI) or a minimal line-based renderer. It is optimized for high-concurrency scenarios with fast insertions and updates.
  2. Understand Location placement for progress metadata

    main

    The Location enum determines where metadata like percentage and throughput are rendered relative to the progress value.

    • BeforeValue: Metadata is placed in front of the numeric value.
    • AfterUnit: Metadata is placed after the unit string (default behavior).
  3. How hierarchical progress works with NestedProgress

    main

    NestedProgress is a trait used to describe hierarchical progress trees. It extends the base Progress trait by allowing you to add child progress instances. When a child is added, it appears contained within the parent in the progress tree.

    You can add children in two ways:

    1. add_child(name): Adds a child with a given name. This child does not have a stable identifier.
    2. add_child_with_id(name, id): Adds a child with a given name and a specific Id, allowing it to be identified later.

    If you need to store progress objects dynamically (e.g., in a Vec), you can use DynNestedProgress and BoxedDynNestedProgress to achieve object safety.

    // Example of adding children to a nested progress instance
    let mut parent = // ... implementation of NestedProgress
    let child = parent.add_child("sub-task");
    let child_with_id = parent.add_child_with_id("stable-task", some_id);
  4. How the progress tree structure works

    main

    The progress tree is composed of a Root which contains an Item. An Item represents a node in the hierarchy and contains:

    • A key identifying the item.
    • A value representing the current StepShared state.
    • A tree (a thread-safe map) containing child Task objects.
    • A messages ring buffer for sending asynchronous messages associated with that specific task.

    This structure allows for granular progress tracking where each node in the tree manages its own state and communication independently of its parent or siblings.

  5. Track incremental updates with MessageCopyState

    main

    When using MessageRingBuffer::copy_new, you must capture and store the returned MessageCopyState. This state contains the cursor, buf_len, and total count required to identify which messages are new in the next polling cycle.

    Warning: Because this is a ring buffer, if the number of new messages exceeds the buffer's capacity before you call copy_new again, you may lose messages. If the buffer wraps around completely, copy_new will fallback to copy_all to ensure you get the most recent data.

  6. Set TUI shutdown behavior with `Interrupt`

    main

    The Interrupt enum determines how the TUI event loop responds to interrupt requests (like Ctrl+C or Esc).

    • Interrupt::Instantly: The default mode. The GUI exits immediately upon receiving an interrupt.
    • Interrupt::Deferred: The GUI will wait until the next Interrupt::Instantly event is received before exiting. This can be used to allow the TUI to finish a final render or clean up state gracefully.
    // Use SetInterruptMode event to switch modes
    event_sender.send(Event::SetInterruptMode(Interrupt::Deferred)).await?; 
  7. Understand Adjacency and SiblingLocation

    main

    The Adjacency struct provides information about the relative position of a task within its hierarchy, specifically identifying if siblings exist Above or Below the current item at various levels.

    SiblingLocation can be one of:

    • Above: A sibling exists above this item.
    • Below: A sibling exists below this item.
    • AboveAndBelow: Siblings exist both above and below.
    • NotFound: No sibling exists at this level.

    You can access and modify sibling locations at specific levels using indexing (e.g., adjacency[level]).

    // Accessing sibling location at a specific level
    let location = adjacency[1]; 
    
    // Modifying sibling location
    adjacency[2] = SiblingLocation::Below;
  8. Enable progress logging with the `progress-tree-log` feature

    main

    When the progress-tree-log feature is enabled, most calls to progress will also be logged. This allows progress messages to be visible even without a Terminal User Interface (TUI) active.

    Warning: Do not log to stdout while the TUI is active and this feature is enabled, as it will interfere with the TUI rendering.

  9. How Prodash works

    main

    Prodash is a dashboard designed to display the progress of concurrent applications. It is composed of two primary components:

    1. Tree: A structure used to gather progress information and messages. While not inherently async, it is designed to be non-blocking and transparent in terms of performance.
    2. Terminal User Interface (TUI): A visual component that displays the gathered information, including optional free-form information provided by the application.

    To use the TUI, ensure the render-tui feature is enabled (it is enabled by default).

  10. Configure prodash via Cargo features

    main

    prodash uses Cargo features to allow users to tailor the library to their specific needs (e.g., choosing a terminal backend or enabling specific unit formatting).

    Core Rendering Features

    render-tui (Terminal User Interface)

    Provides a full-screen TUI visualizing the entire progress state. It treats the terminal as a matrix display and supports keyboard controls and dynamic resizing. Requires one of the following (mutually exclusive):

    • render-tui-crossterm: Uses the crossterm backend. Works natively everywhere but has more dependencies. (Example: cargo build --features render-tui-crossterm,crossterm/event-stream)
    • render-tui-termion: Uses the termion backend. Leaner, but works only on unix systems.

    render-line (Line-based Renderer)

    A minimal, low-dependency renderer that displays progress in a single line (or subset of the hierarchy). It supports clicolors and no-color specs and can include an initial delay to show progress only when needed. Requires one of the following (mutually exclusive):

    • render-line-crossterm: Uses the crossterm backend (useful for Windows).
    • render-line-termion: Uses the termion backend (useful for lean Unix-only builds).

    Optional render-line features:

    • render-line-autoconfigure: Automatically configures display based on terminal presence and color support via render::line::Options::auto_configure().
    • signal-hook: Automatically handles SIG_INT and SIG_TERM to reset the cursor if hide_cursor is enabled. Requires an extra thread and dependencies.

    Progress and Logging

    • progress-tree (default): Provides Progress and Root trait implementations using dashmap for render-line and render-tui.
      • progress-tree-hp-hashmap: High-performance registry for ultra-heavy insertions/deletions.
      • progress-tree-log: Redirects tree::Item::message(...) calls to the log crate instead of writing progress.
    • progress-log: A Progress implementation that logs messages and progress using the log crate.

    Unit Formatting

    • unit-bytes: Supports dynamic byte display (via bytesize).
    • unit-human: Displays counts in human-readable formats (via human_format).
    • unit-duration: Displays durations in a friendly format like _5m4s_ (via jiff).

    Other

    • local-time: Uses local time instead of UTC for timestamps in render-tui or render-line message panes.
  11. How progress hierarchy keys work

    main

    A Key represents a specific position within a hierarchical tree of tasks. It is composed of up to 6 levels of nesting, where each level is identified by a unique Id (a u16).

    To build a path for a child task, use the add_child method on an existing Key. This creates a new Key with the child's ID at the next nesting level.

    Important Limitations:

    • Maximum Depth: The maximum nesting level is 6. If you attempt to add a child beyond this depth, the system will warn you and add the task to the current parent level instead.
    • Sibling Capacity: There is a practical limit of $2^{16}$ tasks at any single level. Exceeding this may cause unexpected behavior as multiple progress handles might attempt to manipulate the same state.
    // Example of building a hierarchical key
    let root = Key::default();
    let level1 = root.add_child(101);
    let level2 = level1.add_child(202);
    
    assert_eq!(level2.level(), 2);
  12. Manage progress using the Root and Item tree structure

    main

    The progress tree is organized starting from a Root. You can use the tree to create a hierarchy of tasks, where each task (represented by an Item) can have its own progress state, messages, and child tasks.

    To use the tree:

    1. Initialize a Root.
    2. Add children to the root or existing items using add_child or add_child_with_id.
    3. Use the returned progress handle to call init, set, done, or fail to update the task's status.

    This allows for complex, nested progress reporting where sub-tasks can be tracked independently under a parent task.

    let tree = prodash::tree::Root::new();
    let mut progress = tree.add_child("task 1");
    
    // Initialize with total steps and an optional label
    progress.init(Some(10), Some("elements".into()));
    
    // Update progress
    for p in 0..10 {
        progress.set(p);
    }
    
    // Mark task as completed
    progress.done("great success");
    
    // Add a sub-task with a specific ID
    let mut sub_progress = progress.add_child_with_id("sub-task 1", *b"TSK2");
    sub_progress.init(None, None);
    sub_progress.set(5);
    sub_progress.fail("couldn't finish");