The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

ProgressBar::Stack - Progress bar implementation with stack support and useful loop wrappers

SYNOPSIS

    use ProgressBar::Stack;

    init_progress;
    sleep(1);
    update_progress 20;
    sleep(2);
    update_progress 60;
    sleep(2);
    update_progress 100;
    print "\n";

    init_progress(message => "Calculating");
    my $sum = 0;
    for_progress {
        $sum+=$_;
        sleep(1);
    } 0..10;
    print "\nSum = $sum\n";

DESCRIPTION

ProgressBar::Stack creates a convenient framework for adding progress bars to long processes. Sometimes you have long process which consists of several subprocesses, some of which have cycles (including nested ones), some called several times and so on. If you want to display continuous progress bar from 0% to 100% for such complex process, you will have bad times calculating current percentage for each subprocess. ProgressBar::Stack does much of dirty work for you.

Note that ProgressBar::Stack provides only simple console renderer of current progress. If you want to use it in some GUI application, you should write your own renderer and pass it to init_progress (see below).

There are two interfaces provided: one is object-oriented, the other is not. Non-OO interface actually creates single object and delegates all calls to it. Practically using non-OO interface is enough in many cases, especially taking into account that different threads will have independent progress bars, but for some windowed applications several progress bars might be necessary.

Non-OO interface

All functions of non-OO interface are exported by default.

init_progress %parameters

Initializes progress bar and updates it to 0%. Parameters (all optional) include:

message

Default message describing the action performed. This will be passed to renderer and displayed to the user. Can be overridden later by update_progress calls.

Default value: empty string.

count

Maximum value for your progress bar. This takes effect when you call update_progress or sub_progress. Example:

    init_progress(count => 2);
    sleep(1);
    update_progress(1);  # means half of process is done
    sleep(2);
    update_progress(2);  # means whole process is done

Default value: 100.

Actually it's better not to use this parameter at all always scaling your progress bar from 0 to 100.

renderer

Subroutine to be called when progress bar should be updated. Note that calling update_progress doesn't mean this renderer will be called for sure. update_progress may suppress calls to the renderer in order not to update progress bar too fast.

renderer receives three parameters: $value, $message and $progress. $value is float value between 0 and 100 (regardless of count parameter) which represents current progress. $message is supplementary message describing current action. $progress is progress bar object, which you can use to access some advanced parameters. For example if you want to calculate estimated time, you can use $progress->{starttime} to get time when the process started. See also running_time, remaining_time and total_time.

