serendipity_hq/component-stopwatch

测量您的脚本(基于Symofny的版本进行分支和改进)

2.0.3 2022-08-02 20:29 UTC

This package is auto-updated.

Last update: 2024-09-20 07:00:31 UTC


README

Serendipity HQ Stopwatch

分析您的代码,测量时间和内存使用。

支持

测试平台

当前状态

Coverage Maintainability Rating Quality Gate Status Reliability Rating Security Rating Technical Debt Vulnerabilities

Phan PHPStan PSalm PHPUnit Composer PHP CS Fixer Rector

功能

  • 计时测量
  • 内存测量(消耗的、分配的、由 emalloc() 消耗的峰值、分配的峰值)
  • 按部分划分配置文件
  • 按事件划分部分
  • 按类别分组事件
  • 开始和停止
  • 圈功能
  • 精度测量

你喜欢这个库吗?
留下一个 ★

或者运行
composer global require symfony/thanks && composer thanks
以感谢您当前项目中使用的所有库,包括这个库!

原因

此组件是Symofny的Stopwatch组件的一个分支,因此您可以查看其文档以获取更多信息。

我们决定进行分支,因为关于内存测量的建议被拒绝

注意 PHP中的内存使用测量是一个非常复杂的话题:请小心使用此组件,仅用于基本用途。对于更高级的测量,请使用更精确的工具,如xDebug或Blackfire。

安装Serendipity HQ Stopwatch

.. code-block:: terminal

$ composer require serendipity_hq/component-stopwatch

或者,您可以克隆 <https://github.com/aerendir/stopwatch>_ 仓库。

此库遵循 http://semver.org/ 版本规范。

如何使用Stopwatch

Stopwatch组件允许您以您喜欢的方式分析代码的每一部分,非常精确。

Stopwatch组件提供了一种简单一致的方法来测量某些代码部分的执行时间和内存使用信息,因此您不需要自己不断解析microtime。

相反,使用简单的 SerendipityHQ\Component\Stopwatch\Stopwatch

use Symfony\Component\Stopwatch\Stopwatch;

// Initialize the class
$stopwatch = new Stopwatch();

与接受一个 true 参数($morePrecision)的Symofny的Stopwatch组件不同,SerendipityHQ的Stopwatch组件始终以微秒精度测量时间,因此您不需要向构造函数传递任何参数。

// This is the Symfony's Stopwatch way
$stopwatch = new Stopwatch(true);

// This is the SerendipityHQ's Stopwatch way: always microseconds precision
$stopwatch = new Stopwatch();

现在您可以开始测量了

// ...

// Starts event named 'event_name'
$stopwatch->start('event_name');

// ... some code goes here

// Stop the event and get it
$event = $stopwatch->stop('event_name');

$event 是一个 Event 对象,您可以从它获取分析信息。

基本概念

测量

当涉及到测量时,Stopwatch组件有三个主要概念:

  1. 周期:从开始($stopwatch->start('event_name))到停止($stopwatch->stop('event_name);)之间经过的时间;
  2. 事件:在您的代码中发生的事情,您想要测量。它包含周期;
  3. 部分:一组逻辑上连接的事件。
  4. 起源:对于 EventSection 都可用,是创建 EventSection 时记录的起始时间和内存测量以及最后一次调用 $stopwatch->stop('event_name') 时的时间和内存测量。

每个都是通过一个类表示的,但您只需要与主要的 Stopwatch 类交互。

Stopwatch 类公开了这些方法进行测量

// To manage Events
$stopwatch->start('event_name', 'event_category'); // Starts an event starting a new Period
$stopwatch->stop('event_name');                    // Stops the current Period
$stopwatch->lap('event_name');                     // Stops the current Period and starts a new one.
                                                   // Equals to $stopwatch->stop('event_name')->start('event_name')

// To manage Sections
$stopwatch->openSection();
$stopwatch->stopSection('section_name');

// Other methods
$stopwatch->reset();

事件和周期

事件(Event)是应用程序中发生的事情:路由、图像处理、循环等。

事件(Event)使用周期和来源来衡量时间流逝。

来源包括开始时间、内存测量以及上次调用$stopwatch->stop('event_name')时的最后时间和内存测量。

因此,事件(Event)基本上是一系列周期的集合。

