Created
June 11, 2012 13:58
-
-
Save thijzert/2910214 to your computer and use it in GitHub Desktop.
A PHP progress bar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
require_once( "termcolours.inc" ); | |
class ProgressBar | |
{ | |
protected $ticktime = false; | |
protected $exp; | |
protected $xsquare; | |
protected $count = 0; | |
protected $max = 100; | |
protected $lmax = 0; | |
protected $variance = 0; | |
protected $line_size = 80; | |
protected $line_pos = 0; | |
public function __construct( $max = 100 ) | |
{ | |
$this->reset( $max ); | |
} | |
public function reset( $max = 100 ) | |
{ | |
$this->max = $max; | |
$this->lmax = (floor(log10($max))+1); // number of digits in $max; | |
$this->exp = new Expectation(); | |
$this->xsquare = new Expectation(); | |
$this->stop(); | |
$this->start(); | |
} | |
public function start() | |
{ | |
if ( $this->ticktime !== false ) | |
throw new Exception( "Timer already started." ); | |
$this->ticktime = microtime( true ); | |
$this->detect_line_size(); | |
} | |
public function stop() | |
{ | |
if ( $this->line_pos > 0 ) | |
$this->big_tick(); | |
$this->ticktime = false; | |
$this->line_pos = 0; | |
} | |
public function tick() | |
{ | |
$this->count++; | |
$time = microtime(true) - $this->ticktime; | |
// Update expected value and variance | |
$delta = $this->exp->point( $time ); | |
$this->xsquare->point( $time * $time ); | |
if ( $this->variance <= 0 ) | |
print( "." ); | |
elseif ( $delta < -1*($this->variance) ) | |
print( green(".") ); | |
elseif ( $delta < $this->variance ) | |
print( "." ); | |
elseif ( $delta < 2*$this->variance ) | |
print( yellow(".") ); | |
else | |
print( bred(".") ); | |
$this->line_pos++; | |
if ( $this->line_pos == $this->line_size ) | |
$this->big_tick(); | |
$this->ticktime = microtime(true); | |
} | |
protected function detect_line_size() | |
{ | |
@list($height,$width) = explode(" ",shell_exec("stty size 2>/dev/null")); | |
if ( !$width || $width < 40 ) | |
$width = 80; | |
$infobox_size = 2 + // Left spacing | |
1 + // [ | |
(2 * $this->lmax) + 1 + // "123/456" progress indicator | |
1 + // a space | |
6 + // "(100%)" progress indicator | |
1 + // another space | |
4 + (2+1+2+1) + // "ETA 10m24s" progress indicator | |
// 1 + // yet another space | |
1 + // ] | |
1; // Right spacing | |
$this->line_size = $width - $infobox_size; | |
} | |
protected function big_tick() | |
{ | |
$exp = $this->exp->exp(); | |
$this->variance = sqrt( ($this->xsquare->exp()) - ($exp*$exp) ); | |
print( str_repeat(" ",($this->line_size - $this->line_pos)) ); | |
print( " [" ); | |
print( str_pad( "{$this->count}/{$this->max}", (2 * $this->lmax) + 1, " ", STR_PAD_LEFT ) . " " ); | |
$perc = floor( 100 * ($this->count / $this->max) ); | |
print( str_pad( "({$perc}%)", 6, " ", STR_PAD_LEFT ) . " " ); | |
print( "ETA " . $this->format_time($this->ETR()) ); | |
print( "]\n" ); | |
$this->line_pos = 0; | |
} | |
/** | |
* Estimate Time Remaining (in seconds) | |
* | |
* Calculates a pessimistic average of the average time between ticks, and | |
* multiplies that time by the number of ticks remaining. | |
**/ | |
protected function ETR() | |
{ | |
$count = $this->max - $this->count; | |
if ( $count <= 0 ) return 0; | |
return $count * ( $this->exp->exp() + sqrt($this->xsquare->exp()) ); | |
} | |
protected function format_time( $time ) | |
{ | |
if ( $time < 3600 ) | |
{ | |
$s = str_pad( floor($time % 60), 2, "0", STR_PAD_LEFT ); | |
$m = str_pad( floor($time / 60), 2, " ", STR_PAD_LEFT ); | |
return "{$m}m{$s}s"; | |
} | |
if ( $time < 86400 ) | |
{ | |
$m = str_pad( round(($time % 3600)/60), 2, "0", STR_PAD_LEFT ); | |
$h = floor($time / 3600); | |
if ( $m >= "60" ) | |
{ | |
$m = "00"; | |
$h++; | |
} | |
$h = str_pad( $h, 2, " ", STR_PAD_LEFT ); | |
return "{$h}h{$m}m"; | |
} | |
if ( $time < 86400*30 ) | |
{ | |
$h = str_pad( round(($time % 86400)/3600), 2, "0", STR_PAD_LEFT ); | |
$d = floor($time / 86400); | |
if ( $h >= "24" ) | |
{ | |
$h = "00"; | |
$d++; | |
} | |
$d = str_pad( $d, 2, " ", STR_PAD_LEFT ); | |
return "{$d}d{$h}h"; | |
} | |
if ( $time < 86400*365 ) | |
{ | |
$d = str_pad( round(($time % 86400*30)/86400), 2, "0", STR_PAD_LEFT ); | |
$m = floor($time / 86400*30); | |
if ( $d >= "30" ) | |
{ | |
$d = "00"; | |
$m++; | |
} | |
$m = str_pad( $d, 2, " ", STR_PAD_LEFT ); | |
return "{$m}m{$d}d"; | |
} | |
$m = str_pad( round(($time % 86400*365)/86400*30), 2, "0", STR_PAD_LEFT ); | |
$y = floor($time / 86400*365); | |
if ( $m >= 12 ) | |
{ | |
$m = "00"; | |
$y++; | |
} | |
$y = str_pad( $d, 2, " ", STR_PAD_LEFT ); | |
return "{$y}y{$d}d"; | |
} | |
} | |
class Expectation | |
{ | |
private $count = 0; | |
private $sum = 0; | |
public function point( $p ) | |
{ | |
$this->count++; | |
$this->sum += $p; | |
return $p - $this->exp(); | |
} | |
public function exp() | |
{ | |
return $this->sum / $this->count; | |
} | |
} | |
if ( basename(@$argv[0]) == basename(__FILE__) ) | |
{ | |
$max = mt_rand( 150, 500 ); | |
$pb = new ProgressBar( $max ); | |
require_once( "stats.inc" ); | |
$av = pow( 1.25, mt_rand( 40, 61 ) ); | |
$sd = $av * 0.15; | |
for ( $i = 0; $i < $max; $i++ ) | |
{ | |
usleep( St::normal( $av, $sd ) ); | |
$pb->tick(); | |
} | |
$pb->stop(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment