Add GetUptimeByKey to store interface

This commit is contained in:
TwinProduction 2021-08-12 21:54:23 -04:00 committed by Chris
parent 968b960283
commit 0b6fc6b520
18 changed files with 182 additions and 71 deletions

View File

@ -6,9 +6,8 @@ import (
"strings"
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/gorilla/mux"
)
@ -19,22 +18,31 @@ import (
func badgeHandler(writer http.ResponseWriter, request *http.Request) {
variables := mux.Vars(request)
duration := variables["duration"]
if duration != "7d" && duration != "24h" && duration != "1h" {
var from time.Time
switch duration {
case "7d":
from = time.Now().Add(-time.Hour * 24 * 7)
case "24h":
from = time.Now().Add(-time.Hour * 24)
case "1h":
from = time.Now().Add(-time.Hour)
default:
writer.WriteHeader(http.StatusBadRequest)
_, _ = writer.Write([]byte("Durations supported: 7d, 24h, 1h"))
return
}
identifier := variables["identifier"]
key := strings.TrimSuffix(identifier, ".svg")
serviceStatus := storage.Get().GetServiceStatusByKey(key, paging.NewServiceStatusParams().WithUptime())
if serviceStatus == nil {
writer.WriteHeader(http.StatusNotFound)
_, _ = writer.Write([]byte("Requested service not found"))
return
}
if serviceStatus.Uptime == nil {
writer.WriteHeader(http.StatusInternalServerError)
_, _ = writer.Write([]byte("Failed to compute uptime"))
uptime, err := storage.Get().GetUptimeByKey(key, from, time.Now())
if err != nil {
if err == common.ErrServiceNotFound {
writer.WriteHeader(http.StatusNotFound)
} else if err == common.ErrInvalidTimeRange {
writer.WriteHeader(http.StatusBadRequest)
} else {
writer.WriteHeader(http.StatusInternalServerError)
}
_, _ = writer.Write([]byte(err.Error()))
return
}
formattedDate := time.Now().Format(http.TimeFormat)
@ -42,26 +50,22 @@ func badgeHandler(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Date", formattedDate)
writer.Header().Set("Expires", formattedDate)
writer.Header().Set("Content-Type", "image/svg+xml")
_, _ = writer.Write(generateSVG(duration, serviceStatus.Uptime))
_, _ = writer.Write(generateSVG(duration, uptime))
}
func generateSVG(duration string, uptime *core.Uptime) []byte {
func generateSVG(duration string, uptime float64) []byte {
var labelWidth, valueWidth, valueWidthAdjustment int
var value float64
switch duration {
case "7d":
labelWidth = 65
value = uptime.LastSevenDays
case "24h":
labelWidth = 70
value = uptime.LastTwentyFourHours
case "1h":
labelWidth = 65
value = uptime.LastHour
default:
}
color := getBadgeColorFromUptime(value)
sanitizedValue := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", value*100), "0"), ".") + "%"
color := getBadgeColorFromUptime(uptime)
sanitizedValue := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", uptime*100), "0"), ".") + "%"
if strings.Contains(sanitizedValue, ".") {
valueWidthAdjustment = -10
}

View File

@ -13,10 +13,10 @@ import (
"time"
"github.com/TwinProduction/gatus/config"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/security"
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gocache"
"github.com/TwinProduction/health"
"github.com/gorilla/mux"
@ -138,7 +138,7 @@ func serviceStatusesHandler(writer http.ResponseWriter, r *http.Request) {
func serviceStatusHandler(writer http.ResponseWriter, r *http.Request) {
page, pageSize := extractPageAndPageSizeFromRequest(r)
vars := mux.Vars(r)
serviceStatus := storage.Get().GetServiceStatusByKey(vars["key"], paging.NewServiceStatusParams().WithResults(page, pageSize).WithEvents(1, core.MaximumNumberOfEvents).WithUptime())
serviceStatus := storage.Get().GetServiceStatusByKey(vars["key"], paging.NewServiceStatusParams().WithResults(page, pageSize).WithEvents(1, common.MaximumNumberOfEvents).WithUptime())
if serviceStatus == nil {
log.Printf("[controller][serviceStatusHandler] Service with key=%s not found", vars["key"])
writer.WriteHeader(http.StatusNotFound)

View File

@ -4,7 +4,7 @@ import (
"net/http"
"strconv"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common"
)
const (
@ -15,7 +15,7 @@ const (
DefaultPageSize = 20
// MaximumPageSize is the maximum page size allowed
MaximumPageSize = core.MaximumNumberOfResults
MaximumPageSize = common.MaximumNumberOfResults
)
func extractPageAndPageSizeFromRequest(r *http.Request) (page int, pageSize int) {

View File

@ -1,13 +1,5 @@
package core
const (
// MaximumNumberOfResults is the maximum number of results that ServiceStatus.Results can have
MaximumNumberOfResults = 100
// MaximumNumberOfEvents is the maximum number of events that ServiceStatus.Events can have
MaximumNumberOfEvents = 50
)
// ServiceStatus contains the evaluation Results of a Service
type ServiceStatus struct {
// Name of the service
@ -34,6 +26,10 @@ type ServiceStatus struct {
// We don't expose this through JSON, because the main dashboard doesn't need to have this data.
// However, the detailed service page does leverage this by including it to a map that will be
// marshalled alongside the ServiceStatus.
//
// TODO: Get rid of this in favor of using the new store.GetUptimeByKey.
// TODO: For memory, store the uptime in a different map? (is that possible, given that we need to persist it through gocache?)
// Deprecated
Uptime *Uptime `json:"-"`
}

View File

@ -0,0 +1,8 @@
package common
import "errors"
var (
ErrServiceNotFound = errors.New("service not found") // When a service does not exist in the store
ErrInvalidTimeRange = errors.New("'from' cannot be older than 'to'") // When an invalid time range is provided
)

View File

@ -0,0 +1,9 @@
package common
const (
// MaximumNumberOfResults is the maximum number of results that a service can have
MaximumNumberOfResults = 100
// MaximumNumberOfEvents is the maximum number of events that a service can have
MaximumNumberOfEvents = 50
)

View File

@ -6,7 +6,8 @@ import (
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/util"
"github.com/TwinProduction/gocache"
)
@ -69,6 +70,35 @@ func (s *Store) GetServiceStatusByKey(key string, params *paging.ServiceStatusPa
return ShallowCopyServiceStatus(serviceStatus.(*core.ServiceStatus), params)
}
// 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
}
serviceStatus := s.cache.GetValue(key)
if serviceStatus == nil || serviceStatus.(*core.ServiceStatus).Uptime == nil {
return 0, common.ErrServiceNotFound
}
successfulExecutions := uint64(0)
totalExecutions := uint64(0)
current := from
for to.Sub(current) >= 0 {
hourlyUnixTimestamp := current.Truncate(time.Hour).Unix()
hourlyStats := serviceStatus.(*core.ServiceStatus).Uptime.HourlyStatistics[hourlyUnixTimestamp]
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
}
// Insert adds the observed result for the specified service into the store
func (s *Store) Insert(service *core.Service, result *core.Result) {
key := service.Key()

View File

@ -5,7 +5,7 @@ import (
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/common/paging"
)
var (

View File

@ -2,7 +2,8 @@ package memory
import (
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
)
// ShallowCopyServiceStatus returns a shallow copy of a ServiceStatus with only the results
@ -63,11 +64,11 @@ func AddResult(ss *core.ServiceStatus, result *core.Result) {
// Check if there's any change since the last result
if ss.Results[len(ss.Results)-1].Success != result.Success {
ss.Events = append(ss.Events, core.NewEventFromResult(result))
if len(ss.Events) > core.MaximumNumberOfEvents {
if len(ss.Events) > common.MaximumNumberOfEvents {
// Doing ss.Events[1:] would usually be sufficient, but in the case where for some reason, the slice has
// more than one extra element, we can get rid of all of them at once and thus returning the slice to a
// length of MaximumNumberOfEvents by using ss.Events[len(ss.Events)-MaximumNumberOfEvents:] instead
ss.Events = ss.Events[len(ss.Events)-core.MaximumNumberOfEvents:]
ss.Events = ss.Events[len(ss.Events)-common.MaximumNumberOfEvents:]
}
}
} else {
@ -75,11 +76,11 @@ func AddResult(ss *core.ServiceStatus, result *core.Result) {
ss.Events = append(ss.Events, core.NewEventFromResult(result))
}
ss.Results = append(ss.Results, result)
if len(ss.Results) > core.MaximumNumberOfResults {
if len(ss.Results) > common.MaximumNumberOfResults {
// Doing ss.Results[1:] would usually be sufficient, but in the case where for some reason, the slice has more
// than one extra element, we can get rid of all of them at once and thus returning the slice to a length of
// MaximumNumberOfResults by using ss.Results[len(ss.Results)-MaximumNumberOfResults:] instead
ss.Results = ss.Results[len(ss.Results)-core.MaximumNumberOfResults:]
ss.Results = ss.Results[len(ss.Results)-common.MaximumNumberOfResults:]
}
processUptimeAfterResult(ss.Uptime, result)
}

View File

@ -4,13 +4,14 @@ import (
"testing"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
)
func BenchmarkShallowCopyServiceStatus(b *testing.B) {
service := &testService
serviceStatus := core.NewServiceStatus(service.Key(), service.Group, service.Name)
for i := 0; i < core.MaximumNumberOfResults; i++ {
for i := 0; i < common.MaximumNumberOfResults; i++ {
AddResult(serviceStatus, &testSuccessfulResult)
}
for n := 0; n < b.N; n++ {

View File

@ -5,20 +5,21 @@ import (
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
)
func TestAddResult(t *testing.T) {
service := &core.Service{Name: "name", Group: "group"}
serviceStatus := core.NewServiceStatus(service.Key(), service.Group, service.Name)
for i := 0; i < (core.MaximumNumberOfResults+core.MaximumNumberOfEvents)*2; i++ {
for i := 0; i < (common.MaximumNumberOfResults+common.MaximumNumberOfEvents)*2; i++ {
AddResult(serviceStatus, &core.Result{Success: i%2 == 0, Timestamp: time.Now()})
}
if len(serviceStatus.Results) != core.MaximumNumberOfResults {
t.Errorf("expected serviceStatus.Results to not exceed a length of %d", core.MaximumNumberOfResults)
if len(serviceStatus.Results) != common.MaximumNumberOfResults {
t.Errorf("expected serviceStatus.Results to not exceed a length of %d", common.MaximumNumberOfResults)
}
if len(serviceStatus.Events) != core.MaximumNumberOfEvents {
t.Errorf("expected serviceStatus.Events to not exceed a length of %d", core.MaximumNumberOfEvents)
if len(serviceStatus.Events) != common.MaximumNumberOfEvents {
t.Errorf("expected serviceStatus.Events to not exceed a length of %d", common.MaximumNumberOfEvents)
}
// Try to add nil serviceStatus
AddResult(nil, &core.Result{Timestamp: time.Now()})

View File

@ -9,7 +9,8 @@ import (
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/util"
_ "modernc.org/sqlite"
)
@ -24,9 +25,9 @@ const (
// for aesthetic purposes, I deemed it wasn't worth the performance impact of yet another one-to-many table.
arraySeparator = "|~|"
uptimeCleanUpThreshold = 10 * 24 * time.Hour // Maximum uptime age before triggering a clean up
eventsCleanUpThreshold = core.MaximumNumberOfEvents + 10 // Maximum number of events before triggering a clean up
resultsCleanUpThreshold = core.MaximumNumberOfResults + 10 // Maximum number of results before triggering a clean up
uptimeCleanUpThreshold = 10 * 24 * time.Hour // Maximum uptime age before triggering a clean up
eventsCleanUpThreshold = common.MaximumNumberOfEvents + 10 // Maximum number of events before triggering a clean up
resultsCleanUpThreshold = common.MaximumNumberOfResults + 10 // Maximum number of results before triggering a clean up
uptimeRetention = 7 * 24 * time.Hour
)
@ -38,8 +39,7 @@ var (
// ErrDatabaseDriverNotSpecified is the error returned when the driver parameter passed in NewStore is blank
ErrDatabaseDriverNotSpecified = errors.New("database driver cannot be empty")
errServiceNotFoundInDatabase = errors.New("service does not exist in database")
errNoRowsReturned = errors.New("expected a row to be returned, but none was")
errNoRowsReturned = errors.New("expected a row to be returned, but none was")
)
// Store that leverages a database
@ -194,6 +194,31 @@ func (s *Store) GetServiceStatusByKey(key string, params *paging.ServiceStatusPa
return serviceStatus
}
// 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
}
tx, err := s.db.Begin()
if err != nil {
return 0, err
}
serviceID, _, _, err := s.getServiceIDGroupAndNameByKey(tx, key)
if err != nil {
_ = tx.Rollback()
return 0, err
}
uptime, _, err := s.getServiceUptime(tx, serviceID, from, to)
if err != nil {
_ = tx.Rollback()
return 0, err
}
if err = tx.Commit(); err != nil {
_ = tx.Rollback()
}
return uptime, nil
}
// Insert adds the observed result for the specified service into the store
func (s *Store) Insert(service *core.Service, result *core.Result) {
tx, err := s.db.Begin()
@ -203,7 +228,7 @@ func (s *Store) Insert(service *core.Service, result *core.Result) {
//start := time.Now()
serviceID, err := s.getServiceID(tx, service)
if err != nil {
if err == errServiceNotFoundInDatabase {
if err == common.ErrServiceNotFound {
// Service doesn't exist in the database, insert it
if serviceID, err = s.insertService(tx, service); err != nil {
_ = tx.Rollback()
@ -503,7 +528,7 @@ func (s *Store) getServiceIDGroupAndNameByKey(tx *sql.Tx, key string) (id int64,
}
_ = rows.Close()
if id == 0 {
return 0, "", "", errServiceNotFoundInDatabase
return 0, "", "", common.ErrServiceNotFound
}
return
}
@ -642,7 +667,7 @@ func (s *Store) getServiceID(tx *sql.Tx, service *core.Service) (int64, error) {
}
_ = rows.Close()
if !found {
return 0, errServiceNotFoundInDatabase
return 0, common.ErrServiceNotFound
}
return id, nil
}
@ -735,7 +760,7 @@ func (s *Store) deleteOldServiceEvents(tx *sql.Tx, serviceID int64) error {
)
`,
serviceID,
core.MaximumNumberOfEvents,
common.MaximumNumberOfEvents,
)
if err != nil {
return err
@ -760,7 +785,7 @@ func (s *Store) deleteOldServiceResults(tx *sql.Tx, serviceID int64) error {
)
`,
serviceID,
core.MaximumNumberOfResults,
common.MaximumNumberOfResults,
)
if err != nil {
return err

View File

@ -5,7 +5,8 @@ import (
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
)
var (
@ -157,7 +158,7 @@ func TestStore_InsertCleansUpEventsAndResultsProperly(t *testing.T) {
for i := 0; i < resultsCleanUpThreshold+eventsCleanUpThreshold; i++ {
store.Insert(&testService, &testSuccessfulResult)
store.Insert(&testService, &testUnsuccessfulResult)
ss := store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(1, core.MaximumNumberOfResults*5).WithEvents(1, core.MaximumNumberOfEvents*5))
ss := store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(1, common.MaximumNumberOfResults*5).WithEvents(1, common.MaximumNumberOfEvents*5))
if len(ss.Results) > resultsCleanUpThreshold+1 {
t.Errorf("number of results shouldn't have exceeded %d, reached %d", resultsCleanUpThreshold, len(ss.Results))
}
@ -173,7 +174,7 @@ func TestStore_Persistence(t *testing.T) {
store, _ := NewStore("sqlite", file)
store.Insert(&testService, &testSuccessfulResult)
store.Insert(&testService, &testUnsuccessfulResult)
ssFromOldStore := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, core.MaximumNumberOfResults).WithEvents(1, core.MaximumNumberOfEvents).WithUptime())
ssFromOldStore := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, common.MaximumNumberOfResults).WithEvents(1, common.MaximumNumberOfEvents).WithUptime())
if ssFromOldStore == nil || ssFromOldStore.Group != "group" || ssFromOldStore.Name != "name" || len(ssFromOldStore.Events) != 3 || len(ssFromOldStore.Results) != 2 || ssFromOldStore.Uptime.LastHour != 0.5 || ssFromOldStore.Uptime.LastTwentyFourHours != 0.5 || ssFromOldStore.Uptime.LastSevenDays != 0.5 {
store.Close()
t.Fatal("sanity check failed")
@ -181,7 +182,7 @@ func TestStore_Persistence(t *testing.T) {
store.Close()
store, _ = NewStore("sqlite", file)
defer store.Close()
ssFromNewStore := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, core.MaximumNumberOfResults).WithEvents(1, core.MaximumNumberOfEvents).WithUptime())
ssFromNewStore := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, common.MaximumNumberOfResults).WithEvents(1, common.MaximumNumberOfEvents).WithUptime())
if ssFromNewStore == nil || ssFromNewStore.Group != "group" || ssFromNewStore.Name != "name" || len(ssFromNewStore.Events) != 3 || len(ssFromNewStore.Results) != 2 || ssFromNewStore.Uptime.LastHour != 0.5 || ssFromNewStore.Uptime.LastTwentyFourHours != 0.5 || ssFromNewStore.Uptime.LastSevenDays != 0.5 {
t.Fatal("failed sanity check")
}

View File

@ -2,9 +2,10 @@ package store
import (
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/storage/store/memory"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/sqlite"
"time"
)
// Store is the interface that each stores should implement
@ -19,6 +20,9 @@ type Store interface {
// GetServiceStatusByKey returns the service status for a given key
GetServiceStatusByKey(key string, params *paging.ServiceStatusParams) *core.ServiceStatus
// GetUptimeByKey returns the uptime percentage during a time range
GetUptimeByKey(key string, from, to time.Time) (float64, error)
// Insert adds the observed result for the specified service into the store
Insert(service *core.Service, result *core.Result)

View File

@ -5,8 +5,8 @@ import (
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/storage/store/memory"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/sqlite"
)

View File

@ -5,8 +5,9 @@ import (
"time"
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/storage/store/memory"
"github.com/TwinProduction/gatus/storage/store/paging"
"github.com/TwinProduction/gatus/storage/store/sqlite"
)
@ -126,7 +127,7 @@ func TestStore_GetServiceStatusByKey(t *testing.T) {
scenario.Store.Insert(&testService, &firstResult)
scenario.Store.Insert(&testService, &secondResult)
serviceStatus := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime())
serviceStatus := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults).WithUptime())
if serviceStatus == nil {
t.Fatalf("serviceStatus shouldn't have been nil")
}
@ -165,15 +166,15 @@ func TestStore_GetServiceStatusForMissingStatusReturnsNil(t *testing.T) {
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
scenario.Store.Insert(&testService, &testSuccessfulResult)
serviceStatus := scenario.Store.GetServiceStatus("nonexistantgroup", "nonexistantname", paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime())
serviceStatus := scenario.Store.GetServiceStatus("nonexistantgroup", "nonexistantname", paging.NewServiceStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults).WithUptime())
if serviceStatus != nil {
t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", testService.Group, testService.Name)
}
serviceStatus = scenario.Store.GetServiceStatus(testService.Group, "nonexistantname", paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime())
serviceStatus = scenario.Store.GetServiceStatus(testService.Group, "nonexistantname", paging.NewServiceStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults).WithUptime())
if serviceStatus != nil {
t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", testService.Group, "nonexistantname")
}
serviceStatus = scenario.Store.GetServiceStatus("nonexistantgroup", testService.Name, paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime())
serviceStatus = scenario.Store.GetServiceStatus("nonexistantgroup", testService.Name, paging.NewServiceStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults).WithUptime())
if serviceStatus != nil {
t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", "nonexistantgroup", testService.Name)
}
@ -273,6 +274,36 @@ func TestStore_GetServiceStatusPage1IsHasMoreRecentResultsThanPage2(t *testing.T
}
}
func TestStore_GetUptimeByKey(t *testing.T) {
scenarios := initStoresAndBaseScenarios(t, "TestStore_GetUptimeByKey")
defer cleanUp(scenarios)
firstResult := testSuccessfulResult
firstResult.Timestamp = now.Add(-time.Minute)
secondResult := testUnsuccessfulResult
secondResult.Timestamp = now
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
if _, err := scenario.Store.GetUptimeByKey(testService.Key(), time.Now().Add(-time.Hour), time.Now()); err != common.ErrServiceNotFound {
t.Errorf("should've returned not found because there's nothing yet, got %v", err)
}
scenario.Store.Insert(&testService, &firstResult)
scenario.Store.Insert(&testService, &secondResult)
if uptime, _ := scenario.Store.GetUptimeByKey(testService.Key(), time.Now().Add(-time.Hour), time.Now()); uptime != 0.5 {
t.Errorf("the uptime over the past 1h should've been 0.5, got %f", uptime)
}
if uptime, _ := scenario.Store.GetUptimeByKey(testService.Key(), time.Now().Add(-time.Hour*24), time.Now()); uptime != 0.5 {
t.Errorf("the uptime over the past 24h should've been 0.5, got %f", uptime)
}
if uptime, _ := scenario.Store.GetUptimeByKey(testService.Key(), time.Now().Add(-time.Hour*24*7), time.Now()); uptime != 0.5 {
t.Errorf("the uptime over the past 7d should've been 0.5, got %f", uptime)
}
if _, err := scenario.Store.GetUptimeByKey(testService.Key(), time.Now(), time.Now().Add(-time.Hour)); err == nil {
t.Error("should've returned an error because the parameter 'from' cannot be older than 'to'")
}
})
}
}
func TestStore_Insert(t *testing.T) {
scenarios := initStoresAndBaseScenarios(t, "TestStore_Insert")
defer cleanUp(scenarios)
@ -285,7 +316,7 @@ func TestStore_Insert(t *testing.T) {
scenario.Store.Insert(&testService, &testSuccessfulResult)
scenario.Store.Insert(&testService, &testUnsuccessfulResult)
ss := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime())
ss := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults).WithUptime())
if ss == nil {
t.Fatalf("Store should've had key '%s', but didn't", testService.Key())
}