Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .codacy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
exclude_paths:
- "vendor/**"
- "var/**"
- "composer.lock"
- ".php-cs-fixer.cache"
- ".phpunit.cache/**"
150 changes: 150 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
name: CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
tests:
name: PHP ${{ matrix.php }} - Symfony ${{ matrix.symfony }}
runs-on: ubuntu-latest
needs: [quality]

strategy:
fail-fast: false
matrix:
php: ['8.4']
symfony: ['8.0.*']

steps:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
fetch-depth: 0

- name: Setup PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: pcov
ini-values: memory_limit=-1

- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-

- name: Validate composer.json and composer.lock
run: composer validate --strict

- name: Install dependencies
run: |
composer require "symfony/framework-bundle:${{ matrix.symfony }}" --no-update
composer update --prefer-dist --no-interaction --no-progress --prefer-stable --optimize-autoloader

- name: Run tests with coverage
run: vendor/bin/phpunit --coverage-clover clover.xml

- name: Verify coverage report was generated
run: |
if [ ! -f clover.xml ]; then
echo "ERROR: Coverage report (clover.xml) was not generated!"
exit 1
fi

echo "Coverage report generated successfully"

if ! grep -q '<file name=' clover.xml; then
echo "WARNING: Coverage report appears to be empty (no files found)"
exit 1
fi

FILE_COUNT=$(grep -c '<file name=' clover.xml || echo "0")
echo "Files in coverage report: $FILE_COUNT"
echo "Report size: $(wc -c < clover.xml) bytes"

- name: Upload coverage to Codacy
if: |
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
continue-on-error: true
uses: codacy/codacy-coverage-reporter-action@89d6c85cfafaec52c72b6c5e8b2878d33104c699
with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
coverage-reports: clover.xml

quality:
name: Quality Checks
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5

- name: Setup PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1
with:
php-version: 8.4
tools: composer:v2
coverage: none
ini-values: memory_limit=-1

- name: Get composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-

- name: Validate composer.json and composer.lock
run: composer validate --strict

- name: Install dependencies
run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader

- name: Run PHPStan
run: vendor/bin/phpstan analyse --error-format=github

- name: Run PHP-CS-Fixer
run: vendor/bin/php-cs-fixer fix --dry-run --diff --format=txt

- name: Run security audit
run: composer audit --locked

ci-success:
name: CI Success
runs-on: ubuntu-latest
needs: [tests]
if: always()

steps:
- name: Check all jobs status
run: |
if [ "${{ needs.tests.result }}" != "success" ]; then
echo "CI failed"
echo "Tests: ${{ needs.tests.result }}"
exit 1
fi
echo "All CI checks passed successfully"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
vendor
.phpunit.cache
.php-cs-fixer.cache
.phpstan.cache
56 changes: 56 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

$finder = (new PhpCsFixer\Finder())
->in(__DIR__)
->exclude('var')
->notPath('config/reference.php')
;

return (new PhpCsFixer\Config())
->setRules([
'@Symfony' => true,
'@Symfony:risky' => true,
'@PHP8x4Migration' => true,
'declare_strict_types' => true,
'no_redundant_readonly_property' => true,
'modernize_types_casting' => true,
'no_superfluous_phpdoc_tags' => [
'allow_mixed' => true,
'remove_inheritdoc' => true,
],
'class_attributes_separation' => [
'elements' => [
'const' => 'one',
'method' => 'one',
'property' => 'one',
'trait_import' => 'none',
],
],
'global_namespace_import' => [
'import_classes' => true,
'import_constants' => true,
'import_functions' => true,
],
'ordered_imports' => [
'sort_algorithm' => 'alpha',
'imports_order' => ['class', 'function', 'const'],
],
'yoda_style' => [
'always_move_variable' => true,
'equal' => false,
'identical' => false,
'less_and_greater' => false,
],
'phpdoc_to_comment' => false,
'native_function_invocation' => [
'include' => ['@compiler_optimized'],
'scope' => 'namespaced',
'strict' => true,
],
'native_constant_invocation' => true,
'strict_param' => true,
'strict_comparison' => true,
])
->setRiskyAllowed(true)
->setFinder($finder)
;
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Rahul Chavan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
97 changes: 67 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,36 @@
# Console Profiler Bundle

