Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
75.00% covered (success)
75.00%
6 / 8
CRAP
55.79% covered (warning)
55.79%
53 / 95
iaTimer
0.00% covered (danger)
0.00%
0 / 1
75.00% covered (success)
75.00%
6 / 8
155.30
55.79% covered (warning)
55.79%
53 / 95
 start
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
16 / 16
 clear
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 end
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
13 / 13
 report
n/a
0 / 0
1
n/a
0 / 0
 table
n/a
0 / 0
8
n/a
0 / 0
 getTimers
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
8 / 8
 cssClases
n/a
0 / 0
1
n/a
0 / 0
 requestStart
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
1 / 1
 microtimeFormat
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
12 / 12
 report_rusage
0.00% covered (danger)
0.00%
0 / 1
30.00
0.00% covered (danger)
0.00%
0 / 37
 ramUsage
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 5
<?php
/**
 * iaTimer. Calculate & report timing for pieces of code, by label
 * Each label started more than once, simple stats are reported-
 *
 *  iaTimer::start('s1'); ... iaTimer::end('s1'); ... echo iaTimer::report();
 *  Tip init with iaTimer::start("initTimers"); iaTimer::end("initTimers");
 *
 * @package dev_tools
 * @author Informática Asocaida SA de CV
 * @author Raul Jose Santos
 * @version 1.1.2
 * @copyright 2015
 * @license MIT
 */
namespace ia\DevTools;
use ia\Util\Str;
use ia\Util\FormatIt;
use ia\Lib\iaTableIt;
/**
 * iaTimer. Calculate & report timing for pieces of code, by label
 * Each label started more than once, simple stats are reported-
 *
 *  iaTimer::start('s1'); ... iaTimer::end('s1'); ... echo iaTimer::report();
 *  Tip init with iaTimer::start("initTimers"); iaTimer::end("initTimers");
 *
 */
