Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Console Profiler Bundle Laravel Package

rcsofttech/console-profiler-bundle

Console Profiler Bundle adds a Symfony web profiler panel for console commands, capturing arguments, input, output, execution time, memory usage, and exceptions. Inspect CLI command runs directly in the profiler to debug performance and failures faster.

View on GitHub
Deep Wiki
Context7

Getting Started

The bundle is small (16 stars, MIT, single-digit thousand lines at most), so the fastest orientation is the source, not the docs. Read in this order:

  1. README.md — install line and the dashboard command name.
  2. src/ConsoleProfilerBundle.php (or similarly named bundle class) — confirms it is a standard Symfony bundle, i.e. Symfony\Component\HttpKernel\Bundle\Bundle.
  3. The event subscriber — almost certainly listening to Symfony\Component\Console\ConsoleEvents::COMMAND, ::TERMINATE, and possibly ::ERROR. This is the entire mechanism: start a stopwatch on COMMAND, record duration/peak memory/exit code on TERMINATE, then let a separate TUI process poll and render.
  4. The TUI command — how it refreshes (loop + Terminal clear), where it reads state from (file, cache pool, in-memory).

Install in a Symfony app:

composer require rcsofttech/console-profiler-bundle

With Symfony Flex the bundle is enabled automatically. Without Flex, register it manually in config/bundles.php:

return [
    // ...
    Rcsofttech\ConsoleProfilerBundle\ConsoleProfilerBundle::class => ['dev' => true, 'test' => true],
];

Check composer.json constraints first — this targets Symfony 6/7 console components; on a Symfony 5 app Composer will refuse or you will get a runtime ConsoleEvents mismatch.

First use case: open two panes. In pane A run your slowest command (bin/console app:import --env=dev), in pane B run the profiler command from the README. You should see duration, memory, and exit status update without touching a single line of your command code.

For a Laravel developer

Be clear-eyed: this is a Symfony bundle and Laravel does not boot bundles. There is no config/bundles.php in Laravel and no Bundle base class. You cannot composer require this into a Laravel app and expect it to appear in php artisan. Two realistic routes:

Route A — use it where it runs. Any Symfony service, internal tool, or Composer package you maintain that ships against symfony/console can use it directly. This is the day-to-day win if you straddle both ecosystems.

Route B — port the 30-line pattern. Laravel's Artisan is Symfony Console, and Laravel exposes exactly the hooks this bundle builds on:

// app/Providers/ConsoleProfilerServiceProvider.php
use Illuminate\Console\Events\CommandStarting;
use Illuminate\Console\Events\CommandFinished;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Cache;

public function boot(): void
{
    if (! $this->app->isLocal()) {
        return;
    }

    Event::listen(CommandStarting::class, function (CommandStarting $e) {
        Cache::put("profiler:{$e->command}", [
            'start' => microtime(true),
            'mem'   => memory_get_usage(true),
        ], now()->addHour());
    });

    Event::listen(CommandFinished::class, function (CommandFinished $e) {
        $start = Cache::pull("profiler:{$e->command}");
        if (! $start) return;

        Cache::put("profiler:last:{$e->command}", [
            'duration_ms' => (microtime(true) - $start['start']) * 1000,
            'peak_mb'     => round(memory_get_peak_usage(true) / 1048576, 2),
            'exit_code'   => $e->exitCode,
            'finished_at' => now()->toIso8601String(),
        ], now()->addHour());
    });
}

Then a php artisan profiler:watch command that loops, reads the cache keys, and renders with Termwind or Laravel\Prompts. That is the same architecture as the bundle, minus the Symfony dependency.

Implementation Patterns

Observer, never inline. The dashboard and the profiled command must be separate OS processes. Launching the TUI from inside your command fights for the same TTY and will garble output. Standard workflow: tmux split, iTerm/Windows Terminal split pane, or two SSH sessions.

Zero instrumentation. Because the bundle hooks console events, existing commands are profiled without edits. This is the main argument for it over hand-rolled microtime() calls sprinkled through handle(). Treat any command that needs --profile flags as a smell — the event listener should be sufficient.

Gate it to dev. In Symfony, put the bundle class in ['dev' => true, 'test' => true] in config/bundles.php, and wrap its config in a when@dev: block in config/packages/. The listener overhead is small but non-zero, and you do not want a file-writing subscriber in production.

Split the storage from the renderer. If the bundle exposes a storage/repository class (look for a Storage, Repository, or State directory), you can:

  • point it at /tmp or var/profiler/ and share it across containers,
  • feed the same data into a JSON dump for CI (--no-ansi),
  • write a Laravel-side reader that parses the same file — useful if you have a Symfony CLI and a Laravel CLI in the same mono-repo.

CI pattern. TUI is interactive-only. For pipeline visibility, run the command and have a small wrapper dump json_encode of the collected metrics, then assert thresholds:

bin/console app:import --no-ansi --no-interaction
php -r 'exit(json_decode(file_get_contents("var/profiler/last.json"), true)["duration_ms"] > 30000 ? 1 : 0);'

Laravel-side integration. If you standardise on Route B above, register it in a provider that only boots in local — $this->app->isLocal() — and store in Cache::store('file') or the database rather than Redis, so php artisan cache:clear does not wipe a run mid-flight.

Gotchas and Tips

Bundle ≠ ServiceProvider. The number one mistake a Laravel developer makes here is adding the bundle class to config/app.php's providers array. It is not a provider; it has no register()/boot() contract and will fatal. Same for trying to composer require it into Laravel and expecting artisan to see the commands.

Short commands may never appear. The dashboard polls. A command finishing in under one refresh interval may be overwritten before you look. Either lower the refresh interval or — better — make sure the storage is append-only keyed by command name, so you see the last run rather than only the currently-running one.

SIGINT / Ctrl+C. ConsoleEvents::TERMINATE fires on normal exit and on exceptions, but signal-terminated processes on some PHP builds bypass it. If your dashboard shows a command "running" forever, that is the cause. The listener typically registers pcntl_signal(SIGINT, ...) — check the source; if not, that is a legitimate patch to submit.

Memory numbers mislead. memory_get_peak_usage(true) reports the allocator's arena (what the OS gave PHP), not what your objects actually hold. memory_get_peak_usage(false) is closer to "useful" but still includes opcache and framework bootstrap. When comparing commands, use the delta from CommandStarting, not the absolute peak.

TTY and ANSI. Any --no-ansi, --no-interaction, or piped output will change what the dashboard can render. If colours or redraws look broken, check stream_isatty(STDOUT) and the TERM variable; inside Docker without -t, the TUI degrades badly.

Concurrency. Two commands running at once will race on the same storage key. If you routinely run parallel imports (--queue, &, Process::pool), verify the storage is keyed by PID or command instance, not just command name, before trusting the dashboard.

Extension points to look for. In a repo this size there are usually one or two seams: a collector interface you can implement for custom metrics, or a configuration key for the storage path/refresh rate. If neither exists, the event subscriber is a plain class — decorating or replacing it is a five-minute fork, and the MIT licence makes upstreaming a PR trivial.

Version drift. Release date is 2026-03-25 and the package has 0 dependents, so nothing else is pinning it. Treat minor bumps as potentially breaking and pin an exact version in composer.json until you've read the diff.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo