feat: 增加 Metadata 失败原因诊断

This commit is contained in:
chuan
2026-08-10 18:01:31 +08:00
parent 0f9072565d
commit 4671a1d0ed
6 changed files with 89 additions and 5 deletions
+1
View File
@@ -348,6 +348,7 @@
- [x] 增加配置查询完整校验原子保存并发修订和统一重启提示
- [x] 增加 Web 诊断页和配置管理页
- [x] 精简诊断页即时指标并由趋势图承担重复的资源和吞吐数据
- [x] 在系统诊断中区分 Peer 下载结果和 Metadata 内容有效性并展示 Peer 失败原因构成
- [x] 使用页签拆分配置分类并隐藏底层配置存储位置
- [x] 将主配置和内容过滤规则合并为唯一 `config.toml`
+11
View File
@@ -104,6 +104,17 @@ pub(crate) async fn stats(State(state): State<ApiState>) -> Json<StatsResponse>
metadata_ok_from_sample_snapshot: dht.metadata_success_from_sample_snapshot,
metadata_ok_from_active_lookup: dht.metadata_success_from_active_lookup,
metadata_failed: dht.metadata_peer_failed,
metadata_peer_canceled: dht.metadata_peer_canceled,
metadata_peer_failure_cache_hits: dht.metadata_peer_failure_cache_hits,
metadata_peer_failure_cache_entries: dht.metadata_peer_failure_cache_entries,
metadata_failure_timeout: observability.metadata_failure_timeout,
metadata_failure_connect: observability.metadata_failure_connect,
metadata_failure_no_extension: observability.metadata_failure_no_extension,
metadata_failure_send: observability.metadata_failure_send,
metadata_failure_size_limit: observability.metadata_failure_size_limit,
metadata_failure_sha1: observability.metadata_failure_sha1,
metadata_failure_parse: observability.metadata_failure_parse,
metadata_failure_other: observability.metadata_failure_other,
metadata_filtered: observability
.metadata_failure_size_limit
.saturating_add(filtered.total()),
+10
View File
@@ -205,6 +205,16 @@ mod tests {
.unwrap();
assert_eq!(json["metadata_filtered"], 0);
assert_eq!(json["metadata_filtered_too_many_files"], 0);
assert_eq!(json["metadata_failure_timeout"], 0);
assert_eq!(json["metadata_failure_connect"], 0);
assert_eq!(json["metadata_failure_no_extension"], 0);
assert_eq!(json["metadata_failure_send"], 0);
assert_eq!(json["metadata_failure_size_limit"], 0);
assert_eq!(json["metadata_failure_sha1"], 0);
assert_eq!(json["metadata_failure_parse"], 0);
assert_eq!(json["metadata_failure_other"], 0);
assert_eq!(json["metadata_peer_canceled"], 0);
assert_eq!(json["metadata_peer_failure_cache_hits"], 0);
assert_eq!(json["disk_state"], "normal");
assert!(json["disk_available_bytes"].is_null());
assert_eq!(json["backup_created"], 0);
+11
View File
@@ -68,6 +68,17 @@ pub(crate) struct StatsResponse {
pub(crate) metadata_ok_from_sample_snapshot: u64,
pub(crate) metadata_ok_from_active_lookup: u64,
pub(crate) metadata_failed: u64,
pub(crate) metadata_peer_canceled: u64,
pub(crate) metadata_peer_failure_cache_hits: u64,
pub(crate) metadata_peer_failure_cache_entries: usize,
pub(crate) metadata_failure_timeout: u64,
pub(crate) metadata_failure_connect: u64,
pub(crate) metadata_failure_no_extension: u64,
pub(crate) metadata_failure_send: u64,
pub(crate) metadata_failure_size_limit: u64,
pub(crate) metadata_failure_sha1: u64,
pub(crate) metadata_failure_parse: u64,
pub(crate) metadata_failure_other: u64,
pub(crate) metadata_filtered: u64,
pub(crate) metadata_filtered_too_large: u64,
pub(crate) metadata_filtered_invalid_info_hash: u64,
+44 -5
View File
@@ -3,7 +3,7 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { AlertTriangle, Database, Gauge, HardDrive, LoaderCircle, Network } from '@lucide/vue'
import { use } from 'echarts/core'
import { CanvasRenderer } from 'echarts/renderers'
import { LineChart } from 'echarts/charts'
import { LineChart, PieChart } from 'echarts/charts'
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import type { EChartsOption } from 'echarts'
import VChart from 'vue-echarts'
@@ -13,7 +13,7 @@ import { getCurrentDiagnostics, getDiagnosticHistory, getStats } from '@/lib/api
import { formatBytes, formatDate } from '@/lib/format'
import type { CurrentDiagnostics, DiagnosticHistory, DiagnosticSample, ServiceStats } from '@/types/api'
use([CanvasRenderer, LineChart, GridComponent, LegendComponent, TooltipComponent])
use([CanvasRenderer, LineChart, PieChart, GridComponent, LegendComponent, TooltipComponent])
const rangeSecs = ref(3_600)
const current = ref<CurrentDiagnostics | null>(null)
@@ -66,10 +66,48 @@ const storageChart = computed(() => chartOption([
const throughputChart = computed(() => chartOption([
{ name: 'UDP 发送', values: perSecond(samples.value, (item) => item.runtime.udp_tx_packets) },
{ name: 'Metadata 成功', values: perSecond(samples.value, (item) => item.runtime.metadata_succeeded) },
{ name: 'Metadata 失败', values: perSecond(samples.value, (item) => item.runtime.metadata_failed) },
{ name: 'Peer 下载成功', values: perSecond(samples.value, (item) => item.runtime.metadata_succeeded) },
{ name: 'Peer 尝试失败', values: perSecond(samples.value, (item) => item.runtime.metadata_failed) },
], '/s'))
const metadataFailureReasons = computed(() => {
if (!stats.value) return []
return [
{ name: '连接失败', value: stats.value.metadata_failure_connect },
{ name: '下载超时', value: stats.value.metadata_failure_timeout },
{ name: '不支持扩展', value: stats.value.metadata_failure_no_extension },
{ name: '发送失败', value: stats.value.metadata_failure_send },
{ name: '超过大小限制', value: stats.value.metadata_failure_size_limit },
{ name: 'SHA1 不一致', value: stats.value.metadata_failure_sha1 },
{ name: '解析失败', value: stats.value.metadata_failure_parse },
{ name: '接收或分片错误', value: stats.value.metadata_failure_other },
].filter((item) => item.value > 0)
})
const metadataFailureChart = computed<EChartsOption>(() => ({
animation: false,
color: ['#ef4444', '#f59e0b', '#8b5cf6', '#ec4899', '#6366f1', '#14b8a6', '#84cc16', '#64748b'],
tooltip: { trigger: 'item', valueFormatter: (value) => `${Number(value).toLocaleString()}` },
legend: { type: 'scroll', bottom: 0, textStyle: { color: '#737373' } },
series: [{
type: 'pie',
radius: ['38%', '66%'],
center: ['50%', '42%'],
minAngle: 2,
stillShowZeroSum: false,
avoidLabelOverlap: true,
label: { color: '#737373', formatter: '{b}\n{d}%' },
data: metadataFailureReasons.value,
}],
}))
const metadataPeerSummary = computed(() => {
if (!stats.value) return ''
const completed = stats.value.metadata_ok + stats.value.metadata_failed
const successRate = completed === 0 ? 0 : stats.value.metadata_ok * 100 / completed
return `成功率 ${successRate.toFixed(2)}% · 成功后取消 ${stats.value.metadata_peer_canceled.toLocaleString()}`
})
const httpChart = computed(() => chartOption([
{ name: 'HTTP 请求', values: perSecond(samples.value, (item) => item.http.requests) },
{ name: '客户端错误', values: perSecond(samples.value, (item) => item.http.client_errors) },
@@ -184,7 +222,8 @@ onBeforeUnmount(() => {
<section class="mt-6 grid gap-4 lg:grid-cols-2">
<article class="chart-card"><div><h2>进程内存</h2><p>{{ sample.process.thread_count ?? '未知' }} 个线程 · {{ sample.process.handle_count ?? '未知' }} 个句柄</p></div><VChart class="diagnostic-chart" :option="memoryChart" autoresize /></article>
<article class="chart-card"><div><h2>RocksDB 内存与压缩</h2><p>SST {{ sample.storage.live_sst_bytes === null ? '未知' : formatBytes(sample.storage.live_sst_bytes) }}</p></div><VChart class="diagnostic-chart" :option="storageChart" autoresize /></article>
<article class="chart-card lg:col-span-2"><div><h2>网络与 Metadata 吞吐</h2><p>{{ history?.samples.length ?? 0 }} 个采样点 · 最近保存 {{ formatDate(current?.status.last_persisted_at ?? null) }}</p></div><VChart class="diagnostic-chart" :option="throughputChart" autoresize /></article>
<article class="chart-card"><div><h2>网络与 Peer 下载吞吐</h2><p>{{ history?.samples.length ?? 0 }} 个采样点 · 最近保存 {{ formatDate(current?.status.last_persisted_at ?? null) }}</p></div><VChart class="diagnostic-chart" :option="throughputChart" autoresize /></article>
<article class="chart-card"><div><h2>Peer 失败原因构成</h2><p>{{ metadataPeerSummary }}</p></div><VChart class="diagnostic-chart" :option="metadataFailureChart" autoresize /></article>
<article class="chart-card lg:col-span-2"><div><h2>HTTP 请求与错误</h2><p>当前并发 {{ sample.http.active_requests }} · P95 {{ sample.http.latency_p95_millis === null ? '暂无' : `${sample.http.latency_p95_millis} ms` }}</p></div><VChart class="diagnostic-chart" :option="httpChart" autoresize /></article>
</section>
</template>
+12
View File
@@ -112,8 +112,20 @@ export interface ServiceStats {
disk_transitions: number
disk_rejected_new_work: number
nodes: number
metadata_peer_attempts: number
metadata_ok: number
metadata_failed: number
metadata_peer_canceled: number
metadata_peer_failure_cache_hits: number
metadata_peer_failure_cache_entries: number
metadata_failure_timeout: number
metadata_failure_connect: number
metadata_failure_no_extension: number
metadata_failure_send: number
metadata_failure_size_limit: number
metadata_failure_sha1: number
metadata_failure_parse: number
metadata_failure_other: number
metadata_in_flight: number
metadata_filtered: number
persistence_inserted: number