more accurate resource pool data in aggregation reports
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-01-23 11:59:52 +11:00
parent 871d7c2024
commit 25564efa54
4 changed files with 252 additions and 83 deletions

View File

@@ -1067,7 +1067,7 @@ func normalizeCellValue(value interface{}) interface{} {
type totalsPoint struct {
Label string
VmCount int64
VmCount float64
VcpuTotal float64
RamTotal float64
PresenceRatio float64
@@ -1078,7 +1078,18 @@ type totalsPoint struct {
}
func buildHourlyTotals(ctx context.Context, dbConn *sqlx.DB, records []SnapshotRecord) ([]totalsPoint, error) {
points := make([]totalsPoint, 0, len(records))
type hourBucket struct {
samples int
vmSum float64
vcpuSum float64
ramSum float64
tinSum float64
bronzeSum float64
silverSum float64
goldSum float64
}
buckets := make(map[int64]*hourBucket)
for _, record := range records {
if err := db.ValidateTableName(record.TableName); err != nil {
return nil, err
@@ -1112,17 +1123,46 @@ WHERE %s
if err := dbConn.GetContext(ctx, &row, query); err != nil {
return nil, err
}
hourKey := record.SnapshotTime.Local().Truncate(time.Hour).Unix()
bucket := buckets[hourKey]
if bucket == nil {
bucket = &hourBucket{}
buckets[hourKey] = bucket
}
bucket.samples++
bucket.vmSum += float64(row.VmCount)
bucket.vcpuSum += float64(row.VcpuTotal)
bucket.ramSum += float64(row.RamTotal)
bucket.tinSum += row.TinTotal
bucket.bronzeSum += row.BronzeTotal
bucket.silverSum += row.SilverTotal
bucket.goldSum += row.GoldTotal
}
keys := make([]int64, 0, len(buckets))
for key := range buckets {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
points := make([]totalsPoint, 0, len(keys))
for _, key := range keys {
bucket := buckets[key]
if bucket.samples == 0 {
continue
}
denom := float64(bucket.samples)
vmAvg := bucket.vmSum / denom
points = append(points, totalsPoint{
Label: record.SnapshotTime.Local().Format("2006-01-02 15:04"),
VmCount: row.VmCount,
VcpuTotal: float64(row.VcpuTotal),
RamTotal: float64(row.RamTotal),
// For hourly snapshots, prorated VM count equals VM count (no finer granularity).
PresenceRatio: float64(row.VmCount),
TinTotal: row.TinTotal,
BronzeTotal: row.BronzeTotal,
SilverTotal: row.SilverTotal,
GoldTotal: row.GoldTotal,
Label: time.Unix(key, 0).Local().Format("2006-01-02 15:00"),
VmCount: vmAvg,
VcpuTotal: bucket.vcpuSum / denom,
RamTotal: bucket.ramSum / denom,
PresenceRatio: vmAvg,
TinTotal: bucket.tinSum / denom,
BronzeTotal: bucket.bronzeSum / denom,
SilverTotal: bucket.silverSum / denom,
GoldTotal: bucket.goldSum / denom,
})
}
return points, nil
@@ -1175,10 +1215,10 @@ WHERE %s
}
points = append(points, totalsPoint{
Label: record.SnapshotTime.Local().Format("2006-01-02"),
VmCount: row.VmCount,
VmCount: float64(row.VmCount),
VcpuTotal: row.VcpuTotal,
RamTotal: row.RamTotal,
PresenceRatio: computeProratedVmCount(row.PresenceRatio, row.VmCount, prorateByAvg),
PresenceRatio: computeProratedVmCount(row.PresenceRatio, float64(row.VmCount), prorateByAvg),
TinTotal: row.TinTotal,
BronzeTotal: row.BronzeTotal,
SilverTotal: row.SilverTotal,
@@ -1188,11 +1228,11 @@ WHERE %s
return points, nil
}
func computeProratedVmCount(presenceRatio float64, vmCount int64, prorate bool) float64 {
func computeProratedVmCount(presenceRatio float64, vmCount float64, prorate bool) float64 {
if !prorate {
return float64(vmCount)
return vmCount
}
return presenceRatio * float64(vmCount)
return presenceRatio * vmCount
}
func writeTotalsChart(logger *slog.Logger, xlsx *excelize.File, sheetName string, points []totalsPoint) {
@@ -1224,6 +1264,18 @@ func writeTotalsChart(logger *slog.Logger, xlsx *excelize.File, sheetName string
xlsx.SetCellValue(sheetName, fmt.Sprintf("I%d", row), point.GoldTotal)
}
if lastRow := len(points) + 1; lastRow >= 2 {
numFmt := "0.00000000"
styleID, err := xlsx.NewStyle(&excelize.Style{CustomNumFmt: &numFmt})
if err == nil {
if err := xlsx.SetCellStyle(sheetName, "E2", fmt.Sprintf("I%d", lastRow), styleID); err != nil {
logger.Error("Error setting totals number format", "error", err)
}
} else {
logger.Error("Error creating totals number format", "error", err)
}
}
if endCell, err := excelize.CoordinatesToCellName(len(headers), 1); err == nil {
filterRange := "A1:" + endCell
if err := xlsx.AutoFilter(sheetName, filterRange, nil); err != nil {