class iaTimer {
    /**
     * @var array saved times keyed by label
     * ie [
     * 'label1'=>[
     *   'n'=>number of observations
     *   'sum'=>'sum of observed lapsed times, total time
     *   'sqSum'=>'sum of squared observred lapsed times'
     *   'note'=>'Advice missing start/end calls'
     *   't0'=>start time, null on end called
     *   'max'=>maximum lapsed time observed
     *   'avg'=>average
     *   'min'=>minimum lapsed time observed
     *   'sdev'=>standard deviation
     * ]
     * , ...
     * ]
     */
    static protected $timer = array();
    /** @var bool true at least one label has multiple runs, show basic stats */
    static protected $haveStats = false;
    /**
     * Indicate start timer named $label.
     *
     * iaTimer::start('s1'); ....; iaTimer::end('section 1');
     *
     * @param string $label identifier for start/stop and report
     * @return void
     */
    public static function start($label) {
        if(!isset(self::$timer[$label])) {
        // First time label is seen, set vars and return
            self::$timer[$label] = array(
                'n'=>0,
                'sum'=>0,
                'sqSum'=>0,
                'note'=>'',
                't0'=>microtime(true),
                'max'=>-1,
                'min'=>1e9,
            );
            return;
        }
        $t = &self::$timer[$label];
        if($t['t0'] !== null) {
            // check if end was called
            $t['note'] = "Missing ".__CLASS__."::end($label), called.";
            self::end($label);
        }
        // Repeated label set new start time
        $t['t0']=microtime(true);
        self::$haveStats = true;
    }
    /**
     * Clears all timers
     *
     * @return void
     */
    public static function clear() {
        self::$timer = array();
        self::$haveStats = false;
    }
    /**
     * Indicate end timer named $label.
     *
     * iaTimer::start('s1'); .... iaTimer::end('section 1');
     *
     * @param string $label identifier for start/stop and report
     * @return number lapsed time for $label
     */
    public static function end($label) {
        if(!isset(self::$timer[$label]) || self::$timer[$label]['t0'] === null) {
            // $label not started, start it
            self::start($label);
            self::$timer[$label]['note'] = "Missing ".__CLASS__."::start($label), using requestStart.";
            self::$timer[$label]['t0'] = self::requestStart();
        }
        // set variables for stats
        $t = &self::$timer[$label];
        $t0 = abs(microtime(true) - $t['t0']);
        // set auxliary vars for stats
        $t['sum'] += $t0;
        $t['sqSum'] += $t0 * $t0;
        $t['min'] = min($t['min'], $t0);
        $t['max'] = max($t['max'], $t0);
        $t['n']++;
        // flag as end called
        $t['t0'] = null;
        return $t0;
    }
    /**
     * Returns css and html table with timing information.
     * echo iaTimer::report();
     *
     * @return string: css and html table with timing information
     *
     * @codeCoverageIgnore
     */
    public static function report($reportMemory = false, $reportRusage = false, $rusageWho = null) {
        $requestTime = microtime(true) - self::requestStart();
        return "<style>".self::cssClases()."</style>".self::table($requestTime, $reportMemory, $reportRusage, $rusageWho);
    }
    /**
     * Returns an html table with timing information.
     * echo iaTimer::table();
     *
     * @return string an html table (class=iaTimerTable) with timing information
     *
     * @codeCoverageIgnore
     */
    public static function table($requestTime=null, $reportMemory = false, $reportRusage = false, $rusageWho = null) {
        $memoryUsage = $reportMemory ? self::ramUsage() : '';
        $rUsageTable = $reportRusage ? self::report_rusage($rusageWho) : '';
        if($requestTime === null)
            $requestTime = microtime(true) - self::requestStart();
        $colspan = self::$haveStats ? 6 : 1;
        // report total time upto now
        $table = "<tr><td>Request<td>".self::microtimeFormat(abs($requestTime))."<td colspan=$colspan>";
        self::getTimers();
        foreach(self::$timer as $label => $t) {
            if($t['n'] <= 1) {
                // one measurment no stats
                $table .= "<tr><td>$label<td>".self::microtimeFormat($t['sum'])."<td colspan=$colspan>$t[note]";
                continue;
            }
            $table .= "<tr><td>$label".
                "<td>".self::microtimeFormat($t['max']).
                "<td>".self::microtimeFormat($t['avg']).
                "<td>".self::microtimeFormat($t['min']).
                "<td>".self::microtimeFormat($t['sdev']).
                "<td>".number_format($t['n'],0,'.',',').
                "<td>".self::microtimeFormat($t['sum']).
                "<td>$t[note]";
        }
        $header = "";
        if(self::$haveStats) {
            $header .= "/Max<th>&mu;<th>Min<th>&sigma;<th>n<th>&sum;";
        }
        return "<table class='iaTimerTable'>
            <caption>iaTimers</caption>
            <thead><tr><th>Timer<th>t$header<th>Notes</thead>
            <tbody>$table</tbody></table>$memoryUsage$rUsageTable";
    }
    /**
     * returns timers keyed by label, with basic stats calculated
     *
     * @return array timers keyed by label basic stats calculated
     * ['label1'=>[
     *   'n'=>number of observations
     *   'sum'=>'sum of observed lapsed times, total time
     *   'sqSum'=>'sum of squared observred lapsed times'
     *   'note'=>'Advice missing start/end calls'
     *   't0'=>start time, null on end called
     *   'max'=>maximum lapsed time observed
     *   'avg'=>average
     *   'min'=>minimum lapsed time observed
     *   'sdev'=>standard deviation
     * ],...]
     */
    public static function getTimers() {
        foreach(self::$timer as $label => &$t) {
            if($t['t0'] !== null) {
                // end not called, callit
                self::end($label);
                //$t = self::$timer[$label];
                $t['note'] = "Missing ".__CLASS__."::end($label), issuing it!";
            }
            $avg = $t['sum']/$t['n'];
            $t['avg'] = $avg;
            $t['sdev'] = sqrt(($t['sqSum'] / $t['n']) - ($avg * $avg));
        }
        return self::$timer;
    }
    /**
     * Return class used by report.
     * echo "&lt; style>".iaTimer::cssCLases()."&lt; /style>";
     *
     * @return string css clases used by report
     *
     * @codeCoverageIgnore
     */
    public static function cssClases() {
        return "
            .iaTimerTable {border:1px silver solid;border-collapse:collapse;margin:1em;}
            .iaTimerTable caption {border:1px silver solid; font-weight:bold;}
            .iaTimerTable th {padding:0.25em;border:1px silver solid;}
            .iaTimerTable td {padding:0.25em;border:1px silver solid;vertical-align:top;text-align:right}
            .iaTimerTable td:first-child {text-align:left;font-weight:500;}
            .iaTimerTable td:last-child {text-align:left;}
            ";
    }
    /**
     * Returns start request time from $_SERVER
     *
     * @return number start request time from $_SERVER
     */
    protected static function requestStart() {
        return isset($_SERVER['REQUEST_TIME_FLOAT']) ? $_SERVER['REQUEST_TIME_FLOAT'] : isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : microtime(true);
    }
    /**
     * Formats microtime result to strings with time units.
     * Limite 23 h 59 min 59 sec 999 ms
     *
     * @param number $t0 the number from microtime to format (sec.fraction)
     * @return string formatted lapsed time in milliseconds
     */
    protected static function microtimeFormat($t0) {
        if(empty($t0) || !is_numeric($t0)) {
            $t0 = 0;
        }
        if($t0 < 0) {
            $t0 = -1 * $t0;
        }
        if($t0 >= 60*60*24) {
            return ">= 1 day not supported";
        }
        if($t0 < 1) {
            return round($t0,4)." ms";
        }
        return Str::strim( str_replace(
             array('0 h','00 min','00 sec',' 01',' 02',' 03',' 04',' 05',' 06',' 07',' 08',' 09')
             ,array('','',''               ,' 1',' 2',' 3',' 4',' 5',' 6',' 7',' 8',' 9')
            ,gmDate(' G \h i \m\i\n s \s\e\c '.(round($t0-floor($t0),3)*1000).' \m\s',floor($t0) )
        ));
    }
    public static function report_rusage($who = null) {
        if(!function_exists('getrusage')) {
            return '';
        }
        $rusage = getrusage($who);
        $tit['ru_utime.tv_sec']='CPU user time secs.';
        $tit['ru_utime.tv_usec']='CPU user time microseconds.';
        $tit['ru_stime.tv_sec']='CPU system time secs.';
        $tit['ru_stime.tv_usec']='CPU system time microseconds.';
        $tit['ru_maxrss']='Maximum resident size in Kb.';
        $tit['ru_ixrss']='Integer, amount of memory used by the text segment that was also shared among other processes in kb * ticks-of-execution';
        $tit['ru_idrss']='Integer, amount of unshared memory residing in the data segment of a process in kb * ticks-of-execution';
        $tit['ru_isrss']='Integer, amount of unshared memory residing in the stack segment of a process in kb * ticks-of-execution';
        $tit['ru_minflt']='Number of page faults serviced without any I/O activity';
        $tit['ru_majflt']='Number of page faults serviced that required I/O activity';
        $tit['ru_nswap']='Number of times a process was swapped out of main memory';
        $tit['ru_inblock']='Number of times the file system had to perform input';
        $tit['ru_oublock']='Number of times the file system had to perform output';
        $tit['ru_msgsnd']='Number of IPC messages sent';
        $tit['ru_msgrcv']='Number of IPC messages received';
        $tit['ru_nsignals']='Number of signals delivered';
        $tit['ru_nvcsw']='Number of voluntary context switches';
        $tit['ru_nivcsw']='Number of context switches due to higher priority or time slice exceeded';
        $uni['ru_stime.tv_sec']=$uni['ru_utime.tv_sec']='secs.';
        $uni['ru_stime.tv_usec']=$uni['ru_utime.tv_usec']='microseconds';
        $uni['ru_maxrss']='Kb.';
        $uni['ru_isrss']=$uni['ru_idrss']=$uni['ru_ixrss']='kb * ticks-of-execution';
        $uni['ru_majflt']=$uni['ru_minflt']='pages';
        $uni['ru_inblock']=$uni['ru_oublock']=$uni['ru_nswap']='# veces';
        $uni['ru_msgrcv']=$uni['ru_msgsnd']='# IPC messages';
        $uni['ru_nsignals']='# se&ntilde;ales';
        $uni['ru_nivcsw']=$uni['ru_nvcsw']='# context switches';
        $rows = [];
        foreach($rusage as $k => $v) {
            $rows[] =  "<th><span".(array_key_exists($k,$tit) ? " title='$tit[$k]'" : '').
                ">$k</span><td NOWRAP class='der nowrap'>".number_format($v,0,'',',') .
                "<td>".(array_key_exists($k,$uni) ? $uni[$k] : '');
        }
        return "<table class='iaTimerTable'><caption>rusage</caption><thead><tr><th>Item<th>Value<th>Units</thead><tbody><tr>" .
            implode("\r\n<tr>", $rows) .
            "</tbody></table>";
    }
     /**
     * Reports memory usage
     *
     * @return string a div with memory usage information
     */
    private static function ramUsage() {
        if(!function_exists('memory_get_usage') || !function_exists('memory_get_peak_usage')) {
            return '';
        }
        //  style='font-family:courier;margin-left:16px;'
        return "<table class='iaTimerTable'><caption>Used Memory</caption><thead><tr><th>Memory<th>Used<th>Total Memory<br/>Allocated</thead><tbody>".
             "<tr><td>Current<td>".FormatIt::bytes2units( memory_get_usage() )."<td>".FormatIt::bytes2units( memory_get_usage(true) ) .
            "<tr><td>Peak<td>".FormatIt::bytes2units( memory_get_peak_usage() )."<td>".FormatIt::bytes2units( memory_get_peak_usage(true) ) .
            "</tbody></table>";
    }
}