Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When used in multiple thread, all the progress bars are in one line #11

Closed
chaoyangnz opened this issue Nov 25, 2016 · 23 comments · Fixed by #69
Closed

When used in multiple thread, all the progress bars are in one line #11

chaoyangnz opened this issue Nov 25, 2016 · 23 comments · Fixed by #69
Milestone

Comments

@chaoyangnz
Copy link

I use a progressbar for each thread, but all the progress bars are shown in one single line.

@ctongfei
Copy link
Owner

Could you please clarify? Are you using a progressbar for each thread, and those threads are running in parallel?
If that is the case, then the current version of progressbar does not support that. Writing multiple progressbars to the console simultaneously require tweaking the cursor location in the terminal window, which is an issue that is not yet completely resolved.

@chaoyangnz
Copy link
Author

Yes, correctly. I use multiple threads and one thread one progress bar.

@ctongfei
Copy link
Owner

This is not currently doable right now. Will probably be fixed in a later version.

@Veske
Copy link

Veske commented Mar 3, 2017

Yeah, this would be a quite useful feature for the project.

@ctongfei
Copy link
Owner

ctongfei commented Mar 3, 2017

This involves the manipulation of cursor position in the terminal window which is a bit hard. Will probably be fixed in a later version (1.0?)

@ctongfei ctongfei added this to the 1.0 milestone May 22, 2017
@berlinguyinca
Copy link

any update on this feature?

@ctongfei
Copy link
Owner

It is on the horizon but recently I've got no time to implement this.

@aleck7
Copy link

aleck7 commented Aug 9, 2018

As an option you can output multiple threads in one line, i.e.:
Map 10% [==> ] 644/5855 (0:02:50 / 0:22:55) Reduce 2% [> ] 123/5855 (0:02:50 / 0:22:55)
or even simpler:
Map|Reduce|Save [10%==> |2%> |0% ] 817/17645 (0:02:50 / 0:22:55)

Can elaborate on details of elapsed/estimated time.

@dodalovic
Copy link

@ctongfei Is an instance of ProgressBar thread safe?

@ctongfei
Copy link
Owner

@dodalovic It is thread safe after version 0.5.0.

@o2themar
Copy link

o2themar commented Apr 2, 2020

@ctongfei I already tried to use this in a multi threaded environment and its working for me. It thread is independent of the other and creates its own instant of the progress bar and I haven't had any issues. I was wondering though if it was possible to get the progress bar to print on 1 line rather than it keeps appending to the logger.
image

@o2themar
Copy link

o2themar commented Apr 3, 2020

So now I see what you are talking about. The picture in my previous comment was when I had the progress bar delegate to a logger and I had the logger printing stuff to the console. After removing the consumer I now see the progress bar overwriting each other on one line. However they create a new line when they update. I also see some repeating going on the right. Not sure what that is all about. Look at the image below for more detail.
image

@ctongfei
Copy link
Owner

ctongfei commented Apr 3, 2020

@o2themar The planned solution is to have a multi-line progressbar, where each line prints the progress of one thread. This is hard to implement --- sorry for not supporting this. I'll consider implementing this when I'm not busy, or you could start a PR!

@vehovsky
Copy link
Contributor

vehovsky commented Apr 3, 2020

The problem with current implementation is simple actually

ConsoleProgressBarConsumer

out.print('\r'); // before update
out.print(str);

What print('\r') does is that it moves the cursor to the beginning of the line. And then the line is rewritten.

Based on ANSI/VT100 Terminal Control Escape Sequences to implement multiline progressbar you would have to be moving the cursor up and down to rewrite the corresponding line in the output.

Something like (over simplified obviously):

import org.jline.terminal.TerminalBuilder

class TerminalPrinter(initialText: String) {

    private val position = currentCursorPosition()

    init {
        if (System.console() == null) throw RuntimeException("non-interactive console")
        System.err.print('\r')
        System.err.println(initialText)
    }

    suspend fun rewrite(str: String) {
        delay(1000)
        val count = currentCursorPosition() - position
        moveCursorUp(count)
        replaceLine(str)
        moveCursorDown(count)
    }

    private fun moveCursorUp(count: Int) {
        System.err.print("\u001b[${count}A")
    }

    private fun moveCursorDown(count: Int) {
        System.err.print("\u001b[${count}B")
    }

    private fun replaceLine(str: String) {
        System.err.print('\r')
        System.err.print(str)
        System.err.print('\r')
    }

    private fun currentCursorPosition(): Int {
        val terminal = TerminalBuilder.builder()
            .jna(true)
            .system(true)
            .build()
        terminal.use {
            terminal.enterRawMode()
            System.err.print("\u001b[6n")
            val reader = terminal.reader()
            reader.use {
                val buf = CharArray(7)
                System.`in`.bufferedReader().read(buf)
                val position = buf
                    .dropWhile { it != '[' }
                    .drop(1)
                    .takeWhile { it != ';' }
                    .joinToString("")
                return position.toInt()
            }
        }
    }
}

