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
+
+
+
+
+
+
+## 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