Add infrastructure reliability monitoring
This commit is contained in:
74
alerts.go
74
alerts.go
@@ -75,6 +75,15 @@ func evaluateAlerts(metrics dashboardMetrics, thresholds AlertThresholds) []Aler
|
||||
add("disk-temp-"+disk.Name, "warning", "Диски", disk.Model+": высокая температура", fmt.Sprintf("Температура %.1f °C.", *disk.Temperature))
|
||||
}
|
||||
}
|
||||
if len(disk.CounterChanges) > 0 {
|
||||
add("disk-growth-"+disk.Name, "critical", "Диски", disk.Model+": ухудшились SMART-счётчики", strings.Join(disk.CounterChanges, " · "))
|
||||
}
|
||||
if disk.SelfTestNeverRun {
|
||||
add("disk-selftest-"+disk.Name, "warning", "Диски", disk.Model+": нет завершённого SMART self-test", "Короткий тест будет автоматически запущен; проверьте его результат после завершения.")
|
||||
}
|
||||
}
|
||||
for _, missing := range metrics.MissingDisks {
|
||||
add("disk-missing-"+missing, "critical", "Диски", "Физический диск исчез", missing+" больше не обнаруживается в /sys/block.")
|
||||
}
|
||||
|
||||
for _, pool := range metrics.ZFS.Pools {
|
||||
@@ -89,6 +98,25 @@ func evaluateAlerts(metrics dashboardMetrics, thresholds AlertThresholds) []Aler
|
||||
if strings.Contains(strings.ToLower(pool.Scan), "in progress") {
|
||||
add("zfs-scan-"+pool.Name, "info", "ZFS", pool.Name+": выполняется scrub/resilver", pool.Scan)
|
||||
}
|
||||
if pool.ReadErrors+pool.WriteErrors+pool.ChecksumErrors > 0 {
|
||||
add("zfs-vdev-errors-"+pool.Name, "critical", "ZFS", pool.Name+": ошибки чтения/записи/checksum", fmt.Sprintf("read=%d, write=%d, checksum=%d.", pool.ReadErrors, pool.WriteErrors, pool.ChecksumErrors))
|
||||
}
|
||||
if len(pool.ErrorChanges) > 0 {
|
||||
add("zfs-new-errors-"+pool.Name, "critical", "ZFS", pool.Name+": появились новые ошибки", strings.Join(pool.ErrorChanges, " · "))
|
||||
}
|
||||
if pool.ScrubTooOld {
|
||||
add("zfs-scrub-old-"+pool.Name, "warning", "ZFS", pool.Name+": scrub просрочен", "Scrub не выполнялся более 35 дней либо ни разу не запускался.")
|
||||
}
|
||||
}
|
||||
for _, dataset := range metrics.ZFS.Datasets {
|
||||
if dataset.CapacityPercent >= 90 {
|
||||
add("zfs-dataset-"+dataset.Name, "critical", "ZFS", dataset.Name+": dataset почти заполнен", fmt.Sprintf("Заполнение %.1f%%, доступно %.1f GB.", dataset.CapacityPercent, float64(dataset.AvailableBytes)/1073741824))
|
||||
} else if dataset.CapacityPercent >= 80 {
|
||||
add("zfs-dataset-"+dataset.Name, "warning", "ZFS", dataset.Name+": мало свободного места", fmt.Sprintf("Заполнение %.1f%%.", dataset.CapacityPercent))
|
||||
}
|
||||
}
|
||||
if metrics.ZFS.ARC.Available && metrics.ZFS.ARC.Hits+metrics.ZFS.ARC.Misses > 10000 && metrics.ZFS.ARC.HitRatio < 70 {
|
||||
add("zfs-arc-hit", "warning", "ZFS", "Низкий ARC hit ratio", fmt.Sprintf("Попадания ARC: %.1f%%.", metrics.ZFS.ARC.HitRatio))
|
||||
}
|
||||
|
||||
if metrics.UPS.Available {
|
||||
@@ -101,6 +129,14 @@ func evaluateAlerts(metrics dashboardMetrics, thresholds AlertThresholds) []Aler
|
||||
if metrics.UPS.ChargePercent != nil && *metrics.UPS.ChargePercent < thresholds.UPSChargeCritical {
|
||||
add("ups-charge", "critical", "UPS", "Низкий заряд батареи", fmt.Sprintf("Осталось %.1f%%.", *metrics.UPS.ChargePercent))
|
||||
}
|
||||
if metrics.UPS.ReplaceBattery {
|
||||
add("ups-replace-battery", "critical", "UPS", "UPS требует заменить батарею", "NUT/APC сообщает флаг Replace Battery.")
|
||||
}
|
||||
if metrics.UPS.BatteryAgeDays > 3*365 {
|
||||
add("ups-battery-age", "warning", "UPS", "Батарея UPS старше трёх лет", fmt.Sprintf("Возраст батареи примерно %d дней.", metrics.UPS.BatteryAgeDays))
|
||||
}
|
||||
} else if metrics.UPS.Source != "" {
|
||||
add("ups-communication", "warning", "UPS", "Нет связи с UPS", "NUT и apcupsd не вернули данные. Проверьте USB/сетевое подключение и службу UPS.")
|
||||
}
|
||||
|
||||
if metrics.Backups.Available && metrics.Guests.Available {
|
||||
@@ -164,6 +200,44 @@ func evaluateAlerts(metrics dashboardMetrics, thresholds AlertThresholds) []Aler
|
||||
if networkErrors > 0 {
|
||||
add("network-errors", "warning", "Сеть", "Сетевые ошибки и потери пакетов", fmt.Sprintf("Суммарный счётчик интерфейсов: %d.", networkErrors))
|
||||
}
|
||||
if !metrics.Network.Health.CheckedAt.IsZero() && !metrics.Network.Health.GatewayUp {
|
||||
add("network-gateway", "critical", "Сеть", "Недоступен локальный шлюз", firstNonEmpty(metrics.Network.Health.Gateway, "Default gateway не определён."))
|
||||
}
|
||||
if !metrics.Network.Health.CheckedAt.IsZero() && !metrics.Network.Health.DNSUp {
|
||||
add("network-dns", "critical", "Сеть", "Не работает DNS", "Не удалось разрешить deb.debian.org.")
|
||||
}
|
||||
if !metrics.Network.Health.CheckedAt.IsZero() && !metrics.Network.Health.InternetUp {
|
||||
add("network-internet", "warning", "Сеть", "Нет доступа в интернет", "Внешняя проверка IP не выполнена.")
|
||||
}
|
||||
if metrics.Network.Health.PacketLoss > 0 {
|
||||
add("network-loss", "warning", "Сеть", "Потери пакетов до шлюза", fmt.Sprintf("Потери %.1f%%, задержка %.1f ms.", metrics.Network.Health.PacketLoss, metrics.Network.Health.LatencyMs))
|
||||
}
|
||||
if metrics.Network.Health.ExternalIPChanged {
|
||||
add("network-public-ip", "info", "Сеть", "Изменился внешний IP", "Новый адрес: "+metrics.Network.Health.ExternalIP)
|
||||
}
|
||||
for _, name := range metrics.Network.Health.DisappearedInterfaces {
|
||||
add("network-interface-missing-"+name, "warning", "Сеть", "Исчез сетевой интерфейс", name+" больше не присутствует в /proc/net/dev.")
|
||||
}
|
||||
for _, iface := range metrics.Network.Interfaces {
|
||||
if iface.Kind == "Physical" && !iface.Carrier && (iface.Master != "" || iface.ReceiveBytes+iface.TransmitBytes > 0) {
|
||||
add("network-link-"+iface.Name, "warning", "Сеть", iface.Name+": нет carrier", "Физический линк не поднят.")
|
||||
}
|
||||
if (iface.Kind == "Bridge" || iface.Kind == "Bond" || iface.Kind == "VLAN") && iface.State == "down" {
|
||||
add("network-state-"+iface.Name, "warning", "Сеть", iface.Name+": интерфейс выключен", iface.Kind+" находится в состоянии down.")
|
||||
}
|
||||
}
|
||||
if !metrics.Monitor.LastSuccessfulCollection.IsZero() && time.Since(metrics.Monitor.LastSuccessfulCollection) > 30*time.Second {
|
||||
add("monitor-stale", "critical", "Dashboard", "Данные мониторинга устарели", "Последний успешный сбор был "+time.Since(metrics.Monitor.LastSuccessfulCollection).Round(time.Second).String()+" назад.")
|
||||
}
|
||||
if metrics.Monitor.SlowCollector {
|
||||
add("monitor-slow", "warning", "Dashboard", "Сбор метрик выполняется слишком долго", fmt.Sprintf("Последний цикл занял %d ms.", metrics.Monitor.CollectionDurationMs))
|
||||
}
|
||||
if metrics.Monitor.HistoryWriteError != "" {
|
||||
add("monitor-history", "critical", "Dashboard", "Ошибка записи истории", metrics.Monitor.HistoryWriteError)
|
||||
}
|
||||
if metrics.Monitor.DatabaseFreeBytes > 0 && metrics.Monitor.DatabaseFreeBytes < 1024*1024*1024 {
|
||||
add("monitor-db-space", "warning", "Dashboard", "Мало места для базы истории", fmt.Sprintf("На разделе базы свободно %.1f GB.", float64(metrics.Monitor.DatabaseFreeBytes)/1073741824))
|
||||
}
|
||||
oldSnapshots := 0
|
||||
for _, snapshot := range metrics.Activity.Snapshots {
|
||||
if snapshot.AgeSeconds > 30*24*3600 {
|
||||
|
||||
Reference in New Issue
Block a user