你可以为事件(Event)提供一个分类,这样你可以在逻辑上将同一类别的事件分组。

// Starts event named 'event_name'
$stopwatch->start('event_name', 'event_category');

分类例如在Symfony WebProfileBundle中被用来以彩色编码的事件展示时间线。

Image

在图中,“default”、“section”、“event_listener”等都是事件的分类。

你可以通过调用以下方法来获取事件(Event)对象:

$stopwatch->start('event_name')
$stopwatch->stop('event_name')
$stopwatch->lap('event_name')
$stopwatch->getEvent('event_name')

当你需要检索正在运行的事件的持续时间时,应使用后者。

事件(Event)对象基本上存储两种信息:内存消耗和计时。

你可以从事件对象中获取所有这些有用的信息。

$event->getCategory();          // Returns the category the event was started in
$event->getOrigin();            // Returns the event start time in milliseconds
$event->ensureStopped();        // Stops all periods not already stopped
$event->getStartTime();         // Returns the start time of the very first period
$event->getEndTime();           // Returns the end time of the very last period
$event->getDuration();          // Returns the event duration, including all periods
$event->getMemory();            // Of all periods, gets the max memory amount assigned
                                // to PHP (measured with memory_get_usage(true))
$event->getMemoryCurrent();     // Of all periods, gets the max amount of memory used
                                // by the script (measured with memory_get_usage())
$event->getMemoryPeak();        // Of all periods, gets the max peak amount of memory
                                // assigned to PHP (measured with memory_get_peak_usage(true))
$event->getMemoryPeakEmalloc(); // Of all periods, gets the max amount of memory assigned
                                // to PHP and used by emalloc() (measured with memory_get_peak_usage())

除此之外,事件(Event)对象还存储了周期

正如你从现实世界中知道的,所有计时器都带有两个按钮:一个用于开始和停止计时器,另一个用于测量圈数。

这正是Stopwatch::lap()方法所做的事情:

    // ...

    // starts event named 'process_elements'
    $stopwatch->start('process_elements');

    // Maybe here some other code

    // Start cycling the elements
    foreach ($lements as $element) {
        // Process the $element

        // At the end use lap() to stop the timer and start a new Period
        $stopwatch->lap('process_elements');

    }

    // ... Some other code goes here

    // Finally stop the Event and get it to get information about timing and memory
    $event = $stopwatch->stop('process_elements');

圈数信息作为事件中的“周期”存储。

要获取每个圈数的详细计时和内存信息,请调用:

// Get all Periods measured in the Event
$periods = $event->getPeriods();

格式化事件(Event)信息

计时器提供了一个有用的辅助方法,用于格式化测量结果。

use SerendipityHQ\Component\Stopwatch\Utils\Formatter;

$event = $stopwatch->getSection('section_name')->getEvent('event_name');

