chore: initial snapshot of 2meet-data-optimizer v0.1.0

Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
This commit is contained in:
2026-07-31 05:06:36 +08:00
commit d36bb954d1
206 changed files with 66538 additions and 0 deletions
+252
View File
@@ -0,0 +1,252 @@
<?php
// phpcs:ignore WPDO.AntiEAV -- platform admin UI: stress test SQL example display
/**
* Comment Stress Test template (v2.13.1).
*
* Full async + polling UI mirroring Term / Post / User Stress Test:
* 1. 設定並啟動測試(form: post_id / target / mode / batch_size + Start/Cancel
* 2. 即時進度(progress bar + processed/target/rate/ETA/peak memory
* 3. Benchmark 報告(write metrics + DB sizes + query performance
*
* Variables in scope from render_comment_stress_test():
* $state — TMDO_Comment_Stress_Tester::get_progress(false) output
* $test_comment_count — int, comments matching @wpdo-stress.local email
* $posts — array<post_id, label> of available posts (top by comment_count)
*
* @package WP_Data_Optimizer
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$status_str = (string) ( $state['status'] ?? 'idle' );
$is_running = ( 'running' === $status_str || 'benchmarking' === $status_str );
$current_mode = class_exists( 'TMDO_Mode_Manager' ) ? TMDO_Mode_Manager::get( 'comment' ) : 'disabled';
$mode_color = 'aeav_only' === $current_mode ? '#28a745' : ( 'shadow_read' === $current_mode ? '#dba617' : '#dc3545' );
$mode_optimal = 'aeav_only' === $current_mode;
$settings_url = admin_url( 'tools.php?page=wp-data-optimizer&tab=settings' );
global $wpdb;
$flat_hp_review = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wpdo_comment_hp_review" );
$flat_misc = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wpdo_comment_misc" );
$total_comments = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->comments}" );
$total_commentmeta = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->commentmeta}" );
?>
<div class="wpdo-comment-stress-test-tab">
<h2><?php esc_html_e( 'Comment Entity 壓力測試 & Benchmark', '2meet-data-optimizer' ); ?></h2>
<div style="margin:14px 0;padding:12px 14px;background:#f8d7da;color:#721c24;border-left:4px solid #dc3545;border-radius:4px;font-size:13px;line-height:1.6;">
⚠️ <strong><?php esc_html_e( '此工具僅供開發 / 測試環境使用。', '2meet-data-optimizer' ); ?></strong>
<?php esc_html_e( '會建立大量測試 comment 並 seed 對應 hp_review 群組 keys。請勿在生產環境執行。', '2meet-data-optimizer' ); ?>
</div>
<p class="description">
<?php esc_html_e( '透過自動產生大量測試 comment,評估 comment entity 反 EAV 系統在不同規模下的寫入吞吐與查詢效能。所有測試 comment 均以 @wpdo-stress.local 為 email 後綴,可一鍵清除。', '2meet-data-optimizer' ); ?>
</p>
<!-- Diagnose snapshot -->
<div class="wpdo-card" style="padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);margin-top:20px;">
<h3 style="margin-top:0;"><?php esc_html_e( '當前 Comment Entity 狀態', '2meet-data-optimizer' ); ?></h3>
<table class="widefat striped">
<tr>
<td><?php esc_html_e( 'wp_comments', '2meet-data-optimizer' ); ?></td>
<td><strong><?php echo esc_html( number_format_i18n( $total_comments ) ); ?></strong></td>
<td><?php esc_html_e( 'wp_commentmeta', '2meet-data-optimizer' ); ?></td>
<td><strong><?php echo esc_html( number_format_i18n( $total_commentmeta ) ); ?></strong></td>
</tr>
<tr>
<td><?php esc_html_e( 'Mode', '2meet-data-optimizer' ); ?></td>
<td><strong style="color:<?php echo esc_attr( $mode_color ); ?>;"><?php echo esc_html( $current_mode ); ?></strong></td>
<td><?php esc_html_e( 'Ratio', '2meet-data-optimizer' ); ?></td>
<td><strong>1:<?php echo esc_html( (string) ( $total_comments > 0 ? round( $total_commentmeta / $total_comments, 2 ) : 0 ) ); ?></strong></td>
</tr>
<tr>
<td><code>wpdo_comment_hp_review</code></td>
<td><strong><?php echo esc_html( number_format_i18n( $flat_hp_review ) ); ?></strong> rows</td>
<td><code>wpdo_comment_misc</code></td>
<td><strong><?php echo esc_html( number_format_i18n( $flat_misc ) ); ?></strong> rows</td>
</tr>
<tr>
<td colspan="2"><?php esc_html_e( 'Stress 測試 comment 數', '2meet-data-optimizer' ); ?></td>
<td colspan="2"><strong id="wpdo-cst-count" style="color:#dc3545;font-size:18px;"><?php echo esc_html( number_format_i18n( $test_comment_count ) ); ?></strong></td>
</tr>
</table>
</div>
<!-- 驗證反 EAV 優化指南 -->
<div class="wpdo-card" style="padding:20px;background:<?php echo $mode_optimal ? '#e8f5e9' : '#fff3cd'; ?>;border-left:4px solid <?php echo esc_attr( $mode_color ); ?>;border-radius:6px;margin-top:16px;font-size:13px;line-height:1.7;">
<h3 style="margin-top:0;font-size:14px;">
<?php if ( $mode_optimal ) : ?>
✅ <?php esc_html_e( 'Comment Mode = aeav_only — 已具備驗證優化的條件', '2meet-data-optimizer' ); ?>
<?php else : ?>
⚠️
<?php
printf(
/* translators: %s: current mode */
esc_html__( 'Comment Mode = %s — 此模式下壓力測試結果不會展示完整反 EAV 優化效果', '2meet-data-optimizer' ),
'<code style="background:#fff;padding:2px 6px;border-radius:3px;">' . esc_html( $current_mode ) . '</code>'
);
?>
<?php endif; ?>
</h3>
<p style="margin:8px 0 0 0;">
<strong><?php esc_html_e( '想看 wp_commentmeta 真實減量?必須兩條件同時滿足:', '2meet-data-optimizer' ); ?></strong>
</p>
<ol style="margin:6px 0 8px 22px;padding:0;">
<li>
<?php
printf(
/* translators: 1: open code, 2: close code */
esc_html__( 'Comment mode 設為 %1$saeav_only%2$s(前往設定 tab → Entity Bridge → Comment entity', '2meet-data-optimizer' ),
'<code style="background:#fff;padding:1px 5px;border-radius:3px;">',
'</code>'
);
?>
<?php if ( ! $mode_optimal ) : ?>
<a href="<?php echo esc_url( $settings_url ); ?>" class="button button-small" style="margin-left:8px;">→ <?php esc_html_e( '前往設定', '2meet-data-optimizer' ); ?></a>
<?php endif; ?>
</li>
<li>
<?php esc_html_e( '寫入模式選 🐢 Realistic(走 wp_insert_comment + Hook Bus,會被攔截短路 wp_commentmeta', '2meet-data-optimizer' ); ?>
</li>
</ol>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:20px;">
<!-- Left: Configure & Start -->
<div class="wpdo-card" style="padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);">
<h3 style="margin-top:0;"><?php esc_html_e( '1. 設定並啟動測試', '2meet-data-optimizer' ); ?></h3>
<table class="form-table" style="margin-top:0;">
<tr>
<th style="width:35%;"><label for="wpdo-cst-post-id"><?php esc_html_e( '目標 Post', '2meet-data-optimizer' ); ?></label></th>
<td>
<select id="wpdo-cst-post-id" class="regular-text">
<?php if ( empty( $posts ) ) : ?>
<option value=""><?php esc_html_e( '— 沒有可用的 publish post —', '2meet-data-optimizer' ); ?></option>
<?php else : ?>
<?php foreach ( $posts as $pid => $label ) : ?>
<option value="<?php echo esc_attr( (string) $pid ); ?>">
<?php echo esc_html( $label ); ?>
</option>
<?php endforeach; ?>
<?php endif; ?>
</select>
<p class="description"><?php esc_html_e( '所有測試 comment 會 attach 到此 post。建議用 hp_listing post — seed 後 hp_rating 走 hp_review 群組。', '2meet-data-optimizer' ); ?></p>
</td>
</tr>
<tr>
<th><label for="wpdo-cst-target"><?php esc_html_e( '要建立的 comment 數', '2meet-data-optimizer' ); ?></label></th>
<td>
<input type="number" id="wpdo-cst-target" min="1" max="100000" value="500" class="regular-text" />
<p class="description"><?php esc_html_e( '常用:100 / 500 / 1,000 / 10,000(上限 100,000', '2meet-data-optimizer' ); ?></p>
</td>
</tr>
<tr>
<th><label><?php esc_html_e( '寫入模式', '2meet-data-optimizer' ); ?></label></th>
<td>
<label style="display:block;margin-bottom:6px;">
<input type="radio" name="wpdo-cst-mode" value="fast" checked />
<strong>Fast</strong> — <?php esc_html_e( '直接 $wpdb->insert,跳過 WP filter chain(最快,但不測 Hook Bus', '2meet-data-optimizer' ); ?>
</label>
<label style="display:block;">
<input type="radio" name="wpdo-cst-mode" value="realistic" />
<strong>Realistic</strong> — <?php esc_html_e( '走 wp_insert_comment + update_comment_meta(較慢,模擬生產路徑 + 觸發 Hook Bus', '2meet-data-optimizer' ); ?>
</label>
</td>
</tr>
<tr>
<th><label for="wpdo-cst-batch"><?php esc_html_e( '批次大小', '2meet-data-optimizer' ); ?></label></th>
<td>
<input type="number" id="wpdo-cst-batch" min="1" max="1000" value="200" class="small-text" />
<p class="description"><?php esc_html_e( '每批 8 秒 wall-clock 上限。Fast 建議 200-1000Realistic 建議 10-30。', '2meet-data-optimizer' ); ?></p>
</td>
</tr>
</table>
<p>
<button type="button" class="button button-primary button-large" id="wpdo-cst-start" <?php disabled( $is_running || empty( $posts ) ); ?>>
<?php esc_html_e( '🚀 啟動壓力測試', '2meet-data-optimizer' ); ?>
</button>
<button type="button" class="button" id="wpdo-cst-cancel" <?php disabled( ! $is_running ); ?>>
<?php esc_html_e( '⏹ 取消', '2meet-data-optimizer' ); ?>
</button>
</p>
</div>
<!-- Right: Cleanup + Re-run benchmark -->
<div class="wpdo-card" style="padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);">
<h3 style="margin-top:0;"><?php esc_html_e( '清除測試資料', '2meet-data-optimizer' ); ?></h3>
<p>
<?php esc_html_e( '目前 stress test comment 數:', '2meet-data-optimizer' ); ?>
<strong id="wpdo-cst-count-mirror" style="font-size:18px;color:#dc3545;">
<?php echo esc_html( number_format_i18n( $test_comment_count ) ); ?>
</strong>
</p>
<p class="description">
<?php esc_html_e( '一鍵清除所有 email 後綴 @wpdo-stress.local 的 comment,連同 wp_commentmeta + flat 表的對應 row。', '2meet-data-optimizer' ); ?>
</p>
<p>
<button type="button" class="button button-secondary" id="wpdo-cst-cleanup" <?php disabled( $is_running || 0 === $test_comment_count ); ?>>
<?php esc_html_e( '🗑 清除全部測試 comment', '2meet-data-optimizer' ); ?>
</button>
</p>
<hr style="margin:18px 0;" />
<p>
<button type="button" class="button" id="wpdo-cst-rerun-bench" <?php disabled( $is_running || 0 === $test_comment_count ); ?>>
<?php esc_html_e( '📊 重跑 Benchmark(不新增資料)', '2meet-data-optimizer' ); ?>
</button>
</p>
</div>
</div>
<!-- Progress card (live) -->
<div id="wpdo-cst-progress-card" class="wpdo-card" style="margin-top:20px;padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);<?php echo $is_running ? '' : 'display:none;'; ?>">
<h3 style="margin-top:0;"><?php esc_html_e( '2. 即時進度', '2meet-data-optimizer' ); ?></h3>
<div style="margin-bottom:10px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;font-size:13px;">
<span>
<strong id="wpdo-cst-pg-status"><?php echo esc_html( $status_str ); ?></strong>
· <code id="wpdo-cst-pg-post-id">post #<?php echo esc_html( (string) ( $state['post_id'] ?? '' ) ); ?></code>
· <span id="wpdo-cst-pg-mode"><?php echo esc_html( (string) ( $state['mode'] ?? '' ) ); ?></span> mode
</span>
<span id="wpdo-cst-pg-pct" style="font-weight:600;"><?php echo esc_html( (string) ( $state['pct'] ?? 0 ) ); ?>%</span>
</div>
<div style="height:14px;background:#e0e0e0;border-radius:7px;overflow:hidden;">
<div id="wpdo-cst-pg-bar" style="height:100%;background:linear-gradient(90deg,#28a745,#20c997);width:<?php echo esc_attr( (string) ( $state['pct'] ?? 0 ) ); ?>%;transition:width .4s;"></div>
</div>
</div>
<table style="width:100%;font-size:13px;margin-top:10px;border-collapse:collapse;">
<tr>
<td style="padding:6px;color:#666;"><?php esc_html_e( '已建立', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;font-weight:600;"><span id="wpdo-cst-pg-processed"><?php echo esc_html( (string) ( $state['processed'] ?? 0 ) ); ?></span> / <span id="wpdo-cst-pg-target"><?php echo esc_html( (string) ( $state['target'] ?? 0 ) ); ?></span></td>
<td style="padding:6px;color:#666;"><?php esc_html_e( '速率', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;font-weight:600;"><span id="wpdo-cst-pg-rate"><?php echo esc_html( (string) ( $state['rate_per_sec'] ?? 0 ) ); ?></span> comments/sec</td>
</tr>
<tr>
<td style="padding:6px;color:#666;"><?php esc_html_e( '已耗時', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-cst-pg-elapsed"><?php echo esc_html( (string) ( $state['elapsed_sec'] ?? 0 ) ); ?></span> 秒</td>
<td style="padding:6px;color:#666;"><?php esc_html_e( '預估剩餘', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-cst-pg-eta"><?php echo esc_html( (string) ( $state['eta_sec'] ?? 0 ) ); ?></span> 秒</td>
</tr>
<tr>
<td style="padding:6px;color:#666;"><?php esc_html_e( '完成批次', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-cst-pg-batches"><?php echo esc_html( (string) ( $state['batches_done'] ?? 0 ) ); ?></span></td>
<td style="padding:6px;color:#666;"><?php esc_html_e( 'PHP Peak Mem', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-cst-pg-mem"><?php echo esc_html( (string) round( ( (int) ( $state['peak_memory'] ?? 0 ) ) / 1048576, 1 ) ); ?></span> MB</td>
</tr>
</table>
</div>
<!-- Benchmark report -->
<div id="wpdo-cst-bench-card" class="wpdo-card" style="margin-top:20px;padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);<?php echo ! empty( $state['benchmark'] ) ? '' : 'display:none;'; ?>">
<h3 style="margin-top:0;"><?php esc_html_e( '3. Benchmark 報告', '2meet-data-optimizer' ); ?></h3>
<div id="wpdo-cst-bench-content"></div>
</div>
</div>
+217
View File
@@ -0,0 +1,217 @@
<?php
/**
* One-click User Entity Migration Wizard.
*
* Variables in scope (from class-tmdo-admin.php::render_migration_wizard):
* $preflight array TMDO_Migration_Orchestrator::preflight() snapshot.
* $status array TMDO_Migration_Orchestrator::get_status() snapshot.
*
* @package WP_Data_Optimizer
* @since 2.8.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$ratio = $preflight['ratio'] ?? 0;
$users_count = $preflight['users'] ?? 0;
$usermeta_count = $preflight['usermeta'] ?? 0;
$eav_residue = $preflight['eav_rows'] ?? 0;
$current_mode = $preflight['mode'] ?? 'unknown';
$strategy = $preflight['estimated_strategy'] ?? 'sync';
$est_sec = $preflight['estimated_sec'] ?? 0;
$groups = $preflight['groups'] ?? array();
$state = $status['state'] ?? 'idle';
$active = in_array( $state, array( 'running', 'paused' ), true );
$completed = 'completed' === $state;
$failed = 'failed' === $state;
?>
<div class="wpdo-migration-wizard" data-state="<?php echo esc_attr( $state ); ?>">
<header class="wpdo-mw-header">
<h2><?php esc_html_e( 'User Entity 遷移精靈', '2meet-data-optimizer' ); ?></h2>
<p class="description">
<?php esc_html_e( '把 wp_usermeta 中已註冊為 entity field 的 legacy keys 一次遷移到 flat tables。流程包含自動備份、demote、bulk SQL backfill、shadow_read 驗證、aeav_only cutover、以及最終的 EAV 殘留清除。', '2meet-data-optimizer' ); ?>
</p>
</header>
<!-- Pre-flight diagnostic panel ─────────────────────────────────────── -->
<section class="wpdo-mw-panel wpdo-mw-preflight" id="wpdo-mw-preflight">
<h3><?php esc_html_e( '當前狀態', '2meet-data-optimizer' ); ?></h3>
<div class="wpdo-mw-metrics">
<div class="wpdo-mw-metric">
<span class="wpdo-mw-metric-label"><?php esc_html_e( 'wp_users', '2meet-data-optimizer' ); ?></span>
<span class="wpdo-mw-metric-value" id="wpdo-mw-users"><?php echo esc_html( number_format_i18n( $users_count ) ); ?></span>
</div>
<div class="wpdo-mw-metric">
<span class="wpdo-mw-metric-label"><?php esc_html_e( 'wp_usermeta', '2meet-data-optimizer' ); ?></span>
<span class="wpdo-mw-metric-value" id="wpdo-mw-usermeta"><?php echo esc_html( number_format_i18n( $usermeta_count ) ); ?></span>
</div>
<div class="wpdo-mw-metric wpdo-mw-metric-primary">
<span class="wpdo-mw-metric-label"><?php esc_html_e( '當前 ratio', '2meet-data-optimizer' ); ?></span>
<span class="wpdo-mw-metric-value" id="wpdo-mw-ratio">1:<?php echo esc_html( (string) $ratio ); ?></span>
</div>
<div class="wpdo-mw-metric">
<span class="wpdo-mw-metric-label"><?php esc_html_e( 'EAV 殘留', '2meet-data-optimizer' ); ?></span>
<span class="wpdo-mw-metric-value" id="wpdo-mw-residue"><?php echo esc_html( number_format_i18n( $eav_residue ) ); ?></span>
</div>
<div class="wpdo-mw-metric">
<span class="wpdo-mw-metric-label"><?php esc_html_e( 'user mode', '2meet-data-optimizer' ); ?></span>
<span class="wpdo-mw-metric-value wpdo-mw-mode wpdo-mw-mode-<?php echo esc_attr( $current_mode ); ?>"><?php echo esc_html( $current_mode ); ?></span>
</div>
</div>
<details class="wpdo-mw-groups">
<summary><?php esc_html_e( '各 entity group 詳細狀態', '2meet-data-optimizer' ); ?></summary>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'Group', '2meet-data-optimizer' ); ?></th>
<th><?php esc_html_e( 'Keys', '2meet-data-optimizer' ); ?></th>
<th><?php esc_html_e( 'Flat rows', '2meet-data-optimizer' ); ?></th>
<th><?php esc_html_e( 'EAV 殘留', '2meet-data-optimizer' ); ?></th>
<th><?php esc_html_e( 'Backfill 策略', '2meet-data-optimizer' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $groups as $name => $g ) : ?>
<tr>
<td><code><?php echo esc_html( $name ); ?></code></td>
<td><?php echo esc_html( (string) count( $g['keys'] ) ); ?></td>
<td><?php echo esc_html( (string) $g['flat_rows'] ); ?></td>
<td>
<?php if ( $g['residue'] > 0 ) : ?>
<strong><?php echo esc_html( (string) $g['residue'] ); ?></strong>
<?php else : ?>
<span class="wpdo-mw-zero">0</span>
<?php endif; ?>
</td>
<td><code><?php echo esc_html( $g['strategy'] ); ?></code></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</details>
</section>
<!-- Run-migration panel (idle state) ──────────────────────────────── -->
<section class="wpdo-mw-panel wpdo-mw-run" id="wpdo-mw-run-panel"
<?php echo $active || $completed ? 'hidden' : ''; ?>>
<?php if ( 0 === $eav_residue && 'aeav_only' === $current_mode ) : ?>
<div class="wpdo-mw-nothing-todo">
<h3>✅ <?php esc_html_e( '無事可做', '2meet-data-optimizer' ); ?></h3>
<p><?php esc_html_e( '所有 entity group 已 aeav_only / 0 EAV 殘留。', '2meet-data-optimizer' ); ?></p>
<p>
<?php
echo esc_html(
sprintf(
/* translators: %s: ratio */
__( '當前 ratio1:%s', '2meet-data-optimizer' ),
(string) $ratio
)
);
?>
</p>
</div>
<?php else : ?>
<h3><?php esc_html_e( '執行一鍵遷移', '2meet-data-optimizer' ); ?></h3>
<div class="wpdo-mw-estimate">
<?php
printf(
/* translators: 1: rows count, 2: strategy, 3: estimated seconds */
esc_html__( '將遷移 %1$s 行 EAV 殘留,預估策略:%2$s(約 %3$s 秒)。', '2meet-data-optimizer' ),
'<strong>' . esc_html( number_format_i18n( $eav_residue ) ) . '</strong>',
'<code>' . esc_html( $strategy ) . '</code>',
'<strong>' . esc_html( (string) $est_sec ) . '</strong>'
);
?>
</div>
<fieldset class="wpdo-mw-options">
<legend><?php esc_html_e( '進階選項', '2meet-data-optimizer' ); ?></legend>
<label>
<input type="checkbox" id="wpdo-mw-opt-backup" checked>
<?php esc_html_e( '自動備份 wp_usermeta 至 uploads/wpdo-backups/(推薦)', '2meet-data-optimizer' ); ?>
</label>
<label>
<input type="checkbox" id="wpdo-mw-opt-strict" checked>
<?php esc_html_e( '嚴格驗證(500 users / 10% 抽樣)', '2meet-data-optimizer' ); ?>
</label>
<label>
<input type="checkbox" id="wpdo-mw-opt-24h">
<?php esc_html_e( '24 小時 shadow_read 觀察視窗(生產環境用,會暫停 24h 才繼續)', '2meet-data-optimizer' ); ?>
</label>
<label>
<input type="checkbox" id="wpdo-mw-opt-async">
<?php esc_html_e( '強制 asynccron 推進,不阻塞 HTTP 請求)', '2meet-data-optimizer' ); ?>
</label>
<label>
<input type="checkbox" id="wpdo-mw-opt-dryrun">
<?php esc_html_e( 'Dry-run(不寫入、不刪除,僅模擬流程)', '2meet-data-optimizer' ); ?>
</label>
</fieldset>
<div class="wpdo-mw-confirm">
<label>
<input type="checkbox" id="wpdo-mw-confirm-backup" required>
<?php esc_html_e( '我已了解:流程將從 wp_usermeta 刪除已註冊欄位的 EAV 行,過程不可逆(自動備份提供救援路徑)。', '2meet-data-optimizer' ); ?>
</label>
</div>
<button type="button" class="button button-primary button-hero" id="wpdo-mw-start" disabled>
🚀 <?php esc_html_e( '開始一鍵遷移', '2meet-data-optimizer' ); ?>
</button>
<?php endif; ?>
</section>
<!-- Live progress panel (running state) ───────────────────────────── -->
<section class="wpdo-mw-panel wpdo-mw-progress" id="wpdo-mw-progress-panel"
<?php echo $active || $failed ? '' : 'hidden'; ?>>
<h3 id="wpdo-mw-progress-title"><?php esc_html_e( '執行中…', '2meet-data-optimizer' ); ?></h3>
<div class="wpdo-mw-progress-bar">
<div class="wpdo-mw-progress-fill" id="wpdo-mw-progress-fill"
style="width:<?php echo esc_attr( (string) ( $status['overall_progress'] ?? 0 ) ); ?>%"></div>
<span class="wpdo-mw-progress-pct" id="wpdo-mw-progress-pct"><?php echo esc_html( (string) ( $status['overall_progress'] ?? 0 ) ); ?>%</span>
</div>
<div class="wpdo-mw-current">
<span class="wpdo-mw-current-phase">
<?php esc_html_e( '當前階段:', '2meet-data-optimizer' ); ?>
<code id="wpdo-mw-current-phase-name"><?php echo esc_html( $status['phase'] ?? 'idle' ); ?></code>
</span>
<span class="wpdo-mw-elapsed" id="wpdo-mw-elapsed">0.0s</span>
</div>
<div class="wpdo-mw-live-ratio">
<span><?php esc_html_e( 'Live ratio', '2meet-data-optimizer' ); ?></span>
<span id="wpdo-mw-live-ratio-text">
1:<?php echo esc_html( (string) ( $status['metrics']['ratio_start'] ?? $ratio ) ); ?>
1:<?php echo esc_html( (string) ( $status['metrics']['ratio_now'] ?? $ratio ) ); ?>
</span>
</div>
<div class="wpdo-mw-log-wrap">
<h4><?php esc_html_e( 'Log', '2meet-data-optimizer' ); ?></h4>
<pre class="wpdo-mw-log" id="wpdo-mw-log"><?php echo esc_html( implode( "\n", (array) ( $status['log'] ?? array() ) ) ); ?></pre>
</div>
<div class="wpdo-mw-actions">
<button type="button" class="button" id="wpdo-mw-cancel"><?php esc_html_e( '取消', '2meet-data-optimizer' ); ?></button>
<button type="button" class="button button-primary" id="wpdo-mw-resume" hidden><?php esc_html_e( 'Resume', '2meet-data-optimizer' ); ?></button>
</div>
</section>
<!-- Completed state ───────────────────────────────────────────────── -->
<section class="wpdo-mw-panel wpdo-mw-done" id="wpdo-mw-done-panel"
<?php echo $completed ? '' : 'hidden'; ?>>
<h3>✅ <?php esc_html_e( '遷移完成', '2meet-data-optimizer' ); ?></h3>
<div class="wpdo-mw-done-summary" id="wpdo-mw-done-summary"></div>
<button type="button" class="button" id="wpdo-mw-reset"><?php esc_html_e( '回到診斷頁', '2meet-data-optimizer' ); ?></button>
</section>
</div>
+298
View File
@@ -0,0 +1,298 @@
<?php
/**
* Post Migration Wizard template (v2.10.0).
*
* Variables in scope from render_post_migration_wizard():
* $diagnose — TMDO_Post_Migration::diagnose() output
* $garbage — TMDO_Postmeta_Cleaner::count_garbage('all') output
*
* Sync execution model — each action button POSTs back to this page with a
* query arg + nonce, the action runs to completion in one request, then
* redirects with `wpdo_msg=*` for the success/failure banner.
*
* @package WP_Data_Optimizer
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$page_url = admin_url( 'tools.php?page=wp-data-optimizer&tab=post-migration-wizard' );
$total_eav = 0;
foreach ( $diagnose['groups'] as $g ) {
$total_eav += (int) $g['eav_rows'];
}
$total_flat = 0;
foreach ( $diagnose['groups'] as $g ) {
$total_flat += (int) $g['flat_rows'];
}
$mode_label = array(
'disabled' => __( '🔵 disabled — wp_postmeta 仍是 source-of-truth', '2meet-data-optimizer' ),
'dual_write' => __( '🟡 dual_write — 同時寫 wp_postmeta + flat 表', '2meet-data-optimizer' ),
'shadow_read' => __( '🟠 shadow_read — 寫雙路徑,讀比對', '2meet-data-optimizer' ),
'aeav_only' => __( '🟢 aeav_only — flat 表為 source-of-truth', '2meet-data-optimizer' ),
);
// Action URLs (each carries nonce).
$cleanup_garbage_url = wp_nonce_url(
add_query_arg( array( 'wpdo_postmeta_cleanup' => '1' ), $page_url ),
'wpdo_postmeta_cleanup'
);
$backfill_all_url = wp_nonce_url(
add_query_arg( array( 'wpdo_post_backfill_all' => '1' ), $page_url ),
'wpdo_post_backfill_all'
);
$cutover_legacy_url = wp_nonce_url(
add_query_arg( array( 'wpdo_post_cutover_legacy' => '1' ), $page_url ),
'wpdo_post_cutover_legacy'
);
$promote_dual_write_url = wp_nonce_url(
add_query_arg( array( 'wpdo_post_promote_dual_write' => '1' ), $page_url ),
'wpdo_post_promote_dual_write'
);
$promote_aeav_url = wp_nonce_url(
add_query_arg( array( 'wpdo_post_promote_aeav' => '1' ), $page_url ),
'wpdo_post_promote_aeav'
);
// Status banner from prior action redirect.
// Read-only display banner — server-set redirect message, no form processing.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$msg_raw = isset( $_GET['wpdo_msg'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['wpdo_msg'] ) ) : '';
/**
* Map redirect message code → user-facing string.
*
* @param string $code Message code from wpdo_msg query arg.
* @return string
*/
$wpdo_format_msg = static function ( string $code ): string {
$prefix = strtok( $code, '_' );
$rest = substr( $code, strlen( (string) $prefix ) + 1 );
switch ( $prefix ) {
case 'backfill':
return sprintf(
/* translators: %s: total flat rows */
__( 'Backfill 完成:7 個 group 共 %s 個 post 已寫入 flat 表。', '2meet-data-optimizer' ),
$rest
);
case 'cutover':
return sprintf(
/* translators: %s: rows */
__( 'Legacy hot table cutover 完成:%s 行已 copy 至 flat 表。', '2meet-data-optimizer' ),
$rest
);
case 'promote':
return sprintf(
/* translators: %s: target mode */
__( 'Post mode 已升級至 %s。', '2meet-data-optimizer' ),
$rest
);
case 'postmetacleanupdone':
return sprintf(
/* translators: %s: deleted rows */
__( '已從 wp_postmeta 刪除 %s 行垃圾。', '2meet-data-optimizer' ),
$rest
);
case 'err':
return sprintf(
/* translators: %s: error code */
__( '錯誤:%s(請查看 wpdo_errors 日誌)。', '2meet-data-optimizer' ),
str_replace( '_', ' ', $rest )
);
default:
return $code;
}
};
?>
<style>
.wpdo-post-wizard h2 { margin-top: 1.4em; }
.wpdo-post-wizard .wpdo-card {
background: #fff;
border: 1px solid #c3c4c7;
border-radius: 4px;
padding: 1em 1.4em;
margin: 0.8em 0;
}
.wpdo-post-wizard .wpdo-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1em;
margin: 1em 0;
}
.wpdo-post-wizard .wpdo-stat {
background: #f6f7f7;
border-left: 4px solid #2271b1;
padding: 0.8em 1em;
border-radius: 3px;
}
.wpdo-post-wizard .wpdo-stat-label { font-size: 0.85em; color: #50575e; }
.wpdo-post-wizard .wpdo-stat-value { font-size: 1.6em; font-weight: 600; }
.wpdo-post-wizard .wpdo-mode-banner {
padding: 0.6em 1em;
border-radius: 3px;
margin: 0.8em 0;
}
.wpdo-post-wizard .wpdo-mode-disabled { background: #e5f5fa; border-left: 4px solid #00a0d2; }
.wpdo-post-wizard .wpdo-mode-dual_write { background: #fffbe5; border-left: 4px solid #dba617; }
.wpdo-post-wizard .wpdo-mode-shadow_read { background: #fff4e5; border-left: 4px solid #d54e21; }
.wpdo-post-wizard .wpdo-mode-aeav_only { background: #ecf7ed; border-left: 4px solid #46b450; }
.wpdo-post-wizard table.widefat td.right { text-align: right; }
.wpdo-post-wizard .actions {
margin-top: 1.6em;
padding-top: 1em;
border-top: 1px solid #dcdcde;
}
.wpdo-post-wizard .actions .button { margin-right: 0.4em; margin-bottom: 0.4em; }
</style>
<div class="wpdo-post-wizard">
<?php if ( $msg_raw ) : ?>
<?php
$msg_class = str_starts_with( $msg_raw, 'err' ) ? 'notice-error' : 'notice-success';
$msg_text = $wpdo_format_msg( $msg_raw );
?>
<div class="notice <?php echo esc_attr( $msg_class ); ?> is-dismissible">
<p><?php echo esc_html( $msg_text ); ?></p>
</div>
<?php endif; ?>
<h2><?php esc_html_e( 'Post Entity 遷移精靈', '2meet-data-optimizer' ); ?></h2>
<p class="description">
<?php esc_html_e( '把 wp_postmeta 中的 47 個 managed keys 遷移到 7 張 flat 表(wp_wpdo_post_*)。每個按鈕只執行單一階段;按順序執行可達成完整 EAV → flat 切換。', '2meet-data-optimizer' ); ?>
</p>
<!-- Mode banner -->
<div class="wpdo-mode-banner wpdo-mode-<?php echo esc_attr( $diagnose['mode'] ); ?>">
<strong><?php esc_html_e( '當前 post mode', '2meet-data-optimizer' ); ?></strong>
<?php echo esc_html( $mode_label[ $diagnose['mode'] ] ?? $diagnose['mode'] ); ?>
</div>
<!-- Stats -->
<div class="wpdo-stats">
<div class="wpdo-stat">
<div class="wpdo-stat-label"><?php esc_html_e( 'Posts', '2meet-data-optimizer' ); ?></div>
<div class="wpdo-stat-value"><?php echo esc_html( number_format_i18n( $diagnose['posts'] ) ); ?></div>
</div>
<div class="wpdo-stat">
<div class="wpdo-stat-label"><?php esc_html_e( 'Postmeta', '2meet-data-optimizer' ); ?></div>
<div class="wpdo-stat-value"><?php echo esc_html( number_format_i18n( $diagnose['postmeta'] ) ); ?></div>
</div>
<div class="wpdo-stat">
<div class="wpdo-stat-label"><?php esc_html_e( 'Ratio', '2meet-data-optimizer' ); ?></div>
<div class="wpdo-stat-value">1:<?php echo esc_html( (string) $diagnose['ratio'] ); ?></div>
</div>
<div class="wpdo-stat">
<div class="wpdo-stat-label"><?php esc_html_e( 'Total flat rows', '2meet-data-optimizer' ); ?></div>
<div class="wpdo-stat-value"><?php echo esc_html( number_format_i18n( $total_flat ) ); ?></div>
</div>
</div>
<!-- Group breakdown -->
<h3><?php esc_html_e( '7 個 group 詳細狀態', '2meet-data-optimizer' ); ?></h3>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'Group', '2meet-data-optimizer' ); ?></th>
<th><?php esc_html_e( 'post_type', '2meet-data-optimizer' ); ?></th>
<th class="right"><?php esc_html_e( 'Keys', '2meet-data-optimizer' ); ?></th>
<th class="right"><?php esc_html_e( 'EAV rows', '2meet-data-optimizer' ); ?></th>
<th class="right"><?php esc_html_e( 'Flat rows', '2meet-data-optimizer' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $diagnose['groups'] as $name => $g ) : ?>
<tr>
<td><code><?php echo esc_html( $name ); ?></code></td>
<td><?php echo esc_html( $g['post_type'] ?: '(any)' ); ?></td>
<td class="right"><?php echo esc_html( (string) count( $g['keys'] ) ); ?></td>
<td class="right"><?php echo esc_html( number_format_i18n( (int) $g['eav_rows'] ) ); ?></td>
<td class="right"><?php echo esc_html( number_format_i18n( (int) $g['flat_rows'] ) ); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- Phase actions -->
<div class="wpdo-card">
<h3><?php esc_html_e( '步驟 1:清 wp_postmeta 垃圾(v2.9.0 phase 0', '2meet-data-optimizer' ); ?></h3>
<p>
<?php
printf(
/* translators: 1: total garbage rows, 2: transients, 3: wp_old_date, 4: edit_locks */
esc_html__( '偵測到 %1$s 行可清理垃圾(transients %2$s + _wp_old_date %3$s + 過期 _edit_lock %4$s)。', '2meet-data-optimizer' ),
esc_html( number_format_i18n( (int) $garbage['total'] ) ),
esc_html( number_format_i18n( (int) $garbage['transients'] ) ),
esc_html( number_format_i18n( (int) $garbage['wp_old_date'] ) ),
esc_html( number_format_i18n( (int) $garbage['edit_locks'] ) )
);
?>
</p>
<div class="actions">
<a href="<?php echo esc_url( $cleanup_garbage_url ); ?>"
class="button button-primary <?php echo (int) $garbage['total'] > 0 ? '' : 'disabled'; ?>"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要刪除 wp_postmeta 中的垃圾資料?此動作不可逆。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '清理垃圾資料', '2meet-data-optimizer' ); ?>
</a>
</div>
</div>
<div class="wpdo-card">
<h3><?php esc_html_e( '步驟 2:把 wp_postmeta 既有資料 backfill 至 flat 表(v2.9.3', '2meet-data-optimizer' ); ?></h3>
<p><?php esc_html_e( 'Idempotent — 重跑安全。每個 group 跑一次 bulk SQL pivot。', '2meet-data-optimizer' ); ?></p>
<div class="actions">
<a href="<?php echo esc_url( $backfill_all_url ); ?>" class="button button-primary"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要對 7 個 group 跑 backfill?此動作 idempotent,可重跑。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '把 wp_postmeta backfill 到 7 張 flat 表', '2meet-data-optimizer' ); ?>
</a>
</div>
</div>
<div class="wpdo-card">
<h3><?php esc_html_e( '步驟 3:把 legacy wpdo_hot_hp_listing 抄到 flat 表(v2.9.5', '2meet-data-optimizer' ); ?></h3>
<p><?php esc_html_e( '非破壞 — legacy hot 表保留作為 v3.0.0 rollback safety net。', '2meet-data-optimizer' ); ?></p>
<div class="actions">
<a href="<?php echo esc_url( $cutover_legacy_url ); ?>" class="button"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要 copy wpdo_hot_hp_listing 到 wp_wpdo_post_hp_listing_core', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( 'Copy legacy hot table', '2meet-data-optimizer' ); ?>
</a>
</div>
</div>
<div class="wpdo-card">
<h3><?php esc_html_e( '步驟 4:升級 mode 至 dual_write', '2meet-data-optimizer' ); ?></h3>
<p><?php esc_html_e( 'wp_postmeta 與 flat 表同時寫入。讀仍走 wp_postmeta(生產 safe)。建議至少觀察 24h 後再升級下一階。', '2meet-data-optimizer' ); ?></p>
<div class="actions">
<a href="<?php echo esc_url( $promote_dual_write_url ); ?>" class="button"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要升級 post mode 至 dual_write?此後 update_post_meta() 會雙寫。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '升級 mode → dual_write', '2meet-data-optimizer' ); ?>
</a>
</div>
</div>
<div class="wpdo-card">
<h3><?php esc_html_e( '步驟 5:升級 mode 至 aeav_only(最終 cutover', '2meet-data-optimizer' ); ?></h3>
<p><?php esc_html_e( 'flat 表成為 source-of-truth,讀寫都走 flat。完成後可 wp wpdo post-cleanup --confirm 清掉 wp_postmeta 已遷移 keys。', '2meet-data-optimizer' ); ?></p>
<div class="actions">
<a href="<?php echo esc_url( $promote_aeav_url ); ?>" class="button"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要升級 post mode 至 aeav_only?此後讀路徑切換到 flat 表,wp_postmeta 變成 backup。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '升級 mode → aeav_only', '2meet-data-optimizer' ); ?>
</a>
</div>
</div>
<div class="wpdo-card" style="background: #f6f7f7;">
<h3><?php esc_html_e( '回退路徑(rollback', '2meet-data-optimizer' ); ?></h3>
<p>
<?php esc_html_e( '若任一步驟出問題,可下降 mode', '2meet-data-optimizer' ); ?>
<code>wp option update wpdo_bridge_modes '{"post":"disabled",...}'</code>
<?php esc_html_e( '。Legacy wpdo_hot_* 表完整保留 — 直到 v3.0.0 才考慮 DROP。', '2meet-data-optimizer' ); ?>
</p>
</div>
</div>
+393
View File
@@ -0,0 +1,393 @@
<?php
/**
* Post Stress Test template (v2.11.4).
*
* Full async + polling UI mirroring User Stress Test:
* 1. 設定並啟動測試(form: post_type / target / mode / batch_size + Start/Cancel
* 2. 即時進度(progress bar + processed/target/rate/ETA/peak memory
* 3. Benchmark 報告(write metrics + DB sizes + query performance
*
* Backward-compat: legacy GET ?wpdo_post_stress_create / cleanup / bench
* handlers in admin still work for bookmarked URLs; the new UI uses REST.
*
* Variables in scope from render_post_stress_test():
* $test_post_count — int, posts matching TMDO_STRESS_TEST_ prefix
* $diagnose — TMDO_Post_Migration::diagnose() output
*
* @package WP_Data_Optimizer
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$page_url = admin_url( 'tools.php?page=wp-data-optimizer&tab=post-stress-test' );
$cleanup_url = wp_nonce_url(
add_query_arg( array( 'wpdo_post_stress_cleanup' => '1' ), $page_url ),
'wpdo_post_stress_cleanup'
);
// Read-only display banner — server-set redirect message, no form processing.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$msg_raw = isset( $_GET['wpdo_msg'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['wpdo_msg'] ) ) : '';
$msg_text = '';
$msg_class = '';
if ( '' !== $msg_raw ) {
if ( str_starts_with( $msg_raw, 'stress_cleanup_' ) ) {
$n = (int) substr( $msg_raw, strlen( 'stress_cleanup_' ) );
$msg_text = sprintf(
/* translators: %s: count */
__( '已清除 %s 個 stress test posts(連同 wp_postmeta + 7 張 flat 表的對應 row)。', '2meet-data-optimizer' ),
number_format_i18n( $n )
);
$msg_class = 'notice-success';
} elseif ( str_starts_with( $msg_raw, 'err_' ) ) {
$msg_text = __( '錯誤:請查看 wpdo_errors 日誌。', '2meet-data-optimizer' );
$msg_class = 'notice-error';
}
}
// Live state from the new state machine — drives initial render of cards.
$state = class_exists( 'TMDO_Post_Stress_Tester' ) ? TMDO_Post_Stress_Tester::get_progress( false ) : array();
$wpdo_pst_stat = (string) ( $state['status'] ?? 'idle' );
$is_running = ( 'running' === $wpdo_pst_stat || 'benchmarking' === $wpdo_pst_stat );
// Map of post_type → seed key count, surfaced inline next to the dropdown.
$post_type_options = array(
'product' => array( 'product (WC)', 5 ),
'hp_listing' => array( 'hp_listing (HivePress)', 7 ),
'hp_request' => array( 'hp_request', 5 ),
'hp_vendor' => array( 'hp_vendor', 5 ),
'attachment' => array( 'attachment', 2 ),
'nav_menu_item' => array( 'nav_menu_item', 5 ),
'post' => array( 'post', 2 ),
);
?>
<div class="wpdo-post-stress-test-tab">
<h2><?php esc_html_e( 'Post Entity 壓力測試 & Benchmark', '2meet-data-optimizer' ); ?></h2>
<div style="margin:14px 0;padding:12px 14px;background:#f8d7da;color:#721c24;border-left:4px solid #dc3545;border-radius:4px;font-size:13px;line-height:1.6;">
⚠️ <strong><?php esc_html_e( '此工具僅供開發 / 測試環境使用。', '2meet-data-optimizer' ); ?></strong>
<?php esc_html_e( '會建立大量測試 post 並填滿對應 wp_postmeta + flat 表。請勿在生產環境執行。', '2meet-data-optimizer' ); ?>
</div>
<p class="description">
<?php esc_html_e( '透過自動產生大量測試 post,評估 post entity 反 EAV 系統在不同規模下的寫入吞吐與查詢效能。所有測試 post 均以 TMDO_STRESS_TEST_ 為 post_title 前綴,可一鍵清除。', '2meet-data-optimizer' ); ?>
</p>
<?php if ( '' !== $msg_text ) : ?>
<div class="notice <?php echo esc_attr( $msg_class ); ?> is-dismissible">
<p><?php echo esc_html( $msg_text ); ?></p>
</div>
<?php endif; ?>
<!-- Diagnose snapshot -->
<?php
$current_post_mode = (string) ( $diagnose['mode'] ?? 'disabled' );
$mode_color = 'aeav_only' === $current_post_mode ? '#28a745' : '#dc3545';
$mode_optimal = 'aeav_only' === $current_post_mode;
$settings_tab_url = admin_url( 'tools.php?page=wp-data-optimizer&tab=settings' );
?>
<div class="wpdo-card" style="padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);margin-top:20px;">
<h3 style="margin-top:0;"><?php esc_html_e( '當前 Post Entity 狀態', '2meet-data-optimizer' ); ?></h3>
<table class="widefat striped">
<tr>
<td><?php esc_html_e( 'Posts', '2meet-data-optimizer' ); ?></td>
<td><strong><?php echo esc_html( number_format_i18n( $diagnose['posts'] ) ); ?></strong></td>
<td><?php esc_html_e( 'Postmeta', '2meet-data-optimizer' ); ?></td>
<td><strong><?php echo esc_html( number_format_i18n( $diagnose['postmeta'] ) ); ?></strong></td>
</tr>
<tr>
<td><?php esc_html_e( 'Ratio', '2meet-data-optimizer' ); ?></td>
<td><strong>1:<?php echo esc_html( (string) $diagnose['ratio'] ); ?></strong></td>
<td><?php esc_html_e( 'Mode', '2meet-data-optimizer' ); ?></td>
<td><strong style="color:<?php echo esc_attr( $mode_color ); ?>;"><?php echo esc_html( $current_post_mode ); ?></strong></td>
</tr>
<tr>
<td colspan="2"><?php esc_html_e( 'Stress 測試 post 數', '2meet-data-optimizer' ); ?></td>
<td colspan="2"><strong id="wpdo-pst-count" style="color:#dc3545;font-size:18px;"><?php echo esc_html( number_format_i18n( $test_post_count ) ); ?></strong></td>
</tr>
</table>
</div>
<!-- 驗證反 EAV 優化指南 -->
<div class="wpdo-card" style="padding:20px;background:<?php echo $mode_optimal ? '#e8f5e9' : '#fff3cd'; ?>;border-left:4px solid <?php echo esc_attr( $mode_color ); ?>;border-radius:6px;margin-top:16px;font-size:13px;line-height:1.7;">
<h3 style="margin-top:0;font-size:14px;">
<?php if ( $mode_optimal ) : ?>
✅ <?php esc_html_e( 'Post Mode = aeav_only — 已具備驗證優化的條件', '2meet-data-optimizer' ); ?>
<?php else : ?>
⚠️
<?php
printf(
/* translators: %s: current mode */
esc_html__( 'Post Mode = %s — 此模式下壓力測試結果不會展示反 EAV 優化效果', '2meet-data-optimizer' ),
'<code style="background:#fff;padding:2px 6px;border-radius:3px;">' . esc_html( $current_post_mode ) . '</code>'
);
?>
<?php endif; ?>
</h3>
<p style="margin:8px 0 0 0;">
<strong><?php esc_html_e( '想看 wp_postmeta 真實減量?必須同時滿足兩個條件:', '2meet-data-optimizer' ); ?></strong>
</p>
<ol style="margin:6px 0 8px 22px;padding:0;">
<li>
<?php
printf(
/* translators: 1: settings tab anchor open, 2: settings tab anchor close */
esc_html__( 'Post mode 設為 %1$saeav_only%2$s(前往設定 tab → Entity Bridge → Post entity', '2meet-data-optimizer' ),
'<code style="background:#fff;padding:1px 5px;border-radius:3px;">',
'</code>'
);
?>
<?php if ( ! $mode_optimal ) : ?>
<a href="<?php echo esc_url( $settings_tab_url ); ?>" class="button button-small" style="margin-left:8px;">→ <?php esc_html_e( '前往設定', '2meet-data-optimizer' ); ?></a>
<?php endif; ?>
</li>
<li>
<?php esc_html_e( '寫入模式選 🐢 Realistic(走 wp_insert_post + Hook Bus,會被攔截短路 wp_postmeta', '2meet-data-optimizer' ); ?>
</li>
</ol>
<details style="margin-top:8px;">
<summary style="cursor:pointer;color:#0073aa;font-weight:600;"><?php esc_html_e( '📐 模式 × 寫入路徑 → 預期結果矩陣', '2meet-data-optimizer' ); ?></summary>
<table class="widefat" style="margin-top:8px;background:#fff;font-size:12px;">
<thead>
<tr style="background:#f0f0f0;">
<th><?php esc_html_e( 'Post Mode', '2meet-data-optimizer' ); ?></th>
<th>⚡ Fast</th>
<th>🐢 Realistic</th>
</tr>
</thead>
<tbody>
<tr<?php echo 'disabled' === $current_post_mode ? ' style="background:#fff8e1;"' : ''; ?>>
<td><code>disabled</code></td>
<td>wp_postmeta 5 rows / flat 0 → <strong>1:5(無優化)</strong></td>
<td>wp_postmeta 5 / flat 0 → 1:5(無優化)</td>
</tr>
<tr<?php echo 'dual_write' === $current_post_mode ? ' style="background:#fff8e1;"' : ''; ?>>
<td><code>dual_write</code></td>
<td>wp_postmeta 5 / flat 0 → 1:5</td>
<td>wp_postmeta 5 + flat 1 → 1:5(有 flat 但 wp_postmeta 不減)</td>
</tr>
<tr<?php echo 'shadow_read' === $current_post_mode ? ' style="background:#fff8e1;"' : ''; ?>>
<td><code>shadow_read</code></td>
<td>wp_postmeta 5 / flat 0 → 1:5</td>
<td>wp_postmeta 5 + flat 1 → 1:5(讀走 flat,寫仍雙寫)</td>
</tr>
<tr<?php echo 'aeav_only' === $current_post_mode ? ' style="background:#e8f5e9;font-weight:600;"' : ''; ?>>
<td><code>aeav_only</code></td>
<td>wp_postmeta 5(直 SQL 繞過 Hook Bus/ flat 0 → 1:5 ⚠️</td>
<td>wp_postmeta <strong>0</strong> / flat 1 → <strong>0:1(完全優化)</strong> ✅</td>
</tr>
</tbody>
</table>
<p class="description" style="margin-top:6px;">
<?php esc_html_e( '範例以 nav_menu_item5 keys/post)為基準。Fast 模式直接 $wpdb->insert,故意繞過 Hook Bus → 即使 mode=aeav_only 也會寫滿 wp_postmeta(用途:快速灌 fixture 給 Query Router benchmark)。驗證反 EAV 優化效果一律用 Realistic。', '2meet-data-optimizer' ); ?>
</p>
</details>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:20px;">
<!-- Left: Configure & Start -->
<div class="wpdo-card" style="padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);">
<h3 style="margin-top:0;"><?php esc_html_e( '1. 設定並啟動測試', '2meet-data-optimizer' ); ?></h3>
<table class="form-table" style="margin-top:0;">
<tr>
<th style="width:35%;"><label for="wpdo-pst-post-type"><?php esc_html_e( 'Post Type', '2meet-data-optimizer' ); ?></label></th>
<td>
<select id="wpdo-pst-post-type" class="regular-text">
<?php foreach ( $post_type_options as $pt => $info ) : ?>
<?php
list( $label, $key_count ) = $info;
?>
<option value="<?php echo esc_attr( $pt ); ?>" data-keys="<?php echo esc_attr( (string) $key_count ); ?>">
<?php
printf(
/* translators: 1: post_type slug, 2: human label, 3: number of seeded keys */
esc_html__( '%1$s — %2$s · %3$d keys/post', '2meet-data-optimizer' ),
esc_html( $pt ),
esc_html( $label ),
(int) $key_count
);
?>
</option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( '每筆 post 會 seed 該 post_type 對應 entity group 的標準 meta keysv2.9.1 entity registry)。', '2meet-data-optimizer' ); ?></p>
</td>
</tr>
<tr>
<th><label for="wpdo-pst-target"><?php esc_html_e( '要建立的 post 數', '2meet-data-optimizer' ); ?></label></th>
<td>
<input type="number" id="wpdo-pst-target" min="1" max="100000" value="500" class="regular-text" />
<p class="description"><?php esc_html_e( '常用:100 / 500 / 1,000 / 10,000(上限 100,000', '2meet-data-optimizer' ); ?></p>
</td>
</tr>
<tr>
<th><label><?php esc_html_e( '寫入模式', '2meet-data-optimizer' ); ?></label></th>
<td>
<label style="display:block;margin-bottom:6px;">
<input type="radio" name="wpdo-pst-mode" value="fast" checked />
<strong>Fast</strong> — <?php esc_html_e( '直接 $wpdb->insert,跳過 WP filter chain(最快,但不測 Hook Bus', '2meet-data-optimizer' ); ?>
</label>
<label style="display:block;">
<input type="radio" name="wpdo-pst-mode" value="realistic" />
<strong>Realistic</strong> — <?php esc_html_e( '走 wp_insert_post + update_post_meta(較慢,模擬生產路徑 + 觸發 Hook Bus → mode=dual_write+ 時 flat 表自動填入)', '2meet-data-optimizer' ); ?>
</label>
</td>
</tr>
<tr>
<th><label for="wpdo-pst-batch"><?php esc_html_e( '批次大小', '2meet-data-optimizer' ); ?></label></th>
<td>
<input type="number" id="wpdo-pst-batch" min="1" max="1000" value="200" class="small-text" />
<p class="description"><?php esc_html_e( '每批執行有 8 秒 wall-clock 上限(避免 nginx 504)。Fast 建議 200-1000Realistic 建議 5-20(每 post ~100-300ms)。', '2meet-data-optimizer' ); ?></p>
</td>
</tr>
</table>
<p>
<button type="button" class="button button-primary button-large" id="wpdo-pst-start" <?php disabled( $is_running ); ?>>
<?php esc_html_e( '🚀 啟動壓力測試', '2meet-data-optimizer' ); ?>
</button>
<button type="button" class="button" id="wpdo-pst-cancel" <?php disabled( ! $is_running ); ?>>
<?php esc_html_e( '⏹ 取消', '2meet-data-optimizer' ); ?>
</button>
</p>
</div>
<!-- Right: Cleanup + Re-run benchmark -->
<div class="wpdo-card" style="padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);">
<h3 style="margin-top:0;"><?php esc_html_e( '清除測試資料', '2meet-data-optimizer' ); ?></h3>
<p>
<?php esc_html_e( '目前 stress test post 數:', '2meet-data-optimizer' ); ?>
<strong id="wpdo-pst-count-mirror" style="font-size:18px;color:#dc3545;">
<?php echo esc_html( number_format_i18n( $test_post_count ) ); ?>
</strong>
</p>
<p class="description">
<?php esc_html_e( '一鍵清除所有 post_title 前綴 TMDO_STRESS_TEST_ 的 post,連同 wp_postmeta + 7 張 flat 表的對應 row。', '2meet-data-optimizer' ); ?>
</p>
<p>
<button type="button" class="button button-secondary" id="wpdo-pst-cleanup" <?php disabled( $is_running || 0 === $test_post_count ); ?>>
<?php esc_html_e( '🗑 清除全部測試 post', '2meet-data-optimizer' ); ?>
</button>
</p>
<hr style="margin:18px 0;" />
<p>
<button type="button" class="button" id="wpdo-pst-rerun-bench" <?php disabled( $is_running || 0 === $test_post_count ); ?>>
<?php esc_html_e( '📊 重跑 Benchmark(不新增資料)', '2meet-data-optimizer' ); ?>
</button>
</p>
<p class="description">
<?php esc_html_e( '針對最後一次啟動所選的 post_type 重新量測寫入指標(保留)+ DB 容量 + 查詢效能。', '2meet-data-optimizer' ); ?>
</p>
</div>
</div>
<!-- Progress section (live) -->
<div id="wpdo-pst-progress-card" class="wpdo-card" style="margin-top:20px;padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);<?php echo $is_running ? '' : 'display:none;'; ?>">
<h3 style="margin-top:0;"><?php esc_html_e( '2. 即時進度', '2meet-data-optimizer' ); ?></h3>
<div style="margin-bottom:10px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;font-size:13px;">
<span>
<strong id="wpdo-pst-pg-status"><?php echo esc_html( $wpdo_pst_stat ); ?></strong>
· <code id="wpdo-pst-pg-post-type"><?php echo esc_html( (string) ( $state['post_type'] ?? '' ) ); ?></code>
· <span id="wpdo-pst-pg-mode"><?php echo esc_html( (string) ( $state['mode'] ?? '' ) ); ?></span> mode
</span>
<span id="wpdo-pst-pg-pct" style="font-weight:600;"><?php echo esc_html( (string) ( $state['pct'] ?? 0 ) ); ?>%</span>
</div>
<div style="height:14px;background:#e0e0e0;border-radius:7px;overflow:hidden;">
<div id="wpdo-pst-pg-bar" style="height:100%;background:linear-gradient(90deg,#28a745,#20c997);width:<?php echo esc_attr( (string) ( $state['pct'] ?? 0 ) ); ?>%;transition:width .4s;"></div>
</div>
</div>
<table style="width:100%;font-size:13px;margin-top:10px;border-collapse:collapse;">
<tr>
<td style="padding:6px;color:#666;"><?php esc_html_e( '已建立', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;font-weight:600;"><span id="wpdo-pst-pg-processed"><?php echo esc_html( (string) ( $state['processed'] ?? 0 ) ); ?></span> / <span id="wpdo-pst-pg-target"><?php echo esc_html( (string) ( $state['target'] ?? 0 ) ); ?></span></td>
<td style="padding:6px;color:#666;"><?php esc_html_e( '速率', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;font-weight:600;"><span id="wpdo-pst-pg-rate"><?php echo esc_html( (string) ( $state['rate_per_sec'] ?? 0 ) ); ?></span> posts/sec</td>
</tr>
<tr>
<td style="padding:6px;color:#666;"><?php esc_html_e( '已耗時', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-pst-pg-elapsed"><?php echo esc_html( (string) ( $state['elapsed_sec'] ?? 0 ) ); ?></span> 秒</td>
<td style="padding:6px;color:#666;"><?php esc_html_e( '預估剩餘', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-pst-pg-eta"><?php echo esc_html( (string) ( $state['eta_sec'] ?? 0 ) ); ?></span> 秒</td>
</tr>
<tr>
<td style="padding:6px;color:#666;"><?php esc_html_e( '完成批次', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-pst-pg-batches"><?php echo esc_html( (string) ( $state['batches_done'] ?? 0 ) ); ?></span></td>
<td style="padding:6px;color:#666;"><?php esc_html_e( 'PHP Peak Mem', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-pst-pg-mem"><?php echo esc_html( (string) round( ( (int) ( $state['peak_memory'] ?? 0 ) ) / 1048576, 1 ) ); ?></span> MB</td>
</tr>
</table>
</div>
<!-- Benchmark report -->
<div id="wpdo-pst-bench-card" class="wpdo-card" style="margin-top:20px;padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);<?php echo ! empty( $state['benchmark'] ) ? '' : 'display:none;'; ?>">
<h3 style="margin-top:0;"><?php esc_html_e( '3. Benchmark 報告', '2meet-data-optimizer' ); ?></h3>
<div id="wpdo-pst-bench-content"></div>
</div>
<!-- 7-group seed map info -->
<div class="wpdo-card" style="padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);margin-top:20px;">
<h3 style="margin-top:0;"><?php esc_html_e( '4. TMDO_Post_Stress_Tester seed map7 個 post_type', '2meet-data-optimizer' ); ?></h3>
<p class="description">
<?php esc_html_e( '每個 stress post 自動 seed 對應 group 的 canonical meta keysv2.9.1 entity registry 定義)。', '2meet-data-optimizer' ); ?>
</p>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'post_type', '2meet-data-optimizer' ); ?></th>
<th><?php esc_html_e( 'Seeded keys', '2meet-data-optimizer' ); ?></th>
<th><?php esc_html_e( 'Flat 表', '2meet-data-optimizer' ); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td><code>product</code></td>
<td>_price, _regular_price, _stock, _stock_status, _sku</td>
<td><code>wp_wpdo_post_wc_product</code></td>
</tr>
<tr>
<td><code>hp_listing</code></td>
<td>hp_price, hp_status, hp_featured, hp_verified, hp_vendor, hp_view_count, hp_expired_time</td>
<td><code>wp_wpdo_post_hp_listing_core</code></td>
</tr>
<tr>
<td><code>hp_request</code></td>
<td>hp_status, hp_user, hp_budget, hp_view_count, hp_expired_time</td>
<td><code>wp_wpdo_post_hp_request_core</code></td>
</tr>
<tr>
<td><code>hp_vendor</code></td>
<td>hp_user, hp_verified, hp_hourly_rate, hp_rating_count, hp_rating</td>
<td><code>wp_wpdo_post_hp_vendor_core</code></td>
</tr>
<tr>
<td><code>attachment</code></td>
<td>_wp_attached_file, _wp_attachment_image_alt</td>
<td><code>wp_wpdo_post_attachment</code></td>
</tr>
<tr>
<td><code>nav_menu_item</code></td>
<td>_menu_item_type, _menu_item_object_id, _menu_item_object, _menu_item_target, _menu_item_url</td>
<td><code>wp_wpdo_post_nav_menu_item</code></td>
</tr>
<tr>
<td><code>post</code></td>
<td>_thumbnail_id, _edit_last</td>
<td><code>wp_wpdo_post_wp_core</code></td>
</tr>
</tbody>
</table>
</div>
</div>
+248
View File
@@ -0,0 +1,248 @@
<?php
// phpcs:ignore WPDO.AntiEAV -- platform admin UI: stress test SQL example display
/**
* Term Stress Test template (v2.13.0).
*
* Full async + polling UI mirroring Post / User Stress Test:
* 1. 設定並啟動測試(form: taxonomy / target / mode / batch_size + Start/Cancel
* 2. 即時進度(progress bar + processed/target/rate/ETA/peak memory
* 3. Benchmark 報告(write metrics + DB sizes + query performance
*
* Variables in scope from render_term_stress_test():
* $state — TMDO_Term_Stress_Tester::get_progress(false) output
* $test_term_count — int, terms matching wpdo-stress- slug prefix
* $taxonomies — array<slug,label> of available taxonomies
*
* @package WP_Data_Optimizer
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$status_str = (string) ( $state['status'] ?? 'idle' );
$is_running = ( 'running' === $status_str || 'benchmarking' === $status_str );
$current_mode = class_exists( 'TMDO_Mode_Manager' ) ? TMDO_Mode_Manager::get( 'term' ) : 'disabled';
$mode_color = 'aeav_only' === $current_mode ? '#28a745' : ( 'shadow_read' === $current_mode ? '#dba617' : '#dc3545' );
$mode_optimal = 'aeav_only' === $current_mode;
$settings_url = admin_url( 'tools.php?page=wp-data-optimizer&tab=settings' );
global $wpdb;
$flat_hp_taxonomy = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wpdo_term_hp_taxonomy" );
$flat_misc = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}wpdo_term_misc" );
$total_terms = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->terms}" );
$total_termmeta = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->termmeta}" );
?>
<div class="wpdo-term-stress-test-tab">
<h2><?php esc_html_e( 'Term Entity 壓力測試 & Benchmark', '2meet-data-optimizer' ); ?></h2>
<div style="margin:14px 0;padding:12px 14px;background:#f8d7da;color:#721c24;border-left:4px solid #dc3545;border-radius:4px;font-size:13px;line-height:1.6;">
⚠️ <strong><?php esc_html_e( '此工具僅供開發 / 測試環境使用。', '2meet-data-optimizer' ); ?></strong>
<?php esc_html_e( '會建立大量測試 term 並 seed 對應 hp_taxonomy 群組 keys。請勿在生產環境執行。', '2meet-data-optimizer' ); ?>
</div>
<p class="description">
<?php esc_html_e( '透過自動產生大量測試 term,評估 term entity 反 EAV 系統在不同規模下的寫入吞吐與查詢效能。所有測試 term 均以 wpdo-stress- 為 slug 前綴,可一鍵清除。', '2meet-data-optimizer' ); ?>
</p>
<!-- Diagnose snapshot -->
<div class="wpdo-card" style="padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);margin-top:20px;">
<h3 style="margin-top:0;"><?php esc_html_e( '當前 Term Entity 狀態', '2meet-data-optimizer' ); ?></h3>
<table class="widefat striped">
<tr>
<td><?php esc_html_e( 'wp_terms', '2meet-data-optimizer' ); ?></td>
<td><strong><?php echo esc_html( number_format_i18n( $total_terms ) ); ?></strong></td>
<td><?php esc_html_e( 'wp_termmeta', '2meet-data-optimizer' ); ?></td>
<td><strong><?php echo esc_html( number_format_i18n( $total_termmeta ) ); ?></strong></td>
</tr>
<tr>
<td><?php esc_html_e( 'Mode', '2meet-data-optimizer' ); ?></td>
<td><strong style="color:<?php echo esc_attr( $mode_color ); ?>;"><?php echo esc_html( $current_mode ); ?></strong></td>
<td><?php esc_html_e( 'Ratio', '2meet-data-optimizer' ); ?></td>
<td><strong>1:<?php echo esc_html( (string) ( $total_terms > 0 ? round( $total_termmeta / $total_terms, 2 ) : 0 ) ); ?></strong></td>
</tr>
<tr>
<td><code>wpdo_term_hp_taxonomy</code></td>
<td><strong><?php echo esc_html( number_format_i18n( $flat_hp_taxonomy ) ); ?></strong> rows</td>
<td><code>wpdo_term_misc</code></td>
<td><strong><?php echo esc_html( number_format_i18n( $flat_misc ) ); ?></strong> rows</td>
</tr>
<tr>
<td colspan="2"><?php esc_html_e( 'Stress 測試 term 數', '2meet-data-optimizer' ); ?></td>
<td colspan="2"><strong id="wpdo-tst-count" style="color:#dc3545;font-size:18px;"><?php echo esc_html( number_format_i18n( $test_term_count ) ); ?></strong></td>
</tr>
</table>
</div>
<!-- 驗證反 EAV 優化指南 -->
<div class="wpdo-card" style="padding:20px;background:<?php echo $mode_optimal ? '#e8f5e9' : '#fff3cd'; ?>;border-left:4px solid <?php echo esc_attr( $mode_color ); ?>;border-radius:6px;margin-top:16px;font-size:13px;line-height:1.7;">
<h3 style="margin-top:0;font-size:14px;">
<?php if ( $mode_optimal ) : ?>
✅ <?php esc_html_e( 'Term Mode = aeav_only — 已具備驗證優化的條件', '2meet-data-optimizer' ); ?>
<?php else : ?>
⚠️
<?php
printf(
/* translators: %s: current mode */
esc_html__( 'Term Mode = %s — 此模式下壓力測試結果不會展示完整反 EAV 優化效果', '2meet-data-optimizer' ),
'<code style="background:#fff;padding:2px 6px;border-radius:3px;">' . esc_html( $current_mode ) . '</code>'
);
?>
<?php endif; ?>
</h3>
<p style="margin:8px 0 0 0;">
<strong><?php esc_html_e( '想看 wp_termmeta 真實減量?必須兩條件同時滿足:', '2meet-data-optimizer' ); ?></strong>
</p>
<ol style="margin:6px 0 8px 22px;padding:0;">
<li>
<?php
printf(
/* translators: 1: open code, 2: close code */
esc_html__( 'Term mode 設為 %1$saeav_only%2$s(前往設定 tab → Entity Bridge → Term entity', '2meet-data-optimizer' ),
'<code style="background:#fff;padding:1px 5px;border-radius:3px;">',
'</code>'
);
?>
<?php if ( ! $mode_optimal ) : ?>
<a href="<?php echo esc_url( $settings_url ); ?>" class="button button-small" style="margin-left:8px;">→ <?php esc_html_e( '前往設定', '2meet-data-optimizer' ); ?></a>
<?php endif; ?>
</li>
<li>
<?php esc_html_e( '寫入模式選 🐢 Realistic(走 wp_insert_term + Hook Bus,會被攔截短路 wp_termmeta', '2meet-data-optimizer' ); ?>
</li>
</ol>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:20px;">
<!-- Left: Configure & Start -->
<div class="wpdo-card" style="padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);">
<h3 style="margin-top:0;"><?php esc_html_e( '1. 設定並啟動測試', '2meet-data-optimizer' ); ?></h3>
<table class="form-table" style="margin-top:0;">
<tr>
<th style="width:35%;"><label for="wpdo-tst-taxonomy"><?php esc_html_e( 'Taxonomy', '2meet-data-optimizer' ); ?></label></th>
<td>
<select id="wpdo-tst-taxonomy" class="regular-text">
<?php foreach ( $taxonomies as $slug => $label ) : ?>
<option value="<?php echo esc_attr( $slug ); ?>" <?php selected( $slug, 'listing_category' ); ?>>
<?php echo esc_html( $slug . ' — ' . $label ); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( '建議用 HivePress 自訂 taxonomylisting_category / listing_tag 等)— seed 後 hp_sort_order/hp_default/hp_icon 三 keys 走 hp_taxonomy 群組。', '2meet-data-optimizer' ); ?></p>
</td>
</tr>
<tr>
<th><label for="wpdo-tst-target"><?php esc_html_e( '要建立的 term 數', '2meet-data-optimizer' ); ?></label></th>
<td>
<input type="number" id="wpdo-tst-target" min="1" max="100000" value="500" class="regular-text" />
<p class="description"><?php esc_html_e( '常用:100 / 500 / 1,000 / 10,000(上限 100,000', '2meet-data-optimizer' ); ?></p>
</td>
</tr>
<tr>
<th><label><?php esc_html_e( '寫入模式', '2meet-data-optimizer' ); ?></label></th>
<td>
<label style="display:block;margin-bottom:6px;">
<input type="radio" name="wpdo-tst-mode" value="fast" checked />
<strong>Fast</strong> — <?php esc_html_e( '直接 $wpdb->insert,跳過 WP filter chain(最快,但不測 Hook Bus', '2meet-data-optimizer' ); ?>
</label>
<label style="display:block;">
<input type="radio" name="wpdo-tst-mode" value="realistic" />
<strong>Realistic</strong> — <?php esc_html_e( '走 wp_insert_term + update_term_meta(較慢,模擬生產路徑 + 觸發 Hook Bus', '2meet-data-optimizer' ); ?>
</label>
</td>
</tr>
<tr>
<th><label for="wpdo-tst-batch"><?php esc_html_e( '批次大小', '2meet-data-optimizer' ); ?></label></th>
<td>
<input type="number" id="wpdo-tst-batch" min="1" max="1000" value="200" class="small-text" />
<p class="description"><?php esc_html_e( '每批 8 秒 wall-clock 上限。Fast 建議 200-1000Realistic 建議 10-30。', '2meet-data-optimizer' ); ?></p>
</td>
</tr>
</table>
<p>
<button type="button" class="button button-primary button-large" id="wpdo-tst-start" <?php disabled( $is_running ); ?>>
<?php esc_html_e( '🚀 啟動壓力測試', '2meet-data-optimizer' ); ?>
</button>
<button type="button" class="button" id="wpdo-tst-cancel" <?php disabled( ! $is_running ); ?>>
<?php esc_html_e( '⏹ 取消', '2meet-data-optimizer' ); ?>
</button>
</p>
</div>
<!-- Right: Cleanup + Re-run benchmark -->
<div class="wpdo-card" style="padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);">
<h3 style="margin-top:0;"><?php esc_html_e( '清除測試資料', '2meet-data-optimizer' ); ?></h3>
<p>
<?php esc_html_e( '目前 stress test term 數:', '2meet-data-optimizer' ); ?>
<strong id="wpdo-tst-count-mirror" style="font-size:18px;color:#dc3545;">
<?php echo esc_html( number_format_i18n( $test_term_count ) ); ?>
</strong>
</p>
<p class="description">
<?php esc_html_e( '一鍵清除所有 slug 前綴 wpdo-stress- 的 term,連同 wp_termmeta + wp_term_taxonomy + flat 表的對應 row。', '2meet-data-optimizer' ); ?>
</p>
<p>
<button type="button" class="button button-secondary" id="wpdo-tst-cleanup" <?php disabled( $is_running || 0 === $test_term_count ); ?>>
<?php esc_html_e( '🗑 清除全部測試 term', '2meet-data-optimizer' ); ?>
</button>
</p>
<hr style="margin:18px 0;" />
<p>
<button type="button" class="button" id="wpdo-tst-rerun-bench" <?php disabled( $is_running || 0 === $test_term_count ); ?>>
<?php esc_html_e( '📊 重跑 Benchmark(不新增資料)', '2meet-data-optimizer' ); ?>
</button>
</p>
</div>
</div>
<!-- Progress card (live) -->
<div id="wpdo-tst-progress-card" class="wpdo-card" style="margin-top:20px;padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);<?php echo $is_running ? '' : 'display:none;'; ?>">
<h3 style="margin-top:0;"><?php esc_html_e( '2. 即時進度', '2meet-data-optimizer' ); ?></h3>
<div style="margin-bottom:10px;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;font-size:13px;">
<span>
<strong id="wpdo-tst-pg-status"><?php echo esc_html( $status_str ); ?></strong>
· <code id="wpdo-tst-pg-taxonomy"><?php echo esc_html( (string) ( $state['taxonomy'] ?? '' ) ); ?></code>
· <span id="wpdo-tst-pg-mode"><?php echo esc_html( (string) ( $state['mode'] ?? '' ) ); ?></span> mode
</span>
<span id="wpdo-tst-pg-pct" style="font-weight:600;"><?php echo esc_html( (string) ( $state['pct'] ?? 0 ) ); ?>%</span>
</div>
<div style="height:14px;background:#e0e0e0;border-radius:7px;overflow:hidden;">
<div id="wpdo-tst-pg-bar" style="height:100%;background:linear-gradient(90deg,#28a745,#20c997);width:<?php echo esc_attr( (string) ( $state['pct'] ?? 0 ) ); ?>%;transition:width .4s;"></div>
</div>
</div>
<table style="width:100%;font-size:13px;margin-top:10px;border-collapse:collapse;">
<tr>
<td style="padding:6px;color:#666;"><?php esc_html_e( '已建立', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;font-weight:600;"><span id="wpdo-tst-pg-processed"><?php echo esc_html( (string) ( $state['processed'] ?? 0 ) ); ?></span> / <span id="wpdo-tst-pg-target"><?php echo esc_html( (string) ( $state['target'] ?? 0 ) ); ?></span></td>
<td style="padding:6px;color:#666;"><?php esc_html_e( '速率', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;font-weight:600;"><span id="wpdo-tst-pg-rate"><?php echo esc_html( (string) ( $state['rate_per_sec'] ?? 0 ) ); ?></span> terms/sec</td>
</tr>
<tr>
<td style="padding:6px;color:#666;"><?php esc_html_e( '已耗時', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-tst-pg-elapsed"><?php echo esc_html( (string) ( $state['elapsed_sec'] ?? 0 ) ); ?></span> 秒</td>
<td style="padding:6px;color:#666;"><?php esc_html_e( '預估剩餘', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-tst-pg-eta"><?php echo esc_html( (string) ( $state['eta_sec'] ?? 0 ) ); ?></span> 秒</td>
</tr>
<tr>
<td style="padding:6px;color:#666;"><?php esc_html_e( '完成批次', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-tst-pg-batches"><?php echo esc_html( (string) ( $state['batches_done'] ?? 0 ) ); ?></span></td>
<td style="padding:6px;color:#666;"><?php esc_html_e( 'PHP Peak Mem', '2meet-data-optimizer' ); ?></td>
<td style="padding:6px;"><span id="wpdo-tst-pg-mem"><?php echo esc_html( (string) round( ( (int) ( $state['peak_memory'] ?? 0 ) ) / 1048576, 1 ) ); ?></span> MB</td>
</tr>
</table>
</div>
<!-- Benchmark report -->
<div id="wpdo-tst-bench-card" class="wpdo-card" style="margin-top:20px;padding:20px;background:#fff;border-radius:8px;box-shadow:0 1px 4px rgba(0,0,0,.08);<?php echo ! empty( $state['benchmark'] ) ? '' : 'display:none;'; ?>">
<h3 style="margin-top:0;"><?php esc_html_e( '3. Benchmark 報告', '2meet-data-optimizer' ); ?></h3>
<div id="wpdo-tst-bench-content"></div>
</div>
</div>