mirror of
https://github.com/immich-app/yucca.git
synced 2026-09-30 13:33:00 +08:00
115 lines
3.5 KiB
Go
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)
|
|
}
|
|
}
|