2025-12-22 04:38:48 -05:00
// Copyright The Prometheus Authors
2016-02-23 04:58:16 -05:00
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2018-02-01 04:55:07 -05:00
package scrape
2016-02-23 04:58:16 -05:00
import (
2017-01-15 11:33:07 -05:00
"bytes"
2021-05-15 22:19:22 -04:00
"compress/gzip"
2017-10-25 00:21:42 -04:00
"context"
2023-07-13 08:16:10 -04:00
"encoding/binary"
2023-11-01 15:06:46 -04:00
"errors"
2016-02-28 03:51:02 -05:00
"fmt"
2017-01-15 11:33:07 -05:00
"io"
2026-02-20 11:23:18 -05:00
"log/slog"
2025-08-27 08:38:54 -04:00
"maps"
2017-04-13 13:07:23 -04:00
"math"
2016-02-28 17:59:03 -05:00
"net/http"
"net/http/httptest"
"net/url"
2025-02-03 09:46:39 -05:00
"os"
"sort"
2022-05-31 05:31:20 -04:00
"strconv"
2016-02-28 17:59:03 -05:00
"strings"
2016-02-28 03:51:02 -05:00
"sync"
2016-02-23 04:58:16 -05:00
"testing"
2024-07-03 05:56:48 -04:00
"text/template"
2016-02-23 04:58:16 -05:00
"time"
2023-07-13 08:16:10 -04:00
"github.com/gogo/protobuf/proto"
2024-09-06 08:02:44 -04:00
"github.com/grafana/regexp"
2023-04-21 15:14:19 -04:00
"github.com/prometheus/client_golang/prometheus"
2024-08-19 05:58:35 -04:00
prom_testutil "github.com/prometheus/client_golang/prometheus/testutil"
2019-02-13 08:24:22 -05:00
dto "github.com/prometheus/client_model/go"
2020-01-22 07:13:47 -05:00
config_util "github.com/prometheus/common/config"
2024-12-29 10:10:39 -05:00
"github.com/prometheus/common/expfmt"
2016-02-23 04:58:16 -05:00
"github.com/prometheus/common/model"
2024-09-09 21:41:53 -04:00
"github.com/prometheus/common/promslog"
2020-10-29 05:43:23 -04:00
"github.com/stretchr/testify/require"
2025-07-15 03:37:24 -04:00
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
2025-10-30 06:58:31 -04:00
"go.uber.org/atomic"
2025-12-12 08:01:57 -05:00
"go.uber.org/goleak"
2016-02-23 04:58:16 -05:00
2016-02-23 05:56:09 -05:00
"github.com/prometheus/prometheus/config"
2024-03-27 11:32:37 -04:00
"github.com/prometheus/prometheus/discovery"
2018-01-04 09:13:31 -05:00
"github.com/prometheus/prometheus/discovery/targetgroup"
2021-11-08 09:23:17 -05:00
"github.com/prometheus/prometheus/model/exemplar"
2023-07-13 08:16:10 -04:00
"github.com/prometheus/prometheus/model/histogram"
2021-11-08 09:23:17 -05:00
"github.com/prometheus/prometheus/model/labels"
2025-01-15 06:33:42 -05:00
"github.com/prometheus/prometheus/model/metadata"
2021-11-08 09:23:17 -05:00
"github.com/prometheus/prometheus/model/relabel"
"github.com/prometheus/prometheus/model/textparse"
"github.com/prometheus/prometheus/model/timestamp"
"github.com/prometheus/prometheus/model/value"
2016-02-28 03:51:02 -05:00
"github.com/prometheus/prometheus/storage"
2026-01-21 03:21:56 -05:00
"github.com/prometheus/prometheus/tsdb"
2021-11-29 02:54:23 -05:00
"github.com/prometheus/prometheus/tsdb/chunkenc"
2023-10-17 05:27:46 -04:00
"github.com/prometheus/prometheus/util/pool"
2019-08-08 21:35:39 -04:00
"github.com/prometheus/prometheus/util/teststorage"
2017-09-15 05:08:51 -04:00
"github.com/prometheus/prometheus/util/testutil"
2016-02-23 04:58:16 -05:00
)
2020-07-27 04:38:08 -04:00
func TestMain ( m * testing . M ) {
testutil . TolerantVerifyLeak ( m )
}
2024-04-10 05:38:15 -04:00
func newTestRegistryAndScrapeMetrics ( t testing . TB ) ( * prometheus . Registry , * scrapeMetrics ) {
2023-09-22 12:47:44 -04:00
reg := prometheus . NewRegistry ( )
metrics , err := newScrapeMetrics ( reg )
require . NoError ( t , err )
2024-04-10 05:38:15 -04:00
return reg , metrics
}
func newTestScrapeMetrics ( t testing . TB ) * scrapeMetrics {
_ , metrics := newTestRegistryAndScrapeMetrics ( t )
2023-09-22 12:47:44 -04:00
return metrics
}
2016-02-28 03:51:02 -05:00
func TestNewScrapePool ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testNewScrapePool ( t , appV2 )
} )
}
func testNewScrapePool ( t * testing . T , appV2 bool ) {
2016-02-28 03:51:02 -05:00
var (
2025-12-22 04:38:48 -05:00
app = teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sa = selectAppendable ( app , appV2 )
2025-03-26 18:27:28 -04:00
cfg = & config . ScrapeConfig {
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
}
2026-01-21 03:21:56 -05:00
sp , err = newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2016-02-28 03:51:02 -05:00
)
2025-03-26 18:27:28 -04:00
require . NoError ( t , err )
2016-02-28 03:51:02 -05:00
2026-01-21 03:21:56 -05:00
if appV2 {
a , ok := sp . appendableV2 . ( * teststorage . Appendable )
require . True ( t , ok , "Failure to append." )
require . Equal ( t , app , a , "Wrong sample AppenderV2." )
require . Equal ( t , cfg , sp . config , "Wrong scrape config." )
require . Nil ( t , sp . appendable )
return
}
2025-12-22 04:38:48 -05:00
a , ok := sp . appendable . ( * teststorage . Appendable )
2021-09-04 08:35:03 -04:00
require . True ( t , ok , "Failure to append." )
require . Equal ( t , app , a , "Wrong sample appender." )
require . Equal ( t , cfg , sp . config , "Wrong scrape config." )
2026-01-21 03:21:56 -05:00
require . Nil ( t , sp . appendableV2 )
2016-02-28 03:51:02 -05:00
}
2024-10-23 11:34:28 -04:00
func TestStorageHandlesOutOfOrderTimestamps ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testStorageHandlesOutOfOrderTimestamps ( t , appV2 )
} )
}
func testStorageHandlesOutOfOrderTimestamps ( t * testing . T , appV2 bool ) {
2024-10-23 11:34:28 -04:00
// Test with default OutOfOrderTimeWindow (0)
t . Run ( "Out-Of-Order Sample Disabled" , func ( t * testing . T ) {
s := teststorage . New ( t )
2026-01-21 03:21:56 -05:00
runScrapeLoopTest ( t , appV2 , s , false )
2024-10-23 11:34:28 -04:00
} )
// Test with specific OutOfOrderTimeWindow (600000)
t . Run ( "Out-Of-Order Sample Enabled" , func ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
s := teststorage . New ( t , func ( opt * tsdb . Options ) {
opt . OutOfOrderTimeWindow = 600000
} )
2024-10-23 11:34:28 -04:00
2026-01-21 03:21:56 -05:00
runScrapeLoopTest ( t , appV2 , s , true )
2024-10-23 11:34:28 -04:00
} )
}
2026-01-21 03:21:56 -05:00
func runScrapeLoopTest ( t * testing . T , appV2 bool , s * teststorage . TestStorage , expectOutOfOrder bool ) {
sl , _ := newTestScrapeLoop ( t , withAppendable ( s , appV2 ) )
2024-10-23 11:34:28 -04:00
// Current time for generating timestamps.
now := time . Now ( )
// Calculate timestamps for the samples based on the current time.
now = now . Truncate ( time . Minute ) // round down the now timestamp to the nearest minute
timestampInorder1 := now
timestampOutOfOrder := now . Add ( - 5 * time . Minute )
timestampInorder2 := now . Add ( 5 * time . Minute )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( ` metric_total { a="1",b="1"} 1 ` ) , "text/plain" , timestampInorder1 )
2024-10-23 11:34:28 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
_ , _ , _ , err = app . append ( [ ] byte ( ` metric_total { a="1",b="1"} 2 ` ) , "text/plain" , timestampOutOfOrder )
2024-10-23 11:34:28 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
_ , _ , _ , err = app . append ( [ ] byte ( ` metric_total { a="1",b="1"} 3 ` ) , "text/plain" , timestampInorder2 )
2024-10-23 11:34:28 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2024-10-23 11:34:28 -04:00
// Query the samples back from the storage.
q , err := s . Querier ( time . Time { } . UnixNano ( ) , time . Now ( ) . UnixNano ( ) )
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
t . Cleanup ( func ( ) { _ = q . Close ( ) } )
2024-10-23 11:34:28 -04:00
// Use a matcher to filter the metric name.
2025-12-22 04:38:48 -05:00
series := q . Select ( t . Context ( ) , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , "metric_total" ) )
2024-10-23 11:34:28 -04:00
2025-12-22 04:38:48 -05:00
var results [ ] sample
2024-10-23 11:34:28 -04:00
for series . Next ( ) {
it := series . At ( ) . Iterator ( nil )
for it . Next ( ) == chunkenc . ValFloat {
t , v := it . At ( )
2025-12-22 04:38:48 -05:00
results = append ( results , sample {
L : series . At ( ) . Labels ( ) ,
T : t ,
V : v ,
2024-10-23 11:34:28 -04:00
} )
}
require . NoError ( t , it . Err ( ) )
}
require . NoError ( t , series . Err ( ) )
// Define the expected results
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2024-10-23 11:34:28 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( "__name__" , "metric_total" , "a" , "1" , "b" , "1" ) ,
T : timestamp . FromTime ( timestampInorder1 ) ,
V : 1 ,
2024-10-23 11:34:28 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( "__name__" , "metric_total" , "a" , "1" , "b" , "1" ) ,
T : timestamp . FromTime ( timestampInorder2 ) ,
V : 3 ,
2024-10-23 11:34:28 -04:00
} ,
}
if expectOutOfOrder {
2026-01-21 03:21:56 -05:00
teststorage . RequireNotEqual ( t , want , results , "Expected results to include out-of-order sample:\n%s" , results )
2024-10-23 11:34:28 -04:00
} else {
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , results , "Appended samples not as expected:\n%s" , results )
2024-10-23 11:34:28 -04:00
}
}
2025-01-15 06:33:42 -05:00
// Regression test against https://github.com/prometheus/prometheus/issues/15831.
2025-12-22 04:38:48 -05:00
func TestScrapeAppend_MetadataUpdate ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeAppendMetadataUpdate ( t , appV2 )
} )
}
func testScrapeAppendMetadataUpdate ( t * testing . T , appV2 bool ) {
2025-01-15 06:33:42 -05:00
const (
scrape1 = ` # TYPE test_metric counter
# HELP test_metric some help text
# UNIT test_metric metric
test_metric_total 1
# TYPE test_metric2 gauge
# HELP test_metric2 other help text
test_metric2 { foo = "bar" } 2
# TYPE test_metric3 gauge
# HELP test_metric3 this represents tricky case of "broken" text that is not trivial to detect
test_metric3_metric4 { foo = "bar" } 2
# EOF `
scrape2 = ` # TYPE test_metric counter
# HELP test_metric different help text
test_metric_total 11
# TYPE test_metric2 gauge
# HELP test_metric2 other help text
# UNIT test_metric2 metric2
test_metric2 { foo = "bar" } 22
# EOF `
)
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
2025-01-15 06:33:42 -05:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( scrape1 ) , "application/openmetrics-text" , now )
2025-01-15 06:33:42 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , [ ] sample {
2025-12-22 04:38:48 -05:00
{ L : labels . FromStrings ( "__name__" , "test_metric_total" ) , M : metadata . Metadata { Type : "counter" , Unit : "metric" , Help : "some help text" } } ,
{ L : labels . FromStrings ( "__name__" , "test_metric2" , "foo" , "bar" ) , M : metadata . Metadata { Type : "gauge" , Unit : "" , Help : "other help text" } } ,
} , appTest . ResultMetadata ( ) )
appTest . ResultReset ( )
app = sl . appender ( )
_ , _ , _ , err = app . append ( [ ] byte ( scrape1 ) , "application/openmetrics-text" , now . Add ( 15 * time . Second ) )
2025-01-15 06:33:42 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2026-01-21 03:21:56 -05:00
if appV2 {
// Next (the same) scrape should pass new metadata entries as per always-on metadata Appendable V2 contract.
teststorage . RequireEqual ( t , [ ] sample {
{ L : labels . FromStrings ( "__name__" , "test_metric_total" ) , M : metadata . Metadata { Type : "counter" , Unit : "metric" , Help : "some help text" } } ,
{ L : labels . FromStrings ( "__name__" , "test_metric2" , "foo" , "bar" ) , M : metadata . Metadata { Type : "gauge" , Unit : "" , Help : "other help text" } } ,
} , appTest . ResultMetadata ( ) )
} else {
// Next (the same) scrape should not add new metadata entries.
require . Empty ( t , appTest . ResultMetadata ( ) )
}
2025-12-22 04:38:48 -05:00
appTest . ResultReset ( )
2025-01-15 06:33:42 -05:00
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
_ , _ , _ , err = app . append ( [ ] byte ( scrape2 ) , "application/openmetrics-text" , now . Add ( 15 * time . Second ) )
2025-01-15 06:33:42 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , [ ] sample {
2025-12-22 04:38:48 -05:00
{ L : labels . FromStrings ( "__name__" , "test_metric_total" ) , M : metadata . Metadata { Type : "counter" , Unit : "metric" , Help : "different help text" } } , // Here, technically we should have no unit, but it's a known limitation of the current implementation.
{ L : labels . FromStrings ( "__name__" , "test_metric2" , "foo" , "bar" ) , M : metadata . Metadata { Type : "gauge" , Unit : "metric2" , Help : "other help text" } } ,
} , appTest . ResultMetadata ( ) )
appTest . ResultReset ( )
2025-01-15 06:33:42 -05:00
}
2025-12-22 04:38:48 -05:00
func TestScrapeReportMetadata ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeReportMetadata ( t , appV2 )
} )
}
func testScrapeReportMetadata ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
2025-01-16 14:47:32 -05:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
require . NoError ( t , sl . report ( app , now , 2 * time . Second , 1 , 1 , 1 , 512 , nil ) )
require . NoError ( t , app . Commit ( ) )
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , [ ] sample {
2025-12-22 04:38:48 -05:00
{ L : labels . FromStrings ( "__name__" , "up" ) , M : scrapeHealthMetric . Metadata } ,
{ L : labels . FromStrings ( "__name__" , "scrape_duration_seconds" ) , M : scrapeDurationMetric . Metadata } ,
{ L : labels . FromStrings ( "__name__" , "scrape_samples_scraped" ) , M : scrapeSamplesMetric . Metadata } ,
{ L : labels . FromStrings ( "__name__" , "scrape_samples_post_metric_relabeling" ) , M : samplesPostRelabelMetric . Metadata } ,
{ L : labels . FromStrings ( "__name__" , "scrape_series_added" ) , M : scrapeSeriesAddedMetric . Metadata } ,
} , appTest . ResultMetadata ( ) )
2025-01-16 14:47:32 -05:00
}
2025-01-15 06:33:42 -05:00
func TestIsSeriesPartOfFamily ( t * testing . T ) {
t . Run ( "counter" , func ( t * testing . T ) {
require . True ( t , isSeriesPartOfFamily ( "http_requests_total" , [ ] byte ( "http_requests_total" ) , model . MetricTypeCounter ) ) // Prometheus text style.
require . True ( t , isSeriesPartOfFamily ( "http_requests_total" , [ ] byte ( "http_requests" ) , model . MetricTypeCounter ) ) // OM text style.
require . True ( t , isSeriesPartOfFamily ( "http_requests_total" , [ ] byte ( "http_requests_total" ) , model . MetricTypeUnknown ) )
require . False ( t , isSeriesPartOfFamily ( "http_requests_total" , [ ] byte ( "http_requests" ) , model . MetricTypeUnknown ) ) // We don't know.
require . False ( t , isSeriesPartOfFamily ( "http_requests2_total" , [ ] byte ( "http_requests_total" ) , model . MetricTypeCounter ) )
require . False ( t , isSeriesPartOfFamily ( "http_requests_requests_total" , [ ] byte ( "http_requests" ) , model . MetricTypeCounter ) )
} )
t . Run ( "gauge" , func ( t * testing . T ) {
require . True ( t , isSeriesPartOfFamily ( "http_requests_count" , [ ] byte ( "http_requests_count" ) , model . MetricTypeGauge ) )
require . True ( t , isSeriesPartOfFamily ( "http_requests_count" , [ ] byte ( "http_requests_count" ) , model . MetricTypeUnknown ) )
require . False ( t , isSeriesPartOfFamily ( "http_requests_count2" , [ ] byte ( "http_requests_count" ) , model . MetricTypeCounter ) )
} )
t . Run ( "histogram" , func ( t * testing . T ) {
require . True ( t , isSeriesPartOfFamily ( "http_requests_seconds_sum" , [ ] byte ( "http_requests_seconds" ) , model . MetricTypeHistogram ) )
require . True ( t , isSeriesPartOfFamily ( "http_requests_seconds_count" , [ ] byte ( "http_requests_seconds" ) , model . MetricTypeHistogram ) )
require . True ( t , isSeriesPartOfFamily ( "http_requests_seconds_bucket" , [ ] byte ( "http_requests_seconds" ) , model . MetricTypeHistogram ) )
require . True ( t , isSeriesPartOfFamily ( "http_requests_seconds" , [ ] byte ( "http_requests_seconds" ) , model . MetricTypeHistogram ) )
require . False ( t , isSeriesPartOfFamily ( "http_requests_seconds_sum" , [ ] byte ( "http_requests_seconds" ) , model . MetricTypeUnknown ) ) // We don't know.
require . False ( t , isSeriesPartOfFamily ( "http_requests_seconds2_sum" , [ ] byte ( "http_requests_seconds" ) , model . MetricTypeHistogram ) )
} )
t . Run ( "summary" , func ( t * testing . T ) {
require . True ( t , isSeriesPartOfFamily ( "http_requests_seconds_sum" , [ ] byte ( "http_requests_seconds" ) , model . MetricTypeSummary ) )
require . True ( t , isSeriesPartOfFamily ( "http_requests_seconds_count" , [ ] byte ( "http_requests_seconds" ) , model . MetricTypeSummary ) )
require . True ( t , isSeriesPartOfFamily ( "http_requests_seconds" , [ ] byte ( "http_requests_seconds" ) , model . MetricTypeSummary ) )
require . False ( t , isSeriesPartOfFamily ( "http_requests_seconds_sum" , [ ] byte ( "http_requests_seconds" ) , model . MetricTypeUnknown ) ) // We don't know.
require . False ( t , isSeriesPartOfFamily ( "http_requests_seconds2_sum" , [ ] byte ( "http_requests_seconds" ) , model . MetricTypeSummary ) )
} )
t . Run ( "info" , func ( t * testing . T ) {
require . True ( t , isSeriesPartOfFamily ( "go_build_info" , [ ] byte ( "go_build_info" ) , model . MetricTypeInfo ) ) // Prometheus text style.
require . True ( t , isSeriesPartOfFamily ( "go_build_info" , [ ] byte ( "go_build" ) , model . MetricTypeInfo ) ) // OM text style.
require . True ( t , isSeriesPartOfFamily ( "go_build_info" , [ ] byte ( "go_build_info" ) , model . MetricTypeUnknown ) )
require . False ( t , isSeriesPartOfFamily ( "go_build_info" , [ ] byte ( "go_build" ) , model . MetricTypeUnknown ) ) // We don't know.
require . False ( t , isSeriesPartOfFamily ( "go_build2_info" , [ ] byte ( "go_build_info" ) , model . MetricTypeInfo ) )
require . False ( t , isSeriesPartOfFamily ( "go_build_build_info" , [ ] byte ( "go_build_info" ) , model . MetricTypeInfo ) )
} )
}
2018-01-04 09:13:31 -05:00
func TestDroppedTargetsList ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testDroppedTargetsList ( t , appV2 )
} )
}
func testDroppedTargetsList ( t * testing . T , appV2 bool ) {
2018-01-04 09:13:31 -05:00
var (
2025-12-22 04:38:48 -05:00
app = teststorage . NewAppendable ( )
2018-01-04 09:13:31 -05:00
cfg = & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "dropMe" ,
ScrapeInterval : model . Duration ( 1 ) ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2018-12-18 06:26:36 -05:00
RelabelConfigs : [ ] * relabel . Config {
2018-01-04 09:13:31 -05:00
{
2025-08-18 04:09:00 -04:00
Action : relabel . Drop ,
Regex : relabel . MustNewRegexp ( "dropMe" ) ,
SourceLabels : model . LabelNames { "job" } ,
NameValidationScheme : model . UTF8Validation ,
2018-01-04 09:13:31 -05:00
} ,
} ,
}
tgs = [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet {
2019-01-16 17:28:08 -05:00
{ model . AddressLabel : "127.0.0.1:9090" } ,
2023-08-20 09:25:32 -04:00
{ model . AddressLabel : "127.0.0.1:9091" } ,
2018-01-04 09:13:31 -05:00
} ,
} ,
}
2026-01-21 03:21:56 -05:00
sa = selectAppendable ( app , appV2 )
sp , _ = newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2021-08-31 11:37:32 -04:00
expectedLabelSetString = "{__address__=\"127.0.0.1:9090\", __scrape_interval__=\"0s\", __scrape_timeout__=\"0s\", job=\"dropMe\"}"
2023-08-20 09:25:32 -04:00
expectedLength = 2
2018-01-04 09:13:31 -05:00
)
sp . Sync ( tgs )
sp . Sync ( tgs )
2023-12-07 06:35:01 -05:00
require . Len ( t , sp . droppedTargets , expectedLength )
2023-08-20 09:25:32 -04:00
require . Equal ( t , expectedLength , sp . droppedTargetsCount )
2024-12-21 08:33:08 -05:00
lb := labels . NewBuilder ( labels . EmptyLabels ( ) )
require . Equal ( t , expectedLabelSetString , sp . droppedTargets [ 0 ] . DiscoveredLabels ( lb ) . String ( ) )
2023-08-20 09:25:32 -04:00
// Check that count is still correct when we don't retain all dropped targets.
sp . config . KeepDroppedTargets = 1
sp . Sync ( tgs )
2023-12-07 06:35:01 -05:00
require . Len ( t , sp . droppedTargets , 1 )
2023-08-20 09:25:32 -04:00
require . Equal ( t , expectedLength , sp . droppedTargetsCount )
2018-01-04 09:13:31 -05:00
}
2018-02-07 05:29:27 -05:00
// TestDiscoveredLabelsUpdate checks that DiscoveredLabels are updated
// even when new labels don't affect the target `hash`.
func TestDiscoveredLabelsUpdate ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
sp := newTestScrapePool ( t , nil , false , nil )
2023-09-22 12:47:44 -04:00
2018-02-07 05:29:27 -05:00
// These are used when syncing so need this to avoid a panic.
sp . config = & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
ScrapeInterval : model . Duration ( 1 ) ,
ScrapeTimeout : model . Duration ( 1 ) ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2018-02-07 05:29:27 -05:00
}
2018-09-26 05:20:56 -04:00
sp . activeTargets = make ( map [ uint64 ] * Target )
2018-02-07 05:29:27 -05:00
t1 := & Target {
2024-12-21 08:33:08 -05:00
tLabels : model . LabelSet { "label" : "name" } ,
scrapeConfig : sp . config ,
2018-02-07 05:29:27 -05:00
}
2018-09-26 05:20:56 -04:00
sp . activeTargets [ t1 . hash ( ) ] = t1
2018-02-07 05:29:27 -05:00
t2 := & Target {
2024-12-21 08:33:08 -05:00
tLabels : model . LabelSet { "labelNew" : "nameNew" } ,
scrapeConfig : sp . config ,
2018-02-07 05:29:27 -05:00
}
sp . sync ( [ ] * Target { t2 } )
2024-12-21 08:33:08 -05:00
lb := labels . NewBuilder ( labels . EmptyLabels ( ) )
require . Equal ( t , t2 . DiscoveredLabels ( lb ) , sp . activeTargets [ t1 . hash ( ) ] . DiscoveredLabels ( lb ) )
2018-02-07 05:29:27 -05:00
}
2016-02-28 03:51:02 -05:00
type testLoop struct {
2020-07-30 08:20:24 -04:00
startFunc func ( interval , timeout time . Duration , errc chan <- error )
stopFunc func ( )
forcedErr error
forcedErrMtx sync . Mutex
2020-09-30 14:21:32 -04:00
runOnce bool
2021-08-31 11:37:32 -04:00
interval time . Duration
timeout time . Duration
2016-02-28 03:51:02 -05:00
}
2025-08-04 02:20:57 -04:00
func ( * testLoop ) setScrapeFailureLogger ( FailureLogger ) {
2024-08-26 05:41:56 -04:00
}
2021-08-31 11:37:32 -04:00
func ( l * testLoop ) run ( errc chan <- error ) {
2020-09-30 14:21:32 -04:00
if l . runOnce {
panic ( "loop must be started only once" )
}
l . runOnce = true
2021-08-31 11:37:32 -04:00
l . startFunc ( l . interval , l . timeout , errc )
2016-02-28 03:51:02 -05:00
}
2025-08-04 02:20:57 -04:00
func ( * testLoop ) disableEndOfRunStalenessMarkers ( ) {
2020-03-20 12:43:26 -04:00
}
2020-07-30 08:20:24 -04:00
func ( l * testLoop ) setForcedError ( err error ) {
l . forcedErrMtx . Lock ( )
defer l . forcedErrMtx . Unlock ( )
l . forcedErr = err
}
func ( l * testLoop ) getForcedError ( ) error {
l . forcedErrMtx . Lock ( )
defer l . forcedErrMtx . Unlock ( )
return l . forcedErr
}
2016-02-28 03:51:02 -05:00
func ( l * testLoop ) stop ( ) {
l . stopFunc ( )
}
2025-08-04 02:20:57 -04:00
func ( * testLoop ) getCache ( ) * scrapeCache {
2020-01-22 07:13:47 -05:00
return nil
}
2016-02-28 03:51:02 -05:00
func TestScrapePoolStop ( t * testing . T ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2026-01-21 03:21:56 -05:00
sp := newTestScrapePool ( t , nil , false , nil )
2025-12-22 04:38:48 -05:00
2016-02-28 03:51:02 -05:00
var mtx sync . Mutex
2016-02-28 13:56:18 -05:00
stopped := map [ uint64 ] bool { }
2016-02-28 03:51:02 -05:00
numTargets := 20
// Stopping the scrape pool must call stop() on all scrape loops,
// clean them and the respective targets up. It must wait until each loop's
// stop function returned before returning itself.
2025-08-27 08:38:54 -04:00
for i := range numTargets {
2016-02-28 03:51:02 -05:00
t := & Target {
2024-12-21 08:33:08 -05:00
labels : labels . FromStrings ( model . AddressLabel , fmt . Sprintf ( "example.com:%d" , i ) ) ,
scrapeConfig : & config . ScrapeConfig { } ,
2016-02-28 03:51:02 -05:00
}
l := & testLoop { }
2023-04-26 10:26:58 -04:00
d := time . Duration ( ( i + 1 ) * 20 ) * time . Millisecond
2016-02-28 03:51:02 -05:00
l . stopFunc = func ( ) {
2023-04-26 10:26:58 -04:00
time . Sleep ( d )
2016-02-28 03:51:02 -05:00
mtx . Lock ( )
2016-02-28 13:56:18 -05:00
stopped [ t . hash ( ) ] = true
2016-02-28 03:51:02 -05:00
mtx . Unlock ( )
}
2018-09-26 05:20:56 -04:00
sp . activeTargets [ t . hash ( ) ] = t
2016-02-28 13:56:18 -05:00
sp . loops [ t . hash ( ) ] = l
2016-02-28 03:51:02 -05:00
}
done := make ( chan struct { } )
stopTime := time . Now ( )
go func ( ) {
sp . stop ( )
close ( done )
} ( )
select {
case <- time . After ( 5 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . Fail ( t , "scrapeLoop.stop() did not return as expected" )
2016-02-28 03:51:02 -05:00
case <- done :
// This should have taken at least as long as the last target slept.
2021-09-04 08:35:03 -04:00
require . GreaterOrEqual ( t , time . Since ( stopTime ) , time . Duration ( numTargets * 20 ) * time . Millisecond , "scrapeLoop.stop() exited before all targets stopped" )
2016-02-28 03:51:02 -05:00
}
mtx . Lock ( )
2023-12-07 06:35:01 -05:00
require . Len ( t , stopped , numTargets , "Unexpected number of stopped loops" )
2016-02-28 03:51:02 -05:00
mtx . Unlock ( )
2023-12-07 06:35:01 -05:00
require . Empty ( t , sp . activeTargets , "Targets were not cleared on stopping: %d left" , len ( sp . activeTargets ) )
require . Empty ( t , sp . loops , "Loops were not cleared on stopping: %d left" , len ( sp . loops ) )
2016-02-28 03:51:02 -05:00
}
2025-12-22 04:38:48 -05:00
// TestScrapePoolReload tests reloading logic, so:
// * all loops are reloaded, reusing cache if scrape config changed.
// * reloaded loops are stopped before new ones are started.
// * new scrapeLoops are configured with the updated scrape config.
2016-02-28 03:51:02 -05:00
func TestScrapePoolReload ( t * testing . T ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2016-02-28 03:51:02 -05:00
2025-12-22 04:38:48 -05:00
var (
mtx sync . Mutex
numTargets = 20
stopped = map [ uint64 ] bool { }
)
2016-02-28 03:51:02 -05:00
2025-12-22 04:38:48 -05:00
cfg0 := & config . ScrapeConfig { }
cfg1 := & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
ScrapeInterval : model . Duration ( 3 * time . Second ) ,
ScrapeTimeout : model . Duration ( 2 * time . Second ) ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2025-12-22 04:38:48 -05:00
// Test a few example options.
SampleLimit : 123 ,
ScrapeFallbackProtocol : "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited" ,
2016-02-28 03:51:02 -05:00
}
2025-12-22 04:38:48 -05:00
newLoopCfg1 := func ( opts scrapeLoopOptions ) loop {
// Test cfg1 is being used.
require . Equal ( t , cfg1 , opts . sp . config )
// Inject out testLoop that allows mocking start and stop.
l := & testLoop { interval : opts . interval , timeout : opts . timeout }
// On start, expect previous loop instances for the same target to be stopped.
2025-02-10 02:06:58 -05:00
l . startFunc = func ( interval , timeout time . Duration , _ chan <- error ) {
2025-12-22 04:38:48 -05:00
// Ensure cfg1 interval and timeout are correctly configured.
require . Equal ( t , time . Duration ( cfg1 . ScrapeInterval ) , interval , "Unexpected scrape interval" )
require . Equal ( t , time . Duration ( cfg1 . ScrapeTimeout ) , timeout , "Unexpected scrape timeout" )
2019-11-04 18:43:42 -05:00
2016-02-28 03:51:02 -05:00
mtx . Lock ( )
2019-11-04 18:43:42 -05:00
targetScraper := opts . scraper . ( * targetScraper )
2020-10-29 05:43:23 -04:00
require . True ( t , stopped [ targetScraper . hash ( ) ] , "Scrape loop for %v not stopped yet" , targetScraper )
2016-02-28 03:51:02 -05:00
mtx . Unlock ( )
}
return l
}
2023-09-22 12:47:44 -04:00
2025-12-22 04:38:48 -05:00
// Create test pool.
2024-04-10 05:38:15 -04:00
reg , metrics := newTestRegistryAndScrapeMetrics ( t )
2026-01-21 03:21:56 -05:00
sp := newTestScrapePool ( t , nil , false , newLoopCfg1 )
2025-12-22 04:38:48 -05:00
sp . metrics = metrics
2016-02-28 03:51:02 -05:00
2025-12-22 04:38:48 -05:00
// Prefill pool with 20 loops, simulating 20 scrape targets.
2025-08-27 08:38:54 -04:00
for i := range numTargets {
2016-02-28 03:51:02 -05:00
t := & Target {
2025-12-22 04:38:48 -05:00
labels : labels . FromStrings ( model . AddressLabel , fmt . Sprintf ( "example.com:%d" , i ) ) ,
scrapeConfig : cfg0 ,
2016-02-28 03:51:02 -05:00
}
l := & testLoop { }
2023-04-26 10:26:58 -04:00
d := time . Duration ( ( i + 1 ) * 20 ) * time . Millisecond
2016-02-28 03:51:02 -05:00
l . stopFunc = func ( ) {
2025-12-22 04:38:48 -05:00
time . Sleep ( d ) // Sleep uneven time on stop.
2016-02-28 03:51:02 -05:00
mtx . Lock ( )
2016-02-28 13:56:18 -05:00
stopped [ t . hash ( ) ] = true
2016-02-28 03:51:02 -05:00
mtx . Unlock ( )
}
2018-09-26 05:20:56 -04:00
sp . activeTargets [ t . hash ( ) ] = t
2016-02-28 13:56:18 -05:00
sp . loops [ t . hash ( ) ] = l
2016-02-28 03:51:02 -05:00
}
2016-02-28 13:56:18 -05:00
beforeTargets := map [ uint64 ] * Target { }
2025-08-27 08:38:54 -04:00
maps . Copy ( beforeTargets , sp . activeTargets )
2016-02-28 03:51:02 -05:00
2025-12-22 04:38:48 -05:00
// Reloading a scrape pool with a new scrape configuration must stop all scrape
// loops and start new ones. A new loop must not be started before the preceding
// one terminated.
require . NoError ( t , sp . reload ( cfg1 ) )
var stoppedCount int
2016-02-28 03:51:02 -05:00
mtx . Lock ( )
2025-12-22 04:38:48 -05:00
stoppedCount = len ( stopped )
2016-02-28 03:51:02 -05:00
mtx . Unlock ( )
2025-12-22 04:38:48 -05:00
require . Equal ( t , numTargets , stoppedCount , "Unexpected number of stopped loops" )
2020-10-29 05:43:23 -04:00
require . Equal ( t , sp . activeTargets , beforeTargets , "Reloading affected target states unexpectedly" )
2025-12-22 04:38:48 -05:00
require . Len ( t , sp . loops , numTargets , "Unexpected number of loops after reload" )
2024-04-10 05:38:15 -04:00
2025-12-22 04:38:48 -05:00
// Check if prometheus_target_reload_length_seconds points to cfg1.ScrapeInterval.
2024-04-10 05:38:15 -04:00
got , err := gatherLabels ( reg , "prometheus_target_reload_length_seconds" )
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
expectedName , expectedValue := "interval" , cfg1 . ScrapeInterval . String ( )
2024-04-10 05:38:15 -04:00
require . Equal ( t , [ ] [ ] * dto . LabelPair { { { Name : & expectedName , Value : & expectedValue } } } , got )
require . Equal ( t , 1.0 , prom_testutil . ToFloat64 ( sp . metrics . targetScrapePoolReloads ) )
2016-02-28 03:51:02 -05:00
}
2022-06-28 05:58:52 -04:00
func TestScrapePoolReloadPreserveRelabeledIntervalTimeout ( t * testing . T ) {
reloadCfg := & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
ScrapeInterval : model . Duration ( 3 * time . Second ) ,
ScrapeTimeout : model . Duration ( 2 * time . Second ) ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2022-06-28 05:58:52 -04:00
}
newLoop := func ( opts scrapeLoopOptions ) loop {
2023-04-09 03:08:40 -04:00
l := & testLoop { interval : opts . interval , timeout : opts . timeout }
2025-02-10 02:06:58 -05:00
l . startFunc = func ( interval , timeout time . Duration , _ chan <- error ) {
2022-06-28 05:58:52 -04:00
require . Equal ( t , 5 * time . Second , interval , "Unexpected scrape interval" )
require . Equal ( t , 3 * time . Second , timeout , "Unexpected scrape timeout" )
}
return l
}
2024-04-10 05:38:15 -04:00
reg , metrics := newTestRegistryAndScrapeMetrics ( t )
2026-01-21 03:21:56 -05:00
sp := newTestScrapePool ( t , nil , false , newLoop )
2025-12-22 04:38:48 -05:00
sp . activeTargets [ 1 ] = & Target {
labels : labels . FromStrings ( model . ScrapeIntervalLabel , "5s" , model . ScrapeTimeoutLabel , "3s" ) ,
2022-06-28 05:58:52 -04:00
}
2025-12-22 04:38:48 -05:00
sp . metrics = metrics
sp . loops [ 1 ] = noopLoop ( )
2022-06-28 05:58:52 -04:00
err := sp . reload ( reloadCfg )
if err != nil {
t . Fatalf ( "unable to reload configuration: %s" , err )
}
2024-04-10 05:38:15 -04:00
// Check that the reload metric is labeled with the pool interval, not the overridden interval.
got , err := gatherLabels ( reg , "prometheus_target_reload_length_seconds" )
require . NoError ( t , err )
expectedName , expectedValue := "interval" , "3s"
require . Equal ( t , [ ] [ ] * dto . LabelPair { { { Name : & expectedName , Value : & expectedValue } } } , got )
}
// Gather metrics from the provided Gatherer with specified familyName,
// and return all sets of name/value pairs.
func gatherLabels ( g prometheus . Gatherer , familyName string ) ( [ ] [ ] * dto . LabelPair , error ) {
families , err := g . Gather ( )
if err != nil {
return nil , err
}
ret := make ( [ ] [ ] * dto . LabelPair , 0 )
for _ , f := range families {
if f . GetName ( ) == familyName {
for _ , m := range f . GetMetric ( ) {
ret = append ( ret , m . GetLabel ( ) )
}
break
}
}
return ret , nil
2022-06-28 05:58:52 -04:00
}
2020-07-30 08:20:24 -04:00
func TestScrapePoolTargetLimit ( t * testing . T ) {
2020-09-30 14:21:32 -04:00
var wg sync . WaitGroup
2020-07-30 08:20:24 -04:00
// On starting to run, new loops created on reload check whether their preceding
// equivalents have been stopped.
2025-08-04 02:20:57 -04:00
newLoop := func ( scrapeLoopOptions ) loop {
2020-09-30 14:21:32 -04:00
wg . Add ( 1 )
2020-07-30 08:20:24 -04:00
l := & testLoop {
2025-02-10 02:06:58 -05:00
startFunc : func ( _ , _ time . Duration , _ chan <- error ) {
2020-09-30 14:21:32 -04:00
wg . Done ( )
} ,
stopFunc : func ( ) { } ,
2020-07-30 08:20:24 -04:00
}
return l
}
2026-01-21 03:21:56 -05:00
sp := newTestScrapePool ( t , nil , false , newLoop )
2025-12-22 04:38:48 -05:00
var tgs [ ] * targetgroup . Group
2025-08-27 08:38:54 -04:00
for i := range 50 {
2020-07-30 08:20:24 -04:00
tgs = append ( tgs ,
& targetgroup . Group {
Targets : [ ] model . LabelSet {
{ model . AddressLabel : model . LabelValue ( fmt . Sprintf ( "127.0.0.1:%d" , 9090 + i ) ) } ,
} ,
} ,
)
}
var limit uint
reloadWithLimit := func ( l uint ) {
limit = l
2020-10-29 05:43:23 -04:00
require . NoError ( t , sp . reload ( & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
ScrapeInterval : model . Duration ( 3 * time . Second ) ,
ScrapeTimeout : model . Duration ( 2 * time . Second ) ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
TargetLimit : l ,
2020-07-30 08:20:24 -04:00
} ) )
}
var targets int
loadTargets := func ( n int ) {
targets = n
sp . Sync ( tgs [ : n ] )
}
2020-09-30 14:21:32 -04:00
validateIsRunning := func ( ) {
wg . Wait ( )
for _ , l := range sp . loops {
2020-10-29 05:43:23 -04:00
require . True ( t , l . ( * testLoop ) . runOnce , "loop should be running" )
2020-09-30 14:21:32 -04:00
}
}
2020-07-30 08:20:24 -04:00
validateErrorMessage := func ( shouldErr bool ) {
for _ , l := range sp . loops {
lerr := l . ( * testLoop ) . getForcedError ( )
if shouldErr {
2023-12-07 06:35:01 -05:00
require . Error ( t , lerr , "error was expected for %d targets with a limit of %d" , targets , limit )
2024-10-06 12:35:29 -04:00
require . EqualError ( t , lerr , fmt . Sprintf ( "target_limit exceeded (number of targets: %d, limit: %d)" , targets , limit ) )
2020-07-30 08:20:24 -04:00
} else {
2023-12-07 06:35:01 -05:00
require . NoError ( t , lerr )
2020-07-30 08:20:24 -04:00
}
}
}
reloadWithLimit ( 0 )
loadTargets ( 50 )
2020-09-30 14:21:32 -04:00
validateIsRunning ( )
2020-07-30 08:20:24 -04:00
// Simulate an initial config with a limit.
sp . config . TargetLimit = 30
limit = 30
loadTargets ( 50 )
2020-09-30 14:21:32 -04:00
validateIsRunning ( )
2020-07-30 08:20:24 -04:00
validateErrorMessage ( true )
reloadWithLimit ( 50 )
2020-09-30 14:21:32 -04:00
validateIsRunning ( )
2020-07-30 08:20:24 -04:00
validateErrorMessage ( false )
reloadWithLimit ( 40 )
2020-09-30 14:21:32 -04:00
validateIsRunning ( )
2020-07-30 08:20:24 -04:00
validateErrorMessage ( true )
loadTargets ( 30 )
2020-09-30 14:21:32 -04:00
validateIsRunning ( )
2020-07-30 08:20:24 -04:00
validateErrorMessage ( false )
loadTargets ( 40 )
2020-09-30 14:21:32 -04:00
validateIsRunning ( )
2020-07-30 08:20:24 -04:00
validateErrorMessage ( false )
loadTargets ( 41 )
2020-09-30 14:21:32 -04:00
validateIsRunning ( )
2020-07-30 08:20:24 -04:00
validateErrorMessage ( true )
2021-07-27 06:48:55 -04:00
reloadWithLimit ( 0 )
validateIsRunning ( )
validateErrorMessage ( false )
2020-07-30 08:20:24 -04:00
reloadWithLimit ( 51 )
2020-09-30 14:21:32 -04:00
validateIsRunning ( )
validateErrorMessage ( false )
tgs = append ( tgs ,
& targetgroup . Group {
Targets : [ ] model . LabelSet {
2025-12-22 04:38:48 -05:00
{ model . AddressLabel : "127.0.0.1:1090" } ,
2020-09-30 14:21:32 -04:00
} ,
} ,
& targetgroup . Group {
Targets : [ ] model . LabelSet {
2025-12-22 04:38:48 -05:00
{ model . AddressLabel : "127.0.0.1:1090" } ,
2020-09-30 14:21:32 -04:00
} ,
} ,
)
sp . Sync ( tgs )
validateIsRunning ( )
2020-07-30 08:20:24 -04:00
validateErrorMessage ( false )
}
2025-12-22 04:38:48 -05:00
func TestScrapePoolAppenderWithLimits ( t * testing . T ) {
// Create a unique value, to validate the correct chain of appenders.
baseAppender := struct { storage . Appender } { }
appendable := appendableFunc ( func ( context . Context ) storage . Appender { return baseAppender } )
2019-11-04 18:43:42 -05:00
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , func ( sl * scrapeLoop ) {
sl . appendable = appendable
} )
2025-12-22 04:38:48 -05:00
wrapped := appenderWithLimits ( sl . appendable . Appender ( context . Background ( ) ) , 0 , 0 , histogram . ExponentialSchemaMax )
2016-02-23 05:56:09 -05:00
2017-09-08 08:34:45 -04:00
tl , ok := wrapped . ( * timeLimitAppender )
2020-10-29 05:43:23 -04:00
require . True ( t , ok , "Expected timeLimitAppender but got %T" , wrapped )
2019-11-04 18:43:42 -05:00
2025-12-22 04:38:48 -05:00
require . Equal ( t , baseAppender , tl . Appender , "Expected base appender but got %T" , tl . Appender )
2016-02-23 05:56:09 -05:00
2021-12-10 07:03:28 -05:00
sampleLimit := 100
2025-12-22 04:38:48 -05:00
sl , _ = newTestScrapeLoop ( t , func ( sl * scrapeLoop ) {
sl . appendable = appendable
sl . sampleLimit = sampleLimit
2019-03-12 06:26:18 -04:00
} )
2025-12-22 04:38:48 -05:00
wrapped = appenderWithLimits ( sl . appendable . Appender ( context . Background ( ) ) , sampleLimit , 0 , histogram . ExponentialSchemaMax )
2017-09-08 08:34:45 -04:00
2025-12-22 04:38:48 -05:00
la , ok := wrapped . ( * limitAppender )
2020-10-29 05:43:23 -04:00
require . True ( t , ok , "Expected limitAppender but got %T" , wrapped )
2019-11-04 18:43:42 -05:00
2025-12-22 04:38:48 -05:00
tl , ok = la . Appender . ( * timeLimitAppender )
require . True ( t , ok , "Expected timeLimitAppender but got %T" , la . Appender )
2019-11-04 18:43:42 -05:00
2025-12-22 04:38:48 -05:00
require . Equal ( t , baseAppender , tl . Appender , "Expected base appender but got %T" , tl . Appender )
2023-04-21 15:14:19 -04:00
2025-12-22 04:38:48 -05:00
wrapped = appenderWithLimits ( sl . appendable . Appender ( context . Background ( ) ) , sampleLimit , 100 , histogram . ExponentialSchemaMax )
2023-04-21 15:14:19 -04:00
bl , ok := wrapped . ( * bucketLimitAppender )
require . True ( t , ok , "Expected bucketLimitAppender but got %T" , wrapped )
2025-12-22 04:38:48 -05:00
la , ok = bl . Appender . ( * limitAppender )
2023-04-21 15:14:19 -04:00
require . True ( t , ok , "Expected limitAppender but got %T" , bl )
2025-12-22 04:38:48 -05:00
tl , ok = la . Appender . ( * timeLimitAppender )
require . True ( t , ok , "Expected timeLimitAppender but got %T" , la . Appender )
2023-04-21 15:14:19 -04:00
2025-12-22 04:38:48 -05:00
require . Equal ( t , baseAppender , tl . Appender , "Expected base appender but got %T" , tl . Appender )
2024-01-17 10:58:54 -05:00
2025-12-22 04:38:48 -05:00
wrapped = appenderWithLimits ( sl . appendable . Appender ( context . Background ( ) ) , sampleLimit , 100 , 0 )
2024-01-17 10:58:54 -05:00
ml , ok := wrapped . ( * maxSchemaAppender )
require . True ( t , ok , "Expected maxSchemaAppender but got %T" , wrapped )
bl , ok = ml . Appender . ( * bucketLimitAppender )
require . True ( t , ok , "Expected bucketLimitAppender but got %T" , wrapped )
2025-12-22 04:38:48 -05:00
la , ok = bl . Appender . ( * limitAppender )
2024-01-17 10:58:54 -05:00
require . True ( t , ok , "Expected limitAppender but got %T" , bl )
2025-12-22 04:38:48 -05:00
tl , ok = la . Appender . ( * timeLimitAppender )
require . True ( t , ok , "Expected timeLimitAppender but got %T" , la . Appender )
2024-01-17 10:58:54 -05:00
2025-12-22 04:38:48 -05:00
require . Equal ( t , baseAppender , tl . Appender , "Expected base appender but got %T" , tl . Appender )
2016-02-23 05:56:09 -05:00
}
2026-01-21 03:21:56 -05:00
type appendableV2Func func ( ctx context . Context ) storage . AppenderV2
func ( a appendableV2Func ) AppenderV2 ( ctx context . Context ) storage . AppenderV2 { return a ( ctx ) }
func TestScrapePoolAppenderWithLimits_AppendV2 ( t * testing . T ) {
// Create a unique value, to validate the correct chain of appenders.
baseAppender := struct { storage . AppenderV2 } { }
appendable := appendableV2Func ( func ( context . Context ) storage . AppenderV2 { return baseAppender } )
sl , _ := newTestScrapeLoop ( t , func ( sl * scrapeLoop ) {
sl . appendableV2 = appendable
} )
wrapped := appenderV2WithLimits ( sl . appendableV2 . AppenderV2 ( context . Background ( ) ) , 0 , 0 , histogram . ExponentialSchemaMax )
tl , ok := wrapped . ( * timeLimitAppenderV2 )
require . True ( t , ok , "Expected timeLimitAppenderV2 but got %T" , wrapped )
require . Equal ( t , baseAppender , tl . AppenderV2 , "Expected base AppenderV2 but got %T" , tl . AppenderV2 )
sampleLimit := 100
sl , _ = newTestScrapeLoop ( t , func ( sl * scrapeLoop ) {
sl . appendableV2 = appendable
sl . sampleLimit = sampleLimit
} )
wrapped = appenderV2WithLimits ( sl . appendableV2 . AppenderV2 ( context . Background ( ) ) , sampleLimit , 0 , histogram . ExponentialSchemaMax )
la , ok := wrapped . ( * limitAppenderV2 )
require . True ( t , ok , "Expected limitAppenderV2 but got %T" , wrapped )
tl , ok = la . AppenderV2 . ( * timeLimitAppenderV2 )
require . True ( t , ok , "Expected timeLimitAppenderV2 but got %T" , la . AppenderV2 )
require . Equal ( t , baseAppender , tl . AppenderV2 , "Expected base AppenderV2 but got %T" , tl . AppenderV2 )
wrapped = appenderV2WithLimits ( sl . appendableV2 . AppenderV2 ( context . Background ( ) ) , sampleLimit , 100 , histogram . ExponentialSchemaMax )
bl , ok := wrapped . ( * bucketLimitAppenderV2 )
require . True ( t , ok , "Expected bucketLimitAppenderV2 but got %T" , wrapped )
la , ok = bl . AppenderV2 . ( * limitAppenderV2 )
require . True ( t , ok , "Expected limitAppenderV2 but got %T" , bl )
tl , ok = la . AppenderV2 . ( * timeLimitAppenderV2 )
require . True ( t , ok , "Expected timeLimitAppenderV2 but got %T" , la . AppenderV2 )
require . Equal ( t , baseAppender , tl . AppenderV2 , "Expected base AppenderV2 but got %T" , tl . AppenderV2 )
wrapped = appenderV2WithLimits ( sl . appendableV2 . AppenderV2 ( context . Background ( ) ) , sampleLimit , 100 , 0 )
ml , ok := wrapped . ( * maxSchemaAppenderV2 )
require . True ( t , ok , "Expected maxSchemaAppenderV2 but got %T" , wrapped )
bl , ok = ml . AppenderV2 . ( * bucketLimitAppenderV2 )
require . True ( t , ok , "Expected bucketLimitAppenderV2 but got %T" , wrapped )
la , ok = bl . AppenderV2 . ( * limitAppenderV2 )
require . True ( t , ok , "Expected limitAppenderV2 but got %T" , bl )
tl , ok = la . AppenderV2 . ( * timeLimitAppenderV2 )
require . True ( t , ok , "Expected timeLimitAppenderV2 but got %T" , la . AppenderV2 )
require . Equal ( t , baseAppender , tl . AppenderV2 , "Expected base AppenderV2 but got %T" , tl . AppenderV2 )
}
2018-04-13 08:21:41 -04:00
func TestScrapePoolRaces ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapePoolRaces ( t , appV2 )
} )
}
func testScrapePoolRaces ( t * testing . T , appV2 bool ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2021-08-31 11:37:32 -04:00
interval , _ := model . ParseDuration ( "1s" )
timeout , _ := model . ParseDuration ( "500ms" )
2018-04-13 08:21:41 -04:00
newConfig := func ( ) * config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
return & config . ScrapeConfig {
ScrapeInterval : interval ,
ScrapeTimeout : timeout ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
}
2018-04-13 08:21:41 -04:00
}
2026-01-21 03:21:56 -05:00
sa := selectAppendable ( teststorage . NewAppendable ( ) , appV2 )
sp , _ := newScrapePool ( newConfig ( ) , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2018-04-13 08:21:41 -04:00
tgts := [ ] * targetgroup . Group {
2019-01-16 17:28:08 -05:00
{
2018-04-13 08:21:41 -04:00
Targets : [ ] model . LabelSet {
2019-01-16 17:28:08 -05:00
{ model . AddressLabel : "127.0.0.1:9090" } ,
{ model . AddressLabel : "127.0.0.2:9090" } ,
{ model . AddressLabel : "127.0.0.3:9090" } ,
{ model . AddressLabel : "127.0.0.4:9090" } ,
{ model . AddressLabel : "127.0.0.5:9090" } ,
{ model . AddressLabel : "127.0.0.6:9090" } ,
{ model . AddressLabel : "127.0.0.7:9090" } ,
{ model . AddressLabel : "127.0.0.8:9090" } ,
2018-04-13 08:21:41 -04:00
} ,
} ,
}
2018-09-26 05:20:56 -04:00
sp . Sync ( tgts )
active := sp . ActiveTargets ( )
dropped := sp . DroppedTargets ( )
2018-04-13 08:21:41 -04:00
expectedActive , expectedDropped := len ( tgts [ 0 ] . Targets ) , 0
2019-11-04 18:43:42 -05:00
2023-12-07 06:35:01 -05:00
require . Len ( t , active , expectedActive , "Invalid number of active targets" )
require . Len ( t , dropped , expectedDropped , "Invalid number of dropped targets" )
2018-04-13 08:21:41 -04:00
2025-08-27 08:38:54 -04:00
for range 20 {
2023-04-09 03:08:40 -04:00
time . Sleep ( 10 * time . Millisecond )
2025-12-22 04:38:48 -05:00
_ = sp . reload ( newConfig ( ) )
2018-04-13 08:21:41 -04:00
}
sp . stop ( )
}
2020-09-30 14:21:32 -04:00
func TestScrapePoolScrapeLoopsStarted ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapePoolScrapeLoopsStarted ( t , appV2 )
} )
}
func testScrapePoolScrapeLoopsStarted ( t * testing . T , appV2 bool ) {
2020-09-30 14:21:32 -04:00
var wg sync . WaitGroup
2025-08-04 02:20:57 -04:00
newLoop := func ( scrapeLoopOptions ) loop {
2020-09-30 14:21:32 -04:00
wg . Add ( 1 )
l := & testLoop {
2025-02-10 02:06:58 -05:00
startFunc : func ( _ , _ time . Duration , _ chan <- error ) {
2020-09-30 14:21:32 -04:00
wg . Done ( )
} ,
stopFunc : func ( ) { } ,
}
return l
}
2026-01-21 03:21:56 -05:00
sp := newTestScrapePool ( t , teststorage . NewAppendable ( ) , appV2 , newLoop )
2020-09-30 14:21:32 -04:00
tgs := [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet {
{ model . AddressLabel : model . LabelValue ( "127.0.0.1:9090" ) } ,
} ,
} ,
{
Targets : [ ] model . LabelSet {
{ model . AddressLabel : model . LabelValue ( "127.0.0.1:9090" ) } ,
} ,
} ,
}
2020-10-29 05:43:23 -04:00
require . NoError ( t , sp . reload ( & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
ScrapeInterval : model . Duration ( 3 * time . Second ) ,
ScrapeTimeout : model . Duration ( 2 * time . Second ) ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2020-09-30 14:21:32 -04:00
} ) )
sp . Sync ( tgs )
2023-12-07 06:35:01 -05:00
require . Len ( t , sp . loops , 1 )
2020-09-30 14:21:32 -04:00
wg . Wait ( )
for _ , l := range sp . loops {
2020-10-29 05:43:23 -04:00
require . True ( t , l . ( * testLoop ) . runOnce , "loop should be running" )
2020-09-30 14:21:32 -04:00
}
}
2023-10-16 09:47:10 -04:00
func TestScrapeLoopStopBeforeRun ( t * testing . T ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2025-12-22 04:38:48 -05:00
sl , scraper := newTestScrapeLoop ( t )
2016-02-28 03:51:02 -05:00
// The scrape pool synchronizes on stopping scrape loops. However, new scrape
2025-12-22 04:38:48 -05:00
// loops are started asynchronously. Thus, it's possible, that a loop is stopped
2016-02-28 03:51:02 -05:00
// again before having started properly.
// Stopping not-yet-started loops must block until the run method was called and exited.
// The run method must exit immediately.
stopDone := make ( chan struct { } )
go func ( ) {
sl . stop ( )
close ( stopDone )
} ( )
select {
case <- stopDone :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Stopping terminated before run exited successfully." )
2016-02-28 03:51:02 -05:00
case <- time . After ( 500 * time . Millisecond ) :
}
// Running the scrape loop must exit before calling the scraper even once.
2017-01-15 11:33:07 -05:00
scraper . scrapeFunc = func ( context . Context , io . Writer ) error {
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Scraper was called for terminated scrape loop." )
2017-01-15 11:33:07 -05:00
return nil
2016-02-28 03:51:02 -05:00
}
runDone := make ( chan struct { } )
go func ( ) {
2021-08-31 11:37:32 -04:00
sl . run ( nil )
2016-02-28 03:51:02 -05:00
close ( runDone )
} ( )
select {
case <- runDone :
case <- time . After ( 1 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Running terminated scrape loop did not exit." )
2016-02-28 03:51:02 -05:00
}
select {
case <- stopDone :
case <- time . After ( 1 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Stopping did not terminate after running exited." )
2016-02-28 03:51:02 -05:00
}
}
2017-09-08 08:34:45 -04:00
func nopMutator ( l labels . Labels ) labels . Labels { return l }
2017-05-10 11:59:02 -04:00
func TestScrapeLoopStop ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopStop ( t , appV2 )
} )
}
func testScrapeLoopStop ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
signal := make ( chan struct { } , 1 )
2017-05-10 11:59:02 -04:00
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
// Since we're writing samples directly below we need to provide a protocol fallback.
sl . fallbackScrapeProtocol = "text/plain"
} )
2017-09-08 08:34:45 -04:00
// Terminate loop after 2 scrapes.
numScrapes := 0
2017-05-10 11:59:02 -04:00
scraper . scrapeFunc = func ( ctx context . Context , w io . Writer ) error {
2017-05-26 04:44:48 -04:00
numScrapes ++
2017-05-10 11:59:02 -04:00
if numScrapes == 2 {
2017-05-26 04:44:48 -04:00
go sl . stop ( )
2020-08-07 09:58:16 -04:00
<- sl . ctx . Done ( )
2017-05-10 11:59:02 -04:00
}
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "metric_a 42\n" ) )
2020-08-07 09:58:16 -04:00
return ctx . Err ( )
2017-05-10 11:59:02 -04:00
}
go func ( ) {
2021-08-31 11:37:32 -04:00
sl . run ( nil )
2017-05-10 11:59:02 -04:00
signal <- struct { } { }
} ( )
select {
case <- signal :
case <- time . After ( 5 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Scrape wasn't stopped." )
2017-05-10 11:59:02 -04:00
}
2025-12-22 04:38:48 -05:00
got := appTest . ResultSamples ( )
2019-05-08 17:24:00 -04:00
// We expected 1 actual sample for each scrape plus 5 for report samples.
2017-09-08 08:34:45 -04:00
// At least 2 scrapes were made, plus the final stale markers.
2025-12-22 04:38:48 -05:00
require . GreaterOrEqual ( t , len ( got ) , 6 * 3 , "Expected at least 3 scrapes with 6 samples each." )
require . Zero ( t , len ( got ) % 6 , "There is a scrape with missing samples." )
2018-04-08 05:51:54 -04:00
// All samples in a scrape must have the same timestamp.
2017-09-08 08:34:45 -04:00
var ts int64
2025-12-22 04:38:48 -05:00
for i , s := range got {
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 10:14:31 -04:00
switch {
case i % 6 == 0 :
2025-12-22 04:38:48 -05:00
ts = s . T
case s . T != ts :
2017-09-08 08:34:45 -04:00
t . Fatalf ( "Unexpected multiple timestamps within single scrape" )
}
2017-05-11 09:43:43 -04:00
}
2017-09-08 08:34:45 -04:00
// All samples from the last scrape must be stale markers.
2025-12-22 04:38:48 -05:00
for _ , s := range got [ len ( got ) - 5 : ] {
require . True ( t , value . IsStaleNaN ( s . V ) , "Appended last sample not as expected. Wanted: stale NaN Got: %x" , math . Float64bits ( s . V ) )
2017-05-11 09:43:43 -04:00
}
2017-05-10 11:59:02 -04:00
}
2016-02-23 04:58:16 -05:00
func TestScrapeLoopRun ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopRun ( t , appV2 )
} )
}
func testScrapeLoopRun ( t * testing . T , appV2 bool ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2016-02-23 04:58:16 -05:00
var (
2020-02-13 02:53:07 -05:00
signal = make ( chan struct { } , 1 )
2016-02-23 04:58:16 -05:00
errc = make ( chan error )
2017-09-08 08:34:45 -04:00
)
2016-02-23 04:58:16 -05:00
2025-12-22 04:38:48 -05:00
ctx , cancel := context . WithCancel ( t . Context ( ) )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withCtx ( ctx ) , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) )
2016-02-23 04:58:16 -05:00
// The loop must terminate during the initial offset if the context
// is canceled.
scraper . offsetDur = time . Hour
go func ( ) {
2021-08-31 11:37:32 -04:00
sl . run ( errc )
2016-02-23 04:58:16 -05:00
signal <- struct { } { }
} ( )
// Wait to make sure we are actually waiting on the offset.
time . Sleep ( 1 * time . Second )
cancel ( )
select {
case <- signal :
case <- time . After ( 5 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Cancellation during initial offset failed." )
2016-02-23 04:58:16 -05:00
case err := <- errc :
2025-05-03 13:05:13 -04:00
require . FailNow ( t , "Unexpected error" , "err: %s" , err )
2016-02-23 04:58:16 -05:00
}
2025-12-22 04:38:48 -05:00
ctx , cancel = context . WithCancel ( t . Context ( ) )
2026-01-21 03:21:56 -05:00
sl , scraper = newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . ctx = ctx
sl . timeout = 100 * time . Millisecond
} )
2019-10-10 05:47:30 -04:00
// The provided timeout must cause cancellation of the context passed down to the
2016-02-23 04:58:16 -05:00
// scraper. The scraper has to respect the context.
scraper . offsetDur = 0
2025-12-22 04:38:48 -05:00
blockCtx , blockCancel := context . WithCancel ( t . Context ( ) )
2017-01-15 11:33:07 -05:00
scraper . scrapeFunc = func ( ctx context . Context , _ io . Writer ) error {
2016-02-23 04:58:16 -05:00
select {
2025-12-22 04:38:48 -05:00
case <- blockCtx . Done ( ) :
cancel ( )
2016-02-23 04:58:16 -05:00
case <- ctx . Done ( ) :
2017-01-15 11:33:07 -05:00
return ctx . Err ( )
2016-02-23 04:58:16 -05:00
}
2017-01-15 11:33:07 -05:00
return nil
2016-02-23 04:58:16 -05:00
}
go func ( ) {
2021-08-31 11:37:32 -04:00
sl . run ( errc )
2016-02-23 04:58:16 -05:00
signal <- struct { } { }
} ( )
select {
case err := <- errc :
2021-09-04 08:35:03 -04:00
require . ErrorIs ( t , err , context . DeadlineExceeded )
2016-02-23 04:58:16 -05:00
case <- time . After ( 3 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Expected timeout error but got none." )
2016-02-23 04:58:16 -05:00
}
// We already caught the timeout error and are certainly in the loop.
// Let the scrapes returns immediately to cause no further timeout errors
// and check whether canceling the parent context terminates the loop.
2025-12-22 04:38:48 -05:00
blockCancel ( )
2016-02-23 04:58:16 -05:00
select {
case <- signal :
// Loop terminated as expected.
case err := <- errc :
2025-05-03 13:05:13 -04:00
require . FailNow ( t , "Unexpected error" , "err: %s" , err )
2016-02-23 04:58:16 -05:00
case <- time . After ( 3 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Loop did not terminate on context cancellation" )
2016-02-23 04:58:16 -05:00
}
}
2020-07-30 08:20:24 -04:00
func TestScrapeLoopForcedErr ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopForcedErr ( t , appV2 )
} )
}
func testScrapeLoopForcedErr ( t * testing . T , appV2 bool ) {
2020-07-30 08:20:24 -04:00
var (
signal = make ( chan struct { } , 1 )
errc = make ( chan error )
)
2025-12-22 04:38:48 -05:00
ctx , cancel := context . WithCancel ( t . Context ( ) )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withCtx ( ctx ) , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) )
2020-07-30 08:20:24 -04:00
2024-11-03 07:15:51 -05:00
forcedErr := errors . New ( "forced err" )
2020-07-30 08:20:24 -04:00
sl . setForcedError ( forcedErr )
scraper . scrapeFunc = func ( context . Context , io . Writer ) error {
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Should not be scraped." )
2020-07-30 08:20:24 -04:00
return nil
}
go func ( ) {
2021-08-31 11:37:32 -04:00
sl . run ( errc )
2020-07-30 08:20:24 -04:00
signal <- struct { } { }
} ( )
select {
case err := <- errc :
2021-09-04 08:35:03 -04:00
require . ErrorIs ( t , err , forcedErr )
2020-07-30 08:20:24 -04:00
case <- time . After ( 3 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Expected forced error but got none." )
2020-07-30 08:20:24 -04:00
}
cancel ( )
select {
case <- signal :
case <- time . After ( 5 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Scrape not stopped." )
2020-07-30 08:20:24 -04:00
}
}
2025-12-12 08:01:57 -05:00
func TestScrapeLoopRun_ContextCancelTerminatesBlockedSend ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopRunContextCancelTerminatesBlockedSend ( t , appV2 )
} )
}
func testScrapeLoopRunContextCancelTerminatesBlockedSend ( t * testing . T , appV2 bool ) {
2025-12-12 08:01:57 -05:00
// Regression test for issue #17553
defer goleak . VerifyNone ( t )
var (
2025-12-22 04:38:48 -05:00
signal = make ( chan struct { } )
errc = make ( chan error )
2025-12-12 08:01:57 -05:00
)
2025-12-22 04:38:48 -05:00
ctx , cancel := context . WithCancel ( t . Context ( ) )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withCtx ( ctx ) , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) )
2025-12-12 08:01:57 -05:00
forcedErr := errors . New ( "forced err" )
sl . setForcedError ( forcedErr )
scraper . scrapeFunc = func ( context . Context , io . Writer ) error {
return nil
}
go func ( ) {
sl . run ( errc )
close ( signal )
} ( )
time . Sleep ( 50 * time . Millisecond )
cancel ( )
select {
case <- signal :
// success case
case <- time . After ( 3 * time . Second ) :
require . FailNow ( t , "Scrape loop failed to exit on context cancellation (goroutine leak detected)" )
}
}
2018-05-18 03:32:11 -04:00
func TestScrapeLoopMetadata ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopMetadata ( t , appV2 )
} )
}
func testScrapeLoopMetadata ( t * testing . T , appV2 bool ) {
sl , _ := newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) )
2018-05-18 03:32:11 -04:00
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
total , _ , _ , err := app . append ( [ ] byte ( ` # TYPE test_metric counter
2018-05-18 03:32:11 -04:00
# HELP test_metric some help text
2018-10-05 12:11:16 -04:00
# UNIT test_metric metric
2025-01-15 06:33:42 -05:00
test_metric_total 1
2018-05-18 03:32:11 -04:00
# TYPE test_metric_no_help gauge
2018-10-05 12:11:16 -04:00
# HELP test_metric_no_type other help text
# EOF ` ) , "application/openmetrics-text" , time . Now ( ) )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2020-10-29 05:43:23 -04:00
require . Equal ( t , 1 , total )
2018-05-18 03:32:11 -04:00
2025-12-22 04:38:48 -05:00
md , ok := sl . cache . GetMetadata ( "test_metric" )
2020-10-29 05:43:23 -04:00
require . True ( t , ok , "expected metadata to be present" )
2023-11-22 09:39:21 -05:00
require . Equal ( t , model . MetricTypeCounter , md . Type , "unexpected metric type" )
2020-10-29 05:43:23 -04:00
require . Equal ( t , "some help text" , md . Help )
require . Equal ( t , "metric" , md . Unit )
2018-05-18 03:32:11 -04:00
2025-12-22 04:38:48 -05:00
md , ok = sl . cache . GetMetadata ( "test_metric_no_help" )
2020-10-29 05:43:23 -04:00
require . True ( t , ok , "expected metadata to be present" )
2023-11-22 09:39:21 -05:00
require . Equal ( t , model . MetricTypeGauge , md . Type , "unexpected metric type" )
2025-05-03 13:05:13 -04:00
require . Empty ( t , md . Help )
require . Empty ( t , md . Unit )
2018-05-18 03:32:11 -04:00
2025-12-22 04:38:48 -05:00
md , ok = sl . cache . GetMetadata ( "test_metric_no_type" )
2020-10-29 05:43:23 -04:00
require . True ( t , ok , "expected metadata to be present" )
2023-11-22 09:39:21 -05:00
require . Equal ( t , model . MetricTypeUnknown , md . Type , "unexpected metric type" )
2020-10-29 05:43:23 -04:00
require . Equal ( t , "other help text" , md . Help )
2025-05-03 13:05:13 -04:00
require . Empty ( t , md . Unit )
2018-05-18 03:32:11 -04:00
}
2021-09-08 04:09:21 -04:00
func TestScrapeLoopSeriesAdded ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopSeriesAdded ( t , appV2 )
} )
}
func testScrapeLoopSeriesAdded ( t * testing . T , appV2 bool ) {
sl , _ := newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) )
2019-05-08 17:24:00 -04:00
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
total , added , seriesAdded , err := app . append ( [ ] byte ( "test_metric 1\n" ) , "text/plain" , time . Time { } )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2020-10-29 05:43:23 -04:00
require . Equal ( t , 1 , total )
require . Equal ( t , 1 , added )
require . Equal ( t , 1 , seriesAdded )
2019-05-08 17:24:00 -04:00
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
total , added , seriesAdded , err = app . append ( [ ] byte ( "test_metric 1\n" ) , "text/plain" , time . Time { } )
require . NoError ( t , app . Commit ( ) )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
require . Equal ( t , 1 , total )
require . Equal ( t , 1 , added )
require . Equal ( t , 0 , seriesAdded )
2019-05-08 17:24:00 -04:00
}
2022-12-07 22:09:43 -05:00
func TestScrapeLoopFailWithInvalidLabelsAfterRelabel ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopFailWithInvalidLabelsAfterRelabel ( t , appV2 )
} )
}
func testScrapeLoopFailWithInvalidLabelsAfterRelabel ( t * testing . T , appV2 bool ) {
2022-12-07 22:09:43 -05:00
target := & Target {
2025-02-26 10:55:26 -05:00
labels : labels . FromStrings ( "pod_label_invalid_012\xff" , "test" ) ,
2022-12-07 22:09:43 -05:00
}
relabelConfig := [ ] * relabel . Config { {
2025-08-18 04:09:00 -04:00
Action : relabel . LabelMap ,
Regex : relabel . MustNewRegexp ( "pod_label_invalid_(.+)" ) ,
Separator : ";" ,
Replacement : "$1" ,
NameValidationScheme : model . UTF8Validation ,
2022-12-07 22:09:43 -05:00
} }
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . sampleMutator = func ( l labels . Labels ) labels . Labels {
return mutateSampleLabels ( l , target , true , relabelConfig )
}
} )
2022-12-07 22:09:43 -05:00
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
total , added , seriesAdded , err := app . append ( [ ] byte ( "test_metric 1\n" ) , "text/plain" , time . Time { } )
2022-12-07 22:09:43 -05:00
require . ErrorContains ( t , err , "invalid metric name or label names" )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Rollback ( ) )
2022-12-07 22:09:43 -05:00
require . Equal ( t , 1 , total )
require . Equal ( t , 0 , added )
require . Equal ( t , 0 , seriesAdded )
}
2024-08-28 11:15:42 -04:00
func TestScrapeLoopFailLegacyUnderUTF8 ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopFailLegacyUnderUTF8 ( t , appV2 )
} )
}
func testScrapeLoopFailLegacyUnderUTF8 ( t * testing . T , appV2 bool ) {
sl , _ := newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . validationScheme = model . LegacyValidation
} )
2024-08-28 11:15:42 -04:00
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
total , added , seriesAdded , err := app . append ( [ ] byte ( "{\"test.metric\"} 1\n" ) , "text/plain" , time . Time { } )
2024-08-28 11:15:42 -04:00
require . ErrorContains ( t , err , "invalid metric name or label names" )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Rollback ( ) )
2024-08-28 11:15:42 -04:00
require . Equal ( t , 1 , total )
require . Equal ( t , 0 , added )
require . Equal ( t , 0 , seriesAdded )
// When scrapeloop has validation set to UTF-8, the metric is allowed.
2026-01-21 03:21:56 -05:00
sl , _ = newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . validationScheme = model . UTF8Validation
} )
2024-08-28 11:15:42 -04:00
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
total , added , seriesAdded , err = app . append ( [ ] byte ( "{\"test.metric\"} 1\n" ) , "text/plain" , time . Time { } )
2024-08-28 11:15:42 -04:00
require . NoError ( t , err )
require . Equal ( t , 1 , total )
require . Equal ( t , 1 , added )
require . Equal ( t , 1 , seriesAdded )
}
2025-02-03 09:46:39 -05:00
func readTextParseTestMetrics ( t testing . TB ) [ ] byte {
t . Helper ( )
2025-02-10 07:26:18 -05:00
b , err := os . ReadFile ( "../model/textparse/testdata/alltypes.237mfs.prom.txt" )
2025-02-03 09:46:39 -05:00
if err != nil {
t . Fatal ( err )
}
2026-01-30 06:44:07 -05:00
// Replace all Carriage Return chars that appear when testing on windows.
return bytes . ReplaceAll ( b , [ ] byte { '\r' } , nil )
2025-02-03 09:46:39 -05:00
}
func makeTestGauges ( n int ) [ ] byte {
2021-09-08 04:09:21 -04:00
sb := bytes . Buffer { }
2025-12-22 04:38:48 -05:00
sb . WriteString ( "# TYPE metric_a gauge\n" )
sb . WriteString ( "# HELP metric_a help text\n" )
2025-08-27 08:38:54 -04:00
for i := range n {
2025-12-22 04:38:48 -05:00
_ , _ = fmt . Fprintf ( & sb , "metric_a{foo=\"%d\",bar=\"%d\"} 1\n" , i , i * 100 )
2021-09-08 04:09:21 -04:00
}
2025-12-22 04:38:48 -05:00
sb . WriteString ( "# EOF\n" )
2021-09-08 04:09:21 -04:00
return sb . Bytes ( )
}
2026-01-21 03:21:56 -05:00
func makeTestHistogramsWithExemplars ( n int ) [ ] byte {
sb := bytes . Buffer { }
for i := range n {
sb . WriteString ( strings . ReplaceAll ( ` # HELP rpc_durations_histogram % d_seconds RPC latency distributions .
# TYPE rpc_durations_histogram % d_seconds histogram
rpc_durations_histogram % d_seconds_bucket { le = "-0.00099" } 0
rpc_durations_histogram % d_seconds_bucket { le = "-0.00089" } 1 # { dummyID = "1242" } - 0.00091 1.7268398142239082e+09
rpc_durations_histogram % d_seconds_bucket { le = "-0.0007899999999999999" } 1 # { dummyID = "17783" } - 0.0003825067330956884 1.7268398142239082e+09
rpc_durations_histogram % d_seconds_bucket { le = "-0.0006899999999999999" } 2 # { dummyID = "17783" } - 0.0003825067330956884 1.7268398142239082e+09
rpc_durations_histogram % d_seconds_bucket { le = "-0.0005899999999999998" } 3 # { dummyID = "17783" } - 0.0003825067330956884 1.7268398142239082e+09
rpc_durations_histogram % d_seconds_bucket { le = "-0.0004899999999999998" } 4 # { dummyID = "17783" } - 0.0003825067330956884 1.7268398142239082e+09
rpc_durations_histogram % d_seconds_bucket { le = "-0.0003899999999999998" } 5 # { dummyID = "17783" } - 0.0003825067330956884 1.7268398142239082e+09
rpc_durations_histogram % d_seconds_bucket { le = "-0.0002899999999999998" } 6 # { dummyID = "17783" } - 0.0003825067330956884 1.7268398142239082e+09
rpc_durations_histogram % d_seconds_bucket { le = "-0.0001899999999999998" } 7 # { dummyID = "84741" } - 0.00020178290006788965 1.726839814829977e+09
rpc_durations_histogram % d_seconds_bucket { le = "-8.999999999999979e-05" } 7
rpc_durations_histogram % d_seconds_bucket { le = "1.0000000000000216e-05" } 8 # { dummyID = "19206" } - 4.6156147425468016e-05 1.7268398151337721e+09
rpc_durations_histogram % d_seconds_bucket { le = "0.00011000000000000022" } 9 # { dummyID = "3974" } 9.528436760156754e-05 1.726839814526797e+09
rpc_durations_histogram % d_seconds_bucket { le = "0.00021000000000000023" } 11 # { dummyID = "29640" } 0.00017459624183458996 1.7268398139220061e+09
rpc_durations_histogram % d_seconds_bucket { le = "0.0003100000000000002" } 15 # { dummyID = "9818" } 0.0002791130914009552 1.7268398149821382e+09
rpc_durations_histogram % d_seconds_bucket { le = "0.0004100000000000002" } 15
rpc_durations_histogram % d_seconds_bucket { le = "0.0005100000000000003" } 15
rpc_durations_histogram % d_seconds_bucket { le = "0.0006100000000000003" } 15
rpc_durations_histogram % d_seconds_bucket { le = "0.0007100000000000003" } 15
rpc_durations_histogram % d_seconds_bucket { le = "0.0008100000000000004" } 15
rpc_durations_histogram % d_seconds_bucket { le = "0.0009100000000000004" } 15
rpc_durations_histogram % d_seconds_bucket { le = "+Inf" } 15
rpc_durations_histogram % d_seconds_sum - 8.452185437166741e-05
rpc_durations_histogram % d_seconds_count 15
rpc_durations_histogram % d_seconds_created 1.726839813016302e+09
` , "%d" , strconv . Itoa ( i ) ) )
}
sb . WriteString ( "# EOF\n" )
return sb . Bytes ( )
}
// promTextToProto converts Prometheus text to proto.
// Given expfmt decoding limitations, it does not support OpenMetrics fully (e.g. exemplars).
2024-12-29 10:10:39 -05:00
func promTextToProto ( tb testing . TB , text [ ] byte ) [ ] byte {
tb . Helper ( )
2021-09-08 04:09:21 -04:00
2025-09-03 11:20:04 -04:00
p := expfmt . NewTextParser ( model . UTF8Validation )
2025-02-03 09:46:39 -05:00
fams , err := p . TextToMetricFamilies ( bytes . NewReader ( text ) )
2024-12-29 10:10:39 -05:00
if err != nil {
2026-01-21 03:21:56 -05:00
tb . Fatal ( "TextToMetricFamilies:" , err )
2021-09-08 04:09:21 -04:00
}
2025-02-03 09:46:39 -05:00
// Order by name for the deterministic tests.
var names [ ] string
for n := range fams {
names = append ( names , n )
}
sort . Strings ( names )
2024-12-29 10:10:39 -05:00
buf := bytes . Buffer { }
2025-02-03 09:46:39 -05:00
for _ , n := range names {
o , err := proto . Marshal ( fams [ n ] )
if err != nil {
tb . Fatal ( err )
}
// Write first length, then binary protobuf.
varintBuf := binary . AppendUvarint ( nil , uint64 ( len ( o ) ) )
buf . Write ( varintBuf )
buf . Write ( o )
}
2024-12-29 10:10:39 -05:00
return buf . Bytes ( )
2021-09-08 04:09:21 -04:00
}
2025-02-03 09:46:39 -05:00
func TestPromTextToProto ( t * testing . T ) {
metricsText := readTextParseTestMetrics ( t )
2026-01-21 03:21:56 -05:00
// On windows \r is added when reading, but parsers do not support this. Kill it.
2025-02-03 09:46:39 -05:00
metricsText = bytes . ReplaceAll ( metricsText , [ ] byte ( "\r" ) , nil )
metricsProto := promTextToProto ( t , metricsText )
d := expfmt . NewDecoder ( bytes . NewReader ( metricsProto ) , expfmt . NewFormat ( expfmt . TypeProtoDelim ) )
var got [ ] string
for {
mf := & dto . MetricFamily { }
if err := d . Decode ( mf ) ; err != nil {
if errors . Is ( err , io . EOF ) {
break
}
t . Fatal ( err )
}
got = append ( got , mf . GetName ( ) )
}
2025-02-10 07:26:18 -05:00
require . Len ( t , got , 237 )
2025-02-03 09:46:39 -05:00
// Check a few to see if those are not dups.
2025-02-10 07:26:18 -05:00
require . Equal ( t , "go_gc_cycles_automatic_gc_cycles_total" , got [ 0 ] )
require . Equal ( t , "prometheus_sd_kuma_fetch_duration_seconds" , got [ 128 ] )
require . Equal ( t , "promhttp_metric_handler_requests_total" , got [ 236 ] )
2025-02-03 09:46:39 -05:00
}
2026-01-30 06:44:07 -05:00
// TestScrapeLoopAppend_WithStorage tests appends and storage integration for the
// large input files that are also used in benchmarks.
func TestScrapeLoopAppend_WithStorage ( t * testing . T ) {
ts := time . Now ( )
for _ , appV2 := range [ ] bool { false , true } {
for _ , tc := range [ ] struct {
name string
parsableText [ ] byte
expectedSamplesLen int
testAppendedSamples func ( t * testing . T , committed [ ] sample )
testExemplars func ( t * testing . T , er [ ] exemplar . QueryResult )
} {
{
name : "1Fam2000Gauges" ,
parsableText : makeTestGauges ( 2000 ) ,
expectedSamplesLen : 2000 ,
testAppendedSamples : func ( t * testing . T , committed [ ] sample ) {
var expectedMF string
if appV2 {
expectedMF = "metric_a" // Only AppenderV2 supports metric family passing.
}
// Verify a few samples.
testutil . RequireEqual ( t , sample {
MF : expectedMF ,
M : metadata . Metadata { Type : model . MetricTypeGauge , Help : "help text" } ,
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" , "foo" , "0" , "bar" , "0" ) , V : 1 , T : timestamp . FromTime ( ts ) ,
} , committed [ 0 ] )
testutil . RequireEqual ( t , sample {
MF : expectedMF ,
M : metadata . Metadata { Type : model . MetricTypeGauge , Help : "help text" } ,
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" , "foo" , "1245" , "bar" , "124500" ) , V : 1 , T : timestamp . FromTime ( ts ) ,
} , committed [ 1245 ] )
testutil . RequireEqual ( t , sample {
MF : expectedMF ,
M : metadata . Metadata { Type : model . MetricTypeGauge , Help : "help text" } ,
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" , "foo" , "1999" , "bar" , "199900" ) , V : 1 , T : timestamp . FromTime ( ts ) ,
} , committed [ len ( committed ) - 1 ] )
} ,
} ,
{
name : "237FamsAllTypes" ,
parsableText : readTextParseTestMetrics ( t ) ,
expectedSamplesLen : 1857 ,
testAppendedSamples : func ( t * testing . T , committed [ ] sample ) {
// Verify a few samples.
testutil . RequireEqual ( t , sample {
MF : func ( ) string {
if ! appV2 {
return ""
}
return "go_gc_gomemlimit_bytes"
} ( ) ,
M : metadata . Metadata { Type : model . MetricTypeGauge , Help : "Go runtime memory limit configured by the user, otherwise math.MaxInt64. This value is set by the GOMEMLIMIT environment variable, and the runtime/debug.SetMemoryLimit function. Sourced from /gc/gomemlimit:bytes" } ,
L : labels . FromStrings ( model . MetricNameLabel , "go_gc_gomemlimit_bytes" ) , V : 9.03676723e+08 , T : timestamp . FromTime ( ts ) ,
} , committed [ 11 ] )
testutil . RequireEqual ( t , sample {
MF : func ( ) string {
if ! appV2 {
return "" // Only AppenderV2 supports metric family passing.
}
return "prometheus_http_request_duration_seconds"
} ( ) ,
M : metadata . Metadata { Type : model . MetricTypeHistogram , Help : "Histogram of latencies for HTTP requests." } ,
L : labels . FromStrings ( model . MetricNameLabel , "prometheus_http_request_duration_seconds_bucket" , "handler" , "/api/v1/query_range" , "le" , "120.0" ) , V : 118157 , T : timestamp . FromTime ( ts ) ,
} , committed [ 448 ] )
testutil . RequireEqual ( t , sample {
MF : func ( ) string {
if ! appV2 {
return "" // Only AppenderV2 supports metric family passing.
}
return "promhttp_metric_handler_requests_total"
} ( ) ,
M : metadata . Metadata { Type : model . MetricTypeCounter , Help : "Total number of scrapes by HTTP status code." } ,
L : labels . FromStrings ( model . MetricNameLabel , "promhttp_metric_handler_requests_total" , "code" , "503" ) , V : 0 , T : timestamp . FromTime ( ts ) ,
} , committed [ len ( committed ) - 1 ] )
} ,
} ,
{
name : "100HistsWithExemplars" ,
parsableText : makeTestHistogramsWithExemplars ( 100 ) ,
expectedSamplesLen : 24 * 100 ,
testAppendedSamples : func ( t * testing . T , committed [ ] sample ) {
// Verify a few samples.
m := metadata . Metadata { Type : model . MetricTypeHistogram , Help : "RPC latency distributions." }
testutil . RequireEqual ( t , sample {
MF : func ( ) string {
if ! appV2 {
return "" // Only AppenderV2 supports metric family passing.
}
return "rpc_durations_histogram0_seconds"
} ( ) ,
M : m , L : labels . FromStrings ( model . MetricNameLabel , "rpc_durations_histogram0_seconds_bucket" , "le" , "0.0003100000000000002" ) , V : 15 , T : timestamp . FromTime ( ts ) ,
ES : [ ] exemplar . Exemplar {
{ Labels : labels . FromStrings ( "dummyID" , "9818" ) , Value : 0.0002791130914009552 , Ts : 1726839814982 , HasTs : true } ,
} ,
} , committed [ 13 ] )
testutil . RequireEqual ( t , sample {
MF : func ( ) string {
if ! appV2 {
return "" // Only AppenderV2 supports metric family passing.
}
return "rpc_durations_histogram49_seconds"
} ( ) ,
M : m , L : labels . FromStrings ( model . MetricNameLabel , "rpc_durations_histogram49_seconds_sum" ) , V : - 8.452185437166741e-05 , T : timestamp . FromTime ( ts ) ,
} , committed [ 24 * 50 - 3 ] )
// This series does not have metadata, nor metric family, because of isSeriesPartOfFamily bug and OpenMetric 1.0 limitations around _created series.
// TODO(bwplotka): Fix with https://github.com/prometheus/prometheus/issues/17900
testutil . RequireEqual ( t , sample {
L : labels . FromStrings ( model . MetricNameLabel , "rpc_durations_histogram99_seconds_created" ) , V : 1.726839813016302e+09 , T : timestamp . FromTime ( ts ) ,
} , committed [ len ( committed ) - 1 ] )
} ,
testExemplars : func ( t * testing . T , er [ ] exemplar . QueryResult ) {
// 12 out of 24 histogram series have exemplars.
require . Len ( t , er , 12 * 100 )
testutil . RequireEqual ( t , exemplar . QueryResult {
SeriesLabels : labels . FromStrings ( model . MetricNameLabel , "rpc_durations_histogram0_seconds_bucket" , "le" , "0.0003100000000000002" ) ,
Exemplars : [ ] exemplar . Exemplar {
{ Labels : labels . FromStrings ( "dummyID" , "9818" ) , Value : 0.0002791130914009552 , Ts : 1726839814982 , HasTs : true } ,
} ,
} , er [ 10 ] )
testutil . RequireEqual ( t , exemplar . QueryResult {
SeriesLabels : labels . FromStrings ( model . MetricNameLabel , "rpc_durations_histogram9_seconds_bucket" , "le" , "1.0000000000000216e-05" ) ,
Exemplars : [ ] exemplar . Exemplar {
{ Labels : labels . FromStrings ( "dummyID" , "19206" ) , Value : - 4.6156147425468016e-05 , Ts : 1726839815133 , HasTs : true } ,
} ,
} , er [ len ( er ) - 1 ] )
} ,
} ,
} {
t . Run ( fmt . Sprintf ( "appV2=%v/data=%v" , appV2 , tc . name ) , func ( t * testing . T ) {
s := teststorage . New ( t , func ( opt * tsdb . Options ) {
opt . EnableMetadataWALRecords = true
} )
appTest := teststorage . NewAppendable ( ) . Then ( s )
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
app := sl . appender ( )
_ , _ , _ , err := app . append ( tc . parsableText , "application/openmetrics-text" , ts )
require . NoError ( t , err )
require . NoError ( t , app . Commit ( ) )
// Check the recorded samples on the Appender layer.
require . Nil ( t , appTest . PendingSamples ( ) )
require . Nil ( t , appTest . RolledbackSamples ( ) )
got := appTest . ResultSamples ( )
require . Len ( t , got , tc . expectedSamplesLen )
tc . testAppendedSamples ( t , got )
// Check basic storage stats.
stats := s . Head ( ) . Stats ( model . MetricNameLabel , 2000 )
require . Equal ( t , tc . expectedSamplesLen , int ( stats . NumSeries ) )
// Check exemplars.
eq , err := s . ExemplarQuerier ( t . Context ( ) )
require . NoError ( t , err )
er , err := eq . Select ( math . MinInt64 , math . MaxInt64 , nil )
require . NoError ( t , err )
if tc . testExemplars != nil {
tc . testExemplars ( t , er )
} else {
// Expect no exemplars.
require . Empty ( t , er , "%v is not empty" , er )
}
} )
}
}
}
2026-01-21 03:21:56 -05:00
// BenchmarkScrapeLoopAppend benchmarks scrape appends for typical cases.
//
2026-02-02 07:44:11 -05:00
// Benchmark compares append function run across 5 dimensions:
// * `withStorage`: without storage isolates the benchmark to the scrape loop append code. With storage is an
// integration benchmark with the TSDB head appender code. For acceptance criteria run with storage, without for debugging.
// * `appV2`: appender V1 or V2.
// * `appendMetadataToWAL`: metadata-wal-records feature enabled or not (problematic feature we might need to change
// soon, see https://github.com/prometheus/prometheus/issues/15911.
// * `data`: different sizes of metrics scraped e.g. one big gauge metric family
2025-02-10 07:26:18 -05:00
// with a thousand series and more realistic scenario with common types.
2026-02-02 07:44:11 -05:00
// * `fmt`: different scrape formats which will benchmark different parsers e.g.
2025-02-10 07:26:18 -05:00
// promtext, omtext and promproto.
//
2026-02-02 07:44:11 -05:00
// NOTE: withStorage=true uses sync.Pool buffers which is heavily non-deterministic and shared across go routines.
// As a result, it's recommended to run dimensions you want to compare with in e.g. separate go tool invocations.
// Recommended CLI invocation(s):
2024-12-29 10:10:39 -05:00
/ *
2026-02-02 07:44:11 -05:00
# Acceptance : With storage with V1 and V2 in separate process :
export bench = appendV1 && go test . / scrape / ... \
- run ' ^ $ ' - bench ' ^ BenchmarkScrapeLoopAppend / withStorage = true / appV2 = false / $ ' \
- benchtime 2 s - count 6 - cpu 2 - timeout 999 m \
| tee $ { bench } . txt
export bench = appendV2 && go test . / scrape / ... \
- run ' ^ $ ' - bench ' ^ BenchmarkScrapeLoopAppend / withStorage = true / appV2 = true / $ ' \
- benchtime 2 s - count 6 - cpu 2 - timeout 999 m \
| tee $ { bench } . txt
# For debugging scrape overheads :
export bench = appendNoStorage && go test . / scrape / ... \
- run ' ^ $ ' - bench ' ^ BenchmarkScrapeLoopAppend / withStorage = false / $ ' \
2026-01-21 03:21:56 -05:00
- benchtime 2 s - count 6 - cpu 2 - timeout 999 m \
2024-12-29 10:10:39 -05:00
| tee $ { bench } . txt
* /
func BenchmarkScrapeLoopAppend ( b * testing . B ) {
2026-02-02 07:44:11 -05:00
for _ , withStorage := range [ ] bool { false , true } {
for _ , appV2 := range [ ] bool { false , true } {
for _ , appendMetadataToWAL := range [ ] bool { false , true } {
for _ , data := range [ ] struct {
name string
parsableText [ ] byte
} {
{ name : "1Fam2000Gauges" , parsableText : makeTestGauges ( 2000 ) } , // ~68.1 KB, ~77.9 KB in proto.
{ name : "237FamsAllTypes" , parsableText : readTextParseTestMetrics ( b ) } , // ~185.7 KB, ~70.6 KB in proto.
} {
b . Run ( fmt . Sprintf ( "withStorage=%v/appV2=%v/appendMetadataToWAL=%v/data=%v" , withStorage , appV2 , appendMetadataToWAL , data . name ) , func ( b * testing . B ) {
metricsProto := promTextToProto ( b , data . parsableText )
for _ , bcase := range [ ] struct {
name string
contentType string
parsable [ ] byte
} {
{ name : "PromText" , contentType : "text/plain" , parsable : data . parsableText } ,
{ name : "OMText" , contentType : "application/openmetrics-text" , parsable : data . parsableText } ,
{ name : "PromProto" , contentType : "application/vnd.google.protobuf" , parsable : metricsProto } ,
} {
b . Run ( fmt . Sprintf ( "fmt=%v" , bcase . name ) , func ( b * testing . B ) {
benchScrapeLoopAppend ( b , withStorage , appV2 , bcase . parsable , bcase . contentType , appendMetadataToWAL , false )
} )
}
} )
}
2024-12-29 10:10:39 -05:00
}
2026-01-21 03:21:56 -05:00
}
}
}
func benchScrapeLoopAppend (
b * testing . B ,
2026-02-02 07:44:11 -05:00
withStorage bool ,
2026-01-21 03:21:56 -05:00
appV2 bool ,
parsable [ ] byte ,
contentType string ,
appendMetadataToWAL bool ,
enableExemplarStorage bool ,
) {
2026-02-02 07:44:11 -05:00
var a compatAppendable = teststorage . NewAppendable ( ) . SkipRecording ( true ) // Make it noop for benchmark purposes.
if withStorage {
a = teststorage . New ( b , func ( opt * tsdb . Options ) {
opt . EnableMetadataWALRecords = appendMetadataToWAL
if enableExemplarStorage {
opt . EnableExemplarStorage = true
opt . MaxExemplars = 1e5
}
} )
}
sl , _ := newTestScrapeLoop ( b , withAppendable ( a , appV2 ) , func ( sl * scrapeLoop ) {
2026-01-21 03:21:56 -05:00
sl . appendMetadataToWAL = appendMetadataToWAL
} )
ts := time . Time { }
b . ReportAllocs ( )
b . ResetTimer ( )
for b . Loop ( ) {
2026-02-02 07:44:11 -05:00
app := sl . appender ( )
2026-01-21 03:21:56 -05:00
ts = ts . Add ( time . Second )
_ , _ , _ , err := app . append ( parsable , contentType , ts )
if err != nil {
b . Fatal ( err )
}
// Reset the appender so it doesn't grow indefinitely, and it mimics what prod scrape will do.
// We do rollback, because it's cheaper than Commit.
if err := app . Rollback ( ) ; err != nil {
b . Fatal ( err )
}
}
}
// BenchmarkScrapeLoopAppend_HistogramsWithExemplars benchmarks OM scrapes with histograms full of exemplars.
//
// For e2e TSDB impact, we enable the TSDB exemplar storage
//
// Recommended CLI invocation:
/ *
export bench = appendHistWithExemplars && go test . / scrape / ... \
- run ' ^ $ ' - bench ' ^ BenchmarkScrapeLoopAppend_HistogramsWithExemplars ' \
- benchtime 5 s - count 6 - cpu 2 - timeout 999 m \
| tee $ { bench } . txt
* /
func BenchmarkScrapeLoopAppend_HistogramsWithExemplars ( b * testing . B ) {
for _ , appV2 := range [ ] bool { false , true } {
b . Run ( fmt . Sprintf ( "appV2=%v" , appV2 ) , func ( b * testing . B ) {
parsable := makeTestHistogramsWithExemplars ( 100 ) // ~255.8 KB in OM text.
2026-02-02 07:44:11 -05:00
benchScrapeLoopAppend ( b , true , appV2 , parsable , "application/openmetrics-text" , false , true )
2024-12-29 10:10:39 -05:00
} )
2024-08-20 15:12:02 -04:00
}
}
2025-12-22 04:38:48 -05:00
func TestScrapeLoopScrapeAndReport ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopScrapeAndReport ( t , appV2 )
} )
}
func testScrapeLoopScrapeAndReport ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
parsableText := readTextParseTestMetrics ( t )
// On windows \r is added when reading, but parsers do not support this. Kill it.
parsableText = bytes . ReplaceAll ( parsableText , [ ] byte ( "\r" ) , nil )
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . fallbackScrapeProtocol = "application/openmetrics-text"
} )
scraper . scrapeFunc = func ( _ context . Context , writer io . Writer ) error {
_ , err := writer . Write ( parsableText )
return err
}
ts := time . Time { }
sl . scrapeAndReport ( time . Time { } , ts , nil )
require . NoError ( t , scraper . lastError )
require . Len ( t , appTest . ResultSamples ( ) , 1862 )
require . Len ( t , appTest . ResultMetadata ( ) , 1862 )
}
// Recommended CLI invocation:
/ *
export bench = scrapeAndReport && go test . / scrape / ... \
2026-01-21 03:21:56 -05:00
- run ' ^ $ ' - bench ' ^ BenchmarkScrapeLoopScrapeAndReport $ ' \
2025-12-22 04:38:48 -05:00
- benchtime 5 s - count 6 - cpu 2 - timeout 999 m \
| tee $ { bench } . txt
* /
func BenchmarkScrapeLoopScrapeAndReport ( b * testing . B ) {
2026-01-21 03:21:56 -05:00
for _ , appV2 := range [ ] bool { false , true } {
b . Run ( fmt . Sprintf ( "appV2=%v" , appV2 ) , func ( b * testing . B ) {
parsableText := readTextParseTestMetrics ( b )
2025-12-22 04:38:48 -05:00
2026-01-21 03:21:56 -05:00
s := teststorage . New ( b )
2025-12-22 04:38:48 -05:00
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( b , withAppendable ( s , appV2 ) , func ( sl * scrapeLoop ) {
sl . fallbackScrapeProtocol = "application/openmetrics-text"
} )
scraper . scrapeFunc = func ( _ context . Context , writer io . Writer ) error {
_ , err := writer . Write ( parsableText )
return err
}
2025-12-22 04:38:48 -05:00
2026-01-21 03:21:56 -05:00
ts := time . Time { }
2025-12-22 04:38:48 -05:00
2026-01-21 03:21:56 -05:00
b . ReportAllocs ( )
b . ResetTimer ( )
for b . Loop ( ) {
ts = ts . Add ( time . Second )
sl . scrapeAndReport ( time . Time { } , ts , nil )
require . NoError ( b , scraper . lastError )
}
} )
2025-12-22 04:38:48 -05:00
}
}
2024-10-23 11:34:28 -04:00
func TestSetOptionsHandlingStaleness ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testSetOptionsHandlingStaleness ( t , appV2 )
} )
}
func testSetOptionsHandlingStaleness ( t * testing . T , appV2 bool ) {
s := teststorage . New ( t , func ( opt * tsdb . Options ) {
opt . OutOfOrderTimeWindow = 600000
} )
2024-10-23 11:34:28 -04:00
signal := make ( chan struct { } , 1 )
2025-12-22 04:38:48 -05:00
ctx , cancel := context . WithCancel ( t . Context ( ) )
2024-10-23 11:34:28 -04:00
defer cancel ( )
// Function to run the scrape loop
runScrapeLoop := func ( ctx context . Context , t * testing . T , cue int , action func ( * scrapeLoop ) ) {
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( s , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . ctx = ctx
} )
2024-10-23 11:34:28 -04:00
numScrapes := 0
2025-02-10 02:06:58 -05:00
scraper . scrapeFunc = func ( _ context . Context , w io . Writer ) error {
2024-10-23 11:34:28 -04:00
numScrapes ++
if numScrapes == cue {
action ( sl )
}
2025-12-22 04:38:48 -05:00
_ , _ = fmt . Fprintf ( w , "metric_a{a=\"1\",b=\"1\"} %d\n" , 42 + numScrapes )
2024-10-23 11:34:28 -04:00
return nil
}
sl . run ( nil )
}
go func ( ) {
runScrapeLoop ( ctx , t , 2 , func ( sl * scrapeLoop ) {
go sl . stop ( )
// Wait a bit then start a new target.
time . Sleep ( 100 * time . Millisecond )
go func ( ) {
2025-08-04 02:20:57 -04:00
runScrapeLoop ( ctx , t , 4 , func ( * scrapeLoop ) {
2024-10-23 11:34:28 -04:00
cancel ( )
} )
signal <- struct { } { }
} ( )
} )
} ( )
select {
case <- signal :
case <- time . After ( 10 * time . Second ) :
t . Fatalf ( "Scrape wasn't stopped." )
}
2025-12-22 04:38:48 -05:00
ctx1 , cancel := context . WithCancel ( t . Context ( ) )
2024-10-23 11:34:28 -04:00
defer cancel ( )
q , err := s . Querier ( 0 , time . Now ( ) . UnixNano ( ) )
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
t . Cleanup ( func ( ) { _ = q . Close ( ) } )
2024-10-23 11:34:28 -04:00
series := q . Select ( ctx1 , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , "metric_a" ) )
2025-12-22 04:38:48 -05:00
var results [ ] sample
2024-10-23 11:34:28 -04:00
for series . Next ( ) {
it := series . At ( ) . Iterator ( nil )
for it . Next ( ) == chunkenc . ValFloat {
t , v := it . At ( )
2025-12-22 04:38:48 -05:00
results = append ( results , sample {
L : series . At ( ) . Labels ( ) ,
T : t ,
V : v ,
2024-10-23 11:34:28 -04:00
} )
}
require . NoError ( t , it . Err ( ) )
}
require . NoError ( t , series . Err ( ) )
var c int
for _ , s := range results {
2025-12-22 04:38:48 -05:00
if value . IsStaleNaN ( s . V ) {
2024-10-23 11:34:28 -04:00
c ++
}
}
2026-01-21 03:21:56 -05:00
require . Zero ( t , c , "invalid count of staleness markers after stopping the engine" )
2024-10-23 11:34:28 -04:00
}
2017-05-03 09:55:35 -04:00
func TestScrapeLoopRunCreatesStaleMarkersOnFailedScrape ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopRunCreatesStaleMarkersOnFailedScrape ( t , appV2 )
} )
}
func testScrapeLoopRunCreatesStaleMarkersOnFailedScrape ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
signal := make ( chan struct { } , 1 )
ctx , cancel := context . WithCancel ( t . Context ( ) )
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . ctx = ctx
// Since we're writing samples directly below we need to provide a protocol fallback.
sl . fallbackScrapeProtocol = "text/plain"
} )
2017-05-03 09:55:35 -04:00
// Succeed once, several failures, then stop.
2017-09-08 08:34:45 -04:00
numScrapes := 0
2025-02-10 02:06:58 -05:00
scraper . scrapeFunc = func ( _ context . Context , w io . Writer ) error {
2017-05-26 04:44:48 -04:00
numScrapes ++
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 10:14:31 -04:00
switch numScrapes {
case 1 :
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "metric_a 42\n" ) )
2017-05-03 09:55:35 -04:00
return nil
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 10:14:31 -04:00
case 5 :
2017-05-03 09:55:35 -04:00
cancel ( )
}
2019-03-25 19:01:12 -04:00
return errors . New ( "scrape failed" )
2017-05-03 09:55:35 -04:00
}
go func ( ) {
2021-08-31 11:37:32 -04:00
sl . run ( nil )
2017-05-03 09:55:35 -04:00
signal <- struct { } { }
} ( )
select {
case <- signal :
case <- time . After ( 5 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Scrape wasn't stopped." )
2017-05-03 09:55:35 -04:00
}
2025-12-22 04:38:48 -05:00
got := appTest . ResultSamples ( )
// 1 successfully scraped sample
// 1 stale marker after first fail
// 5x 5 report samples for each scrape successful or not.
require . Len ( t , got , 27 , "Appended samples not as expected:\n%s" , appTest )
require . Equal ( t , 42.0 , got [ 0 ] . V , "Appended first sample not as expected" )
require . True ( t , value . IsStaleNaN ( got [ 6 ] . V ) ,
"Appended second sample not as expected. Wanted: stale NaN Got: %x" , math . Float64bits ( got [ 6 ] . V ) )
2017-05-03 11:51:45 -04:00
}
func TestScrapeLoopRunCreatesStaleMarkersOnParseFailure ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopRunCreatesStaleMarkersOnParseFailure ( t , appV2 )
} )
}
func testScrapeLoopRunCreatesStaleMarkersOnParseFailure ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
signal := make ( chan struct { } , 1 )
2017-05-03 11:51:45 -04:00
2025-12-22 04:38:48 -05:00
ctx , cancel := context . WithCancel ( t . Context ( ) )
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . ctx = ctx
// Since we're writing samples directly below we need to provide a protocol fallback.
sl . fallbackScrapeProtocol = "text/plain"
} )
2017-05-03 11:51:45 -04:00
// Succeed once, several failures, then stop.
2025-12-22 04:38:48 -05:00
numScrapes := 0
2025-02-10 02:06:58 -05:00
scraper . scrapeFunc = func ( _ context . Context , w io . Writer ) error {
2017-05-26 04:44:48 -04:00
numScrapes ++
2025-12-22 04:38:48 -05:00
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 10:14:31 -04:00
switch numScrapes {
case 1 :
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "metric_a 42\n" ) )
2017-05-03 11:51:45 -04:00
return nil
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 10:14:31 -04:00
case 2 :
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "7&-\n" ) )
2017-05-03 11:51:45 -04:00
return nil
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 10:14:31 -04:00
case 3 :
2017-05-03 11:51:45 -04:00
cancel ( )
}
2019-03-25 19:01:12 -04:00
return errors . New ( "scrape failed" )
2017-05-03 11:51:45 -04:00
}
go func ( ) {
2021-08-31 11:37:32 -04:00
sl . run ( nil )
2017-05-03 11:51:45 -04:00
signal <- struct { } { }
} ( )
select {
case <- signal :
2025-12-22 04:38:48 -05:00
// TODO(bwplotka): Prone to flakiness, depend on atomic numScrapes.
2017-05-03 11:51:45 -04:00
case <- time . After ( 5 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Scrape wasn't stopped." )
2017-05-03 11:51:45 -04:00
}
2025-12-22 04:38:48 -05:00
got := appTest . ResultSamples ( )
// 1 successfully scraped sample
// 1 stale marker after first fail
// 3x 5 report samples for each scrape successful or not.
require . Len ( t , got , 17 , "Appended samples not as expected:\n%s" , appTest )
require . Equal ( t , 42.0 , got [ 0 ] . V , "Appended first sample not as expected" )
require . True ( t , value . IsStaleNaN ( got [ 6 ] . V ) ,
"Appended second sample not as expected. Wanted: stale NaN Got: %x" , math . Float64bits ( got [ 6 ] . V ) )
2019-03-28 13:07:14 -04:00
}
2025-12-22 04:38:48 -05:00
// If we have a target with sample_limit set and scrape initially works, but then we hit the sample_limit error,
2025-04-16 08:34:08 -04:00
// then we don't expect to see any StaleNaNs appended for the series that disappeared due to sample_limit error.
func TestScrapeLoopRunCreatesStaleMarkersOnSampleLimit ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopRunCreatesStaleMarkersOnSampleLimit ( t , appV2 )
} )
}
func testScrapeLoopRunCreatesStaleMarkersOnSampleLimit ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
signal := make ( chan struct { } , 1 )
2025-04-16 08:34:08 -04:00
2025-12-22 04:38:48 -05:00
ctx , cancel := context . WithCancel ( t . Context ( ) )
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . ctx = ctx
// Since we're writing samples directly below we need to provide a protocol fallback.
sl . fallbackScrapeProtocol = "text/plain"
sl . sampleLimit = 4
} )
2025-04-16 08:34:08 -04:00
// Succeed once, several failures, then stop.
2025-12-22 04:38:48 -05:00
numScrapes := 0
2025-04-16 08:34:08 -04:00
scraper . scrapeFunc = func ( _ context . Context , w io . Writer ) error {
numScrapes ++
switch numScrapes {
case 1 :
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "metric_a 10\nmetric_b 10\nmetric_c 10\nmetric_d 10\n" ) )
2025-04-16 08:34:08 -04:00
return nil
case 2 :
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "metric_a 20\nmetric_b 20\nmetric_c 20\nmetric_d 20\nmetric_e 999\n" ) )
2025-04-16 08:34:08 -04:00
return nil
case 3 :
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "metric_a 30\nmetric_b 30\nmetric_c 30\nmetric_d 30\n" ) )
2025-04-16 08:34:08 -04:00
return nil
case 4 :
cancel ( )
}
return errors . New ( "scrape failed" )
}
go func ( ) {
sl . run ( nil )
signal <- struct { } { }
} ( )
select {
case <- signal :
case <- time . After ( 5 * time . Second ) :
require . FailNow ( t , "Scrape wasn't stopped." )
}
2025-12-22 04:38:48 -05:00
got := appTest . ResultSamples ( )
2025-04-16 08:34:08 -04:00
// 4 scrapes in total:
// #1 - success - 4 samples appended + 5 report series
// #2 - sample_limit exceeded - no samples appended, only 5 report series
// #3 - success - 4 samples appended + 5 report series
// #4 - scrape canceled - 4 StaleNaNs appended because of scrape error + 5 report series
2025-12-22 04:38:48 -05:00
require . Len ( t , got , ( 4 + 5 ) + 5 + ( 4 + 5 ) + ( 4 + 5 ) , "Appended samples not as expected:\n%s" , appTest )
2025-04-16 08:34:08 -04:00
// Expect first 4 samples to be metric_X [0-3].
for i := range 4 {
2025-12-22 04:38:48 -05:00
require . Equal ( t , 10.0 , got [ i ] . V , "Appended %d sample not as expected" , i )
2025-04-16 08:34:08 -04:00
}
// Next 5 samples are report series [4-8].
// Next 5 samples are report series for the second scrape [9-13].
// Expect first 4 samples to be metric_X from the third scrape [14-17].
for i := 14 ; i <= 17 ; i ++ {
2025-12-22 04:38:48 -05:00
require . Equal ( t , 30.0 , got [ i ] . V , "Appended %d sample not as expected" , i )
2025-04-16 08:34:08 -04:00
}
// Next 5 samples are report series [18-22].
// Next 5 samples are report series [23-26].
for i := 23 ; i <= 26 ; i ++ {
2025-12-22 04:38:48 -05:00
require . True ( t , value . IsStaleNaN ( got [ i ] . V ) ,
"Appended second sample not as expected. Wanted: stale NaN Got: %x" , math . Float64bits ( got [ i ] . V ) )
2025-04-16 08:34:08 -04:00
}
}
2019-03-28 13:07:14 -04:00
func TestScrapeLoopCache ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopCache ( t , appV2 )
} )
}
func testScrapeLoopCache ( t * testing . T , appV2 bool ) {
2019-08-08 21:35:39 -04:00
s := teststorage . New ( t )
2019-03-28 13:07:14 -04:00
2025-12-22 04:38:48 -05:00
signal := make ( chan struct { } , 1 )
2019-03-28 13:07:14 -04:00
2025-12-22 04:38:48 -05:00
ctx , cancel := context . WithCancel ( t . Context ( ) )
appTest := teststorage . NewAppendable ( ) . Then ( s )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . ctx = ctx
sl . l = promslog . New ( & promslog . Config { } )
// Since we're writing samples directly below we need to provide a protocol fallback.
sl . fallbackScrapeProtocol = "text/plain"
// Decreasing the scrape interval could make the test fail, as multiple scrapes might be initiated at identical millisecond timestamps.
// See https://github.com/prometheus/prometheus/issues/12727.
sl . interval = 100 * time . Millisecond
} )
2019-03-28 13:07:14 -04:00
numScrapes := 0
2025-02-10 02:06:58 -05:00
scraper . scrapeFunc = func ( _ context . Context , w io . Writer ) error {
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 10:14:31 -04:00
switch numScrapes {
case 1 , 2 :
2021-09-04 08:35:03 -04:00
_ , ok := sl . cache . series [ "metric_a" ]
require . True ( t , ok , "metric_a missing from cache after scrape %d" , numScrapes )
_ , ok = sl . cache . series [ "metric_b" ]
require . True ( t , ok , "metric_b missing from cache after scrape %d" , numScrapes )
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 10:14:31 -04:00
case 3 :
2021-09-04 08:35:03 -04:00
_ , ok := sl . cache . series [ "metric_a" ]
require . True ( t , ok , "metric_a missing from cache after scrape %d" , numScrapes )
_ , ok = sl . cache . series [ "metric_b" ]
require . False ( t , ok , "metric_b present in cache after scrape %d" , numScrapes )
2019-03-28 13:07:14 -04:00
}
numScrapes ++
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 10:14:31 -04:00
switch numScrapes {
case 1 :
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "metric_a 42\nmetric_b 43\n" ) )
2019-03-28 13:07:14 -04:00
return nil
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 10:14:31 -04:00
case 3 :
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "metric_a 44\n" ) )
2019-03-28 13:07:14 -04:00
return nil
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 10:14:31 -04:00
case 4 :
2019-03-28 13:07:14 -04:00
cancel ( )
}
2024-11-03 07:15:51 -05:00
return errors . New ( "scrape failed" )
2019-03-28 13:07:14 -04:00
}
go func ( ) {
2021-08-31 11:37:32 -04:00
sl . run ( nil )
2019-03-28 13:07:14 -04:00
signal <- struct { } { }
} ( )
select {
case <- signal :
case <- time . After ( 5 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Scrape wasn't stopped." )
2019-03-28 13:07:14 -04:00
}
2025-12-22 04:38:48 -05:00
// 3 successfully scraped samples
// 3 stale marker after samples were missing.
// 4x 5 report samples for each scrape successful or not.
require . Len ( t , appTest . ResultSamples ( ) , 26 , "Appended samples not as expected:\n%s" , appTest )
2019-03-28 13:52:46 -04:00
}
func TestScrapeLoopCacheMemoryExhaustionProtection ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopCacheMemoryExhaustionProtection ( t , appV2 )
} )
}
func testScrapeLoopCacheMemoryExhaustionProtection ( t * testing . T , appV2 bool ) {
2019-08-08 21:35:39 -04:00
s := teststorage . New ( t )
2019-03-28 13:52:46 -04:00
2025-12-22 04:38:48 -05:00
signal := make ( chan struct { } , 1 )
2019-03-28 13:52:46 -04:00
2025-12-22 04:38:48 -05:00
ctx , cancel := context . WithCancel ( t . Context ( ) )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) . Then ( s ) , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . ctx = ctx
} )
2019-03-28 13:52:46 -04:00
numScrapes := 0
2025-02-10 02:06:58 -05:00
scraper . scrapeFunc = func ( _ context . Context , w io . Writer ) error {
2019-03-28 13:52:46 -04:00
numScrapes ++
if numScrapes < 5 {
s := ""
2025-08-27 08:38:54 -04:00
for i := range 500 {
2019-03-28 13:52:46 -04:00
s = fmt . Sprintf ( "%smetric_%d_%d 42\n" , s , i , numScrapes )
}
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( s + "&" ) )
2019-03-28 13:52:46 -04:00
} else {
cancel ( )
}
return nil
}
go func ( ) {
2021-08-31 11:37:32 -04:00
sl . run ( nil )
2019-03-28 13:52:46 -04:00
signal <- struct { } { }
} ( )
select {
case <- signal :
case <- time . After ( 5 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Scrape wasn't stopped." )
2019-03-28 13:52:46 -04:00
}
2021-09-04 08:35:03 -04:00
require . LessOrEqual ( t , len ( sl . cache . series ) , 2000 , "More than 2000 series cached." )
2017-05-03 09:55:35 -04:00
}
2026-01-21 03:21:56 -05:00
func TestScrapeLoopAppend_HonorLabels ( t * testing . T ) {
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendHonorLabels ( t , appV2 )
} )
}
func testScrapeLoopAppendHonorLabels ( t * testing . T , appV2 bool ) {
for _ , test := range [ ] struct {
2018-02-15 09:26:24 -05:00
title string
honorLabels bool
2026-01-21 03:21:56 -05:00
scrapeText string
2018-02-15 09:26:24 -05:00
discoveryLabels [ ] string
expLset labels . Labels
} {
2017-04-11 10:42:17 -04:00
{
2026-01-21 03:21:56 -05:00
// On label collision, when "honor_labels" is not set, prefix is added.
title : "HonorLabels=false" ,
scrapeText : ` metric { n="1"} 1 ` ,
2018-02-15 09:26:24 -05:00
discoveryLabels : [ ] string { "n" , "2" } ,
expLset : labels . FromStrings ( "__name__" , "metric" , "exported_n" , "1" , "n" , "2" ) ,
2026-01-21 03:21:56 -05:00
} ,
{
// Case where SD already has the prefixed label - it shouldn't be overridden.
title : "HonorLabels=false;exported prefix already exists in SD" ,
scrapeText : ` metric { n="1"} 1 ` ,
2019-11-20 10:50:05 -05:00
discoveryLabels : [ ] string { "n" , "2" , "exported_n" , "2" } ,
2026-01-21 03:21:56 -05:00
expLset : labels . FromStrings ( "__name__" , "metric" , "n" , "2" , "exported_n" , "2" , "exported_exported_n" , "1" ) ,
} ,
{
2018-02-15 09:26:24 -05:00
// Labels with no value need to be removed as these should not be ingested.
2026-01-21 03:21:56 -05:00
title : "HonorLabels=false;empty label" ,
scrapeText : ` metric { n=""} 1 ` ,
2018-02-15 09:26:24 -05:00
discoveryLabels : nil ,
expLset : labels . FromStrings ( "__name__" , "metric" ) ,
2026-01-21 03:21:56 -05:00
} ,
{
// On label collision, when "honor_labels" is true, label is overridden.
title : "HonorLabels=true" ,
2018-02-15 09:26:24 -05:00
honorLabels : true ,
2026-01-21 03:21:56 -05:00
scrapeText : ` metric { n="1"} 1 ` ,
discoveryLabels : [ ] string { "n" , "2" } ,
expLset : labels . FromStrings ( "__name__" , "metric" , "n" , "1" ) ,
2017-04-11 10:42:17 -04:00
} ,
2026-01-21 03:21:56 -05:00
} {
t . Run ( test . title , func ( t * testing . T ) {
discoveryLabels := & Target {
labels : labels . FromStrings ( test . discoveryLabels ... ) ,
2025-12-22 04:38:48 -05:00
}
2018-02-15 09:26:24 -05:00
2026-01-21 03:21:56 -05:00
appTest := teststorage . NewAppendable ( )
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
sl . sampleMutator = func ( l labels . Labels ) labels . Labels {
return mutateSampleLabels ( l , discoveryLabels , test . honorLabels , nil )
}
sl . reportSampleMutator = func ( l labels . Labels ) labels . Labels {
return mutateReportSampleLabels ( l , discoveryLabels )
}
} )
2018-02-15 09:26:24 -05:00
2026-01-21 03:21:56 -05:00
now := time . Now ( )
2018-02-15 09:26:24 -05:00
2026-01-21 03:21:56 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( test . scrapeText ) , "text/plain" , now )
require . NoError ( t , err )
require . NoError ( t , app . Commit ( ) )
2018-02-15 09:26:24 -05:00
2026-01-21 03:21:56 -05:00
expected := [ ] sample {
{
L : test . expLset ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
} ,
}
teststorage . RequireEqual ( t , expected , appTest . ResultSamples ( ) )
} )
2017-04-11 10:42:17 -04:00
}
2017-04-14 05:41:18 -04:00
}
2026-01-21 03:21:56 -05:00
func TestScrapeLoopAppendForConflictingPrefixedLabels ( t * testing . T ) {
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendForConflictingPrefixedLabels ( t , appV2 )
} )
2023-04-16 08:13:31 -04:00
}
2026-01-21 03:21:56 -05:00
func testScrapeLoopAppendForConflictingPrefixedLabels ( t * testing . T , appV2 bool ) {
for _ , tc := range [ ] struct {
name string
2021-10-15 14:31:03 -04:00
targetLabels [ ] string
exposedLabels string
expected [ ] string
} {
2026-01-21 03:21:56 -05:00
{
name : "One target label collides with existing label" ,
2021-10-15 14:31:03 -04:00
targetLabels : [ ] string { "foo" , "2" } ,
exposedLabels : ` metric { foo="1"} 0 ` ,
expected : [ ] string { "__name__" , "metric" , "exported_foo" , "1" , "foo" , "2" } ,
} ,
2026-01-21 03:21:56 -05:00
{
name : "One target label collides with existing label, plus target label already with prefix 'exported'" ,
2021-10-15 14:31:03 -04:00
targetLabels : [ ] string { "foo" , "2" , "exported_foo" , "3" } ,
exposedLabels : ` metric { foo="1"} 0 ` ,
expected : [ ] string { "__name__" , "metric" , "exported_exported_foo" , "1" , "exported_foo" , "3" , "foo" , "2" } ,
} ,
2026-01-21 03:21:56 -05:00
{
name : "One target label collides with existing label, plus existing label already with prefix 'exported" ,
2021-10-15 14:31:03 -04:00
targetLabels : [ ] string { "foo" , "3" } ,
2024-02-15 14:25:12 -05:00
exposedLabels : ` metric { foo="1", exported_foo="2"} 0 ` ,
2021-10-15 14:31:03 -04:00
expected : [ ] string { "__name__" , "metric" , "exported_exported_foo" , "1" , "exported_foo" , "2" , "foo" , "3" } ,
} ,
2026-01-21 03:21:56 -05:00
{
name : "One target label collides with existing label, both already with prefix 'exported'" ,
2021-10-15 14:31:03 -04:00
targetLabels : [ ] string { "exported_foo" , "2" } ,
exposedLabels : ` metric { exported_foo="1"} 0 ` ,
expected : [ ] string { "__name__" , "metric" , "exported_exported_foo" , "1" , "exported_foo" , "2" } ,
} ,
2026-01-21 03:21:56 -05:00
{
name : "Two target labels collide with existing labels, both with and without prefix 'exported'" ,
2021-10-15 14:31:03 -04:00
targetLabels : [ ] string { "foo" , "3" , "exported_foo" , "4" } ,
2024-02-15 14:25:12 -05:00
exposedLabels : ` metric { foo="1", exported_foo="2"} 0 ` ,
2021-10-22 04:06:44 -04:00
expected : [ ] string {
"__name__" , "metric" , "exported_exported_foo" , "1" , "exported_exported_exported_foo" ,
"2" , "exported_foo" , "4" , "foo" , "3" ,
} ,
2021-10-15 14:31:03 -04:00
} ,
2026-01-21 03:21:56 -05:00
{
name : "Extreme example" ,
2021-10-15 14:31:03 -04:00
targetLabels : [ ] string { "foo" , "0" , "exported_exported_foo" , "1" , "exported_exported_exported_foo" , "2" } ,
2024-02-15 14:25:12 -05:00
exposedLabels : ` metric { foo="3", exported_foo="4", exported_exported_exported_foo="5"} 0 ` ,
2021-10-15 14:31:03 -04:00
expected : [ ] string {
"__name__" , "metric" ,
"exported_exported_exported_exported_exported_foo" , "5" ,
"exported_exported_exported_exported_foo" , "3" ,
"exported_exported_exported_foo" , "2" ,
"exported_exported_foo" , "1" ,
"exported_foo" , "4" ,
"foo" , "0" ,
} ,
} ,
2026-01-21 03:21:56 -05:00
} {
t . Run ( tc . name , func ( t * testing . T ) {
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . sampleMutator = func ( l labels . Labels ) labels . Labels {
return mutateSampleLabels ( l , & Target { labels : labels . FromStrings ( tc . targetLabels ... ) } , false , nil )
}
} )
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( tc . exposedLabels ) , "text/plain" , time . Date ( 2000 , 1 , 1 , 1 , 0 , 0 , 0 , time . UTC ) )
2021-10-15 14:31:03 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2021-10-15 14:31:03 -04:00
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , [ ] sample {
2021-10-15 14:31:03 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( tc . expected ... ) ,
T : timestamp . FromTime ( time . Date ( 2000 , 1 , 1 , 1 , 0 , 0 , 0 , time . UTC ) ) ,
V : 0 ,
2021-10-15 14:31:03 -04:00
} ,
2025-12-22 04:38:48 -05:00
} , appTest . ResultSamples ( ) )
2021-10-15 14:31:03 -04:00
} )
}
}
2020-03-25 22:31:48 -04:00
func TestScrapeLoopAppendCacheEntryButErrNotFound ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendCacheEntryButErrNotFound ( t , appV2 )
} )
}
func testScrapeLoopAppendCacheEntryButErrNotFound ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
2020-03-25 22:31:48 -04:00
2021-11-06 06:10:04 -04:00
fakeRef := storage . SeriesRef ( 1 )
2020-03-25 22:31:48 -04:00
expValue := float64 ( 1 )
2022-12-20 11:54:07 -05:00
metric := [ ] byte ( ` metric { n="1"} 1 ` )
2025-09-11 05:49:42 -04:00
p , warning := textparse . New ( metric , "text/plain" , labels . NewSymbolTable ( ) , textparse . ParserOptions { } )
2024-10-18 11:12:31 -04:00
require . NotNil ( t , p )
2022-02-08 04:57:56 -05:00
require . NoError ( t , warning )
2020-03-25 22:31:48 -04:00
var lset labels . Labels
2025-12-22 04:38:48 -05:00
_ , err := p . Next ( )
require . NoError ( t , err )
2025-02-12 10:47:56 -05:00
p . Labels ( & lset )
2020-03-25 22:31:48 -04:00
hash := lset . Hash ( )
// Create a fake entry in the cache
2022-12-20 11:54:07 -05:00
sl . cache . addRef ( metric , fakeRef , lset , hash )
2020-03-25 22:31:48 -04:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err = app . append ( metric , "text/plain" , now )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2020-03-25 22:31:48 -04:00
2025-12-22 04:38:48 -05:00
expected := [ ] sample {
2020-03-25 22:31:48 -04:00
{
2025-12-22 04:38:48 -05:00
L : lset ,
T : timestamp . FromTime ( now ) ,
V : expValue ,
2020-03-25 22:31:48 -04:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , expected , appTest . ResultSamples ( ) )
2020-03-25 22:31:48 -04:00
}
2025-12-22 04:38:48 -05:00
type appendableFunc func ( ctx context . Context ) storage . Appender
func ( a appendableFunc ) Appender ( ctx context . Context ) storage . Appender { return a ( ctx ) }
2018-01-09 10:43:28 -05:00
2025-12-22 04:38:48 -05:00
func TestScrapeLoopAppendSampleLimit ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendSampleLimit ( t , appV2 )
} )
}
func testScrapeLoopAppendSampleLimit ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
sl , _ := newTestScrapeLoop ( t , func ( sl * scrapeLoop ) {
2026-01-21 03:21:56 -05:00
if appV2 {
sl . appendableV2 = appendableV2Func ( func ( ctx context . Context ) storage . AppenderV2 {
// Chain appTest to verify what samples passed through.
return & limitAppenderV2 { AppenderV2 : appTest . AppenderV2 ( ctx ) , limit : 1 }
} )
} else {
sl . appendable = appendableFunc ( func ( ctx context . Context ) storage . Appender {
// Chain appTest to verify what samples passed through.
return & limitAppender { Appender : appTest . Appender ( ctx ) , limit : 1 }
} )
}
2025-12-22 04:38:48 -05:00
sl . sampleMutator = func ( l labels . Labels ) labels . Labels {
if l . Has ( "deleteme" ) {
return labels . EmptyLabels ( )
}
return l
2023-10-16 09:47:10 -04:00
}
2025-12-22 04:38:48 -05:00
sl . sampleLimit = 1 // Same as limitAppender.limit
} )
2018-01-09 10:43:28 -05:00
2025-12-22 04:38:48 -05:00
// Get the value of the Counter before performing append.
2018-01-09 10:43:28 -05:00
beforeMetric := dto . Metric { }
2023-09-22 12:47:44 -04:00
err := sl . metrics . targetScrapeSampleLimit . Write ( & beforeMetric )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2019-11-04 18:43:42 -05:00
2018-01-09 10:43:28 -05:00
beforeMetricValue := beforeMetric . GetCounter ( ) . GetValue ( )
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
total , added , seriesAdded , err := app . append ( [ ] byte ( "metric_a 1\nmetric_b 1\nmetric_c 1\n" ) , "text/plain" , now )
2021-09-04 08:35:03 -04:00
require . ErrorIs ( t , err , errSampleLimit )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Rollback ( ) )
2020-10-29 05:43:23 -04:00
require . Equal ( t , 3 , total )
require . Equal ( t , 3 , added )
require . Equal ( t , 1 , seriesAdded )
2018-01-09 10:43:28 -05:00
2019-02-10 04:46:20 -05:00
// Check that the Counter has been incremented a single time for the scrape,
2018-01-09 10:43:28 -05:00
// not multiple times for each sample.
metric := dto . Metric { }
2023-09-22 12:47:44 -04:00
err = sl . metrics . targetScrapeSampleLimit . Write ( & metric )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2019-11-04 18:43:42 -05:00
2025-12-22 04:38:48 -05:00
v := metric . GetCounter ( ) . GetValue ( )
change := v - beforeMetricValue
2020-10-29 05:43:23 -04:00
require . Equal ( t , 1.0 , change , "Unexpected change of sample limit metric: %f" , change )
2018-01-09 10:43:28 -05:00
// And verify that we got the samples that fit under the limit.
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2018-01-09 10:43:28 -05:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2018-01-09 10:43:28 -05:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . RolledbackSamples ( ) , "Appended samples not as expected:\n%s" , appTest )
2020-01-29 12:47:36 -05:00
now = time . Now ( )
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
total , added , seriesAdded , err = app . append ( [ ] byte ( "metric_a 1\nmetric_b 1\nmetric_c{deleteme=\"yes\"} 1\nmetric_d 1\nmetric_e 1\nmetric_f 1\nmetric_g 1\nmetric_h{deleteme=\"yes\"} 1\nmetric_i{deleteme=\"yes\"} 1\n" ) , "text/plain" , now )
2021-09-04 08:35:03 -04:00
require . ErrorIs ( t , err , errSampleLimit )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Rollback ( ) )
2020-10-29 05:43:23 -04:00
require . Equal ( t , 9 , total )
require . Equal ( t , 6 , added )
2025-12-22 04:38:48 -05:00
require . Equal ( t , 1 , seriesAdded )
2018-01-09 10:43:28 -05:00
}
2023-04-21 15:14:19 -04:00
func TestScrapeLoop_HistogramBucketLimit ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopHistogramBucketLimit ( t , appV2 )
} )
}
func testScrapeLoopHistogramBucketLimit ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
sl , _ := newTestScrapeLoop ( t , func ( sl * scrapeLoop ) {
2026-01-21 03:21:56 -05:00
if appV2 {
sl . appendableV2 = appendableV2Func ( func ( ctx context . Context ) storage . AppenderV2 {
return & bucketLimitAppenderV2 { AppenderV2 : teststorage . NewAppendable ( ) . AppenderV2 ( ctx ) , limit : 2 }
} )
} else {
sl . appendable = appendableFunc ( func ( ctx context . Context ) storage . Appender {
return & bucketLimitAppender { Appender : teststorage . NewAppendable ( ) . Appender ( ctx ) , limit : 2 }
} )
}
2025-12-22 04:38:48 -05:00
sl . enableNativeHistogramScraping = true
sl . sampleMutator = func ( l labels . Labels ) labels . Labels {
if l . Has ( "deleteme" ) {
return labels . EmptyLabels ( )
}
return l
2023-10-16 09:47:10 -04:00
}
2025-12-22 04:38:48 -05:00
} )
app := sl . appender ( )
2023-04-21 15:14:19 -04:00
metric := dto . Metric { }
2023-09-22 12:47:44 -04:00
err := sl . metrics . targetScrapeNativeHistogramBucketLimit . Write ( & metric )
2023-04-21 15:14:19 -04:00
require . NoError ( t , err )
beforeMetricValue := metric . GetCounter ( ) . GetValue ( )
2023-04-24 13:41:04 -04:00
nativeHistogram := prometheus . NewHistogramVec (
prometheus . HistogramOpts {
Namespace : "testing" ,
Name : "example_native_histogram" ,
Help : "This is used for testing" ,
ConstLabels : map [ string ] string { "some" : "value" } ,
NativeHistogramBucketFactor : 1.1 , // 10% increase from bucket to bucket
NativeHistogramMaxBucketNumber : 100 , // intentionally higher than the limit we'll use in the scraper
} ,
[ ] string { "size" } ,
)
2023-04-21 15:14:19 -04:00
registry := prometheus . NewRegistry ( )
2025-12-22 04:38:48 -05:00
require . NoError ( t , registry . Register ( nativeHistogram ) )
2023-04-24 13:41:04 -04:00
nativeHistogram . WithLabelValues ( "S" ) . Observe ( 1.0 )
nativeHistogram . WithLabelValues ( "M" ) . Observe ( 1.0 )
nativeHistogram . WithLabelValues ( "L" ) . Observe ( 1.0 )
nativeHistogram . WithLabelValues ( "M" ) . Observe ( 10.0 )
nativeHistogram . WithLabelValues ( "L" ) . Observe ( 10.0 ) // in different bucket since > 1*1.1
2023-04-21 15:14:19 -04:00
gathered , err := registry . Gather ( )
require . NoError ( t , err )
require . NotEmpty ( t , gathered )
histogramMetricFamily := gathered [ 0 ]
2023-05-04 02:36:44 -04:00
msg , err := MetricFamilyToProtobuf ( histogramMetricFamily )
2023-04-21 15:14:19 -04:00
require . NoError ( t , err )
now := time . Now ( )
2025-12-22 04:38:48 -05:00
total , added , seriesAdded , err := app . append ( msg , "application/vnd.google.protobuf" , now )
2023-04-21 15:14:19 -04:00
require . NoError ( t , err )
2023-04-24 13:41:04 -04:00
require . Equal ( t , 3 , total )
require . Equal ( t , 3 , added )
require . Equal ( t , 3 , seriesAdded )
2023-04-21 15:14:19 -04:00
2023-09-22 12:47:44 -04:00
err = sl . metrics . targetScrapeNativeHistogramBucketLimit . Write ( & metric )
2023-04-21 15:14:19 -04:00
require . NoError ( t , err )
metricValue := metric . GetCounter ( ) . GetValue ( )
require . Equal ( t , beforeMetricValue , metricValue )
beforeMetricValue = metricValue
2023-04-24 13:41:04 -04:00
nativeHistogram . WithLabelValues ( "L" ) . Observe ( 100.0 ) // in different bucket since > 10*1.1
2023-04-21 15:14:19 -04:00
gathered , err = registry . Gather ( )
require . NoError ( t , err )
require . NotEmpty ( t , gathered )
histogramMetricFamily = gathered [ 0 ]
2023-05-04 02:36:44 -04:00
msg , err = MetricFamilyToProtobuf ( histogramMetricFamily )
2023-04-21 15:14:19 -04:00
require . NoError ( t , err )
now = time . Now ( )
2025-12-22 04:38:48 -05:00
total , added , seriesAdded , err = app . append ( msg , "application/vnd.google.protobuf" , now )
2023-11-21 03:56:56 -05:00
require . NoError ( t , err )
require . Equal ( t , 3 , total )
require . Equal ( t , 3 , added )
2025-12-22 04:38:48 -05:00
require . Equal ( t , 0 , seriesAdded ) // Series are cached.
2023-11-21 03:56:56 -05:00
err = sl . metrics . targetScrapeNativeHistogramBucketLimit . Write ( & metric )
require . NoError ( t , err )
metricValue = metric . GetCounter ( ) . GetValue ( )
require . Equal ( t , beforeMetricValue , metricValue )
beforeMetricValue = metricValue
nativeHistogram . WithLabelValues ( "L" ) . Observe ( 100000.0 ) // in different bucket since > 10*1.1
gathered , err = registry . Gather ( )
require . NoError ( t , err )
require . NotEmpty ( t , gathered )
histogramMetricFamily = gathered [ 0 ]
msg , err = MetricFamilyToProtobuf ( histogramMetricFamily )
require . NoError ( t , err )
2023-04-21 15:14:19 -04:00
now = time . Now ( )
2025-12-22 04:38:48 -05:00
total , added , seriesAdded , err = app . append ( msg , "application/vnd.google.protobuf" , now )
2023-11-01 15:06:46 -04:00
if ! errors . Is ( err , errBucketLimit ) {
2023-04-21 15:14:19 -04:00
t . Fatalf ( "Did not see expected histogram bucket limit error: %s" , err )
}
require . NoError ( t , app . Rollback ( ) )
2023-04-24 13:41:04 -04:00
require . Equal ( t , 3 , total )
require . Equal ( t , 3 , added )
2025-12-22 04:38:48 -05:00
require . Equal ( t , 0 , seriesAdded ) // Series are cached.
2023-04-21 15:14:19 -04:00
2023-09-22 12:47:44 -04:00
err = sl . metrics . targetScrapeNativeHistogramBucketLimit . Write ( & metric )
2023-04-21 15:14:19 -04:00
require . NoError ( t , err )
metricValue = metric . GetCounter ( ) . GetValue ( )
require . Equal ( t , beforeMetricValue + 1 , metricValue )
}
2017-09-15 05:08:51 -04:00
func TestScrapeLoop_ChangingMetricString ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopChangingMetricString ( t , appV2 )
} )
}
func testScrapeLoopChangingMetricString ( t * testing . T , appV2 bool ) {
2017-09-15 05:08:51 -04:00
// This is a regression test for the scrape loop cache not properly maintaining
2025-12-22 04:38:48 -05:00
// IDs when the string representation of a metric changes across a scrape. Thus,
2017-09-15 05:08:51 -04:00
// we use a real storage appender here.
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
2017-09-15 05:08:51 -04:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( ` metric_a { a="1",b="1"} 1 ` ) , "text/plain" , now )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2019-11-04 18:43:42 -05:00
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
_ , _ , _ , err = app . append ( [ ] byte ( ` metric_a { b="1",a="1"} 2 ` ) , "text/plain" , now . Add ( time . Minute ) )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2017-09-15 05:08:51 -04:00
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2017-09-15 05:08:51 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( "__name__" , "metric_a" , "a" , "1" , "b" , "1" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2017-09-15 05:08:51 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( "__name__" , "metric_a" , "a" , "1" , "b" , "1" ) ,
T : timestamp . FromTime ( now . Add ( time . Minute ) ) ,
V : 2 ,
2017-09-15 05:08:51 -04:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , appTest )
2017-09-15 05:08:51 -04:00
}
2024-11-07 06:30:03 -05:00
func TestScrapeLoopAppendFailsWithNoContentType ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendFailsWithNoContentType ( t , appV2 )
} )
}
func testScrapeLoopAppendFailsWithNoContentType ( t * testing . T , appV2 bool ) {
sl , _ := newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
// Explicitly setting the lack of fallback protocol here to make it obvious.
sl . fallbackScrapeProtocol = ""
} )
2024-11-07 06:30:03 -05:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( "metric_a 1\n" ) , "" , now )
// We expected the appropriate error.
2024-11-07 06:30:03 -05:00
require . ErrorContains ( t , err , "non-compliant scrape target sending blank Content-Type and no fallback_scrape_protocol specified for target" , "Expected \"non-compliant scrape\" error but got: %s" , err )
}
2025-12-22 04:38:48 -05:00
// TestScrapeLoopAppendEmptyWithNoContentType ensures we there are no errors when we get a blank scrape or just want to append a stale marker.
2024-11-07 06:30:03 -05:00
func TestScrapeLoopAppendEmptyWithNoContentType ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendEmptyWithNoContentType ( t , appV2 )
} )
}
func testScrapeLoopAppendEmptyWithNoContentType ( t * testing . T , appV2 bool ) {
sl , _ := newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
// Explicitly setting the lack of fallback protocol here to make it obvious.
sl . fallbackScrapeProtocol = ""
} )
2024-11-07 06:30:03 -05:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( "" ) , "" , now )
2024-11-07 06:30:03 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2024-11-07 06:30:03 -05:00
}
2017-04-14 05:41:18 -04:00
func TestScrapeLoopAppendStaleness ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendStaleness ( t , appV2 )
} )
}
func testScrapeLoopAppendStaleness ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
2017-04-14 05:41:18 -04:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( "metric_a 1\n" ) , "text/plain" , now )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2019-11-04 18:43:42 -05:00
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
_ , _ , _ , err = app . append ( [ ] byte ( "" ) , "" , now . Add ( time . Second ) )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2017-04-14 05:41:18 -04:00
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2017-04-14 05:41:18 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2017-04-14 05:41:18 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" ) ,
T : timestamp . FromTime ( now . Add ( time . Second ) ) ,
V : math . Float64frombits ( value . StaleNaN ) ,
2017-04-14 05:41:18 -04:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , appTest )
2017-04-11 10:42:17 -04:00
}
2017-04-28 11:36:36 -04:00
func TestScrapeLoopAppendNoStalenessIfTimestamp ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendNoStalenessIfTimestamp ( t , appV2 )
} )
}
func testScrapeLoopAppendNoStalenessIfTimestamp ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
2017-04-28 11:36:36 -04:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( "metric_a 1 1000\n" ) , "text/plain" , now )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2019-11-04 18:43:42 -05:00
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
_ , _ , _ , err = app . append ( [ ] byte ( "" ) , "" , now . Add ( time . Second ) )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2017-04-28 11:36:36 -04:00
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2017-04-28 11:36:36 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" ) ,
T : 1000 ,
V : 1 ,
2017-04-28 11:36:36 -04:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , appTest )
2017-05-29 09:08:55 -04:00
}
2023-11-09 11:18:00 -05:00
func TestScrapeLoopAppendStalenessIfTrackTimestampStaleness ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendStalenessIfTrackTimestampStaleness ( t , appV2 )
} )
}
func testScrapeLoopAppendStalenessIfTrackTimestampStaleness ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . trackTimestampsStaleness = true
} )
2023-11-09 11:18:00 -05:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( "metric_a 1 1000\n" ) , "text/plain" , now )
2023-11-09 11:18:00 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2023-11-09 11:18:00 -05:00
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
_ , _ , _ , err = app . append ( [ ] byte ( "" ) , "" , now . Add ( time . Second ) )
2023-11-09 11:18:00 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2023-11-09 11:18:00 -05:00
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2023-11-09 11:18:00 -05:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" ) ,
T : 1000 ,
V : 1 ,
2023-11-09 11:18:00 -05:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" ) ,
T : timestamp . FromTime ( now . Add ( time . Second ) ) ,
V : math . Float64frombits ( value . StaleNaN ) ,
2023-11-09 11:18:00 -05:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , appTest )
2023-11-09 11:18:00 -05:00
}
2026-01-21 03:21:56 -05:00
// TestScrapeLoopAppend is the main table test testing the scrape appends, including histograms, exemplar and metadata.
func TestScrapeLoopAppend ( t * testing . T ) {
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppend ( t , appV2 )
} )
}
func testScrapeLoopAppend ( t * testing . T , appV2 bool ) {
for _ , test := range [ ] struct {
2024-04-24 09:53:54 -04:00
title string
2024-10-18 03:32:15 -04:00
alwaysScrapeClassicHist bool
2024-04-24 09:53:54 -04:00
enableNativeHistogramsIngestion bool
scrapeText string
contentType string
discoveryLabels [ ] string
2025-12-22 04:38:48 -05:00
samples [ ] sample
2021-03-16 05:47:45 -04:00
} {
2026-01-21 03:21:56 -05:00
{
title : "Normal NaN scraped" ,
scrapeText : "metric_total{n=\"1\"} NaN\n# EOF" ,
contentType : "application/openmetrics-text" ,
samples : [ ] sample { {
L : labels . FromStrings ( "__name__" , "metric_total" , "n" , "1" ) ,
V : math . Float64frombits ( value . NormalNaN ) ,
} } ,
} ,
2021-03-16 05:47:45 -04:00
{
title : "Metric without exemplars" ,
scrapeText : "metric_total{n=\"1\"} 0\n# EOF" ,
2023-07-13 08:16:10 -04:00
contentType : "application/openmetrics-text" ,
2021-03-16 05:47:45 -04:00
discoveryLabels : [ ] string { "n" , "2" } ,
2025-12-22 04:38:48 -05:00
samples : [ ] sample { {
L : labels . FromStrings ( "__name__" , "metric_total" , "exported_n" , "1" , "n" , "2" ) ,
V : 0 ,
2021-03-16 05:47:45 -04:00
} } ,
} ,
{
title : "Metric with exemplars" ,
scrapeText : "metric_total{n=\"1\"} 0 # {a=\"abc\"} 1.0\n# EOF" ,
2023-07-13 08:16:10 -04:00
contentType : "application/openmetrics-text" ,
2021-03-16 05:47:45 -04:00
discoveryLabels : [ ] string { "n" , "2" } ,
2025-12-22 04:38:48 -05:00
samples : [ ] sample { {
L : labels . FromStrings ( "__name__" , "metric_total" , "exported_n" , "1" , "n" , "2" ) ,
V : 0 ,
ES : [ ] exemplar . Exemplar {
{ Labels : labels . FromStrings ( "a" , "abc" ) , Value : 1 } ,
} ,
2021-03-16 05:47:45 -04:00
} } ,
2021-10-22 04:06:44 -04:00
} ,
{
2021-03-16 05:47:45 -04:00
title : "Metric with exemplars and TS" ,
scrapeText : "metric_total{n=\"1\"} 0 # {a=\"abc\"} 1.0 10000\n# EOF" ,
2023-07-13 08:16:10 -04:00
contentType : "application/openmetrics-text" ,
2021-03-16 05:47:45 -04:00
discoveryLabels : [ ] string { "n" , "2" } ,
2025-12-22 04:38:48 -05:00
samples : [ ] sample { {
L : labels . FromStrings ( "__name__" , "metric_total" , "exported_n" , "1" , "n" , "2" ) ,
V : 0 ,
ES : [ ] exemplar . Exemplar { { Labels : labels . FromStrings ( "a" , "abc" ) , Value : 1 , Ts : 10000000 , HasTs : true } } ,
2021-03-16 05:47:45 -04:00
} } ,
2021-10-22 04:06:44 -04:00
} ,
{
2021-03-16 05:47:45 -04:00
title : "Two metrics and exemplars" ,
scrapeText : ` metric_total { n = "1" } 1 # { t = "1" } 1.0 10000
metric_total { n = "2" } 2 # { t = "2" } 2.0 20000
# EOF ` ,
2023-07-13 08:16:10 -04:00
contentType : "application/openmetrics-text" ,
2025-12-22 04:38:48 -05:00
samples : [ ] sample { {
L : labels . FromStrings ( "__name__" , "metric_total" , "n" , "1" ) ,
V : 1 ,
ES : [ ] exemplar . Exemplar { { Labels : labels . FromStrings ( "t" , "1" ) , Value : 1 , Ts : 10000000 , HasTs : true } } ,
2021-03-16 05:47:45 -04:00
} , {
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( "__name__" , "metric_total" , "n" , "2" ) ,
V : 2 ,
ES : [ ] exemplar . Exemplar { { Labels : labels . FromStrings ( "t" , "2" ) , Value : 2 , Ts : 20000000 , HasTs : true } } ,
2021-03-16 05:47:45 -04:00
} } ,
} ,
2023-07-13 08:16:10 -04:00
{
2025-03-17 09:07:07 -04:00
title : "Native histogram with three exemplars from classic buckets" ,
2024-04-24 09:53:54 -04:00
enableNativeHistogramsIngestion : true ,
2023-07-13 08:16:10 -04:00
scrapeText : ` name : "test_histogram"
help : "Test histogram with many buckets removed to keep it manageable in size."
type : HISTOGRAM
metric : <
histogram : <
sample_count : 175
sample_sum : 0.0008280461746287094
bucket : <
cumulative_count : 2
upper_bound : - 0.0004899999999999998
>
bucket : <
cumulative_count : 4
upper_bound : - 0.0003899999999999998
exemplar : <
label : <
name : "dummyID"
value : "59727"
>
value : - 0.00039
timestamp : <
seconds : 1625851155
nanos : 146848499
>
>
>
bucket : <
cumulative_count : 16
upper_bound : - 0.0002899999999999998
exemplar : <
label : <
name : "dummyID"
value : "5617"
>
value : - 0.00029
>
>
2023-11-16 09:07:37 -05:00
bucket : <
cumulative_count : 32
upper_bound : - 0.0001899999999999998
exemplar : <
label : <
name : "dummyID"
value : "58215"
>
value : - 0.00019
timestamp : <
seconds : 1625851055
nanos : 146848599
>
>
>
2023-07-13 08:16:10 -04:00
schema : 3
zero_threshold : 2.938735877055719e-39
zero_count : 2
negative_span : <
offset : - 162
length : 1
>
negative_span : <
offset : 23
length : 4
>
negative_delta : 1
negative_delta : 3
negative_delta : - 2
negative_delta : - 1
negative_delta : 1
positive_span : <
offset : - 161
length : 1
>
positive_span : <
offset : 8
length : 3
>
positive_delta : 1
positive_delta : 2
positive_delta : - 1
positive_delta : - 1
>
timestamp_ms : 1234568
>
` ,
contentType : "application/vnd.google.protobuf" ,
2025-12-22 04:38:48 -05:00
samples : [ ] sample { {
T : 1234568 ,
L : labels . FromStrings ( "__name__" , "test_histogram" ) ,
H : & histogram . Histogram {
2023-07-13 08:16:10 -04:00
Count : 175 ,
ZeroCount : 2 ,
Sum : 0.0008280461746287094 ,
ZeroThreshold : 2.938735877055719e-39 ,
Schema : 3 ,
PositiveSpans : [ ] histogram . Span {
{ Offset : - 161 , Length : 1 } ,
{ Offset : 8 , Length : 3 } ,
} ,
NegativeSpans : [ ] histogram . Span {
{ Offset : - 162 , Length : 1 } ,
{ Offset : 23 , Length : 4 } ,
} ,
PositiveBuckets : [ ] int64 { 1 , 2 , - 1 , - 1 } ,
NegativeBuckets : [ ] int64 { 1 , 3 , - 2 , - 1 , 1 } ,
} ,
2025-12-22 04:38:48 -05:00
ES : [ ] exemplar . Exemplar {
// Native histogram exemplars are arranged by timestamp, and those with missing timestamps are dropped.
{ Labels : labels . FromStrings ( "dummyID" , "58215" ) , Value : - 0.00019 , Ts : 1625851055146 , HasTs : true } ,
{ Labels : labels . FromStrings ( "dummyID" , "59727" ) , Value : - 0.00039 , Ts : 1625851155146 , HasTs : true } ,
} ,
2023-07-13 08:16:10 -04:00
} } ,
} ,
2023-08-21 07:12:45 -04:00
{
2023-11-16 09:07:37 -05:00
title : "Native histogram with three exemplars scraped as classic histogram" ,
2024-04-24 09:53:54 -04:00
enableNativeHistogramsIngestion : true ,
2023-08-21 07:12:45 -04:00
scrapeText : ` name : "test_histogram"
help : "Test histogram with many buckets removed to keep it manageable in size."
type : HISTOGRAM
metric : <
histogram : <
sample_count : 175
sample_sum : 0.0008280461746287094
bucket : <
cumulative_count : 2
upper_bound : - 0.0004899999999999998
>
bucket : <
cumulative_count : 4
upper_bound : - 0.0003899999999999998
exemplar : <
label : <
name : "dummyID"
value : "59727"
>
value : - 0.00039
timestamp : <
seconds : 1625851155
nanos : 146848499
>
>
>
bucket : <
cumulative_count : 16
upper_bound : - 0.0002899999999999998
exemplar : <
label : <
name : "dummyID"
value : "5617"
>
value : - 0.00029
>
>
2023-11-16 09:07:37 -05:00
bucket : <
cumulative_count : 32
upper_bound : - 0.0001899999999999998
exemplar : <
label : <
name : "dummyID"
value : "58215"
>
value : - 0.00019
timestamp : <
seconds : 1625851055
nanos : 146848599
>
>
>
2023-08-21 07:12:45 -04:00
schema : 3
zero_threshold : 2.938735877055719e-39
zero_count : 2
negative_span : <
offset : - 162
length : 1
>
negative_span : <
offset : 23
length : 4
>
negative_delta : 1
negative_delta : 3
negative_delta : - 2
negative_delta : - 1
negative_delta : 1
positive_span : <
offset : - 161
length : 1
>
positive_span : <
offset : 8
length : 3
>
positive_delta : 1
positive_delta : 2
positive_delta : - 1
positive_delta : - 1
>
timestamp_ms : 1234568
>
` ,
2024-10-18 03:32:15 -04:00
alwaysScrapeClassicHist : true ,
2023-08-21 07:12:45 -04:00
contentType : "application/vnd.google.protobuf" ,
2025-12-22 04:38:48 -05:00
samples : [ ] sample {
{
T : 1234568 ,
L : labels . FromStrings ( "__name__" , "test_histogram" ) ,
H : & histogram . Histogram {
Count : 175 ,
ZeroCount : 2 ,
Sum : 0.0008280461746287094 ,
ZeroThreshold : 2.938735877055719e-39 ,
Schema : 3 ,
PositiveSpans : [ ] histogram . Span {
{ Offset : - 161 , Length : 1 } ,
{ Offset : 8 , Length : 3 } ,
} ,
NegativeSpans : [ ] histogram . Span {
{ Offset : - 162 , Length : 1 } ,
{ Offset : 23 , Length : 4 } ,
} ,
PositiveBuckets : [ ] int64 { 1 , 2 , - 1 , - 1 } ,
NegativeBuckets : [ ] int64 { 1 , 3 , - 2 , - 1 , 1 } ,
2023-08-21 07:12:45 -04:00
} ,
2025-12-22 04:38:48 -05:00
ES : [ ] exemplar . Exemplar {
// Native histogram one is arranged by timestamp.
// Exemplars with missing timestamps are dropped for native histograms.
{ Labels : labels . FromStrings ( "dummyID" , "58215" ) , Value : - 0.00019 , Ts : 1625851055146 , HasTs : true } ,
{ Labels : labels . FromStrings ( "dummyID" , "59727" ) , Value : - 0.00039 , Ts : 1625851155146 , HasTs : true } ,
2023-08-21 07:12:45 -04:00
} ,
} ,
2026-01-21 03:21:56 -05:00
{ L : labels . FromStrings ( "__name__" , "test_histogram_count" ) , T : 1234568 , V : 175 } ,
{ L : labels . FromStrings ( "__name__" , "test_histogram_sum" ) , T : 1234568 , V : 0.0008280461746287094 } ,
{ L : labels . FromStrings ( "__name__" , "test_histogram_bucket" , "le" , "-0.0004899999999999998" ) , T : 1234568 , V : 2 } ,
{
L : labels . FromStrings ( "__name__" , "test_histogram_bucket" , "le" , "-0.0003899999999999998" ) , T : 1234568 , V : 4 ,
ES : [ ] exemplar . Exemplar { { Labels : labels . FromStrings ( "dummyID" , "59727" ) , Value : - 0.00039 , Ts : 1625851155146 , HasTs : true } } ,
} ,
{
L : labels . FromStrings ( "__name__" , "test_histogram_bucket" , "le" , "-0.0002899999999999998" ) , T : 1234568 , V : 16 ,
ES : [ ] exemplar . Exemplar { { Labels : labels . FromStrings ( "dummyID" , "5617" ) , Value : - 0.00029 , Ts : 1234568 , HasTs : false } } ,
} ,
{
L : labels . FromStrings ( "__name__" , "test_histogram_bucket" , "le" , "-0.0001899999999999998" ) , T : 1234568 , V : 32 ,
ES : [ ] exemplar . Exemplar { { Labels : labels . FromStrings ( "dummyID" , "58215" ) , Value : - 0.00019 , Ts : 1625851055146 , HasTs : true } } ,
} ,
{ L : labels . FromStrings ( "__name__" , "test_histogram_bucket" , "le" , "+Inf" ) , T : 1234568 , V : 175 } ,
2023-08-21 07:12:45 -04:00
} ,
} ,
2025-03-17 09:07:07 -04:00
{
title : "Native histogram with exemplars and no classic buckets" ,
contentType : "application/vnd.google.protobuf" ,
enableNativeHistogramsIngestion : true ,
scrapeText : ` name : "test_histogram"
help : "Test histogram."
type : HISTOGRAM
metric : <
histogram : <
sample_count : 175
sample_sum : 0.0008280461746287094
schema : 3
zero_threshold : 2.938735877055719e-39
zero_count : 2
negative_span : <
offset : - 162
length : 1
>
negative_span : <
offset : 23
length : 4
>
negative_delta : 1
negative_delta : 3
negative_delta : - 2
negative_delta : - 1
negative_delta : 1
positive_span : <
offset : - 161
length : 1
>
positive_span : <
offset : 8
length : 3
>
positive_delta : 1
positive_delta : 2
positive_delta : - 1
positive_delta : - 1
exemplars : <
label : <
name : "dummyID"
value : "59732"
>
value : - 0.00039
timestamp : <
seconds : 1625851155
nanos : 146848499
>
>
exemplars : <
label : <
name : "dummyID"
value : "58242"
>
value : - 0.00019
timestamp : <
seconds : 1625851055
nanos : 146848599
>
>
exemplars : <
label : <
name : "dummyID"
value : "5617"
>
value : - 0.00029
>
>
timestamp_ms : 1234568
>
` ,
2025-12-22 04:38:48 -05:00
samples : [ ] sample { {
T : 1234568 ,
L : labels . FromStrings ( "__name__" , "test_histogram" ) ,
H : & histogram . Histogram {
2025-03-17 09:07:07 -04:00
Count : 175 ,
ZeroCount : 2 ,
Sum : 0.0008280461746287094 ,
ZeroThreshold : 2.938735877055719e-39 ,
Schema : 3 ,
PositiveSpans : [ ] histogram . Span {
{ Offset : - 161 , Length : 1 } ,
{ Offset : 8 , Length : 3 } ,
} ,
NegativeSpans : [ ] histogram . Span {
{ Offset : - 162 , Length : 1 } ,
{ Offset : 23 , Length : 4 } ,
} ,
PositiveBuckets : [ ] int64 { 1 , 2 , - 1 , - 1 } ,
NegativeBuckets : [ ] int64 { 1 , 3 , - 2 , - 1 , 1 } ,
} ,
2025-12-22 04:38:48 -05:00
ES : [ ] exemplar . Exemplar {
// Exemplars with missing timestamps are dropped for native histograms.
{ Labels : labels . FromStrings ( "dummyID" , "58242" ) , Value : - 0.00019 , Ts : 1625851055146 , HasTs : true } ,
{ Labels : labels . FromStrings ( "dummyID" , "59732" ) , Value : - 0.00039 , Ts : 1625851155146 , HasTs : true } ,
} ,
2025-03-17 09:07:07 -04:00
} } ,
} ,
{
title : "Native histogram with exemplars but ingestion disabled" ,
contentType : "application/vnd.google.protobuf" ,
enableNativeHistogramsIngestion : false ,
scrapeText : ` name : "test_histogram"
help : "Test histogram."
type : HISTOGRAM
metric : <
histogram : <
sample_count : 175
sample_sum : 0.0008280461746287094
schema : 3
zero_threshold : 2.938735877055719e-39
zero_count : 2
negative_span : <
offset : - 162
length : 1
>
negative_span : <
offset : 23
length : 4
>
negative_delta : 1
negative_delta : 3
negative_delta : - 2
negative_delta : - 1
negative_delta : 1
positive_span : <
offset : - 161
length : 1
>
positive_span : <
offset : 8
length : 3
>
positive_delta : 1
positive_delta : 2
positive_delta : - 1
positive_delta : - 1
exemplars : <
label : <
name : "dummyID"
value : "59732"
>
value : - 0.00039
timestamp : <
seconds : 1625851155
nanos : 146848499
>
>
exemplars : <
label : <
name : "dummyID"
value : "58242"
>
value : - 0.00019
timestamp : <
seconds : 1625851055
nanos : 146848599
>
>
exemplars : <
label : <
name : "dummyID"
value : "5617"
>
value : - 0.00029
>
>
timestamp_ms : 1234568
>
` ,
2025-12-22 04:38:48 -05:00
samples : [ ] sample {
{ L : labels . FromStrings ( "__name__" , "test_histogram_count" ) , T : 1234568 , V : 175 } ,
{ L : labels . FromStrings ( "__name__" , "test_histogram_sum" ) , T : 1234568 , V : 0.0008280461746287094 } ,
{ L : labels . FromStrings ( "__name__" , "test_histogram_bucket" , "le" , "+Inf" ) , T : 1234568 , V : 175 } ,
2025-10-09 10:56:13 -04:00
} ,
2025-03-17 09:07:07 -04:00
} ,
2026-01-21 03:21:56 -05:00
} {
2021-03-16 05:47:45 -04:00
t . Run ( test . title , func ( t * testing . T ) {
discoveryLabels := & Target {
labels : labels . FromStrings ( test . discoveryLabels ... ) ,
}
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . enableNativeHistogramScraping = test . enableNativeHistogramsIngestion
sl . sampleMutator = func ( l labels . Labels ) labels . Labels {
return mutateSampleLabels ( l , discoveryLabels , false , nil )
}
sl . reportSampleMutator = func ( l labels . Labels ) labels . Labels {
return mutateReportSampleLabels ( l , discoveryLabels )
}
sl . alwaysScrapeClassicHist = test . alwaysScrapeClassicHist
2026-01-21 03:21:56 -05:00
// This test does not care about metadata.
2026-02-02 07:44:11 -05:00
// Having this true would mean we need to add metadata to sample
// expectations.
// TODO(bwplotka): Add cases for append metadata to WAL and pass metadata
2025-12-22 04:38:48 -05:00
sl . appendMetadataToWAL = false
} )
app := sl . appender ( )
2021-03-16 05:47:45 -04:00
now := time . Now ( )
2026-01-21 03:21:56 -05:00
// Process expected samples.
2025-12-22 04:38:48 -05:00
for i := range test . samples {
2026-01-21 03:21:56 -05:00
if ! appV2 && test . samples [ i ] . MF != "" {
// AppenderV1 does not support metric family passing.
test . samples [ i ] . MF = ""
}
2025-12-22 04:38:48 -05:00
if test . samples [ i ] . T != 0 {
2023-08-21 07:55:13 -04:00
continue
}
2025-12-22 04:38:48 -05:00
test . samples [ i ] . T = timestamp . FromTime ( now )
2021-03-16 05:47:45 -04:00
2025-12-22 04:38:48 -05:00
// We need to set the timestamp for expected exemplars that does not have a timestamp.
for j := range test . samples [ i ] . ES {
if test . samples [ i ] . ES [ j ] . Ts == 0 {
test . samples [ i ] . ES [ j ] . Ts = timestamp . FromTime ( now )
}
2021-03-16 05:47:45 -04:00
}
}
2023-07-13 08:16:10 -04:00
buf := & bytes . Buffer { }
if test . contentType == "application/vnd.google.protobuf" {
2024-08-20 15:12:02 -04:00
require . NoError ( t , textToProto ( test . scrapeText , buf ) )
2023-07-13 08:16:10 -04:00
} else {
buf . WriteString ( test . scrapeText )
}
2025-12-22 04:38:48 -05:00
_ , _ , _ , err := app . append ( buf . Bytes ( ) , test . contentType , now )
2021-03-16 05:47:45 -04:00
require . NoError ( t , err )
require . NoError ( t , app . Commit ( ) )
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , test . samples , appTest . ResultSamples ( ) )
2021-03-16 05:47:45 -04:00
} )
}
}
2024-08-20 15:12:02 -04:00
func textToProto ( text string , buf * bytes . Buffer ) error {
// In case of protobuf, we have to create the binary representation.
pb := & dto . MetricFamily { }
// From text to proto message.
err := proto . UnmarshalText ( text , pb )
if err != nil {
return err
}
// From proto message to binary protobuf.
protoBuf , err := proto . Marshal ( pb )
if err != nil {
return err
}
// Write first length, then binary protobuf.
varintBuf := binary . AppendUvarint ( nil , uint64 ( len ( protoBuf ) ) )
buf . Write ( varintBuf )
buf . Write ( protoBuf )
return nil
}
2021-03-16 05:47:45 -04:00
func TestScrapeLoopAppendExemplarSeries ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendExemplarSeries ( t , appV2 )
} )
}
func testScrapeLoopAppendExemplarSeries ( t * testing . T , appV2 bool ) {
2021-03-16 05:47:45 -04:00
scrapeText := [ ] string { ` metric_total { n = "1" } 1 # { t = "1" } 1.0 10000
# EOF ` , ` metric_total { n = "1" } 2 # { t = "2" } 2.0 20000
# EOF ` }
2025-12-22 04:38:48 -05:00
samples := [ ] sample { {
L : labels . FromStrings ( "__name__" , "metric_total" , "n" , "1" ) ,
V : 1 ,
ES : [ ] exemplar . Exemplar {
{ Labels : labels . FromStrings ( "t" , "1" ) , Value : 1 , Ts : 10000000 , HasTs : true } ,
} ,
2021-03-16 05:47:45 -04:00
} , {
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( "__name__" , "metric_total" , "n" , "1" ) ,
V : 2 ,
ES : [ ] exemplar . Exemplar {
{ Labels : labels . FromStrings ( "t" , "2" ) , Value : 2 , Ts : 20000000 , HasTs : true } ,
} ,
2021-03-16 05:47:45 -04:00
} }
discoveryLabels := & Target {
labels : labels . FromStrings ( ) ,
}
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . sampleMutator = func ( l labels . Labels ) labels . Labels {
return mutateSampleLabels ( l , discoveryLabels , false , nil )
}
sl . reportSampleMutator = func ( l labels . Labels ) labels . Labels {
return mutateReportSampleLabels ( l , discoveryLabels )
}
// This test does not care about metadata. Having this true would mean we need to add metadata to sample
// expectations.
sl . appendMetadataToWAL = false
} )
2021-03-16 05:47:45 -04:00
now := time . Now ( )
for i := range samples {
ts := now . Add ( time . Second * time . Duration ( i ) )
2025-12-22 04:38:48 -05:00
samples [ i ] . T = timestamp . FromTime ( ts )
2021-03-16 05:47:45 -04:00
}
for i , st := range scrapeText {
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( st ) , "application/openmetrics-text" , timestamp . Time ( samples [ i ] . T ) )
2021-03-16 05:47:45 -04:00
require . NoError ( t , err )
require . NoError ( t , app . Commit ( ) )
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , samples , appTest . ResultSamples ( ) )
2021-03-16 05:47:45 -04:00
}
2017-06-14 22:08:03 -04:00
func TestScrapeLoopRunReportsTargetDownOnScrapeError ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopRunReportsTargetDownOnScrapeError ( t , appV2 )
} )
}
func testScrapeLoopRunReportsTargetDownOnScrapeError ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
ctx , cancel := context . WithCancel ( t . Context ( ) )
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . ctx = ctx
} )
2025-08-04 02:20:57 -04:00
scraper . scrapeFunc = func ( context . Context , io . Writer ) error {
2017-06-14 22:08:03 -04:00
cancel ( )
2019-03-25 19:01:12 -04:00
return errors . New ( "scrape failed" )
2017-06-14 22:08:03 -04:00
}
2021-08-31 11:37:32 -04:00
sl . run ( nil )
2025-12-22 04:38:48 -05:00
require . Equal ( t , 0.0 , appTest . ResultSamples ( ) [ 0 ] . V , "bad 'up' value" )
2017-06-14 22:08:03 -04:00
}
2017-06-16 08:09:50 -04:00
func TestScrapeLoopRunReportsTargetDownOnInvalidUTF8 ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopRunReportsTargetDownOnInvalidUTF8 ( t , appV2 )
} )
}
func testScrapeLoopRunReportsTargetDownOnInvalidUTF8 ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
ctx , cancel := context . WithCancel ( t . Context ( ) )
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . ctx = ctx
} )
2025-02-10 02:06:58 -05:00
scraper . scrapeFunc = func ( _ context . Context , w io . Writer ) error {
2017-06-16 08:09:50 -04:00
cancel ( )
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "a{l=\"\xff\"} 1\n" ) )
2017-06-16 08:09:50 -04:00
return nil
}
2021-08-31 11:37:32 -04:00
sl . run ( nil )
2025-12-22 04:38:48 -05:00
require . Equal ( t , 0.0 , appTest . ResultSamples ( ) [ 0 ] . V , "bad 'up' value" )
2017-05-03 12:20:07 -04:00
}
2017-07-04 08:55:33 -04:00
func TestScrapeLoopAppendGracefullyIfAmendOrOutOfOrderOrOutOfBounds ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendGracefullyIfAmendOrOutOfOrderOrOutOfBounds ( t , appV2 )
} )
}
func testScrapeLoopAppendGracefullyIfAmendOrOutOfOrderOrOutOfBounds ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( ) . WithErrs (
func ( ls labels . Labels ) error {
switch ls . Get ( model . MetricNameLabel ) {
case "out_of_order" :
return storage . ErrOutOfOrderSample
case "amend" :
return storage . ErrDuplicateSampleForTimestamp
case "out_of_bounds" :
return storage . ErrOutOfBounds
default :
return nil
}
} , nil , nil )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
2017-05-03 12:20:07 -04:00
now := time . Unix ( 1 , 0 )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
total , added , seriesAdded , err := app . append ( [ ] byte ( "out_of_order 1\namend 1\nnormal 1\nout_of_bounds 1\n" ) , "text/plain" , now )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2019-11-04 18:43:42 -05:00
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2017-05-03 12:20:07 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "normal" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2017-05-03 12:20:07 -04:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , appTest )
2020-10-29 05:43:23 -04:00
require . Equal ( t , 4 , total )
require . Equal ( t , 4 , added )
require . Equal ( t , 1 , seriesAdded )
2017-07-04 08:55:33 -04:00
}
2017-05-03 12:20:07 -04:00
2017-07-04 08:55:33 -04:00
func TestScrapeLoopOutOfBoundsTimeError ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopOutOfBoundsTimeError ( t , appV2 )
} )
}
func testScrapeLoopOutOfBoundsTimeError ( t * testing . T , appV2 bool ) {
sl , _ := newTestScrapeLoop ( t , func ( sl * scrapeLoop ) {
if appV2 {
sl . appendableV2 = appendableV2Func ( func ( ctx context . Context ) storage . AppenderV2 {
return & timeLimitAppenderV2 {
AppenderV2 : teststorage . NewAppendable ( ) . AppenderV2 ( ctx ) ,
maxTime : timestamp . FromTime ( time . Now ( ) . Add ( 10 * time . Minute ) ) ,
}
} )
} else {
sl . appendable = appendableFunc ( func ( ctx context . Context ) storage . Appender {
return & timeLimitAppender {
Appender : teststorage . NewAppendable ( ) . Appender ( ctx ) ,
maxTime : timestamp . FromTime ( time . Now ( ) . Add ( 10 * time . Minute ) ) ,
}
} )
}
} )
2017-07-04 08:55:33 -04:00
now := time . Now ( ) . Add ( 20 * time . Minute )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
total , added , seriesAdded , err := app . append ( [ ] byte ( "normal 1\n" ) , "text/plain" , now )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2020-10-29 05:43:23 -04:00
require . Equal ( t , 1 , total )
require . Equal ( t , 1 , added )
require . Equal ( t , 0 , seriesAdded )
2017-05-03 12:20:07 -04:00
}
2025-03-26 18:27:28 -04:00
func TestAcceptHeader ( t * testing . T ) {
tests := [ ] struct {
name string
scrapeProtocols [ ] config . ScrapeProtocol
scheme model . EscapingScheme
expectedHeader string
} {
{
name : "default scrape protocols with underscore escaping" ,
scrapeProtocols : config . DefaultScrapeProtocols ,
scheme : model . UnderscoreEscaping ,
expectedHeader : "application/openmetrics-text;version=1.0.0;escaping=underscores;q=0.6,application/openmetrics-text;version=0.0.1;q=0.5,text/plain;version=1.0.0;escaping=underscores;q=0.4,text/plain;version=0.0.4;q=0.3,*/*;q=0.2" ,
} ,
{
name : "default proto first scrape protocols with underscore escaping" ,
scrapeProtocols : config . DefaultProtoFirstScrapeProtocols ,
scheme : model . DotsEscaping ,
expectedHeader : "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.6,application/openmetrics-text;version=1.0.0;escaping=dots;q=0.5,application/openmetrics-text;version=0.0.1;q=0.4,text/plain;version=1.0.0;escaping=dots;q=0.3,text/plain;version=0.0.4;q=0.2,*/*;q=0.1" ,
} ,
{
name : "default scrape protocols with no escaping" ,
scrapeProtocols : config . DefaultScrapeProtocols ,
scheme : model . NoEscaping ,
expectedHeader : "application/openmetrics-text;version=1.0.0;escaping=allow-utf-8;q=0.6,application/openmetrics-text;version=0.0.1;q=0.5,text/plain;version=1.0.0;escaping=allow-utf-8;q=0.4,text/plain;version=0.0.4;q=0.3,*/*;q=0.2" ,
} ,
{
name : "default proto first scrape protocols with no escaping" ,
scrapeProtocols : config . DefaultProtoFirstScrapeProtocols ,
scheme : model . NoEscaping ,
expectedHeader : "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.6,application/openmetrics-text;version=1.0.0;escaping=allow-utf-8;q=0.5,application/openmetrics-text;version=0.0.1;q=0.4,text/plain;version=1.0.0;escaping=allow-utf-8;q=0.3,text/plain;version=0.0.4;q=0.2,*/*;q=0.1" ,
} ,
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
header := acceptHeader ( tc . scrapeProtocols , tc . scheme )
require . Equal ( t , tc . expectedHeader , header )
} )
}
}
2025-07-15 03:37:24 -04:00
// setupTracing temporarily sets the global TracerProvider and Propagator
// and restores the original state after the test completes.
func setupTracing ( t * testing . T ) {
t . Helper ( )
origTracerProvider := otel . GetTracerProvider ( )
origPropagator := otel . GetTextMapPropagator ( )
tp := sdktrace . NewTracerProvider ( sdktrace . WithSampler ( sdktrace . AlwaysSample ( ) ) )
otel . SetTracerProvider ( tp )
otel . SetTextMapPropagator ( propagation . TraceContext { } )
t . Cleanup ( func ( ) {
otel . SetTracerProvider ( origTracerProvider )
otel . SetTextMapPropagator ( origPropagator )
} )
}
// TestRequestTraceparentHeader verifies that the HTTP client used by the target scraper
// propagates the OpenTelemetry "traceparent" header correctly.
func TestRequestTraceparentHeader ( t * testing . T ) {
setupTracing ( t )
server := httptest . NewServer ( http . HandlerFunc ( func ( _ http . ResponseWriter , r * http . Request ) {
// the traceparent header is sent.
require . NotEmpty ( t , r . Header . Get ( "traceparent" ) )
} ) )
defer server . Close ( )
serverURL , err := url . Parse ( server . URL )
require . NoError ( t , err )
client , err := newScrapeClient ( config_util . DefaultHTTPClientConfig , "test" )
require . NoError ( t , err )
ts := & targetScraper {
Target : & Target {
labels : labels . FromStrings (
model . SchemeLabel , serverURL . Scheme ,
model . AddressLabel , serverURL . Host ,
) ,
scrapeConfig : & config . ScrapeConfig { } ,
} ,
client : client ,
}
resp , err := ts . scrape ( context . Background ( ) )
require . NoError ( t , err )
require . NotNil ( t , resp )
2025-12-22 04:38:48 -05:00
t . Cleanup ( func ( ) { _ = resp . Body . Close ( ) } )
2025-07-15 03:37:24 -04:00
}
2016-02-28 17:59:03 -05:00
func TestTargetScraperScrapeOK ( t * testing . T ) {
2017-04-04 13:26:28 -04:00
const (
configTimeout = 1500 * time . Millisecond
2021-06-18 03:38:12 -04:00
expectedTimeout = "1.5"
2017-04-04 13:26:28 -04:00
)
2024-09-06 08:02:44 -04:00
var (
protobufParsing bool
allowUTF8 bool
qValuePattern = regexp . MustCompile ( ` q=([0-9]+(\.\d+)?) ` )
)
2022-10-12 03:48:25 -04:00
2016-02-28 17:59:03 -05:00
server := httptest . NewServer (
http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2024-07-18 14:08:21 -04:00
accept := r . Header . Get ( "Accept" )
if allowUTF8 {
2024-11-20 11:22:20 -05:00
require . Containsf ( t , accept , "escaping=allow-utf-8" , "Expected Accept header to allow utf8, got %q" , accept )
2025-02-20 06:22:23 -05:00
} else {
require . NotContainsf ( t , accept , "escaping=allow-utf-8" , "Expected Accept header to not allow utf8, got %q" , accept )
2024-07-18 14:08:21 -04:00
}
2022-10-12 03:48:25 -04:00
if protobufParsing {
2021-09-04 08:35:03 -04:00
require . True ( t , strings . HasPrefix ( accept , "application/vnd.google.protobuf;" ) ,
"Expected Accept header to prefer application/vnd.google.protobuf." )
2017-09-22 12:06:43 -04:00
}
2025-10-09 11:06:28 -04:00
contentTypes := strings . SplitSeq ( accept , "," )
2025-11-13 09:17:51 -05:00
for st := range contentTypes {
match := qValuePattern . FindStringSubmatch ( st )
2024-09-06 08:02:44 -04:00
require . Len ( t , match , 3 )
qValue , err := strconv . ParseFloat ( match [ 1 ] , 64 )
require . NoError ( t , err , "Error parsing q value" )
require . GreaterOrEqual ( t , qValue , float64 ( 0 ) )
require . LessOrEqual ( t , qValue , float64 ( 1 ) )
require . LessOrEqual ( t , len ( strings . Split ( match [ 1 ] , "." ) [ 1 ] ) , 3 , "q value should have at most 3 decimal places" )
}
2017-04-05 14:56:22 -04:00
timeout := r . Header . Get ( "X-Prometheus-Scrape-Timeout-Seconds" )
2021-09-04 08:35:03 -04:00
require . Equal ( t , expectedTimeout , timeout , "Expected scrape timeout header." )
2017-04-04 13:26:28 -04:00
2024-07-18 14:08:21 -04:00
if allowUTF8 {
w . Header ( ) . Set ( "Content-Type" , ` text/plain; version=1.0.0; escaping=allow-utf-8 ` )
} else {
w . Header ( ) . Set ( "Content-Type" , ` text/plain; version=0.0.4 ` )
}
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "metric_a 1\nmetric_b 2\n" ) )
2016-02-28 17:59:03 -05:00
} ) ,
)
defer server . Close ( )
serverURL , err := url . Parse ( server . URL )
if err != nil {
panic ( err )
}
2025-02-20 06:22:23 -05:00
runTest := func ( t * testing . T , acceptHeader string ) {
2022-10-12 03:48:25 -04:00
ts := & targetScraper {
Target : & Target {
labels : labels . FromStrings (
model . SchemeLabel , serverURL . Scheme ,
model . AddressLabel , serverURL . Host ,
) ,
2024-12-21 08:33:08 -05:00
scrapeConfig : & config . ScrapeConfig { } ,
2022-10-12 03:48:25 -04:00
} ,
client : http . DefaultClient ,
timeout : configTimeout ,
acceptHeader : acceptHeader ,
}
var buf bytes . Buffer
2023-10-09 12:23:53 -04:00
resp , err := ts . scrape ( context . Background ( ) )
require . NoError ( t , err )
contentType , err := ts . readResponse ( context . Background ( ) , resp , & buf )
2022-10-12 03:48:25 -04:00
require . NoError ( t , err )
2024-07-18 14:08:21 -04:00
if allowUTF8 {
require . Equal ( t , "text/plain; version=1.0.0; escaping=allow-utf-8" , contentType )
} else {
require . Equal ( t , "text/plain; version=0.0.4" , contentType )
}
2022-10-12 03:48:25 -04:00
require . Equal ( t , "metric_a 1\nmetric_b 2\n" , buf . String ( ) )
2016-02-28 17:59:03 -05:00
}
2025-02-20 06:22:23 -05:00
for _ , tc := range [ ] struct {
scrapeProtocols [ ] config . ScrapeProtocol
2025-03-26 18:27:28 -04:00
scheme model . EscapingScheme
2025-02-20 06:22:23 -05:00
protobufParsing bool
allowUTF8 bool
} {
{
scrapeProtocols : config . DefaultScrapeProtocols ,
2025-03-26 18:27:28 -04:00
scheme : model . UnderscoreEscaping ,
2025-02-20 06:22:23 -05:00
protobufParsing : false ,
allowUTF8 : false ,
} ,
{
scrapeProtocols : config . DefaultProtoFirstScrapeProtocols ,
2025-03-26 18:27:28 -04:00
scheme : model . UnderscoreEscaping ,
2025-02-20 06:22:23 -05:00
protobufParsing : true ,
allowUTF8 : false ,
} ,
{
scrapeProtocols : config . DefaultScrapeProtocols ,
2025-03-26 18:27:28 -04:00
scheme : model . NoEscaping ,
2025-02-20 06:22:23 -05:00
protobufParsing : false ,
allowUTF8 : true ,
} ,
{
scrapeProtocols : config . DefaultProtoFirstScrapeProtocols ,
2025-03-26 18:27:28 -04:00
scheme : model . NoEscaping ,
2025-02-20 06:22:23 -05:00
protobufParsing : true ,
allowUTF8 : true ,
} ,
} {
t . Run ( fmt . Sprintf ( "%+v" , tc ) , func ( t * testing . T ) {
protobufParsing = tc . protobufParsing
allowUTF8 = tc . allowUTF8
runTest ( t , acceptHeader ( tc . scrapeProtocols , tc . scheme ) )
} )
}
2016-02-28 17:59:03 -05:00
}
func TestTargetScrapeScrapeCancel ( t * testing . T ) {
block := make ( chan struct { } )
server := httptest . NewServer (
2025-08-04 02:20:57 -04:00
http . HandlerFunc ( func ( http . ResponseWriter , * http . Request ) {
2016-02-28 17:59:03 -05:00
<- block
} ) ,
)
defer server . Close ( )
serverURL , err := url . Parse ( server . URL )
if err != nil {
panic ( err )
}
ts := & targetScraper {
Target : & Target {
2016-12-29 03:27:30 -05:00
labels : labels . FromStrings (
model . SchemeLabel , serverURL . Scheme ,
model . AddressLabel , serverURL . Host ,
) ,
2024-12-21 08:33:08 -05:00
scrapeConfig : & config . ScrapeConfig { } ,
2016-02-28 17:59:03 -05:00
} ,
2022-10-12 03:48:25 -04:00
client : http . DefaultClient ,
2025-03-26 18:27:28 -04:00
acceptHeader : acceptHeader ( config . DefaultGlobalConfig . ScrapeProtocols , model . UnderscoreEscaping ) ,
2016-02-28 17:59:03 -05:00
}
ctx , cancel := context . WithCancel ( context . Background ( ) )
2020-02-13 02:53:07 -05:00
errc := make ( chan error , 1 )
2016-02-28 17:59:03 -05:00
go func ( ) {
time . Sleep ( 1 * time . Second )
cancel ( )
} ( )
go func ( ) {
2023-10-09 12:23:53 -04:00
_ , err := ts . scrape ( ctx )
2023-04-09 03:08:40 -04:00
switch {
case err == nil :
2025-12-22 04:38:48 -05:00
errc <- errors . New ( "expected error but got nil" )
2023-11-01 15:06:46 -04:00
case ! errors . Is ( ctx . Err ( ) , context . Canceled ) :
2025-12-22 04:38:48 -05:00
errc <- fmt . Errorf ( "expected context cancellation error but got: %w" , ctx . Err ( ) )
2023-04-09 03:08:40 -04:00
default :
2020-02-13 02:53:07 -05:00
close ( errc )
2016-02-28 17:59:03 -05:00
}
} ( )
select {
case <- time . After ( 5 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Scrape function did not return unexpectedly." )
2016-11-13 12:21:42 -05:00
case err := <- errc :
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2016-02-28 17:59:03 -05:00
}
// If this is closed in a defer above the function the test server
2018-04-27 08:04:02 -04:00
// doesn't terminate and the test doesn't complete.
2016-02-28 17:59:03 -05:00
close ( block )
}
func TestTargetScrapeScrapeNotFound ( t * testing . T ) {
server := httptest . NewServer (
2025-02-10 02:06:58 -05:00
http . HandlerFunc ( func ( w http . ResponseWriter , _ * http . Request ) {
2016-02-28 17:59:03 -05:00
w . WriteHeader ( http . StatusNotFound )
} ) ,
)
defer server . Close ( )
serverURL , err := url . Parse ( server . URL )
if err != nil {
panic ( err )
}
ts := & targetScraper {
Target : & Target {
2016-12-29 03:27:30 -05:00
labels : labels . FromStrings (
model . SchemeLabel , serverURL . Scheme ,
model . AddressLabel , serverURL . Host ,
) ,
2024-12-21 08:33:08 -05:00
scrapeConfig : & config . ScrapeConfig { } ,
2016-02-28 17:59:03 -05:00
} ,
2022-10-12 03:48:25 -04:00
client : http . DefaultClient ,
2025-03-26 18:27:28 -04:00
acceptHeader : acceptHeader ( config . DefaultGlobalConfig . ScrapeProtocols , model . UnderscoreEscaping ) ,
2016-02-28 17:59:03 -05:00
}
2023-10-09 12:23:53 -04:00
resp , err := ts . scrape ( context . Background ( ) )
require . NoError ( t , err )
_ , err = ts . readResponse ( context . Background ( ) , resp , io . Discard )
2024-10-06 12:35:29 -04:00
require . ErrorContains ( t , err , "404" , "Expected \"404 NotFound\" error but got: %s" , err )
2016-02-28 17:59:03 -05:00
}
2021-05-15 22:19:22 -04:00
func TestTargetScraperBodySizeLimit ( t * testing . T ) {
const (
bodySizeLimit = 15
responseBody = "metric_a 1\nmetric_b 2\n"
)
var gzipResponse bool
server := httptest . NewServer (
2025-02-10 02:06:58 -05:00
http . HandlerFunc ( func ( w http . ResponseWriter , _ * http . Request ) {
2021-05-15 22:19:22 -04:00
w . Header ( ) . Set ( "Content-Type" , ` text/plain; version=0.0.4 ` )
if gzipResponse {
w . Header ( ) . Set ( "Content-Encoding" , "gzip" )
gw := gzip . NewWriter ( w )
2025-12-22 04:38:48 -05:00
defer func ( ) { _ = gw . Close ( ) } ( )
_ , _ = gw . Write ( [ ] byte ( responseBody ) )
2021-05-15 22:19:22 -04:00
return
}
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( responseBody ) )
2021-05-15 22:19:22 -04:00
} ) ,
)
defer server . Close ( )
serverURL , err := url . Parse ( server . URL )
if err != nil {
panic ( err )
}
ts := & targetScraper {
Target : & Target {
labels : labels . FromStrings (
model . SchemeLabel , serverURL . Scheme ,
model . AddressLabel , serverURL . Host ,
) ,
2024-12-21 08:33:08 -05:00
scrapeConfig : & config . ScrapeConfig { } ,
2021-05-15 22:19:22 -04:00
} ,
client : http . DefaultClient ,
bodySizeLimit : bodySizeLimit ,
2025-03-26 18:27:28 -04:00
acceptHeader : acceptHeader ( config . DefaultGlobalConfig . ScrapeProtocols , model . UnderscoreEscaping ) ,
2023-09-22 12:47:44 -04:00
metrics : newTestScrapeMetrics ( t ) ,
2021-05-15 22:19:22 -04:00
}
var buf bytes . Buffer
// Target response uncompressed body, scrape with body size limit.
2023-10-09 12:23:53 -04:00
resp , err := ts . scrape ( context . Background ( ) )
require . NoError ( t , err )
_ , err = ts . readResponse ( context . Background ( ) , resp , & buf )
2021-05-15 22:19:22 -04:00
require . ErrorIs ( t , err , errBodySizeLimit )
require . Equal ( t , bodySizeLimit , buf . Len ( ) )
// Target response gzip compressed body, scrape with body size limit.
gzipResponse = true
buf . Reset ( )
2023-10-09 12:23:53 -04:00
resp , err = ts . scrape ( context . Background ( ) )
require . NoError ( t , err )
_ , err = ts . readResponse ( context . Background ( ) , resp , & buf )
2021-05-15 22:19:22 -04:00
require . ErrorIs ( t , err , errBodySizeLimit )
require . Equal ( t , bodySizeLimit , buf . Len ( ) )
// Target response uncompressed body, scrape without body size limit.
gzipResponse = false
buf . Reset ( )
ts . bodySizeLimit = 0
2023-10-09 12:23:53 -04:00
resp , err = ts . scrape ( context . Background ( ) )
require . NoError ( t , err )
_ , err = ts . readResponse ( context . Background ( ) , resp , & buf )
2021-05-15 22:19:22 -04:00
require . NoError ( t , err )
2023-12-07 06:35:01 -05:00
require . Len ( t , responseBody , buf . Len ( ) )
2021-05-15 22:19:22 -04:00
// Target response gzip compressed body, scrape without body size limit.
gzipResponse = true
buf . Reset ( )
2023-10-09 12:23:53 -04:00
resp , err = ts . scrape ( context . Background ( ) )
require . NoError ( t , err )
_ , err = ts . readResponse ( context . Background ( ) , resp , & buf )
2021-05-15 22:19:22 -04:00
require . NoError ( t , err )
2023-12-07 06:35:01 -05:00
require . Len ( t , responseBody , buf . Len ( ) )
2021-05-15 22:19:22 -04:00
}
2016-02-23 04:58:16 -05:00
// testScraper implements the scraper interface and allows setting values
// returned by its methods. It also allows setting a custom scrape function.
type testScraper struct {
offsetDur time . Duration
lastStart time . Time
lastDuration time . Duration
lastError error
scrapeErr error
2017-01-15 11:33:07 -05:00
scrapeFunc func ( context . Context , io . Writer ) error
2016-02-23 04:58:16 -05:00
}
2023-04-12 07:05:41 -04:00
func ( ts * testScraper ) offset ( time . Duration , uint64 ) time . Duration {
2016-02-23 04:58:16 -05:00
return ts . offsetDur
}
2019-11-11 16:42:24 -05:00
func ( ts * testScraper ) Report ( start time . Time , duration time . Duration , err error ) {
2016-02-23 04:58:16 -05:00
ts . lastStart = start
ts . lastDuration = duration
ts . lastError = err
}
2025-08-04 02:20:57 -04:00
func ( ts * testScraper ) scrape ( context . Context ) ( * http . Response , error ) {
2023-10-09 12:23:53 -04:00
return nil , ts . scrapeErr
}
2025-02-10 02:06:58 -05:00
func ( ts * testScraper ) readResponse ( ctx context . Context , _ * http . Response , w io . Writer ) ( string , error ) {
2016-02-23 04:58:16 -05:00
if ts . scrapeFunc != nil {
2018-10-04 09:52:03 -04:00
return "" , ts . scrapeFunc ( ctx , w )
2016-02-23 04:58:16 -05:00
}
2018-10-04 09:52:03 -04:00
return "" , ts . scrapeErr
2016-02-23 04:58:16 -05:00
}
2019-03-15 06:04:15 -04:00
func TestScrapeLoop_RespectTimestamps ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopRespectTimestamps ( t , appV2 )
} )
}
func testScrapeLoopRespectTimestamps ( t * testing . T , appV2 bool ) {
2019-08-08 21:35:39 -04:00
s := teststorage . New ( t )
2019-03-15 06:04:15 -04:00
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( ) . Then ( s )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
2019-03-15 06:04:15 -04:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( ` metric_a { a="1",b="1"} 1 0 ` ) , "text/plain" , now )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2019-03-15 06:04:15 -04:00
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2019-03-15 06:04:15 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( "__name__" , "metric_a" , "a" , "1" , "b" , "1" ) ,
T : 0 ,
V : 1 ,
2019-03-15 06:04:15 -04:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , appTest )
2019-03-15 06:04:15 -04:00
}
func TestScrapeLoop_DiscardTimestamps ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopDiscardTimestamps ( t , appV2 )
} )
}
func testScrapeLoopDiscardTimestamps ( t * testing . T , appV2 bool ) {
2019-08-08 21:35:39 -04:00
s := teststorage . New ( t )
2019-03-15 06:04:15 -04:00
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( ) . Then ( s )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . honorTimestamps = false
} )
2019-03-15 06:04:15 -04:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( ` metric_a { a="1",b="1"} 1 0 ` ) , "text/plain" , now )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2019-03-15 06:04:15 -04:00
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2019-03-15 06:04:15 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( "__name__" , "metric_a" , "a" , "1" , "b" , "1" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2019-03-15 06:04:15 -04:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , appTest )
2019-03-15 06:04:15 -04:00
}
2020-01-20 06:05:27 -05:00
func TestScrapeLoopDiscardDuplicateLabels ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopDiscardDuplicateLabels ( t , appV2 )
} )
}
func testScrapeLoopDiscardDuplicateLabels ( t * testing . T , appV2 bool ) {
2020-01-20 06:05:27 -05:00
s := teststorage . New ( t )
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( ) . Then ( s )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
2020-01-20 06:05:27 -05:00
// We add a good and a bad metric to check that both are discarded.
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( "test_metric{le=\"500\"} 1\ntest_metric{le=\"600\",le=\"700\"} 1\n" ) , "text/plain" , time . Time { } )
2020-10-29 05:43:23 -04:00
require . Error ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Rollback ( ) )
// We need to cycle staleness cache maps after a manual rollback. Otherwise, they will have old entries in them,
2023-11-16 09:35:44 -05:00
// which would cause ErrDuplicateSampleForTimestamp errors on the next append.
sl . cache . iterDone ( true )
2020-01-20 06:05:27 -05:00
2023-09-12 06:37:38 -04:00
q , err := s . Querier ( time . Time { } . UnixNano ( ) , 0 )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
series := q . Select ( sl . ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , ".*" ) )
2023-12-07 06:35:01 -05:00
require . False ( t , series . Next ( ) , "series found in tsdb" )
2020-10-29 05:43:23 -04:00
require . NoError ( t , series . Err ( ) )
2020-01-20 06:05:27 -05:00
// We add a good metric to check that it is recorded.
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
_ , _ , _ , err = app . append ( [ ] byte ( "test_metric{le=\"500\"} 1\n" ) , "text/plain" , time . Time { } )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2020-01-20 06:05:27 -05:00
2023-09-12 06:37:38 -04:00
q , err = s . Querier ( time . Time { } . UnixNano ( ) , 0 )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
series = q . Select ( sl . ctx , false , nil , labels . MustNewMatcher ( labels . MatchEqual , "le" , "500" ) )
2023-12-07 06:35:01 -05:00
require . True ( t , series . Next ( ) , "series not found in tsdb" )
2020-10-29 05:43:23 -04:00
require . NoError ( t , series . Err ( ) )
2023-12-07 06:35:01 -05:00
require . False ( t , series . Next ( ) , "more than one series found in tsdb" )
2020-01-20 06:05:27 -05:00
}
2020-01-22 07:13:47 -05:00
2020-03-02 02:18:05 -05:00
func TestScrapeLoopDiscardUnnamedMetrics ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopDiscardUnnamedMetrics ( t , appV2 )
} )
}
func testScrapeLoopDiscardUnnamedMetrics ( t * testing . T , appV2 bool ) {
2020-03-02 02:18:05 -05:00
s := teststorage . New ( t )
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( ) . Then ( s )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . sampleMutator = func ( l labels . Labels ) labels . Labels {
if l . Has ( "drop" ) {
return labels . FromStrings ( "no" , "name" ) // This label set will trigger an error.
}
return l
2023-10-16 09:47:10 -04:00
}
2025-12-22 04:38:48 -05:00
} )
2020-03-02 02:18:05 -05:00
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( "nok 1\nnok2{drop=\"drop\"} 1\n" ) , "text/plain" , time . Time { } )
2020-10-29 05:43:23 -04:00
require . Error ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Rollback ( ) )
2020-10-29 05:43:23 -04:00
require . Equal ( t , errNameLabelMandatory , err )
2020-03-02 02:18:05 -05:00
2023-09-12 06:37:38 -04:00
q , err := s . Querier ( time . Time { } . UnixNano ( ) , 0 )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
series := q . Select ( sl . ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , ".*" ) )
2023-12-07 06:35:01 -05:00
require . False ( t , series . Next ( ) , "series found in tsdb" )
2020-10-29 05:43:23 -04:00
require . NoError ( t , series . Err ( ) )
2020-03-02 02:18:05 -05:00
}
2020-01-22 07:13:47 -05:00
func TestReusableConfig ( t * testing . T ) {
variants := [ ] * config . ScrapeConfig {
2020-04-15 06:17:41 -04:00
{
2020-01-22 07:13:47 -05:00
JobName : "prometheus" ,
ScrapeTimeout : model . Duration ( 15 * time . Second ) ,
} ,
2020-04-15 06:17:41 -04:00
{
2020-01-22 07:13:47 -05:00
JobName : "httpd" ,
ScrapeTimeout : model . Duration ( 15 * time . Second ) ,
} ,
2020-04-15 06:17:41 -04:00
{
2020-01-22 07:13:47 -05:00
JobName : "prometheus" ,
ScrapeTimeout : model . Duration ( 5 * time . Second ) ,
} ,
2020-04-15 06:17:41 -04:00
{
2020-01-22 07:13:47 -05:00
JobName : "prometheus" ,
MetricsPath : "/metrics" ,
} ,
2020-04-15 06:17:41 -04:00
{
2020-01-22 07:13:47 -05:00
JobName : "prometheus" ,
MetricsPath : "/metrics2" ,
} ,
2020-04-15 06:17:41 -04:00
{
2020-01-22 07:13:47 -05:00
JobName : "prometheus" ,
ScrapeTimeout : model . Duration ( 5 * time . Second ) ,
MetricsPath : "/metrics2" ,
} ,
2020-04-15 06:17:41 -04:00
{
2020-01-22 07:13:47 -05:00
JobName : "prometheus" ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
MetricsPath : "/metrics2" ,
} ,
2020-04-15 06:17:41 -04:00
{
2020-01-22 07:13:47 -05:00
JobName : "prometheus" ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
SampleLimit : 1000 ,
MetricsPath : "/metrics2" ,
} ,
}
match := [ ] [ ] int {
2020-04-15 06:17:41 -04:00
{ 0 , 2 } ,
{ 4 , 5 } ,
{ 4 , 6 } ,
{ 4 , 7 } ,
{ 5 , 6 } ,
{ 5 , 7 } ,
{ 6 , 7 } ,
2020-01-22 07:13:47 -05:00
}
noMatch := [ ] [ ] int {
2020-04-15 06:17:41 -04:00
{ 1 , 2 } ,
{ 0 , 4 } ,
{ 3 , 4 } ,
2020-01-22 07:13:47 -05:00
}
for i , m := range match {
2023-12-07 06:35:01 -05:00
require . True ( t , reusableCache ( variants [ m [ 0 ] ] , variants [ m [ 1 ] ] ) , "match test %d" , i )
require . True ( t , reusableCache ( variants [ m [ 1 ] ] , variants [ m [ 0 ] ] ) , "match test %d" , i )
require . True ( t , reusableCache ( variants [ m [ 1 ] ] , variants [ m [ 1 ] ] ) , "match test %d" , i )
require . True ( t , reusableCache ( variants [ m [ 0 ] ] , variants [ m [ 0 ] ] ) , "match test %d" , i )
2020-01-22 07:13:47 -05:00
}
for i , m := range noMatch {
2023-12-07 06:35:01 -05:00
require . False ( t , reusableCache ( variants [ m [ 0 ] ] , variants [ m [ 1 ] ] ) , "not match test %d" , i )
require . False ( t , reusableCache ( variants [ m [ 1 ] ] , variants [ m [ 0 ] ] ) , "not match test %d" , i )
2020-01-22 07:13:47 -05:00
}
}
func TestReuseScrapeCache ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testReuseScrapeCache ( t , appV2 )
} )
}
func testReuseScrapeCache ( t * testing . T , appV2 bool ) {
2020-01-22 07:13:47 -05:00
var (
2025-12-22 04:38:48 -05:00
app = teststorage . NewAppendable ( )
2020-01-22 07:13:47 -05:00
cfg = & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
ScrapeTimeout : model . Duration ( 5 * time . Second ) ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
MetricsPath : "/metrics" ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2020-01-22 07:13:47 -05:00
}
2026-01-21 03:21:56 -05:00
sa = selectAppendable ( app , appV2 )
sp , _ = newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2020-01-22 07:13:47 -05:00
t1 = & Target {
2025-03-26 18:27:28 -04:00
labels : labels . FromStrings ( "labelNew" , "nameNew" , "labelNew1" , "nameNew1" , "labelNew2" , "nameNew2" ) ,
scrapeConfig : & config . ScrapeConfig {
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
} ,
2020-01-22 07:13:47 -05:00
}
proxyURL , _ = url . Parse ( "http://localhost:2128" )
)
2020-07-27 04:38:08 -04:00
defer sp . stop ( )
2020-01-22 07:13:47 -05:00
sp . sync ( [ ] * Target { t1 } )
steps := [ ] struct {
keep bool
newConfig * config . ScrapeConfig
} {
{
keep : true ,
newConfig : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
ScrapeTimeout : model . Duration ( 5 * time . Second ) ,
MetricsPath : "/metrics" ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2020-01-22 07:13:47 -05:00
} ,
} ,
{
keep : false ,
newConfig : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
ScrapeTimeout : model . Duration ( 15 * time . Second ) ,
MetricsPath : "/metrics2" ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2020-01-22 07:13:47 -05:00
} ,
} ,
{
keep : true ,
newConfig : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
SampleLimit : 400 ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
ScrapeTimeout : model . Duration ( 15 * time . Second ) ,
MetricsPath : "/metrics2" ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2020-01-22 07:13:47 -05:00
} ,
} ,
{
keep : false ,
newConfig : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
HonorTimestamps : true ,
SampleLimit : 400 ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
ScrapeTimeout : model . Duration ( 15 * time . Second ) ,
MetricsPath : "/metrics2" ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2020-01-22 07:13:47 -05:00
} ,
} ,
{
keep : true ,
newConfig : & config . ScrapeConfig {
JobName : "Prometheus" ,
HonorTimestamps : true ,
SampleLimit : 400 ,
HTTPClientConfig : config_util . HTTPClientConfig {
2023-03-08 05:44:15 -05:00
ProxyConfig : config_util . ProxyConfig { ProxyURL : config_util . URL { URL : proxyURL } } ,
2020-01-22 07:13:47 -05:00
} ,
2025-03-26 18:27:28 -04:00
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
ScrapeTimeout : model . Duration ( 15 * time . Second ) ,
MetricsPath : "/metrics2" ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2020-01-22 07:13:47 -05:00
} ,
} ,
{
keep : false ,
newConfig : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
HonorTimestamps : true ,
HonorLabels : true ,
SampleLimit : 400 ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
ScrapeTimeout : model . Duration ( 15 * time . Second ) ,
MetricsPath : "/metrics2" ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2020-01-22 07:13:47 -05:00
} ,
} ,
2022-03-03 12:37:53 -05:00
{
keep : false ,
newConfig : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
ScrapeTimeout : model . Duration ( 15 * time . Second ) ,
MetricsPath : "/metrics" ,
LabelLimit : 1 ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2022-03-03 12:37:53 -05:00
} ,
} ,
{
keep : false ,
newConfig : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
ScrapeTimeout : model . Duration ( 15 * time . Second ) ,
MetricsPath : "/metrics" ,
LabelLimit : 15 ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2022-03-03 12:37:53 -05:00
} ,
} ,
{
keep : false ,
newConfig : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
ScrapeTimeout : model . Duration ( 15 * time . Second ) ,
MetricsPath : "/metrics" ,
LabelLimit : 15 ,
LabelNameLengthLimit : 5 ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2022-03-03 12:37:53 -05:00
} ,
} ,
{
keep : false ,
newConfig : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
ScrapeTimeout : model . Duration ( 15 * time . Second ) ,
MetricsPath : "/metrics" ,
LabelLimit : 15 ,
LabelNameLengthLimit : 5 ,
LabelValueLengthLimit : 7 ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2022-03-03 12:37:53 -05:00
} ,
} ,
2020-01-22 07:13:47 -05:00
}
cacheAddr := func ( sp * scrapePool ) map [ uint64 ] string {
r := make ( map [ uint64 ] string )
for fp , l := range sp . loops {
r [ fp ] = fmt . Sprintf ( "%p" , l . getCache ( ) )
}
return r
}
for i , s := range steps {
initCacheAddr := cacheAddr ( sp )
2025-12-22 04:38:48 -05:00
require . NoError ( t , sp . reload ( s . newConfig ) )
2020-01-22 07:13:47 -05:00
for fp , newCacheAddr := range cacheAddr ( sp ) {
if s . keep {
2020-10-29 05:43:23 -04:00
require . Equal ( t , initCacheAddr [ fp ] , newCacheAddr , "step %d: old cache and new cache are not the same" , i )
2020-01-22 07:13:47 -05:00
} else {
2020-10-29 05:43:23 -04:00
require . NotEqual ( t , initCacheAddr [ fp ] , newCacheAddr , "step %d: old cache and new cache are the same" , i )
2020-01-22 07:13:47 -05:00
}
}
initCacheAddr = cacheAddr ( sp )
2025-12-22 04:38:48 -05:00
require . NoError ( t , sp . reload ( s . newConfig ) )
2020-01-22 07:13:47 -05:00
for fp , newCacheAddr := range cacheAddr ( sp ) {
2020-10-29 05:43:23 -04:00
require . Equal ( t , initCacheAddr [ fp ] , newCacheAddr , "step %d: reloading the exact config invalidates the cache" , i )
2020-01-22 07:13:47 -05:00
}
}
}
2020-03-16 17:52:02 -04:00
func TestScrapeAddFast ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeAddFast ( t , appV2 )
} )
}
func testScrapeAddFast ( t * testing . T , appV2 bool ) {
2020-03-16 17:52:02 -04:00
s := teststorage . New ( t )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( s , appV2 ) )
2020-03-16 17:52:02 -04:00
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( "up 1\n" ) , "text/plain" , time . Time { } )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2020-03-16 17:52:02 -04:00
// Poison the cache. There is just one entry, and one series in the
// storage. Changing the ref will create a 'not found' error.
for _ , v := range sl . getCache ( ) . series {
v . ref ++
}
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
_ , _ , _ , err = app . append ( [ ] byte ( "up 1\n" ) , "text/plain" , time . Time { } . Add ( time . Second ) )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2020-03-16 17:52:02 -04:00
}
2020-03-20 12:43:26 -04:00
2023-09-22 12:47:44 -04:00
func TestReuseCacheRace ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testReuseCacheRace ( t , appV2 )
} )
}
func testReuseCacheRace ( t * testing . T , appV2 bool ) {
2020-03-20 12:43:26 -04:00
var (
cfg = & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
ScrapeTimeout : model . Duration ( 5 * time . Second ) ,
ScrapeInterval : model . Duration ( 5 * time . Second ) ,
MetricsPath : "/metrics" ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2020-03-20 12:43:26 -04:00
}
2025-08-27 08:38:54 -04:00
buffers = pool . New ( 1e3 , 100e6 , 3 , func ( sz int ) any { return make ( [ ] byte , 0 , sz ) } )
2026-01-21 03:21:56 -05:00
sa = selectAppendable ( teststorage . NewAppendable ( ) , appV2 )
sp , _ = newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , buffers , & Options { } , newTestScrapeMetrics ( t ) )
2023-10-17 05:27:46 -04:00
t1 = & Target {
2024-12-21 08:33:08 -05:00
labels : labels . FromStrings ( "labelNew" , "nameNew" ) ,
scrapeConfig : & config . ScrapeConfig { } ,
2020-03-20 12:43:26 -04:00
}
)
2020-07-27 04:38:08 -04:00
defer sp . stop ( )
2020-03-20 12:43:26 -04:00
sp . sync ( [ ] * Target { t1 } )
start := time . Now ( )
for i := uint ( 1 ) ; i > 0 ; i ++ {
if time . Since ( start ) > 5 * time . Second {
break
}
2025-12-22 04:38:48 -05:00
require . NoError ( t , sp . reload ( & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "Prometheus" ,
ScrapeTimeout : model . Duration ( 1 * time . Millisecond ) ,
ScrapeInterval : model . Duration ( 1 * time . Millisecond ) ,
MetricsPath : "/metrics" ,
SampleLimit : i ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2025-12-22 04:38:48 -05:00
} ) )
2020-03-20 12:43:26 -04:00
}
}
2020-05-26 10:14:55 -04:00
func TestCheckAddError ( t * testing . T ) {
var appErrs appendErrors
2025-12-22 04:38:48 -05:00
sl , _ := newTestScrapeLoop ( t )
// TODO: Check err etc
2026-01-21 03:21:56 -05:00
_ , _ = sl . checkAddError ( nil , nil , storage . ErrOutOfOrderSample , nil , nil , & appErrs )
2020-10-29 05:43:23 -04:00
require . Equal ( t , 1 , appErrs . numOutOfOrder )
2025-12-22 04:38:48 -05:00
// TODO(bwplotka): Test partial error check and other cases
2020-05-26 10:14:55 -04:00
}
2020-07-16 07:53:39 -04:00
func TestScrapeReportSingleAppender ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeReportSingleAppender ( t , appV2 )
} )
}
func testScrapeReportSingleAppender ( t * testing . T , appV2 bool ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2020-07-16 07:53:39 -04:00
s := teststorage . New ( t )
2025-12-22 04:38:48 -05:00
signal := make ( chan struct { } , 1 )
2020-07-16 07:53:39 -04:00
2025-12-22 04:38:48 -05:00
ctx , cancel := context . WithCancel ( t . Context ( ) )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( s , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . ctx = ctx
// Since we're writing samples directly below we need to provide a protocol fallback.
sl . fallbackScrapeProtocol = "text/plain"
} )
2020-07-16 07:53:39 -04:00
numScrapes := 0
2025-02-10 02:06:58 -05:00
scraper . scrapeFunc = func ( _ context . Context , w io . Writer ) error {
2020-07-16 07:53:39 -04:00
numScrapes ++
if numScrapes % 4 == 0 {
2024-11-03 07:15:51 -05:00
return errors . New ( "scrape failed" )
2020-07-16 07:53:39 -04:00
}
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "metric_a 44\nmetric_b 44\nmetric_c 44\nmetric_d 44\n" ) )
2020-07-16 07:53:39 -04:00
return nil
}
go func ( ) {
2021-08-31 11:37:32 -04:00
sl . run ( nil )
2020-07-16 07:53:39 -04:00
signal <- struct { } { }
} ( )
start := time . Now ( )
for time . Since ( start ) < 3 * time . Second {
2023-09-12 06:37:38 -04:00
q , err := s . Querier ( time . Time { } . UnixNano ( ) , time . Now ( ) . UnixNano ( ) )
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2023-09-12 06:37:38 -04:00
series := q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , ".+" ) )
2020-07-16 07:53:39 -04:00
c := 0
for series . Next ( ) {
2022-09-20 13:16:45 -04:00
i := series . At ( ) . Iterator ( nil )
2021-11-29 02:54:23 -05:00
for i . Next ( ) != chunkenc . ValNone {
2020-07-16 07:53:39 -04:00
c ++
}
}
2020-10-29 05:43:23 -04:00
require . Equal ( t , 0 , c % 9 , "Appended samples not as expected: %d" , c )
2025-12-22 04:38:48 -05:00
require . NoError ( t , q . Close ( ) )
2020-07-16 07:53:39 -04:00
}
cancel ( )
select {
case <- signal :
case <- time . After ( 5 * time . Second ) :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Scrape wasn't stopped." )
2020-07-16 07:53:39 -04:00
}
}
2021-05-06 04:56:21 -04:00
2021-12-10 07:03:28 -05:00
func TestScrapeReportLimit ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeReportLimit ( t , appV2 )
} )
}
func testScrapeReportLimit ( t * testing . T , appV2 bool ) {
2021-12-10 07:03:28 -05:00
s := teststorage . New ( t )
cfg := & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "test" ,
SampleLimit : 5 ,
Scheme : "http" ,
ScrapeInterval : model . Duration ( 100 * time . Millisecond ) ,
ScrapeTimeout : model . Duration ( 100 * time . Millisecond ) ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2021-12-10 07:03:28 -05:00
}
2024-09-09 10:46:31 -04:00
ts , scrapedTwice := newScrapableServer ( "metric_a 44\nmetric_b 44\nmetric_c 44\nmetric_d 44\n" )
2021-12-10 07:03:28 -05:00
defer ts . Close ( )
2026-01-21 03:21:56 -05:00
sa := selectAppendable ( s , appV2 )
sp , err := newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2021-12-10 07:03:28 -05:00
require . NoError ( t , err )
defer sp . stop ( )
testURL , err := url . Parse ( ts . URL )
require . NoError ( t , err )
sp . Sync ( [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet { { model . AddressLabel : model . LabelValue ( testURL . Host ) } } ,
} ,
} )
select {
case <- time . After ( 5 * time . Second ) :
t . Fatalf ( "target was not scraped twice" )
case <- scrapedTwice :
// If the target has been scraped twice, report samples from the first
// scrape have been inserted in the database.
}
2025-10-09 11:06:28 -04:00
ctx := t . Context ( )
2023-09-12 06:37:38 -04:00
q , err := s . Querier ( time . Time { } . UnixNano ( ) , time . Now ( ) . UnixNano ( ) )
2021-12-10 07:03:28 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
t . Cleanup ( func ( ) { _ = q . Close ( ) } )
2023-09-12 06:37:38 -04:00
series := q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , "up" ) )
2021-12-10 07:03:28 -05:00
var found bool
for series . Next ( ) {
2022-09-20 13:16:45 -04:00
i := series . At ( ) . Iterator ( nil )
2021-12-18 08:12:01 -05:00
for i . Next ( ) == chunkenc . ValFloat {
2021-12-10 07:03:28 -05:00
_ , v := i . At ( )
require . Equal ( t , 1.0 , v )
found = true
}
}
require . True ( t , found )
}
2024-09-09 10:46:31 -04:00
func TestScrapeUTF8 ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeUTF8 ( t , appV2 )
} )
}
func testScrapeUTF8 ( t * testing . T , appV2 bool ) {
2024-09-09 10:46:31 -04:00
s := teststorage . New ( t )
cfg := & config . ScrapeConfig {
JobName : "test" ,
Scheme : "http" ,
ScrapeInterval : model . Duration ( 100 * time . Millisecond ) ,
ScrapeTimeout : model . Duration ( 100 * time . Millisecond ) ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2024-09-09 10:46:31 -04:00
}
ts , scrapedTwice := newScrapableServer ( "{\"with.dots\"} 42\n" )
defer ts . Close ( )
2026-01-21 03:21:56 -05:00
sa := selectAppendable ( s , appV2 )
sp , err := newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2024-09-09 10:46:31 -04:00
require . NoError ( t , err )
defer sp . stop ( )
testURL , err := url . Parse ( ts . URL )
require . NoError ( t , err )
sp . Sync ( [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet { { model . AddressLabel : model . LabelValue ( testURL . Host ) } } ,
} ,
} )
select {
case <- time . After ( 5 * time . Second ) :
t . Fatalf ( "target was not scraped twice" )
case <- scrapedTwice :
// If the target has been scraped twice, report samples from the first
// scrape have been inserted in the database.
}
2025-10-09 11:06:28 -04:00
ctx := t . Context ( )
2024-09-09 10:46:31 -04:00
q , err := s . Querier ( time . Time { } . UnixNano ( ) , time . Now ( ) . UnixNano ( ) )
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
t . Cleanup ( func ( ) { _ = q . Close ( ) } )
2024-09-09 10:46:31 -04:00
series := q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , "with.dots" ) )
require . True ( t , series . Next ( ) , "series not found in tsdb" )
}
2021-05-06 04:56:21 -04:00
func TestScrapeLoopLabelLimit ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopLabelLimit ( t , appV2 )
} )
}
func testScrapeLoopLabelLimit ( t * testing . T , appV2 bool ) {
for _ , test := range [ ] struct {
2021-05-06 04:56:21 -04:00
title string
scrapeLabels string
discoveryLabels [ ] string
labelLimits labelLimits
expectErr bool
} {
{
title : "Valid number of labels" ,
scrapeLabels : ` metric { l1="1", l2="2"} 0 ` ,
discoveryLabels : nil ,
labelLimits : labelLimits { labelLimit : 5 } ,
expectErr : false ,
} , {
title : "Too many labels" ,
scrapeLabels : ` metric { l1="1", l2="2", l3="3", l4="4", l5="5", l6="6"} 0 ` ,
discoveryLabels : nil ,
labelLimits : labelLimits { labelLimit : 5 } ,
expectErr : true ,
} , {
title : "Too many labels including discovery labels" ,
scrapeLabels : ` metric { l1="1", l2="2", l3="3", l4="4"} 0 ` ,
discoveryLabels : [ ] string { "l5" , "5" , "l6" , "6" } ,
labelLimits : labelLimits { labelLimit : 5 } ,
expectErr : true ,
} , {
title : "Valid labels name length" ,
scrapeLabels : ` metric { l1="1", l2="2"} 0 ` ,
discoveryLabels : nil ,
labelLimits : labelLimits { labelNameLengthLimit : 10 } ,
expectErr : false ,
} , {
title : "Label name too long" ,
scrapeLabels : ` metric { label_name_too_long="0"} 0 ` ,
discoveryLabels : nil ,
labelLimits : labelLimits { labelNameLengthLimit : 10 } ,
expectErr : true ,
} , {
title : "Discovery label name too long" ,
scrapeLabels : ` metric { l1="1", l2="2"} 0 ` ,
discoveryLabels : [ ] string { "label_name_too_long" , "0" } ,
labelLimits : labelLimits { labelNameLengthLimit : 10 } ,
expectErr : true ,
} , {
title : "Valid labels value length" ,
scrapeLabels : ` metric { l1="1", l2="2"} 0 ` ,
discoveryLabels : nil ,
labelLimits : labelLimits { labelValueLengthLimit : 10 } ,
expectErr : false ,
} , {
title : "Label value too long" ,
scrapeLabels : ` metric { l1="label_value_too_long"} 0 ` ,
discoveryLabels : nil ,
labelLimits : labelLimits { labelValueLengthLimit : 10 } ,
expectErr : true ,
} , {
title : "Discovery label value too long" ,
scrapeLabels : ` metric { l1="1", l2="2"} 0 ` ,
discoveryLabels : [ ] string { "l1" , "label_value_too_long" } ,
labelLimits : labelLimits { labelValueLengthLimit : 10 } ,
expectErr : true ,
} ,
2026-01-21 03:21:56 -05:00
} {
2021-05-06 04:56:21 -04:00
discoveryLabels := & Target {
labels : labels . FromStrings ( test . discoveryLabels ... ) ,
}
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . sampleMutator = func ( l labels . Labels ) labels . Labels {
return mutateSampleLabels ( l , discoveryLabels , false , nil )
}
sl . reportSampleMutator = func ( l labels . Labels ) labels . Labels {
return mutateReportSampleLabels ( l , discoveryLabels )
}
sl . labelLimits = & test . labelLimits
} )
2021-05-06 04:56:21 -04:00
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( test . scrapeLabels ) , "text/plain" , time . Now ( ) )
2021-05-06 04:56:21 -04:00
t . Logf ( "Test:%s" , test . title )
if test . expectErr {
require . Error ( t , err )
} else {
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2021-05-06 04:56:21 -04:00
}
}
}
2021-08-31 11:37:32 -04:00
func TestTargetScrapeIntervalAndTimeoutRelabel ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testTargetScrapeIntervalAndTimeoutRelabel ( t , appV2 )
} )
}
func testTargetScrapeIntervalAndTimeoutRelabel ( t * testing . T , appV2 bool ) {
2021-08-31 11:37:32 -04:00
interval , _ := model . ParseDuration ( "2s" )
timeout , _ := model . ParseDuration ( "500ms" )
2025-12-22 04:38:48 -05:00
cfg := & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
ScrapeInterval : interval ,
ScrapeTimeout : timeout ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2021-08-31 11:37:32 -04:00
RelabelConfigs : [ ] * relabel . Config {
{
2025-08-18 04:09:00 -04:00
SourceLabels : model . LabelNames { model . ScrapeIntervalLabel } ,
Regex : relabel . MustNewRegexp ( "2s" ) ,
Replacement : "3s" ,
TargetLabel : model . ScrapeIntervalLabel ,
Action : relabel . Replace ,
NameValidationScheme : model . UTF8Validation ,
2021-08-31 11:37:32 -04:00
} ,
{
2025-08-18 04:09:00 -04:00
SourceLabels : model . LabelNames { model . ScrapeTimeoutLabel } ,
Regex : relabel . MustNewRegexp ( "500ms" ) ,
Replacement : "750ms" ,
TargetLabel : model . ScrapeTimeoutLabel ,
Action : relabel . Replace ,
NameValidationScheme : model . UTF8Validation ,
2021-08-31 11:37:32 -04:00
} ,
} ,
}
2026-01-21 03:21:56 -05:00
sa := selectAppendable ( teststorage . NewAppendable ( ) , appV2 )
sp , _ := newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2021-08-31 11:37:32 -04:00
tgts := [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet { { model . AddressLabel : "127.0.0.1:9090" } } ,
} ,
}
sp . Sync ( tgts )
defer sp . stop ( )
require . Equal ( t , "3s" , sp . ActiveTargets ( ) [ 0 ] . labels . Get ( model . ScrapeIntervalLabel ) )
require . Equal ( t , "750ms" , sp . ActiveTargets ( ) [ 0 ] . labels . Get ( model . ScrapeTimeoutLabel ) )
}
2023-10-31 16:58:42 -04:00
2023-11-01 13:30:34 -04:00
// Testing whether we can remove trailing .0 from histogram 'le' and summary 'quantile' labels.
func TestLeQuantileReLabel ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testLeQuantileReLabel ( t , appV2 )
} )
}
func testLeQuantileReLabel ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
s := teststorage . New ( t )
2023-11-01 13:30:34 -04:00
2025-12-22 04:38:48 -05:00
cfg := & config . ScrapeConfig {
2023-11-01 13:30:34 -04:00
JobName : "test" ,
MetricRelabelConfigs : [ ] * relabel . Config {
{
2025-08-18 04:09:00 -04:00
SourceLabels : model . LabelNames { "le" , "__name__" } ,
Regex : relabel . MustNewRegexp ( "(\\d+)\\.0+;.*_bucket" ) ,
Replacement : relabel . DefaultRelabelConfig . Replacement ,
Separator : relabel . DefaultRelabelConfig . Separator ,
TargetLabel : "le" ,
Action : relabel . Replace ,
NameValidationScheme : model . UTF8Validation ,
2023-11-01 13:30:34 -04:00
} ,
{
2025-08-18 04:09:00 -04:00
SourceLabels : model . LabelNames { "quantile" } ,
Regex : relabel . MustNewRegexp ( "(\\d+)\\.0+" ) ,
Replacement : relabel . DefaultRelabelConfig . Replacement ,
Separator : relabel . DefaultRelabelConfig . Separator ,
TargetLabel : "quantile" ,
Action : relabel . Replace ,
NameValidationScheme : model . UTF8Validation ,
2023-11-01 13:30:34 -04:00
} ,
} ,
2025-03-26 18:27:28 -04:00
SampleLimit : 100 ,
Scheme : "http" ,
ScrapeInterval : model . Duration ( 100 * time . Millisecond ) ,
ScrapeTimeout : model . Duration ( 100 * time . Millisecond ) ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2023-11-01 13:30:34 -04:00
}
metricsText := `
# HELP test_histogram This is a histogram with default buckets
# TYPE test_histogram histogram
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "0.005" } 0
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "0.01" } 0
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "0.025" } 0
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "0.05" } 0
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "0.1" } 0
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "0.25" } 0
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "0.5" } 0
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "1.0" } 0
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "2.5" } 0
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "5.0" } 0
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "10.0" } 0
test_histogram_bucket { address = "0.0.0.0" , port = "5001" , le = "+Inf" } 0
test_histogram_sum { address = "0.0.0.0" , port = "5001" } 0
test_histogram_count { address = "0.0.0.0" , port = "5001" } 0
# HELP test_summary Number of inflight requests sampled at a regular interval . Quantile buckets keep track of inflight requests over the last 60 s .
# TYPE test_summary summary
test_summary { quantile = "0.5" } 0
test_summary { quantile = "0.9" } 0
test_summary { quantile = "0.95" } 0
test_summary { quantile = "0.99" } 0
test_summary { quantile = "1.0" } 1
test_summary_sum 1
test_summary_count 199
`
// The expected "le" values do not have the trailing ".0".
expectedLeValues := [ ] string { "0.005" , "0.01" , "0.025" , "0.05" , "0.1" , "0.25" , "0.5" , "1" , "2.5" , "5" , "10" , "+Inf" }
// The expected "quantile" values do not have the trailing ".0".
expectedQuantileValues := [ ] string { "0.5" , "0.9" , "0.95" , "0.99" , "1" }
2024-09-09 10:46:31 -04:00
ts , scrapedTwice := newScrapableServer ( metricsText )
2023-11-01 13:30:34 -04:00
defer ts . Close ( )
2026-01-21 03:21:56 -05:00
sa := selectAppendable ( s , appV2 )
sp , err := newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2023-11-01 13:30:34 -04:00
require . NoError ( t , err )
defer sp . stop ( )
testURL , err := url . Parse ( ts . URL )
require . NoError ( t , err )
sp . Sync ( [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet { { model . AddressLabel : model . LabelValue ( testURL . Host ) } } ,
} ,
} )
2023-12-07 06:35:01 -05:00
require . Len ( t , sp . ActiveTargets ( ) , 1 )
2023-11-01 13:30:34 -04:00
select {
case <- time . After ( 5 * time . Second ) :
t . Fatalf ( "target was not scraped" )
2024-09-09 10:46:31 -04:00
case <- scrapedTwice :
2023-11-01 13:30:34 -04:00
}
2025-10-09 11:06:28 -04:00
ctx := t . Context ( )
2025-12-22 04:38:48 -05:00
q , err := s . Querier ( time . Time { } . UnixNano ( ) , time . Now ( ) . UnixNano ( ) )
2023-11-01 13:30:34 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
t . Cleanup ( func ( ) { _ = q . Close ( ) } )
2023-11-01 13:30:34 -04:00
checkValues := func ( labelName string , expectedValues [ ] string , series storage . SeriesSet ) {
foundLeValues := map [ string ] bool { }
for series . Next ( ) {
s := series . At ( )
v := s . Labels ( ) . Get ( labelName )
require . NotContains ( t , foundLeValues , v , "duplicate label value found" )
foundLeValues [ v ] = true
}
2025-05-03 13:05:13 -04:00
require . Len ( t , foundLeValues , len ( expectedValues ) , "number of label values not as expected" )
2023-11-01 13:30:34 -04:00
for _ , v := range expectedValues {
require . Contains ( t , foundLeValues , v , "label value not found" )
}
}
series := q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , "test_histogram_bucket" ) )
checkValues ( "le" , expectedLeValues , series )
series = q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , "test_summary" ) )
checkValues ( "quantile" , expectedQuantileValues , series )
}
2024-07-03 05:56:48 -04:00
// Testing whether we can automatically convert scraped classic histograms into native histograms with custom buckets.
2024-10-21 07:22:58 -04:00
func TestConvertClassicHistogramsToNHCB ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testConvertClassicHistogramsToNHCB ( t , appV2 )
} )
}
func testConvertClassicHistogramsToNHCB ( t * testing . T , appV2 bool ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2025-12-22 04:38:48 -05:00
genTestCounterText := func ( name string ) string {
return fmt . Sprintf ( `
2024-07-03 05:56:48 -04:00
# HELP % s some help text
# TYPE % s counter
2025-12-22 04:38:48 -05:00
% s { address = "0.0.0.0" , port = "5001" } 1
` , name , name , name )
2024-07-03 05:56:48 -04:00
}
2025-12-22 04:38:48 -05:00
genTestHistText := func ( name string ) string {
2025-08-27 08:38:54 -04:00
data := map [ string ] any {
2024-07-03 05:56:48 -04:00
"name" : name ,
}
b := & bytes . Buffer { }
2025-12-22 04:38:48 -05:00
require . NoError ( t , template . Must ( template . New ( "" ) . Parse ( `
2024-07-03 05:56:48 -04:00
# HELP { { . name } } This is a histogram with default buckets
# TYPE { { . name } } histogram
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "0.005" } 0
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "0.01" } 0
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "0.025" } 0
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "0.05" } 0
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "0.1" } 0
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "0.25" } 0
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "0.5" } 0
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "1" } 0
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "2.5" } 0
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "5" } 0
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "10" } 1
{ { . name } } _bucket { address = "0.0.0.0" , port = "5001" , le = "+Inf" } 1
{ { . name } } _sum { address = "0.0.0.0" , port = "5001" } 10
{ { . name } } _count { address = "0.0.0.0" , port = "5001" } 1
2025-12-22 04:38:48 -05:00
` ) ) . Execute ( b , data ) )
2024-07-03 05:56:48 -04:00
return b . String ( )
}
2025-12-22 04:38:48 -05:00
genTestCounterProto := func ( name string ) string {
2024-07-03 05:56:48 -04:00
return fmt . Sprintf ( `
name : "%s"
help : "some help text"
type : COUNTER
metric : <
2024-07-03 05:56:48 -04:00
label : <
name : "address"
value : "0.0.0.0"
>
label : <
name : "port"
value : "5001"
>
2024-07-03 05:56:48 -04:00
counter : <
value : % d
>
>
2025-12-22 04:38:48 -05:00
` , name , 1 )
2024-07-03 05:56:48 -04:00
}
2024-07-03 05:56:48 -04:00
genTestHistProto := func ( name string , hasClassic , hasExponential bool ) string {
var classic string
if hasClassic {
classic = `
bucket : <
cumulative_count : 0
upper_bound : 0.005
>
bucket : <
cumulative_count : 0
upper_bound : 0.01
>
bucket : <
cumulative_count : 0
upper_bound : 0.025
>
bucket : <
cumulative_count : 0
upper_bound : 0.05
>
bucket : <
cumulative_count : 0
upper_bound : 0.1
>
bucket : <
cumulative_count : 0
upper_bound : 0.25
>
bucket : <
cumulative_count : 0
upper_bound : 0.5
>
bucket : <
cumulative_count : 0
upper_bound : 1
>
bucket : <
cumulative_count : 0
upper_bound : 2.5
>
bucket : <
cumulative_count : 0
upper_bound : 5
>
bucket : <
cumulative_count : 1
upper_bound : 10
> `
}
var expo string
if hasExponential {
expo = `
schema : 3
zero_threshold : 2.938735877055719e-39
zero_count : 0
positive_span : <
offset : 2
length : 1
>
positive_delta : 1 `
}
2024-07-03 05:56:48 -04:00
return fmt . Sprintf ( `
name : "%s"
help : "This is a histogram with default buckets"
type : HISTOGRAM
metric : <
label : <
name : "address"
value : "0.0.0.0"
>
label : <
name : "port"
value : "5001"
>
histogram : <
sample_count : 1
sample_sum : 10
2024-07-03 05:56:48 -04:00
% s
% s
2024-07-03 05:56:48 -04:00
>
>
2024-07-03 05:56:48 -04:00
` , name , classic , expo )
2024-07-03 05:56:48 -04:00
}
2024-07-03 05:56:48 -04:00
metricsTexts := map [ string ] struct {
2024-07-03 05:56:48 -04:00
text [ ] string
contentType string
2024-07-03 05:56:48 -04:00
hasClassic bool
2024-07-03 05:56:48 -04:00
hasExponential bool
2024-07-03 05:56:48 -04:00
} {
"text" : {
text : [ ] string {
2025-12-22 04:38:48 -05:00
genTestCounterText ( "test_metric_1" ) ,
genTestCounterText ( "test_metric_1_count" ) ,
genTestCounterText ( "test_metric_1_sum" ) ,
genTestCounterText ( "test_metric_1_bucket" ) ,
genTestHistText ( "test_histogram_1" ) ,
genTestCounterText ( "test_metric_2" ) ,
genTestCounterText ( "test_metric_2_count" ) ,
genTestCounterText ( "test_metric_2_sum" ) ,
genTestCounterText ( "test_metric_2_bucket" ) ,
genTestHistText ( "test_histogram_2" ) ,
genTestCounterText ( "test_metric_3" ) ,
genTestCounterText ( "test_metric_3_count" ) ,
genTestCounterText ( "test_metric_3_sum" ) ,
genTestCounterText ( "test_metric_3_bucket" ) ,
genTestHistText ( "test_histogram_3" ) ,
2024-07-03 05:56:48 -04:00
} ,
2024-07-03 05:56:48 -04:00
hasClassic : true ,
2024-07-03 05:56:48 -04:00
} ,
2024-07-03 05:56:48 -04:00
"text, in different order" : {
2024-07-03 05:56:48 -04:00
text : [ ] string {
2025-12-22 04:38:48 -05:00
genTestCounterText ( "test_metric_1" ) ,
genTestCounterText ( "test_metric_1_count" ) ,
genTestCounterText ( "test_metric_1_sum" ) ,
genTestCounterText ( "test_metric_1_bucket" ) ,
genTestHistText ( "test_histogram_1" ) ,
genTestCounterText ( "test_metric_2" ) ,
genTestCounterText ( "test_metric_2_count" ) ,
genTestCounterText ( "test_metric_2_sum" ) ,
genTestCounterText ( "test_metric_2_bucket" ) ,
genTestHistText ( "test_histogram_2" ) ,
genTestHistText ( "test_histogram_3" ) ,
genTestCounterText ( "test_metric_3" ) ,
genTestCounterText ( "test_metric_3_count" ) ,
genTestCounterText ( "test_metric_3_sum" ) ,
genTestCounterText ( "test_metric_3_bucket" ) ,
2024-07-03 05:56:48 -04:00
} ,
2024-07-03 05:56:48 -04:00
hasClassic : true ,
2024-07-03 05:56:48 -04:00
} ,
"protobuf" : {
text : [ ] string {
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_1" ) ,
genTestCounterProto ( "test_metric_1_count" ) ,
genTestCounterProto ( "test_metric_1_sum" ) ,
genTestCounterProto ( "test_metric_1_bucket" ) ,
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_1" , true , false ) ,
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_2" ) ,
genTestCounterProto ( "test_metric_2_count" ) ,
genTestCounterProto ( "test_metric_2_sum" ) ,
genTestCounterProto ( "test_metric_2_bucket" ) ,
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_2" , true , false ) ,
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_3" ) ,
genTestCounterProto ( "test_metric_3_count" ) ,
genTestCounterProto ( "test_metric_3_sum" ) ,
genTestCounterProto ( "test_metric_3_bucket" ) ,
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_3" , true , false ) ,
2024-07-03 05:56:48 -04:00
} ,
contentType : "application/vnd.google.protobuf" ,
2024-07-03 05:56:48 -04:00
hasClassic : true ,
2024-07-03 05:56:48 -04:00
} ,
"protobuf, in different order" : {
text : [ ] string {
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_1" , true , false ) ,
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_1" ) ,
genTestCounterProto ( "test_metric_1_count" ) ,
genTestCounterProto ( "test_metric_1_sum" ) ,
genTestCounterProto ( "test_metric_1_bucket" ) ,
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_2" , true , false ) ,
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_2" ) ,
genTestCounterProto ( "test_metric_2_count" ) ,
genTestCounterProto ( "test_metric_2_sum" ) ,
genTestCounterProto ( "test_metric_2_bucket" ) ,
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_3" , true , false ) ,
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_3" ) ,
genTestCounterProto ( "test_metric_3_count" ) ,
genTestCounterProto ( "test_metric_3_sum" ) ,
genTestCounterProto ( "test_metric_3_bucket" ) ,
2024-07-03 05:56:48 -04:00
} ,
contentType : "application/vnd.google.protobuf" ,
2024-07-03 05:56:48 -04:00
hasClassic : true ,
2024-07-03 05:56:48 -04:00
} ,
2024-07-03 05:56:48 -04:00
"protobuf, with additional native exponential histogram" : {
2024-07-03 05:56:48 -04:00
text : [ ] string {
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_1" ) ,
genTestCounterProto ( "test_metric_1_count" ) ,
genTestCounterProto ( "test_metric_1_sum" ) ,
genTestCounterProto ( "test_metric_1_bucket" ) ,
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_1" , true , true ) ,
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_2" ) ,
genTestCounterProto ( "test_metric_2_count" ) ,
genTestCounterProto ( "test_metric_2_sum" ) ,
genTestCounterProto ( "test_metric_2_bucket" ) ,
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_2" , true , true ) ,
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_3" ) ,
genTestCounterProto ( "test_metric_3_count" ) ,
genTestCounterProto ( "test_metric_3_sum" ) ,
genTestCounterProto ( "test_metric_3_bucket" ) ,
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_3" , true , true ) ,
} ,
contentType : "application/vnd.google.protobuf" ,
2024-07-03 05:56:48 -04:00
hasClassic : true ,
hasExponential : true ,
} ,
"protobuf, with only native exponential histogram" : {
text : [ ] string {
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_1" ) ,
genTestCounterProto ( "test_metric_1_count" ) ,
genTestCounterProto ( "test_metric_1_sum" ) ,
genTestCounterProto ( "test_metric_1_bucket" ) ,
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_1" , false , true ) ,
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_2" ) ,
genTestCounterProto ( "test_metric_2_count" ) ,
genTestCounterProto ( "test_metric_2_sum" ) ,
genTestCounterProto ( "test_metric_2_bucket" ) ,
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_2" , false , true ) ,
2025-12-22 04:38:48 -05:00
genTestCounterProto ( "test_metric_3" ) ,
genTestCounterProto ( "test_metric_3_count" ) ,
genTestCounterProto ( "test_metric_3_sum" ) ,
genTestCounterProto ( "test_metric_3_bucket" ) ,
2024-07-03 05:56:48 -04:00
genTestHistProto ( "test_histogram_3" , false , true ) ,
} ,
contentType : "application/vnd.google.protobuf" ,
2024-07-03 05:56:48 -04:00
hasExponential : true ,
} ,
2024-07-03 05:56:48 -04:00
}
2024-07-03 05:56:48 -04:00
2025-12-22 04:38:48 -05:00
checkBucketValues := func ( t testing . TB , expectedCount int , series storage . SeriesSet ) {
2024-07-03 05:56:48 -04:00
labelName := "le"
var expectedValues [ ] string
if expectedCount > 0 {
2024-10-21 05:10:50 -04:00
expectedValues = [ ] string { "0.005" , "0.01" , "0.025" , "0.05" , "0.1" , "0.25" , "0.5" , "1.0" , "2.5" , "5.0" , "10.0" , "+Inf" }
2024-07-03 05:56:48 -04:00
}
2024-07-03 05:56:48 -04:00
foundLeValues := map [ string ] bool { }
for series . Next ( ) {
s := series . At ( )
v := s . Labels ( ) . Get ( labelName )
require . NotContains ( t , foundLeValues , v , "duplicate label value found" )
foundLeValues [ v ] = true
}
2025-05-03 13:05:13 -04:00
require . Len ( t , foundLeValues , len ( expectedValues ) , "unexpected number of label values, expected %v but found %v" , expectedValues , foundLeValues )
2024-07-03 05:56:48 -04:00
for _ , v := range expectedValues {
require . Contains ( t , foundLeValues , v , "label value not found" )
}
}
2024-07-03 05:56:48 -04:00
// Checks that the expected series is present and runs a basic sanity check of the float values.
2025-12-22 04:38:48 -05:00
checkFloatSeries := func ( t testing . TB , series storage . SeriesSet , expectedCount int , expectedFloat float64 ) {
2024-07-03 05:56:48 -04:00
count := 0
for series . Next ( ) {
i := series . At ( ) . Iterator ( nil )
2024-07-03 05:56:48 -04:00
loop :
for {
switch i . Next ( ) {
case chunkenc . ValNone :
break loop
case chunkenc . ValFloat :
2024-07-03 05:56:48 -04:00
_ , f := i . At ( )
2024-07-03 05:56:48 -04:00
require . Equal ( t , expectedFloat , f )
case chunkenc . ValHistogram :
panic ( "unexpected value type: histogram" )
case chunkenc . ValFloatHistogram :
panic ( "unexpected value type: float histogram" )
default :
panic ( "unexpected value type" )
2024-07-03 05:56:48 -04:00
}
2024-07-03 05:56:48 -04:00
}
count ++
}
require . Equal ( t , expectedCount , count , "number of float series not as expected" )
}
// Checks that the expected series is present and runs a basic sanity check of the histogram values.
2025-12-22 04:38:48 -05:00
checkHistSeries := func ( t testing . TB , series storage . SeriesSet , expectedCount int , expectedSchema int32 ) {
2024-07-03 05:56:48 -04:00
count := 0
for series . Next ( ) {
i := series . At ( ) . Iterator ( nil )
loop :
for {
switch i . Next ( ) {
case chunkenc . ValNone :
break loop
case chunkenc . ValFloat :
panic ( "unexpected value type: float" )
case chunkenc . ValHistogram :
2024-07-03 05:56:48 -04:00
_ , h := i . AtHistogram ( nil )
2024-07-03 05:56:48 -04:00
require . Equal ( t , expectedSchema , h . Schema )
require . Equal ( t , uint64 ( 1 ) , h . Count )
require . Equal ( t , 10.0 , h . Sum )
case chunkenc . ValFloatHistogram :
_ , h := i . AtFloatHistogram ( nil )
require . Equal ( t , expectedSchema , h . Schema )
2024-07-03 05:56:48 -04:00
require . Equal ( t , uint64 ( 1 ) , h . Count )
require . Equal ( t , 10.0 , h . Sum )
2024-07-03 05:56:48 -04:00
default :
panic ( "unexpected value type" )
2024-07-03 05:56:48 -04:00
}
}
count ++
}
2024-07-03 05:56:48 -04:00
require . Equal ( t , expectedCount , count , "number of histogram series not as expected" )
2024-07-03 05:56:48 -04:00
}
for metricsTextName , metricsText := range metricsTexts {
for name , tc := range map [ string ] struct {
2025-09-02 07:37:14 -04:00
alwaysScrapeClassicHistograms bool
convertClassicHistToNHCB bool
2024-07-03 05:56:48 -04:00
} {
"convert with scrape" : {
2025-09-02 07:37:14 -04:00
alwaysScrapeClassicHistograms : true ,
convertClassicHistToNHCB : true ,
2024-07-03 05:56:48 -04:00
} ,
"convert without scrape" : {
2025-09-02 07:37:14 -04:00
alwaysScrapeClassicHistograms : false ,
convertClassicHistToNHCB : true ,
2024-07-03 05:56:48 -04:00
} ,
"scrape without convert" : {
2025-09-02 07:37:14 -04:00
alwaysScrapeClassicHistograms : true ,
convertClassicHistToNHCB : false ,
2025-03-17 18:57:14 -04:00
} ,
"scrape with nil convert" : {
2025-09-02 07:37:14 -04:00
alwaysScrapeClassicHistograms : true ,
2024-07-03 05:56:48 -04:00
} ,
"neither scrape nor convert" : {
2025-09-02 07:37:14 -04:00
alwaysScrapeClassicHistograms : false ,
convertClassicHistToNHCB : false ,
2024-07-03 05:56:48 -04:00
} ,
} {
2024-07-03 05:56:48 -04:00
var expectedClassicHistCount , expectedNativeHistCount int
var expectCustomBuckets bool
if metricsText . hasExponential {
expectedNativeHistCount = 1
expectCustomBuckets = false
2024-07-03 05:56:48 -04:00
expectedClassicHistCount = 0
2025-09-02 07:37:14 -04:00
if metricsText . hasClassic && tc . alwaysScrapeClassicHistograms {
2024-07-03 05:56:48 -04:00
expectedClassicHistCount = 1
}
} else if metricsText . hasClassic {
switch {
2025-09-02 07:37:14 -04:00
case ! tc . convertClassicHistToNHCB :
2025-03-17 18:57:14 -04:00
expectedClassicHistCount = 1
expectedNativeHistCount = 0
2025-09-02 07:37:14 -04:00
case tc . alwaysScrapeClassicHistograms && tc . convertClassicHistToNHCB :
2024-07-03 05:56:48 -04:00
expectedClassicHistCount = 1
expectedNativeHistCount = 1
expectCustomBuckets = true
2025-09-02 07:37:14 -04:00
case ! tc . alwaysScrapeClassicHistograms && tc . convertClassicHistToNHCB :
2024-07-03 05:56:48 -04:00
expectedClassicHistCount = 0
expectedNativeHistCount = 1
expectCustomBuckets = true
}
2024-07-03 05:56:48 -04:00
}
2024-07-03 05:56:48 -04:00
t . Run ( fmt . Sprintf ( "%s with %s" , name , metricsTextName ) , func ( t * testing . T ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2025-12-22 04:38:48 -05:00
s := teststorage . New ( t )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( s , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . alwaysScrapeClassicHist = tc . alwaysScrapeClassicHistograms
sl . convertClassicHistToNHCB = tc . convertClassicHistToNHCB
sl . enableNativeHistogramScraping = true
} )
2025-09-02 07:37:14 -04:00
var content [ ] byte
contentType := metricsText . contentType
switch contentType {
case "application/vnd.google.protobuf" :
buf := & bytes . Buffer { }
for _ , text := range metricsText . text {
// In case of protobuf, we have to create the binary representation.
pb := & dto . MetricFamily { }
// From text to proto message.
require . NoError ( t , proto . UnmarshalText ( text , pb ) )
// From proto message to binary protobuf.
buf . Write ( protoMarshalDelimited ( t , pb ) )
2024-07-03 05:56:48 -04:00
}
2025-09-02 07:37:14 -04:00
content = buf . Bytes ( )
case "text/plain" , "" :
// The input text fragments already have a newline at the
// end, so we just concatenate them without separator.
content = [ ] byte ( strings . Join ( metricsText . text , "" ) )
contentType = "text/plain"
default :
t . Error ( "unexpected content type" )
2024-07-03 05:56:48 -04:00
}
2025-12-22 04:38:48 -05:00
now := time . Now ( )
app := sl . appender ( )
_ , _ , _ , err := app . append ( content , contentType , now )
require . NoError ( t , err )
2025-09-02 07:37:14 -04:00
require . NoError ( t , app . Commit ( ) )
2024-07-03 05:56:48 -04:00
2025-12-22 04:38:48 -05:00
var expectedSchema int32
if expectCustomBuckets {
expectedSchema = histogram . CustomBucketsSchema
} else {
expectedSchema = 3
}
// Validated what was appended can be queried.
2025-10-09 11:06:28 -04:00
ctx := t . Context ( )
2025-12-22 04:38:48 -05:00
q , err := s . Querier ( time . Time { } . UnixNano ( ) , time . Now ( ) . UnixNano ( ) )
2024-07-03 05:56:48 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
t . Cleanup ( func ( ) { _ = q . Close ( ) } )
2024-07-03 05:56:48 -04:00
2024-07-03 05:56:48 -04:00
var series storage . SeriesSet
for i := 1 ; i <= 3 ; i ++ {
series = q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , fmt . Sprintf ( "test_metric_%d" , i ) ) )
2025-12-22 04:38:48 -05:00
checkFloatSeries ( t , series , 1 , 1. )
2024-07-03 05:56:48 -04:00
2024-07-03 05:56:48 -04:00
series = q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , fmt . Sprintf ( "test_metric_%d_count" , i ) ) )
2025-12-22 04:38:48 -05:00
checkFloatSeries ( t , series , 1 , 1. )
2024-07-03 05:56:48 -04:00
series = q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , fmt . Sprintf ( "test_metric_%d_sum" , i ) ) )
2025-12-22 04:38:48 -05:00
checkFloatSeries ( t , series , 1 , 1. )
2024-07-03 05:56:48 -04:00
series = q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , fmt . Sprintf ( "test_metric_%d_bucket" , i ) ) )
2025-12-22 04:38:48 -05:00
checkFloatSeries ( t , series , 1 , 1. )
2024-07-03 05:56:48 -04:00
2024-07-03 05:56:48 -04:00
series = q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , fmt . Sprintf ( "test_histogram_%d_count" , i ) ) )
2025-12-22 04:38:48 -05:00
checkFloatSeries ( t , series , expectedClassicHistCount , 1. )
2024-07-03 05:56:48 -04:00
2024-07-03 05:56:48 -04:00
series = q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , fmt . Sprintf ( "test_histogram_%d_sum" , i ) ) )
2025-12-22 04:38:48 -05:00
checkFloatSeries ( t , series , expectedClassicHistCount , 10. )
2024-07-03 05:56:48 -04:00
2024-07-03 05:56:48 -04:00
series = q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , fmt . Sprintf ( "test_histogram_%d_bucket" , i ) ) )
2025-12-22 04:38:48 -05:00
checkBucketValues ( t , expectedClassicHistCount , series )
2024-07-03 05:56:48 -04:00
2024-07-03 05:56:48 -04:00
series = q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , fmt . Sprintf ( "test_histogram_%d" , i ) ) )
2025-12-22 04:38:48 -05:00
checkHistSeries ( t , series , expectedNativeHistCount , expectedSchema )
2024-07-03 05:56:48 -04:00
}
2024-07-03 05:56:48 -04:00
} )
}
}
2024-07-03 05:56:48 -04:00
}
2025-06-23 11:42:38 -04:00
func TestTypeUnitReLabel ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testTypeUnitReLabel ( t , appV2 )
} )
}
func testTypeUnitReLabel ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
s := teststorage . New ( t )
2025-06-23 11:42:38 -04:00
2025-12-22 04:38:48 -05:00
cfg := & config . ScrapeConfig {
2025-06-23 11:42:38 -04:00
JobName : "test" ,
MetricRelabelConfigs : [ ] * relabel . Config {
{
2025-08-18 04:09:00 -04:00
SourceLabels : model . LabelNames { "__name__" } ,
Regex : relabel . MustNewRegexp ( ".*_total$" ) ,
Replacement : "counter" ,
TargetLabel : "__type__" ,
Action : relabel . Replace ,
NameValidationScheme : model . UTF8Validation ,
2025-06-23 11:42:38 -04:00
} ,
{
2025-08-18 04:09:00 -04:00
SourceLabels : model . LabelNames { "__name__" } ,
Regex : relabel . MustNewRegexp ( ".*_bytes$" ) ,
Replacement : "bytes" ,
TargetLabel : "__unit__" ,
Action : relabel . Replace ,
NameValidationScheme : model . UTF8Validation ,
2025-06-23 11:42:38 -04:00
} ,
} ,
SampleLimit : 100 ,
Scheme : "http" ,
ScrapeInterval : model . Duration ( 100 * time . Millisecond ) ,
ScrapeTimeout : model . Duration ( 100 * time . Millisecond ) ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-06-23 11:42:38 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
}
metricsText := `
# HELP test_metric_1_total This is a counter
# TYPE test_metric_1_total counter
test_metric_1_total 123
# HELP test_metric_2_total This is a counter
# TYPE test_metric_2_total counter
test_metric_2_total 234
# HELP disk_usage_bytes This is a gauge
# TYPE disk_usage_bytes gauge
disk_usage_bytes 456
`
ts , scrapedTwice := newScrapableServer ( metricsText )
defer ts . Close ( )
2026-01-21 03:21:56 -05:00
sa := selectAppendable ( s , appV2 )
sp , err := newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2025-06-23 11:42:38 -04:00
require . NoError ( t , err )
defer sp . stop ( )
testURL , err := url . Parse ( ts . URL )
require . NoError ( t , err )
sp . Sync ( [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet { { model . AddressLabel : model . LabelValue ( testURL . Host ) } } ,
} ,
} )
require . Len ( t , sp . ActiveTargets ( ) , 1 )
select {
case <- time . After ( 5 * time . Second ) :
t . Fatalf ( "target was not scraped" )
case <- scrapedTwice :
}
2025-10-09 11:06:28 -04:00
ctx := t . Context ( )
2025-12-22 04:38:48 -05:00
q , err := s . Querier ( time . Time { } . UnixNano ( ) , time . Now ( ) . UnixNano ( ) )
2025-06-23 11:42:38 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
t . Cleanup ( func ( ) { _ = q . Close ( ) } )
2025-06-23 11:42:38 -04:00
series := q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , ".*_total$" ) )
for series . Next ( ) {
s := series . At ( )
require . Equal ( t , "counter" , s . Labels ( ) . Get ( "__type__" ) )
}
series = q . Select ( ctx , false , nil , labels . MustNewMatcher ( labels . MatchRegexp , "__name__" , "disk_usage_bytes" ) )
for series . Next ( ) {
s := series . At ( )
require . Equal ( t , "bytes" , s . Labels ( ) . Get ( "__unit__" ) )
}
}
2023-10-31 16:58:42 -04:00
func TestScrapeLoopRunCreatesStaleMarkersOnFailedScrapeForTimestampedMetrics ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopRunCreatesStaleMarkersOnFailedScrapeForTimestampedMetrics ( t , appV2 )
} )
}
func testScrapeLoopRunCreatesStaleMarkersOnFailedScrapeForTimestampedMetrics ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
signal := make ( chan struct { } , 1 )
ctx , cancel := context . WithCancel ( t . Context ( ) )
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
sl . ctx = ctx // Since we're writing samples directly below we need to provide a protocol fallback.
2025-12-22 04:38:48 -05:00
sl . fallbackScrapeProtocol = "text/plain"
sl . trackTimestampsStaleness = true
} )
2023-10-31 16:58:42 -04:00
// Succeed once, several failures, then stop.
numScrapes := 0
2025-02-10 02:06:58 -05:00
scraper . scrapeFunc = func ( _ context . Context , w io . Writer ) error {
2023-10-31 16:58:42 -04:00
numScrapes ++
switch numScrapes {
case 1 :
2025-12-22 04:38:48 -05:00
_ , _ = fmt . Fprintf ( w , "metric_a 42 %d\n" , time . Now ( ) . UnixNano ( ) / int64 ( time . Millisecond ) )
2023-10-31 16:58:42 -04:00
return nil
case 5 :
cancel ( )
}
return errors . New ( "scrape failed" )
}
go func ( ) {
sl . run ( nil )
signal <- struct { } { }
} ( )
select {
case <- signal :
case <- time . After ( 5 * time . Second ) :
t . Fatalf ( "Scrape wasn't stopped." )
}
2025-12-22 04:38:48 -05:00
got := appTest . ResultSamples ( )
2023-10-31 16:58:42 -04:00
// 1 successfully scraped sample, 1 stale marker after first fail, 5 report samples for
// each scrape successful or not.
2025-12-22 04:38:48 -05:00
require . Len ( t , got , 27 , "Appended samples not as expected:\n%s" , appTest )
require . Equal ( t , 42.0 , got [ 0 ] . V , "Appended first sample not as expected" )
require . True ( t , value . IsStaleNaN ( got [ 6 ] . V ) ,
"Appended second sample not as expected. Wanted: stale NaN Got: %x" , math . Float64bits ( got [ 6 ] . V ) )
2023-10-31 16:58:42 -04:00
}
2023-11-20 07:02:53 -05:00
func TestScrapeLoopCompression ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopCompression ( t , appV2 )
} )
}
func testScrapeLoopCompression ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
s := teststorage . New ( t )
2023-11-20 07:02:53 -05:00
2025-02-03 09:46:39 -05:00
metricsText := makeTestGauges ( 10 )
2023-11-20 07:02:53 -05:00
for _ , tc := range [ ] struct {
enableCompression bool
acceptEncoding string
} {
{
enableCompression : true ,
acceptEncoding : "gzip" ,
} ,
{
enableCompression : false ,
acceptEncoding : "identity" ,
} ,
} {
t . Run ( fmt . Sprintf ( "compression=%v,acceptEncoding=%s" , tc . enableCompression , tc . acceptEncoding ) , func ( t * testing . T ) {
scraped := make ( chan bool )
ts := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
require . Equal ( t , tc . acceptEncoding , r . Header . Get ( "Accept-Encoding" ) , "invalid value of the Accept-Encoding header" )
2025-12-22 04:38:48 -05:00
_ , _ = fmt . Fprint ( w , string ( metricsText ) )
2023-11-20 07:02:53 -05:00
close ( scraped )
} ) )
defer ts . Close ( )
2025-12-22 04:38:48 -05:00
cfg := & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
JobName : "test" ,
SampleLimit : 100 ,
Scheme : "http" ,
ScrapeInterval : model . Duration ( 100 * time . Millisecond ) ,
ScrapeTimeout : model . Duration ( 100 * time . Millisecond ) ,
EnableCompression : tc . enableCompression ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2023-11-20 07:02:53 -05:00
}
2026-01-21 03:21:56 -05:00
sa := selectAppendable ( s , appV2 )
sp , err := newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2023-11-20 07:02:53 -05:00
require . NoError ( t , err )
defer sp . stop ( )
testURL , err := url . Parse ( ts . URL )
require . NoError ( t , err )
sp . Sync ( [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet { { model . AddressLabel : model . LabelValue ( testURL . Host ) } } ,
} ,
} )
2023-12-07 06:35:01 -05:00
require . Len ( t , sp . ActiveTargets ( ) , 1 )
2023-11-20 07:02:53 -05:00
select {
case <- time . After ( 5 * time . Second ) :
t . Fatalf ( "target was not scraped" )
case <- scraped :
}
} )
}
}
2024-01-17 10:58:54 -05:00
func TestPickSchema ( t * testing . T ) {
tcs := [ ] struct {
factor float64
schema int32
} {
{
factor : 65536 ,
schema : - 4 ,
} ,
{
factor : 256 ,
schema : - 3 ,
} ,
{
factor : 16 ,
schema : - 2 ,
} ,
{
factor : 4 ,
schema : - 1 ,
} ,
{
factor : 2 ,
schema : 0 ,
} ,
{
factor : 1.4 ,
schema : 1 ,
} ,
{
factor : 1.1 ,
schema : 2 ,
} ,
{
factor : 1.09 ,
schema : 3 ,
} ,
{
factor : 1.04 ,
schema : 4 ,
} ,
{
factor : 1.02 ,
schema : 5 ,
} ,
{
factor : 1.01 ,
schema : 6 ,
} ,
{
factor : 1.005 ,
schema : 7 ,
} ,
{
factor : 1.002 ,
schema : 8 ,
} ,
// The default value of native_histogram_min_bucket_factor
{
factor : 0 ,
schema : 8 ,
} ,
}
for _ , tc := range tcs {
schema := pickSchema ( tc . factor )
require . Equal ( t , tc . schema , schema )
}
}
2022-05-31 05:31:20 -04:00
func BenchmarkTargetScraperGzip ( b * testing . B ) {
scenarios := [ ] struct {
metricsCount int
body [ ] byte
} {
{ metricsCount : 1 } ,
{ metricsCount : 100 } ,
{ metricsCount : 1000 } ,
{ metricsCount : 10000 } ,
{ metricsCount : 100000 } ,
}
2025-08-27 08:38:54 -04:00
for i := range scenarios {
2022-05-31 05:31:20 -04:00
var buf bytes . Buffer
var name string
gw := gzip . NewWriter ( & buf )
for j := 0 ; j < scenarios [ i ] . metricsCount ; j ++ {
name = fmt . Sprintf ( "go_memstats_alloc_bytes_total_%d" , j )
2025-12-22 04:38:48 -05:00
_ , _ = fmt . Fprintf ( gw , "# HELP %s Total number of bytes allocated, even if freed.\n" , name )
_ , _ = fmt . Fprintf ( gw , "# TYPE %s counter\n" , name )
_ , _ = fmt . Fprintf ( gw , "%s %d\n" , name , i * j )
2022-05-31 05:31:20 -04:00
}
2025-12-22 04:38:48 -05:00
require . NoError ( b , gw . Close ( ) )
2022-05-31 05:31:20 -04:00
scenarios [ i ] . body = buf . Bytes ( )
}
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . Header ( ) . Set ( "Content-Type" , ` text/plain; version=0.0.4 ` )
w . Header ( ) . Set ( "Content-Encoding" , "gzip" )
for _ , scenario := range scenarios {
if strconv . Itoa ( scenario . metricsCount ) == r . URL . Query ( ) [ "count" ] [ 0 ] {
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( scenario . body )
2022-05-31 05:31:20 -04:00
return
}
}
w . WriteHeader ( http . StatusBadRequest )
} )
server := httptest . NewServer ( handler )
defer server . Close ( )
serverURL , err := url . Parse ( server . URL )
if err != nil {
panic ( err )
}
client , err := config_util . NewClientFromConfig ( config_util . DefaultHTTPClientConfig , "test_job" )
if err != nil {
panic ( err )
}
for _ , scenario := range scenarios {
b . Run ( fmt . Sprintf ( "metrics=%d" , scenario . metricsCount ) , func ( b * testing . B ) {
ts := & targetScraper {
Target : & Target {
labels : labels . FromStrings (
model . SchemeLabel , serverURL . Scheme ,
model . AddressLabel , serverURL . Host ,
) ,
2024-12-21 08:33:08 -05:00
scrapeConfig : & config . ScrapeConfig {
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
Params : url . Values { "count" : [ ] string { strconv . Itoa ( scenario . metricsCount ) } } ,
2024-12-21 08:33:08 -05:00
} ,
2022-05-31 05:31:20 -04:00
} ,
client : client ,
timeout : time . Second ,
}
b . ResetTimer ( )
2025-10-09 11:06:28 -04:00
for b . Loop ( ) {
2022-05-31 05:31:20 -04:00
_ , err = ts . scrape ( context . Background ( ) )
require . NoError ( b , err )
}
} )
}
}
2023-11-16 08:22:28 -05:00
// When a scrape contains multiple instances for the same time series we should increment
// prometheus_target_scrapes_sample_duplicate_timestamp_total metric.
func TestScrapeLoopSeriesAddedDuplicates ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopSeriesAddedDuplicates ( t , appV2 )
} )
}
func testScrapeLoopSeriesAddedDuplicates ( t * testing . T , appV2 bool ) {
sl , _ := newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) )
2023-11-16 08:22:28 -05:00
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
total , added , seriesAdded , err := app . append ( [ ] byte ( "test_metric 1\ntest_metric 2\ntest_metric 3\n" ) , "text/plain" , time . Time { } )
2023-11-16 08:22:28 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2023-11-16 08:22:28 -05:00
require . Equal ( t , 3 , total )
require . Equal ( t , 3 , added )
require . Equal ( t , 1 , seriesAdded )
2024-08-19 05:58:35 -04:00
require . Equal ( t , 2.0 , prom_testutil . ToFloat64 ( sl . metrics . targetScrapeSampleDuplicate ) )
2023-11-16 08:22:28 -05:00
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
total , added , seriesAdded , err = app . append ( [ ] byte ( "test_metric 1\ntest_metric 1\ntest_metric 1\n" ) , "text/plain" , time . Time { } )
2023-11-16 08:22:28 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2023-11-16 08:22:28 -05:00
require . Equal ( t , 3 , total )
require . Equal ( t , 3 , added )
require . Equal ( t , 0 , seriesAdded )
2024-08-19 05:58:35 -04:00
require . Equal ( t , 4.0 , prom_testutil . ToFloat64 ( sl . metrics . targetScrapeSampleDuplicate ) )
2023-11-16 08:22:28 -05:00
2024-08-19 05:58:35 -04:00
// When different timestamps are supplied, multiple samples are accepted.
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
total , added , seriesAdded , err = app . append ( [ ] byte ( "test_metric 1 1001\ntest_metric 1 1002\ntest_metric 1 1003\n" ) , "text/plain" , time . Time { } )
2023-11-16 08:22:28 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2024-08-19 05:58:35 -04:00
require . Equal ( t , 3 , total )
require . Equal ( t , 3 , added )
require . Equal ( t , 0 , seriesAdded )
// Metric is not higher than last time.
require . Equal ( t , 4.0 , prom_testutil . ToFloat64 ( sl . metrics . targetScrapeSampleDuplicate ) )
2023-11-16 08:22:28 -05:00
}
2024-03-27 11:32:37 -04:00
// This tests running a full scrape loop and checking that the scrape option
// `native_histogram_min_bucket_factor` is used correctly.
func TestNativeHistogramMaxSchemaSet ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
for _ , tc := range [ ] struct {
name string
minBucketFactor string
expectedSchema int32
} {
{
name : "min factor not specified" ,
minBucketFactor : "" ,
expectedSchema : 3 , // Factor 1.09.
} ,
{
name : "min factor 1" ,
minBucketFactor : "native_histogram_min_bucket_factor: 1" ,
expectedSchema : 3 , // Factor 1.09.
} ,
{
name : "min factor 2" ,
minBucketFactor : "native_histogram_min_bucket_factor: 2" ,
expectedSchema : 0 , // Factor 2.00.
} ,
} {
t . Run ( tc . name , func ( t * testing . T ) {
t . Parallel ( )
testNativeHistogramMaxSchemaSet ( t , tc . minBucketFactor , tc . expectedSchema , appV2 )
} )
}
} )
2024-03-27 11:32:37 -04:00
}
2026-01-21 03:21:56 -05:00
func testNativeHistogramMaxSchemaSet ( t * testing . T , minBucketFactor string , expectedSchema int32 , appV2 bool ) {
2024-03-27 11:32:37 -04:00
// Create a ProtoBuf message to serve as a Prometheus metric.
nativeHistogram := prometheus . NewHistogram (
prometheus . HistogramOpts {
Namespace : "testing" ,
Name : "example_native_histogram" ,
Help : "This is used for testing" ,
NativeHistogramBucketFactor : 1.1 ,
NativeHistogramMaxBucketNumber : 100 ,
} ,
)
registry := prometheus . NewRegistry ( )
2025-12-22 04:38:48 -05:00
require . NoError ( t , registry . Register ( nativeHistogram ) )
2024-03-27 11:32:37 -04:00
nativeHistogram . Observe ( 1.0 )
nativeHistogram . Observe ( 1.0 )
nativeHistogram . Observe ( 1.0 )
nativeHistogram . Observe ( 10.0 ) // in different bucket since > 1*1.1.
nativeHistogram . Observe ( 10.0 )
gathered , err := registry . Gather ( )
require . NoError ( t , err )
require . NotEmpty ( t , gathered )
histogramMetricFamily := gathered [ 0 ]
buffer := protoMarshalDelimited ( t , histogramMetricFamily )
2025-12-22 04:38:48 -05:00
// Create an HTTP server to serve /metrics via ProtoBuf
2025-02-10 02:06:58 -05:00
metricsServer := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , _ * http . Request ) {
2024-03-27 11:32:37 -04:00
w . Header ( ) . Set ( "Content-Type" , ` application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited ` )
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( buffer )
2024-03-27 11:32:37 -04:00
} ) )
defer metricsServer . Close ( )
// Create a scrape loop with the HTTP server as the target.
configStr := fmt . Sprintf ( `
global :
2024-08-21 10:38:27 -04:00
metric_name_validation_scheme : legacy
2024-09-26 12:35:15 -04:00
scrape_interval : 50 ms
scrape_timeout : 25 ms
2024-03-27 11:32:37 -04:00
scrape_configs :
- job_name : test
2025-10-09 10:56:13 -04:00
scrape_native_histograms : true
2024-03-27 11:32:37 -04:00
% s
static_configs :
- targets : [ % s ]
` , minBucketFactor , strings . ReplaceAll ( metricsServer . URL , "http://" , "" ) )
s := teststorage . New ( t )
reg := prometheus . NewRegistry ( )
2026-01-23 04:04:05 -05:00
sa := selectAppendable ( s , appV2 )
mng , err := NewManager ( & Options { DiscoveryReloadInterval : model . Duration ( 10 * time . Millisecond ) } , nil , nil , sa . V1 ( ) , sa . V2 ( ) , reg )
2024-03-27 11:32:37 -04:00
require . NoError ( t , err )
2026-01-21 03:21:56 -05:00
2024-10-17 04:25:05 -04:00
cfg , err := config . Load ( configStr , promslog . NewNopLogger ( ) )
2024-03-27 11:32:37 -04:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , mng . ApplyConfig ( cfg ) )
2024-03-27 11:32:37 -04:00
tsets := make ( chan map [ string ] [ ] * targetgroup . Group )
go func ( ) {
2025-12-22 04:38:48 -05:00
require . NoError ( t , mng . Run ( tsets ) )
2024-03-27 11:32:37 -04:00
} ( )
defer mng . Stop ( )
// Get the static targets and apply them to the scrape manager.
require . Len ( t , cfg . ScrapeConfigs , 1 )
scrapeCfg := cfg . ScrapeConfigs [ 0 ]
require . Len ( t , scrapeCfg . ServiceDiscoveryConfigs , 1 )
staticDiscovery , ok := scrapeCfg . ServiceDiscoveryConfigs [ 0 ] . ( discovery . StaticConfig )
require . True ( t , ok )
require . Len ( t , staticDiscovery , 1 )
tsets <- map [ string ] [ ] * targetgroup . Group { "test" : staticDiscovery }
// Wait for the scrape loop to scrape the target.
require . Eventually ( t , func ( ) bool {
q , err := s . Querier ( 0 , math . MaxInt64 )
require . NoError ( t , err )
seriesS := q . Select ( context . Background ( ) , false , nil , labels . MustNewMatcher ( labels . MatchEqual , "__name__" , "testing_example_native_histogram" ) )
countSeries := 0
for seriesS . Next ( ) {
countSeries ++
}
return countSeries > 0
2024-09-26 12:35:15 -04:00
} , 5 * time . Second , 100 * time . Millisecond )
2024-03-27 11:32:37 -04:00
// Check that native histogram schema is as expected.
q , err := s . Querier ( 0 , math . MaxInt64 )
require . NoError ( t , err )
seriesS := q . Select ( context . Background ( ) , false , nil , labels . MustNewMatcher ( labels . MatchEqual , "__name__" , "testing_example_native_histogram" ) )
2025-12-22 04:38:48 -05:00
var histogramSamples [ ] * histogram . Histogram
2024-03-27 11:32:37 -04:00
for seriesS . Next ( ) {
series := seriesS . At ( )
it := series . Iterator ( nil )
for vt := it . Next ( ) ; vt != chunkenc . ValNone ; vt = it . Next ( ) {
if vt != chunkenc . ValHistogram {
// don't care about other samples
continue
}
_ , h := it . AtHistogram ( nil )
histogramSamples = append ( histogramSamples , h )
}
}
require . NoError ( t , seriesS . Err ( ) )
require . NotEmpty ( t , histogramSamples )
for _ , h := range histogramSamples {
require . Equal ( t , expectedSchema , h . Schema )
}
}
2022-07-15 09:20:44 -04:00
func TestTargetScrapeConfigWithLabels ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testTargetScrapeConfigWithLabels ( t , appV2 )
} )
}
func testTargetScrapeConfigWithLabels ( t * testing . T , appV2 bool ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2022-07-15 09:20:44 -04:00
const (
configTimeout = 1500 * time . Millisecond
expectedTimeout = "1.5"
expectedTimeoutLabel = "1s500ms"
secondTimeout = 500 * time . Millisecond
secondTimeoutLabel = "500ms"
expectedParam = "value1"
secondParam = "value2"
expectedPath = "/metric-ok"
secondPath = "/metric-nok"
httpScheme = "http"
paramLabel = "__param_param"
jobName = "test"
)
createTestServer := func ( t * testing . T , done chan struct { } ) * url . URL {
server := httptest . NewServer (
http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
defer close ( done )
require . Equal ( t , expectedTimeout , r . Header . Get ( "X-Prometheus-Scrape-Timeout-Seconds" ) )
require . Equal ( t , expectedParam , r . URL . Query ( ) . Get ( "param" ) )
require . Equal ( t , expectedPath , r . URL . Path )
w . Header ( ) . Set ( "Content-Type" , ` text/plain; version=0.0.4 ` )
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte ( "metric_a 1\nmetric_b 2\n" ) )
2022-07-15 09:20:44 -04:00
} ) ,
)
t . Cleanup ( server . Close )
serverURL , err := url . Parse ( server . URL )
require . NoError ( t , err )
return serverURL
}
run := func ( t * testing . T , cfg * config . ScrapeConfig , targets [ ] * targetgroup . Group ) chan struct { } {
done := make ( chan struct { } )
srvURL := createTestServer ( t , done )
// Update target addresses to use the dynamically created server URL.
for _ , target := range targets {
for i := range target . Targets {
target . Targets [ i ] [ model . AddressLabel ] = model . LabelValue ( srvURL . Host )
}
}
2026-01-21 03:21:56 -05:00
sa := selectAppendable ( teststorage . NewAppendable ( ) , appV2 )
sp , err := newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2022-07-15 09:20:44 -04:00
require . NoError ( t , err )
t . Cleanup ( sp . stop )
sp . Sync ( targets )
return done
}
cases := [ ] struct {
name string
cfg * config . ScrapeConfig
targets [ ] * targetgroup . Group
} {
{
name : "Everything in scrape config" ,
cfg : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
ScrapeInterval : model . Duration ( 2 * time . Second ) ,
ScrapeTimeout : model . Duration ( configTimeout ) ,
Params : url . Values { "param" : [ ] string { expectedParam } } ,
JobName : jobName ,
Scheme : httpScheme ,
MetricsPath : expectedPath ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
2022-07-15 09:20:44 -04:00
} ,
targets : [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet {
{ model . AddressLabel : model . LabelValue ( "" ) } ,
} ,
} ,
} ,
} ,
{
name : "Overridden in target" ,
cfg : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
ScrapeInterval : model . Duration ( 2 * time . Second ) ,
ScrapeTimeout : model . Duration ( secondTimeout ) ,
JobName : jobName ,
Scheme : httpScheme ,
MetricsPath : secondPath ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
Params : url . Values { "param" : [ ] string { secondParam } } ,
2022-07-15 09:20:44 -04:00
} ,
targets : [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet {
{
model . AddressLabel : model . LabelValue ( "" ) ,
model . ScrapeTimeoutLabel : expectedTimeoutLabel ,
model . MetricsPathLabel : expectedPath ,
paramLabel : expectedParam ,
} ,
} ,
} ,
} ,
} ,
{
name : "Overridden in relabel_config" ,
cfg : & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
ScrapeInterval : model . Duration ( 2 * time . Second ) ,
ScrapeTimeout : model . Duration ( secondTimeout ) ,
JobName : jobName ,
Scheme : httpScheme ,
MetricsPath : secondPath ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
Params : url . Values { "param" : [ ] string { secondParam } } ,
2022-07-15 09:20:44 -04:00
RelabelConfigs : [ ] * relabel . Config {
{
2025-08-18 04:09:00 -04:00
Action : relabel . DefaultRelabelConfig . Action ,
Regex : relabel . DefaultRelabelConfig . Regex ,
SourceLabels : relabel . DefaultRelabelConfig . SourceLabels ,
TargetLabel : model . ScrapeTimeoutLabel ,
Replacement : expectedTimeoutLabel ,
NameValidationScheme : model . UTF8Validation ,
2022-07-15 09:20:44 -04:00
} ,
{
2025-08-18 04:09:00 -04:00
Action : relabel . DefaultRelabelConfig . Action ,
Regex : relabel . DefaultRelabelConfig . Regex ,
SourceLabels : relabel . DefaultRelabelConfig . SourceLabels ,
TargetLabel : paramLabel ,
Replacement : expectedParam ,
NameValidationScheme : model . UTF8Validation ,
2022-07-15 09:20:44 -04:00
} ,
{
2025-08-18 04:09:00 -04:00
Action : relabel . DefaultRelabelConfig . Action ,
Regex : relabel . DefaultRelabelConfig . Regex ,
SourceLabels : relabel . DefaultRelabelConfig . SourceLabels ,
TargetLabel : model . MetricsPathLabel ,
Replacement : expectedPath ,
NameValidationScheme : model . UTF8Validation ,
2022-07-15 09:20:44 -04:00
} ,
} ,
} ,
targets : [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet {
{
model . AddressLabel : model . LabelValue ( "" ) ,
model . ScrapeTimeoutLabel : secondTimeoutLabel ,
model . MetricsPathLabel : secondPath ,
paramLabel : secondParam ,
} ,
} ,
} ,
} ,
} ,
}
for _ , c := range cases {
t . Run ( c . name , func ( t * testing . T ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2022-07-15 09:20:44 -04:00
select {
case <- run ( t , c . cfg , c . targets ) :
case <- time . After ( 10 * time . Second ) :
t . Fatal ( "timeout after 10 seconds" )
}
} )
}
}
2024-09-10 14:51:20 -04:00
2024-09-09 10:46:31 -04:00
func newScrapableServer ( scrapeText string ) ( s * httptest . Server , scrapedTwice chan bool ) {
var scrapes int
scrapedTwice = make ( chan bool )
2025-02-10 02:06:58 -05:00
return httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , _ * http . Request ) {
2025-12-22 04:38:48 -05:00
_ , _ = fmt . Fprint ( w , scrapeText )
2024-09-09 10:46:31 -04:00
scrapes ++
if scrapes == 2 {
close ( scrapedTwice )
}
} ) ) , scrapedTwice
}
2024-12-02 09:08:37 -05:00
2024-12-09 10:36:36 -05:00
// Regression test for the panic fixed in https://github.com/prometheus/prometheus/pull/15523.
func TestScrapePoolScrapeAfterReload ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapePoolScrapeAfterReload ( t , appV2 )
} )
}
func testScrapePoolScrapeAfterReload ( t * testing . T , appV2 bool ) {
2024-12-09 01:17:32 -05:00
h := httptest . NewServer ( http . HandlerFunc (
2025-02-10 02:06:58 -05:00
func ( w http . ResponseWriter , _ * http . Request ) {
2025-12-22 04:38:48 -05:00
_ , _ = w . Write ( [ ] byte { 0x42 , 0x42 } )
2024-12-09 01:17:32 -05:00
} ,
) )
2024-12-02 09:08:37 -05:00
t . Cleanup ( h . Close )
cfg := & config . ScrapeConfig {
2025-03-26 18:27:28 -04:00
BodySizeLimit : 1 ,
JobName : "test" ,
Scheme : "http" ,
ScrapeInterval : model . Duration ( 100 * time . Millisecond ) ,
ScrapeTimeout : model . Duration ( 100 * time . Millisecond ) ,
2025-07-03 09:37:46 -04:00
MetricNameValidationScheme : model . UTF8Validation ,
2025-03-26 18:27:28 -04:00
MetricNameEscapingScheme : model . AllowUTF8 ,
EnableCompression : false ,
2024-12-02 09:08:37 -05:00
ServiceDiscoveryConfigs : discovery . Configs {
& discovery . StaticConfig {
{
Targets : [ ] model . LabelSet { { model . AddressLabel : model . LabelValue ( h . URL ) } } ,
} ,
} ,
} ,
}
2026-01-21 03:21:56 -05:00
sa := selectAppendable ( teststorage . NewAppendable ( ) , appV2 )
p , err := newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
2024-12-02 09:08:37 -05:00
require . NoError ( t , err )
t . Cleanup ( p . stop )
p . Sync ( [ ] * targetgroup . Group {
{
Targets : [ ] model . LabelSet { { model . AddressLabel : model . LabelValue ( strings . TrimPrefix ( h . URL , "http://" ) ) } } ,
Source : "test" ,
} ,
} )
require . NoError ( t , p . reload ( cfg ) )
<- time . After ( 1 * time . Second )
}
2025-03-08 13:45:37 -05:00
// Regression test against https://github.com/prometheus/prometheus/issues/16160.
// The first scrape fails with a parsing error, but the second should
// succeed and cause `metric_1=11` to appear in the appender.
func TestScrapeAppendWithParseError ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeAppendWithParseError ( t , appV2 )
} )
}
func testScrapeAppendWithParseError ( t * testing . T , appV2 bool ) {
2025-03-08 13:45:37 -05:00
const (
scrape1 = ` metric_a 1
`
scrape2 = ` metric_a 11
# EOF `
)
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
2025-03-08 13:45:37 -05:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
_ , _ , _ , err := app . append ( [ ] byte ( scrape1 ) , "application/openmetrics-text" , now )
2025-03-08 13:45:37 -05:00
require . Error ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Rollback ( ) )
app = sl . appender ( )
_ , _ , _ , err = app . append ( nil , "application/openmetrics-text" , now )
2025-03-08 13:45:37 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
require . Empty ( t , appTest . ResultSamples ( ) )
2025-03-08 13:45:37 -05:00
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
_ , _ , _ , err = app . append ( [ ] byte ( scrape2 ) , "application/openmetrics-text" , now . Add ( 15 * time . Second ) )
2025-03-08 13:45:37 -05:00
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2025-03-08 13:45:37 -05:00
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2025-03-08 13:45:37 -05:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" ) ,
T : timestamp . FromTime ( now . Add ( 15 * time . Second ) ) ,
V : 11 ,
2025-03-08 13:45:37 -05:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , appTest )
2025-03-08 13:45:37 -05:00
}
2025-04-14 10:42:19 -04:00
2025-12-22 04:38:48 -05:00
// This test covers a case where there's a target with sample_limit set and some samples
2025-04-14 10:42:19 -04:00
// changes between scrapes.
func TestScrapeLoopAppendSampleLimitWithDisappearingSeries ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendSampleLimitWithDisappearingSeries ( t , appV2 )
} )
}
func testScrapeLoopAppendSampleLimitWithDisappearingSeries ( t * testing . T , appV2 bool ) {
2025-04-14 10:42:19 -04:00
const sampleLimit = 4
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . sampleLimit = sampleLimit
} )
2025-04-14 10:42:19 -04:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
samplesScraped , samplesAfterRelabel , createdSeries , err := app . append (
2025-04-14 10:42:19 -04:00
// Start with 3 samples, all accepted.
[ ] byte ( "metric_a 1\nmetric_b 1\nmetric_c 1\n" ) ,
"text/plain" ,
now ,
)
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2025-04-14 10:42:19 -04:00
require . Equal ( t , 3 , samplesScraped ) // All on scrape.
require . Equal ( t , 3 , samplesAfterRelabel ) // This is series after relabeling.
require . Equal ( t , 3 , createdSeries ) // Newly added to TSDB.
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2025-04-14 10:42:19 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_b" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_c" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , app )
2025-04-14 10:42:19 -04:00
now = now . Add ( time . Minute )
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
samplesScraped , samplesAfterRelabel , createdSeries , err = app . append (
2025-04-14 10:42:19 -04:00
// Start exporting 3 more samples, so we're over the limit now.
[ ] byte ( "metric_a 1\nmetric_b 1\nmetric_c 1\nmetric_d 1\nmetric_e 1\nmetric_f 1\n" ) ,
"text/plain" ,
now ,
)
require . ErrorIs ( t , err , errSampleLimit )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Rollback ( ) )
2025-04-14 10:42:19 -04:00
require . Equal ( t , 6 , samplesScraped )
require . Equal ( t , 6 , samplesAfterRelabel )
require . Equal ( t , 1 , createdSeries ) // We've added one series before hitting the limit.
2026-01-21 03:21:56 -05:00
testutil . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , app )
2025-04-14 10:42:19 -04:00
sl . cache . iterDone ( false )
now = now . Add ( time . Minute )
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
samplesScraped , samplesAfterRelabel , createdSeries , err = app . append (
2025-04-14 10:42:19 -04:00
// Remove all samples except first 2.
[ ] byte ( "metric_a 1\nmetric_b 1\n" ) ,
"text/plain" ,
now ,
)
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2025-04-14 10:42:19 -04:00
require . Equal ( t , 2 , samplesScraped )
require . Equal ( t , 2 , samplesAfterRelabel )
require . Equal ( t , 0 , createdSeries )
// This is where important things happen. We should now see:
// - Appends for samples from metric_a & metric_b.
// - Append with stale markers for metric_c - this series was added during first scrape but disappeared during last scrape.
// - Append with stale marker for metric_d - this series was added during second scrape before we hit the sample_limit.
// We should NOT see:
2025-12-22 04:38:48 -05:00
// - Appends with stale markers for metric_e & metric_f - both over the limit during second scrape, and so they never made it into TSDB.
want = append ( want , [ ] sample {
2025-04-14 10:42:19 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_b" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_c" ) ,
T : timestamp . FromTime ( now ) ,
V : math . Float64frombits ( value . StaleNaN ) ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_d" ) ,
T : timestamp . FromTime ( now ) ,
V : math . Float64frombits ( value . StaleNaN ) ,
2025-04-14 10:42:19 -04:00
} ,
} ... )
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , appTest )
2025-04-14 10:42:19 -04:00
}
// This test covers a case where there's a target with sample_limit set and each scrape sees a completely
// different set of samples.
func TestScrapeLoopAppendSampleLimitReplaceAllSamples ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopAppendSampleLimitReplaceAllSamples ( t , appV2 )
} )
}
func testScrapeLoopAppendSampleLimitReplaceAllSamples ( t * testing . T , appV2 bool ) {
2025-04-14 10:42:19 -04:00
const sampleLimit = 4
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) , func ( sl * scrapeLoop ) {
2025-12-22 04:38:48 -05:00
sl . sampleLimit = sampleLimit
} )
2025-04-14 10:42:19 -04:00
now := time . Now ( )
2025-12-22 04:38:48 -05:00
app := sl . appender ( )
samplesScraped , samplesAfterRelabel , createdSeries , err := app . append (
2025-04-14 10:42:19 -04:00
// Start with 4 samples, all accepted.
[ ] byte ( "metric_a 1\nmetric_b 1\nmetric_c 1\nmetric_d 1\n" ) ,
"text/plain" ,
now ,
)
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2025-04-14 10:42:19 -04:00
require . Equal ( t , 4 , samplesScraped ) // All on scrape.
require . Equal ( t , 4 , samplesAfterRelabel ) // This is series after relabeling.
require . Equal ( t , 4 , createdSeries ) // Newly added to TSDB.
2025-12-22 04:38:48 -05:00
want := [ ] sample {
2025-04-14 10:42:19 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_b" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_c" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_d" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
}
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , app )
2025-04-14 10:42:19 -04:00
now = now . Add ( time . Minute )
2025-12-22 04:38:48 -05:00
app = sl . appender ( )
samplesScraped , samplesAfterRelabel , createdSeries , err = app . append (
2025-04-14 10:42:19 -04:00
// Replace all samples with new time series.
[ ] byte ( "metric_e 1\nmetric_f 1\nmetric_g 1\nmetric_h 1\n" ) ,
"text/plain" ,
now ,
)
require . NoError ( t , err )
2025-12-22 04:38:48 -05:00
require . NoError ( t , app . Commit ( ) )
2025-04-14 10:42:19 -04:00
require . Equal ( t , 4 , samplesScraped )
require . Equal ( t , 4 , samplesAfterRelabel )
require . Equal ( t , 4 , createdSeries )
// We replaced all samples from first scrape with new set of samples.
2025-12-22 04:38:48 -05:00
// We expected to see:
2025-04-14 10:42:19 -04:00
// - 4 appends for new samples.
// - 4 appends with staleness markers for old samples.
2025-12-22 04:38:48 -05:00
want = append ( want , [ ] sample {
2025-04-14 10:42:19 -04:00
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_e" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_f" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_g" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_h" ) ,
T : timestamp . FromTime ( now ) ,
V : 1 ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_a" ) ,
T : timestamp . FromTime ( now ) ,
V : math . Float64frombits ( value . StaleNaN ) ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_b" ) ,
T : timestamp . FromTime ( now ) ,
V : math . Float64frombits ( value . StaleNaN ) ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_c" ) ,
T : timestamp . FromTime ( now ) ,
V : math . Float64frombits ( value . StaleNaN ) ,
2025-04-14 10:42:19 -04:00
} ,
{
2025-12-22 04:38:48 -05:00
L : labels . FromStrings ( model . MetricNameLabel , "metric_d" ) ,
T : timestamp . FromTime ( now ) ,
V : math . Float64frombits ( value . StaleNaN ) ,
2025-04-14 10:42:19 -04:00
} ,
} ... )
2026-01-21 03:21:56 -05:00
teststorage . RequireEqual ( t , want , appTest . ResultSamples ( ) , "Appended samples not as expected:\n%s" , app )
2025-04-14 10:42:19 -04:00
}
2025-10-30 06:58:31 -04:00
func TestScrapeLoopDisableStalenessMarkerInjection ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testScrapeLoopDisableStalenessMarkerInjection ( t , appV2 )
} )
}
func testScrapeLoopDisableStalenessMarkerInjection ( t * testing . T , appV2 bool ) {
2025-12-22 04:38:48 -05:00
loopDone := atomic . NewBool ( false )
2025-10-30 06:58:31 -04:00
2025-12-22 04:38:48 -05:00
appTest := teststorage . NewAppendable ( )
2026-01-21 03:21:56 -05:00
sl , scraper := newTestScrapeLoop ( t , withAppendable ( appTest , appV2 ) )
2025-10-30 06:58:31 -04:00
scraper . scrapeFunc = func ( ctx context . Context , w io . Writer ) error {
if _ , err := w . Write ( [ ] byte ( "metric_a 42\n" ) ) ; err != nil {
return err
}
return ctx . Err ( )
}
// Start the scrape loop.
go func ( ) {
sl . run ( nil )
loopDone . Store ( true )
} ( )
// Wait for some samples to be appended.
require . Eventually ( t , func ( ) bool {
2025-12-22 04:38:48 -05:00
return len ( appTest . ResultSamples ( ) ) > 2
2025-10-30 06:58:31 -04:00
} , 5 * time . Second , 100 * time . Millisecond , "Scrape loop didn't append any samples." )
// Disable end of run staleness markers and stop the loop.
sl . disableEndOfRunStalenessMarkers ( )
sl . stop ( )
require . Eventually ( t , func ( ) bool {
return loopDone . Load ( )
} , 5 * time . Second , 100 * time . Millisecond , "Scrape loop didn't stop." )
// No stale markers should be appended, since they were disabled.
2025-12-22 04:38:48 -05:00
for _ , s := range appTest . ResultSamples ( ) {
if value . IsStaleNaN ( s . V ) {
t . Fatalf ( "Got stale NaN samples while end of run staleness is disabled: %x" , math . Float64bits ( s . V ) )
2025-10-30 06:58:31 -04:00
}
}
}
2025-12-22 04:38:48 -05:00
// Recommended CLI invocation:
/ *
export bench = restartLoops && go test . / scrape / ... \
- run ' ^ $ ' - bench ' ^ BenchmarkScrapePoolRestartLoops ' \
- benchtime 5 s - count 6 - cpu 2 - timeout 999 m \
| tee $ { bench } . txt
* /
func BenchmarkScrapePoolRestartLoops ( b * testing . B ) {
sp , err := newScrapePool (
& config . ScrapeConfig {
MetricNameValidationScheme : model . UTF8Validation ,
ScrapeInterval : model . Duration ( 1 * time . Hour ) ,
ScrapeTimeout : model . Duration ( 1 * time . Hour ) ,
} ,
nil ,
2026-01-21 03:21:56 -05:00
nil ,
2025-12-22 04:38:48 -05:00
0 ,
nil ,
nil ,
& Options { } ,
newTestScrapeMetrics ( b ) ,
)
require . NoError ( b , err )
b . Cleanup ( sp . stop )
for i := range 1000 {
sp . activeTargets [ uint64 ( i ) ] = & Target { scrapeConfig : & config . ScrapeConfig { } }
sp . loops [ uint64 ( i ) ] = noopLoop ( ) // First restart will supplement those with proper scrapeLoops.
}
sp . restartLoops ( true )
b . ReportAllocs ( )
b . ResetTimer ( )
for b . Loop ( ) {
sp . restartLoops ( true )
}
}
2025-12-22 10:28:08 -05:00
// TestNewScrapeLoopHonorLabelsWiring verifies that newScrapeLoop correctly wires
// HonorLabels (not HonorTimestamps) to the sampleMutator.
func TestNewScrapeLoopHonorLabelsWiring ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testNewScrapeLoopHonorLabelsWiring ( t , appV2 )
} )
}
func testNewScrapeLoopHonorLabelsWiring ( t * testing . T , appV2 bool ) {
2025-12-22 10:28:08 -05:00
// Scraped metric has label "lbl" with value "scraped".
// Discovery target has label "lbl" with value "discovery".
// With honor_labels=true, the scraped value should win.
// With honor_labels=false, the discovery value should win and scraped moves to exported_lbl.
testCases := [ ] struct {
name string
honorLabels bool
expectedLbl string
expectedExpLbl string // exported_lbl value, empty if not expected
} {
{
name : "honor_labels=true" ,
honorLabels : true ,
expectedLbl : "scraped" ,
} ,
{
name : "honor_labels=false" ,
honorLabels : false ,
expectedLbl : "discovery" ,
expectedExpLbl : "scraped" ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
ts , scrapedTwice := newScrapableServer ( ` metric { lbl="scraped"} 1 ` )
defer ts . Close ( )
testURL , err := url . Parse ( ts . URL )
require . NoError ( t , err )
s := teststorage . New ( t )
cfg := & config . ScrapeConfig {
JobName : "test" ,
Scheme : "http" ,
HonorLabels : tc . honorLabels ,
HonorTimestamps : ! tc . honorLabels , // Opposite of HonorLabels to catch wiring bugs
ScrapeInterval : model . Duration ( 1 * time . Second ) ,
ScrapeTimeout : model . Duration ( 100 * time . Millisecond ) ,
MetricNameValidationScheme : model . UTF8Validation ,
}
2026-01-21 03:21:56 -05:00
sa := selectAppendable ( s , appV2 )
sp , err := newScrapePool ( cfg , sa . V1 ( ) , sa . V2 ( ) , 0 , nil , nil , & Options { skipOffsetting : true } , newTestScrapeMetrics ( t ) )
2025-12-22 10:28:08 -05:00
require . NoError ( t , err )
defer sp . stop ( )
// Sync with a target that has a conflicting label.
sp . Sync ( [ ] * targetgroup . Group { {
Targets : [ ] model . LabelSet { {
model . AddressLabel : model . LabelValue ( testURL . Host ) ,
"lbl" : "discovery" ,
} } ,
} } )
require . Len ( t , sp . ActiveTargets ( ) , 1 )
// Wait for scrape to complete.
select {
case <- time . After ( 5 * time . Second ) :
t . Fatal ( "scrape did not complete in time" )
case <- scrapedTwice :
}
// Query the storage to verify label values.
q , err := s . Querier ( time . Time { } . UnixNano ( ) , time . Now ( ) . UnixNano ( ) )
require . NoError ( t , err )
defer q . Close ( )
series := q . Select ( t . Context ( ) , false , nil , labels . MustNewMatcher ( labels . MatchEqual , "__name__" , "metric" ) )
require . True ( t , series . Next ( ) , "metric series not found" )
require . Equal ( t , tc . expectedLbl , series . At ( ) . Labels ( ) . Get ( "lbl" ) )
require . Equal ( t , tc . expectedExpLbl , series . At ( ) . Labels ( ) . Get ( "exported_lbl" ) )
} )
}
}
2026-01-07 09:27:01 -05:00
func TestDropsSeriesFromMetricRelabeling ( t * testing . T ) {
2026-01-21 03:21:56 -05:00
foreachAppendable ( t , func ( t * testing . T , appV2 bool ) {
testDropsSeriesFromMetricRelabeling ( t , appV2 )
} )
}
func testDropsSeriesFromMetricRelabeling ( t * testing . T , appV2 bool ) {
2026-01-07 09:27:01 -05:00
target := & Target { }
relabelConfig := [ ] * relabel . Config {
{
SourceLabels : model . LabelNames { "__name__" } ,
Regex : relabel . MustNewRegexp ( "test_metric.*$" ) ,
Action : relabel . Keep ,
NameValidationScheme : model . UTF8Validation ,
} ,
{
SourceLabels : model . LabelNames { "__name__" } ,
Regex : relabel . MustNewRegexp ( "test_metric_2$" ) ,
Action : relabel . Drop ,
NameValidationScheme : model . UTF8Validation ,
} ,
}
2026-01-21 03:21:56 -05:00
sl , _ := newTestScrapeLoop ( t , withAppendable ( teststorage . NewAppendable ( ) , appV2 ) , func ( sl * scrapeLoop ) {
2026-01-07 09:27:01 -05:00
sl . sampleMutator = func ( l labels . Labels ) labels . Labels {
return mutateSampleLabels ( l , target , true , relabelConfig )
}
} )
app := sl . appender ( )
total , added , seriesAdded , err := app . append ( [ ] byte ( "test_metric_1 1\n" ) , "text/plain" , time . Time { } )
require . NoError ( t , err )
require . Equal ( t , 1 , total )
require . Equal ( t , 1 , added )
require . Equal ( t , 1 , seriesAdded )
total , added , seriesAdded , err = app . append ( [ ] byte ( "test_metric_2 1\n" ) , "text/plain" , time . Time { } )
require . NoError ( t , err )
require . Equal ( t , 1 , total )
require . Equal ( t , 0 , added )
require . Equal ( t , 0 , seriesAdded )
total , added , seriesAdded , err = app . append ( [ ] byte ( "unwanted_metric 1\n" ) , "text/plain" , time . Time { } )
require . NoError ( t , err )
require . Equal ( t , 1 , total )
require . Equal ( t , 0 , added )
require . Equal ( t , 0 , seriesAdded )
require . NoError ( t , app . Commit ( ) )
}
2026-02-20 11:23:18 -05:00
// noopFailureLogger is a minimal FailureLogger implementation for testing.
type noopFailureLogger struct { }
func ( noopFailureLogger ) Enabled ( context . Context , slog . Level ) bool { return true }
func ( noopFailureLogger ) Handle ( context . Context , slog . Record ) error { return nil }
func ( noopFailureLogger ) WithAttrs ( [ ] slog . Attr ) slog . Handler { return noopFailureLogger { } }
func ( noopFailureLogger ) WithGroup ( string ) slog . Handler { return noopFailureLogger { } }
func ( noopFailureLogger ) Close ( ) error { return nil }
// TestScrapePoolSetScrapeFailureLoggerRace is a regression test for concurrent
// access to scrapeFailureLogger. Both must use targetMtx for synchronization.
func TestScrapePoolSetScrapeFailureLoggerRace ( t * testing . T ) {
var (
app = teststorage . NewAppendable ( )
cfg = & config . ScrapeConfig {
JobName : "test" ,
ScrapeInterval : model . Duration ( 100 * time . Millisecond ) ,
ScrapeTimeout : model . Duration ( 50 * time . Millisecond ) ,
MetricNameValidationScheme : model . UTF8Validation ,
MetricNameEscapingScheme : model . AllowUTF8 ,
}
sp , err = newScrapePool ( cfg , app , nil , 0 , nil , nil , & Options { } , newTestScrapeMetrics ( t ) )
)
require . NoError ( t , err )
defer sp . stop ( )
// Create a target group with a target.
tg := & targetgroup . Group {
Targets : [ ] model . LabelSet {
{ model . AddressLabel : "127.0.0.1:9090" } ,
} ,
}
var wg sync . WaitGroup
wg . Go ( func ( ) {
for range 100 {
sp . SetScrapeFailureLogger ( noopFailureLogger { } )
sp . SetScrapeFailureLogger ( nil )
}
} )
wg . Go ( func ( ) {
for range 100 {
sp . Sync ( [ ] * targetgroup . Group { tg } )
}
} )
wg . Wait ( )
}