package healthcheck import ( "context" "time" "golang.org/x/exp/slices" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/healthcheck/health" "github.com/coder/coder/v2/codersdk/healthsdk" ) const ( DatabaseDefaultThreshold = 15 * time.Millisecond ) type DatabaseReport healthsdk.DatabaseReport type DatabaseReportOptions struct { DB database.Store Threshold time.Duration Dismissed bool } func (r *DatabaseReport) Run(ctx context.Context, opts *DatabaseReportOptions) { r.Warnings = []health.Message{} r.Severity = health.SeverityOK r.Dismissed = opts.Dismissed r.ThresholdMS = opts.Threshold.Milliseconds() if r.ThresholdMS == 0 { r.ThresholdMS = DatabaseDefaultThreshold.Milliseconds() } ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() pingCount := 5 pings := make([]time.Duration, 0, pingCount) // Ping 5 times and average the latency. for i := 0; i < pingCount; i++ { pong, err := opts.DB.Ping(ctx) if err != nil { r.Error = health.Errorf(health.CodeDatabasePingFailed, "ping database: %s", err) r.Severity = health.SeverityError return } pings = append(pings, pong) } slices.Sort(pings) // Take the median ping. latency := pings[pingCount/2] r.Latency = latency.String() r.LatencyMS = latency.Milliseconds() if r.LatencyMS >= r.ThresholdMS { r.Severity = health.SeverityWarning r.Warnings = append(r.Warnings, health.Messagef(health.CodeDatabasePingSlow, "median database ping above threshold")) } r.Healthy = true r.Reachable = true }