makinacorpus/profiling

轻量级且灵活的性能分析工具箱,使用高分辨率计时器。

2.0.8 2023-06-30 13:27 UTC

This package is auto-updated.

Last update: 2024-09-17 08:07:14 UTC


README

性能分析和度量工具箱。

包含许多功能

  • 计时器API,非常类似于 symfony/stopwatch,具有时间和内存消耗测量。计时器可以在计时器树内启动,从而允许对计时器进行多维视图。

  • 与Prometheus兼容的各种度量收集,包括计数器、仪表、摘要和直方图。这些度量可以通过Prometheus兼容的scraping端点公开。

  • 当处于开发模式时,与symfony/stopwatch集成,用于Symfony网络分析器。

如果可用,计时器使用PHP \hrtime()函数的恒定高分辨率计时器进行计时,这提供了更高的精度,并且对系统时钟变化具有弹性,与\microtime()相反。使用\hrtime()函数使得此API适合在生产中独立运行。

设置

安装

只需运行

composer require makinacorpus/profiling

要注册Symfony捆绑包,将以下内容添加到您的config/bundles.php

return [
    // ... Your other bundles.
    MakinaCorpus\Profiling\Bridge\Symfony\ProfilingBundle::class => ['all' => true],
];

通过设置此环境变量来启用它

PROFILING_ENABLED=1

也可以启用Prometheus支持

PROFILING_PROMOTHEUS_ENABLED=1

然后复制此包中的src/Bridge/Symfony/Resources/packages/profiling.yamlconfig/packages/目录。您可以阅读并根据自己的需求进行修改。所有配置选项都在示例配置文件中进行了文档说明。

存储配置

默认存储是使用makinacorpus/query-builder编写SQL查询的PostgreSQL数据库实现。

它可以在运行时自动创建缺少的表,尽管这可能会影响性能,默认情况下是禁用的。为了允许自动表创建,您可以设置环境变量PROFILING_PROMETHEUS_SCHEMA_AUTOCREATE=1

推荐的方法是至少运行一次profiling:prometheus:create-schema控制台命令

php bin/console profiling:prometheus:create-schema

使用方法

重要注意事项

  • 对于每个传入请求,必须有一个唯一的MakinaCorpus\Profiling\Profiler实例。

  • “传入请求”意味着单个工作负载,在消息总线消费者的上下文中,可以是一条消息的处理。

计时器

基本用法

以下是代码示例

use MakinaCorpus\Profiling\Profiler\DefaultProfiler;

// First, create a profiler. If you are using a framework, you should
// inject in your dependency injection container a global instance.
$profiler = DefaultProfiler();

// Start a new top-level timer, which has no parent.
// Please note that name is optional, it's purely informational.
// A unique identifier will be generated if you don't pass one.
// You need a name later if you wish to stop one timer without
// stopping all the others.
$timer = $profiler->start('doing-something');

// Each time you start a new top-level profiler, it is decoupled from
// the other one, they won't interact with each-ohter.
$otherTimer = $profiler->start('unrelated-other-something');

// From your first timer, you can start children.
$timer1 = $timer->start('1');
$timer2 = $timer->start('2');

// Then subchildren.
$timer21 = $timer2->start('2.1');
$timer22 = $timer2->start('2.2');

// From a parent timer, you can choose stopping only one child.
// You can stop the child directly as well.
// The following two lines are equivalent gives a strictly identical result.
$timer2 = $timer2->stop('2.2');
$timer22->stop();

echo $timer2->isRunning(); // true
echo $timer21->isRunning(); // true
echo $timer22->isRunning(); // false

// When you close a timer, all the children will be stopped as well.
$timer2 = $timer->stop();

echo $timer2->isRunning(); // false
echo $timer21->isRunning(); // false
echo $timer22->isRunning(); // false

// You can fetch timings.
// All given numbers are float, reprensenting a number of milliseconds.
echo $timer2->getElapsedTime(); // 2.2124548
echo $timer21->getElapsedTime(); // 1.88878889
echo $timer22->getElapsedTime(); // 0.98897574

// You can fully reset the global state, which will also free the
// memory it took.
// This is precious for long running deamons, such as message bus
// consumers which will remain alive for hours.
$profiler->flush();

计时器高级用法

MakinaCorpus\Profiling\Timer接口上有许多方法,所有方法都有文档说明。

计时器跟踪处理器

