2025-12-22 04:38:48 -05:00
// Copyright The Prometheus Authors
2013-01-15 11:06:17 -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-08-25 14:36:26 -04:00
import (
2024-10-02 06:52:03 -04:00
"bytes"
2022-12-23 05:55:08 -05:00
"context"
2024-11-03 07:15:51 -05:00
"errors"
2023-12-11 03:43:42 -05:00
"fmt"
2024-12-21 08:33:08 -05:00
"maps"
2019-04-10 08:20:00 -04:00
"net/http"
2023-12-11 03:43:42 -05:00
"net/http/httptest"
"net/url"
"os"
2025-03-08 06:28:05 -05:00
"slices"
2023-11-15 05:41:12 -05:00
"sort"
2018-09-26 05:20:56 -04:00
"strconv"
2023-12-11 03:43:42 -05:00
"sync"
2016-08-25 14:36:26 -04:00
"testing"
2018-09-26 05:20:56 -04:00
"time"
2016-08-25 14:36:26 -04:00
2023-12-11 03:43:42 -05:00
"github.com/gogo/protobuf/proto"
2023-09-22 12:47:44 -04:00
"github.com/prometheus/client_golang/prometheus"
2023-12-11 03:43:42 -05:00
dto "github.com/prometheus/client_model/go"
2024-10-02 06:52:03 -04:00
"github.com/prometheus/common/expfmt"
2016-09-05 08:17:10 -04: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-09-06 07:04:24 -04:00
"go.yaml.in/yaml/v2"
2023-12-11 03:43:42 -05:00
"google.golang.org/protobuf/types/known/timestamppb"
2019-03-25 19:01:12 -04:00
2016-08-25 14:36:26 -04:00
"github.com/prometheus/prometheus/config"
2022-12-23 05:55:08 -05:00
"github.com/prometheus/prometheus/discovery"
2023-11-15 05:41:12 -05:00
_ "github.com/prometheus/prometheus/discovery/file"
2018-09-26 05:20:56 -04:00
"github.com/prometheus/prometheus/discovery/targetgroup"
2024-07-19 10:28:00 -04:00
"github.com/prometheus/prometheus/model/histogram"
2021-11-08 09:23:17 -05:00
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/relabel"
2025-03-22 11:46:13 -04:00
"github.com/prometheus/prometheus/model/timestamp"
"github.com/prometheus/prometheus/storage"
2024-07-19 10:28:00 -04:00
"github.com/prometheus/prometheus/tsdb/tsdbutil"
2023-12-11 03:43:42 -05:00
"github.com/prometheus/prometheus/util/runutil"
2025-12-22 04:38:48 -05:00
"github.com/prometheus/prometheus/util/teststorage"
2024-01-24 11:48:22 -05:00
"github.com/prometheus/prometheus/util/testutil"
2016-08-25 14:36:26 -04:00
)
2016-09-05 08:17:10 -04:00
func TestPopulateLabels ( t * testing . T ) {
cases := [ ] struct {
2024-12-21 08:33:08 -05:00
in model . LabelSet
2024-09-14 14:04:33 -04:00
cfg * config . ScrapeConfig
res labels . Labels
resOrig labels . Labels
err string
2016-09-05 08:17:10 -04:00
} {
// Regular population of scrape config options.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2016-09-05 08:17:10 -04:00
model . AddressLabel : "1.2.3.4:1000" ,
"custom" : "value" ,
2024-12-21 08:33:08 -05:00
} ,
2016-09-05 08:17:10 -04:00
cfg : & config . ScrapeConfig {
2021-08-31 11:37:32 -04:00
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
2016-09-05 08:17:10 -04:00
} ,
2016-12-29 03:27:30 -05:00
res : labels . FromMap ( map [ string ] string {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "1.2.3.4:1000" ,
model . InstanceLabel : "1.2.3.4:1000" ,
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
"custom" : "value" ,
2016-12-29 03:27:30 -05:00
} ) ,
resOrig : labels . FromMap ( map [ string ] string {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "1.2.3.4:1000" ,
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
"custom" : "value" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
2016-12-29 03:27:30 -05:00
} ) ,
2016-09-05 08:17:10 -04:00
} ,
// Pre-define/overwrite scrape config labels.
// Leave out port and expect it to be defaulted to scheme.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "1.2.3.4" ,
model . SchemeLabel : "http" ,
model . MetricsPathLabel : "/custom" ,
model . JobLabel : "custom-job" ,
model . ScrapeIntervalLabel : "2s" ,
model . ScrapeTimeoutLabel : "2s" ,
2024-12-21 08:33:08 -05:00
} ,
2016-09-05 08:17:10 -04:00
cfg : & config . ScrapeConfig {
2021-08-31 11:37:32 -04:00
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
2016-09-05 08:17:10 -04:00
} ,
2016-12-29 03:27:30 -05:00
res : labels . FromMap ( map [ string ] string {
2024-09-14 14:04:33 -04:00
model . AddressLabel : "1.2.3.4" ,
model . InstanceLabel : "1.2.3.4" ,
2021-08-31 11:37:32 -04:00
model . SchemeLabel : "http" ,
model . MetricsPathLabel : "/custom" ,
model . JobLabel : "custom-job" ,
model . ScrapeIntervalLabel : "2s" ,
model . ScrapeTimeoutLabel : "2s" ,
2016-12-29 03:27:30 -05:00
} ) ,
resOrig : labels . FromMap ( map [ string ] string {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "1.2.3.4" ,
model . SchemeLabel : "http" ,
model . MetricsPathLabel : "/custom" ,
model . JobLabel : "custom-job" ,
model . ScrapeIntervalLabel : "2s" ,
model . ScrapeTimeoutLabel : "2s" ,
2016-12-29 03:27:30 -05:00
} ) ,
2016-09-05 08:17:10 -04:00
} ,
// Provide instance label. HTTPS port default for IPv6.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2016-09-05 08:17:10 -04:00
model . AddressLabel : "[::1]" ,
model . InstanceLabel : "custom-instance" ,
2024-12-21 08:33:08 -05:00
} ,
2016-09-05 08:17:10 -04:00
cfg : & config . ScrapeConfig {
2021-08-31 11:37:32 -04:00
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
2016-09-05 08:17:10 -04:00
} ,
2016-12-29 03:27:30 -05:00
res : labels . FromMap ( map [ string ] string {
2024-09-14 14:04:33 -04:00
model . AddressLabel : "[::1]" ,
2021-08-31 11:37:32 -04:00
model . InstanceLabel : "custom-instance" ,
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
2016-12-29 03:27:30 -05:00
} ) ,
resOrig : labels . FromMap ( map [ string ] string {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "[::1]" ,
model . InstanceLabel : "custom-instance" ,
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
2016-12-29 03:27:30 -05:00
} ) ,
2016-09-05 08:17:10 -04:00
} ,
2017-06-09 11:18:19 -04:00
// Address label missing.
2016-09-05 08:17:10 -04:00
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet { "custom" : "value" } ,
2017-06-09 11:18:19 -04:00
cfg : & config . ScrapeConfig {
2021-08-31 11:37:32 -04:00
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
2017-06-09 11:18:19 -04:00
} ,
2022-05-30 10:37:16 -04:00
res : labels . EmptyLabels ( ) ,
resOrig : labels . EmptyLabels ( ) ,
2020-10-22 05:00:08 -04:00
err : "no address" ,
2017-06-09 11:18:19 -04:00
} ,
// Address label missing, but added in relabelling.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet { "custom" : "host:1234" } ,
2017-06-09 11:18:19 -04:00
cfg : & config . ScrapeConfig {
2021-08-31 11:37:32 -04:00
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
2018-12-18 06:26:36 -05:00
RelabelConfigs : [ ] * relabel . Config {
2017-06-09 11:18:19 -04:00
{
2025-08-18 04:09:00 -04:00
Action : relabel . Replace ,
Regex : relabel . MustNewRegexp ( "(.*)" ) ,
SourceLabels : model . LabelNames { "custom" } ,
Replacement : "${1}" ,
TargetLabel : string ( model . AddressLabel ) ,
NameValidationScheme : model . UTF8Validation ,
2017-06-09 11:18:19 -04:00
} ,
} ,
} ,
2017-06-23 07:15:44 -04:00
res : labels . FromMap ( map [ string ] string {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "host:1234" ,
model . InstanceLabel : "host:1234" ,
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
"custom" : "host:1234" ,
2017-06-23 07:15:44 -04:00
} ) ,
resOrig : labels . FromMap ( map [ string ] string {
2021-08-31 11:37:32 -04:00
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
"custom" : "host:1234" ,
2016-12-29 03:27:30 -05:00
} ) ,
2017-06-09 11:18:19 -04:00
} ,
// Address label missing, but added in relabelling.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet { "custom" : "host:1234" } ,
2016-09-05 08:17:10 -04:00
cfg : & config . ScrapeConfig {
2021-08-31 11:37:32 -04:00
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
2018-12-18 06:26:36 -05:00
RelabelConfigs : [ ] * relabel . Config {
2016-09-05 08:17:10 -04:00
{
2025-08-18 04:09:00 -04:00
Action : relabel . Replace ,
Regex : relabel . MustNewRegexp ( "(.*)" ) ,
SourceLabels : model . LabelNames { "custom" } ,
Replacement : "${1}" ,
TargetLabel : string ( model . AddressLabel ) ,
NameValidationScheme : model . UTF8Validation ,
2016-09-05 08:17:10 -04:00
} ,
} ,
} ,
2017-06-23 07:15:44 -04:00
res : labels . FromMap ( map [ string ] string {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "host:1234" ,
model . InstanceLabel : "host:1234" ,
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
"custom" : "host:1234" ,
2017-06-23 07:15:44 -04:00
} ) ,
resOrig : labels . FromMap ( map [ string ] string {
2021-08-31 11:37:32 -04:00
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
"custom" : "host:1234" ,
2017-06-23 07:15:44 -04:00
} ) ,
2017-06-09 11:18:19 -04:00
} ,
// Invalid UTF-8 in label.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2017-06-09 11:18:19 -04:00
model . AddressLabel : "1.2.3.4:1000" ,
"custom" : "\xbd" ,
2024-12-21 08:33:08 -05:00
} ,
2017-06-09 11:18:19 -04:00
cfg : & config . ScrapeConfig {
2021-08-31 11:37:32 -04:00
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
2017-06-09 11:18:19 -04:00
} ,
2022-05-30 10:37:16 -04:00
res : labels . EmptyLabels ( ) ,
resOrig : labels . EmptyLabels ( ) ,
2020-10-22 05:00:08 -04:00
err : "invalid label value for \"custom\": \"\\xbd\"" ,
2016-09-05 08:17:10 -04:00
} ,
2021-08-31 11:37:32 -04:00
// Invalid duration in interval label.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "1.2.3.4:1000" ,
model . ScrapeIntervalLabel : "2notseconds" ,
2024-12-21 08:33:08 -05:00
} ,
2021-08-31 11:37:32 -04:00
cfg : & config . ScrapeConfig {
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
} ,
2022-05-30 10:37:16 -04:00
res : labels . EmptyLabels ( ) ,
resOrig : labels . EmptyLabels ( ) ,
2023-03-08 10:32:39 -05:00
err : "error parsing scrape interval: unknown unit \"notseconds\" in duration \"2notseconds\"" ,
2021-08-31 11:37:32 -04:00
} ,
// Invalid duration in timeout label.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "1.2.3.4:1000" ,
model . ScrapeTimeoutLabel : "2notseconds" ,
2024-12-21 08:33:08 -05:00
} ,
2021-08-31 11:37:32 -04:00
cfg : & config . ScrapeConfig {
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
} ,
2022-05-30 10:37:16 -04:00
res : labels . EmptyLabels ( ) ,
resOrig : labels . EmptyLabels ( ) ,
2023-03-08 10:32:39 -05:00
err : "error parsing scrape timeout: unknown unit \"notseconds\" in duration \"2notseconds\"" ,
2021-08-31 11:37:32 -04:00
} ,
// 0 interval in timeout label.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "1.2.3.4:1000" ,
model . ScrapeIntervalLabel : "0s" ,
2024-12-21 08:33:08 -05:00
} ,
2021-08-31 11:37:32 -04:00
cfg : & config . ScrapeConfig {
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
} ,
2022-05-30 10:37:16 -04:00
res : labels . EmptyLabels ( ) ,
resOrig : labels . EmptyLabels ( ) ,
2021-08-31 11:37:32 -04:00
err : "scrape interval cannot be 0" ,
} ,
// 0 duration in timeout label.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "1.2.3.4:1000" ,
model . ScrapeTimeoutLabel : "0s" ,
2024-12-21 08:33:08 -05:00
} ,
2021-08-31 11:37:32 -04:00
cfg : & config . ScrapeConfig {
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
} ,
2022-05-30 10:37:16 -04:00
res : labels . EmptyLabels ( ) ,
resOrig : labels . EmptyLabels ( ) ,
2021-08-31 11:37:32 -04:00
err : "scrape timeout cannot be 0" ,
} ,
// Timeout less than interval.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2021-08-31 11:37:32 -04:00
model . AddressLabel : "1.2.3.4:1000" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "2s" ,
2024-12-21 08:33:08 -05:00
} ,
2021-08-31 11:37:32 -04:00
cfg : & config . ScrapeConfig {
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
} ,
2022-05-30 10:37:16 -04:00
res : labels . EmptyLabels ( ) ,
resOrig : labels . EmptyLabels ( ) ,
2021-08-31 11:37:32 -04:00
err : "scrape timeout cannot be greater than scrape interval (\"2s\" > \"1s\")" ,
} ,
2022-07-20 07:35:47 -04:00
// Don't attach default port.
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2022-07-20 07:35:47 -04:00
model . AddressLabel : "1.2.3.4" ,
2024-12-21 08:33:08 -05:00
} ,
2022-07-20 07:35:47 -04:00
cfg : & config . ScrapeConfig {
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
} ,
res : labels . FromMap ( map [ string ] string {
model . AddressLabel : "1.2.3.4" ,
model . InstanceLabel : "1.2.3.4" ,
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
} ) ,
resOrig : labels . FromMap ( map [ string ] string {
model . AddressLabel : "1.2.3.4" ,
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
} ) ,
} ,
2024-09-14 14:04:33 -04:00
// verify that the default port is not removed (http).
2022-07-20 07:35:47 -04:00
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2022-07-20 07:35:47 -04:00
model . AddressLabel : "1.2.3.4:80" ,
2024-12-21 08:33:08 -05:00
} ,
2022-07-20 07:35:47 -04:00
cfg : & config . ScrapeConfig {
Scheme : "http" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
} ,
res : labels . FromMap ( map [ string ] string {
2024-09-14 14:04:33 -04:00
model . AddressLabel : "1.2.3.4:80" ,
2022-07-20 07:35:47 -04:00
model . InstanceLabel : "1.2.3.4:80" ,
model . SchemeLabel : "http" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
} ) ,
resOrig : labels . FromMap ( map [ string ] string {
model . AddressLabel : "1.2.3.4:80" ,
model . SchemeLabel : "http" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
} ) ,
} ,
2024-09-14 14:04:33 -04:00
// verify that the default port is not removed (https).
2022-07-20 07:35:47 -04:00
{
2024-12-21 08:33:08 -05:00
in : model . LabelSet {
2022-07-20 07:35:47 -04:00
model . AddressLabel : "1.2.3.4:443" ,
2024-12-21 08:33:08 -05:00
} ,
2022-07-20 07:35:47 -04:00
cfg : & config . ScrapeConfig {
Scheme : "https" ,
MetricsPath : "/metrics" ,
JobName : "job" ,
ScrapeInterval : model . Duration ( time . Second ) ,
ScrapeTimeout : model . Duration ( time . Second ) ,
} ,
res : labels . FromMap ( map [ string ] string {
2024-09-14 14:04:33 -04:00
model . AddressLabel : "1.2.3.4:443" ,
2022-07-20 07:35:47 -04:00
model . InstanceLabel : "1.2.3.4:443" ,
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
} ) ,
resOrig : labels . FromMap ( map [ string ] string {
model . AddressLabel : "1.2.3.4:443" ,
model . SchemeLabel : "https" ,
model . MetricsPathLabel : "/metrics" ,
model . JobLabel : "job" ,
model . ScrapeIntervalLabel : "1s" ,
model . ScrapeTimeoutLabel : "1s" ,
} ) ,
} ,
2016-09-05 08:17:10 -04:00
}
2018-04-27 08:11:16 -04:00
for _ , c := range cases {
2024-12-21 08:33:08 -05:00
in := maps . Clone ( c . in )
lb := labels . NewBuilder ( labels . EmptyLabels ( ) )
2025-08-18 04:09:00 -04:00
c . cfg . MetricNameValidationScheme = model . UTF8Validation
for i := range c . cfg . RelabelConfigs {
c . cfg . RelabelConfigs [ i ] . NameValidationScheme = model . UTF8Validation
}
2024-12-21 08:33:08 -05:00
res , err := PopulateLabels ( lb , c . cfg , c . in , nil )
2020-10-22 05:00:08 -04:00
if c . err != "" {
2020-10-29 05:43:23 -04:00
require . EqualError ( t , err , c . err )
2020-10-22 05:00:08 -04:00
} else {
2020-10-29 05:43:23 -04:00
require . NoError ( t , err )
2024-12-21 08:33:08 -05:00
testutil . RequireEqual ( t , c . res , res )
PopulateDiscoveredLabels ( lb , c . cfg , c . in , nil )
testutil . RequireEqual ( t , c . resOrig , lb . Labels ( ) )
2020-10-22 05:00:08 -04:00
}
2024-12-21 08:33:08 -05:00
require . Equal ( t , c . in , in ) // Check this wasn't altered by PopulateLabels().
2016-09-05 08:17:10 -04:00
}
}
2018-01-19 06:36:21 -05:00
2023-02-28 11:12:27 -05:00
func loadConfiguration ( t testing . TB , c string ) * config . Config {
2019-02-13 08:24:22 -05:00
t . Helper ( )
2018-01-19 06:36:21 -05:00
2024-12-11 06:01:15 -05:00
cfg , err := config . Load ( c , promslog . NewNopLogger ( ) )
require . NoError ( t , err )
2019-02-13 08:24:22 -05:00
return cfg
}
func noopLoop ( ) loop {
return & testLoop {
2025-02-10 02:06:58 -05:00
startFunc : func ( _ , _ time . Duration , _ chan <- error ) { } ,
2019-02-13 08:24:22 -05:00
stopFunc : func ( ) { } ,
}
}
func TestManagerApplyConfig ( t * testing . T ) {
// Valid initial configuration.
cfgText1 := `
2018-07-04 07:01:19 -04:00
scrape_configs :
2019-02-13 08:24:22 -05:00
- job_name : job1
2018-07-04 07:01:19 -04:00
static_configs :
- targets : [ "foo:9090" ]
`
2019-02-13 08:24:22 -05:00
// Invalid configuration.
cfgText2 := `
scrape_configs :
- job_name : job1
scheme : https
static_configs :
- targets : [ "foo:9090" ]
tls_config :
ca_file : / not / existing / ca / file
`
// Valid configuration.
cfgText3 := `
scrape_configs :
- job_name : job1
scheme : https
static_configs :
- targets : [ "foo:9090" ]
`
var (
cfg1 = loadConfiguration ( t , cfgText1 )
cfg2 = loadConfiguration ( t , cfgText2 )
cfg3 = loadConfiguration ( t , cfgText3 )
2018-01-19 06:36:21 -05:00
2019-02-13 08:24:22 -05:00
ch = make ( chan struct { } , 1 )
2023-09-22 12:47:44 -04:00
testRegistry = prometheus . NewRegistry ( )
2019-02-13 08:24:22 -05:00
)
2018-07-04 07:01:19 -04:00
2021-08-24 08:31:14 -04:00
opts := Options { }
2026-01-29 05:50:17 -05:00
scrapeManager , err := NewManager ( & opts , nil , nil , nil , teststorage . NewAppendable ( ) , testRegistry )
2023-09-22 12:47:44 -04:00
require . NoError ( t , err )
2019-03-12 06:26:18 -04:00
newLoop := func ( scrapeLoopOptions ) loop {
2019-02-13 08:24:22 -05:00
ch <- struct { } { }
return noopLoop ( )
2018-01-19 06:36:21 -05:00
}
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 { }
sp . loops [ 1 ] = noopLoop ( )
sp . config = cfg1 . ScrapeConfigs [ 0 ]
sp . metrics = scrapeManager . metrics
2018-01-19 06:36:21 -05:00
scrapeManager . scrapePools = map [ string ] * scrapePool {
2019-02-13 08:24:22 -05:00
"job1" : sp ,
2018-01-19 06:36:21 -05:00
}
2019-02-13 08:24:22 -05:00
// Apply the initial configuration.
2021-09-04 08:35:03 -04:00
err = scrapeManager . ApplyConfig ( cfg1 )
require . NoError ( t , err , "Unable to apply configuration." )
2019-02-13 08:24:22 -05:00
select {
case <- ch :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Reload happened." )
2019-02-13 08:24:22 -05:00
default :
}
// Apply a configuration for which the reload fails.
2021-09-04 08:35:03 -04:00
err = scrapeManager . ApplyConfig ( cfg2 )
require . Error ( t , err , "Expecting error but got none." )
2019-02-13 08:24:22 -05:00
select {
case <- ch :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Reload happened." )
2019-02-13 08:24:22 -05:00
default :
}
// Apply a configuration for which the reload succeeds.
2021-09-04 08:35:03 -04:00
err = scrapeManager . ApplyConfig ( cfg3 )
require . NoError ( t , err , "Unable to apply configuration." )
2019-02-13 08:24:22 -05:00
select {
case <- ch :
default :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Reload didn't happen." )
2019-02-13 08:24:22 -05:00
}
// Re-applying the same configuration shouldn't trigger a reload.
2021-09-04 08:35:03 -04:00
err = scrapeManager . ApplyConfig ( cfg3 )
require . NoError ( t , err , "Unable to apply configuration." )
2019-02-13 08:24:22 -05:00
select {
case <- ch :
2021-09-04 08:35:03 -04:00
require . FailNow ( t , "Reload happened." )
2019-02-13 08:24:22 -05:00
default :
}
2018-01-19 06:36:21 -05:00
}
2018-09-26 05:20:56 -04:00
func TestManagerTargetsUpdates ( t * testing . T ) {
2021-08-24 08:31:14 -04:00
opts := Options { }
2023-09-22 12:47:44 -04:00
testRegistry := prometheus . NewRegistry ( )
2026-01-29 05:50:17 -05:00
m , err := NewManager ( & opts , nil , nil , nil , teststorage . NewAppendable ( ) , testRegistry )
2023-09-22 12:47:44 -04:00
require . NoError ( t , err )
2018-09-26 05:20:56 -04:00
2026-01-27 03:57:40 -05:00
targetSetsCh := make ( chan map [ string ] [ ] * targetgroup . Group )
go m . Run ( targetSetsCh )
2019-09-23 06:28:37 -04:00
defer m . Stop ( )
2018-09-26 05:20:56 -04:00
tgSent := make ( map [ string ] [ ] * targetgroup . Group )
2025-08-27 08:38:54 -04:00
for x := range 10 {
2018-09-26 05:20:56 -04:00
tgSent [ strconv . Itoa ( x ) ] = [ ] * targetgroup . Group {
2019-01-16 17:28:08 -05:00
{
2018-09-26 05:20:56 -04:00
Source : strconv . Itoa ( x ) ,
} ,
}
select {
2026-01-27 03:57:40 -05:00
case targetSetsCh <- tgSent :
2018-09-26 05:20:56 -04:00
case <- time . After ( 10 * time . Millisecond ) :
2021-09-04 08:35:03 -04:00
require . Fail ( t , "Scrape manager's channel remained blocked after the set threshold." )
2018-09-26 05:20:56 -04:00
}
}
m . mtxScrape . Lock ( )
tsetActual := m . targetSets
m . mtxScrape . Unlock ( )
// Make sure all updates have been received.
2020-10-29 05:43:23 -04:00
require . Equal ( t , tgSent , tsetActual )
2018-09-26 05:20:56 -04:00
select {
case <- m . triggerReload :
default :
2021-09-04 08:35:03 -04:00
require . Fail ( t , "No scrape loops reload was triggered after targets update." )
2018-09-26 05:20:56 -04:00
}
}
2019-03-12 06:46:15 -04:00
2023-05-25 05:49:43 -04:00
func TestSetOffsetSeed ( t * testing . T ) {
2019-03-12 06:46:15 -04:00
getConfig := func ( prometheus string ) * config . Config {
cfgText := `
global :
external_labels :
prometheus : ' ` + prometheus + ` '
`
cfg := & config . Config { }
2021-09-04 08:35:03 -04:00
err := yaml . UnmarshalStrict ( [ ] byte ( cfgText ) , cfg )
require . NoError ( t , err , "Unable to load YAML config cfgYaml." )
2019-03-12 06:46:15 -04:00
return cfg
}
2021-08-24 08:31:14 -04:00
opts := Options { }
2023-09-22 12:47:44 -04:00
testRegistry := prometheus . NewRegistry ( )
2026-01-29 05:50:17 -05:00
scrapeManager , err := NewManager ( & opts , nil , nil , nil , teststorage . NewAppendable ( ) , testRegistry )
2023-09-22 12:47:44 -04:00
require . NoError ( t , err )
2019-03-12 06:46:15 -04:00
// Load the first config.
cfg1 := getConfig ( "ha1" )
2021-09-04 08:35:03 -04:00
err = scrapeManager . setOffsetSeed ( cfg1 . GlobalConfig . ExternalLabels )
require . NoError ( t , err )
2023-05-25 05:49:43 -04:00
offsetSeed1 := scrapeManager . offsetSeed
2019-03-12 06:46:15 -04:00
2021-09-04 08:35:03 -04:00
require . NotZero ( t , offsetSeed1 , "Offset seed has to be a hash of uint64." )
2019-03-12 06:46:15 -04:00
// Load the first config.
cfg2 := getConfig ( "ha2" )
2021-09-04 08:35:03 -04:00
require . NoError ( t , scrapeManager . setOffsetSeed ( cfg2 . GlobalConfig . ExternalLabels ) )
2023-05-25 05:49:43 -04:00
offsetSeed2 := scrapeManager . offsetSeed
2019-03-12 06:46:15 -04:00
2021-09-04 08:35:03 -04:00
require . NotEqual ( t , offsetSeed1 , offsetSeed2 , "Offset seed should not be the same on different set of external labels." )
2019-03-12 06:46:15 -04:00
}
2022-12-23 05:55:08 -05:00
func TestManagerScrapePools ( t * testing . T ) {
cfgText1 := `
scrape_configs :
- job_name : job1
static_configs :
- targets : [ "foo:9090" ]
- job_name : job2
static_configs :
- targets : [ "foo:9091" , "foo:9092" ]
`
cfgText2 := `
scrape_configs :
- job_name : job1
static_configs :
- targets : [ "foo:9090" , "foo:9094" ]
- job_name : job3
static_configs :
- targets : [ "foo:9093" ]
`
var (
2023-09-22 12:47:44 -04:00
cfg1 = loadConfiguration ( t , cfgText1 )
cfg2 = loadConfiguration ( t , cfgText2 )
testRegistry = prometheus . NewRegistry ( )
2022-12-23 05:55:08 -05:00
)
reload := func ( scrapeManager * Manager , cfg * config . Config ) {
newLoop := func ( scrapeLoopOptions ) loop {
return noopLoop ( )
}
scrapeManager . scrapePools = map [ string ] * scrapePool { }
for _ , sc := range cfg . ScrapeConfigs {
_ , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2025-12-22 04:38:48 -05:00
2026-01-21 03:21:56 -05:00
sp := newTestScrapePool ( t , nil , false , newLoop )
2025-12-22 04:38:48 -05:00
sp . loops [ 1 ] = noopLoop ( )
sp . config = cfg1 . ScrapeConfigs [ 0 ]
sp . metrics = scrapeManager . metrics
2022-12-23 05:55:08 -05:00
for _ , c := range sc . ServiceDiscoveryConfigs {
staticConfig := c . ( discovery . StaticConfig )
for _ , group := range staticConfig {
for i := range group . Targets {
sp . activeTargets [ uint64 ( i ) ] = & Target { }
}
}
}
scrapeManager . scrapePools [ sc . JobName ] = sp
}
}
opts := Options { }
2026-01-29 05:50:17 -05:00
scrapeManager , err := NewManager ( & opts , nil , nil , nil , teststorage . NewAppendable ( ) , testRegistry )
2023-09-22 12:47:44 -04:00
require . NoError ( t , err )
2022-12-23 05:55:08 -05:00
reload ( scrapeManager , cfg1 )
require . ElementsMatch ( t , [ ] string { "job1" , "job2" } , scrapeManager . ScrapePools ( ) )
reload ( scrapeManager , cfg2 )
require . ElementsMatch ( t , [ ] string { "job1" , "job3" } , scrapeManager . ScrapePools ( ) )
}
2023-12-11 03:43:42 -05:00
2024-10-02 06:52:03 -04:00
func setupTestServer ( t * testing . T , typ string , toWrite [ ] byte ) * httptest . Server {
once := sync . Once { }
2023-12-11 03:43:42 -05:00
2024-10-02 06:52:03 -04:00
server := httptest . NewServer (
2025-02-10 02:06:58 -05:00
http . HandlerFunc ( func ( w http . ResponseWriter , _ * http . Request ) {
2024-10-02 06:52:03 -04:00
fail := true
once . Do ( func ( ) {
fail = false
w . Header ( ) . Set ( "Content-Type" , typ )
w . Write ( toWrite )
2023-12-11 03:43:42 -05:00
} )
2024-10-02 06:52:03 -04:00
if fail {
w . WriteHeader ( http . StatusInternalServerError )
2024-08-29 03:40:17 -04:00
}
2024-10-02 06:52:03 -04:00
} ) ,
)
t . Cleanup ( func ( ) { server . Close ( ) } )
2023-12-11 03:43:42 -05:00
2024-10-02 06:52:03 -04:00
return server
}
2025-11-13 09:17:51 -05:00
// TestManagerSTZeroIngestion tests scrape manager for various ST cases.
2026-01-23 04:04:05 -05:00
// NOTE(bwplotka): There is no AppenderV2 test for this STZeroIngestion feature as in V2 flow it's
// moved to AppenderV2 implementation (e.g. storage) and it's tested there, e.g. tsdb.TestHeadAppenderV2_Append_EnableSTAsZeroSample.
2025-11-13 09:17:51 -05:00
func TestManagerSTZeroIngestion ( t * testing . T ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2024-10-02 06:52:03 -04:00
const (
// _total suffix is required, otherwise expfmt with OMText will mark metric as "unknown"
expectedMetricName = "expected_metric_total"
expectedCreatedMetricName = "expected_metric_created"
expectedSampleValue = 17.0
)
for _ , testFormat := range [ ] config . ScrapeProtocol { config . PrometheusProto , config . OpenMetricsText1_0_0 } {
t . Run ( fmt . Sprintf ( "format=%s" , testFormat ) , func ( t * testing . T ) {
2025-11-13 09:17:51 -05:00
for _ , testWithST := range [ ] bool { false , true } {
t . Run ( fmt . Sprintf ( "withST=%v" , testWithST ) , func ( t * testing . T ) {
for _ , testSTZeroIngest := range [ ] bool { false , true } {
2025-12-22 04:38:48 -05:00
t . Run ( fmt . Sprintf ( "stZeroIngest=%v" , testSTZeroIngest ) , func ( t * testing . T ) {
2024-12-11 06:01:15 -05:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2024-10-02 06:52:03 -04:00
sampleTs := time . Now ( )
2025-11-13 09:17:51 -05:00
stTs := time . Time { }
if testWithST {
stTs = sampleTs . Add ( - 2 * time . Minute )
2024-10-02 06:52:03 -04:00
}
// TODO(bwplotka): Add more types than just counter?
2025-11-13 09:17:51 -05:00
encoded := prepareTestEncodedCounter ( t , testFormat , expectedMetricName , expectedSampleValue , sampleTs , stTs )
2024-10-02 06:52:03 -04:00
2025-12-22 04:38:48 -05:00
app := teststorage . NewAppendable ( )
2024-12-11 06:01:15 -05:00
discoveryManager , scrapeManager := runManagers ( t , ctx , & Options {
2025-11-13 09:17:51 -05:00
EnableStartTimestampZeroIngestion : testSTZeroIngest ,
skipOffsetting : true ,
2026-01-23 04:04:05 -05:00
} , app , nil )
2024-12-11 06:01:15 -05:00
defer scrapeManager . Stop ( )
server := setupTestServer ( t , config . ScrapeProtocolsHeaders [ testFormat ] , encoded )
serverURL , err := url . Parse ( server . URL )
require . NoError ( t , err )
testConfig := fmt . Sprintf ( `
global :
# Disable regular scrapes .
scrape_interval : 9999 m
scrape_timeout : 5 s
scrape_configs :
- job_name : test
honor_timestamps : true
static_configs :
- targets : [ ' % s ' ]
` , serverURL . Host )
applyConfig ( t , testConfig , scrapeManager , discoveryManager )
// Wait for one scrape.
ctx , cancel = context . WithTimeout ( ctx , 1 * time . Minute )
defer cancel ( )
require . NoError ( t , runutil . Retry ( 100 * time . Millisecond , ctx . Done ( ) , func ( ) error {
// Check if scrape happened and grab the relevant samples.
2025-12-22 04:38:48 -05:00
if len ( app . ResultSamples ( ) ) > 0 {
2024-12-11 06:01:15 -05:00
return nil
}
return errors . New ( "expected some float samples, got none" )
} ) , "after 1 minute" )
2024-10-02 06:52:03 -04:00
// Verify results.
2025-11-13 09:17:51 -05:00
// Verify what we got vs expectations around ST injection.
2025-12-22 04:38:48 -05:00
got := findSamplesForMetric ( app . ResultSamples ( ) , expectedMetricName )
2025-11-13 09:17:51 -05:00
if testWithST && testSTZeroIngest {
2025-12-22 04:38:48 -05:00
require . Len ( t , got , 2 )
require . Equal ( t , 0.0 , got [ 0 ] . V )
require . Equal ( t , timestamp . FromTime ( stTs ) , got [ 0 ] . T )
require . Equal ( t , expectedSampleValue , got [ 1 ] . V )
require . Equal ( t , timestamp . FromTime ( sampleTs ) , got [ 1 ] . T )
2024-10-02 06:52:03 -04:00
} else {
2025-12-22 04:38:48 -05:00
require . Len ( t , got , 1 )
require . Equal ( t , expectedSampleValue , got [ 0 ] . V )
require . Equal ( t , timestamp . FromTime ( sampleTs ) , got [ 0 ] . T )
2024-10-02 06:52:03 -04:00
}
// Verify what we got vs expectations around additional _created series for OM text.
2025-11-13 09:17:51 -05:00
// enableSTZeroInjection also kills that _created line.
2025-12-22 04:38:48 -05:00
gotSTSeries := findSamplesForMetric ( app . ResultSamples ( ) , expectedCreatedMetricName )
2025-11-13 09:17:51 -05:00
if testFormat == config . OpenMetricsText1_0_0 && testWithST && ! testSTZeroIngest {
// For OM Text, when counter has ST, and feature flag disabled we should see _created lines.
2025-12-22 04:38:48 -05:00
require . Len ( t , gotSTSeries , 1 )
2024-10-02 06:52:03 -04:00
// Conversion taken from common/expfmt.writeOpenMetricsFloat.
2025-11-13 09:17:51 -05:00
// We don't check the st timestamp as explicit ts was not implemented in expfmt.Encoder,
2024-12-13 16:32:20 -05:00
// but exists in OM https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#:~:text=An%20example%20with%20a%20Metric%20with%20no%20labels%2C%20and%20a%20MetricPoint%20with%20a%20timestamp%20and%20a%20created
2025-11-13 09:17:51 -05:00
// We can implement this, but we want to potentially get rid of OM 1.0 ST lines
2025-12-22 04:38:48 -05:00
require . Equal ( t , float64 ( timestamppb . New ( stTs ) . AsTime ( ) . UnixNano ( ) ) / 1e9 , gotSTSeries [ 0 ] . V )
2024-10-02 06:52:03 -04:00
} else {
2025-12-22 04:38:48 -05:00
require . Empty ( t , gotSTSeries )
2024-10-02 06:52:03 -04:00
}
} )
}
} )
}
2024-08-29 03:40:17 -04:00
} )
2023-12-11 03:43:42 -05:00
}
}
2024-04-05 12:00:52 -04:00
2025-11-13 09:17:51 -05:00
func prepareTestEncodedCounter ( t * testing . T , format config . ScrapeProtocol , mName string , v float64 , ts , st time . Time ) ( encoded [ ] byte ) {
2024-10-02 06:52:03 -04:00
t . Helper ( )
counter := & dto . Counter { Value : proto . Float64 ( v ) }
2025-11-13 09:17:51 -05:00
if ! st . IsZero ( ) {
counter . CreatedTimestamp = timestamppb . New ( st )
2024-10-02 06:52:03 -04:00
}
ctrType := dto . MetricType_COUNTER
inputMetric := & dto . MetricFamily {
Name : proto . String ( mName ) ,
Type : & ctrType ,
Metric : [ ] * dto . Metric { {
TimestampMs : proto . Int64 ( timestamp . FromTime ( ts ) ) ,
Counter : counter ,
} } ,
}
switch format {
case config . PrometheusProto :
return protoMarshalDelimited ( t , inputMetric )
case config . OpenMetricsText1_0_0 :
buf := & bytes . Buffer { }
require . NoError ( t , expfmt . NewEncoder ( buf , expfmt . NewFormat ( expfmt . TypeOpenMetrics ) , expfmt . WithCreatedLines ( ) , expfmt . WithUnit ( ) ) . Encode ( inputMetric ) )
_ , _ = buf . WriteString ( "# EOF" )
t . Log ( "produced OM text to expose:" , buf . String ( ) )
return buf . Bytes ( )
default :
t . Fatalf ( "not implemented format: %v" , format )
return nil
}
}
2025-12-22 04:38:48 -05:00
func findSamplesForMetric ( floats [ ] sample , metricName string ) ( ret [ ] sample ) {
2024-10-02 06:52:03 -04:00
for _ , f := range floats {
2025-12-22 04:38:48 -05:00
if f . L . Get ( model . MetricNameLabel ) == metricName {
2024-10-02 06:52:03 -04:00
ret = append ( ret , f )
}
}
return ret
}
2024-07-19 10:28:00 -04:00
// generateTestHistogram generates the same thing as tsdbutil.GenerateTestHistogram,
// but in the form of dto.Histogram.
func generateTestHistogram ( i int ) * dto . Histogram {
2024-12-15 17:53:36 -05:00
helper := tsdbutil . GenerateTestHistogram ( int64 ( i ) )
2024-07-19 10:28:00 -04:00
h := & dto . Histogram { }
h . SampleCount = proto . Uint64 ( helper . Count )
h . SampleSum = proto . Float64 ( helper . Sum )
h . Schema = proto . Int32 ( helper . Schema )
h . ZeroThreshold = proto . Float64 ( helper . ZeroThreshold )
h . ZeroCount = proto . Uint64 ( helper . ZeroCount )
h . PositiveSpan = make ( [ ] * dto . BucketSpan , len ( helper . PositiveSpans ) )
for i , span := range helper . PositiveSpans {
h . PositiveSpan [ i ] = & dto . BucketSpan {
Offset : proto . Int32 ( span . Offset ) ,
Length : proto . Uint32 ( span . Length ) ,
}
}
h . PositiveDelta = helper . PositiveBuckets
h . NegativeSpan = make ( [ ] * dto . BucketSpan , len ( helper . NegativeSpans ) )
for i , span := range helper . NegativeSpans {
h . NegativeSpan [ i ] = & dto . BucketSpan {
Offset : proto . Int32 ( span . Offset ) ,
Length : proto . Uint32 ( span . Length ) ,
}
}
h . NegativeDelta = helper . NegativeBuckets
return h
}
2026-01-23 04:04:05 -05:00
// NOTE(bwplotka): There is no AppenderV2 test for this STZeroIngestion feature as in V2 flow it's
// moved to AppenderV2 implementation (e.g. storage) and it's tested there, e.g. tsdb.TestHeadAppenderV2_Append_EnableSTAsZeroSample.
2025-11-13 09:17:51 -05:00
func TestManagerSTZeroIngestionHistogram ( t * testing . T ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2024-07-19 10:28:00 -04:00
const mName = "expected_histogram"
for _ , tc := range [ ] struct {
name string
inputHistSample * dto . Histogram
2025-11-13 09:17:51 -05:00
enableSTZeroIngestion bool
2024-07-19 10:28:00 -04:00
} {
{
2025-11-13 09:17:51 -05:00
name : "disabled with ST on histogram" ,
2024-07-19 10:28:00 -04:00
inputHistSample : func ( ) * dto . Histogram {
h := generateTestHistogram ( 0 )
h . CreatedTimestamp = timestamppb . Now ( )
return h
} ( ) ,
2025-11-13 09:17:51 -05:00
enableSTZeroIngestion : false ,
2024-07-19 10:28:00 -04:00
} ,
{
2025-11-13 09:17:51 -05:00
name : "enabled with ST on histogram" ,
2024-07-19 10:28:00 -04:00
inputHistSample : func ( ) * dto . Histogram {
h := generateTestHistogram ( 0 )
h . CreatedTimestamp = timestamppb . Now ( )
return h
} ( ) ,
2025-11-13 09:17:51 -05:00
enableSTZeroIngestion : true ,
2024-07-19 10:28:00 -04:00
} ,
{
2025-11-13 09:17:51 -05:00
name : "enabled without ST on histogram" ,
2024-07-19 10:28:00 -04:00
inputHistSample : func ( ) * dto . Histogram {
h := generateTestHistogram ( 0 )
return h
} ( ) ,
2025-11-13 09:17:51 -05:00
enableSTZeroIngestion : true ,
2024-07-19 10:28:00 -04:00
} ,
} {
t . Run ( tc . name , func ( t * testing . T ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2024-12-11 06:01:15 -05:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2024-07-19 10:28:00 -04:00
2025-12-22 04:38:48 -05:00
app := teststorage . NewAppendable ( )
2024-12-11 06:01:15 -05:00
discoveryManager , scrapeManager := runManagers ( t , ctx , & Options {
2025-11-13 09:17:51 -05:00
EnableStartTimestampZeroIngestion : tc . enableSTZeroIngestion ,
skipOffsetting : true ,
2026-01-23 04:04:05 -05:00
} , app , nil )
2024-12-11 06:01:15 -05:00
defer scrapeManager . Stop ( )
2024-07-19 10:28:00 -04:00
once := sync . Once { }
// Start fake HTTP target to that allow one scrape only.
server := httptest . NewServer (
2025-02-10 02:06:58 -05:00
http . HandlerFunc ( func ( w http . ResponseWriter , _ * http . Request ) {
2024-12-11 06:01:15 -05:00
fail := true
2024-07-19 10:28:00 -04:00
once . Do ( func ( ) {
fail = false
w . Header ( ) . Set ( "Content-Type" , ` application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited ` )
ctrType := dto . MetricType_HISTOGRAM
w . Write ( protoMarshalDelimited ( t , & dto . MetricFamily {
Name : proto . String ( mName ) ,
Type : & ctrType ,
Metric : [ ] * dto . Metric { { Histogram : tc . inputHistSample } } ,
} ) )
} )
if fail {
w . WriteHeader ( http . StatusInternalServerError )
}
} ) ,
)
defer server . Close ( )
serverURL , err := url . Parse ( server . URL )
require . NoError ( t , err )
2024-12-11 06:01:15 -05:00
testConfig := fmt . Sprintf ( `
global :
# Disable regular scrapes .
scrape_interval : 9999 m
scrape_timeout : 5 s
scrape_configs :
- job_name : test
2025-10-09 10:56:13 -04:00
scrape_native_histograms : true
2024-12-11 06:01:15 -05:00
static_configs :
- targets : [ ' % s ' ]
` , serverURL . Host )
applyConfig ( t , testConfig , scrapeManager , discoveryManager )
2024-07-19 10:28:00 -04:00
// Wait for one scrape.
2024-12-11 06:01:15 -05:00
ctx , cancel = context . WithTimeout ( ctx , 1 * time . Minute )
2024-07-19 10:28:00 -04:00
defer cancel ( )
require . NoError ( t , runutil . Retry ( 100 * time . Millisecond , ctx . Done ( ) , func ( ) error {
2025-12-22 04:38:48 -05:00
if len ( app . ResultSamples ( ) ) > 0 {
2024-07-19 10:28:00 -04:00
return nil
}
2024-11-03 07:15:51 -05:00
return errors . New ( "expected some histogram samples, got none" )
2024-07-19 10:28:00 -04:00
} ) , "after 1 minute" )
2025-12-22 04:38:48 -05:00
got := findSamplesForMetric ( app . ResultSamples ( ) , mName )
2024-07-19 10:28:00 -04:00
// Check for zero samples, assuming we only injected always one histogram sample.
2025-11-13 09:17:51 -05:00
// Did it contain ST to inject? If yes, was ST zero enabled?
if tc . inputHistSample . CreatedTimestamp . IsValid ( ) && tc . enableSTZeroIngestion {
2024-07-19 10:28:00 -04:00
require . Len ( t , got , 2 )
// Zero sample.
2025-12-22 04:38:48 -05:00
require . Equal ( t , histogram . Histogram { } , * got [ 0 ] . H )
2024-07-19 10:28:00 -04:00
// Quick soft check to make sure it's the same sample or at least not zero.
2025-12-22 04:38:48 -05:00
require . Equal ( t , tc . inputHistSample . GetSampleSum ( ) , got [ 1 ] . H . Sum )
2024-07-19 10:28:00 -04:00
return
}
// Expect only one, valid sample.
require . Len ( t , got , 1 )
// Quick soft check to make sure it's the same sample or at least not zero.
2025-12-22 04:38:48 -05:00
require . Equal ( t , tc . inputHistSample . GetSampleSum ( ) , got [ 0 ] . H . Sum )
2024-07-19 10:28:00 -04:00
} )
}
}
2024-04-05 12:00:52 -04:00
func TestUnregisterMetrics ( t * testing . T ) {
reg := prometheus . NewRegistry ( )
// Check that all metrics can be unregistered, allowing a second manager to be created.
2025-08-27 08:38:54 -04:00
for range 2 {
2024-04-05 12:00:52 -04:00
opts := Options { }
2026-01-29 05:50:17 -05:00
manager , err := NewManager ( & opts , nil , nil , nil , teststorage . NewAppendable ( ) , reg )
2024-04-05 12:00:52 -04:00
require . NotNil ( t , manager )
require . NoError ( t , err )
// Unregister all metrics.
manager . UnregisterMetrics ( )
}
}
2023-11-15 05:41:12 -05:00
2025-11-13 09:17:51 -05:00
// TestNHCBAndSTZeroIngestion verifies that both ConvertClassicHistogramsToNHCBEnabled
// and EnableStartTimestampZeroIngestion can be used simultaneously without errors.
2025-10-07 23:37:24 -04:00
// This test addresses issue #17216 by ensuring the previously blocking check has been removed.
2025-10-12 11:59:18 -04:00
// The test verifies that the presence of exemplars in the input does not cause errors,
// although exemplars are not preserved during NHCB conversion (as documented below).
2026-01-23 04:04:05 -05:00
//
// NOTE(bwplotka): There is no AppenderV2 test for this STZeroIngestion feature as in V2 flow it's
// moved to AppenderV2 implementation (e.g. storage) and it's tested there, e.g. tsdb.TestHeadAppenderV2_Append_EnableSTAsZeroSample.
2025-11-13 09:17:51 -05:00
func TestNHCBAndSTZeroIngestion ( t * testing . T ) {
2025-10-07 23:37:24 -04:00
t . Parallel ( )
2025-10-12 11:59:18 -04:00
const (
mName = "test_histogram"
// The expected sum of the histogram, as defined by the test's OpenMetrics exposition data.
// This value (45.5) is the sum reported in the test_histogram_sum metric below.
expectedHistogramSum = 45.5
)
2025-10-07 23:37:24 -04:00
2025-11-04 00:13:49 -05:00
ctx := t . Context ( )
2025-10-07 23:37:24 -04:00
2025-12-22 04:38:48 -05:00
app := teststorage . NewAppendable ( )
2025-10-07 23:37:24 -04:00
discoveryManager , scrapeManager := runManagers ( t , ctx , & Options {
2025-11-13 09:17:51 -05:00
EnableStartTimestampZeroIngestion : true ,
skipOffsetting : true ,
2026-01-23 04:04:05 -05:00
} , app , nil )
2025-10-07 23:37:24 -04:00
defer scrapeManager . Stop ( )
once := sync . Once { }
server := httptest . NewServer (
http . HandlerFunc ( func ( w http . ResponseWriter , _ * http . Request ) {
fail := true
once . Do ( func ( ) {
fail = false
2025-10-11 07:57:47 -04:00
w . Header ( ) . Set ( "Content-Type" , ` application/openmetrics-text ` )
2025-10-12 11:59:18 -04:00
// Expose a histogram with created timestamp and exemplars to verify no parsing errors occur.
2025-10-11 07:57:47 -04:00
fmt . Fprint ( w , ` # HELP test_histogram A histogram with created timestamp and exemplars
# TYPE test_histogram histogram
test_histogram_bucket { le = "0.0" } 1
2025-10-12 15:20:42 -04:00
test_histogram_bucket { le = "1.0" } 10 # { trace_id = "trace-1" } 0.5 123456789
test_histogram_bucket { le = "2.0" } 20 # { trace_id = "trace-2" } 1.5 123456780
2025-10-11 07:57:47 -04:00
test_histogram_bucket { le = "+Inf" } 30 # { trace_id = "trace-3" } 2.5
test_histogram_count 30
test_histogram_sum 45.5
test_histogram_created 1520430001
# EOF
` )
2025-10-07 23:37:24 -04:00
} )
if fail {
w . WriteHeader ( http . StatusInternalServerError )
}
} ) ,
)
defer server . Close ( )
serverURL , err := url . Parse ( server . URL )
require . NoError ( t , err )
2025-11-13 09:17:51 -05:00
// Configuration with both convert_classic_histograms_to_nhcb enabled and ST zero ingestion enabled.
2025-10-07 23:37:24 -04:00
testConfig := fmt . Sprintf ( `
global :
2025-10-12 11:59:18 -04:00
# Use a very long scrape_interval to prevent automatic scraping during the test .
2025-10-07 23:37:24 -04:00
scrape_interval : 9999 m
scrape_timeout : 5 s
scrape_configs :
- job_name : test
convert_classic_histograms_to_nhcb : true
static_configs :
- targets : [ ' % s ' ]
` , serverURL . Host )
applyConfig ( t , testConfig , scrapeManager , discoveryManager )
2025-10-12 11:59:18 -04:00
// Verify that the scrape pool was created (proves the blocking check was removed).
require . Eventually ( t , func ( ) bool {
scrapeManager . mtxScrape . Lock ( )
defer scrapeManager . mtxScrape . Unlock ( )
_ , exists := scrapeManager . scrapePools [ "test" ]
return exists
} , 5 * time . Second , 100 * time . Millisecond , "scrape pool should be created for job 'test'" )
require . Eventually ( t , func ( ) bool {
2025-12-22 04:38:48 -05:00
return len ( app . ResultSamples ( ) ) > 0
2025-10-12 11:59:18 -04:00
} , 1 * time . Minute , 100 * time . Millisecond , "expected histogram samples, got none" )
2025-10-07 23:37:24 -04:00
2025-10-12 11:59:18 -04:00
// Verify that samples were ingested (proving both features work together).
2025-12-22 04:38:48 -05:00
got := findSamplesForMetric ( app . ResultSamples ( ) , mName )
2025-10-07 23:37:24 -04:00
2025-11-13 09:17:51 -05:00
// With ST zero ingestion enabled and a created timestamp present, we expect 2 samples:
2025-10-07 23:37:24 -04:00
// one zero sample and one actual sample.
require . Len ( t , got , 2 , "expected 2 histogram samples (zero sample + actual sample)" )
2025-12-22 04:38:48 -05:00
require . Equal ( t , histogram . Histogram { } , * got [ 0 ] . H , "first sample should be zero sample" )
require . InDelta ( t , expectedHistogramSum , got [ 1 ] . H . Sum , 1e-9 , "second sample should retain the expected sum" )
require . Len ( t , got [ 1 ] . ES , 2 , "expected 2 exemplars on second histogram" )
2025-10-07 23:37:24 -04:00
}
2023-11-15 05:41:12 -05:00
func applyConfig (
t * testing . T ,
config string ,
scrapeManager * Manager ,
discoveryManager * discovery . Manager ,
) {
t . Helper ( )
cfg := loadConfiguration ( t , config )
require . NoError ( t , scrapeManager . ApplyConfig ( cfg ) )
c := make ( map [ string ] discovery . Configs )
scfgs , err := cfg . GetScrapeConfigs ( )
require . NoError ( t , err )
for _ , v := range scfgs {
c [ v . JobName ] = v . ServiceDiscoveryConfigs
}
require . NoError ( t , discoveryManager . ApplyConfig ( c ) )
}
2026-01-23 04:04:05 -05:00
func runManagers ( t * testing . T , ctx context . Context , opts * Options , app storage . Appendable , appV2 storage . AppendableV2 ) ( * discovery . Manager , * Manager ) {
2023-11-15 05:41:12 -05:00
t . Helper ( )
2024-12-11 06:01:15 -05:00
if opts == nil {
opts = & Options { }
}
opts . DiscoveryReloadInterval = model . Duration ( 100 * time . Millisecond )
2023-11-15 05:41:12 -05:00
reg := prometheus . NewRegistry ( )
sdMetrics , err := discovery . RegisterSDMetrics ( reg , discovery . NewRefreshMetrics ( reg ) )
require . NoError ( t , err )
discoveryManager := discovery . NewManager (
ctx ,
2024-09-09 21:41:53 -04:00
promslog . NewNopLogger ( ) ,
2023-11-15 05:41:12 -05:00
reg ,
sdMetrics ,
discovery . Updatert ( 100 * time . Millisecond ) ,
)
scrapeManager , err := NewManager (
2024-12-11 06:01:15 -05:00
opts ,
2023-11-15 05:41:12 -05:00
nil ,
2024-08-26 05:41:56 -04:00
nil ,
2026-01-23 04:04:05 -05:00
app , appV2 ,
2023-11-15 05:41:12 -05:00
prometheus . NewRegistry ( ) ,
)
require . NoError ( t , err )
go discoveryManager . Run ( )
go scrapeManager . Run ( discoveryManager . SyncCh ( ) )
return discoveryManager , scrapeManager
}
func writeIntoFile ( t * testing . T , content , filePattern string ) * os . File {
t . Helper ( )
file , err := os . CreateTemp ( "" , filePattern )
require . NoError ( t , err )
_ , err = file . WriteString ( content )
require . NoError ( t , err )
return file
}
func requireTargets (
t * testing . T ,
scrapeManager * Manager ,
jobName string ,
waitToAppear bool ,
expectedTargets [ ] string ,
) {
t . Helper ( )
require . Eventually ( t , func ( ) bool {
targets , ok := scrapeManager . TargetsActive ( ) [ jobName ]
if ! ok {
if waitToAppear {
return false
}
t . Fatalf ( "job %s shouldn't be dropped" , jobName )
}
if expectedTargets == nil {
return targets == nil
}
if len ( targets ) != len ( expectedTargets ) {
return false
}
sTargets := [ ] string { }
for _ , t := range targets {
sTargets = append ( sTargets , t . String ( ) )
}
sort . Strings ( expectedTargets )
sort . Strings ( sTargets )
2025-03-08 06:28:05 -05:00
return slices . Equal ( sTargets , expectedTargets )
2023-11-15 05:41:12 -05:00
} , 1 * time . Second , 100 * time . Millisecond )
}
// TestTargetDisappearsAfterProviderRemoved makes sure that when a provider is dropped, (only) its targets are dropped.
func TestTargetDisappearsAfterProviderRemoved ( t * testing . T ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2025-10-09 11:06:28 -04:00
ctx := t . Context ( )
2023-11-15 05:41:12 -05:00
myJob := "my-job"
myJobSDTargetURL := "my:9876"
myJobStaticTargetURL := "my:5432"
sdFileContent := fmt . Sprintf ( ` [ { "targets": ["%s"]}] ` , myJobSDTargetURL )
sDFile := writeIntoFile ( t , sdFileContent , "*targets.json" )
baseConfig := `
scrape_configs :
- job_name : % s
static_configs :
- targets : [ ' % s ' ]
file_sd_configs :
- files : [ ' % s ' ]
`
2026-01-29 05:50:17 -05:00
discoveryManager , scrapeManager := runManagers ( t , ctx , nil , nil , teststorage . NewAppendable ( ) )
2023-11-15 05:41:12 -05:00
defer scrapeManager . Stop ( )
applyConfig (
t ,
fmt . Sprintf (
baseConfig ,
myJob ,
myJobStaticTargetURL ,
sDFile . Name ( ) ,
) ,
scrapeManager ,
discoveryManager ,
)
// Make sure the jobs targets are taken into account
requireTargets (
t ,
scrapeManager ,
myJob ,
true ,
[ ] string {
fmt . Sprintf ( "http://%s/metrics" , myJobSDTargetURL ) ,
fmt . Sprintf ( "http://%s/metrics" , myJobStaticTargetURL ) ,
} ,
)
// Apply a new config where a provider is removed
baseConfig = `
scrape_configs :
- job_name : % s
static_configs :
- targets : [ ' % s ' ]
`
applyConfig (
t ,
fmt . Sprintf (
baseConfig ,
myJob ,
myJobStaticTargetURL ,
) ,
scrapeManager ,
discoveryManager ,
)
// Make sure the corresponding target was dropped
requireTargets (
t ,
scrapeManager ,
myJob ,
false ,
[ ] string {
fmt . Sprintf ( "http://%s/metrics" , myJobStaticTargetURL ) ,
} ,
)
// Apply a new config with no providers
baseConfig = `
scrape_configs :
- job_name : % s
`
applyConfig (
t ,
fmt . Sprintf (
baseConfig ,
myJob ,
) ,
scrapeManager ,
discoveryManager ,
)
// Make sure the corresponding target was dropped
requireTargets (
t ,
scrapeManager ,
myJob ,
false ,
nil ,
)
}
// TestOnlyProviderStaleTargetsAreDropped makes sure that when a job has only one provider with multiple targets
// and when the provider can no longer discover some of those targets, only those stale targets are dropped.
func TestOnlyProviderStaleTargetsAreDropped ( t * testing . T ) {
2025-08-06 08:31:47 -04:00
t . Parallel ( )
2025-10-09 11:06:28 -04:00
ctx := t . Context ( )
2023-11-15 05:41:12 -05:00
jobName := "my-job"
jobTarget1URL := "foo:9876"
jobTarget2URL := "foo:5432"
sdFile1Content := fmt . Sprintf ( ` [ { "targets": ["%s"]}] ` , jobTarget1URL )
sdFile2Content := fmt . Sprintf ( ` [ { "targets": ["%s"]}] ` , jobTarget2URL )
sDFile1 := writeIntoFile ( t , sdFile1Content , "*targets.json" )
sDFile2 := writeIntoFile ( t , sdFile2Content , "*targets.json" )
baseConfig := `
scrape_configs :
- job_name : % s
file_sd_configs :
- files : [ ' % s ' , ' % s ' ]
`
2026-01-29 05:50:17 -05:00
discoveryManager , scrapeManager := runManagers ( t , ctx , nil , nil , teststorage . NewAppendable ( ) )
2023-11-15 05:41:12 -05:00
defer scrapeManager . Stop ( )
applyConfig (
t ,
fmt . Sprintf ( baseConfig , jobName , sDFile1 . Name ( ) , sDFile2 . Name ( ) ) ,
scrapeManager ,
discoveryManager ,
)
// Make sure the job's targets are taken into account
requireTargets (
t ,
scrapeManager ,
jobName ,
true ,
[ ] string {
fmt . Sprintf ( "http://%s/metrics" , jobTarget1URL ) ,
fmt . Sprintf ( "http://%s/metrics" , jobTarget2URL ) ,
} ,
)
// Apply the same config for the same job but with a non existing file to make the provider
// unable to discover some targets
applyConfig (
t ,
fmt . Sprintf ( baseConfig , jobName , sDFile1 . Name ( ) , "/idontexistdoi.json" ) ,
scrapeManager ,
discoveryManager ,
)
// The old target should get dropped
requireTargets (
t ,
scrapeManager ,
jobName ,
false ,
[ ] string { fmt . Sprintf ( "http://%s/metrics" , jobTarget1URL ) } ,
)
}
// TestProviderStaleTargetsAreDropped makes sure that when a job has only one provider and when that provider
// should no longer discover targets, the targets of that provider are dropped.
// See: https://github.com/prometheus/prometheus/issues/12858
func TestProviderStaleTargetsAreDropped ( t * testing . T ) {
2025-10-09 11:06:28 -04:00
ctx := t . Context ( )
2023-11-15 05:41:12 -05:00
jobName := "my-job"
jobTargetURL := "foo:9876"
sdFileContent := fmt . Sprintf ( ` [ { "targets": ["%s"]}] ` , jobTargetURL )
sDFile := writeIntoFile ( t , sdFileContent , "*targets.json" )
baseConfig := `
scrape_configs :
- job_name : % s
file_sd_configs :
- files : [ ' % s ' ]
`
2026-01-29 05:50:17 -05:00
discoveryManager , scrapeManager := runManagers ( t , ctx , nil , nil , teststorage . NewAppendable ( ) )
2023-11-15 05:41:12 -05:00
defer scrapeManager . Stop ( )
applyConfig (
t ,
fmt . Sprintf ( baseConfig , jobName , sDFile . Name ( ) ) ,
scrapeManager ,
discoveryManager ,
)
// Make sure the job's targets are taken into account
requireTargets (
t ,
scrapeManager ,
jobName ,
true ,
[ ] string {
fmt . Sprintf ( "http://%s/metrics" , jobTargetURL ) ,
} ,
)
// Apply the same config for the same job but with a non existing file to make the provider
// unable to discover some targets
applyConfig (
t ,
fmt . Sprintf ( baseConfig , jobName , "/idontexistdoi.json" ) ,
scrapeManager ,
discoveryManager ,
)
// The old target should get dropped
requireTargets (
t ,
scrapeManager ,
jobName ,
false ,
nil ,
)
}
2024-08-30 07:37:25 -04:00
// TestOnlyStaleTargetsAreDropped makes sure that when a job has multiple providers, when one of them should no
2024-09-10 16:32:03 -04:00
// longer discover targets, only the stale targets of that provider are dropped.
2023-11-15 05:41:12 -05:00
func TestOnlyStaleTargetsAreDropped ( t * testing . T ) {
2025-10-09 11:06:28 -04:00
ctx := t . Context ( )
2023-11-15 05:41:12 -05:00
myJob := "my-job"
myJobSDTargetURL := "my:9876"
myJobStaticTargetURL := "my:5432"
otherJob := "other-job"
otherJobTargetURL := "other:1234"
sdFileContent := fmt . Sprintf ( ` [ { "targets": ["%s"]}] ` , myJobSDTargetURL )
sDFile := writeIntoFile ( t , sdFileContent , "*targets.json" )
baseConfig := `
scrape_configs :
- job_name : % s
static_configs :
- targets : [ ' % s ' ]
file_sd_configs :
- files : [ ' % s ' ]
- job_name : % s
static_configs :
- targets : [ ' % s ' ]
`
2026-01-29 05:50:17 -05:00
discoveryManager , scrapeManager := runManagers ( t , ctx , nil , nil , teststorage . NewAppendable ( ) )
2023-11-15 05:41:12 -05:00
defer scrapeManager . Stop ( )
// Apply the initial config with an existing file
applyConfig (
t ,
fmt . Sprintf (
baseConfig ,
myJob ,
myJobStaticTargetURL ,
sDFile . Name ( ) ,
otherJob ,
otherJobTargetURL ,
) ,
scrapeManager ,
discoveryManager ,
)
// Make sure the jobs targets are taken into account
requireTargets (
t ,
scrapeManager ,
myJob ,
true ,
[ ] string {
fmt . Sprintf ( "http://%s/metrics" , myJobSDTargetURL ) ,
fmt . Sprintf ( "http://%s/metrics" , myJobStaticTargetURL ) ,
} ,
)
requireTargets (
t ,
scrapeManager ,
otherJob ,
true ,
[ ] string { fmt . Sprintf ( "http://%s/metrics" , otherJobTargetURL ) } ,
)
// Apply the same config with a non existing file for myJob
applyConfig (
t ,
fmt . Sprintf (
baseConfig ,
myJob ,
myJobStaticTargetURL ,
"/idontexistdoi.json" ,
otherJob ,
otherJobTargetURL ,
) ,
scrapeManager ,
discoveryManager ,
)
// Only the SD target should get dropped for myJob
requireTargets (
t ,
scrapeManager ,
myJob ,
false ,
[ ] string {
fmt . Sprintf ( "http://%s/metrics" , myJobStaticTargetURL ) ,
} ,
)
// The otherJob should keep its target
requireTargets (
t ,
scrapeManager ,
otherJob ,
false ,
[ ] string { fmt . Sprintf ( "http://%s/metrics" , otherJobTargetURL ) } ,
)
}
2025-10-30 06:58:31 -04:00
func TestManagerDisableEndOfRunStalenessMarkers ( t * testing . T ) {
cfgText := `
scrape_configs :
- job_name : one
scrape_interval : 1 m
scrape_timeout : 1 m
- job_name : two
scrape_interval : 1 m
scrape_timeout : 1 m
`
cfg := loadConfiguration ( t , cfgText )
2026-01-29 05:50:17 -05:00
m , err := NewManager ( & Options { } , nil , nil , nil , teststorage . NewAppendable ( ) , prometheus . NewRegistry ( ) )
2025-10-30 06:58:31 -04:00
require . NoError ( t , err )
defer m . Stop ( )
require . NoError ( t , m . ApplyConfig ( cfg ) )
// Pass targets to the manager.
tgs := map [ string ] [ ] * targetgroup . Group {
"one" : { { Targets : [ ] model . LabelSet { { "__address__" : "h1" } , { "__address__" : "h2" } , { "__address__" : "h3" } } } } ,
"two" : { { Targets : [ ] model . LabelSet { { "__address__" : "h4" } } } } ,
}
m . updateTsets ( tgs )
m . reload ( )
activeTargets := m . TargetsActive ( )
targetsToDisable := [ ] * Target {
activeTargets [ "one" ] [ 0 ] ,
activeTargets [ "one" ] [ 2 ] ,
}
// Disable end of run staleness markers for some targets.
m . DisableEndOfRunStalenessMarkers ( "one" , targetsToDisable )
// This should be a no-op
m . DisableEndOfRunStalenessMarkers ( "non-existent-job" , targetsToDisable )
// Check that the end of run staleness markers are disabled for the correct targets.
for _ , group := range [ ] string { "one" , "two" } {
for _ , tg := range activeTargets [ group ] {
loop := m . scrapePools [ group ] . loops [ tg . hash ( ) ] . ( * scrapeLoop )
expectedDisabled := slices . Contains ( targetsToDisable , tg )
require . Equal ( t , expectedDisabled , loop . disabledEndOfRunStalenessMarkers . Load ( ) )
}
}
}