package app
import (
"fmt"
"github.com/seaweedfs/seaweedfs/weed/admin/dash"
)
// chartCard renders one titled chart with a legend.
templ chartCard(title string, icon string, width string, series []dash.ChartSeries, opts dash.ChartOptions) {
@templ.Raw(dash.RenderChart(series, opts))
@templ.Raw(dash.RenderLegend(series))
}
// statCard shows a single headline number with a colored left border.
templ statCard(label string, value string, icon string, accent string) {
}
templ monitoringHeader(title string, icon string, lastUpdated string) {
{ title }
Updated { lastUpdated }
}
templ noMetricsNotice() {
No metrics collected yet. The admin server scrapes each node's /metrics
endpoint every 15 seconds; charts fill in as samples accumulate.
}
templ Monitoring(data dash.MonitoringData) {
@monitoringHeader("Monitoring Overview", "fa-chart-line", data.LastUpdated.Format("15:04:05"))
if len(data.Overview.UnderReplicatedVolumes) == 0 && len(data.Overview.VolumeReadRate) == 0 {
@noMetricsNotice()
}
if dash.LatestValue(data.Overview.UnderReplicatedVolumes) > 0 {
{ fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.UnderReplicatedVolumes)) }
under-replicated volume(s). Data is at risk until replication catches up.
}
@statCard("Under-replicated", fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.UnderReplicatedVolumes)), "fa-clone", "danger")
@statCard("Writable Volumes", fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.WritableVolumes)), "fa-pen", "success")
@statCard("Disk Usage", dash.FormatChartValue(dash.LatestValue(data.Overview.DiskUsagePct), dash.UnitPercent), "fa-hdd", "warning")
@statCard("Queue Depth", fmt.Sprintf("%.0f", dash.LatestValue(data.Overview.QueueDepth)), "fa-tasks", "info")
Health
@chartCard("Under-replicated Volumes", "fa-clone", "col-xl-6", []dash.ChartSeries{
{Name: "Under-replicated", Color: dash.ChartDanger, Data: data.Overview.UnderReplicatedVolumes, Area: true},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Volume Capacity Pressure", "fa-pen", "col-xl-6", []dash.ChartSeries{
{Name: "Writable", Color: dash.ChartSuccess, Data: data.Overview.WritableVolumes},
{Name: "Crowded", Color: dash.ChartWarning, Data: data.Overview.CrowdedVolumes},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Cluster Disk Usage", "fa-hdd", "col-xl-6", []dash.ChartSeries{
{Name: "Disk used", Color: dash.ChartWarning, Data: data.Overview.DiskUsagePct, Area: true},
}, dash.ChartOptions{Unit: dash.UnitPercent, Threshold: diskThreshold()})
@chartCard("Disk & Quarantine Errors", "fa-triangle-exclamation", "col-xl-6", []dash.ChartSeries{
{Name: "Disk errors", Color: dash.ChartDanger, Data: data.Overview.DiskErrors},
{Name: "Quarantined", Color: dash.ChartWarning, Data: data.Overview.Quarantined},
}, dash.ChartOptions{Unit: dash.UnitCount})
Throughput
@chartCard("Volume Server Request Rate", "fa-server", "col-xl-6", []dash.ChartSeries{
{Name: "Read", Color: dash.ChartPrimary, Data: data.Overview.VolumeReadRate},
{Name: "Write", Color: dash.ChartSuccess, Data: data.Overview.VolumeWriteRate},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Filer & S3 Request Rate", "fa-folder-open", "col-xl-6", []dash.ChartSeries{
{Name: "Filer", Color: dash.ChartInfo, Data: data.Overview.FilerRequestRate},
{Name: "S3", Color: dash.ChartPrimary, Data: data.Overview.S3RequestRate},
}, dash.ChartOptions{Unit: dash.UnitCount})
Latency
@chartCard("Volume Server Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{
{Name: "p50", Color: dash.ChartSuccess, Data: data.Overview.VolumeP50},
{Name: "p95", Color: dash.ChartWarning, Data: data.Overview.VolumeP95},
{Name: "p99", Color: dash.ChartDanger, Data: data.Overview.VolumeP99},
}, dash.ChartOptions{Unit: dash.UnitSeconds})
@chartCard("Filer Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{
{Name: "p50", Color: dash.ChartSuccess, Data: data.Overview.FilerP50},
{Name: "p95", Color: dash.ChartWarning, Data: data.Overview.FilerP95},
{Name: "p99", Color: dash.ChartDanger, Data: data.Overview.FilerP99},
}, dash.ChartOptions{Unit: dash.UnitSeconds})
Errors
@chartCard("Request Error Rate by Component", "fa-bug", "col-xl-6", []dash.ChartSeries{
{Name: "Volume", Color: dash.ChartDanger, Data: data.Overview.VolumeErrorRate},
{Name: "Filer", Color: dash.ChartWarning, Data: data.Overview.FilerErrorRate},
{Name: "S3", Color: dash.ChartInfo, Data: data.Overview.S3ErrorRate},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Maintenance Queue & Worker Slots", "fa-tasks", "col-xl-6", []dash.ChartSeries{
{Name: "Queued tasks", Color: dash.ChartInfo, Data: data.Overview.QueueDepth},
{Name: "Slots used", Color: dash.ChartPrimary, Data: data.Overview.SlotsUsed},
{Name: "Slots max", Color: dash.ChartWarning, Data: data.Overview.SlotsMax},
}, dash.ChartOptions{Unit: dash.UnitCount})
}
templ MonitoringVolumeServers(data dash.MonitoringData) {
@monitoringHeader("Volume Servers", "fa-server", data.LastUpdated.Format("15:04:05"))
if len(data.VolumeServers) == 0 {
@noMetricsNotice()
}
| Server |
Request Rate |
p99 Latency |
Disk Usage |
Error Rate |
for _, vs := range data.VolumeServers {
| { vs.Address } |
{ dash.FormatChartValue(dash.LatestValue(vs.RequestRate), dash.UnitCount) }/s |
{ dash.FormatChartValue(dash.LatestValue(vs.P99), dash.UnitSeconds) } |
{ dash.FormatChartValue(dash.LatestValue(vs.DiskUsagePct), dash.UnitPercent) } |
if dash.LatestValue(vs.ErrorRate) > 0 {
{ dash.FormatChartValue(dash.LatestValue(vs.ErrorRate), dash.UnitCount) }/s
} else {
0
}
|
}
for _, vs := range data.VolumeServers {
{ vs.Address }
@chartCard("Request Rate", "fa-exchange-alt", "col-xl-6", []dash.ChartSeries{
{Name: "Requests", Color: dash.ChartPrimary, Data: vs.RequestRate, Area: true},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("p99 Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{
{Name: "p99", Color: dash.ChartDanger, Data: vs.P99},
}, dash.ChartOptions{Unit: dash.UnitSeconds})
@chartCard("Disk Usage", "fa-hdd", "col-xl-6", []dash.ChartSeries{
{Name: "Disk used", Color: dash.ChartWarning, Data: vs.DiskUsagePct, Area: true},
}, dash.ChartOptions{Unit: dash.UnitPercent, Threshold: diskThreshold()})
@chartCard("Error Rate", "fa-bug", "col-xl-6", []dash.ChartSeries{
{Name: "Errors", Color: dash.ChartDanger, Data: vs.ErrorRate},
}, dash.ChartOptions{Unit: dash.UnitCount})
}
}
templ MonitoringFilers(data dash.MonitoringData) {
@monitoringHeader("Filers", "fa-folder-open", data.LastUpdated.Format("15:04:05"))
if len(data.Filers) == 0 {
@noMetricsNotice()
}
for _, f := range data.Filers {
{ f.Address }
@chartCard("Request Rate", "fa-exchange-alt", "col-xl-6", []dash.ChartSeries{
{Name: "Requests", Color: dash.ChartInfo, Data: f.RequestRate, Area: true},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{
{Name: "Filer p99", Color: dash.ChartDanger, Data: f.P99},
{Name: "Store p99", Color: dash.ChartWarning, Data: f.StoreP99},
}, dash.ChartOptions{Unit: dash.UnitSeconds})
@chartCard("In-flight Requests", "fa-spinner", "col-xl-6", []dash.ChartSeries{
{Name: "In flight", Color: dash.ChartPrimary, Data: f.InFlight, Area: true},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Replication Lag", "fa-clock-rotate-left", "col-xl-6", []dash.ChartSeries{
{Name: "Lag", Color: dash.ChartWarning, Data: f.SyncLag},
}, dash.ChartOptions{Unit: dash.UnitSeconds})
}
}
templ MonitoringS3(data dash.MonitoringData) {
@monitoringHeader("S3", "fa-cloud", data.LastUpdated.Format("15:04:05"))
if len(data.S3) == 0 {
@noMetricsNotice()
}
for _, n := range data.S3 {
{ n.Address }
@chartCard("Operation Rate", "fa-exchange-alt", "col-xl-6", []dash.ChartSeries{
{Name: "Requests", Color: dash.ChartPrimary, Data: n.RequestRate, Area: true},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Error Rate", "fa-bug", "col-xl-6", []dash.ChartSeries{
{Name: "4xx", Color: dash.ChartWarning, Data: n.Errors4xx},
{Name: "5xx", Color: dash.ChartDanger, Data: n.Errors5xx},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("p99 Latency", "fa-stopwatch", "col-xl-6", []dash.ChartSeries{
{Name: "p99", Color: dash.ChartDanger, Data: n.P99},
}, dash.ChartOptions{Unit: dash.UnitSeconds})
}
}
templ MonitoringMasters(data dash.MonitoringData) {
@monitoringHeader("Masters", "fa-crown", data.LastUpdated.Format("15:04:05"))
if len(data.Masters) == 0 {
@noMetricsNotice()
}
for _, m := range data.Masters {
{ m.Address }
if m.IsLeader {
Leader
}
@chartCard("Heartbeat Rate", "fa-heartbeat", "col-xl-6", []dash.ChartSeries{
{Name: "Heartbeats", Color: dash.ChartSuccess, Data: m.HeartbeatRate, Area: true},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Volume Creation Rate", "fa-plus", "col-xl-6", []dash.ChartSeries{
{Name: "Created", Color: dash.ChartPrimary, Data: m.VolumeCreation},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Leader Changes", "fa-random", "col-xl-6", []dash.ChartSeries{
{Name: "Changes", Color: dash.ChartDanger, Data: m.LeaderChanges},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Placement Mismatches", "fa-triangle-exclamation", "col-xl-6", []dash.ChartSeries{
{Name: "Mismatched", Color: dash.ChartWarning, Data: m.PlacementMiss},
}, dash.ChartOptions{Unit: dash.UnitCount})
}
}
templ MonitoringWorkers(data dash.MonitoringData) {
@monitoringHeader("Workers", "fa-users-cog", data.LastUpdated.Format("15:04:05"))
@statCard("Connected Workers", fmt.Sprintf("%d", data.Workers.Connected), "fa-users", "primary")
@statCard("Queue Depth", fmt.Sprintf("%.0f", dash.LatestValue(data.Workers.QueueDepth)), "fa-tasks", "info")
@statCard("Slots Used", fmt.Sprintf("%.0f", dash.LatestValue(data.Workers.SlotsUsed)), "fa-microchip", "success")
@statCard("Slots Max", fmt.Sprintf("%.0f", dash.LatestValue(data.Workers.SlotsMax)), "fa-layer-group", "warning")
@chartCard("Task Queue Depth", "fa-tasks", "col-xl-6", []dash.ChartSeries{
{Name: "Queued", Color: dash.ChartInfo, Data: data.Workers.QueueDepth, Area: true},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Worker Slots", "fa-microchip", "col-xl-6", []dash.ChartSeries{
{Name: "Used", Color: dash.ChartPrimary, Data: data.Workers.SlotsUsed},
{Name: "Max", Color: dash.ChartWarning, Data: data.Workers.SlotsMax},
}, dash.ChartOptions{Unit: dash.UnitCount})
@chartCard("Task Completion Rate", "fa-check", "col-xl-6", []dash.ChartSeries{
{Name: "Completed", Color: dash.ChartSuccess, Data: data.Workers.TaskRate, Area: true},
}, dash.ChartOptions{Unit: dash.UnitCount})
if len(data.Workers.Workers) > 0 {
| Worker ID |
for _, w := range data.Workers.Workers {
| { w.ID } |
}
} else {
No workers are currently connected.
}
}