计时器跟踪处理器是监听所有发出的计时器的组件,然后可以记录信息。默认提供了一些处理器

  • MakinaCorpus\Profiling\Timer\Handler\SentryHandler (sentry)可以将您的计时器发送到Sentry实例。

  • MakinaCorpus\Profiling\Timer\Handler\StoreHandler (store)可以将您的计时器发送到本地存储实现。目前只有一个实现,它将数据发送到SQL数据库表。

  • MakinaCorpus\Profiling\Timer\Handler\StreamHandler (file)将您的计时器发送到日志文件,每个计时器占一行。

  • MakinaCorpus\Profiling\Timer\Handler\SymfonyStopwatchHandler (stopwatch)将计时器发送到symfony/stopwatch组件。这在开发模式下安装了网络分析器调试工具栏时很有用。

处理器可以配置为接受来自所有通道、某些通道或所有通道但某些通道的计时器。

有关处理器配置的更多信息,请参阅示例配置文件。

Prometheus度量

设置

首先,使用环境变量启用它

PROFILING_PROMOTHEUS_ENABLED=1

然后使用您选择的方法计算一个随机访问令牌,并将其设置到您的环境变量中

PROFILING_PROMETHEUS_ACCESS_TOKEN: SuperSecretToken

为了设置Prometheus HTTP端点,请将以下内容添加到config/routes.yaml

prometheus:
    resource: "@ProfilingBundle/Resources/config/routes.prometheus.yaml"
    prefix: /prometheus

然后为了获取指标,只需访问以下URL

curl http://yoursite/prometheus/metrics/?access_token=SuperSecretToken

此外,请注意,如果您配置了某些防火墙,您可能需要将^/prometheus/路径放入非安全防火墙中。

默认配置简单有效,除了需要配置的驱动程序。默认为in_memory,这意味着它根本不存储任何内容。

定义您自己的指标

每个样本必须在schema配置部分中定义。如果在此文件中没有定义样本,那么如果您尝试收集它,它将简单地不执行任何操作。

编辑您的config/packages/profiling.yaml文件

profiling:
    prometheus:
        #
        # Namespace name will prefix all sample names when exported to
        # Prometheus. For example, if you define "some_counter", the final
        # sample name will be "symfony_some_counter".
        #
        namespace: symfony

        #
        # For all sample type, you may set the "labels" entry.
        #
        # Labels are some kind tags, whose values are required when
        # measuring. This is important, please read Prometheus documentation
        # for more information.
        #
        # In all cases, you always should add the [action, method] which then
        # should be populated using respectively the current route name and
        # the HTTP method. There are of course some use cases where you may
        # not need it.
        #
        schema:

            # Gauge is a float value, no more no less, each new measure
            # will erase the existing value.
            some_gauge:
                type: gauge
                help: This is some gauge
                labels: [action, method, foo, bar]

            # A counter is a static value that gets incremented over time.
            # It never gets reseted, always incremented.
            # If you drop the data from your database, the next Prometheus
            # scrap will see the value going down and give invalid data,
            # but as time will pass, data will get eventually consistent
            # soon enough.
            some_counter:
                type: counter
                help: Some counter
                labels: [action, method, foo]

            # Summary is statistical distribution analysis of some value using
            # percentiles. Summaries are computed on the client side (ie.
            # your site) in opposition to histograms which are computed on
            # the service (ie. in Prometheus).
            # You may or may not define quantiles, in this example we define
            # the defaults being applied if none providen.
            some_summary:
                type: summary
                help: Some summary
                labels: [action, method, foo]
                quantiles: [0.01, 0.05, 0.5, 0.95, 0.99]

            # Histogram is a statistical distribution analysis of some value
            # using buckets. Buckets are supposed to be pre-defined in this
            # schema. Histograms are computed in the server side (ie. in
            # Prometheus) in opposition to summaries which are computed on
            # the client side (ie. your site).
            # You may or may not define buckets, in this example we define
            # the defaults being applied if none providen.
            # It is strongly recommended, for histogram usage, that you define
            # explicitely your own buckets depending on expected values for
            # each sample definition.
            some_histogram:
                type: histogram
                help: request durations in milliseconds
                labels: [action, method, foo]
                buckets: [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0]

然后,在您需要性能分析的代码点处,注入MakinaCorpus\Profiling\Profiler服务并使用它。

仪表

使用gauge()方法

\assert($profiler instanceof \MakinaCorpus\Profiling\Profiler);

