* feat(suite): Implement Suites Fixes #1230 * Update docs * Fix variable alignment * Prevent always-run endpoint from running if a context placeholder fails to resolve in the URL * Return errors when a context placeholder path fails to resolve * Add a couple of unit tests * Add a couple of unit tests * fix(ui): Update group count properly Fixes #1233 * refactor: Pass down entire config instead of several sub-configs * fix: Change default suite interval and timeout * fix: Deprecate disable-monitoring-lock in favor of concurrency * fix: Make sure there are no duplicate keys * Refactor some code * Update watchdog/watchdog.go * Update web/app/src/components/StepDetailsModal.vue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: Remove useless log * fix: Set default concurrency to 3 instead of 5 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
74 lines
2.4 KiB
Go
74 lines
2.4 KiB
Go
package watchdog
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/TwiN/gatus/v5/config"
|
|
"golang.org/x/sync/semaphore"
|
|
)
|
|
|
|
const (
|
|
// UnlimitedConcurrencyWeight is the semaphore weight used when concurrency is set to 0 (unlimited).
|
|
// This provides a practical upper limit while allowing very high concurrency for large deployments.
|
|
UnlimitedConcurrencyWeight = 10000
|
|
)
|
|
|
|
var (
|
|
// monitoringSemaphore is used to limit the number of endpoints/suites that can be evaluated concurrently.
|
|
// Without this, conditions using response time may become inaccurate.
|
|
monitoringSemaphore *semaphore.Weighted
|
|
|
|
ctx context.Context
|
|
cancelFunc context.CancelFunc
|
|
)
|
|
|
|
// Monitor loops over each endpoint and starts a goroutine to monitor each endpoint separately
|
|
func Monitor(cfg *config.Config) {
|
|
ctx, cancelFunc = context.WithCancel(context.Background())
|
|
// Initialize semaphore based on concurrency configuration
|
|
if cfg.Concurrency == 0 {
|
|
// Unlimited concurrency - use a very high limit
|
|
monitoringSemaphore = semaphore.NewWeighted(UnlimitedConcurrencyWeight)
|
|
} else {
|
|
// Limited concurrency based on configuration
|
|
monitoringSemaphore = semaphore.NewWeighted(int64(cfg.Concurrency))
|
|
}
|
|
extraLabels := cfg.GetUniqueExtraMetricLabels()
|
|
for _, endpoint := range cfg.Endpoints {
|
|
if endpoint.IsEnabled() {
|
|
// To prevent multiple requests from running at the same time, we'll wait for a little before each iteration
|
|
time.Sleep(222 * time.Millisecond)
|
|
go monitorEndpoint(endpoint, cfg, extraLabels, ctx)
|
|
}
|
|
}
|
|
for _, externalEndpoint := range cfg.ExternalEndpoints {
|
|
// Check if the external endpoint is enabled and is using heartbeat
|
|
// If the external endpoint does not use heartbeat, then it does not need to be monitored periodically, because
|
|
// alerting is checked every time an external endpoint is pushed to Gatus, unlike normal endpoints.
|
|
if externalEndpoint.IsEnabled() && externalEndpoint.Heartbeat.Interval > 0 {
|
|
go monitorExternalEndpointHeartbeat(externalEndpoint, cfg, extraLabels, ctx)
|
|
}
|
|
}
|
|
for _, suite := range cfg.Suites {
|
|
if suite.IsEnabled() {
|
|
time.Sleep(222 * time.Millisecond)
|
|
go monitorSuite(suite, cfg, extraLabels, ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Shutdown stops monitoring all endpoints
|
|
func Shutdown(cfg *config.Config) {
|
|
// Stop in-flight HTTP connections
|
|
for _, ep := range cfg.Endpoints {
|
|
ep.Close()
|
|
}
|
|
for _, s := range cfg.Suites {
|
|
for _, ep := range s.Endpoints {
|
|
ep.Close()
|
|
}
|
|
}
|
|
cancelFunc()
|
|
}
|