Byron / prodash

report progress of concurrent applications and display it in various ways
https://docs.rs/prodash
MIT License
354 stars 9 forks source link

Simpler dashboard example? #2

Closed Absolucy closed 4 years ago

Absolucy commented 4 years ago

Hi, I've been trying to use this lib, and am finding it hard due to the fact that the dashboard example is overly complex.

I'd just like to know how to make a simple dashboard, without the fancy mock data generation stuff, and I'm just confused

image

Byron commented 4 years ago

What about the units example? It shows how to create bare progress bars without any nesting.

Screenshot 2020-09-13 at 13 54 17
Absolucy commented 4 years ago

Sure, but I'm trying to add nesting.

Perhaps there should just be a static example? Part of the issue with dashboard's readability is that it's half an entire mock data generator

Absolucy commented 4 years ago

Here's my current (non-workring) code:

while let Some(msg) = ws.next().await {
    let tree = prodash::Tree::new();
    for stage in task.stages {
        let mut stage_item = tree.add_child(stage.name.to_string());
        stage_item.init(None, None);
        match stage.status {
            Status::Running => stage_item.message(MessageLevel::Info, "Running"),
            Status::Errored => stage_item.message(MessageLevel::Failure, "Errored"),
            Status::Finished => stage_item.message(MessageLevel::Success, "Finished"),
            Status::Canceled => stage_item.message(MessageLevel::Failure, "Canceled"),
            Status::NotStarted => stage_item.message(MessageLevel::Info, "Not Started"),
        }
        for (name, thread) in stage.threads {
            let mut thread_item = tree.add_child(name.to_string());
            thread_item.init(None, None);
            match thread.status {
                Status::Running => {
                    if thread.state.is_empty() {
                        thread_item.message(MessageLevel::Info, "Running")
                    } else {
                        thread_item.message(MessageLevel::Info, ["Running", &*thread.state].join(" - "));
                    }
                }
                Status::Errored => thread_item.message(MessageLevel::Failure, "Errored"),
                Status::Finished => thread_item.message(MessageLevel::Success, "Finished"),
                Status::Canceled => thread_item.message(MessageLevel::Failure, "Canceled"),
                Status::NotStarted => thread_item.message(MessageLevel::Info, "Not Started"),
            }
        }
    }
    prodash::render::tui::render(
        std::io::stdout(),
        tree.clone(),
        prodash::render::tui::Options {
            title: ["Furnaca", &*task.name].join(" - "),
            ..prodash::render::tui::Options::default()
        },
    )
    .unwrap()
    .await;
Byron commented 4 years ago

Thanks for the example. From what I can see, it tries to use the interface as immediate mode GUI, which it is not. …tui::render() blocks forever, and needs to be run in its own thread of execution, commonly achieved with Task::spawn().

Progress instances can be seen as handles which are used by the code that should receive some introspection. The idea is that this code alters the state of the progress tree, which is periodically displayed by the GUI.

This has the advantage of having high-performance handles for progress which you can call freely without limiting its bandwidth, i.e. relatively hot loops can call Progress::inc() nearly free of cost, lowering the code complexity of the code using the progress mechanism.

If this model doesn't fit, you might be better off using TUI directly - code for drawing progress can probably be pulled from this codebase quite easily.

I hope that helps.

Absolucy commented 4 years ago

My program doesn't even have determinate progress, just "NotStarted/Running/Errored/Canceled"

i just chose this because the example looked exactly like what i had in mind

Byron commented 4 years ago

It doesn't show progress because all progress instances but the root are dropped right away.

Absolucy commented 4 years ago

So how would I do this successfully then? Sorry if I'm being rather dumb

Byron commented 4 years ago

It's more like this:

async fn perform_task(p: Progress) {
   for _ in 0..10 {
     p.inc();
     …some work that takes time or awaits
   }
}

The function takes ownership of the progress instance and performs work, using p to provide progress information. Each of these should be in their own thread of execution, so that they can make progress independently of each other.

Prodash is for concurrent application, and each Progress instance is commonly owned by one thread or task.

If it is supposed to be used in single-threaded applications only one progress bar can proceed at a time, but to the viewer it might still look like it's happening concurrently. In any case, the created progress instances have to be kept and must not go out of scope in order to be displayed.

Absolucy commented 4 years ago

I'm trying to use prodash for a client application, which just receives a struct telling the status of tasks/threads.

But alright, thanks!

Byron commented 4 years ago

At least you want to keep the child progress instances alive, by pushing them onto a Vec for example. The problem will really be the GUI which simply isn't immediate mode, thus it can't abort after a single frame as it's on a timer. Of course you can send it events to shut down, but it will probably feel a bit cumbersome given that what you are doing is more akin of immediate mode rendering.

If you control the client, the solution would be to pass Progress instances and spin up a UI in the background. It's 'intrusive' progress, if you so will.

Absolucy commented 4 years ago

Also, how do you do the nested lists appearance in tui