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

NAME

ProgressMonitor - a flexible and configurable framework for providing feedback on how a long-running task is proceeding.

VERSION

Version 0.31

SYNOPSIS

    use strict;
    use warnings;

    use Time::HiRes qw(usleep);

    use ProgressMonitor::Stringify::ToStream;
    use ProgressMonitor::Stringify::Fields::Bar;
    use ProgressMonitor::Stringify::Fields::Fixed;
    use ProgressMonitor::Stringify::Fields::Percentage;

    sub someTask
    {
        my $monitor = shift;

        $monitor->prepare();
        $monitor->begin(100);
        for (1 .. 40)
        {
            usleep(100_000);
            $monitor->tick(1);
        }

        anotherTask($monitor->subMonitor({parentTicks => 20}));

        for (1 .. 40)
        {
            usleep(100_000);
            $monitor->tick(1);
        }
    
        $monitor->end();
    }

    sub anotherTask
    {
        my $monitor = shift;
    
        $monitor->prepare();
        $monitor->begin(3000);

        for (1 .. 3000)
        {
            usleep(1_000);
            $monitor->tick(1);
        }
    
        $monitor->end();
    }

    someTask(
                ProgressMonitor::Stringify::ToStream->new(
                        {
                                fields =>
                    [
                        ProgressMonitor::Stringify::Fields::Bar->new,
                        ProgressMonitor::Stringify::Fields::Fixed->new,
                        ProgressMonitor::Stringify::Fields::Percentage->new,
                    ]
                        }
                )
        );

DESCRIPTION

The above synopsis shows it in a nutshell - cut and paste and try it. Or, peruse the examples in the examples/ directory.

BACKGROUND

This is one more implementation of the idea of making code report progress in what it's doing, and, typically, use this reporting to give feedback to a user on how far we've come and/or how much there is left to do. There are other Perl modules for this, but this is bigger and better :-).

The rationale for this module was twofold: first, I needed a reasonable-sized excuse to try out Rob Muhlestein's (RMUHLE) 'classes' pragma as preparation for a later, bigger project. Second, I like IProgressMonitor, a Java interface in the Eclipse project (more information in "SEE ALSO" below) that I've dealt with and thought the basic ideas around that could be put to good use in Perl code also. There are some differences from IProgressMonitor though, if you end up comparing them.

CONCEPT

An overall principle of a feedback mechanism is that the code doing progress really has no idea as to how (if at all) any of the progress it reports actually result in any feedback, or, more importantly, what form such feedback takes. Thus, this package only provides an abstract interface for progressee's to call into, and this has to be done in a clearly defined manner so as to ensure that any feedback will indicate the right things at the right time. The object of all this is to keep the user informed: 'cool your heels - maybe it's taking a long time but I *am* working!'.

Thus, any code that has no UI of its own should, in the best of worlds, accept an instance of the ProgressMonitor interface, and report its progress through that. Ideally, an entire framework should at all appropriate places be able to make use of a monitor in order to allow the most granular feedback possible.

The other side of the coin is the code that does have an UI and thus knows how feedback should be shown. This code should instantiate a monitor of the correct type and pass it in to the method that reports progress.

There is a middle ground however: a progresse may have need to use lower level functions and they might also be able to use a monitor. In such a case, the higher level progressee should instantiate a SubTask monitor. This special monitor type will pass on information to the parent monitor and cause feedback to be correctly scaled. It is important to never pass on the monitor you have been given to someone else!

The ProgressMonitor 'contract'

So, being a progressee, you get a monitor instance. What do you do?

PREPARING YOURSELF

The first call you should do as soon as possible is to call the 'prepare' method. This tells the monitor that you are in prepare mode, and this means that you now spend your time figuring out how many 'things' you will need to do. While doing this you should as regularly as possible call the 'tick' method (any arguments to tick will be ignored in prepare mode). This will, depending on feedback mechanism, trigger some visible indication of 'work in progress'.

This step is actually optional - maybe you already know how much work you need to do. Or, also common, you really don't know, and it may be either impossible to figure out, or it may be prohibitive to calculate. In this case, you can go straight to calling 'begin'.

BEING ACTIVE

Having called the begin method, you're saying "I'm now actively working with the task".

The begin method takes an optional parameter, an integer. The significance is that if you don't pass anything, you're saying 'the extent of this work is unknown, you'll just have to wait...I will call back once in a while to ensure you see me working'. Some feedback presentations are better at portraying this situation than others - for example, a character changing shape for each call will give a good view of this.