$timer = $profiler->gauge(
    // Name in your schema.
    'some_gauge',
    // Label values, considering you kept "action" and "method"
    // in the (action, method, foo, bar) list:
    [
        $profiler->getContext()->route,
        $profiler->getContext()->method,
        'some_value',
        'some_other_value',
    ],
    // Arbitrary value you actually measure.
    123.456
);

计数器

使用counter()方法

\assert($profiler instanceof \MakinaCorpus\Profiling\Profiler);

$timer = $profiler->counter(
    // Name in your schema.
    'some_counter',
    // Label values, considering you kept "action" and "method"
    // in the (action, method, foo) list:
    [
        $profiler->getContext()->route,
        $profiler->getContext()->method,
        'some_value',
    ],
    // Arbitrary increment value you actually measure.
    // You can omit this parameter and increment will be 1.
    3,
);

摘要

使用与计时器一起的summary方法

\assert($profiler instanceof \MakinaCorpus\Profiling\Profiler);

try {
    $timer = $profiler->timer();

    // Something happens, then...
} finally {
    if ($timer) {
        $duration = $timer->getElapsedTime();

        $profiler->summary(
            'something_duration_msec',
            // Label values, considering you kept "action" and "method"
            // in the (action, method, foo) list:
            [
                $profiler->getContext()->route,
                $profiler->getContext()->method,
                'some_value',
            ],
            // Arbitrary value you actually measure.
            $duration,
        );
    }
}

当然,您可以测量除了持续时间之外的其他内容,任何对您有意义的值都可以添加到摘要中。

重要提示:直方图在输出渲染和存储方面比摘要更高效。如果您事先知道期望值的范围,请避免使用摘要。

直方图

使用与计时器一起的histogram方法

\assert($profiler instanceof \MakinaCorpus\Profiling\Profiler);

try {
    $timer = $profiler->timer();

    // Something happens, then...
} finally {
    if ($timer) {
        $duration = $timer->getElapsedTime();

        $profiler->histogram(
            'something_duration_msec',
            // Label values, considering you kept "action" and "method"
            // in the (action, method, foo) list:
            [
                $profiler->getContext()->route,
                $profiler->getContext()->method,
                'some_value',
            ],
            // Arbitrary value you actually measure.
            $duration,
        );
    }
}

当然,您可以测量除了持续时间之外的其他内容,任何对您有意义的值都可以添加到摘要中。

重要提示:直方图在输出渲染和存储方面比摘要更高效。如果您事先知道期望值的范围,请始终首选直方图而不是摘要。

公开的指标

HTTP请求

  • NAMESPACE_http_exception_total (counter),{action: ROUTE, method: HTTP_METHOD, type: EXCEPTION_CLASS}
  • NAMESPACE_http_request_duration_msec (summary),{action: ROUTE, method: HTTP_METHOD, status: HTTP_STATUS_CODE}
  • NAMESPACE_http_request_total (counter),{action: ROUTE, method: HTTP_METHOD}
  • NAMESPACE_http_response_total (counter),{action: ROUTE, method: HTTP_METHOD, status: HTTP_STATUS_CODE}
  • NAMESPACE_instance_name (gauge),{instance_name: HOSTNAME}

控制台命令

  • NAMESPACE_console_command_total (counter),{action: COMMAND_NAME}
  • NAMESPACE_console_duration_msec (summary),{action: COMMAND_NAME, method: "command", status: EXIT_STATUS_CODE}
  • NAMESPACE_console_exception_total (counter),{action: COMMAND_NAME, method: "command", type: EXCEPTION_CLASS}
  • NAMESPACE_console_status_total (counter),{action: COMMAND_NAME, method: "command", status: EXIT_STATUS_CODE}

消息传递

尚未实现。它可能将是

  • NAMESPACE_message_consumed_total (counter),{action: MESSAGE_CLASS, method: "message"}
  • NAMESPACE_message_duration_msec (summary),{action: MESSAGE_CLASS, method: "message"}
  • NAMESPACE_message_exception_total (counter),{action: MESSAGE_CLASS, method: "message", type: EXCEPTION_CLASS}

Monolog

尚未实现。它可能将是

  • NAMESPACE_monolog_message_total (counter),{action: ROUTE|COMMAND_NAME|MESSAGE_CLASS, method: "message"|"command"|HTTP_METHOD, severity: MONOLOG_LEVEL, channel: MONOLOG_CHANNEL}

系统信息

如果您没有系统监控,此捆绑包可以收集一些系统信息样本。

请参阅src/Bridge/Symfony/Resources/config/packages/profiling.yaml文件中的完整文档。