Default renderer provides simple console output like this:

    [#####               ] 25.0% ETA: 0:05 Message
minupdatetime

Time in seconds during which updates of progress bar (renderer calls) are disabled unless message changed, progress bar changed more than forceupdatevalue (see below) or reached 100%.

Default value is 0.1.

minupdatevalue

Progress bar update will be disabled if difference between current and previous value less than this parameter unless message changed or progress bar reached 100%.

Default value is 0.1.

forceupdatevalue

Progress bar update will be enabled if difference between current and previous value exceeds this parameter even if minupdatetime haven't passed yet.

Default value is 1.

update_progress VALUE, MESSAGE
update_progress VALUE
update_progress

Inform progress bar that it should be updated to value VALUE and message should be changed to MESSAGE. If MESSAGE is omitted, last message on current stack level will be used:

    init_progress;
    update_progress 0, "Outside";
    sleep 1;
    update_progress 20;  # "Outside" message will be used
    sleep 1;
    sub_progress {
        update_progress 0, "Inside";
        sleep 1;
        update_progress 50; # "Inside" message will be used
        sleep 1;
    } 70;
    sleep 1;
    update_progress 80;  # "Outside" message will be used again

If VALUE is omitted, then maximal value will be used (specified by count in init_progress, 100 by default). Progress bar will be updated for sure if it reached 100% or message changed since last time. Otherwise actual update (call to renderer) may not be performed depending on minupdatetime, minupdatevalue and forceupdatevalue parameters (see init_progress).

sub_progress BLOCK, VALUE

Pushes current progress bar range and message to the stack, shortens range to [curvalue, VALUE] (where curvalue determined by the latest update_progress call), evaluates block, calls update_progress and pops current state back. This function lets you defining subprocesses, inside which you can use whole range [0, 100] in update_progress calls as for top-level process. Example:

    init_progress;
    # This subprocess uses [0, 50] progress bar range
    sub_progress {
        sleep 2;
        # 20% will be displayed, because we're inside subprocess
        update_progress 40;
        sleep 2;
        # 40% will be displayed, because we're inside subprocess
        update_progress 80;
        sleep 1;
        # note that at the end of subprocess update_progress
        # is called automatically, thus 50% will be displayed
    } 50;
    # This subprocess uses [50, 100] progress bar range
    sub_progress {
        sleep 1;
        # 75%
        update_progress 50;
        sleep 1;
        # 100% will be displayed automatically
    } 100;

In general any call of function, which works long enough to update progress by its own, should be wrapped into sub_progress, because function should not care whether it's top-level process or part of any subprocess:

    # Pass of some long process
    sub pass() {
        update_progress 0, "Performing pass";
        sleep(1);
        update_progress 50;
        sleep(1);
        update_progress 100; # just for the case it's top-level process
    }
    # Process consisting of two passes:
    init_progress;
    sub_progress {pass} 50; # will display 25%, then 50%
    sub_progress {pass} 100; # will display 75%, then 100%

Of course sub_progress can be unlimitedly nested. Example:

    init_progress;
    sub_progress {
        sub_progress {
            update_progress 0, "First step of first step";
            sleep(1);
            update_progress 50; # 10% displayed
            sleep(1);
        } 40;
        sub_progress {
            update_progress 0, "Last step of first step";
            sleep(1);
            update_progress 50; # 35% displayed
            sleep(1);
        } 100
    } 50;
    sub_progress {
        update_progress 0, "Last step";
        sleep(1);
        update_progress 50; # 75% displayed
        sleep(1);
    } 100;

If BLOCK returns value, it will be returned by sub_progress.

for_progress BLOCK, LIST

Evaluates BLOCK for each element from LIST, loading its elements consequently into $_. For each iteration sub_progress is called reducing the progress bar range to appropriate part assuming that each iteration takes the same time. At the end of iteration update_progress is called automatically. You can use next and last as in normal for cycle. Example:

    init_progress;
    for_progress {
        sleep 1;
    } 1..10;

In this example progress bar will display 10%, 20% and so on till 100%.

Inside BLOCK you can call update_progress changing VALUE from 0 to 100, which represents progress of current iteration:

    init_progress;
    for_progress {
        update_progress(0, "Processing $_");
        sleep 1;
        update_progress(50, "Processing $_");
        sleep 1;
    } qw(Banana Apple Pear Grapes);

You will see the following sequence of progress bar updates:

        [                    ]   0.0% ETA: ?:?? Processing Banana
        [##                  ]  12.5% ETA: 0:06 Processing Banana
        [#####               ]  25.0% ETA: 0:05 Processing Banana
        [#####               ]  25.0% ETA: 0:05 Processing Apple
        [#######             ]  37.5% ETA: 0:04 Processing Apple
        [##########          ]  50.0% ETA: 0:03 Processing Apple
        [##########          ]  50.0% ETA: 0:03 Processing Pear
        [############        ]  62.5% ETA: 0:03 Processing Pear
        [###############     ]  75.0% ETA: 0:02 Processing Pear
        [###############     ]  75.0% ETA: 0:02 Processing Grapes
        [#################   ]  87.5% ETA: 0:01 Processing Grapes
        [####################] 100.0% ETA: 0:00 Processing Grapes

Of course nested loops work fine also:

    init_progress;
    for_progress {
        for_progress {
            sleep 1;
        } 1..$_;
    } 1..5;

Note that this progress bar will become slower to the end as for_progress assumes each iteration takes the same time, but latter iterations of outer for_progress are obviously slower.

map_progress BLOCK, LIST

Similar to for_progress but works like map returning list of processed elements:

    init_progress();
    my @lengths = map_progress {
        sleep(1);
        length($_);
    } qw(Banana Apple Pear Grapes);
reduce_progress BLOCK, LIST

Similar to for_progress but works like List::Util::reduce returning accumulated value:

    init_progress(minupdatevalue => 1);
    print "\nSum of cubes from 1 to 1000000 = ".reduce_progress {$a + $b*$b*$b} 1..1000000;

Note that this works much slower than simple List::Util::reduce (about 4-5 times as measured). Thus use carefully in cases when single iteration is very short. You may consider optimizing the process decomposing the loop into two nested ones and using progress for outer only like this:

    use List::Util qw(reduce);
    init_progress;
    print "\nSum of cubes from 1 to 1000000 = ".reduce {$a + $b} 
        map_progress {reduce {$a + $b} map {$_*$_*$_} $_*1000-999..$_*1000} 1..1000;
file_progress BLOCK, FH

Similar to for_progress but reads text file by given filehandle FH line by line. Progress range is based on current offset inside the file and file size. Thus filesize should be known for this filehandle. Example:

    init_progress;
    open(F, "test.txt") || die "$!";
    my $nbytes = 0;
    file_progress {
        $nbytes+=length($_);
        sleep(1);
    } \*F;
    print "\nLength = $nbytes\n";
push_progress START, END

Low-level function to put new progress range into stack. Also the last message is saved there. Generally you shouldn't use it unless you extend capabilities of this module.

pop_progress

Low-level function to remove current progress range from stack, activating previous progress range and message. It will die if you call it on empty stack. Generally you shouldn't use it unless you extend capabilities of this module.

Object-oriented interface

Object-oriented interface is pretty similar to subroutine interface described above. To get the progress bar object, instead of init_progress you should call new ProgressBar::Stack (parameters are the same). All methods of this object are the same as functions above, but without suffix '_progress' in the title (update, sub, for, map, reduce, file, push and pop). Parameters are the same except that first parameter is the object. Thus, one of above examples may be rewritten as following:

    my $p = new ProgressBar::Stack;
    $p->for(sub {
        $p->for(sub {
            sleep 1;
        }, 1..$_);
    }, 1..5);
running_time
remaining_time
total_time

These methods return running time (more precisely, time between new or init_progress call and the latest call of the renderer), estimated remaining time and estimated total time. All times are in seconds, float numbers (Time::HiRes is used internally). These methods have no non-OO counterparts as they should be used inside renderer only where object is always available as third parameter. You may use them like this:

    init_progress(renderer => sub {
        $progress = $_[2];
        print "Remaining time: ".$progress->remaining_time()."\n";
    });

Estimated time calculation simply divides running time by current progress value, so estimation will be incorrect if process speed changes significantly. When estimation cannot be calculated (progress is still at 0%) remaining_time and total_time return -1.

COPYRIGHT

Copyright (c) 2009-2010 Tagir Valeev <lan@nprog.ru>. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.