115 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			115 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package core
 | |
| 
 | |
| import (
 | |
| 	"log"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// RFC3339WithoutMinutesAndSeconds is the format defined by RFC3339 (see time.RFC3339) but with the minutes
 | |
| 	// and seconds hardcoded to 0.
 | |
| 	RFC3339WithoutMinutesAndSeconds = "2006-01-02T15:00:00Z07:00"
 | |
| 
 | |
| 	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
 | |
| type Uptime struct {
 | |
| 	// LastSevenDays is the uptime percentage over the past 7 days
 | |
| 	LastSevenDays float64 `json:"7d"`
 | |
| 
 | |
| 	// LastTwentyFourHours is the uptime percentage over the past 24 hours
 | |
| 	LastTwentyFourHours float64 `json:"24h"`
 | |
| 
 | |
| 	// LastHour is the uptime percentage over the past hour
 | |
| 	LastHour float64 `json:"1h"`
 | |
| 
 | |
| 	successCountPerHour map[string]uint64
 | |
| 	totalCountPerHour   map[string]uint64
 | |
| }
 | |
| 
 | |
| // NewUptime creates a new Uptime
 | |
| func NewUptime() *Uptime {
 | |
| 	return &Uptime{
 | |
| 		successCountPerHour: make(map[string]uint64),
 | |
| 		totalCountPerHour:   make(map[string]uint64),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ProcessResult processes the result by extracting the relevant from the result and recalculating the uptime
 | |
| // if necessary
 | |
| func (uptime *Uptime) ProcessResult(result *Result) {
 | |
| 	timestampDateWithHour := result.Timestamp.Format(RFC3339WithoutMinutesAndSeconds)
 | |
| 	if result.Success {
 | |
| 		uptime.successCountPerHour[timestampDateWithHour]++
 | |
| 	}
 | |
| 	uptime.totalCountPerHour[timestampDateWithHour]++
 | |
| 	// 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.totalCountPerHour) > numberOfHoursInTenDays {
 | |
| 		sevenDaysAgo := time.Now().Add(-(sevenDays + time.Hour))
 | |
| 		for k := range uptime.totalCountPerHour {
 | |
| 			dateWithHour, err := time.Parse(time.RFC3339, k)
 | |
| 			if err != nil {
 | |
| 				// This shouldn't happen, but we'll log it in case it does happen
 | |
| 				log.Println("[uptime][ProcessResult] Failed to parse programmatically generated timestamp:", err.Error())
 | |
| 				continue
 | |
| 			}
 | |
| 			if sevenDaysAgo.Unix() > dateWithHour.Unix() {
 | |
| 				delete(uptime.totalCountPerHour, k)
 | |
| 				delete(uptime.successCountPerHour, k)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	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 {
 | |
| 		timestampDateWithHour := timestamp.Format(RFC3339WithoutMinutesAndSeconds)
 | |
| 		successCountForTimestamp := uptime.successCountPerHour[timestampDateWithHour]
 | |
| 		totalCountForTimestamp := uptime.totalCountPerHour[timestampDateWithHour]
 | |
| 		uptimeBrackets["7d_success"] += successCountForTimestamp
 | |
| 		uptimeBrackets["7d_total"] += totalCountForTimestamp
 | |
| 		if now.Sub(timestamp) <= 24*time.Hour {
 | |
| 			uptimeBrackets["24h_success"] += successCountForTimestamp
 | |
| 			uptimeBrackets["24h_total"] += totalCountForTimestamp
 | |
| 		}
 | |
| 		if now.Sub(timestamp) <= time.Hour {
 | |
| 			uptimeBrackets["1h_success"] += successCountForTimestamp
 | |
| 			uptimeBrackets["1h_total"] += totalCountForTimestamp
 | |
| 		}
 | |
| 		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"])
 | |
| 	}
 | |
| }
 |