CPU负载平均样本

  • NAMESPACE_sys_load_avg (gauge),{span: INT} - 以分数形式表示的值,为过去span分钟。

内存使用样本

  • NAMESPACE_sys_mem_total (gauge) - 字节值。
  • NAMESPACE_sys_mem_free (gauge) - 字节值。
  • NAMESPACE_sys_mem_used (gauge) - 字节值。
  • NAMESPACE_sys_mem_available (gauge) - 字节值。
  • NAMESPACE_sys_mem_buffers (gauge) - 字节值。
  • NAMESPACE_sys_mem_cached (gauge) - 字节值。
  • NAMESPACE_sys_mem_swap_total (gauge) - 字节值。
  • NAMESPACE_sys_mem_swap_free (gauge) - 字节值。
  • NAMESPACE_sys_mem_swap_used (gauge) - 以字节为单位的值。

磁盘使用情况样本

  • NAMESPACE_sys_disk_total (gauge), {name: NAME} - 以字节为单位的值。
  • NAMESPACE_sys_disk_free (gauge), {name: NAME} - 以字节为单位的值。
  • NAMESPACE_sys_disk_used (gauge), {name: NAME} - 以字节为单位的值。

默认情况下,如果您没有配置任何磁盘,则目标为当前项目目录的挂载点,并且 NAMEapp

警告:因为探测器仅在发生请求时收集数据,您可以选择运行 profiling:prometheus:sys-info 控制台命令以作为 cron 作业定期收集系统信息。

将分析器注入到您的服务中

当在 Symfony 项目中工作时,将分析器注册到服务中的推荐方法是以下

namespace MyVendor\MyApp\SomeNamespace;

use MakinaCorpus\Profiling\ProfilerAware;
use MakinaCorpus\Profiling\ProfilerAwareTrait;

/**
 * Implementing the interface allow autoconfiguration.
 */
class SomeService implements ProfilerAware
{
    use ProfilerAwareTrait;
}

通过使用 \MakinaCorpus\Profiling\ProfilerAwareTrait,您可以使您的代码在初始化错误的情况下更加健壮

  • 如果自动配置失败,它将创建一个默认的空实例,不执行任何操作,这将对性能的影响几乎为零。

  • 如果组件被禁用,它将创建一个默认的空实例,不执行任何操作,这将对性能的影响几乎为零。

然后您可以使用分析器

namespace MyVendor\MyApp\SomeNamespace;

use MakinaCorpus\Profiling\ProfilerAware;
use MakinaCorpus\Profiling\ProfilerAwareTrait;

/**
 * Implementing the interface allows autoconfiguration.
 */
class SomeService implements ProfilerAware
{
    /**
     * Using the trait provides a default working implementation.
     */
    use ProfilerAwareTrait;

    public function doSomething()
    {
        $timer = $this->getProfiler()->start('something');

        try {
            $timer->start('something-else');
            $this->doSomethingElse();
            $timer->stop('something-else');

            $timer->start('something-other');
            $this->doSomethingElse();
            $timer->stop('something-other');

            $timer->start('something-that-fails');
            throw new \Exception("Oups, something bad happened.");
            $timer->stop('something-that-fails');

        } finally {
            // We do heavily recommend that use the try/finally
            // pattern to ensure that exceptions will not betry
            // your timers.
            // The last stop() call within the try block will never
            // be called, by stopping the parent timer here, it
            // stops the child as well.
            $timer->stop();
        }
    }
}

这就差不多了。

内存使用情况

计时器类也测量内存使用情况,但请注意,这些结果将受到该 API 本身消耗内存的偏差。

CLI 杀死开关

如果您正在 CLI 中工作,并且想禁用长时间运行的任务或迁移批次的分析,只需在您的命令行中添加 PROFILING_ENABLE=0 环境变量。

这不会完全禁用组件,这是一个软禁用,只会防止在此运行时创建分析器,例如

PROFILING_ENABLE=0 bin/console app:some-very-long-batch

路线图

  • 允许配置流处理程序的格式化程序。
  • 实现 Prometheus 度量指标的 Redis 存储。
  • 合并度量和分析器接口/类。
  • 将计时器连接到 Prometheus 总结和直方图。
  • Prometheus doctrine SQL 查询计数度量。
  • Prometheus messenger 度量。
  • Prometheus monolog 度量。
  • Prometheus 响应大小度量。
  • Prometheus 意外度量记录到 monolog。
  • 使用日期清除控制台命令。
  • 查看控制台命令。
  • 编写文档网站。