为了强制执行max_execution_time限制,PHP必须跟踪特定脚本所使用的CPU时间。

是否有一种方法可以在脚本中访问它?我希望在测试中包含一些关于实际PHP中消耗了多少CPU的日志记录(当脚本等待数据库时,时间不会增加)。

我用的是Linux机顶盒。


当前回答

如果你像这样格式化秒的输出,它会更漂亮:

echo "Process took ". number_format(microtime(true) - $start, 2). " seconds.";

将打印

Process took 6.45 seconds.

这比

Process took 6.4518549156189 seconds.

其他回答

我想说的是:

如果测量目标是不同php文件中的两点A和B,会发生什么? 如果我们需要不同的度量,比如基于时间、代码执行持续时间、外部资源访问持续时间,该怎么办? 如果我们需要将我们的度量组织成不同的类别,每个类别都有不同的起点,那该怎么办呢?

正如你怀疑的那样,我们需要一些全局变量来被类对象或静态方法访问:我选择第二种方法,它是:

namespace g3;

class Utils {
   public function __construct() {}

   public static $UtilsDtStart = [];
   public static $UtilsDtStats = [];

   public static function dt() {
      global $UtilsDtStart, $UtilsDtStats;
      $obj = new \stdClass();
      $obj->start = function(int $ndx = 0) use (&$UtilsDtStart) {
         $UtilsDtStart[$ndx] = \microtime(true) * 1000;
      };
      $obj->codeStart = function(int $ndx = 0) use (&$UtilsDtStart) {
         $use = \getrusage();
         $UtilsDtStart[$ndx] = ($use["ru_utime.tv_sec"] * 1000) + ($use["ru_utime.tv_usec"] / 1000);
      };
      $obj->resourceStart = function(int $ndx = 0) use (&$UtilsDtStart) {
         $use = \getrusage();
         $UtilsDtStart[$ndx] = $use["ru_stime.tv_usec"] / 1000;
      };
      $obj->end = function(int $ndx = 0) use (&$UtilsDtStart, &$UtilsDtStats) {
         $t = @$UtilsDtStart[$ndx];
         if($t === null)
            return false;
         $end = \microtime(true) * 1000;
         $dt = $end - $t;
         $UtilsDtStats[$ndx][] = $dt;
         return $dt;
      };
      $obj->codeEnd = function(int $ndx = 0) use (&$UtilsDtStart, &$UtilsDtStats) {
         $t = @$UtilsDtStart[$ndx];
         if($t === null)
            return false;
         $use = \getrusage();
         $dt = ($use["ru_utime.tv_sec"] * 1000) + ($use["ru_utime.tv_usec"] / 1000) - $t;
         $UtilsDtStats[$ndx][] = $dt;
         return $dt;
      };
      $obj->resourceEnd = function(int $ndx = 0) use (&$UtilsDtStart, &$UtilsDtStats) {
         $t = @$UtilsDtStart[$ndx];
         if($t === null)
            return false;
         $use = \getrusage();
         $dt = ($use["ru_stime.tv_usec"] / 1000) - $t;
         $UtilsDtStats[$ndx][] = $dt;
         return $dt;
      };
      $obj->stats = function(int $ndx = 0) use (&$UtilsDtStats) {
         $s = @$UtilsDtStats[$ndx];
         if($s !== null)
            $s = \array_slice($s, 0);
         else
            $s = false;
         return $s;
      };
      $obj->statsLength = function() use (&$UtilsDtStats) {
         return \count($UtilsDtStats);
      };
      return $obj;
   }
}

现在你所要做的就是调用属于特定类别的方法,索引表示它的唯一组:

File A
------
\call_user_func_array(\g3\Utils::dt()->start, [0]);   // point A
...
File B
------
$dt = \call_user_func_array(\g3\Utils::dt()->end, [0]);  // point B

值$dt包含点A和点B之间壁钟持续时间的毫秒数。

估算php代码运行所花费的时间:

File A
------
\call_user_func_array(\g3\Utils::dt()->codeStart, [1]);   // point A
...
File B
------
$dt = \call_user_func_array(\g3\Utils::dt()->codeEnd, [1]);  // point B

注意我们是如何改变传递给方法的索引的。

代码基于从函数返回对象/函数时发生的闭包效应(参见示例中重复的\g3\Utils::dt())。

我用php单元进行了测试,在同一测试文件中的不同测试方法之间,到目前为止它表现良好!

希望这能帮助到别人!

我写了一个检查剩余执行时间的函数。

警告:执行时间计数在Windows和Linux平台上是不同的。

/**
 * Check if more that `$miliseconds` ms remains
 * to error `PHP Fatal error:  Maximum execution time exceeded`
 * 
 * @param int $miliseconds
 * @return bool
 */
function isRemainingMaxExecutionTimeBiggerThan($miliseconds = 5000) {
    $max_execution_time = ini_get('max_execution_time');
    if ($max_execution_time === 0) {
        // No script time limitation
        return true;
    }
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
        // On Windows: The real time is measured.
        $spendMiliseconds = (microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]) * 1000;
    } else {
        // On Linux: Any time spent on activity that happens outside the execution
        //           of the script such as system calls using system(), stream operations
        //           database queries, etc. is not included.
        //           @see http://php.net/manual/en/function.set-time-limit.php
        $resourceUsages = getrusage();
        $spendMiliseconds = $resourceUsages['ru_utime.tv_sec'] * 1000 + $resourceUsages['ru_utime.tv_usec'] / 1000;
    }
    $remainingMiliseconds = $max_execution_time * 1000 - $spendMiliseconds;
    return ($remainingMiliseconds >= $miliseconds);
}

使用:

while (true) {
    // so something

    if (!isRemainingMaxExecutionTimeBiggerThan(5000)) {
        // Time to die.
        // Safely close DB and done the iteration.
    }
}

最便宜也是最麻烦的方法是在代码中想要进行基准测试的地方进行microtime()调用。在数据库查询之前和之后执行它,从脚本执行时间的其余部分中删除这些持续时间就很简单了。

提示:PHP执行时间很少会导致脚本超时。如果一个脚本超时,它几乎总是调用外部资源。

PHP微时间文档: http://us.php.net/microtime

作为一种替代方法,你可以把这一行放在你的代码块中,并检查php日志,对于非常慢的函数,它非常有用:

trigger_error("Task done at ". strftime('%H:%m:%S', time()), E_USER_NOTICE); 

严肃的调试使用XDebug + Cachegrind,请参见https://blog.nexcess.net/2011/01/29/diagnosing-slow-php-execution-with-xdebug-and-kcachegrind/

talal7860回答的简短版本

<?php
// At start of script
$time_start = microtime(true); 

// Anywhere else in the script
echo 'Total execution time in seconds: ' . (microtime(true) - $time_start);

正如所指出的,这是“wallclock时间”而不是“cpu时间”