Files
yucca/packages/yuctl/cli/tools/warp/view.go
T

115 lines
3.5 KiB
Go

package warp
import (
"fmt"
"strings"
"time"
"yuctl/fleet"
warpfleet "yuctl/fleet/warp"
"yuctl/ui"
)
// view renders StatusReports as a styled dashboard; watch mode feeds it
// successive samples and it keeps a throughput history for the sparkline.
type view struct {
label string
history fleet.History
}
func (v *view) render(r *warpfleet.StatusReport, sampledAt time.Time, sampleSec int, watching bool) string {
var b strings.Builder
b.WriteString(ui.Badge.Render("WARP") + " " + ui.Title.Render(v.label) + "\n")
if len(r.Pods) == 0 {
b.WriteString(ui.Warn.Render("no runner pods deployed") +
ui.Muted.Render(" — yuctl tools warp deploy") + "\n")
return ui.Frame.Render(strings.TrimRight(b.String(), "\n"))
}
if r.Config != nil {
since := r.Config["started_at"]
if t, err := time.Parse(time.RFC3339, since); err == nil {
since = time.Since(t).Round(time.Second).String() + " ago"
}
b.WriteString(fmt.Sprintf("%s %s · %s PUT / %s GET · %s/%s objects · %s RGWs\n",
ui.OK.Render("● "+r.Config["mode"]),
ui.Muted.Render("started "+since),
r.Config["put_streams"], r.Config["get_streams"],
r.Config["put_obj_size"], r.Config["get_obj_size"],
r.Config["rgw_endpoints"]))
b.WriteString(ui.Muted.Render("endpoint "+r.Config["endpoint"]) + "\n")
} else {
b.WriteString(ui.Warn.Render("○ no active run recorded") +
ui.Muted.Render(" — load stopped or never started") + "\n")
}
b.WriteString("\n")
// Nodes: TX/RX bars scaled to the busiest direction in this frame.
var maxBps float64
for _, n := range r.Nodes {
maxBps = max(maxBps, max(n.TxBps, n.RxBps))
}
nodeW, ifaceW := 4, 5
for _, n := range r.Nodes {
nodeW = max(nodeW, len(n.Node))
ifaceW = max(ifaceW, len(n.Iface))
}
var tx, rx float64
for _, n := range r.Nodes {
b.WriteString(fmt.Sprintf("%-*s %s %s %s %s %s\n",
nodeW, n.Node,
ui.Muted.Render(fmt.Sprintf("%-*s", ifaceW, n.Iface)),
ui.TX.Render(ui.Meter(n.TxBps, maxBps, 14)),
ui.PadGbps(n.TxBps),
ui.RX.Render(ui.Meter(n.RxBps, maxBps, 14)),
ui.PadGbps(n.RxBps)))
tx += n.TxBps
rx += n.RxBps
}
if len(r.Nodes) > 0 {
b.WriteString(fmt.Sprintf("%s %s %s · %s %s · %s\n",
ui.Muted.Render(fmt.Sprintf("%-*s", nodeW+ifaceW-2, "aggregate")),
ui.TX.Render("TX"), ui.PadGbps(tx),
ui.RX.Render("RX"), ui.PadGbps(rx),
ui.Total.Render("combined "+ui.FmtGbps(tx+rx))))
if len(v.history.Values()) > 1 {
b.WriteString(ui.Muted.Render("history ") + ui.Total.Render(ui.Sparkline(v.history.Values(), 40)) + "\n")
}
b.WriteString("\n")
}
// Pods: process liveness and log error counts.
podW := 3
for _, p := range r.Pods {
podW = max(podW, len(p.Name))
}
b.WriteString(ui.Muted.Render(fmt.Sprintf("%-*s %-*s %6s %6s %10s %10s", podW, "POD", nodeW, "NODE", "PUT", "GET", "ERR(PUT)", "ERR(GET)")) + "\n")
for _, p := range r.Pods {
b.WriteString(fmt.Sprintf("%-*s %-*s %s %s %s %s\n",
podW, p.Name, nodeW, p.Node,
procCell(p.PutProcs, r.Config != nil),
procCell(p.GetProcs, r.Config != nil),
ui.ErrCell(p.PutErrors, 10), ui.ErrCell(p.GetErrors, 10)))
}
b.WriteString("\n" + ui.Muted.Render(fleet.Footer(sampleSec, sampledAt, watching)))
return ui.Frame.Render(b.String())
}
// procCell colors a warp process count: green when alive, red when a run is
// recorded but nothing is running, dim otherwise.
func procCell(n int, runRecorded bool) string {
s := fmt.Sprintf("%6d", n)
switch {
case n > 0:
return ui.OK.Render(s)
case runRecorded:
return ui.Bad.Render(s)
default:
return ui.Muted.Render(s)
}
}