use javascript chart instead of svg
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-02-06 16:42:48 +11:00
parent 9677d083a8
commit a993aedf79
13 changed files with 1152 additions and 1290 deletions

View File

@@ -108,114 +108,135 @@ func buildVmTraceChart(entries []views.VmTraceEntry) views.VmTraceChart {
if len(entries) == 0 {
return views.VmTraceChart{}
}
width := 1200.0
height := 220.0
plotWidth := width - 60.0
startX := 40.0
maxVal := float64(0)
maxResource := float64(0)
for _, e := range entries {
if float64(e.VcpuCount) > maxVal {
maxVal = float64(e.VcpuCount)
if float64(e.VcpuCount) > maxResource {
maxResource = float64(e.VcpuCount)
}
if float64(e.RamGB) > maxVal {
maxVal = float64(e.RamGB)
if float64(e.RamGB) > maxResource {
maxResource = float64(e.RamGB)
}
}
if maxVal == 0 {
maxVal = 1
if maxResource == 0 {
maxResource = 1
}
stepX := plotWidth
if len(entries) > 1 {
stepX = plotWidth / float64(len(entries)-1)
}
scale := height / maxVal
var ptsVcpu, ptsRam, ptsTin, ptsBronze, ptsSilver, ptsGold string
appendPt := func(s string, x, y float64) string {
if s == "" {
return fmt.Sprintf("%.1f,%.1f", x, y)
tinLevel := maxResource
bronzeLevel := maxResource * 0.9
silverLevel := maxResource * 0.8
goldLevel := maxResource * 0.7
labels := make([]string, 0, len(entries))
tickLabels := make([]string, 0, len(entries))
vcpuValues := make([]float64, 0, len(entries))
ramValues := make([]float64, 0, len(entries))
tinValues := make([]float64, 0, len(entries))
bronzeValues := make([]float64, 0, len(entries))
silverValues := make([]float64, 0, len(entries))
goldValues := make([]float64, 0, len(entries))
poolNames := make([]string, 0, len(entries))
for _, e := range entries {
t := time.Unix(e.RawTime, 0).Local()
labels = append(labels, t.Format("2006-01-02 15:04:05"))
tickLabels = append(tickLabels, t.Format("01-02 15:04"))
vcpuValues = append(vcpuValues, float64(e.VcpuCount))
ramValues = append(ramValues, float64(e.RamGB))
pool := strings.TrimSpace(e.ResourcePool)
if pool == "" {
pool = "Unknown"
}
return s + " " + fmt.Sprintf("%.1f,%.1f", x, y)
}
for i, e := range entries {
x := startX + float64(i)*stepX
yVcpu := 10 + height - float64(e.VcpuCount)*scale
yRam := 10 + height - float64(e.RamGB)*scale
ptsVcpu = appendPt(ptsVcpu, x, yVcpu)
ptsRam = appendPt(ptsRam, x, yRam)
poolY := map[string]float64{
"tin": 10 + height - scale*maxVal,
"bronze": 10 + height - scale*maxVal*0.9,
"silver": 10 + height - scale*maxVal*0.8,
"gold": 10 + height - scale*maxVal*0.7,
}
lower := strings.ToLower(e.ResourcePool)
poolNames = append(poolNames, pool)
lower := strings.ToLower(pool)
if lower == "tin" {
ptsTin = appendPt(ptsTin, x, poolY["tin"])
tinValues = append(tinValues, tinLevel)
} else {
ptsTin = appendPt(ptsTin, x, 10+height)
tinValues = append(tinValues, 0)
}
if lower == "bronze" {
ptsBronze = appendPt(ptsBronze, x, poolY["bronze"])
bronzeValues = append(bronzeValues, bronzeLevel)
} else {
ptsBronze = appendPt(ptsBronze, x, 10+height)
bronzeValues = append(bronzeValues, 0)
}
if lower == "silver" {
ptsSilver = appendPt(ptsSilver, x, poolY["silver"])
silverValues = append(silverValues, silverLevel)
} else {
ptsSilver = appendPt(ptsSilver, x, 10+height)
silverValues = append(silverValues, 0)
}
if lower == "gold" {
ptsGold = appendPt(ptsGold, x, poolY["gold"])
goldValues = append(goldValues, goldLevel)
} else {
ptsGold = appendPt(ptsGold, x, 10+height)
goldValues = append(goldValues, 0)
}
}
gridY := []float64{}
for i := 0; i <= 4; i++ {
gridY = append(gridY, 10+float64(i)*(height/4))
}
gridX := []float64{}
for i := 0; i < len(entries); i++ {
gridX = append(gridX, startX+float64(i)*stepX)
}
yTicks := []views.ChartTick{}
for i := 0; i <= 4; i++ {
val := maxVal * float64(4-i) / 4
pos := 10 + float64(i)*(height/4)
yTicks = append(yTicks, views.ChartTick{Pos: pos, Label: fmt.Sprintf("%.0f", val)})
}
xTicks := []views.ChartTick{}
maxTicks := 8
stepIdx := 1
if len(entries) > 1 {
stepIdx = (len(entries)-1)/maxTicks + 1
}
for idx := 0; idx < len(entries); idx += stepIdx {
x := startX + float64(idx)*stepX
label := time.Unix(entries[idx].RawTime, 0).Local().Format("01-02 15:04")
xTicks = append(xTicks, views.ChartTick{Pos: x, Label: label})
}
if len(entries) > 1 {
lastIdx := len(entries) - 1
xLast := startX + float64(lastIdx)*stepX
labelLast := time.Unix(entries[lastIdx].RawTime, 0).Local().Format("01-02 15:04")
if len(xTicks) == 0 || xTicks[len(xTicks)-1].Pos != xLast {
xTicks = append(xTicks, views.ChartTick{Pos: xLast, Label: labelLast})
}
cfg := lineChartConfig{
Height: 360,
XTicks: 8,
YTicks: 5,
YLabel: "Resources / Pool",
XLabel: "Snapshots (oldest left, newest right)",
Labels: labels,
TickLabels: tickLabels,
Series: []lineChartSeries{
{
Name: "vCPU",
Color: "#2563eb",
Values: vcpuValues,
TooltipFormat: "int",
LineWidth: 2.5,
},
{
Name: "RAM (GB)",
Color: "#16a34a",
Values: ramValues,
TooltipFormat: "int",
LineWidth: 2.5,
},
{
Name: "Tin",
Color: "#0ea5e9",
Values: tinValues,
Dash: []float64{4, 4},
LineWidth: 1.5,
TooltipHidden: true,
},
{
Name: "Bronze",
Color: "#a855f7",
Values: bronzeValues,
Dash: []float64{4, 4},
LineWidth: 1.5,
TooltipHidden: true,
},
{
Name: "Silver",
Color: "#94a3b8",
Values: silverValues,
Dash: []float64{4, 4},
LineWidth: 1.5,
TooltipHidden: true,
},
{
Name: "Gold",
Color: "#f59e0b",
Values: goldValues,
Dash: []float64{4, 4},
LineWidth: 1.5,
TooltipHidden: true,
},
},
HoverRows: []lineChartHoverRow{
{
Name: "Resource Pool",
Values: poolNames,
},
},
}
return views.VmTraceChart{
PointsVcpu: ptsVcpu,
PointsRam: ptsRam,
PointsTin: ptsTin,
PointsBronze: ptsBronze,
PointsSilver: ptsSilver,
PointsGold: ptsGold,
Width: int(width),
Height: int(height),
GridX: gridX,
GridY: gridY,
XTicks: xTicks,
YTicks: yTicks,
ConfigJSON: encodeLineChartConfig(cfg),
}
}