Docker Compose環境でMariaDB + Laravel + WebSocketサーバーを起動し、 php-ext-mariadb-salvageのプロファイリング機能をブラウザから操作するデモUI。
- Start/Stop ボタンでプロファイリングジョブを制御
- タブUI でセッション(ジョブ)ごとにログを表示
- xterm.js で
tail -fのリアルタイムログストリーミング
┌─────────────────────────────────────────────────────┐
│ Browser (localhost:8080) │
│ ┌────────────────────────────────────────────────┐ │
│ │ Dashboard (Blade + xterm.js) │ │
│ │ [Start] [Stop] [Run Demo Queries] │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │Tab 1 │ │Tab 2 │ │Tab 3 │ ← セッションタブ │ │
│ │ └──────┘ └──────┘ └──────┘ │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ xterm.js terminal │ ← tail -f ログ │ │
│ │ │ [2025-01-23 10:00:01] │ │ │
│ │ │ SELECT * FROM users... │ │ │
│ │ └──────────────────────────┘ │ │
│ └────────────────────────────────────────────────┘ │
└─────────┬──────────────────────┬────────────────────┘
│ HTTP (API/HTML) │ WebSocket
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ Nginx (:8080) │ │ │
│ ├─ / │──▶│ PHP-FPM (app) │
│ │ Laravel UI │ │ + extension.so │
│ ├─ /api/* │──▶│ + CLI profiler │
│ └─ /ws/* │ └──────────────────┘
│ WebSocket │──▶┌──────────────────┐
│ proxy │ │ Node.js (:3000) │
└─────────────────┘ │ WebSocket server│
│ tail -f logs │
└──────────────────┘
│
┌─────────────────┐ ┌──────┴───────────┐
│ MariaDB (:3306)│◀──│ Shared Volume │
│ demo database │ │ /var/profiler/ │
└─────────────────┘ │ - jobs.json │
│ - *.jsonl │
│ - *.raw.log │
└──────────────────┘
- Image:
mariadb:11 - Port: 3306 (internal)
- DB:
demo, User:demo/demo - Volume: persistent data
- Dockerfile.app (PHP 8.3-fpm ベース)
- ビルドステップ:
- phpize + configure + make で extension をコンパイル
- php.ini に extension 設定追加
- composer create-project laravel/laravel
- カスタムファイル(Controllers, Views, Routes, Migrations)をオーバーレイ
- composer install (profiler CLI依存関係含む)
- migration + seed 実行 (entrypoint)
- Volume:
/var/profiler(ログ共有)
- Dockerfile.ws (Node.js 20 ベース)
- 軽量 WebSocket サーバー (
wsパッケージ) - エンドポイント:
ws://host/ws/logs/<jobKey> - 機能:
tail -f /var/profiler/<jobKey>.raw.logを spawn して WebSocket にストリーム - Volume:
/var/profiler(ログ共有、読み取り専用)
- Image:
nginx:alpine - Port: 8080 (外部公開)
- Config:
/→ PHP-FPM (Laravel)/ws/→ WebSocket proxy (Node.js :3000)
demo/
├── docker-compose.yml
├── .env # DB接続情報等
│
├── docker/
│ ├── app/
│ │ ├── Dockerfile # PHP 8.3-fpm + extension build
│ │ ├── php.ini # extension設定
│ │ └── entrypoint.sh # migration, seed, php-fpm起動
│ ├── nginx/
│ │ └── default.conf # Nginx設定
│ └── websocket/
│ ├── Dockerfile # Node.js
│ ├── package.json
│ └── server.js # WebSocket + tail -f
│
├── laravel-app/ # Laravel カスタムファイルのみ
│ ├── app/Http/Controllers/
│ │ ├── DashboardController.php # UI表示
│ │ ├── ProfilerApiController.php # Job制御 API
│ │ └── DemoQueryController.php # デモクエリ実行
│ ├── database/
│ │ ├── migrations/
│ │ │ └── create_demo_tables.php # users, posts, comments
│ │ └── seeders/
│ │ └── DemoSeeder.php # サンプルデータ
│ ├── resources/views/
│ │ └── dashboard.blade.php # メインUI (xterm.js統合)
│ └── routes/
│ └── web.php # ルート定義
│
└── README.md # 使い方ドキュメント
docker-compose.yml:
services:
mariadb:
image: mariadb:11
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: demo
MYSQL_USER: demo
MYSQL_PASSWORD: demo
volumes:
- mariadb_data:/var/lib/mysql
app:
build:
context: .
dockerfile: docker/app/Dockerfile
volumes:
- profiler_logs:/var/profiler
depends_on:
- mariadb
websocket:
build:
context: docker/websocket
volumes:
- profiler_logs:/var/profiler:ro
nginx:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
depends_on:
- app
- websocket
volumes:
mariadb_data:
profiler_logs:Dockerfile.app の主要ステップ:
FROM php:8.3-fpm
# mysqlnd は PHP に組み込み済み
# 必要パッケージ: autoconf, gcc, make (phpize用)
RUN apt-get update && apt-get install -y autoconf gcc make git unzip
# Extension ビルド
COPY ext/mariadb_profiler /tmp/ext
WORKDIR /tmp/ext
RUN phpize && ./configure && make && make install
# PHP設定
COPY demo/docker/app/php.ini /usr/local/etc/php/conf.d/mariadb_profiler.ini
# Composer + Laravel
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
RUN composer create-project laravel/laravel /var/www/html --prefer-dist
# CLI profiler tool
COPY cli /opt/profiler/cli
COPY composer.json /opt/profiler/
RUN cd /opt/profiler && composer install --no-dev
# カスタム Laravel ファイル
COPY demo/laravel-app/ /var/www/html/const WebSocket = require('ws');
const { spawn } = require('child_process');
const http = require('http');
const server = http.createServer();
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws, req) => {
// URL: /ws/logs/<jobKey>
const jobKey = req.url.replace('/ws/logs/', '');
const logFile = `/var/profiler/${jobKey}.raw.log`;
// touch file if not exists, then tail -f
const tail = spawn('tail', ['-f', logFile]);
tail.stdout.on('data', (data) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(data.toString());
}
});
ws.on('close', () => tail.kill());
});
server.listen(3000);POST /api/profiler/start → ジョブ開始 (returns jobKey)
POST /api/profiler/{key}/stop → ジョブ停止
GET /api/profiler/jobs → ジョブ一覧
POST /api/demo/queries → デモクエリ実行
ProfilerApiController.php:
start(): CLI経由php mariadb_profiler.php job startを実行、jobKeyを返すstop($key): CLI経由php mariadb_profiler.php job end $keyを実行list(): CLI経由php mariadb_profiler.php job list→ JSONパース
DemoQueryController.php:
run(): 各種クエリを実行してプロファイラーにキャプチャさせる- SELECT with JOIN
- INSERT
- UPDATE
- Subqueries
- Aggregate queries
UI構成:
┌─────────────────────────────────────────────┐
│ MariaDB Query Profiler Demo │
│ │
│ [▶ Start New Session] [🔄 Run Demo Queries]│
│ │
│ ┌─────────┬─────────┬─────────┐ │
│ │ abc123 │ def456 │ ghi789 │ ← タブ │
│ │ (active)│ (active)│ (done) │ │
│ └─────────┴─────────┴─────────┘ │
│ [■ Stop] [📊 Stats] │
│ │
│ ┌─────────────────────────────────────────┐│
│ │ $ tail -f abc123.raw.log ││
│ │ [2025-01-23 10:00:01.000] SELECT ... ││
│ │ [2025-01-23 10:00:01.050] INSERT ... ││
│ │ [2025-01-23 10:00:01.100] UPDATE ... ││
│ │ █ ││
│ └─────────────────────────────────────────┘│
└─────────────────────────────────────────────┘
使用ライブラリ (CDN):
- xterm.js (v5) + xterm-addon-fit
- Tailwind CSS (CDN)
- Alpine.js (軽量なリアクティブUI)
JavaScript 処理フロー:
- Start クリック →
POST /api/profiler/start→ jobKey取得 - 新タブ作成 → xterm.js Terminal インスタンス生成
- WebSocket接続
ws://host/ws/logs/<jobKey> - WebSocket からデータ受信 →
terminal.write(data) - Stop クリック →
POST /api/profiler/{key}/stop - タブ状態を "done" に更新
server {
listen 80;
# Laravel
root /var/www/html/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# WebSocket proxy
location /ws/ {
proxy_pass http://websocket:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}- docker-compose.yml + Dockerfiles を作成
- Nginx設定 を作成
- WebSocket server.js を作成
- Laravel Controllers (ProfilerApi, DemoQuery, Dashboard) を作成
- Laravel Migrations + Seeders を作成
- Laravel Routes (web.php) を作成
- dashboard.blade.php (メインUI: タブ + xterm.js + Start/Stop) を作成
- entrypoint.sh (migration実行 + php-fpm起動) を作成
- 動作確認用 README.md を作成
- Extension のビルド: Dockerfile内で phpize + make で問題なくビルド可能
- mysqlnd 連携: PHP-FPM で mysqlnd はデフォルト有効。Laravel の PDO MySQL ドライバは mysqlnd を使用
- ログ共有: Docker named volume で app ↔ websocket 間のログファイル共有
- xterm.js + WebSocket: Node.js で
tail -fをストリーミングする標準パターン - CLI 呼び出し: Laravel から
Process::run()でプロファイラーCLIを呼び出し可能
tail -fは対象ファイルが存在しない場合に備え、touchしてからtail -fする- WebSocket 接続切断時に
tailプロセスを確実に kill する - jobs.json のファイルロック競合: app (PHP-FPM) と CLI が同時アクセスするが、既存の flock 実装で対応済み
- Laravel の
.envで DB接続先を Docker サービス名 (mariadb) に設定