perf(storage): Improve benchmarks and fix race condition

This commit is contained in:
TwiN 2022-06-13 19:15:30 -04:00
parent 6d64c3c250
commit fea95b8479
8 changed files with 56 additions and 49 deletions

View File

@ -13,10 +13,6 @@ import (
) )
var ( var (
firstCondition = core.Condition("[STATUS] == 200")
secondCondition = core.Condition("[RESPONSE_TIME] < 500")
thirdCondition = core.Condition("[CERTIFICATE_EXPIRATION] < 72h")
timestamp = time.Now() timestamp = time.Now()
testEndpoint = core.Endpoint{ testEndpoint = core.Endpoint{
@ -26,7 +22,7 @@ var (
Method: "GET", Method: "GET",
Body: "body", Body: "body",
Interval: 30 * time.Second, Interval: 30 * time.Second,
Conditions: []*core.Condition{&firstCondition, &secondCondition, &thirdCondition}, Conditions: []core.Condition{core.Condition("[STATUS] == 200"), core.Condition("[RESPONSE_TIME] < 500"), core.Condition("[CERTIFICATE_EXPIRATION] < 72h")},
Alerts: nil, Alerts: nil,
NumberOfFailuresInARow: 0, NumberOfFailuresInARow: 0,
NumberOfSuccessesInARow: 0, NumberOfSuccessesInARow: 0,

View File

@ -89,7 +89,7 @@ type Endpoint struct {
Interval time.Duration `yaml:"interval,omitempty"` Interval time.Duration `yaml:"interval,omitempty"`
// Conditions used to determine the health of the endpoint // Conditions used to determine the health of the endpoint
Conditions []*Condition `yaml:"conditions"` Conditions []Condition `yaml:"conditions"`
// Alerts is the alerting configuration for the endpoint in case of failure // Alerts is the alerting configuration for the endpoint in case of failure
Alerts []*alert.Alert `yaml:"alerts,omitempty"` Alerts []*alert.Alert `yaml:"alerts,omitempty"`

View File

@ -80,11 +80,10 @@ func TestEndpoint_Type(t *testing.T) {
} }
func TestEndpoint_ValidateAndSetDefaults(t *testing.T) { func TestEndpoint_ValidateAndSetDefaults(t *testing.T) {
condition := Condition("[STATUS] == 200")
endpoint := Endpoint{ endpoint := Endpoint{
Name: "website-health", Name: "website-health",
URL: "https://twin.sh/health", URL: "https://twin.sh/health",
Conditions: []*Condition{&condition}, Conditions: []Condition{Condition("[STATUS] == 200")},
Alerts: []*alert.Alert{{Type: alert.TypePagerDuty}}, Alerts: []*alert.Alert{{Type: alert.TypePagerDuty}},
} }
endpoint.ValidateAndSetDefaults() endpoint.ValidateAndSetDefaults()
@ -129,7 +128,7 @@ func TestEndpoint_ValidateAndSetDefaultsWithClientConfig(t *testing.T) {
endpoint := Endpoint{ endpoint := Endpoint{
Name: "website-health", Name: "website-health",
URL: "https://twin.sh/health", URL: "https://twin.sh/health",
Conditions: []*Condition{&condition}, Conditions: []Condition{condition},
ClientConfig: &client.Config{ ClientConfig: &client.Config{
Insecure: true, Insecure: true,
IgnoreRedirect: true, IgnoreRedirect: true,
@ -158,7 +157,7 @@ func TestEndpoint_ValidateAndSetDefaultsWithNoName(t *testing.T) {
endpoint := &Endpoint{ endpoint := &Endpoint{
Name: "", Name: "",
URL: "http://example.com", URL: "http://example.com",
Conditions: []*Condition{&condition}, Conditions: []Condition{condition},
} }
err := endpoint.ValidateAndSetDefaults() err := endpoint.ValidateAndSetDefaults()
if err == nil { if err == nil {
@ -172,7 +171,7 @@ func TestEndpoint_ValidateAndSetDefaultsWithNoUrl(t *testing.T) {
endpoint := &Endpoint{ endpoint := &Endpoint{
Name: "example", Name: "example",
URL: "", URL: "",
Conditions: []*Condition{&condition}, Conditions: []Condition{condition},
} }
err := endpoint.ValidateAndSetDefaults() err := endpoint.ValidateAndSetDefaults()
if err == nil { if err == nil {
@ -194,7 +193,6 @@ func TestEndpoint_ValidateAndSetDefaultsWithNoConditions(t *testing.T) {
} }
func TestEndpoint_ValidateAndSetDefaultsWithDNS(t *testing.T) { func TestEndpoint_ValidateAndSetDefaultsWithDNS(t *testing.T) {
conditionSuccess := Condition("[DNS_RCODE] == NOERROR")
endpoint := &Endpoint{ endpoint := &Endpoint{
Name: "dns-test", Name: "dns-test",
URL: "http://example.com", URL: "http://example.com",
@ -202,7 +200,7 @@ func TestEndpoint_ValidateAndSetDefaultsWithDNS(t *testing.T) {
QueryType: "A", QueryType: "A",
QueryName: "example.com", QueryName: "example.com",
}, },
Conditions: []*Condition{&conditionSuccess}, Conditions: []Condition{Condition("[DNS_RCODE] == NOERROR")},
} }
err := endpoint.ValidateAndSetDefaults() err := endpoint.ValidateAndSetDefaults()
if err != nil { if err != nil {
@ -218,7 +216,7 @@ func TestEndpoint_buildHTTPRequest(t *testing.T) {
endpoint := Endpoint{ endpoint := Endpoint{
Name: "website-health", Name: "website-health",
URL: "https://twin.sh/health", URL: "https://twin.sh/health",
Conditions: []*Condition{&condition}, Conditions: []Condition{condition},
} }
endpoint.ValidateAndSetDefaults() endpoint.ValidateAndSetDefaults()
request := endpoint.buildHTTPRequest() request := endpoint.buildHTTPRequest()
@ -238,7 +236,7 @@ func TestEndpoint_buildHTTPRequestWithCustomUserAgent(t *testing.T) {
endpoint := Endpoint{ endpoint := Endpoint{
Name: "website-health", Name: "website-health",
URL: "https://twin.sh/health", URL: "https://twin.sh/health",
Conditions: []*Condition{&condition}, Conditions: []Condition{condition},
Headers: map[string]string{ Headers: map[string]string{
"User-Agent": "Test/2.0", "User-Agent": "Test/2.0",
}, },
@ -262,7 +260,7 @@ func TestEndpoint_buildHTTPRequestWithHostHeader(t *testing.T) {
Name: "website-health", Name: "website-health",
URL: "https://twin.sh/health", URL: "https://twin.sh/health",
Method: "POST", Method: "POST",
Conditions: []*Condition{&condition}, Conditions: []Condition{condition},
Headers: map[string]string{ Headers: map[string]string{
"Host": "example.com", "Host": "example.com",
}, },
@ -283,7 +281,7 @@ func TestEndpoint_buildHTTPRequestWithGraphQLEnabled(t *testing.T) {
Name: "website-graphql", Name: "website-graphql",
URL: "https://twin.sh/graphql", URL: "https://twin.sh/graphql",
Method: "POST", Method: "POST",
Conditions: []*Condition{&condition}, Conditions: []Condition{condition},
GraphQL: true, GraphQL: true,
Body: `{ Body: `{
users(gender: "female") { users(gender: "female") {
@ -314,7 +312,7 @@ func TestIntegrationEvaluateHealth(t *testing.T) {
endpoint := Endpoint{ endpoint := Endpoint{
Name: "website-health", Name: "website-health",
URL: "https://twin.sh/health", URL: "https://twin.sh/health",
Conditions: []*Condition{&condition, &bodyCondition}, Conditions: []Condition{condition, bodyCondition},
} }
endpoint.ValidateAndSetDefaults() endpoint.ValidateAndSetDefaults()
result := endpoint.EvaluateHealth() result := endpoint.EvaluateHealth()
@ -337,7 +335,7 @@ func TestIntegrationEvaluateHealthWithFailure(t *testing.T) {
endpoint := Endpoint{ endpoint := Endpoint{
Name: "website-health", Name: "website-health",
URL: "https://twin.sh/health", URL: "https://twin.sh/health",
Conditions: []*Condition{&condition}, Conditions: []Condition{condition},
} }
endpoint.ValidateAndSetDefaults() endpoint.ValidateAndSetDefaults()
result := endpoint.EvaluateHealth() result := endpoint.EvaluateHealth()
@ -357,7 +355,7 @@ func TestIntegrationEvaluateHealthWithInvalidCondition(t *testing.T) {
endpoint := Endpoint{ endpoint := Endpoint{
Name: "invalid-condition", Name: "invalid-condition",
URL: "https://twin.sh/health", URL: "https://twin.sh/health",
Conditions: []*Condition{&condition}, Conditions: []Condition{condition},
} }
if err := endpoint.ValidateAndSetDefaults(); err != nil { if err := endpoint.ValidateAndSetDefaults(); err != nil {
// XXX: Should this really not return an error? After all, the condition is not valid and conditions are part of the endpoint... // XXX: Should this really not return an error? After all, the condition is not valid and conditions are part of the endpoint...
@ -377,7 +375,7 @@ func TestIntegrationEvaluateHealthWithError(t *testing.T) {
endpoint := Endpoint{ endpoint := Endpoint{
Name: "invalid-host", Name: "invalid-host",
URL: "http://invalid/health", URL: "http://invalid/health",
Conditions: []*Condition{&condition}, Conditions: []Condition{condition},
UIConfig: &ui.Config{ UIConfig: &ui.Config{
HideHostname: true, HideHostname: true,
}, },
@ -408,7 +406,7 @@ func TestIntegrationEvaluateHealthForDNS(t *testing.T) {
QueryType: "A", QueryType: "A",
QueryName: "example.com.", QueryName: "example.com.",
}, },
Conditions: []*Condition{&conditionSuccess, &conditionBody}, Conditions: []Condition{conditionSuccess, conditionBody},
} }
endpoint.ValidateAndSetDefaults() endpoint.ValidateAndSetDefaults()
result := endpoint.EvaluateHealth() result := endpoint.EvaluateHealth()
@ -428,7 +426,7 @@ func TestIntegrationEvaluateHealthForICMP(t *testing.T) {
endpoint := Endpoint{ endpoint := Endpoint{
Name: "icmp-test", Name: "icmp-test",
URL: "icmp://127.0.0.1", URL: "icmp://127.0.0.1",
Conditions: []*Condition{&conditionSuccess}, Conditions: []Condition{conditionSuccess},
} }
endpoint.ValidateAndSetDefaults() endpoint.ValidateAndSetDefaults()
result := endpoint.EvaluateHealth() result := endpoint.EvaluateHealth()
@ -448,7 +446,7 @@ func TestEndpoint_getIP(t *testing.T) {
endpoint := Endpoint{ endpoint := Endpoint{
Name: "invalid-url-test", Name: "invalid-url-test",
URL: "", URL: "",
Conditions: []*Condition{&conditionSuccess}, Conditions: []Condition{conditionSuccess},
} }
result := &Result{} result := &Result{}
endpoint.getIP(result) endpoint.getIP(result)
@ -461,22 +459,22 @@ func TestEndpoint_NeedsToReadBody(t *testing.T) {
statusCondition := Condition("[STATUS] == 200") statusCondition := Condition("[STATUS] == 200")
bodyCondition := Condition("[BODY].status == UP") bodyCondition := Condition("[BODY].status == UP")
bodyConditionWithLength := Condition("len([BODY].tags) > 0") bodyConditionWithLength := Condition("len([BODY].tags) > 0")
if (&Endpoint{Conditions: []*Condition{&statusCondition}}).needsToReadBody() { if (&Endpoint{Conditions: []Condition{statusCondition}}).needsToReadBody() {
t.Error("expected false, got true") t.Error("expected false, got true")
} }
if !(&Endpoint{Conditions: []*Condition{&bodyCondition}}).needsToReadBody() { if !(&Endpoint{Conditions: []Condition{bodyCondition}}).needsToReadBody() {
t.Error("expected true, got false") t.Error("expected true, got false")
} }
if !(&Endpoint{Conditions: []*Condition{&bodyConditionWithLength}}).needsToReadBody() { if !(&Endpoint{Conditions: []Condition{bodyConditionWithLength}}).needsToReadBody() {
t.Error("expected true, got false") t.Error("expected true, got false")
} }
if !(&Endpoint{Conditions: []*Condition{&statusCondition, &bodyCondition}}).needsToReadBody() { if !(&Endpoint{Conditions: []Condition{statusCondition, bodyCondition}}).needsToReadBody() {
t.Error("expected true, got false") t.Error("expected true, got false")
} }
if !(&Endpoint{Conditions: []*Condition{&bodyCondition, &statusCondition}}).needsToReadBody() { if !(&Endpoint{Conditions: []Condition{bodyCondition, statusCondition}}).needsToReadBody() {
t.Error("expected true, got false") t.Error("expected true, got false")
} }
if !(&Endpoint{Conditions: []*Condition{&bodyConditionWithLength, &statusCondition}}).needsToReadBody() { if !(&Endpoint{Conditions: []Condition{bodyConditionWithLength, statusCondition}}).needsToReadBody() {
t.Error("expected true, got false") t.Error("expected true, got false")
} }
} }

View File

@ -22,7 +22,7 @@ var (
Method: "GET", Method: "GET",
Body: "body", Body: "body",
Interval: 30 * time.Second, Interval: 30 * time.Second,
Conditions: []*core.Condition{&firstCondition, &secondCondition, &thirdCondition}, Conditions: []core.Condition{firstCondition, secondCondition, thirdCondition},
Alerts: nil, Alerts: nil,
NumberOfFailuresInARow: 0, NumberOfFailuresInARow: 0,
NumberOfSuccessesInARow: 0, NumberOfSuccessesInARow: 0,

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"strconv"
"strings" "strings"
"time" "time"
@ -579,7 +580,7 @@ func (s *Store) getEndpointResultsByEndpointID(tx *sql.Tx, endpointID int64, pag
WHERE endpoint_result_id IN (` WHERE endpoint_result_id IN (`
index := 1 index := 1
for endpointResultID := range idResultMap { for endpointResultID := range idResultMap {
query += fmt.Sprintf("$%d,", index) query += "$" + strconv.Itoa(index) + ","
args = append(args, endpointResultID) args = append(args, endpointResultID)
index++ index++
} }

View File

@ -23,7 +23,7 @@ var (
Method: "GET", Method: "GET",
Body: "body", Body: "body",
Interval: 30 * time.Second, Interval: 30 * time.Second,
Conditions: []*core.Condition{&firstCondition, &secondCondition, &thirdCondition}, Conditions: []core.Condition{firstCondition, secondCondition, thirdCondition},
Alerts: nil, Alerts: nil,
NumberOfFailuresInARow: 0, NumberOfFailuresInARow: 0,
NumberOfSuccessesInARow: 0, NumberOfSuccessesInARow: 0,

View File

@ -1,6 +1,7 @@
package store package store
import ( import (
"strconv"
"testing" "testing"
"time" "time"
@ -48,23 +49,34 @@ func BenchmarkStore_GetAllEndpointStatuses(b *testing.B) {
}, },
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {
scenario.Store.Insert(&testEndpoint, &testSuccessfulResult) numberOfEndpoints := []int{10, 25, 50, 100}
scenario.Store.Insert(&testEndpoint, &testUnsuccessfulResult) for _, numberOfEndpointsToCreate := range numberOfEndpoints {
b.Run(scenario.Name, func(b *testing.B) { // Create endpoints and insert results
if scenario.Parallel { for i := 0; i < numberOfEndpointsToCreate; i++ {
b.RunParallel(func(pb *testing.PB) { endpoint := testEndpoint
for pb.Next() { endpoint.Name = "endpoint" + strconv.Itoa(i)
scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20)) // Insert 20 results for each endpoint
} for j := 0; j < 20; j++ {
}) scenario.Store.Insert(&endpoint, &testSuccessfulResult)
} else {
for n := 0; n < b.N; n++ {
scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20))
} }
} }
b.ReportAllocs() // Run the scenarios
}) b.Run(scenario.Name+"-with-"+strconv.Itoa(numberOfEndpointsToCreate)+"-endpoints", func(b *testing.B) {
scenario.Store.Clear() if scenario.Parallel {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, _ = scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20))
}
})
} else {
for n := 0; n < b.N; n++ {
_, _ = scenario.Store.GetAllEndpointStatuses(paging.NewEndpointStatusParams().WithResults(1, 20))
}
}
b.ReportAllocs()
})
scenario.Store.Clear()
}
} }
} }

View File

@ -26,7 +26,7 @@ var (
Method: "GET", Method: "GET",
Body: "body", Body: "body",
Interval: 30 * time.Second, Interval: 30 * time.Second,
Conditions: []*core.Condition{&firstCondition, &secondCondition, &thirdCondition}, Conditions: []core.Condition{firstCondition, secondCondition, thirdCondition},
Alerts: nil, Alerts: nil,
NumberOfFailuresInARow: 0, NumberOfFailuresInARow: 0,
NumberOfSuccessesInARow: 0, NumberOfSuccessesInARow: 0,