fix: полный продакшен-фикс dashboard retention и timeseries по fix37
This commit is contained in:
+15
-10
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user