diff --git a/.github/workflows/demo-e2e.yml b/.github/workflows/demo-e2e.yml index 8ac71f8..0a1950d 100644 --- a/.github/workflows/demo-e2e.yml +++ b/.github/workflows/demo-e2e.yml @@ -17,8 +17,13 @@ on: - '.github/workflows/demo-e2e.yml' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: demo-e2e: + if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'no_run') name: Demo E2E (Docker + Selenium) runs-on: ubuntu-latest timeout-minutes: 20 diff --git a/.github/workflows/plugin-build.yml b/.github/workflows/plugin-build.yml index 0c56b54..6727228 100644 --- a/.github/workflows/plugin-build.yml +++ b/.github/workflows/plugin-build.yml @@ -12,8 +12,13 @@ on: - 'jetbrains-plugin/**' - '.github/workflows/plugin-build.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: + if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'no_run') name: Build & Test Plugin runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8bb165e..87a0717 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -160,12 +160,45 @@ jobs: name: php_mariadb_profiler-php${{ matrix.php }}-${{ matrix.ts }}-${{ matrix.arch }} path: dist\*.dll + # --------------------------------------------------------------------------- + # JetBrains Plugin build + # --------------------------------------------------------------------------- + build-plugin: + name: JetBrains Plugin + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Run tests + working-directory: jetbrains-plugin + run: ./gradlew test + + - name: Build plugin + working-directory: jetbrains-plugin + run: ./gradlew buildPlugin + + - uses: actions/upload-artifact@v4 + with: + name: mariadb-profiler-viewer-plugin + path: jetbrains-plugin/build/distributions/*.zip + if-no-files-found: error + # --------------------------------------------------------------------------- # Create GitHub Release # --------------------------------------------------------------------------- release: name: Create Release - needs: [build-linux, build-windows] + needs: [build-linux, build-windows, build-plugin] runs-on: ubuntu-latest permissions: contents: write @@ -181,7 +214,7 @@ jobs: - name: Collect release assets run: | mkdir -p release - find artifacts -type f \( -name "*.so" -o -name "*.dll" \) -exec cp {} release/ \; + find artifacts -type f \( -name "*.so" -o -name "*.dll" -o -name "*.zip" \) -exec cp {} release/ \; echo "=== Release assets ===" ls -lh release/ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 62d0d0e..96bf54a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,8 +6,13 @@ on: pull_request: branches: ['**'] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test-cli: + if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'no_run') name: CLI Tests (PHP ${{ matrix.php-version }}) runs-on: ubuntu-latest strategy: @@ -42,6 +47,7 @@ jobs: run: php tests/test_integration.php build-extension: + if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'no_run') name: Build Extension (PHP ${{ matrix.php-version }}) runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 021c59c..4b946d2 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -12,8 +12,13 @@ on: - 'ext/**' - '.github/workflows/windows-build.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build-windows: + if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'no_run') name: Windows - PHP ${{ matrix.php }} ${{ matrix.ts }} ${{ matrix.arch }} runs-on: windows-${{ matrix.os }} defaults: diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac20968 --- /dev/null +++ b/README.md @@ -0,0 +1,147 @@ +# MariaDB Profiler for PHP + +A MariaDB/MySQL query profiler that runs as a PHP extension. It hooks into PHP's `mysqlnd` driver to intercept, record, and analyze all executed SQL queries. + +Works with any database access method that uses mysqlnd, including PDO, mysqli, and Laravel Eloquent. + +## IntelliJ Plugin Ready +ss0
+ss1
+ss2 + + + +## Components + +| Component | Description | +|---|---| +| `ext/mariadb_profiler/` | PHP extension (C) | +| `cli/` | CLI profiler management tool (PHP) | +| `demo/` | Docker-based web demo (Laravel + WebSocket) | +| `jetbrains-plugin/` | JetBrains IDE plugin (Kotlin) | + +## Features + +- **Query interception** — Captures all SQL queries at the mysqlnd level +- **Context tags** — Stack-based tags to group queries by business logic +- **PHP backtrace** — Records call stacks at configurable depth +- **Prepared statement support** — Logs bound parameters (PHP 7.0+) +- **SQL analysis** — Automatic extraction of table and column names +- **Job management** — Concurrent profiling sessions with parent-child relationships +- **Cross-platform** — Linux / macOS / Windows + +## Requirements + +| Component | Requirements | +|---|---| +| Extension | PHP 5.3 – 8.4+, mysqlnd | +| CLI tool | PHP 5.3+, Composer | +| Demo | Docker, Docker Compose | + +## Installation + +### Building the Extension + +```bash +cd ext/mariadb_profiler +phpize +./configure --enable-mariadb_profiler +make +sudo make install +``` + +Add the following to php.ini: + +```ini +extension=mariadb_profiler.so +mariadb_profiler.enabled=1 +mariadb_profiler.log_dir=/var/log/mariadb_profiler +``` + +### CLI Tool + +```bash +composer install +``` + +## Configuration (php.ini) + +```ini +mariadb_profiler.enabled = 1 ; Enable the extension +mariadb_profiler.log_dir = /tmp/mariadb_profiler ; Log output directory +mariadb_profiler.raw_log = 1 ; Write raw text logs +mariadb_profiler.job_check_interval = 1 ; Interval to check jobs.json (seconds) +mariadb_profiler.trace_depth = 0 ; Backtrace depth (0 = disabled) +``` + +## Usage + +### Managing Profiling Jobs + +```bash +# Start a job +php cli/mariadb_profiler.php job start [] + +# End a job +php cli/mariadb_profiler.php job end + +# List jobs +php cli/mariadb_profiler.php job list + +# Show parsed queries +php cli/mariadb_profiler.php job show [--tag=] + +# Show raw log +php cli/mariadb_profiler.php job raw + +# Export as JSON +php cli/mariadb_profiler.php job export + +# Show tag summary +php cli/mariadb_profiler.php job tags + +# Show caller summary +php cli/mariadb_profiler.php job callers + +# Purge completed jobs +php cli/mariadb_profiler.php job purge +``` + +### Tagging Queries in PHP + +```php +// Push a tag +mariadb_profiler_tag('checkout_flow'); + +// Queries executed here are tagged with 'checkout_flow' +$db->query('SELECT * FROM orders WHERE user_id = ?'); + +// Get the current tag +$tag = mariadb_profiler_get_tag(); // 'checkout_flow' + +// Pop the tag +mariadb_profiler_untag(); +``` + +### Demo + +```bash +cd demo +docker compose up --build +# Open http://localhost:8080 +``` + +## PHP Function Reference + +| Function | Description | +|---|---| +| `mariadb_profiler_tag(string $tag): void` | Push a context tag onto the stack | +| `mariadb_profiler_untag(?string $tag = null): ?string` | Pop a tag (optionally unwind to a specific tag) | +| `mariadb_profiler_get_tag(): ?string` | Get the current tag (null if none) | + +## Log Formats + +Two files are generated per job: + +- `{job_key}.raw.log` — One query per line in text format (with timestamp, status, tag, and trace) +- `{job_key}.jsonl` — Parsed JSON format with extracted table and column names