fun main() = runBlocking {
    val a = TerminalPrinter("[a] 1")
    val b = TerminalPrinter("[b] 1")
    val c = TerminalPrinter("[c] 1")
    val d = TerminalPrinter("[d] 1")

    System.out.println("some log...")
    a.rewrite("[a] 2")
    c.rewrite("[c] 2")
    b.rewrite("[b] 2")
    val e = TerminalPrinter("[e] 1")
    d.rewrite("[d] 2")
    e.rewrite("[e] 2")

    System.out.println("more log...")

    a.rewrite("[a] 3")
    e.rewrite("[e] 3")
    b.rewrite("[b] 3")
    d.rewrite("[d] 3")
    c.rewrite("[c] 3")
}

@vslee
Copy link

vslee commented Apr 4, 2020

@vehovsky, can you send a pull request?

@vehovsky
Copy link
Contributor

vehovsky commented Apr 4, 2020

@vslee Not really, the above example works well on osx (linux as well probably) but not on windows. Moving the cursor just up/down (without asking terminal for cursor position) would probably work on windows as well, but that is just way too naive implementation and would break a minute some other thread wrote anything to console.

@ctongfei
Copy link
Owner

ctongfei commented Apr 4, 2020

@vehovsky I understand these ANSI control code (and will probably have to be tested under many different envs, and use jline to actually check the capabilities of the terminal). Yes this is a planned feature for the next release (But I don't know when that will be).

@vehovsky
Copy link
Contributor

vehovsky commented Apr 6, 2020

I've prepared some initial implementation. Will create PR (after work) to start discussion about implementation details.

@vehovsky
Copy link
Contributor

vehovsky commented Apr 9, 2020

@o2themar

was wondering though if it was possible to get the progress bar to print on 1 line rather than it keeps appending to the logger.
image

Yes it is possible. Not supported out of the box thought.

So to summarise the problem here. Logger will use defined pattern, lets use simple logback pattern as example:

[%d{YYYY-MM-dd HH:mm:ss.SSS}]%msg%n

Basically here our log message (%msg) will be prefixed with timestamp and suffixed with new line.

The problem is that potentially the control codes to move cursor are in the message (in the middle of your pattern). Also I said potentially, because DelegatingProgressBarConsumer does not send any.

So you would first have to write your own consumer. Something like:

public class CustomDelegatingProgressBarConsumer implements ProgressBarConsumer {

    private final Character MOVE_CURSOR_TO_LINE_START = '\r';
    private final int maxProgressLength;
    private final Consumer<String> consumer;

    public CustomDelegatingProgressBarConsumer(Consumer<String> consumer, int maxProgressLength) {
        this.maxProgressLength = maxProgressLength;
        this.consumer = consumer;
    }

    @Override
    public int getMaxProgressLength() {
        return maxProgressLength;
    }

    @Override
    public void accept(String str) {
        this.consumer.accept(MOVE_CURSOR_TO_LINE_START + str);
    }

    @Override
    public void close() {
        this.consumer.accept(System.lineSeparator());
    }
}

Then you would have to write converter of some kind, logging framework dependent, here is example for logback:

class InteractiveConsoleConverter : CompositeConverter<ILoggingEvent>() {
    private val MOVE_CURSOR_TO_LINE_START = '\r'

    override fun transform(event: ILoggingEvent, input: String): String {
        if (input.contains(MOVE_CURSOR_TO_LINE_START)) {
            return MOVE_CURSOR_TO_LINE_START + input.replaceFirst(MOVE_CURSOR_TO_LINE_START, '\u0000').replace(System.lineSeparator(), "")
        }
        return event.message
    }
}

and wrap your wole pattern with something like:

%interactiveConsol([%d{YYYY-MM-dd HH:mm:ss.SSS}]%msg%n)

And yes, that actually works.

@o2themar
Copy link

o2themar commented Apr 14, 2020

@vehovsky Does the pull request that you created not require this work around? Or even with the new version I would still need to implement my own consumer?

@vehovsky
Copy link
Contributor

@o2themar short answer, no.

The pull requests ensures multiple progress bars can be displayed simultaneously, when supported by terminal in which you have started the JVM.

When the terminal does not support moving cursor, now it at least ensures all "done" progressbars are displayed correctly. The "active" progressbars are re-writing each others at the line where cursos currently is (last line).

So you may want to create another issue for enhancing loggers integration.

@o2themar
Copy link

ok thanks @vehovsky I'll go with your suggestion for now about implementing a consumer to support the logging. I was just making sure that the new implantation didn't already cover this and you were suggestion a work around for now. Its not a big deal having to implement a consumer for the logging and I appreciate you sharing the code to help get me started. Thank you for your help in quickly getting a solution.

@ctongfei ctongfei modified the milestones: 1.0, 0.9 Apr 24, 2020
@ctongfei
Copy link
Owner

Fixed in 0.9.0 with @vehovsky 's fantastic addition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment