Separate network faults from software drops and fix CPU temperature history

This commit is contained in:
Maxim
2026-10-07 22:02:08 +03:00
parent 1296d052a5
commit 72869dcf5f
12 changed files with 341 additions and 73 deletions

View File

@@ -43,26 +43,31 @@ type NetworkHealth struct {
}
type NetworkInterface struct {
Name string `json:"name"`
ReceiveBytes uint64 `json:"receiveBytes"`
TransmitBytes uint64 `json:"transmitBytes"`
ReceiveErrors uint64 `json:"receiveErrors"`
TransmitErrors uint64 `json:"transmitErrors"`
ReceiveDrops uint64 `json:"receiveDrops"`
TransmitDrops uint64 `json:"transmitDrops"`
NewErrors uint64 `json:"newErrors"`
NewDrops uint64 `json:"newDrops"`
ReceivePerSec float64 `json:"receivePerSec"`
TransmitPerSec float64 `json:"transmitPerSec"`
VMID int `json:"vmid,omitempty"`
GuestName string `json:"guestName,omitempty"`
GuestType string `json:"guestType,omitempty"`
Kind string `json:"kind"`
State string `json:"state"`
Carrier bool `json:"carrier"`
SpeedMbps int `json:"speedMbps,omitempty"`
Duplex string `json:"duplex,omitempty"`
Master string `json:"master,omitempty"`
Name string `json:"name"`
ReceiveBytes uint64 `json:"receiveBytes"`
TransmitBytes uint64 `json:"transmitBytes"`
ReceiveErrors uint64 `json:"receiveErrors"`
TransmitErrors uint64 `json:"transmitErrors"`
ReceiveDrops uint64 `json:"receiveDrops"`
TransmitDrops uint64 `json:"transmitDrops"`
NewErrors uint64 `json:"newErrors"`
NewReceiveDrops uint64 `json:"newReceiveDrops"`
NewTransmitDrops uint64 `json:"newTransmitDrops"`
FaultCounters map[string]uint64 `json:"faultCounters,omitempty"`
NewFaults uint64 `json:"newFaults"`
LinkDown bool `json:"linkDown"`
NewDrops uint64 `json:"newDrops"`
ReceivePerSec float64 `json:"receivePerSec"`
TransmitPerSec float64 `json:"transmitPerSec"`
VMID int `json:"vmid,omitempty"`
GuestName string `json:"guestName,omitempty"`
GuestType string `json:"guestType,omitempty"`
Kind string `json:"kind"`
State string `json:"state"`
Carrier bool `json:"carrier"`
SpeedMbps int `json:"speedMbps,omitempty"`
Duplex string `json:"duplex,omitempty"`
Master string `json:"master,omitempty"`
}
type networkSnapshot struct {
@@ -83,13 +88,14 @@ type networkCollector struct {
}
func newNetworkCollector(path string) (*networkCollector, error) {
snapshot, _, _, err := readNetworkSnapshot(path)
snapshot, _, details, err := readNetworkSnapshot(path)
if err != nil {
return nil, err
}
_, _, details, _ := readNetworkSnapshot(path)
previous := map[string]NetworkInterface{}
for _, v := range details {
decorateInterface(&v)
collectInterfaceFaults(&v)
previous[v.Name] = v
}
known := map[string]bool{}
@@ -116,16 +122,11 @@ func (c *networkCollector) collect() (NetworkMetrics, error) {
for i := range details {
v := &details[i]
decorateInterface(v)
if old, ok := c.previousInterfaces[v.Name]; ok && elapsed > 0 && v.ReceiveBytes >= old.ReceiveBytes && v.TransmitBytes >= old.TransmitBytes {
v.ReceivePerSec = float64(v.ReceiveBytes-old.ReceiveBytes) / elapsed
v.TransmitPerSec = float64(v.TransmitBytes-old.TransmitBytes) / elapsed
if v.ReceiveErrors >= old.ReceiveErrors && v.TransmitErrors >= old.TransmitErrors {
v.NewErrors = v.ReceiveErrors - old.ReceiveErrors + v.TransmitErrors - old.TransmitErrors
}
if v.ReceiveDrops >= old.ReceiveDrops && v.TransmitDrops >= old.TransmitDrops {
v.NewDrops = v.ReceiveDrops - old.ReceiveDrops + v.TransmitDrops - old.TransmitDrops
}
collectInterfaceFaults(v)
if old, ok := c.previousInterfaces[v.Name]; ok {
updateInterfaceDeltas(v, old, elapsed)
}
v.LinkDown = interfaceLinkDown(*v)
}
c.previousInterfaces = map[string]NetworkInterface{}
for _, v := range details {
@@ -166,19 +167,19 @@ func explainNetworkHealth(health *NetworkHealth, interfaces []NetworkInterface)
health.Summary, health.LikelyCause, health.RecommendedAction = "Хост не видит локальный шлюз", "Проблема между Proxmox и роутером: линк, bridge, bond или VLAN", "Проверьте кабель, carrier и настройки vmbr/bond/VLAN"
case health.PacketLoss > 0:
health.Summary = fmt.Sprintf("Нестабильная локальная сеть: потеряно %.1f%% пакетов", health.PacketLoss)
health.LikelyCause, health.RecommendedAction = "Перегрузка или ошибки линка между Proxmox и шлюзом", "Проверьте errors/drops, кабель, порт коммутатора и duplex"
health.LikelyCause, health.RecommendedAction = "Перегрузка или ошибки линка между Proxmox и шлюзом", "Проверьте аппаратные ошибки, кабель, порт коммутатора и duplex"
case !health.DNSUp && health.InternetUp:
health.Summary, health.LikelyCause, health.RecommendedAction = "Интернет доступен, но DNS не отвечает", "Проблема DNS-сервера или /etc/resolv.conf", "Проверьте настроенные DNS и попробуйте разрешить домен вручную"
case health.DNSUp && !health.InternetUp:
health.Summary, health.LikelyCause, health.RecommendedAction = "Локальная сеть и DNS работают, но внешняя проверка недоступна", "Проблема выше роутера, блокировка HTTPS или недоступность провайдера", "Проверьте маршрут наружу, firewall и доступ к внешним адресам"
}
for _, v := range interfaces {
if v.Kind == "Physical" && (!v.Carrier || v.State == "down") {
health.Summary, health.LikelyCause, health.RecommendedAction = v.Name+": физический линк не поднят", "Нет carrier либо интерфейс административно выключен", "Проверьте кабель, порт коммутатора и состояние интерфейса"
if v.LinkDown {
health.Summary, health.LikelyCause, health.RecommendedAction = v.Name+": линк не поднят", "Нет carrier либо интерфейс административно выключен", "Проверьте кабель, порт коммутатора и состояние интерфейса"
return
}
if v.NewErrors+v.NewDrops > 0 {
health.Summary = fmt.Sprintf("%s: за последний интервал появилось %d сетевых ошибок/потерь", v.Name, v.NewErrors+v.NewDrops)
if v.NewErrors+v.NewFaults > 0 {
health.Summary = fmt.Sprintf("%s: за последний интервал появилось %d изменений счётчиков сетевых ошибок", v.Name, v.NewErrors+v.NewFaults)
health.LikelyCause, health.RecommendedAction = "Счётчики интерфейса сейчас растут из-за перегрузки, кабеля, MTU или duplex", "Проверьте скорость и duplex, кабель, порт коммутатора и MTU на обоих концах"
return
}
@@ -195,6 +196,10 @@ func decorateInterface(v *NetworkInterface) {
v.Master = filepath.Base(target)
}
switch {
case strings.HasPrefix(v.Name, "fwbr"):
v.Kind = "Firewall bridge"
case strings.HasPrefix(v.Name, "fwpr") || strings.HasPrefix(v.Name, "fwln"):
v.Kind = "Firewall link"
case strings.HasPrefix(v.Name, "vmbr"):
v.Kind = "Bridge"
case strings.HasPrefix(v.Name, "bond"):
@@ -273,7 +278,7 @@ func readNetworkSnapshot(path string) (networkSnapshot, []string, []NetworkInter
continue
}
fields := strings.Fields(values)
if len(fields) < 9 {
if len(fields) < 16 {
continue
}
rx, errRX := strconv.ParseUint(fields[0], 10, 64)