dump(Formatter::formatTime($event->getDuration()), Formatter::formatMemory($event->getMemory());

部分(Section)

部分(Section)是将时间线逻辑上分割成组的方法。

你可以看到Symfony是如何使用部分(Section)在Symfony Profiler工具中优雅地可视化框架生命周期的。

Image

在图中,“kernel_request”是一个部分(Section)

基于前面的例子,尝试实现使用部分(Section)的某种功能

    // ...

    // Open a section
    $stopwatch->openSection();

    // Start the event assigning the category "numbers"
    $stopwatch->start('fibonacci_event', 'numbers');

    // Execute the code
    dump('fibonacci_event result', '-------------', '');
    $prev = 0;
    $next = 1;
    while($prev < 10000000000000) {
        $num = $prev + $next;

        dump($num);

        $prev = $next;
        $next = $num;

        // Get a lap (returns the current event to be used if you like!)
        $stopwatch->lap('fibonacci_event');
    }

    // Stop the event
    $stopwatch->stop('fibonacci_event');

    // Start a new event assigning the category "geometry"
    $stopwatch->start('square_numbers_event', 'geometry');

    // Execute the code
    dump('square_numbers_event result', '-------------', '');
    $root = 0;
    while ($root < 50) {
        dump($root * $root); // or pow($root, 2);

        $root++;

        // Get a lap (returns the current event to be used if you like!)
        $stopwatch->lap('square_numbers_event');
    }

    // Stop the event
    $stopwatch->stop('square_numbers_event');

    // Stop the section assigning it a name (yes, when closing, not when opening!)
    $stopwatch->stopSection('fibonacci_and_squares');

    // Open a new section
    $stopwatch->openSection();

    // Start a new event assigning the category "geometry"
    $stopwatch->start('triangle_numbers_event', 'geometry');

    // Execute some code
    dump('triangle_numbers_event result', '-------------', '');
    for($i = 1; $i <= 10; $i++) {
        $triangle = [];

        for($j = 1; $j <= $i; $j++) {
            $triangle[] = $j;
        }

        dump(implode(' ', $triangle));

        // Get a lap (returns the current event to be used if you like!)
        $stopwatch->lap('triangle_numbers_event');
    }

    // Stop the event
    $stopwatch->stop('triangle_numbers_event');

    // Start a new event assigning the category "numbers"
    $stopwatch->start('magic_square', 'numbers');

    // Execute some code
    dump('magic_square result', '-------------', '');
    $order = 5;

    for ($row = 0; $row < $order; $row++) {
        $rows = [];
        for ($col = 0; $col < $order; $col++) {
            $rowMatrix = ((($order + 1) / 2 + $row + $col) % $order);
            $colMatrix = ((($order + 1) / 2 + $row + $order - $col - 1) % $order) + 1;

            $rows[] = $rowMatrix * $order + $colMatrix;
        }

        dump(implode(' ', $rows));

        // Get a lap (returns the current event to be used if you like!)
        $stopwatch->lap('magic_square');
    }

    // Stop the event
    $stopwatch->stop('magic_square');

    // Stop the section assigning it a name (yes, when closing, not when opening!)
    $stopwatch->stopSection('triangle_numbersand_magic_square');


    dd($stopwatch);

通过调用$stopwatch::openSection('section_name')可以重新打开已关闭的部分(Section)。

例如,如果我们想在部分fibonacci_and_squares中添加另一个事件,我们做:

$stopwatch->openSection('fibonacci_and_squares');

// Start another event, execute other code...

// Stop the event and then stop the section again

获取部分(Section)的测量结果

正如所述,当你调用Stopwatch::openSection()时,计时器创建了一个测量该部分(Section)本身的事件,而不仅仅是收集你手动创建的其他事件。

这非常有用,可以测量整个部分(Section),而无需将其中所有事件相加。

你可以使用以下简单代码获取部分(Section)的事件:

use SerendipityHQ\Component\Stopwatch\Utils\Formatter;

$sectionEvent = $stopwatch->getSection('section_name')->getEvent(Stopwatch::SECTION);

dump(Formatter::formatTime($sectionEvent->getDuration()), Formatter::formatMemory($sectionEvent->getMemory());

你还可以使用快捷方式Section::getSectionEvent()来获取部分(Section)的事件。

$sectionEvent = $stopwatch->getSection('section_name')->getSectionEvent();

内存

正如所述,在PHP中测量内存不是一件简单的事情,也不太精确。

使用Stopwatch组件本身,你会消耗内存(一个非常小的量,但无论如何是一个量!),因此当你测量内存消耗时,你会得到累积的结果。

这意味着,例如,如果你在服务器上运行两个脚本,并且只从其中一个测量内存,那么你得到的内存测量结果无论如何都会受到未测量的另一个脚本的影响。

在阅读计时器(Stopwatch)的结果时,请考虑这一点。

如果你想获得更精确的测量结果,你应该考虑使用更高级的剖析工具,如Blackfire,它也可以在生产环境中使用。

还有其他注意事项,但这是最重要的一个。

在长时间运行的过程中,你需要长时间剖析大量代码时,计时器(Stopwatch)组件可能会变得非常“臃肿”,因为它存储了大量的事件(Event)周期(Period)和可能的部分(Section)。

在这种情况下,也许优化PHP使用的内存量是有用的,这样也会优化Stopwatch使用的内存。

因此,如果你喜欢,可以调用$stopwatch->reset()方法从计时器对象中删除收集的所有信息,释放内存。

显然,一旦调用,直到那一刻收集到的信息将不再可用,所以将它们“保存”到某处(数据库、日志或其他地方)是一个好主意。

资源

你喜欢这个库吗?
留下一个 ★

或者运行
composer global require symfony/thanks && composer thanks
以感谢您当前项目中使用的所有库,包括这个库!