refactor(analysis): 去掉排行 10/30/50 选择器,改为一次取 50 + 前端分页;零点结算昨天
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s

=== 1. 为什么去掉「显示条数」选择器 ===
topk 的 N 不影响查询成本(扫描的 chunk 集合完全相同),实测:
  topk(10)  17833ms / 11 条
  topk(30)  18019ms / 34 条
  topk(50)   7394ms / 54 条
  topk(100) 17553ms / 74 条
波动来自集群负载,与 N 无关 —— 它只决定最后返回多少序列。
所以"选 30"既不更快也不更慢,本质是个翻页开关;
而选它还要重新等一次十几秒的查询,纯亏。
改为:一次 topk(50),前端 10 条/页 → 5 页只需 1 次查询。
附带收益:占比的分母从"前 10 合计"变成"前 50 合计",更接近真实占比。
副标题明确写清"一次取前 50 个应用(占比按这 50 个合计计算)"。

=== 2. 零点结算昨天(daily volume)===
原来只有 30 分钟 ticker 回补,且启动时间不对齐整点 →
昨天最早的一段可能已经超出 Loki 24h 保留期被删,
结算结果系统性偏低,图上却标"完整一天",属静默错数。
新增 nextMidnightFinalize():每天 00:01 触发 FinalizeYesterdayVolume()
  - 昨天定稿 partial=false
  - 补一次条数(index/stats,一天一次可接受)
  - 顺带刷新今天 partial=true 的柱子
ticker 仍保留,负责当天数据的 30 分钟刷新。

=== 3. 顺带 ===
- 去掉每日量"其中 N 天平台没有留存记录"的提示文案(按用户要求)
- 修 QuickQueryTemplates.tsx 的未使用变量导致 tsc -b 失败
- 工作区里上一轮中断的"排行体积列"改动一并纳入(app-ranking 返回 bytes,
  TopAppsTable 增加体积列,快照记录 per-app 字节数)

Pod = Hub latest = a16978d2f88b
This commit is contained in:
opsx committed 2026-10-30 17:13:18 +00:00
1 parent 63dd491c5b
commit d4422f1541
22 files changed
+715 -117

No files matched your search

+54 -2
View File
@@ -349,8 +349,60 @@ func (ctrl *LogsController) AppRanking(c *gin.Context) {
limit = v
}
}
rows := services.GetAppRanking(uint(sourceID), time.Unix(0, from), time.Unix(0, to), limit)
utils.Success(c, gin.H{"list": rows, "hours": services.AppVolumeCoverage(uint(sourceID))})
rows := services.GetAppRanking(uint(sourceID), time.Unix(0, from), time.Unix(0, to), limit, c.DefaultQuery("sort", "bytes"))
hours, bytesHours := services.AppVolumeCoverageInfo(uint(sourceID))
utils.Success(c, gin.H{"list": rows, "hours": hours, "bytesHours": bytesHours})
}
// LabelValuesBatch 一次取多个 label 的候选值:GET /logs/label-values?names=app,pod&query={...}
//
// 快捷查询的下拉是级联的(命名空间 → 应用 → Pod),而"应用"本身要走
// app → service_name → container 三级回退。逐个请求时前端一次要发 3 个往返,
// 每个都要经 K8s API 代理到 Loki 索引;合并成一个接口后只发一次。
// 返回 {"app": [...], "service_name": [...], ...},单个 label 失败返回空数组不报错。
func (ctrl *LogsController) LabelValuesBatch(c *gin.Context) {
id, _ := strconv.ParseUint(c.Query("sourceId"), 10, 64)
src := services.GetLogSource(uint(id))
if src == nil {
utils.BadRequest(c, "数据源不存在")
return
}
raw := strings.TrimSpace(c.Query("names"))
if raw == "" {
utils.BadRequest(c, "缺少 names(逗号分隔的 label 名,如 app,pod)")
return
}
names := make([]string, 0, 4)
for _, name := range strings.Split(raw, ",") {
if name = strings.TrimSpace(name); name != "" {
names = append(names, name)
}
}
if len(names) == 0 {
utils.BadRequest(c, "names 里没有有效的 label 名")
return
}
if len(names) > 10 {
utils.BadRequest(c, "一次最多取 10 个 label 的值")
return
}
requestedQuery := strings.TrimSpace(c.Query("query"))
if requestedQuery == "" {
requestedQuery = "{}"
}
query, err := services.AuthorizedLogQuery(src, c.GetUint("userId"), c.GetString("role") == "admin", requestedQuery)
if err != nil {
utils.Forbidden(c, err.Error())
return
}
start, _ := strconv.ParseInt(c.Query("start"), 10, 64)
end, _ := strconv.ParseInt(c.Query("end"), 10, 64)
values, err := services.LokiLabelValuesBatch(c.Request.Context(), src, names, query, start, end)
if err != nil {
utils.Error(c, 502, err.Error())
return
}
utils.Success(c, values)
}
// DailyVolume 每日日志量历史(平台留存,不受 Loki retention 限制)。