feat(report): add 24h slow-sql dashboard and metrics api
This commit is contained in:
@@ -17,7 +17,7 @@ import {
|
||||
|
||||
import { fetchFeedbackStats } from '../api/feedbacks'
|
||||
import { fetchEmailStats } from '../api/email'
|
||||
import { fetchDockerStats, fetchRequestMetrics, fetchRunningTasks, fetchServerInfo, fetchTaskStats } from '../api/tasks'
|
||||
import { fetchDockerStats, fetchRequestMetrics, fetchRunningTasks, fetchServerInfo, fetchSlowSqlMetrics, fetchTaskStats } from '../api/tasks'
|
||||
import { fetchBrowserPoolStats } from '../api/browser_pool'
|
||||
import { fetchSystemConfig } from '../api/system'
|
||||
import MetricGrid from '../components/MetricGrid.vue'
|
||||
@@ -38,7 +38,9 @@ const dockerStats = ref(null)
|
||||
const browserPoolStats = ref(null)
|
||||
const systemConfig = ref(null)
|
||||
const requestMetrics = ref(null)
|
||||
const slowSqlMetrics = ref(null)
|
||||
const requestDetailsOpen = ref(false)
|
||||
const slowSqlDetailsOpen = ref(false)
|
||||
const queueTab = ref('running')
|
||||
|
||||
function recordUpdatedAt() {
|
||||
@@ -350,6 +352,66 @@ const requestModuleDesc = computed(() => {
|
||||
return `均值 ${avgMs} · 峰值 ${maxMs} · 慢阈 ${slowThreshold} · 最近 ${lastAt}`
|
||||
})
|
||||
|
||||
const slowSqlTopRows = computed(() => {
|
||||
const rows = Array.isArray(slowSqlMetrics.value?.top_sql) ? slowSqlMetrics.value.top_sql : []
|
||||
return rows.slice(0, 3)
|
||||
})
|
||||
|
||||
const slowSqlWindowHours = computed(() => {
|
||||
const seconds = normalizeCount(slowSqlMetrics.value?.window_seconds)
|
||||
if (seconds <= 0) return 24
|
||||
return Math.max(1, Math.round(seconds / 3600))
|
||||
})
|
||||
|
||||
const slowSqlModuleItems = computed(() => {
|
||||
const items = [
|
||||
{ label: `慢SQL(${slowSqlWindowHours.value}h)`, value: normalizeCount(slowSqlMetrics.value?.total_slow_queries) },
|
||||
{ label: '去重SQL', value: normalizeCount(slowSqlMetrics.value?.unique_sql) },
|
||||
{ label: '平均耗时', value: formatMs(slowSqlMetrics.value?.avg_duration_ms) },
|
||||
{ label: '峰值耗时', value: formatMs(slowSqlMetrics.value?.max_duration_ms) },
|
||||
]
|
||||
|
||||
slowSqlTopRows.value.forEach((row, index) => {
|
||||
items.push({
|
||||
label: `慢SQL${index + 1}`,
|
||||
value: `${formatMs(row?.max_ms)} · ${String(row?.sql || '-')}`,
|
||||
})
|
||||
})
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
const slowSqlModuleDesc = computed(() => {
|
||||
const threshold = formatMs(slowSqlMetrics.value?.slow_threshold_ms)
|
||||
const lastAt = formatUnixTime(slowSqlMetrics.value?.last_slow_ts)
|
||||
return `窗口 ${slowSqlWindowHours.value}h · 慢阈 ${threshold} · 最近 ${lastAt}`
|
||||
})
|
||||
|
||||
const slowSqlDetailTopRows = computed(() => {
|
||||
const rows = Array.isArray(slowSqlMetrics.value?.top_sql) ? slowSqlMetrics.value.top_sql : []
|
||||
return rows.map((row, index) => ({
|
||||
rank: index + 1,
|
||||
sql: String(row?.sql || '-'),
|
||||
count: normalizeCount(row?.count),
|
||||
avg_ms: formatMs(row?.avg_ms),
|
||||
max_ms: formatMs(row?.max_ms),
|
||||
last_seen: formatUnixDateTime(row?.last_ts),
|
||||
sample_params: String(row?.sample_params || '-'),
|
||||
}))
|
||||
})
|
||||
|
||||
const slowSqlRecentRows = computed(() => {
|
||||
const rows = Array.isArray(slowSqlMetrics.value?.recent_slow_sql) ? slowSqlMetrics.value.recent_slow_sql : []
|
||||
return [...rows]
|
||||
.sort((a, b) => Number(b?.time || 0) - Number(a?.time || 0))
|
||||
.map((row) => ({
|
||||
time_text: formatUnixDateTime(row?.time),
|
||||
sql: String(row?.sql || '-'),
|
||||
duration_ms: formatMs(row?.duration_ms),
|
||||
params: String(row?.params || '-'),
|
||||
}))
|
||||
})
|
||||
|
||||
const requestDetailTopPaths = computed(() => {
|
||||
const rows = Array.isArray(requestMetrics.value?.top_paths) ? requestMetrics.value.top_paths : []
|
||||
return rows.map((row, index) => ({
|
||||
@@ -387,6 +449,10 @@ function openRequestDetails() {
|
||||
requestDetailsOpen.value = true
|
||||
}
|
||||
|
||||
function openSlowSqlDetails() {
|
||||
slowSqlDetailsOpen.value = true
|
||||
}
|
||||
|
||||
const configModuleItems = computed(() => [
|
||||
{ label: '定时任务', value: scheduleEnabled.value ? '启用' : '关闭' },
|
||||
{ label: '执行时间', value: scheduleTime.value || '-' },
|
||||
@@ -448,6 +514,13 @@ const mobileModules = computed(() => [
|
||||
tone: 'purple',
|
||||
items: requestModuleItems.value,
|
||||
},
|
||||
{
|
||||
key: 'slow_sql',
|
||||
title: '慢SQL监控',
|
||||
desc: slowSqlModuleDesc.value,
|
||||
tone: 'red',
|
||||
items: slowSqlModuleItems.value,
|
||||
},
|
||||
{
|
||||
key: 'worker',
|
||||
title: '截图线程池',
|
||||
@@ -481,6 +554,7 @@ async function refreshAll(options = {}) {
|
||||
dockerResult,
|
||||
browserPoolResult,
|
||||
requestMetricsResult,
|
||||
slowSqlResult,
|
||||
configResult,
|
||||
] = await Promise.allSettled([
|
||||
fetchTaskStats(),
|
||||
@@ -491,6 +565,7 @@ async function refreshAll(options = {}) {
|
||||
fetchDockerStats(),
|
||||
fetchBrowserPoolStats(),
|
||||
fetchRequestMetrics(),
|
||||
fetchSlowSqlMetrics(),
|
||||
fetchSystemConfig(),
|
||||
])
|
||||
|
||||
@@ -502,6 +577,7 @@ async function refreshAll(options = {}) {
|
||||
if (dockerResult.status === 'fulfilled') dockerStats.value = dockerResult.value
|
||||
if (browserPoolResult.status === 'fulfilled') browserPoolStats.value = browserPoolResult.value
|
||||
if (requestMetricsResult.status === 'fulfilled') requestMetrics.value = requestMetricsResult.value
|
||||
if (slowSqlResult.status === 'fulfilled') slowSqlMetrics.value = slowSqlResult.value
|
||||
if (configResult.status === 'fulfilled') systemConfig.value = configResult.value
|
||||
|
||||
await refreshStats?.()
|
||||
@@ -599,8 +675,15 @@ onUnmounted(() => {
|
||||
<div class="mobile-metric-value">{{ item.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="module.key === 'request'" class="module-extra-actions">
|
||||
<el-button size="small" type="primary" plain @click="openRequestDetails">查看慢接口详情</el-button>
|
||||
<div v-if="module.key === 'request' || module.key === 'slow_sql'" class="module-extra-actions">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
plain
|
||||
@click="module.key === 'request' ? openRequestDetails() : openSlowSqlDetails()"
|
||||
>
|
||||
{{ module.key === 'request' ? '查看慢接口详情' : '查看慢SQL详情' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</section>
|
||||
@@ -644,6 +727,43 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="slowSqlDetailsOpen" title="慢SQL详情(近24小时)" width="min(1080px, 96vw)">
|
||||
<div class="request-dialog-summary app-muted">
|
||||
<span>慢SQL总数:{{ normalizeCount(slowSqlMetrics?.total_slow_queries) }}</span>
|
||||
<span>去重SQL:{{ normalizeCount(slowSqlMetrics?.unique_sql) }}</span>
|
||||
<span>平均耗时:{{ formatMs(slowSqlMetrics?.avg_duration_ms) }}</span>
|
||||
<span>峰值耗时:{{ formatMs(slowSqlMetrics?.max_duration_ms) }}</span>
|
||||
<span>慢阈值:{{ formatMs(slowSqlMetrics?.slow_threshold_ms) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="request-dialog-block">
|
||||
<div class="request-dialog-title">TOP 慢SQL(按出现次数)</div>
|
||||
<div class="table-wrap">
|
||||
<el-table :data="slowSqlDetailTopRows" size="small" max-height="320">
|
||||
<el-table-column prop="rank" label="#" width="60" />
|
||||
<el-table-column prop="sql" label="SQL" min-width="400" show-overflow-tooltip />
|
||||
<el-table-column prop="count" label="次数" width="90" />
|
||||
<el-table-column prop="avg_ms" label="平均耗时" width="120" />
|
||||
<el-table-column prop="max_ms" label="峰值耗时" width="120" />
|
||||
<el-table-column prop="last_seen" label="最近出现" width="180" />
|
||||
<el-table-column prop="sample_params" label="参数样本" min-width="140" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="request-dialog-block">
|
||||
<div class="request-dialog-title">最近慢SQL</div>
|
||||
<div class="table-wrap">
|
||||
<el-table :data="slowSqlRecentRows" size="small" max-height="320">
|
||||
<el-table-column prop="time_text" label="时间" width="180" />
|
||||
<el-table-column prop="sql" label="SQL" min-width="420" show-overflow-tooltip />
|
||||
<el-table-column prop="duration_ms" label="耗时" width="110" />
|
||||
<el-table-column prop="params" label="参数" min-width="130" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user