prodash
repository·main·Indexed 19 days ago
https://github.com/gitoxidelabs/prodashA 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.
What's inside prodash
- 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.
Understand Location placement for progress metadata
mainThe
Locationenum 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).
How hierarchical progress works with NestedProgress
mainNestedProgressis a trait used to describe hierarchical progress trees. It extends the baseProgresstrait 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:
add_child(name): Adds a child with a given name. This child does not have a stable identifier.add_child_with_id(name, id): Adds a child with a given name and a specificId, allowing it to be identified later.
If you need to store progress objects dynamically (e.g., in a
Vec), you can useDynNestedProgressandBoxedDynNestedProgressto 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);How the progress tree structure works
mainThe progress tree is composed of a
Rootwhich contains anItem. AnItemrepresents a node in the hierarchy and contains:- A
keyidentifying the item. - A
valuerepresenting the currentStepSharedstate. - A
tree(a thread-safe map) containing childTaskobjects. - A
messagesring 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.
- A
Track incremental updates with MessageCopyState
mainWhen using
MessageRingBuffer::copy_new, you must capture and store the returnedMessageCopyState. This state contains thecursor,buf_len, andtotalcount 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_newagain, you may lose messages. If the buffer wraps around completely,copy_newwill fallback tocopy_allto ensure you get the most recent data.Set TUI shutdown behavior with `Interrupt`
mainThe
Interruptenum determines how the TUI event loop responds to interrupt requests (likeCtrl+CorEsc).Interrupt::Instantly: The default mode. The GUI exits immediately upon receiving an interrupt.Interrupt::Deferred: The GUI will wait until the nextInterrupt::Instantlyevent 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?;Understand Adjacency and SiblingLocation
mainThe
Adjacencystruct provides information about the relative position of a task within its hierarchy, specifically identifying if siblings existAboveorBelowthe current item at various levels.SiblingLocationcan 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;Enable progress logging with the `progress-tree-log` feature
mainWhen the
progress-tree-logfeature is enabled, most calls toprogresswill also be logged. This allows progress messages to be visible even without a Terminal User Interface (TUI) active.Warning: Do not log to
stdoutwhile the TUI is active and this feature is enabled, as it will interfere with the TUI rendering.How Prodash works
mainProdash is a dashboard designed to display the progress of concurrent applications. It is composed of two primary components:
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.- 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-tuifeature is enabled (it is enabled by default).Configure prodash via Cargo features
mainprodash 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 thecrosstermbackend. Works natively everywhere but has more dependencies. (Example:cargo build --features render-tui-crossterm,crossterm/event-stream)render-tui-termion: Uses thetermionbackend. Leaner, but works only onunixsystems.
render-line(Line-based Renderer)A minimal, low-dependency renderer that displays progress in a single line (or subset of the hierarchy). It supports
clicolorsandno-colorspecs and can include an initial delay to show progress only when needed. Requires one of the following (mutually exclusive):render-line-crossterm: Uses thecrosstermbackend (useful for Windows).render-line-termion: Uses thetermionbackend (useful for lean Unix-only builds).
Optional
render-linefeatures:render-line-autoconfigure: Automatically configures display based on terminal presence and color support viarender::line::Options::auto_configure().signal-hook: Automatically handlesSIG_INTandSIG_TERMto reset the cursor ifhide_cursoris enabled. Requires an extra thread and dependencies.
Progress and Logging
progress-tree(default): ProvidesProgressandRoottrait implementations usingdashmapforrender-lineandrender-tui.progress-tree-hp-hashmap: High-performance registry for ultra-heavy insertions/deletions.progress-tree-log: Redirectstree::Item::message(...)calls to thelogcrate instead of writing progress.
progress-log: AProgressimplementation that logs messages and progress using thelogcrate.
Unit Formatting
unit-bytes: Supports dynamic byte display (viabytesize).unit-human: Displays counts in human-readable formats (viahuman_format).unit-duration: Displays durations in a friendly format like_5m4s_(viajiff).
Other
local-time: Uses local time instead of UTC for timestamps inrender-tuiorrender-linemessage panes.
How progress hierarchy keys work
mainA
Keyrepresents 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 uniqueId(au16).To build a path for a child task, use the
add_childmethod on an existingKey. This creates a newKeywith 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);Manage progress using the Root and Item tree structure
mainThe progress tree is organized starting from a
Root. You can use the tree to create a hierarchy of tasks, where each task (represented by anItem) can have its own progress state, messages, and child tasks.To use the tree:
- Initialize a
Root. - Add children to the root or existing items using
add_childoradd_child_with_id. - Use the returned progress handle to call
init,set,done, orfailto 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");- Initialize a