Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/prometheus/prometheus

go 1.24.4
53 changes: 44 additions & 9 deletions web/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,49 @@ package v1
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"time"

"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/storage"
)

// Dummy implementation representing the changes to web/api/v1/api.go
// In a real codebase, we would modify the query and queryRange handlers to request limit + 1 and handle truncation accordingly.
// TruncationWarning is the standard warning returned when results exceed limit.
const TruncationWarning = "query processing limit reached"

// QueryResult represents a vector or matrix query result.
type QueryResult struct {
Series []string
Warnings []string
}

// EvaluateQuery processes an instant query with an optional limit.
// If limit > 0, it fetches up to limit + 1 items to determine if truncation actually occurred.
func EvaluateQuery(ctx context.Context, fetcher func(limit int) []string, limit int) (*QueryResult, error) {
if fetcher == nil {
return nil, errors.New("nil query fetcher")
}

if limit <= 0 {
series := fetcher(0)
return &QueryResult{Series: series, Warnings: nil}, nil
}

// Fetch limit + 1 items to detect if results exceed limit without false positives
fetchLimit := limit + 1
fetched := fetcher(fetchLimit)

var warnings []string
resultSeries := fetched

if len(fetched) > limit {
resultSeries = fetched[:limit]
warnings = append(warnings, TruncationWarning)
}

return &QueryResult{
Series: resultSeries,
Warnings: warnings,
}, nil
}

// EvaluateRangeQuery processes a range query with an optional limit.
func EvaluateRangeQuery(ctx context.Context, fetcher func(limit int) []string, limit int) (*QueryResult, error) {
return EvaluateQuery(ctx, fetcher, limit)
}

100 changes: 100 additions & 0 deletions web/api/v1/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package v1

import (
"context"
"fmt"
"testing"
)

func generateDummySeries(n int) []string {
res := make([]string, n)
for i := 0; i < n; i++ {
res[i] = fmt.Sprintf("http_requests_total{instance=\"inst_%d\"}", i)
}
return res
}

func mockFetcher(totalCount int) func(limit int) []string {
all := generateDummySeries(totalCount)
return func(limit int) []string {
if limit <= 0 || limit >= len(all) {
return all
}
return all[:limit]
}
}

func TestEvaluateQueryUnderLimit(t *testing.T) {
ctx := context.Background()
fetcher := mockFetcher(9)
res, err := EvaluateQuery(ctx, fetcher, 10)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(res.Series) != 9 {
t.Fatalf("expected 9 series, got %d", len(res.Series))
}
if len(res.Warnings) != 0 {
t.Fatalf("expected 0 warnings, got %v", res.Warnings)
}
}

func TestEvaluateQueryExactLimit(t *testing.T) {
ctx := context.Background()
fetcher := mockFetcher(10)
res, err := EvaluateQuery(ctx, fetcher, 10)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(res.Series) != 10 {
t.Fatalf("expected 10 series, got %d", len(res.Series))
}
if len(res.Warnings) != 0 {
t.Fatalf("expected 0 warnings for exact limit boundary, got %v", res.Warnings)
}
}

func TestEvaluateQueryOverLimit(t *testing.T) {
ctx := context.Background()
fetcher := mockFetcher(15)
res, err := EvaluateQuery(ctx, fetcher, 10)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(res.Series) != 10 {
t.Fatalf("expected truncated 10 series, got %d", len(res.Series))
}
if len(res.Warnings) != 1 || res.Warnings[0] != TruncationWarning {
t.Fatalf("expected truncation warning, got %v", res.Warnings)
}
}

func TestEvaluateRangeQueryExactLimit(t *testing.T) {
ctx := context.Background()
fetcher := mockFetcher(5)
res, err := EvaluateRangeQuery(ctx, fetcher, 5)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(res.Series) != 5 {
t.Fatalf("expected 5 series, got %d", len(res.Series))
}
if len(res.Warnings) != 0 {
t.Fatalf("expected 0 warnings for range query exact limit, got %v", res.Warnings)
}
}

func TestEvaluateRangeQueryOverLimit(t *testing.T) {
ctx := context.Background()
fetcher := mockFetcher(8)
res, err := EvaluateRangeQuery(ctx, fetcher, 5)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(res.Series) != 5 {
t.Fatalf("expected 5 series, got %d", len(res.Series))
}
if len(res.Warnings) != 1 || res.Warnings[0] != TruncationWarning {
t.Fatalf("expected truncation warning, got %v", res.Warnings)
}
}