CLI command

Outside the Profilerand the Timeline you can use the package features through a CLI command. A CLI Command is available to allow you to benchmark PHP functions and methods located in a specific file or directory using the custom #[Bakame\Stackwatch\Profile] attribute. This is especially useful for:

  • Automating performance regressions in CI pipelines
  • Profiling code outside the context of an application

Example

First, let’s assume you have the following file located in /path/profiler/test.php.

<?php

declare(strict_types=1);

namespace Foobar\Baz;

use Bakame\Stackwatch\Profile;
use function random_int;
use function usleep;

require 'vendor/autoload.php';

trait TimerTrait {
    #[Profile(type: Profile::SUMMARY, iterations: 10)]
    private function test() : int {
        usleep(100);

        return random_int(1, 100);
    }
}

enum Foobar
{
    use TimerTrait;

    case Foobar;
}

#[Profile(type: Profile::DETAILED, iterations: 20, warmup: 2)]
function test() : int {
    usleep(100);

    return random_int(1, 100);
}

If you run the following command:

php vendor/bin/stackwatch --path=/path/profiler/test.php

It will output the following:

stackwatch v0.13.0 (Marrakesh) by Ignace Nyamagana Butera and contributors.

Runtime: PHP 8.3.24 OS: Linux Memory Limit: 64M

(Average) Target: Foobar\Baz\Foobar::test; Path: /path/to/profiling/code.php; Iterations: 3; Warmup: 0;

CPU Time ............................................................. 19.000 µs
Execution Time ...................................................... 144.611 µs
Memory Usage ............................................................ 1.0 KB
Real Memory Usage ........................................................ 0.0 B
Peak Memory Usage ........................................................ 0.0 B
Real Peak Memory Usage ................................................... 0.0 B

(Detailed) Target: Foobar\Baz\test; Path: /path/to/test.php; Iterations: 20; Warmup: 2:
+------------------------+---------------+------------+------------+--------------+------------+-----------+------------+------------+----------+-----------+
| Metric                 | Nb Iterations | Min Value  | Max Value  | Median Value | Sum        | Range     | Average    | Variance   | Std Dev  | Coef Var  |
+------------------------+---------------+------------+------------+--------------+------------+-----------+------------+------------+----------+-----------+
| CPU Time               | 20            | 9.000 µs   | 20.000 µs  | 12.000 µs    | 240.000 µs | 11.000 µs | 12.000 µs  | 6.300 μs²  | 2.510 µs | 20.9165 % |
| Execution Time         | 20            | 135.166 µs | 169.833 µs | 149.979 µs   | 2.980 ms   | 34.667 µs | 149.021 µs | 65.499 μs² | 8.093 µs | 5.4309 %  |
| Memory Usage           | 20            | 1.031 KB   | 1.031 KB   | 1.031 KB     | 20.625 KB  | 0.000 B   | 1.031 KB   | 0.000 B²   | 0.000 B  | 0.0000 %  |
| Peak Memory Usage      | 20            | 0.000 B    | 0.000 B    | 0.000 B      | 0.000 B    | 0.000 B   | 0.000 B    | 0.000 B²   | 0.000 B  | 0.0000 %  |
| Real Memory Usage      | 20            | 0.000 B    | 0.000 B    | 0.000 B      | 0.000 B    | 0.000 B   | 0.000 B    | 0.000 B²   | 0.000 B  | 0.0000 %  |
| Real Peak Memory Usage | 20            | 0.000 B    | 0.000 B    | 0.000 B      | 0.000 B    | 0.000 B   | 0.000 B    | 0.000 B²   | 0.000 B  | 0.0000 %  |
+------------------------+---------------+------------+------------+--------------+------------+-----------+------------+------------+----------+-----------+
  • the leader list shows the average metrics for the Foobar::test method.
  • the table shows the fully detailed report on the function test.

The Profile attribute

To work, the command line relies on the presence of the #[Bakame\Stackwatch\Profile] attribute. The attribute can mark a function, method, or class for performance profiling during execution. When applied, the profiler will repeatedly execute the target code to collect detailed runtime metrics, allowing developers to analyze and optimize code performance with statistically meaningful data.