[![CI](https://github.com/rcsofttech85/ConsoleProfilerBundle/actions/workflows/ci.yaml/badge.svg)](https://github.com/rcsofttech85/ConsoleProfilerBundle/actions/workflows/ci.yaml)
[![Version](https://img.shields.io/packagist/v/rcsofttech/console-profiler-bundle.svg?label=stable)](https://packagist.org/packages/rcsofttech/console-profiler-bundle)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/828f3e302ce84185a0b0befdac5f1b27)](https://app.codacy.com/gh/rcsofttech85/ConsoleProfilerBundle/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
[![Codacy Badge](https://app.codacy.com/project/badge/Coverage/828f3e302ce84185a0b0befdac5f1b27)](https://app.codacy.com/gh/rcsofttech85/ConsoleProfilerBundle/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)

If you've ever watched a long-running Symfony console command crawl and
wondered, "Is this thing leaking memory? Am I hammering the database with N+1
queries right now?" — this bundle is for you.

The standard Symfony Profiler is amazing for HTTP requests, but it doesn't help
you much when a queue worker is eating up RAM in the background. The Console
Profiler Bundle hooks right into your terminal to give you a live, self-updating
dashboard while your commands are actually running.

It doesn't bloat your production environment, and the UI won't break your
standard output logs.
Profiler Bundle hooks right into your terminal to give you a live, **premium TUI
dashboard** while your commands are actually running.

![Console Profiler Dashboard](docs/dashboard.png)

---

## Why use this over the Web Profiler?
## Features

The web profiler gives you a detailed snapshot after a request finishes. This
bundle is built for **real-time CLI visibility**.
* Live, auto-refreshing TUI dashboard pinned to the top of your terminal
* Memory usage, peak memory, and growth rate with color-coded bars
* Real-time trend indicators (`↑` `↓` `→`) for memory and SQL
* CPU user/system time tracking via `getrusage()`
* Automatic SQL query counting via Doctrine DBAL 4 Middleware
* JSON profile export for CI pipeline regression testing
* Exit code stamping on command completion
* Zero configuration required — works out of the box
* Graceful degradation without `ext-pcntl` (no auto-refresh)

Here are a few things it can do that the standard profiler can't:

1. **Catch memory leaks before the OOM crash:** We track your *Live Memory
Growth Rate* (in bytes/sec). If your Messenger worker is leaking memory, the
growth rate turns red so you can kill and debug it before the container dies.
2. **Profile CI pipeline performance:** You can configure the bundle to dump a
JSON profile snapshot when a command finishes. You can parse this in your CI
(like GitHub Actions) to fail a build if someone introduces a massive N+1
query regression.
3. **Capture exit codes cleanly:** When you string commands together in bash,
it's easy to lose track of what failed. The profiler freezes on completion
and stamps the final exit code right at the top.
---

## Installation

Expand Down Expand Up @@ -65,13 +63,12 @@ console_profiler:
refresh_interval: 1

# Don't bother profiling these noisy default commands
# (these four are the defaults — add your own as needed)
excluded_commands:
- 'list'
- 'help'
- 'completion'
- '_complete'
- 'cache:clear'
- 'cache:warmup'

# Set this to a path to save a JSON dump for CI regression testing
profile_dump_path: '%kernel.project_dir%/var/log/profiler/last_run.json'
Expand Down Expand Up @@ -114,16 +111,56 @@ The JSON dump tracks memory, CPU times, SQL counts, and more.

---

## How it works under the hood
## JSON Profile Schema

When `profile_dump_path` is configured, the following JSON is written
on command completion:

```json
{
"timestamp": "2024-01-15T10:30:00+00:00",
"command": "app:import-data",
"environment": "dev",
"exit_code": 0,
"duration_seconds": 12.4523,
"memory": {
"usage_bytes": 16777216,
"peak_bytes": 33554432,
"limit_bytes": 268435456,
"growth_rate_bytes_per_sec": 524288.0
},
"cpu": {
"user_seconds": 8.12,
"system_seconds": 0.34
},
"counters": {
"sql_queries": 142,
"loaded_classes": 312,
"declared_functions": 1204,
"included_files": 89,
"gc_cycles": 2
},
"system": {
"php_version": "8.4.12",
"sapi": "cli",
"pid": 12345,
"opcache_enabled": true,
"xdebug_enabled": false
}
}
```

---

## Contributing

We wanted this to be fast and safe, so we leaned on modern PHP features:
Contributions are welcome! Please:

* **PHP 8.4 Property Hooks:** We use hooks to pull live memory directly via
`memory_get_usage()` instead of caching stale data.
* **Ext-PCNTL:** We use async signals (`SIGALRM`) to safely redraw the dashboard
once a second without freezing or interrupting your actual command logic.
* **Doctrine Middleware:** We wrap Doctrine DBAL 4 connections natively, meaning
you don't have to change your repository code to count queries accurately.
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/my-feature`)
3. Ensure tests pass: `vendor/bin/phpunit`
4. Ensure static analysis passes: `vendor/bin/phpstan analyze`
5. Submit a pull request

## License

Expand Down
Loading
Loading