2020-12-30 02:22:17 +01:00
package core
import (
2021-04-18 06:51:47 +02:00
"log"
2020-12-30 02:22:17 +01:00
"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
2021-04-18 06:51:47 +02:00
// and some other statistics
2020-12-30 02:22:17 +01:00
type Uptime struct {
2021-04-18 07:01:10 +02:00
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
2020-12-30 02:22:17 +01:00
2021-03-06 21:19:35 +01:00
// SuccessfulExecutionsPerHour is a map containing the number of successes (value)
// for every hourly unix timestamps (key)
2021-04-18 06:51:47 +02:00
// Deprecated
2021-03-06 21:19:35 +01:00
SuccessfulExecutionsPerHour map [ int64 ] uint64 ` json:"-" `
2021-02-03 05:06:34 +01:00
2021-03-06 21:19:35 +01:00
// TotalExecutionsPerHour is a map containing the total number of checks (value)
// for every hourly unix timestamps (key)
2021-04-18 06:51:47 +02:00
// Deprecated
2021-03-06 21:19:35 +01:00
TotalExecutionsPerHour map [ int64 ] uint64 ` json:"-" `
2021-04-18 06:51:47 +02:00
// 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
2020-12-30 02:22:17 +01:00
}
// NewUptime creates a new Uptime
func NewUptime ( ) * Uptime {
return & Uptime {
2021-04-18 06:51:47 +02:00
HourlyStatistics : make ( map [ int64 ] * HourlyUptimeStatistics ) ,
2020-12-30 02:22:17 +01:00
}
}
// ProcessResult processes the result by extracting the relevant from the result and recalculating the uptime
// if necessary
func ( uptime * Uptime ) ProcessResult ( result * Result ) {
2021-04-18 06:51:47 +02:00
// 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 )
2021-03-06 21:19:35 +01:00
}
2021-03-05 05:00:30 +01:00
unixTimestampFlooredAtHour := result . Timestamp . Unix ( ) - ( result . Timestamp . Unix ( ) % 3600 )
2021-04-18 06:51:47 +02:00
hourlyStats , _ := uptime . HourlyStatistics [ unixTimestampFlooredAtHour ]
if hourlyStats == nil {
hourlyStats = & HourlyUptimeStatistics { }
uptime . HourlyStatistics [ unixTimestampFlooredAtHour ] = hourlyStats
}
2020-12-30 02:22:17 +01:00
if result . Success {
2021-04-18 06:51:47 +02:00
hourlyStats . SuccessfulExecutions ++
2020-12-30 02:22:17 +01:00
}
2021-04-18 06:51:47 +02:00
hourlyStats . TotalExecutions ++
hourlyStats . TotalExecutionsResponseTime += uint64 ( result . Duration . Milliseconds ( ) )
2020-12-30 02:22:17 +01:00
// 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.
2021-04-18 06:51:47 +02:00
if len ( uptime . HourlyStatistics ) > numberOfHoursInTenDays {
2021-03-05 05:00:30 +01:00
sevenDaysAgo := time . Now ( ) . Add ( - ( sevenDays + time . Hour ) ) . Unix ( )
2021-04-18 06:51:47 +02:00
for hourlyUnixTimestamp := range uptime . HourlyStatistics {
2021-03-05 05:00:30 +01:00
if sevenDaysAgo > hourlyUnixTimestamp {
2021-04-18 06:51:47 +02:00
delete ( uptime . HourlyStatistics , hourlyUnixTimestamp )
2020-12-30 02:22:17 +01:00
}
}
}
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 {
2021-03-05 05:00:30 +01:00
hourlyUnixTimestamp := timestamp . Unix ( ) - ( timestamp . Unix ( ) % 3600 )
2021-04-18 06:51:47 +02:00
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
2020-12-30 02:22:17 +01:00
if now . Sub ( timestamp ) <= 24 * time . Hour {
2021-04-18 06:51:47 +02:00
uptimeBrackets [ "24h_success" ] += hourlyStats . SuccessfulExecutions
uptimeBrackets [ "24h_total" ] += hourlyStats . TotalExecutions
2020-12-30 02:22:17 +01:00
}
if now . Sub ( timestamp ) <= time . Hour {
2021-04-18 06:51:47 +02:00
uptimeBrackets [ "1h_success" ] += hourlyStats . SuccessfulExecutions
uptimeBrackets [ "1h_total" ] += hourlyStats . TotalExecutions
2020-12-30 02:22:17 +01:00
}
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" ] )
}
}
2021-04-18 06:51:47 +02:00
2021-04-26 01:56:09 +02:00
// XXX: Remove this on v3.0.0
// Deprecated
2021-04-18 06:51:47 +02:00
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
}