feat(ops): 添加QPS脉搏线图并优化指标布局
- 添加实时QPS/TPS历史数据追踪(最近60个数据点) - 在平均QPS/TPS上方添加SVG脉搏线图(sparkline) - 将延迟和TTFT卡片的指标布局从2列改为3列 - 恢复Max指标显示(P95/P90/P50/Avg/Max)
This commit is contained in:
@@ -162,6 +162,25 @@ const displayRealTimeTps = computed(() => {
|
||||
return typeof v === 'number' && Number.isFinite(v) ? v : 0
|
||||
})
|
||||
|
||||
// Sparkline history (keep last 60 data points)
|
||||
const qpsHistory = ref<number[]>([])
|
||||
const tpsHistory = ref<number[]>([])
|
||||
const MAX_HISTORY_POINTS = 60
|
||||
|
||||
watch([displayRealTimeQps, displayRealTimeTps], ([newQps, newTps]) => {
|
||||
// Add new data points
|
||||
qpsHistory.value.push(newQps)
|
||||
tpsHistory.value.push(newTps)
|
||||
|
||||
// Keep only last N points
|
||||
if (qpsHistory.value.length > MAX_HISTORY_POINTS) {
|
||||
qpsHistory.value.shift()
|
||||
}
|
||||
if (tpsHistory.value.length > MAX_HISTORY_POINTS) {
|
||||
tpsHistory.value.shift()
|
||||
}
|
||||
})
|
||||
|
||||
const qpsPeakLabel = computed(() => {
|
||||
const v = overview.value?.qps?.peak
|
||||
if (typeof v !== 'number') return '-'
|
||||
@@ -866,6 +885,16 @@ function openJobsDetails() {
|
||||
|
||||
<!-- Average QPS/TPS -->
|
||||
<div class="col-span-2">
|
||||
<!-- Sparkline -->
|
||||
<svg v-if="qpsHistory.length > 1" class="mb-2 h-8 w-full" viewBox="0 0 200 32" preserveAspectRatio="none">
|
||||
<polyline
|
||||
:points="qpsHistory.map((v, i) => `${(i / (qpsHistory.length - 1)) * 200},${32 - (v / Math.max(...qpsHistory, 1)) * 28}`).join(' ')"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
class="text-blue-500"
|
||||
/>
|
||||
</svg>
|
||||
<div class="text-[10px] font-bold uppercase text-gray-400">{{ t('admin.ops.average') }}</div>
|
||||
<div class="mt-1 flex items-center gap-4 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
<span>QPS: <span class="font-bold">{{ qpsAvgLabel }}</span></span>
|
||||
@@ -974,7 +1003,7 @@ function openJobsDetails() {
|
||||
</div>
|
||||
<span class="text-xs font-bold text-gray-400">ms (P99)</span>
|
||||
</div>
|
||||
<div class="mt-3 grid grid-cols-2 gap-x-4 gap-y-1 text-xs">
|
||||
<div class="mt-3 grid grid-cols-3 gap-x-3 gap-y-1 text-xs">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">P95:</span>
|
||||
<span class="font-bold" :class="getLatencyColor(durationP95Ms)">{{ durationP95Ms ?? '-' }}ms</span>
|
||||
@@ -991,6 +1020,10 @@ function openJobsDetails() {
|
||||
<span class="text-gray-500">Avg:</span>
|
||||
<span class="font-bold" :class="getLatencyColor(durationAvgMs)">{{ durationAvgMs ?? '-' }}ms</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">Max:</span>
|
||||
<span class="font-bold" :class="getLatencyColor(durationMaxMs)">{{ durationMaxMs ?? '-' }}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1015,7 +1048,7 @@ function openJobsDetails() {
|
||||
</div>
|
||||
<span class="text-xs font-bold text-gray-400">ms (P99)</span>
|
||||
</div>
|
||||
<div class="mt-3 grid grid-cols-2 gap-x-4 gap-y-1 text-xs">
|
||||
<div class="mt-3 grid grid-cols-3 gap-x-3 gap-y-1 text-xs">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">P95:</span>
|
||||
<span class="font-bold" :class="getLatencyColor(ttftP95Ms)">{{ ttftP95Ms ?? '-' }}ms</span>
|
||||
@@ -1032,6 +1065,10 @@ function openJobsDetails() {
|
||||
<span class="text-gray-500">Avg:</span>
|
||||
<span class="font-bold" :class="getLatencyColor(ttftAvgMs)">{{ ttftAvgMs ?? '-' }}ms</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">Max:</span>
|
||||
<span class="font-bold" :class="getLatencyColor(ttftMaxMs)">{{ ttftMaxMs ?? '-' }}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user