fix: полный продакшен-фикс dashboard retention и timeseries по fix37

This commit is contained in:
2026-05-09 16:18:08 +05:00
parent 44c01d39d0
commit b6c86009e6
6 changed files with 35 additions and 33 deletions
+15 -10
View File
@@ -83,8 +83,8 @@ func DashboardTrafficSummary() (vo.DashboardTrafficVo, error) {
result.SinceResetDownloadBytes = r.Download result.SinceResetDownloadBytes = r.Download
result.SinceResetUploadBytes = r.Upload result.SinceResetUploadBytes = r.Upload
now := time.Now().UnixMilli() nowTime := time.Now()
dayStart := now - (now % int64(24*time.Hour/time.Millisecond)) dayStart := time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, nowTime.Location()).UnixMilli()
var today row var today row
if tx := sqliteDB.Raw(`SELECT if tx := sqliteDB.Raw(`SELECT
COALESCE(SUM(rx_bytes),0) AS download, COALESCE(SUM(rx_bytes),0) AS download,
@@ -135,9 +135,6 @@ func DashboardTrafficTimeseries(fromMs int64, toMs int64, bucketMs int64, source
case "hourly": case "hourly":
sourceTable = "traffic_aggregate_hourly" sourceTable = "traffic_aggregate_hourly"
fromColumn = "hour_start" fromColumn = "hour_start"
case "daily":
sourceTable = "traffic_aggregate_daily"
fromColumn = "day_start"
default: default:
sourceTable = "traffic_sample" sourceTable = "traffic_sample"
fromColumn = "sampled_at" fromColumn = "sampled_at"
@@ -171,18 +168,26 @@ func DashboardTrafficTimeseries(fromMs int64, toMs int64, bucketMs int64, source
return filled, nil return filled, nil
} }
func DashboardSystemTimeseries(fromMs int64, toMs int64) ([]vo.DashboardSeriesPointVo, error) { func DashboardSystemTimeseries(fromMs int64, toMs int64, bucketMs int64) ([]vo.DashboardSeriesPointVo, error) {
rows := make([]vo.DashboardSeriesPointVo, 0) rows := make([]vo.DashboardSeriesPointVo, 0)
if !tableExists("metric_sample") { if !tableExists("metric_sample") {
return rows, nil return rows, nil
} }
if toMs < fromMs {
return rows, nil
}
if bucketMs <= 0 {
bucketMs = int64(time.Minute / time.Millisecond)
}
alignedFrom := fromMs - (fromMs % bucketMs)
if tx := sqliteDB.Raw(`SELECT if tx := sqliteDB.Raw(`SELECT
sampled_at AS ts, (? + CAST((sampled_at - ?) / ? AS INTEGER) * ?) AS ts,
cpu_percent AS cpu, AVG(cpu_percent) AS cpu,
mem_percent AS mem AVG(mem_percent) AS mem
FROM metric_sample FROM metric_sample
WHERE sampled_at BETWEEN ? AND ? WHERE sampled_at BETWEEN ? AND ?
ORDER BY sampled_at ASC`, fromMs, toMs).Scan(&rows); tx.Error != nil { GROUP BY ts
ORDER BY ts ASC`, alignedFrom, alignedFrom, bucketMs, bucketMs, fromMs, toMs).Scan(&rows); tx.Error != nil {
logrus.Errorf("%v", tx.Error) logrus.Errorf("%v", tx.Error)
return rows, errors.New(constant.SysError) return rows, errors.New(constant.SysError)
} }
+1
View File
@@ -27,6 +27,7 @@ export default {
dashboard: { dashboard: {
stale: "Dashboard data is stale. Retrying automatically...", stale: "Dashboard data is stale. Retrying automatically...",
topPeers24h: "Top peers (24h)", topPeers24h: "Top peers (24h)",
topPeersRange: "Top peers ({range})",
refreshFailed: "Failed to refresh dashboard data", refreshFailed: "Failed to refresh dashboard data",
cpu: "CPU", cpu: "CPU",
ram: "RAM", ram: "RAM",
+1
View File
@@ -25,6 +25,7 @@ export default {
dashboard: { dashboard: {
stale: "Данные дашборда устарели. Выполняется автоматическая повторная попытка...", stale: "Данные дашборда устарели. Выполняется автоматическая повторная попытка...",
topPeers24h: "Топ пиров (24ч)", topPeers24h: "Топ пиров (24ч)",
topPeersRange: "Топ пиров ({range})",
refreshFailed: "Не удалось обновить данные дашборда", refreshFailed: "Не удалось обновить данные дашборда",
cpu: "CPU", cpu: "CPU",
ram: "RAM", ram: "RAM",
+16 -22
View File
@@ -52,7 +52,7 @@
</template> </template>
<div v-loading="timeseriesLoading" class="chart-grid"> <div v-loading="timeseriesLoading" class="chart-grid">
<el-empty <el-empty
v-if="!timeseriesLoading && (timeseries.traffic?.length || 0) === 0" v-if="!timeseriesLoading && !hasTrafficData"
:description="$t('dashboard.noTrafficData')" :description="$t('dashboard.noTrafficData')"
class="chart-empty" class="chart-empty"
/> />
@@ -62,7 +62,7 @@
</el-card> </el-card>
<el-card shadow="never" class="mt-3"> <el-card shadow="never" class="mt-3">
<template #header>{{ $t("dashboard.topPeers24h") }}</template> <template #header>{{ $t("dashboard.topPeersRange", { range }) }}</template>
<el-table :data="topPeers" size="small"> <el-table :data="topPeers" size="small">
<el-table-column prop="name" :label="$t('peer.name')" /> <el-table-column prop="name" :label="$t('peer.name')" />
<el-table-column prop="download" :label="$t('dashboard.download')"> <el-table-column prop="download" :label="$t('dashboard.download')">
@@ -96,7 +96,6 @@ import VChart from "vue-echarts";
import { dashboardSecurityApi, dashboardSummaryApi, dashboardTimeseriesApi, dashboardTopPeersApi } from "@/api/dashboard"; import { dashboardSecurityApi, dashboardSummaryApi, dashboardTimeseriesApi, dashboardTopPeersApi } from "@/api/dashboard";
import { DashboardSummaryVo, DashboardTimeseriesVo, DashboardTopPeerVo, SecurityRiskVo } from "@/api/dashboard/types"; import { DashboardSummaryVo, DashboardTimeseriesVo, DashboardTopPeerVo, SecurityRiskVo } from "@/api/dashboard/types";
import { formatBytes } from "@/utils/byte"; import { formatBytes } from "@/utils/byte";
import { timestampToDateTime } from "@/utils/time";
use([CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent, DataZoomComponent, TitleComponent]); use([CanvasRenderer, LineChart, GridComponent, TooltipComponent, LegendComponent, DataZoomComponent, TitleComponent]);
@@ -164,14 +163,6 @@ const loadDashboard = async () => {
} }
}; };
const systemXAxisLabels = computed(() => {
const allTs = new Set<number>();
for (const item of timeseries.value.system || []) {
allTs.add(item.ts);
}
return Array.from(allTs).sort((a, b) => a - b);
});
const trafficDownloadData = computed(() => const trafficDownloadData = computed(() =>
(timeseries.value.traffic || []).map((item) => [item.ts, item.download || 0]) (timeseries.value.traffic || []).map((item) => [item.ts, item.download || 0])
); );
@@ -180,15 +171,19 @@ const trafficUploadData = computed(() =>
(timeseries.value.traffic || []).map((item) => [item.ts, item.upload || 0]) (timeseries.value.traffic || []).map((item) => [item.ts, item.upload || 0])
); );
const hasTrafficData = computed(() =>
(timeseries.value.traffic || []).some((item) => (item.download || 0) > 0 || (item.upload || 0) > 0)
);
const hasSingleTrafficPoint = computed(() => (timeseries.value.traffic || []).length === 1); const hasSingleTrafficPoint = computed(() => (timeseries.value.traffic || []).length === 1);
const systemMap = computed(() => { const systemCpuData = computed(() =>
const m = new Map<number, { cpu: number; mem: number }>(); (timeseries.value.system || []).map((item) => [item.ts, item.cpu || 0])
for (const item of timeseries.value.system || []) { );
m.set(item.ts, { cpu: item.cpu || 0, mem: item.mem || 0 });
} const systemMemData = computed(() =>
return m; (timeseries.value.system || []).map((item) => [item.ts, item.mem || 0])
}); );
const trafficChartOption = computed(() => ({ const trafficChartOption = computed(() => ({
title: { text: t("dashboard.trafficSeriesChart"), left: "left", textStyle: { fontSize: 14, fontWeight: 600 } }, title: { text: t("dashboard.trafficSeriesChart"), left: "left", textStyle: { fontSize: 14, fontWeight: 600 } },
@@ -237,9 +232,8 @@ const systemChartOption = computed(() => ({
grid: { left: 30, right: 20, top: 50, bottom: 50, containLabel: true }, grid: { left: 30, right: 20, top: 50, bottom: 50, containLabel: true },
dataZoom: [{ type: "inside" }, { type: "slider", height: 16, bottom: 10 }], dataZoom: [{ type: "inside" }, { type: "slider", height: 16, bottom: 10 }],
xAxis: { xAxis: {
type: "category", type: "time",
boundaryGap: false, boundaryGap: false,
data: systemXAxisLabels.value.map((ts) => timestampToDateTime(ts)),
}, },
yAxis: { yAxis: {
type: "value", type: "value",
@@ -253,14 +247,14 @@ const systemChartOption = computed(() => ({
type: "line", type: "line",
smooth: true, smooth: true,
showSymbol: false, showSymbol: false,
data: systemXAxisLabels.value.map((ts) => systemMap.value.get(ts)?.cpu || 0), data: systemCpuData.value,
}, },
{ {
name: t("dashboard.ram"), name: t("dashboard.ram"),
type: "line", type: "line",
smooth: true, smooth: true,
showSymbol: false, showSymbol: false,
data: systemXAxisLabels.value.map((ts) => systemMap.value.get(ts)?.mem || 0), data: systemMemData.value,
}, },
], ],
})); }));
+1
View File
@@ -38,6 +38,7 @@ func InitCron() error {
logrus.Errorf("cron add func CronResetTraffic err: %v", err) logrus.Errorf("cron add func CronResetTraffic err: %v", err)
} }
} }
go service.CleanupStatsRetention()
c.Start() c.Start()
return nil return nil
} }
+1 -1
View File
@@ -75,7 +75,7 @@ func DashboardTimeseries(rangeKey string) (vo.DashboardTimeseriesVo, error) {
if err != nil { if err != nil {
return vo.DashboardTimeseriesVo{}, err return vo.DashboardTimeseriesVo{}, err
} }
systemRows, err := dao.DashboardSystemTimeseries(fromMs, nowMs) systemRows, err := dao.DashboardSystemTimeseries(fromMs, nowMs, bucketMs)
if err != nil { if err != nil {
return vo.DashboardTimeseriesVo{}, err return vo.DashboardTimeseriesVo{}, err
} }