Passing a number however, constitutes a promise; 'I will call back exactly this number of times' (there's no implication of time between calls though). Again, some presentations look better than others in this situation - specifically, presentations that can show "I'm now here, and so much remains" will then give a fairly clear picture. A typical presentation is the percentage - '85 %' clearly shows that it's almost finished.

So, in either of the above cases you should call the 'tick' method. If the total is unknown, each tick simply increments the internal counter by one (i.e. any argument to tick is ignored). If the total is unknown however, you should call with an integer argument. Actually, the integer may be 0 in which case presentations sees it as an 'idle' tick but still may do something visually interesting. Any other integer is simply added to the current count - but beware of calling tick with a number that causes the total to be greater than your promise; this is an error. In any event, this gives the clearest signal to presentations, one which they typically use to calculate amount done vs amount remaining, and then render this in the best way.

While 'ticking' is the primary way of informing the user, sometimes it makes sense not only saying "I'm active", but also saying 'I'm currently doing this', i.e. a straightforward message. Messages is a sort of out-of-band communication in regards to ticks. Depending on how the monitor was set up, they may be ignored altogether, written using newlines 'beside' the tick, or perhaps overlaying the tick field(s) (all or in part) - and then automatically time out, restoring the tick fields. Anyway, feel free to give informational messages as needed (but don't assume they'll be seen - just as with ticks, as the monitor in total may be just a black hole).

FINISHING

When all your tasks are complete, you should call the 'end' method. Ideally, you should by now have called tick the right number of times, and thus the final presentation should show (the equivalent to) 100% complete.

The monitor is now unusuable for further calls and should be discarded.

CANCELLATION

Preferably, you should intermittently also call 'isCanceled' on the monitor. A true value signals that you should cancel your work at the earliest convenience - if at all possible. I.e. it is legal to not care about the cancellation status. Only you can decide, but it's very nice to allow users to cancel a task.

SUBTASKS

During your prepare or active phase, you may utilize a lower level method/function to do the overall task. In order to still report progress, you need to wrap your monitor inside a SubTask monitor and pass that on.

It is illegal to pass on your own monitor - this will break as the lower level method should follow the same pattern detailed here - thus, the first thing called will be 'prepare' and since your own monitor is already in the active phase, it could get very confusing indeed!

The pattern here is that you allocate the subtask a certain amount of 'your' progress. Regardless of how many iterations the subtask will do, the progress will be scaled to the parent so that by the time the subtask is 100% complete, it has used up only the allotment it was given.

If you think about it, it's clear that this will work for arbitrarily deeply nested subtasks - as long as all methods accept a monitor and use the pattern described here, they can call each other in any order.

Available monitor and field types

This package provides 4 concrete types that can be used. Two of them are special purpose monitors, and the other two are variations on how to present the feedback as strings.

The last two uses 'field' objects to display in different ways (spinner, percentage, bar etc).

If this is not adequate for you, it's fairly easy to derive new specializations of either complete monitors or field variations.

For each of these, see their respective documentation for details.

MONITOR TYPES

ProgressMonitor::Null

If you wish to skip feedback, you may instantiate a null monitor. It implements the interface and contract but doesn't do anything with the information.

Note: this is commonly used inside monitor accepting methods; if the caller sends no monitor, the code instantiates a null monitor and can then use the monitor interface normally.

ProgressMonitor::SubTask

This is the monitor necessary to wrap a parent monitor for a subtask. This is a concrete implementation and you may use this or call on the monitor for a new suitable instance using 'subMonitor'.

ProgressMonitor::Stringify::ToStream

This monitor type is ideal for simply displaying feedback on stdout, for example. It must be given some field objects which defines the ultimate presentation.

ProgressMonitor::Stringify::ToCallback

This monitor type is useful if you have special needs for displaying, but is still content with a plain string. It acts like ToStream, but instead of printing to the defined stream, it will callback to a code reference you provide.

FIELD TYPES

ProgressMonitor::Stringify::Fields::Bar

This will display a traditional 'bar' that grows from left to right indicating completion amount. If the resolution is too poor to show progress, it will still do something. In cases of an unknown total it will provide a visual indication of movement.

ProgressMonitor::Stringify::Fields::Counter

This will display the common counter, optionally with the total. It will try to show idle progress, but for unknown totals it can only show '?'. It may also overflow if the numbers get to big (settable though, and uncommon mostly).

ProgressMonitor::Stringify::Fields::ETA

This will display an estimated time to completion. When the task is finished, this field will show the actual time it spent (this is very different from other fields as they typically end with the final value, e.g. '100%').

Note that since there is nothing forcing progress to happen at a steady beat, this can be a very unreliable estimate. For many tasks however, progress is regular enough to give a reasonable value.

ProgressMonitor::Stringify::Fields::Fixed

This will display a fixed text. Useful for combining with other fields.

ProgressMonitor::Stringify::Fields::Percentage

This will display a percentage showing completion.

ProgressMonitor::Stringify::Fields::Spinner

This will display a 'moving' character/string only. Totals can't be shown with this.

SEE ALSO

ProgressMonitor is (loosely) based on IProgressMonitor and associated mechanisms in the Eclipse/Java framework. Throw 'IProgressMonitor' into Google and you'll most likely find your way to Eclipse docs around it. Also, this is an article I wrote on how to deal with IProgressMonitor; it is somewhat relevant in this context: http://www.eclipse.org/articles/Article-Progress-Monitors/article.html

AUTHOR

Kenneth Olwing, <knth at cpan.org>

BUGS

I wouldn't be surprised! If you can come up with a minimal test that shows the problem I might be able to take a look. Even better, send me a patch.

Please report any bugs or feature requests to bug-progressmonitor at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ProgressMonitor. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc ProgressMonitor

You can also look for information at:

ACKNOWLEDGEMENTS

Thanks to my family. I'm deeply grateful for you!

Thanks to the Eclipse project for coming up with the IProgressMonitor interface and surrounding mechanisms.

COPYRIGHT & LICENSE

Copyright 2006, 2007, 2008 Kenneth Olwing, all rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.