Files
komari/utils/utils.go
kdwycz 0f7cd5f438 新增了GPU监控数据接收存储
接收agent发送的gpu监控数据.
参考Record实现, 新增GPURecord表存储监控数据和压缩监控数据.
2025-09-15 20:23:16 +08:00

411 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package utils
import (
"sort"
"strings"
"time"
"github.com/komari-monitor/komari/common"
"github.com/komari-monitor/komari/database/models"
)
// AverageReport 根据 topPercentage 参数计算报告的平均值。
// 如果 topPercentage 为 0则计算所有记录的平均值。
// 如果 topPercentage 大于 0 且小于等于 1则计算从大到小排序后的前 topPercentage 记录的平均值。
func AverageReport(uuid string, time time.Time, records []common.Report, topPercentage float64) models.Record {
count := len(records)
if count == 0 {
return models.Record{}
}
recordsToAverageCount := count
if topPercentage > 0 && topPercentage <= 1 {
recordsToAverageCount = int(float64(count) * topPercentage)
if recordsToAverageCount == 0 && count > 0 {
recordsToAverageCount = 1 // 确保至少选择一个记录除非总数为0
}
}
// 定义一个辅助函数,用于排序和求和
sumAndSort := func(getFloat32Value func(common.Report) float32, getInt64Value func(common.Report) int64, isFloat bool) (float32, int64) {
if isFloat {
sort.Slice(records, func(i, j int) bool {
return getFloat32Value(records[i]) > getFloat32Value(records[j])
})
var sum float32
for i := 0; i < recordsToAverageCount; i++ {
sum += getFloat32Value(records[i])
}
return sum, 0
} else {
sort.Slice(records, func(i, j int) bool {
return getInt64Value(records[i]) > getInt64Value(records[j])
})
var sum int64
for i := 0; i < recordsToAverageCount; i++ {
sum += getInt64Value(records[i])
}
return 0, sum
}
}
var sumCPU, sumLOAD, sumGPU float32
var sumRAM, sumRAMTotal, sumSWAP, sumSWAPTotal, sumDISK, sumDISKTotal, sumNETIn, sumNETOut, sumNETTotalUp, sumNETTotalDown int64
var sumPROCESS, sumConnections, sumConnectionsUDP int
if topPercentage > 0 && topPercentage <= 1 {
sumCPU, _ = sumAndSort(func(r common.Report) float32 { return float32(r.CPU.Usage) }, nil, true)
sumLOAD, _ = sumAndSort(func(r common.Report) float32 { return float32(r.Load.Load1) }, nil, true)
sumGPU, _ = sumAndSort(func(r common.Report) float32 {
if r.GPU != nil {
return float32(r.GPU.AverageUsage)
}
return 0
}, nil, true)
_, sumRAM = sumAndSort(nil, func(r common.Report) int64 { return r.Ram.Used }, false)
//_, sumRAMTotal = sumAndSort(nil, nil, func(r common.Report) int64 { return r.Ram.Total }, false)
_, sumSWAP = sumAndSort(nil, func(r common.Report) int64 { return r.Swap.Used }, false)
//_, sumSWAPTotal = sumAndSort(nil, nil, func(r common.Report) int64 { return r.Swap.Total }, false)
_, sumDISK = sumAndSort(nil, func(r common.Report) int64 { return r.Disk.Used }, false)
//_, sumDISKTotal = sumAndSort(nil, nil, func(r common.Report) int64 { return r.Disk.Total }, false)
_, sumNETIn = sumAndSort(nil, func(r common.Report) int64 { return r.Network.Down }, false)
_, sumNETOut = sumAndSort(nil, func(r common.Report) int64 { return r.Network.Up }, false)
_, sumNETTotalUp = sumAndSort(nil, func(r common.Report) int64 { return r.Network.TotalUp }, false)
_, sumNETTotalDown = sumAndSort(nil, func(r common.Report) int64 { return r.Network.TotalDown }, false)
_, sumInt := sumAndSort(nil, func(r common.Report) int64 { return int64(r.Process) }, false)
sumPROCESS = int(sumInt)
_, sumInt = sumAndSort(nil, func(r common.Report) int64 { return int64(r.Connections.TCP) }, false)
sumConnections = int(sumInt)
_, sumInt = sumAndSort(nil, func(r common.Report) int64 { return int64(r.Connections.UDP) }, false)
sumConnectionsUDP = int(sumInt)
} else { // 计算所有记录的平均值
for _, r := range records {
sumCPU += float32(r.CPU.Usage)
sumLOAD += float32(r.Load.Load1)
if r.GPU != nil {
sumGPU += float32(r.GPU.AverageUsage)
}
sumRAM += r.Ram.Used
sumRAMTotal += r.Ram.Total
sumSWAP += r.Swap.Used
sumSWAPTotal += r.Swap.Total
sumDISK += r.Disk.Used
sumDISKTotal += r.Disk.Total
sumNETIn += r.Network.Down
sumNETOut += r.Network.Up
sumNETTotalUp += r.Network.TotalUp
sumNETTotalDown += r.Network.TotalDown
sumPROCESS += r.Process
sumConnections += r.Connections.TCP
sumConnectionsUDP += r.Connections.UDP
}
}
// 创建新的聚合记录
newRecord := models.Record{
Client: uuid,
Time: models.FromTime(time),
Cpu: sumCPU / float32(recordsToAverageCount),
Gpu: sumGPU / float32(recordsToAverageCount), // 计算GPU平均使用率
Ram: sumRAM / int64(recordsToAverageCount),
RamTotal: records[0].Ram.Total,
Swap: sumSWAP / int64(recordsToAverageCount),
SwapTotal: records[0].Swap.Total,
Load: sumLOAD / float32(recordsToAverageCount),
Temp: 0, // 保持原始行为
Disk: sumDISK / int64(recordsToAverageCount),
DiskTotal: records[0].Disk.Total,
NetIn: sumNETIn / int64(recordsToAverageCount),
NetOut: sumNETOut / int64(recordsToAverageCount),
NetTotalUp: sumNETTotalUp / int64(recordsToAverageCount),
NetTotalDown: sumNETTotalDown / int64(recordsToAverageCount),
Process: sumPROCESS / recordsToAverageCount,
Connections: sumConnections / recordsToAverageCount,
ConnectionsUdp: sumConnectionsUDP / recordsToAverageCount,
}
return newRecord
}
// AverageGPUReports 使用与 AverageReport 相同的聚合逻辑处理GPU数据
// 返回每个GPU设备的聚合记录
func AverageGPUReports(uuid string, time time.Time, reports []common.Report, topPercentage float64) []models.GPURecord {
if len(reports) == 0 {
return []models.GPURecord{}
}
// 收集所有GPU设备数据
deviceData := make(map[int]struct {
DeviceName string
MemTotal []int64
MemUsed []int64
Utilization []float64
Temperature []int
})
for _, report := range reports {
if report.GPU != nil && len(report.GPU.DetailedInfo) > 0 {
for idx, gpu := range report.GPU.DetailedInfo {
if _, exists := deviceData[idx]; !exists {
deviceData[idx] = struct {
DeviceName string
MemTotal []int64
MemUsed []int64
Utilization []float64
Temperature []int
}{DeviceName: gpu.Name}
}
data := deviceData[idx]
data.MemTotal = append(data.MemTotal, gpu.MemoryTotal)
data.MemUsed = append(data.MemUsed, gpu.MemoryUsed)
data.Utilization = append(data.Utilization, gpu.Utilization)
data.Temperature = append(data.Temperature, gpu.Temperature)
deviceData[idx] = data
}
}
}
// 复用现有的聚合逻辑
sumAndSort := func(values []float64, topPerc float64) float64 {
if len(values) == 0 {
return 0
}
count := len(values)
recordsToAverageCount := count
if topPerc > 0 && topPerc <= 1 {
recordsToAverageCount = int(float64(count) * topPerc)
if recordsToAverageCount == 0 && count > 0 {
recordsToAverageCount = 1
}
}
if topPerc > 0 && topPerc <= 1 {
sort.Float64s(values)
// 取最高的值
var sum float64
for i := count - recordsToAverageCount; i < count; i++ {
sum += values[i]
}
return sum / float64(recordsToAverageCount)
} else {
var sum float64
for _, val := range values {
sum += val
}
return sum / float64(count)
}
}
sumAndSortInt64 := func(values []int64, topPerc float64) int64 {
if len(values) == 0 {
return 0
}
count := len(values)
recordsToAverageCount := count
if topPerc > 0 && topPerc <= 1 {
recordsToAverageCount = int(float64(count) * topPerc)
if recordsToAverageCount == 0 && count > 0 {
recordsToAverageCount = 1
}
}
if topPerc > 0 && topPerc <= 1 {
sort.Slice(values, func(i, j int) bool { return values[i] > values[j] })
var sum int64
for i := 0; i < recordsToAverageCount; i++ {
sum += values[i]
}
return sum / int64(recordsToAverageCount)
} else {
var sum int64
for _, val := range values {
sum += val
}
return sum / int64(count)
}
}
sumAndSortInt := func(values []int, topPerc float64) int {
if len(values) == 0 {
return 0
}
int64Values := make([]int64, len(values))
for i, v := range values {
int64Values[i] = int64(v)
}
return int(sumAndSortInt64(int64Values, topPerc))
}
// 生成每个设备的聚合记录
var result []models.GPURecord
for deviceIndex, data := range deviceData {
if len(data.MemTotal) > 0 {
record := models.GPURecord{
Client: uuid,
Time: models.FromTime(time),
DeviceIndex: deviceIndex,
DeviceName: data.DeviceName,
MemTotal: sumAndSortInt64(data.MemTotal, topPercentage),
MemUsed: sumAndSortInt64(data.MemUsed, topPercentage),
Utilization: float32(sumAndSort(data.Utilization, topPercentage)),
Temperature: sumAndSortInt(data.Temperature, topPercentage),
}
result = append(result, record)
}
}
return result
}
func DataMasking(str string, private []string) string {
if str == "" || len(private) == 0 {
return str
}
mask := "********"
// 相似度阈值可根据需要调节0~1越大越严格
const threshold = 0.8
runes := []rune(str)
n := len(runes)
toMask := make([]bool, n)
// 预处理 private 中的词,去掉空、重复
uniq := make(map[string]struct{})
var words []string
for _, w := range private {
w = strings.TrimSpace(w)
if w == "" {
continue
}
if _, ok := uniq[w]; ok {
continue
}
uniq[w] = struct{}{}
words = append(words, w)
}
if len(words) == 0 {
return str
}
// 逐词进行滑动窗口匹配 + 模糊匹配Levenshtein 相似度)
for _, w := range words {
wRunes := []rune(w)
wl := len(wRunes)
if wl == 0 || wl > n {
continue
}
// 滑动窗口大小采用敏感词长度
for i := 0; i <= n-wl; i++ {
if allMasked(toMask[i : i+wl]) { // 已全被标记则跳过
continue
}
sub := string(runes[i : i+wl])
sim := similarity(sub, w)
if sim >= threshold {
for k := 0; k < wl; k++ {
toMask[i+k] = true
}
}
}
}
// 构造输出:连续的掩码段只输出一次;如果原始被遮蔽长度>5展示首尾字符
var b strings.Builder
i := 0
for i < n {
if toMask[i] {
start := i
for i < n && toMask[i] {
i++
}
end := i // 不包含
segLen := end - start
if segLen > 5 {
b.WriteRune(runes[start])
b.WriteString(mask)
b.WriteRune(runes[end-1])
} else {
b.WriteString(mask)
}
} else {
b.WriteRune(runes[i])
i++
}
}
return b.String()
}
// allMasked 判断一个区间是否全部已经被标记
func allMasked(bools []bool) bool {
for _, v := range bools {
if !v {
return false
}
}
return true
}
// similarity 返回两个字符串的相似度 (0~1),基于 Levenshtein 距离
func similarity(a, b string) float64 {
if a == b {
return 1
}
ar := []rune(a)
br := []rune(b)
dist := levenshtein(ar, br)
maxLen := len(ar)
if len(br) > maxLen {
maxLen = len(br)
}
if maxLen == 0 {
return 1
}
return 1 - float64(dist)/float64(maxLen)
}
// levenshtein 计算两个 rune slice 的编辑距离
func levenshtein(a, b []rune) int {
la, lb := len(a), len(b)
if la == 0 {
return lb
}
if lb == 0 {
return la
}
// 使用滚动数组降低空间复杂度
prev := make([]int, lb+1)
curr := make([]int, lb+1)
for j := 0; j <= lb; j++ {
prev[j] = j
}
for i := 1; i <= la; i++ {
curr[0] = i
for j := 1; j <= lb; j++ {
cost := 0
if a[i-1] != b[j-1] {
cost = 1
}
del := prev[j] + 1
ins := curr[j-1] + 1
sub := prev[j-1] + cost
curr[j] = minInt(del, ins, sub)
}
prev, curr = curr, prev
}
return prev[lb]
}
func minInt(vals ...int) int {
m := vals[0]
for _, v := range vals[1:] {
if v < m {
m = v
}
}
return m
}