Long running operations are not necessarily a bad thing, for example to slowly import or export data on the boundary between two systems without causing a huge load on one of them. Still, you need visibility in their status in order to:

  • have an estimate of when they will finish
  • decide whether to interrupt them
  • be reassured that progress is happening

When you have control over the single steps of a long running operation, such as importing millions of documents inside a database, the progress bar is a classical UX pattern that shows you the total advancement. Another useful pattern is the speedometer, showing you the current speed of your car instead of the total kilometers you have covered:

I am generating 1890000 numbers per second.
I am generating 1880000 numbers per second.
I am generating 1880000 numbers per second.
I am generating 1890000 numbers per second.
I am generating 1880000 numbers per second.
I am generating 1890000 numbers per second.
I am generating 1870000 numbers per second.
I am generating 1880000 numbers per second.

In order to get this feedback, you can easily build a Meter object, frequently updated to show the average speed of a process in the last sampling interval. In this example, the quantity measured is how many random numbers can be generated per second.

<?php
require_once "10_meter_class.php";
$meter = new ThroughputMeter(function($value) {
    echo "I am generating $value numbers per second.", PHP_EOL;
});

$batch = 10000;
for ($i = 0; $i < 10000000; $i++) {
    for ($j = 0; $j < $batch; $j++) {
        $number = rand();
    }
    $meter->newEvent($batch);
}

The class works by receiving updates several times each second (the default interval). In order to avoid the overhead of time-related system calls, the Meter should be updated in batches, for example with newEvent(100) calls instead of with newEvent(1).

<?php

class ThroughputMeter
{
    const INTERVAL = 1;
    private $gauge;
    private $count;
    private $start;
    
    public function __construct(callable $gauge)
    {
        $this->gauge = $gauge;
        $this->restartCountingFrom($this->now());
    }

    /**
     * @param int $howManyJobs
     */
    public function newEvent($howManyJobs)
    {
        $this->count += $howManyJobs;
        $now = $this->now();
        if ($now - $this->start >= self::INTERVAL) {
            call_user_func($this->gauge, $this->count);
            $this->restartCountingFrom($now);
        }
    }

    private function now()
    {
        return microtime(true);
    }

    private function restartCountingFrom($when)
    {
        $this->count = 0;
        $this->start = $when;
    }
}

The class itself measures the current time everytime it is updated, showing the measured throughput only once per each second passed. The operator running this process receives feedback at a rate human eyes can process, instead of a Matrix-like wall of green text constantly moving upward.

 

Giorgio Sironi

Developer at Onebip , Website , Git home page , @giorgiosironi , Linkedin profile
I search for the harmony between form and context, which is a fancy way of saying I build software to fit in the world I'm in and its rapid changes. In the specific, my areas of expertise are testing, OOP design and distributed computing.

All articles by Giorgio Sironi

Comments

comments powered by Disqus

cloudparty

Follow Us