Files
gatus/watchdog/watchdog.go
TwiN d668a14703 feat(suite): Implement Suites (#1239)
* 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>
2025-09-05 15:39:12 -04:00

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()
}