diff --git a/server/observability/slo.go b/server/observability/slo.go index 42c5f13f..5e93cd95 100644 --- a/server/observability/slo.go +++ b/server/observability/slo.go @@ -1,6 +1,7 @@ package observability import ( + "math" "sort" "time" ) @@ -52,7 +53,13 @@ func percentile(values []time.Duration, p float64) time.Duration { } ordered := append([]time.Duration(nil), values...) sort.Slice(ordered, func(i, j int) bool { return ordered[i] < ordered[j] }) - index := int(float64(len(ordered)-1) * p) + index := int(math.Ceil(p*float64(len(ordered)))) - 1 + if index < 0 { + index = 0 + } + if index >= len(ordered) { + index = len(ordered) - 1 + } return ordered[index] } diff --git a/server/observability/slo_test.go b/server/observability/slo_test.go index 6e319306..6da4b4ae 100644 --- a/server/observability/slo_test.go +++ b/server/observability/slo_test.go @@ -25,3 +25,9 @@ func TestEvaluateSLODoesNotInventFailureForEmptyOptionalWindows(t *testing.T) { t.Fatalf("empty window violations = %+v", violations) } } + +func TestPercentileUsesConservativeNearestRankForSmallWindows(t *testing.T) { + if got := percentile([]time.Duration{time.Millisecond, 101 * time.Millisecond}, .95); got != 101*time.Millisecond { + t.Fatalf("p95 underreported small window: %s", got) + } +}