serendipity_hq/stopwatch

此包已被放弃,不再维护。作者建议使用serendipity_hq/component-stopwatch包。

测量你的脚本(从Symfony的版本分叉并改进)

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
来感谢你当前项目中使用的所有库,包括这个!

原因

此组件是Symfony的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)的Symfony的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类交互即可。

《计时器》类公开了这些用于测量的方法

// 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();

事件和周期

一个 事件 是在您的应用程序中发生的事情:路由、图像处理、循环等。

通过使用 周期 和起源,一个 事件 测量经过的时间。

起源是开始时间和内存测量值,以及上一次调用 $stopwatch->stop('event_name') 时的最后时间和内存测量值。

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

您可以为 事件 任意指定一个类别:这样,您可以在同一个类别中逻辑地分组 事件

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

类别被用于,例如,Symfony WebProfileBundle 来以彩色编码显示事件的时间线。

Image

在图像中,“默认”、“部分”、“事件监听器”等都是 事件 的类别。

您可以调用以下方法来获取 事件 对象

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

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

事件 对象存储两种基本类型的信息:内存消耗和计时。

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

$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())

此外,事件 对象还存储 周期

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

这正是 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();

格式化 事件 的信息

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

use SerendipityHQ\Component\Stopwatch\Utils\Formatter;

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

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

部分

部分是将时间线逻辑上分成组的一种方式。

您可以在 Symfony Profiler 工具中看到 Symfony 如何使用部分来很好地可视化框架的生命周期。

Image

在图像中,“kernel_request” 是一个 部分

在之前的例子基础上,尝试实现一些使用 部分 的功能

    // ...

    // 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') 来重新打开一个已关闭的部分。

例如,如果我们想向 fibonacci_and_squares 部分添加另一个 事件,我们这样做:

$stopwatch->openSection('fibonacci_and_squares');

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

// Stop the event and then stop the section again

获取部分的测量值

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

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

您可以通过以下简单代码获取 部分事件

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() 来获取 部分事件

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

内存

如前所述,在 PHP 中测量内存不是一项简单任务,也不是一项精确任务。

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

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

在读取 计时器 的结果时,请考虑这一点。

如果您想要更精确的测量,您应考虑使用更高级的剖析工具,如 Blackfire,它也可以在生产中使用。

还有其他需要注意的问题,但这是最重要的问题。

在长时间运行的过程中,如果您需要在长时间内剖析大量代码,那么 计时器 组件可能会变得非常“臃肿”,因为它存储了大量的 事件周期,甚至可能是 部分

在这种情况下,优化PHP以及通过Stopwatch使用的内存量可能很有用)。

因此,如果您愿意,可以调用$stopwatch->reset()方法从Stopwatch对象中删除所有收集到的信息,释放内存。

显然,一旦调用,从那一刻起收集到的信息将不再可用,因此将它们“保存”在某个地方(在数据库中、在日志中或任何其他地方)是个好主意。

资源

你喜欢这个库吗?
留下 ★

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