2021-02-03 04:06:34 +00:00
|
|
|
package memory
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/gob"
|
2021-09-03 03:09:29 +00:00
|
|
|
"sort"
|
2021-07-13 02:53:35 +00:00
|
|
|
"sync"
|
2021-07-14 05:53:14 +00:00
|
|
|
"time"
|
2021-02-03 04:06:34 +00:00
|
|
|
|
2022-06-21 01:25:14 +00:00
|
|
|
"github.com/TwiN/gatus/v4/core"
|
|
|
|
"github.com/TwiN/gatus/v4/storage/store/common"
|
|
|
|
"github.com/TwiN/gatus/v4/storage/store/common/paging"
|
|
|
|
"github.com/TwiN/gatus/v4/util"
|
2022-06-15 03:36:18 +00:00
|
|
|
"github.com/TwiN/gocache/v2"
|
2021-02-03 04:06:34 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2021-10-23 20:47:12 +00:00
|
|
|
gob.Register(&core.EndpointStatus{})
|
2021-08-07 16:22:39 +00:00
|
|
|
gob.Register(&core.HourlyUptimeStatistics{})
|
2021-02-03 04:06:34 +00:00
|
|
|
gob.Register(&core.Uptime{})
|
|
|
|
gob.Register(&core.Result{})
|
|
|
|
gob.Register(&core.Event{})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store that leverages gocache
|
|
|
|
type Store struct {
|
2021-07-13 02:53:35 +00:00
|
|
|
sync.RWMutex
|
2022-06-15 03:36:18 +00:00
|
|
|
|
2021-02-03 04:06:34 +00:00
|
|
|
cache *gocache.Cache
|
|
|
|
}
|
|
|
|
|
2021-07-18 00:18:27 +00:00
|
|
|
// NewStore creates a new store using gocache.Cache
|
|
|
|
//
|
|
|
|
// This store holds everything in memory, and if the file parameter is not blank,
|
|
|
|
// supports eventual persistence.
|
2021-02-03 04:06:34 +00:00
|
|
|
func NewStore(file string) (*Store, error) {
|
|
|
|
store := &Store{
|
|
|
|
cache: gocache.NewCache().WithMaxSize(gocache.NoMaxSize),
|
|
|
|
}
|
|
|
|
return store, nil
|
|
|
|
}
|
|
|
|
|
2021-10-23 20:47:12 +00:00
|
|
|
// GetAllEndpointStatuses returns all monitored core.EndpointStatus
|
2021-02-25 03:41:36 +00:00
|
|
|
// with a subset of core.Result defined by the page and pageSize parameters
|
2021-10-23 20:47:12 +00:00
|
|
|
func (s *Store) GetAllEndpointStatuses(params *paging.EndpointStatusParams) ([]*core.EndpointStatus, error) {
|
|
|
|
endpointStatuses := s.cache.GetAll()
|
|
|
|
pagedEndpointStatuses := make([]*core.EndpointStatus, 0, len(endpointStatuses))
|
|
|
|
for _, v := range endpointStatuses {
|
|
|
|
pagedEndpointStatuses = append(pagedEndpointStatuses, ShallowCopyEndpointStatus(v.(*core.EndpointStatus), params))
|
|
|
|
}
|
|
|
|
sort.Slice(pagedEndpointStatuses, func(i, j int) bool {
|
|
|
|
return pagedEndpointStatuses[i].Key < pagedEndpointStatuses[j].Key
|
2021-09-03 03:09:29 +00:00
|
|
|
})
|
2021-10-23 20:47:12 +00:00
|
|
|
return pagedEndpointStatuses, nil
|
2021-02-03 04:06:34 +00:00
|
|
|
}
|
|
|
|
|
2021-10-23 20:47:12 +00:00
|
|
|
// GetEndpointStatus returns the endpoint status for a given endpoint name in the given group
|
|
|
|
func (s *Store) GetEndpointStatus(groupName, endpointName string, params *paging.EndpointStatusParams) (*core.EndpointStatus, error) {
|
|
|
|
return s.GetEndpointStatusByKey(util.ConvertGroupAndEndpointNameToKey(groupName, endpointName), params)
|
2021-02-03 04:06:34 +00:00
|
|
|
}
|
|
|
|
|
2021-10-23 20:47:12 +00:00
|
|
|
// GetEndpointStatusByKey returns the endpoint status for a given key
|
|
|
|
func (s *Store) GetEndpointStatusByKey(key string, params *paging.EndpointStatusParams) (*core.EndpointStatus, error) {
|
|
|
|
endpointStatus := s.cache.GetValue(key)
|
|
|
|
if endpointStatus == nil {
|
|
|
|
return nil, common.ErrEndpointNotFound
|
2021-02-03 04:06:34 +00:00
|
|
|
}
|
2021-10-23 20:47:12 +00:00
|
|
|
return ShallowCopyEndpointStatus(endpointStatus.(*core.EndpointStatus), params), nil
|
2021-02-03 04:06:34 +00:00
|
|
|
}
|
|
|
|
|
2021-08-13 01:54:23 +00:00
|
|
|
// GetUptimeByKey returns the uptime percentage during a time range
|
|
|
|
func (s *Store) GetUptimeByKey(key string, from, to time.Time) (float64, error) {
|
|
|
|
if from.After(to) {
|
|
|
|
return 0, common.ErrInvalidTimeRange
|
|
|
|
}
|
2021-10-23 20:47:12 +00:00
|
|
|
endpointStatus := s.cache.GetValue(key)
|
|
|
|
if endpointStatus == nil || endpointStatus.(*core.EndpointStatus).Uptime == nil {
|
|
|
|
return 0, common.ErrEndpointNotFound
|
2021-08-13 01:54:23 +00:00
|
|
|
}
|
|
|
|
successfulExecutions := uint64(0)
|
|
|
|
totalExecutions := uint64(0)
|
|
|
|
current := from
|
|
|
|
for to.Sub(current) >= 0 {
|
|
|
|
hourlyUnixTimestamp := current.Truncate(time.Hour).Unix()
|
2021-10-23 20:47:12 +00:00
|
|
|
hourlyStats := endpointStatus.(*core.EndpointStatus).Uptime.HourlyStatistics[hourlyUnixTimestamp]
|
2021-08-13 01:54:23 +00:00
|
|
|
if hourlyStats == nil || hourlyStats.TotalExecutions == 0 {
|
|
|
|
current = current.Add(time.Hour)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
successfulExecutions += hourlyStats.SuccessfulExecutions
|
|
|
|
totalExecutions += hourlyStats.TotalExecutions
|
|
|
|
current = current.Add(time.Hour)
|
|
|
|
}
|
|
|
|
if totalExecutions == 0 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
return float64(successfulExecutions) / float64(totalExecutions), nil
|
|
|
|
}
|
|
|
|
|
2021-08-21 14:59:09 +00:00
|
|
|
// GetAverageResponseTimeByKey returns the average response time in milliseconds (value) during a time range
|
|
|
|
func (s *Store) GetAverageResponseTimeByKey(key string, from, to time.Time) (int, error) {
|
|
|
|
if from.After(to) {
|
|
|
|
return 0, common.ErrInvalidTimeRange
|
|
|
|
}
|
2021-10-23 20:47:12 +00:00
|
|
|
endpointStatus := s.cache.GetValue(key)
|
|
|
|
if endpointStatus == nil || endpointStatus.(*core.EndpointStatus).Uptime == nil {
|
|
|
|
return 0, common.ErrEndpointNotFound
|
2021-08-21 14:59:09 +00:00
|
|
|
}
|
|
|
|
current := from
|
|
|
|
var totalExecutions, totalResponseTime uint64
|
|
|
|
for to.Sub(current) >= 0 {
|
|
|
|
hourlyUnixTimestamp := current.Truncate(time.Hour).Unix()
|
2021-10-23 20:47:12 +00:00
|
|
|
hourlyStats := endpointStatus.(*core.EndpointStatus).Uptime.HourlyStatistics[hourlyUnixTimestamp]
|
2021-08-21 14:59:09 +00:00
|
|
|
if hourlyStats == nil || hourlyStats.TotalExecutions == 0 {
|
|
|
|
current = current.Add(time.Hour)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
totalExecutions += hourlyStats.TotalExecutions
|
|
|
|
totalResponseTime += hourlyStats.TotalExecutionsResponseTime
|
|
|
|
current = current.Add(time.Hour)
|
|
|
|
}
|
|
|
|
if totalExecutions == 0 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
return int(float64(totalResponseTime) / float64(totalExecutions)), nil
|
|
|
|
}
|
|
|
|
|
2021-08-20 03:07:21 +00:00
|
|
|
// GetHourlyAverageResponseTimeByKey returns a map of hourly (key) average response time in milliseconds (value) during a time range
|
|
|
|
func (s *Store) GetHourlyAverageResponseTimeByKey(key string, from, to time.Time) (map[int64]int, error) {
|
|
|
|
if from.After(to) {
|
|
|
|
return nil, common.ErrInvalidTimeRange
|
|
|
|
}
|
2021-10-23 20:47:12 +00:00
|
|
|
endpointStatus := s.cache.GetValue(key)
|
|
|
|
if endpointStatus == nil || endpointStatus.(*core.EndpointStatus).Uptime == nil {
|
|
|
|
return nil, common.ErrEndpointNotFound
|
2021-08-20 03:07:21 +00:00
|
|
|
}
|
|
|
|
hourlyAverageResponseTimes := make(map[int64]int)
|
|
|
|
current := from
|
|
|
|
for to.Sub(current) >= 0 {
|
|
|
|
hourlyUnixTimestamp := current.Truncate(time.Hour).Unix()
|
2021-10-23 20:47:12 +00:00
|
|
|
hourlyStats := endpointStatus.(*core.EndpointStatus).Uptime.HourlyStatistics[hourlyUnixTimestamp]
|
2021-08-20 03:07:21 +00:00
|
|
|
if hourlyStats == nil || hourlyStats.TotalExecutions == 0 {
|
|
|
|
current = current.Add(time.Hour)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
hourlyAverageResponseTimes[hourlyUnixTimestamp] = int(float64(hourlyStats.TotalExecutionsResponseTime) / float64(hourlyStats.TotalExecutions))
|
|
|
|
current = current.Add(time.Hour)
|
|
|
|
}
|
|
|
|
return hourlyAverageResponseTimes, nil
|
|
|
|
}
|
|
|
|
|
2021-10-23 20:47:12 +00:00
|
|
|
// Insert adds the observed result for the specified endpoint into the store
|
|
|
|
func (s *Store) Insert(endpoint *core.Endpoint, result *core.Result) error {
|
|
|
|
key := endpoint.Key()
|
2021-07-13 02:53:35 +00:00
|
|
|
s.Lock()
|
2021-10-23 20:47:12 +00:00
|
|
|
status, exists := s.cache.Get(key)
|
2021-02-03 04:06:34 +00:00
|
|
|
if !exists {
|
2021-10-23 20:47:12 +00:00
|
|
|
status = core.NewEndpointStatus(endpoint.Group, endpoint.Name)
|
|
|
|
status.(*core.EndpointStatus).Events = append(status.(*core.EndpointStatus).Events, &core.Event{
|
2021-07-14 05:53:14 +00:00
|
|
|
Type: core.EventStart,
|
|
|
|
Timestamp: time.Now(),
|
|
|
|
})
|
2021-02-03 04:06:34 +00:00
|
|
|
}
|
2021-10-23 20:47:12 +00:00
|
|
|
AddResult(status.(*core.EndpointStatus), result)
|
|
|
|
s.cache.Set(key, status)
|
2021-07-13 02:53:35 +00:00
|
|
|
s.Unlock()
|
2021-09-10 22:00:04 +00:00
|
|
|
return nil
|
2021-02-03 04:06:34 +00:00
|
|
|
}
|
|
|
|
|
2021-10-23 20:47:12 +00:00
|
|
|
// DeleteAllEndpointStatusesNotInKeys removes all EndpointStatus that are not within the keys provided
|
|
|
|
func (s *Store) DeleteAllEndpointStatusesNotInKeys(keys []string) int {
|
2021-02-03 04:06:34 +00:00
|
|
|
var keysToDelete []string
|
|
|
|
for _, existingKey := range s.cache.GetKeysByPattern("*", 0) {
|
|
|
|
shouldDelete := true
|
|
|
|
for _, key := range keys {
|
|
|
|
if existingKey == key {
|
|
|
|
shouldDelete = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if shouldDelete {
|
|
|
|
keysToDelete = append(keysToDelete, existingKey)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s.cache.DeleteAll(keysToDelete)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear deletes everything from the store
|
|
|
|
func (s *Store) Clear() {
|
|
|
|
s.cache.Clear()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Save persists the cache to the store file
|
|
|
|
func (s *Store) Save() error {
|
2021-02-06 01:45:28 +00:00
|
|
|
return nil
|
2021-02-03 04:06:34 +00:00
|
|
|
}
|
2021-07-16 02:07:30 +00:00
|
|
|
|
|
|
|
// Close does nothing, because there's nothing to close
|
|
|
|
func (s *Store) Close() {
|
|
|
|
return
|
|
|
|
}
|