The attribute can be applied to:

  • Standalone functions
  • Class methods, regardless of visibility (public, protected, or private)
  • Classes — When applied at the class level, all methods of that class will be profiled using the class-level attribute configuration.
If a method within a class is also marked with its own #[Profile] attribute, the method-level attribute configuration overrides the class-level configuration for that specific method.
Functions or methods that declare one or more arguments will not be profiled. Only functions or methods without parameters can be profiled using this attribute.

Attribute properties

  • type: (string) Level of detail in the profiling output, (default to: Profile::SUMMARY ).
    • Profile::SUMMARY: Outputs core statistics such as average execution time.
    • Profile::DETAILED: Tags enable grouping and filtering in profiling reports, helping users focus on specific categories or subsets of profiled code.
  • iterations: (int) Controls how many times the target will be executed during profiling to ensure statistical significance. Larger values provide more accurate metrics but increase profiling time. Must be > 0 (default to 3)
  • warmup: Allows the profiler to run the target code several times before recording metrics, which helps mitigate effects like JIT compilation or caching impacting the results. Must be >= 0 (default to 0)
  • tags: Tags enable grouping and filtering in profiling reports, helping users focus on specific categories or subsets of profiled code. Must be a list of non-emptu string (default to an empty array)

Attribute usage

On a function

#[Profile(iterations: 500, type: Profile::SUMMARY)]
function calculateSomething(): void
{
    // ...
}

On a method

class Example 
{
    #[Profile(iterations: 1000, warmup: 50, type: Profile::DETAILED, tags: ['api'])]
    protected function fetchData(): array
    {
        // ...
    }
}

On a class

#[Profile(iterations: 100, type: Profile::SUMMARY)]
class MyService 
{
    public function methodOne() 
    { 
        /* profiled with class-level config */
    }

    #[Profile(iterations: 50, type: Profile::DETAILED)]
    private function methodTwo()
    { 
        /* profiled using the method-level attribute */
    }

    public function methodThree(string $arg1)
    {
        /* not profiled due to parameter */
    }
}
Be mindful of the performance impact during profiling, especially with high iteration counts.
All required dependencies should be loaded in the target file (use `require`, `include` or Composer autoload).

Command Line Usage

The Stackwatch command-line profile runner can be invoked through the stackwatch command. The following code shows the generic way to run profiling with the stackwatch command-line:

php vendor/bin/stackwatch --path=PATH [options]

Apart from the path argument, all the other command line options are optional.

Command Line Options

Configuration

-p, --path=PATH
Path to scan for PHP files to profile. Required. The path can be a file or a directory. If it is a directory it will be recursively scan.

-i, --info
Show additional system and environment information.

-h, --help
Display the help message.

-V, --version
Display the version and exit.

Output

-f, --format=FORMAT
Output format. Can be either 'text' or 'json'. Default is 'text'. Use 'text' for human-readable output and 'json' for newline-delimited JSON (NDJSON).

-o, --output=OUTPUT
Path to store the profiling output.

--log=FILE
Writes log information to the specified file. By default the log are written to the STDERR stream.

-P, --pretty
Pretty-print the JSON/NDJSON output. JSON only.

--no-progress
Hides the progress bar

Selection

-d, --depth=DEPTH
Recursion depth. 0 means scan only the current directory. By default, recursion is unlimited.

-n, --no-recursion
Disable directory recursion an alias to --depth=0

-t, --tags=TAGS
Only run the profiles for the listed tag(s). The tags are separated by a , and refers to the tags defined on the #[Profile] attribute.

--file-suffix=SUFFIX
Only search for test in files with specified suffix(es). Default: .php The suffixes are separated by a ,

--method-visibility=VISIBILITY Only run the profiles for methods with the listed visibilities. The visibility is separated by a , And can only take private, protected or public values. By default, all methods are run regardless of their visibility status.

Execution

-x, --isolation
Profile each file in isolation.

--dry-run
List profiling targets without actually performing the profiling.

--memory-limit=MEMORY-LIMIT
Memory limit to use for the analysis.

Integration into CI

You can run the profiling command in your CI pipelines to detect regressions or performance anomalies.

- name: Run Profiler
  run: php vendor/bin/stackwatch --path=/path/profiler/test.php --format=json
The json output is a NDJSON each line representing the result of a successful file scan.
Logo