Utku Ozdemir f55f5df739
feat: move dashboard package & run it in tty2
Move dashboard package into a common location where both Talos and talosctl can use it.

Add support for overriding stdin, stdout, stderr and ctt in process runner.

Create a dashboard service which runs the dashboard on /dev/tty2.

Redirect kernel messages to tty1 and switch to tty2 after starting the dashboard on it.

Related to siderolabs/talos#6841, siderolabs/talos#4791.

Signed-off-by: Utku Ozdemir <utku.ozdemir@siderolabs.com>
2023-02-28 12:00:25 +01:00

201 lines
5.1 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package data
import (
"github.com/siderolabs/gen/slices"
"github.com/siderolabs/talos/pkg/machinery/api/machine"
)
// Node represents data gathered from a single node.
type Node struct {
// These fields are directly API responses.
Hostname *machine.Hostname
LoadAvg *machine.LoadAvg
Version *machine.Version
Memory *machine.Memory
SystemStat *machine.SystemStat
CPUsInfo *machine.CPUsInfo
NetDevStats *machine.NetworkDeviceStats
DiskStats *machine.DiskStats
Processes *machine.Process
// These fields are calculated as diff with Node data from previous pol.
SystemStatDiff *machine.SystemStat
NetDevStatsDiff *machine.NetworkDeviceStats
DiskStatsDiff *machine.DiskStats
ProcsDiff map[int32]*machine.ProcessInfo
// Time-series data.
Series map[string][]float64
}
// MemUsage as used/total.
func (node *Node) MemUsage() float64 {
memTotal := node.Memory.GetMeminfo().GetMemtotal()
memUsed := node.Memory.GetMeminfo().GetMemtotal() - node.Memory.GetMeminfo().GetMemfree() - node.Memory.GetMeminfo().GetCached() - node.Memory.GetMeminfo().GetBuffers()
if memTotal == 0 {
return 0
}
return float64(memUsed) / float64(memTotal)
}
// CPUUsageByName returns CPU usage by name.
//
//nolint:gocyclo
func (node *Node) CPUUsageByName(name string) float64 {
if node.SystemStatDiff == nil || node.SystemStatDiff.CpuTotal == nil {
return 0
}
stat := node.SystemStatDiff.CpuTotal
idle := stat.Idle + stat.Iowait
nonIdle := stat.User + stat.Nice + stat.System + stat.Irq + stat.Steal + stat.SoftIrq
total := idle + nonIdle
if total == 0 {
return 0
}
switch name {
case "user":
return stat.User / total
case "system":
return stat.System / total
case "idle":
return stat.Idle / total
case "iowait":
return stat.Iowait / total
case "nice":
return stat.Nice / total
case "irq":
return stat.Irq / total
case "steal":
return stat.Steal / total
case "softirq":
return stat.SoftIrq / total
case "usage":
return (total - idle) / total
case "total":
return total
case "total_weighted":
cpuCount := len(node.CPUsInfo.GetCpuInfo())
if cpuCount == 0 {
return total
}
return total / float64(cpuCount)
}
panic("unknown cpu usage name")
}
// CtxSwitches returns diff of context switches.
func (node *Node) CtxSwitches() uint64 {
if node.SystemStatDiff == nil {
return 0
}
return node.SystemStatDiff.GetContextSwitches()
}
// ProcsCreated returns diff of processes created.
func (node *Node) ProcsCreated() uint64 {
if node.SystemStatDiff == nil {
return 0
}
return node.SystemStatDiff.GetProcessCreated()
}
// UpdateSeries builds time-series data based on previous iteration data.
func (node *Node) UpdateSeries(old *Node) {
node.Series = make(map[string][]float64)
for _, graphInfo := range []struct {
name string
f func() float64
}{
{
"mem",
node.MemUsage,
},
{
"user",
func() float64 { return node.CPUUsageByName("user") },
},
{
"system",
func() float64 { return node.CPUUsageByName("system") },
},
{
"loadavg",
func() float64 { return node.LoadAvg.GetLoad1() },
},
{
"netrxbytes",
func() float64 { return float64(node.NetDevStatsDiff.GetTotal().GetRxBytes()) },
},
{
"nettxbytes",
func() float64 { return float64(node.NetDevStatsDiff.GetTotal().GetTxBytes()) },
},
{
"diskrdsectors",
func() float64 { return float64(node.DiskStatsDiff.GetTotal().GetReadSectors()) },
},
{
"diskwrsectors",
func() float64 { return float64(node.DiskStatsDiff.GetTotal().GetWriteSectors()) },
},
} {
oldSeries := old.Series[graphInfo.name]
off := 0
if len(oldSeries) > maxPoints {
off = len(oldSeries) - maxPoints
}
node.Series[graphInfo.name] = append(oldSeries[off:], graphInfo.f())
// TODO: bug with plot widget
for len(node.Series[graphInfo.name]) < 2 {
node.Series[graphInfo.name] = append([]float64{0.0}, node.Series[graphInfo.name]...)
}
}
}
// UpdateDiff calculates diff with node data from previous iteration.
func (node *Node) UpdateDiff(old *Node) {
node.SystemStatDiff = &machine.SystemStat{
// TODO: support other fields
CpuTotal: cpuInfoDiff(old.SystemStat.GetCpuTotal(), node.SystemStat.GetCpuTotal()),
ContextSwitches: node.SystemStat.ContextSwitches - old.SystemStat.ContextSwitches,
ProcessCreated: node.SystemStat.ProcessCreated - old.SystemStat.ProcessCreated,
}
node.NetDevStatsDiff = &machine.NetworkDeviceStats{
// TODO: support other fields
Total: netDevDiff(old.NetDevStats.GetTotal(), node.NetDevStats.GetTotal()),
}
node.DiskStatsDiff = &machine.DiskStats{
// TODO: support other fields
Total: diskStatDiff(old.DiskStats.GetTotal(), node.DiskStats.GetTotal()),
}
index := slices.ToMap(old.Processes.GetProcesses(), func(proc *machine.ProcessInfo) (int32, *machine.ProcessInfo) {
return proc.Pid, proc
})
node.ProcsDiff = slices.ToMap(node.Processes.GetProcesses(), func(proc *machine.ProcessInfo) (int32, *machine.ProcessInfo) {
return proc.Pid, procDiff(index[proc.Pid], proc)
})
}