mirror of
https://github.com/TwiN/gatus.git
synced 2024-12-15 17:51:09 +00:00
152 lines
6.2 KiB
Go
152 lines
6.2 KiB
Go
package core
|
|
|
|
import (
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
numberOfHoursInTenDays = 10 * 24
|
|
sevenDays = 7 * 24 * time.Hour
|
|
)
|
|
|
|
// Uptime is the struct that contains the relevant data for calculating the uptime as well as the uptime itself
|
|
// and some other statistics
|
|
type Uptime struct {
|
|
LastSevenDays float64 `json:"7d"` // Uptime percentage over the past 7 days
|
|
LastTwentyFourHours float64 `json:"24h"` // Uptime percentage over the past 24 hours
|
|
LastHour float64 `json:"1h"` // Uptime percentage over the past hour
|
|
|
|
// SuccessfulExecutionsPerHour is a map containing the number of successes (value)
|
|
// for every hourly unix timestamps (key)
|
|
// Deprecated
|
|
SuccessfulExecutionsPerHour map[int64]uint64 `json:"-"`
|
|
|
|
// TotalExecutionsPerHour is a map containing the total number of checks (value)
|
|
// for every hourly unix timestamps (key)
|
|
// Deprecated
|
|
TotalExecutionsPerHour map[int64]uint64 `json:"-"`
|
|
|
|
// HourlyStatistics is a map containing metrics collected (value) for every hourly unix timestamps (key)
|
|
HourlyStatistics map[int64]*HourlyUptimeStatistics `json:"-"`
|
|
}
|
|
|
|
// HourlyUptimeStatistics is a struct containing all metrics collected over the course of an hour
|
|
type HourlyUptimeStatistics struct {
|
|
TotalExecutions uint64 // Total number of checks
|
|
SuccessfulExecutions uint64 // Number of successful executions
|
|
TotalExecutionsResponseTime uint64 // Total response time for all executions
|
|
}
|
|
|
|
// NewUptime creates a new Uptime
|
|
func NewUptime() *Uptime {
|
|
return &Uptime{
|
|
HourlyStatistics: make(map[int64]*HourlyUptimeStatistics),
|
|
}
|
|
}
|
|
|
|
// ProcessResult processes the result by extracting the relevant from the result and recalculating the uptime
|
|
// if necessary
|
|
func (uptime *Uptime) ProcessResult(result *Result) {
|
|
// XXX: Remove this on v3.0.0
|
|
if len(uptime.SuccessfulExecutionsPerHour) != 0 || len(uptime.TotalExecutionsPerHour) != 0 {
|
|
uptime.migrateToHourlyStatistics()
|
|
}
|
|
if uptime.HourlyStatistics == nil {
|
|
uptime.HourlyStatistics = make(map[int64]*HourlyUptimeStatistics)
|
|
}
|
|
unixTimestampFlooredAtHour := result.Timestamp.Unix() - (result.Timestamp.Unix() % 3600)
|
|
hourlyStats, _ := uptime.HourlyStatistics[unixTimestampFlooredAtHour]
|
|
if hourlyStats == nil {
|
|
hourlyStats = &HourlyUptimeStatistics{}
|
|
uptime.HourlyStatistics[unixTimestampFlooredAtHour] = hourlyStats
|
|
}
|
|
if result.Success {
|
|
hourlyStats.SuccessfulExecutions++
|
|
}
|
|
hourlyStats.TotalExecutions++
|
|
hourlyStats.TotalExecutionsResponseTime += uint64(result.Duration.Milliseconds())
|
|
// Clean up only when we're starting to have too many useless keys
|
|
// Note that this is only triggered when there are more entries than there should be after
|
|
// 10 days, despite the fact that we are deleting everything that's older than 7 days.
|
|
// This is to prevent re-iterating on every `ProcessResult` as soon as the uptime has been logged for 7 days.
|
|
if len(uptime.HourlyStatistics) > numberOfHoursInTenDays {
|
|
sevenDaysAgo := time.Now().Add(-(sevenDays + time.Hour)).Unix()
|
|
for hourlyUnixTimestamp := range uptime.HourlyStatistics {
|
|
if sevenDaysAgo > hourlyUnixTimestamp {
|
|
delete(uptime.HourlyStatistics, hourlyUnixTimestamp)
|
|
}
|
|
}
|
|
}
|
|
if result.Success {
|
|
// Recalculate uptime if at least one of the 1h, 24h or 7d uptime are not 100%
|
|
// If they're all 100%, then recalculating the uptime would be useless unless
|
|
// the result added was a failure (!result.Success)
|
|
if uptime.LastSevenDays != 1 || uptime.LastTwentyFourHours != 1 || uptime.LastHour != 1 {
|
|
uptime.recalculate()
|
|
}
|
|
} else {
|
|
// Recalculate uptime if at least one of the 1h, 24h or 7d uptime are not 0%
|
|
// If they're all 0%, then recalculating the uptime would be useless unless
|
|
// the result added was a success (result.Success)
|
|
if uptime.LastSevenDays != 0 || uptime.LastTwentyFourHours != 0 || uptime.LastHour != 0 {
|
|
uptime.recalculate()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (uptime *Uptime) recalculate() {
|
|
uptimeBrackets := make(map[string]uint64)
|
|
now := time.Now()
|
|
// The oldest uptime bracket starts 7 days ago, so we'll start from there
|
|
timestamp := now.Add(-sevenDays)
|
|
for now.Sub(timestamp) >= 0 {
|
|
hourlyUnixTimestamp := timestamp.Unix() - (timestamp.Unix() % 3600)
|
|
hourlyStats := uptime.HourlyStatistics[hourlyUnixTimestamp]
|
|
if hourlyStats == nil || hourlyStats.TotalExecutions == 0 {
|
|
timestamp = timestamp.Add(time.Hour)
|
|
continue
|
|
}
|
|
uptimeBrackets["7d_success"] += hourlyStats.SuccessfulExecutions
|
|
uptimeBrackets["7d_total"] += hourlyStats.TotalExecutions
|
|
if now.Sub(timestamp) <= 24*time.Hour {
|
|
uptimeBrackets["24h_success"] += hourlyStats.SuccessfulExecutions
|
|
uptimeBrackets["24h_total"] += hourlyStats.TotalExecutions
|
|
}
|
|
if now.Sub(timestamp) <= time.Hour {
|
|
uptimeBrackets["1h_success"] += hourlyStats.SuccessfulExecutions
|
|
uptimeBrackets["1h_total"] += hourlyStats.TotalExecutions
|
|
}
|
|
timestamp = timestamp.Add(time.Hour)
|
|
}
|
|
if uptimeBrackets["7d_total"] > 0 {
|
|
uptime.LastSevenDays = float64(uptimeBrackets["7d_success"]) / float64(uptimeBrackets["7d_total"])
|
|
}
|
|
if uptimeBrackets["24h_total"] > 0 {
|
|
uptime.LastTwentyFourHours = float64(uptimeBrackets["24h_success"]) / float64(uptimeBrackets["24h_total"])
|
|
}
|
|
if uptimeBrackets["1h_total"] > 0 {
|
|
uptime.LastHour = float64(uptimeBrackets["1h_success"]) / float64(uptimeBrackets["1h_total"])
|
|
}
|
|
}
|
|
|
|
// XXX: Remove this on v3.0.0
|
|
// Deprecated
|
|
func (uptime *Uptime) migrateToHourlyStatistics() {
|
|
log.Println("[migrateToHourlyStatistics] Got", len(uptime.SuccessfulExecutionsPerHour), "entries for successful executions and", len(uptime.TotalExecutionsPerHour), "entries for total executions")
|
|
uptime.HourlyStatistics = make(map[int64]*HourlyUptimeStatistics)
|
|
for hourlyUnixTimestamp, totalExecutions := range uptime.TotalExecutionsPerHour {
|
|
if totalExecutions == 0 {
|
|
log.Println("[migrateToHourlyStatistics] Skipping entry at", hourlyUnixTimestamp, "because total number of executions is 0")
|
|
continue
|
|
}
|
|
uptime.HourlyStatistics[hourlyUnixTimestamp] = &HourlyUptimeStatistics{
|
|
TotalExecutions: totalExecutions,
|
|
SuccessfulExecutions: uptime.SuccessfulExecutionsPerHour[hourlyUnixTimestamp],
|
|
TotalExecutionsResponseTime: 0,
|
|
}
|
|
}
|
|
log.Println("[migrateToHourlyStatistics] Migrated", len(uptime.HourlyStatistics), "entries")
|
|
uptime.SuccessfulExecutionsPerHour = nil
|
|
uptime.TotalExecutionsPerHour = nil
|
|
}
|