Repository: prometheus/prometheus
Pull request #13293 (2023-12-15 06:58:16)

Adding small test update for temp dir using t.TempDir
This is a little fix update for this issue: 
https://github.com/prometheus/prometheus/issues/9594

Use case. Why is this important? 
Error handling is implicit.
Cleanup is implicit.


Patch:
diff --git a/go.mod b/go.mod
index 9ea078083..0ecfd4374 100644
--- a/go.mod
+++ b/go.mod
@@ -48,7 +48,7 @@ require (
 	github.com/prometheus/alertmanager v0.26.0
 	github.com/prometheus/client_golang v1.17.0
 	github.com/prometheus/client_model v0.5.0
-	github.com/prometheus/common v0.45.0
+	github.com/prometheus/common v0.45.1-0.20231122191551-832cd6e99f99
 	github.com/prometheus/common/assets v0.2.0
 	github.com/prometheus/common/sigv4 v0.1.0
 	github.com/prometheus/exporter-toolkit v0.10.0
diff --git a/go.sum b/go.sum
index a6f535dd7..fb31c7a84 100644
--- a/go.sum
+++ b/go.sum
@@ -653,8 +653,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
-github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
-github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
+github.com/prometheus/common v0.45.1-0.20231122191551-832cd6e99f99 h1:V5ajRiLiCQGO+ggTr+07gMUcTqlIMMkDBfrJe5zKLmc=
+github.com/prometheus/common v0.45.1-0.20231122191551-832cd6e99f99/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
 github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM=
 github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI=
 github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4=
diff --git a/model/metadata/metadata.go b/model/metadata/metadata.go
index f227af0b9..f6f2827a4 100644
--- a/model/metadata/metadata.go
+++ b/model/metadata/metadata.go
@@ -13,11 +13,11 @@
 
 package metadata
 
-import "github.com/prometheus/prometheus/model/textparse"
+import "github.com/prometheus/common/model"
 
 // Metadata stores a series' metadata information.
 type Metadata struct {
-	Type textparse.MetricType
+	Type model.MetricType
 	Unit string
 	Help string
 }
diff --git a/model/relabel/relabel.go b/model/relabel/relabel.go
index fadf35b86..d29c3d07a 100644
--- a/model/relabel/relabel.go
+++ b/model/relabel/relabel.go
@@ -108,6 +108,10 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if c.Regex.Regexp == nil {
 		c.Regex = MustNewRegexp("")
 	}
+	return c.Validate()
+}
+
+func (c *Config) Validate() error {
 	if c.Action == "" {
 		return fmt.Errorf("relabel action cannot be empty")
 	}
@@ -117,7 +121,13 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	if (c.Action == Replace || c.Action == HashMod || c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.TargetLabel == "" {
 		return fmt.Errorf("relabel configuration for %s action requires 'target_label' value", c.Action)
 	}
-	if (c.Action == Replace || c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && !relabelTarget.MatchString(c.TargetLabel) {
+	if c.Action == Replace && !strings.Contains(c.TargetLabel, "$") && !model.LabelName(c.TargetLabel).IsValid() {
+		return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
+	}
+	if c.Action == Replace && strings.Contains(c.TargetLabel, "$") && !relabelTarget.MatchString(c.TargetLabel) {
+		return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
+	}
+	if (c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && !model.LabelName(c.TargetLabel).IsValid() {
 		return fmt.Errorf("%q is invalid 'target_label' for %s action", c.TargetLabel, c.Action)
 	}
 	if (c.Action == Lowercase || c.Action == Uppercase || c.Action == KeepEqual || c.Action == DropEqual) && c.Replacement != DefaultRelabelConfig.Replacement {
@@ -264,12 +274,11 @@ func relabel(cfg *Config, lb *labels.Builder) (keep bool) {
 		}
 		target := model.LabelName(cfg.Regex.ExpandString([]byte{}, cfg.TargetLabel, val, indexes))
 		if !target.IsValid() {
-			lb.Del(cfg.TargetLabel)
 			break
 		}
 		res := cfg.Regex.ExpandString([]byte{}, cfg.Replacement, val, indexes)
 		if len(res) == 0 {
-			lb.Del(cfg.TargetLabel)
+			lb.Del(string(target))
 			break
 		}
 		lb.Set(string(target), string(res))
diff --git a/model/textparse/interface.go b/model/textparse/interface.go
index df4259c85..3a363ebfb 100644
--- a/model/textparse/interface.go
+++ b/model/textparse/interface.go
@@ -16,6 +16,8 @@ package textparse
 import (
 	"mime"
 
+	"github.com/prometheus/common/model"
+
 	"github.com/prometheus/prometheus/model/exemplar"
 	"github.com/prometheus/prometheus/model/histogram"
 	"github.com/prometheus/prometheus/model/labels"
@@ -42,7 +44,7 @@ type Parser interface {
 	// Type returns the metric name and type in the current entry.
 	// Must only be called after Next returned a type entry.
 	// The returned byte slices become invalid after the next call to Next.
-	Type() ([]byte, MetricType)
+	Type() ([]byte, model.MetricType)
 
 	// Unit returns the metric name and unit in the current entry.
 	// Must only be called after Next returned a unit entry.
@@ -109,17 +111,3 @@ const (
 	EntryUnit      Entry = 4
 	EntryHistogram Entry = 5 // A series with a native histogram as a value.
 )
-
-// MetricType represents metric type values.
-type MetricType string
-
-const (
-	MetricTypeCounter        = MetricType("counter")
-	MetricTypeGauge          = MetricType("gauge")
-	MetricTypeHistogram      = MetricType("histogram")
-	MetricTypeGaugeHistogram = MetricType("gaugehistogram")
-	MetricTypeSummary        = MetricType("summary")
-	MetricTypeInfo           = MetricType("info")
-	MetricTypeStateset       = MetricType("stateset")
-	MetricTypeUnknown        = MetricType("unknown")
-)
diff --git a/model/textparse/openmetricsparse.go b/model/textparse/openmetricsparse.go
index f0c383723..ddfbe4fc5 100644
--- a/model/textparse/openmetricsparse.go
+++ b/model/textparse/openmetricsparse.go
@@ -24,6 +24,8 @@ import (
 	"strings"
 	"unicode/utf8"
 
+	"github.com/prometheus/common/model"
+
 	"github.com/prometheus/prometheus/model/exemplar"
 	"github.com/prometheus/prometheus/model/histogram"
 	"github.com/prometheus/prometheus/model/labels"
@@ -74,7 +76,7 @@ type OpenMetricsParser struct {
 	builder labels.ScratchBuilder
 	series  []byte
 	text    []byte
-	mtype   MetricType
+	mtype   model.MetricType
 	val     float64
 	ts      int64
 	hasTS   bool
@@ -126,7 +128,7 @@ func (p *OpenMetricsParser) Help() ([]byte, []byte) {
 // Type returns the metric name and type in the current entry.
 // Must only be called after Next returned a type entry.
 // The returned byte slices become invalid after the next call to Next.
-func (p *OpenMetricsParser) Type() ([]byte, MetricType) {
+func (p *OpenMetricsParser) Type() ([]byte, model.MetricType) {
 	return p.l.b[p.offsets[0]:p.offsets[1]], p.mtype
 }
 
@@ -272,21 +274,21 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
 		case tType:
 			switch s := yoloString(p.text); s {
 			case "counter":
-				p.mtype = MetricTypeCounter
+				p.mtype = model.MetricTypeCounter
 			case "gauge":
-				p.mtype = MetricTypeGauge
+				p.mtype = model.MetricTypeGauge
 			case "histogram":
-				p.mtype = MetricTypeHistogram
+				p.mtype = model.MetricTypeHistogram
 			case "gaugehistogram":
-				p.mtype = MetricTypeGaugeHistogram
+				p.mtype = model.MetricTypeGaugeHistogram
 			case "summary":
-				p.mtype = MetricTypeSummary
+				p.mtype = model.MetricTypeSummary
 			case "info":
-				p.mtype = MetricTypeInfo
+				p.mtype = model.MetricTypeInfo
 			case "stateset":
-				p.mtype = MetricTypeStateset
+				p.mtype = model.MetricTypeStateset
 			case "unknown":
-				p.mtype = MetricTypeUnknown
+				p.mtype = model.MetricTypeUnknown
 			default:
 				return EntryInvalid, fmt.Errorf("invalid metric type %q", s)
 			}
diff --git a/model/textparse/promparse.go b/model/textparse/promparse.go
index 935801fb9..7123e52c3 100644
--- a/model/textparse/promparse.go
+++ b/model/textparse/promparse.go
@@ -26,6 +26,8 @@ import (
 	"unicode/utf8"
 	"unsafe"
 
+	"github.com/prometheus/common/model"
+
 	"github.com/prometheus/prometheus/model/exemplar"
 	"github.com/prometheus/prometheus/model/histogram"
 	"github.com/prometheus/prometheus/model/labels"
@@ -146,7 +148,7 @@ type PromParser struct {
 	builder labels.ScratchBuilder
 	series  []byte
 	text    []byte
-	mtype   MetricType
+	mtype   model.MetricType
 	val     float64
 	ts      int64
 	hasTS   bool
@@ -190,7 +192,7 @@ func (p *PromParser) Help() ([]byte, []byte) {
 // Type returns the metric name and type in the current entry.
 // Must only be called after Next returned a type entry.
 // The returned byte slices become invalid after the next call to Next.
-func (p *PromParser) Type() ([]byte, MetricType) {
+func (p *PromParser) Type() ([]byte, model.MetricType) {
 	return p.l.b[p.offsets[0]:p.offsets[1]], p.mtype
 }
 
@@ -305,15 +307,15 @@ func (p *PromParser) Next() (Entry, error) {
 		case tType:
 			switch s := yoloString(p.text); s {
 			case "counter":
-				p.mtype = MetricTypeCounter
+				p.mtype = model.MetricTypeCounter
 			case "gauge":
-				p.mtype = MetricTypeGauge
+				p.mtype = model.MetricTypeGauge
 			case "histogram":
-				p.mtype = MetricTypeHistogram
+				p.mtype = model.MetricTypeHistogram
 			case "summary":
-				p.mtype = MetricTypeSummary
+				p.mtype = model.MetricTypeSummary
 			case "untyped":
-				p.mtype = MetricTypeUnknown
+				p.mtype = model.MetricTypeUnknown
 			default:
 				return EntryInvalid, fmt.Errorf("invalid metric type %q", s)
 			}
diff --git a/model/textparse/protobufparse.go b/model/textparse/protobufparse.go
index baede7e1d..534bbebb2 100644
--- a/model/textparse/protobufparse.go
+++ b/model/textparse/protobufparse.go
@@ -252,21 +252,21 @@ func (p *ProtobufParser) Help() ([]byte, []byte) {
 // Type returns the metric name and type in the current entry.
 // Must only be called after Next returned a type entry.
 // The returned byte slices become invalid after the next call to Next.
-func (p *ProtobufParser) Type() ([]byte, MetricType) {
+func (p *ProtobufParser) Type() ([]byte, model.MetricType) {
 	n := p.metricBytes.Bytes()
 	switch p.mf.GetType() {
 	case dto.MetricType_COUNTER:
-		return n, MetricTypeCounter
+		return n, model.MetricTypeCounter
 	case dto.MetricType_GAUGE:
-		return n, MetricTypeGauge
+		return n, model.MetricTypeGauge
 	case dto.MetricType_HISTOGRAM:
-		return n, MetricTypeHistogram
+		return n, model.MetricTypeHistogram
 	case dto.MetricType_GAUGE_HISTOGRAM:
-		return n, MetricTypeGaugeHistogram
+		return n, model.MetricTypeGaugeHistogram
 	case dto.MetricType_SUMMARY:
-		return n, MetricTypeSummary
+		return n, model.MetricTypeSummary
 	}
-	return n, MetricTypeUnknown
+	return n, model.MetricTypeUnknown
 }
 
 // Unit always returns (nil, nil) because units aren't supported by the protobuf
diff --git a/prompb/types.pb.go b/prompb/types.pb.go
index 125f868e9..93883daa1 100644
--- a/prompb/types.pb.go
+++ b/prompb/types.pb.go
@@ -164,7 +164,7 @@ func (Chunk_Encoding) EnumDescriptor() ([]byte, []int) {
 
 type MetricMetadata struct {
 	// Represents the metric type, these match the set from Prometheus.
-	// Refer to model/textparse/interface.go for details.
+	// Refer to github.com/prometheus/common/model/metadata.go for details.
 	Type                 MetricMetadata_MetricType `protobuf:"varint,1,opt,name=type,proto3,enum=prometheus.MetricMetadata_MetricType" json:"type,omitempty"`
 	MetricFamilyName     string                    `protobuf:"bytes,2,opt,name=metric_family_name,json=metricFamilyName,proto3" json:"metric_family_name,omitempty"`
 	Help                 string                    `protobuf:"bytes,4,opt,name=help,proto3" json:"help,omitempty"`
diff --git a/prompb/types.proto b/prompb/types.proto
index aa322515c..61fc1e014 100644
--- a/prompb/types.proto
+++ b/prompb/types.proto
@@ -31,7 +31,7 @@ message MetricMetadata {
   }
 
   // Represents the metric type, these match the set from Prometheus.
-  // Refer to model/textparse/interface.go for details.
+  // Refer to github.com/prometheus/common/model/metadata.go for details.
   MetricType type = 1;
   string metric_family_name = 2;
   string help = 4;
diff --git a/promql/engine.go b/promql/engine.go
index 16b8ee500..2ea37dae6 100644
--- a/promql/engine.go
+++ b/promql/engine.go
@@ -2052,7 +2052,12 @@ func (ev *evaluator) matrixIterSlice(
 		var drop int
 		for drop = 0; histograms[drop].T < mint; drop++ {
 		}
+		// Rotate the buffer around the drop index so that points before mint can be
+		// reused to store new histograms.
+		tail := make([]HPoint, drop)
+		copy(tail, histograms[:drop])
 		copy(histograms, histograms[drop:])
+		copy(histograms[len(histograms)-drop:], tail)
 		histograms = histograms[:len(histograms)-drop]
 		ev.currentSamples -= totalHPointSize(histograms)
 		// Only append points with timestamps after the last timestamp we have.
@@ -2121,17 +2126,22 @@ loop:
 	// The sought sample might also be in the range.
 	switch soughtValueType {
 	case chunkenc.ValFloatHistogram, chunkenc.ValHistogram:
-		t, h := it.AtFloatHistogram()
-		if t == maxt && !value.IsStaleNaN(h.Sum) {
-			if ev.currentSamples >= ev.maxSamples {
-				ev.error(ErrTooManySamples(env))
-			}
-			if histograms == nil {
-				histograms = getHPointSlice(16)
+		t := it.AtT()
+		if t == maxt {
+			_, h := it.AtFloatHistogram()
+			if !value.IsStaleNaN(h.Sum) {
+				if ev.currentSamples >= ev.maxSamples {
+					ev.error(ErrTooManySamples(env))
+				}
+				if histograms == nil {
+					histograms = getHPointSlice(16)
+				}
+				// The last sample comes directly from the iterator, so we need to copy it to
+				// avoid having the same reference twice in the buffer.
+				point := HPoint{T: t, H: h.Copy()}
+				histograms = append(histograms, point)
+				ev.currentSamples += point.size()
 			}
-			point := HPoint{T: t, H: h}
-			histograms = append(histograms, point)
-			ev.currentSamples += point.size()
 		}
 	case chunkenc.ValFloat:
 		t, f := it.At()
diff --git a/scrape/manager.go b/scrape/manager.go
index faa46f54d..3ad315a50 100644
--- a/scrape/manager.go
+++ b/scrape/manager.go
@@ -288,24 +288,10 @@ func (m *Manager) TargetsActive() map[string][]*Target {
 	m.mtxScrape.Lock()
 	defer m.mtxScrape.Unlock()
 
-	var (
-		wg  sync.WaitGroup
-		mtx sync.Mutex
-	)
-
 	targets := make(map[string][]*Target, len(m.scrapePools))
-	wg.Add(len(m.scrapePools))
 	for tset, sp := range m.scrapePools {
-		// Running in parallel limits the blocking time of scrapePool to scrape
-		// interval when there's an update from SD.
-		go func(tset string, sp *scrapePool) {
-			mtx.Lock()
-			targets[tset] = sp.ActiveTargets()
-			mtx.Unlock()
-			wg.Done()
-		}(tset, sp)
+		targets[tset] = sp.ActiveTargets()
 	}
-	wg.Wait()
 	return targets
 }
 
diff --git a/scrape/scrape.go b/scrape/scrape.go
index be27a5d48..2518912a7 100644
--- a/scrape/scrape.go
+++ b/scrape/scrape.go
@@ -675,7 +675,7 @@ func acceptHeader(sps []config.ScrapeProtocol) string {
 		weight--
 	}
 	// Default match anything.
-	vals = append(vals, fmt.Sprintf("*/*;q=%d", weight))
+	vals = append(vals, fmt.Sprintf("*/*;q=0.%d", weight))
 	return strings.Join(vals, ",")
 }
 
@@ -961,12 +961,12 @@ func (c *scrapeCache) forEachStale(f func(labels.Labels) bool) {
 	}
 }
 
-func (c *scrapeCache) setType(metric []byte, t textparse.MetricType) {
+func (c *scrapeCache) setType(metric []byte, t model.MetricType) {
 	c.metaMtx.Lock()
 
 	e, ok := c.metadata[string(metric)]
 	if !ok {
-		e = &metaEntry{Metadata: metadata.Metadata{Type: textparse.MetricTypeUnknown}}
+		e = &metaEntry{Metadata: metadata.Metadata{Type: model.MetricTypeUnknown}}
 		c.metadata[string(metric)] = e
 	}
 	if e.Type != t {
@@ -983,7 +983,7 @@ func (c *scrapeCache) setHelp(metric, help []byte) {
 
 	e, ok := c.metadata[string(metric)]
 	if !ok {
-		e = &metaEntry{Metadata: metadata.Metadata{Type: textparse.MetricTypeUnknown}}
+		e = &metaEntry{Metadata: metadata.Metadata{Type: model.MetricTypeUnknown}}
 		c.metadata[string(metric)] = e
 	}
 	if e.Help != string(help) {
@@ -1000,7 +1000,7 @@ func (c *scrapeCache) setUnit(metric, unit []byte) {
 
 	e, ok := c.metadata[string(metric)]
 	if !ok {
-		e = &metaEntry{Metadata: metadata.Metadata{Type: textparse.MetricTypeUnknown}}
+		e = &metaEntry{Metadata: metadata.Metadata{Type: model.MetricTypeUnknown}}
 		c.metadata[string(metric)] = e
 	}
 	if e.Unit != string(unit) {
diff --git a/scrape/target.go b/scrape/target.go
index fd984f5a6..0605f5349 100644
--- a/scrape/target.go
+++ b/scrape/target.go
@@ -30,7 +30,6 @@ import (
 	"github.com/prometheus/prometheus/model/histogram"
 	"github.com/prometheus/prometheus/model/labels"
 	"github.com/prometheus/prometheus/model/relabel"
-	"github.com/prometheus/prometheus/model/textparse"
 	"github.com/prometheus/prometheus/model/value"
 	"github.com/prometheus/prometheus/storage"
 )
@@ -87,7 +86,7 @@ type MetricMetadataStore interface {
 // MetricMetadata is a piece of metadata for a metric.
 type MetricMetadata struct {
 	Metric string
-	Type   textparse.MetricType
+	Type   model.MetricType
 	Help   string
 	Unit   string
 }
diff --git a/storage/remote/codec.go b/storage/remote/codec.go
index 67035cd8e..ffab821a5 100644
--- a/storage/remote/codec.go
+++ b/storage/remote/codec.go
@@ -33,7 +33,6 @@ import (
 	"github.com/prometheus/prometheus/model/exemplar"
 	"github.com/prometheus/prometheus/model/histogram"
 	"github.com/prometheus/prometheus/model/labels"
-	"github.com/prometheus/prometheus/model/textparse"
 	"github.com/prometheus/prometheus/prompb"
 	"github.com/prometheus/prometheus/storage"
 	"github.com/prometheus/prometheus/tsdb/chunkenc"
@@ -784,7 +783,7 @@ func labelsToLabelsProto(lbls labels.Labels, buf []prompb.Label) []prompb.Label
 }
 
 // metricTypeToMetricTypeProto transforms a Prometheus metricType into prompb metricType. Since the former is a string we need to transform it to an enum.
-func metricTypeToMetricTypeProto(t textparse.MetricType) prompb.MetricMetadata_MetricType {
+func metricTypeToMetricTypeProto(t model.MetricType) prompb.MetricMetadata_MetricType {
 	mt := strings.ToUpper(string(t))
 	v, ok := prompb.MetricMetadata_MetricType_value[mt]
 	if !ok {
diff --git a/tsdb/head_append.go b/tsdb/head_append.go
index f509317c8..f112ffa3a 100644
--- a/tsdb/head_append.go
+++ b/tsdb/head_append.go
@@ -1038,7 +1038,7 @@ func (a *headAppender) Commit() (err error) {
 	for i, m := range a.metadata {
 		series = a.metadataSeries[i]
 		series.Lock()
-		series.meta = &metadata.Metadata{Type: record.ToTextparseMetricType(m.Type), Unit: m.Unit, Help: m.Help}
+		series.meta = &metadata.Metadata{Type: record.ToMetricType(m.Type), Unit: m.Unit, Help: m.Help}
 		series.Unlock()
 	}
 
diff --git a/tsdb/head_wal.go b/tsdb/head_wal.go
index a492a85a0..1be65f134 100644
--- a/tsdb/head_wal.go
+++ b/tsdb/head_wal.go
@@ -388,7 +388,7 @@ Outer:
 					continue
 				}
 				s.meta = &metadata.Metadata{
-					Type: record.ToTextparseMetricType(m.Type),
+					Type: record.ToMetricType(m.Type),
 					Unit: m.Unit,
 					Help: m.Help,
 				}
diff --git a/tsdb/record/record.go b/tsdb/record/record.go
index 42a656dfe..3931ad05d 100644
--- a/tsdb/record/record.go
+++ b/tsdb/record/record.go
@@ -20,9 +20,10 @@ import (
 	"fmt"
 	"math"
 
+	"github.com/prometheus/common/model"
+
 	"github.com/prometheus/prometheus/model/histogram"
 	"github.com/prometheus/prometheus/model/labels"
-	"github.com/prometheus/prometheus/model/textparse"
 	"github.com/prometheus/prometheus/storage"
 	"github.com/prometheus/prometheus/tsdb/chunks"
 	"github.com/prometheus/prometheus/tsdb/encoding"
@@ -90,45 +91,45 @@ const (
 	Stateset        MetricType = 7
 )
 
-func GetMetricType(t textparse.MetricType) uint8 {
+func GetMetricType(t model.MetricType) uint8 {
 	switch t {
-	case textparse.MetricTypeCounter:
+	case model.MetricTypeCounter:
 		return uint8(Counter)
-	case textparse.MetricTypeGauge:
+	case model.MetricTypeGauge:
 		return uint8(Gauge)
-	case textparse.MetricTypeHistogram:
+	case model.MetricTypeHistogram:
 		return uint8(HistogramSample)
-	case textparse.MetricTypeGaugeHistogram:
+	case model.MetricTypeGaugeHistogram:
 		return uint8(GaugeHistogram)
-	case textparse.MetricTypeSummary:
+	case model.MetricTypeSummary:
 		return uint8(Summary)
-	case textparse.MetricTypeInfo:
+	case model.MetricTypeInfo:
 		return uint8(Info)
-	case textparse.MetricTypeStateset:
+	case model.MetricTypeStateset:
 		return uint8(Stateset)
 	default:
 		return uint8(UnknownMT)
 	}
 }
 
-func ToTextparseMetricType(m uint8) textparse.MetricType {
+func ToMetricType(m uint8) model.MetricType {
 	switch m {
 	case uint8(Counter):
-		return textparse.MetricTypeCounter
+		return model.MetricTypeCounter
 	case uint8(Gauge):
-		return textparse.MetricTypeGauge
+		return model.MetricTypeGauge
 	case uint8(HistogramSample):
-		return textparse.MetricTypeHistogram
+		return model.MetricTypeHistogram
 	case uint8(GaugeHistogram):
-		return textparse.MetricTypeGaugeHistogram
+		return model.MetricTypeGaugeHistogram
 	case uint8(Summary):
-		return textparse.MetricTypeSummary
+		return model.MetricTypeSummary
 	case uint8(Info):
-		return textparse.MetricTypeInfo
+		return model.MetricTypeInfo
 	case uint8(Stateset):
-		return textparse.MetricTypeStateset
+		return model.MetricTypeStateset
 	default:
-		return textparse.MetricTypeUnknown
+		return model.MetricTypeUnknown
 	}
 }
 
diff --git a/tsdb/tsdbutil/histogram.go b/tsdb/tsdbutil/histogram.go
index 0847f81a8..bb8d49b20 100644
--- a/tsdb/tsdbutil/histogram.go
+++ b/tsdb/tsdbutil/histogram.go
@@ -30,6 +30,14 @@ func GenerateTestHistograms(n int) (r []*histogram.Histogram) {
 	return r
 }
 
+func GenerateTestHistogramsWithUnknownResetHint(n int) []*histogram.Histogram {
+	hs := GenerateTestHistograms(n)
+	for i := range hs {
+		hs[i].CounterResetHint = histogram.UnknownCounterReset
+	}
+	return hs
+}
+
 // GenerateTestHistogram but it is up to the user to set any known counter reset hint.
 func GenerateTestHistogram(i int) *histogram.Histogram {
 	return &histogram.Histogram{
diff --git a/web/api/v1/api.go b/web/api/v1/api.go
index 8fa7ce14a..dd35d1fe9 100644
--- a/web/api/v1/api.go
+++ b/web/api/v1/api.go
@@ -41,7 +41,7 @@ import (
 
 	"github.com/prometheus/prometheus/config"
 	"github.com/prometheus/prometheus/model/labels"
-	"github.com/prometheus/prometheus/model/textparse"
+	"github.com/prometheus/prometheus/model/metadata"
 	"github.com/prometheus/prometheus/model/timestamp"
 	"github.com/prometheus/prometheus/promql"
 	"github.com/prometheus/prometheus/promql/parser"
@@ -1141,11 +1141,11 @@ func (api *API) targetMetadata(r *http.Request) apiFuncResult {
 }
 
 type metricMetadata struct {
-	Target labels.Labels        `json:"target"`
-	Metric string               `json:"metric,omitempty"`
-	Type   textparse.MetricType `json:"type"`
-	Help   string               `json:"help"`
-	Unit   string               `json:"unit"`
+	Target labels.Labels    `json:"target"`
+	Metric string           `json:"metric,omitempty"`
+	Type   model.MetricType `json:"type"`
+	Help   string           `json:"help"`
+	Unit   string           `json:"unit"`
 }
 
 // AlertmanagerDiscovery has all the active Alertmanagers.
@@ -1221,14 +1221,8 @@ func rulesAlertsToAPIAlerts(rulesAlerts []*rules.Alert) []*Alert {
 	return apiAlerts
 }
 
-type metadata struct {
-	Type textparse.MetricType `json:"type"`
-	Help string               `json:"help"`
-	Unit string               `json:"unit"`
-}
-
 func (api *API) metricMetadata(r *http.Request) apiFuncResult {
-	metrics := map[string]map[metadata]struct{}{}
+	metrics := map[string]map[metadata.Metadata]struct{}{}
 
 	limit := -1
 	if s := r.FormValue("limit"); s != "" {
@@ -1250,7 +1244,7 @@ func (api *API) metricMetadata(r *http.Request) apiFuncResult {
 		for _, t := range tt {
 			if metric == "" {
 				for _, mm := range t.ListMetadata() {
-					m := metadata{Type: mm.Type, Help: mm.Help, Unit: mm.Unit}
+					m := metadata.Metadata{Type: mm.Type, Help: mm.Help, Unit: mm.Unit}
 					ms, ok := metrics[mm.Metric]
 
 					if limitPerMetric > 0 && len(ms) >= limitPerMetric {
@@ -1258,7 +1252,7 @@ func (api *API) metricMetadata(r *http.Request) apiFuncResult {
 					}
 
 					if !ok {
-						ms = map[metadata]struct{}{}
+						ms = map[metadata.Metadata]struct{}{}
 						metrics[mm.Metric] = ms
 					}
 					ms[m] = struct{}{}
@@ -1267,7 +1261,7 @@ func (api *API) metricMetadata(r *http.Request) apiFuncResult {
 			}
 
 			if md, ok := t.GetMetadata(metric); ok {
-				m := metadata{Type: md.Type, Help: md.Help, Unit: md.Unit}
+				m := metadata.Metadata{Type: md.Type, Help: md.Help, Unit: md.Unit}
 				ms, ok := metrics[md.Metric]
 
 				if limitPerMetric > 0 && len(ms) >= limitPerMetric {
@@ -1275,7 +1269,7 @@ func (api *API) metricMetadata(r *http.Request) apiFuncResult {
 				}
 
 				if !ok {
-					ms = map[metadata]struct{}{}
+					ms = map[metadata.Metadata]struct{}{}
 					metrics[md.Metric] = ms
 				}
 				ms[m] = struct{}{}
@@ -1284,13 +1278,13 @@ func (api *API) metricMetadata(r *http.Request) apiFuncResult {
 	}
 
 	// Put the elements from the pseudo-set into a slice for marshaling.
-	res := map[string][]metadata{}
+	res := map[string][]metadata.Metadata{}
 	for name, set := range metrics {
 		if limit >= 0 && len(res) >= limit {
 			break
 		}
 
-		s := []metadata{}
+		s := []metadata.Metadata{}
 		for metadata := range set {
 			s = append(s, metadata)
 		}


Test patch:
diff --git a/model/relabel/relabel_test.go b/model/relabel/relabel_test.go
index b50ff4010..517b9b822 100644
--- a/model/relabel/relabel_test.go
+++ b/model/relabel/relabel_test.go
@@ -14,6 +14,7 @@
 package relabel
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/prometheus/common/model"
@@ -213,6 +214,25 @@ func TestRelabel(t *testing.T) {
 				"a": "boo",
 			}),
 		},
+		{
+			// Blank replacement should delete the label.
+			input: labels.FromMap(map[string]string{
+				"a": "foo",
+				"f": "baz",
+			}),
+			relabel: []*Config{
+				{
+					SourceLabels: model.LabelNames{"a"},
+					Regex:        MustNewRegexp("(f).*"),
+					TargetLabel:  "$1",
+					Replacement:  "$2",
+					Action:       Replace,
+				},
+			},
+			output: labels.FromMap(map[string]string{
+				"a": "foo",
+			}),
+		},
 		{
 			input: labels.FromMap(map[string]string{
 				"a": "foo",
@@ -334,7 +354,7 @@ func TestRelabel(t *testing.T) {
 		},
 		{ // invalid target_labels
 			input: labels.FromMap(map[string]string{
-				"a": "some-name-value",
+				"a": "some-name-0",
 			}),
 			relabel: []*Config{
 				{
@@ -349,18 +369,18 @@ func TestRelabel(t *testing.T) {
 					Regex:        MustNewRegexp("some-([^-]+)-([^,]+)"),
 					Action:       Replace,
 					Replacement:  "${1}",
-					TargetLabel:  "0${3}",
+					TargetLabel:  "${3}",
 				},
 				{
 					SourceLabels: model.LabelNames{"a"},
-					Regex:        MustNewRegexp("some-([^-]+)-([^,]+)"),
+					Regex:        MustNewRegexp("some-([^-]+)(-[^,]+)"),
 					Action:       Replace,
 					Replacement:  "${1}",
-					TargetLabel:  "-${3}",
+					TargetLabel:  "${3}",
 				},
 			},
 			output: labels.FromMap(map[string]string{
-				"a": "some-name-value",
+				"a": "some-name-0",
 			}),
 		},
 		{ // more complex real-life like usecase
@@ -565,6 +585,7 @@ func TestRelabel(t *testing.T) {
 			if cfg.Replacement == "" {
 				cfg.Replacement = DefaultRelabelConfig.Replacement
 			}
+			require.NoError(t, cfg.Validate())
 		}
 
 		res, keep := Process(test.input, test.relabel...)
@@ -575,6 +596,77 @@ func TestRelabel(t *testing.T) {
 	}
 }
 
+func TestRelabelValidate(t *testing.T) {
+	tests := []struct {
+		config   Config
+		expected string
+	}{
+		{
+			config:   Config{},
+			expected: `relabel action cannot be empty`,
+		},
+		{
+			config: Config{
+				Action: Replace,
+			},
+			expected: `requires 'target_label' value`,
+		},
+		{
+			config: Config{
+				Action: Lowercase,
+			},
+			expected: `requires 'target_label' value`,
+		},
+		{
+			config: Config{
+				Action:      Lowercase,
+				Replacement: DefaultRelabelConfig.Replacement,
+				TargetLabel: "${3}",
+			},
+			expected: `"${3}" is invalid 'target_label'`,
+		},
+		{
+			config: Config{
+				SourceLabels: model.LabelNames{"a"},
+				Regex:        MustNewRegexp("some-([^-]+)-([^,]+)"),
+				Action:       Replace,
+				Replacement:  "${1}",
+				TargetLabel:  "${3}",
+			},
+		},
+		{
+			config: Config{
+				SourceLabels: model.LabelNames{"a"},
+				Regex:        MustNewRegexp("some-([^-]+)-([^,]+)"),
+				Action:       Replace,
+				Replacement:  "${1}",
+				TargetLabel:  "0${3}",
+			},
+			expected: `"0${3}" is invalid 'target_label'`,
+		},
+		{
+			config: Config{
+				SourceLabels: model.LabelNames{"a"},
+				Regex:        MustNewRegexp("some-([^-]+)-([^,]+)"),
+				Action:       Replace,
+				Replacement:  "${1}",
+				TargetLabel:  "-${3}",
+			},
+			expected: `"-${3}" is invalid 'target_label' for replace action`,
+		},
+	}
+	for i, test := range tests {
+		t.Run(fmt.Sprint(i), func(t *testing.T) {
+			err := test.config.Validate()
+			if test.expected == "" {
+				require.NoError(t, err)
+			} else {
+				require.ErrorContains(t, err, test.expected)
+			}
+		})
+	}
+}
+
 func TestTargetLabelValidity(t *testing.T) {
 	tests := []struct {
 		str   string
diff --git a/model/textparse/openmetricsparse_test.go b/model/textparse/openmetricsparse_test.go
index eed30364c..2b1d909f3 100644
--- a/model/textparse/openmetricsparse_test.go
+++ b/model/textparse/openmetricsparse_test.go
@@ -18,6 +18,7 @@ import (
 	"io"
 	"testing"
 
+	"github.com/prometheus/common/model"
 	"github.com/stretchr/testify/require"
 
 	"github.com/prometheus/prometheus/model/exemplar"
@@ -77,7 +78,7 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
 		m       string
 		t       *int64
 		v       float64
-		typ     MetricType
+		typ     model.MetricType
 		help    string
 		unit    string
 		comment string
@@ -88,7 +89,7 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
 			help: "A summary of the GC invocation durations.",
 		}, {
 			m:   "go_gc_duration_seconds",
-			typ: MetricTypeSummary,
+			typ: model.MetricTypeSummary,
 		}, {
 			m:    "go_gc_duration_seconds",
 			unit: "seconds",
@@ -130,7 +131,7 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
 			help: "Number of goroutines that currently exist.",
 		}, {
 			m:   "go_goroutines",
-			typ: MetricTypeGauge,
+			typ: model.MetricTypeGauge,
 		}, {
 			m:    `go_goroutines`,
 			v:    33,
@@ -138,21 +139,21 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
 			lset: labels.FromStrings("__name__", "go_goroutines"),
 		}, {
 			m:   "hh",
-			typ: MetricTypeHistogram,
+			typ: model.MetricTypeHistogram,
 		}, {
 			m:    `hh_bucket{le="+Inf"}`,
 			v:    1,
 			lset: labels.FromStrings("__name__", "hh_bucket", "le", "+Inf"),
 		}, {
 			m:   "gh",
-			typ: MetricTypeGaugeHistogram,
+			typ: model.MetricTypeGaugeHistogram,
 		}, {
 			m:    `gh_bucket{le="+Inf"}`,
 			v:    1,
 			lset: labels.FromStrings("__name__", "gh_bucket", "le", "+Inf"),
 		}, {
 			m:   "hhh",
-			typ: MetricTypeHistogram,
+			typ: model.MetricTypeHistogram,
 		}, {
 			m:    `hhh_bucket{le="+Inf"}`,
 			v:    1,
@@ -165,7 +166,7 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
 			e:    &exemplar.Exemplar{Labels: labels.FromStrings("id", "histogram-count-test"), Value: 4},
 		}, {
 			m:   "ggh",
-			typ: MetricTypeGaugeHistogram,
+			typ: model.MetricTypeGaugeHistogram,
 		}, {
 			m:    `ggh_bucket{le="+Inf"}`,
 			v:    1,
@@ -178,7 +179,7 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
 			e:    &exemplar.Exemplar{Labels: labels.FromStrings("id", "gaugehistogram-count-test", "xx", "yy"), Value: 4, HasTs: true, Ts: 123123},
 		}, {
 			m:   "smr_seconds",
-			typ: MetricTypeSummary,
+			typ: model.MetricTypeSummary,
 		}, {
 			m:    `smr_seconds_count`,
 			v:    2,
@@ -191,14 +192,14 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
 			e:    &exemplar.Exemplar{Labels: labels.FromStrings("id", "summary-sum-test"), Value: 1, HasTs: true, Ts: 123321},
 		}, {
 			m:   "ii",
-			typ: MetricTypeInfo,
+			typ: model.MetricTypeInfo,
 		}, {
 			m:    `ii{foo="bar"}`,
 			v:    1,
 			lset: labels.FromStrings("__name__", "ii", "foo", "bar"),
 		}, {
 			m:   "ss",
-			typ: MetricTypeStateset,
+			typ: model.MetricTypeStateset,
 		}, {
 			m:    `ss{ss="foo"}`,
 			v:    1,
@@ -213,7 +214,7 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
 			lset: labels.FromStrings("A", "a", "__name__", "ss"),
 		}, {
 			m:   "un",
-			typ: MetricTypeUnknown,
+			typ: model.MetricTypeUnknown,
 		}, {
 			m:    "_metric_starting_with_underscore",
 			v:    1,
@@ -228,7 +229,7 @@ foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
 			lset: labels.FromStrings("__name__", "testmetric", "label", `"bar"`),
 		}, {
 			m:   "foo",
-			typ: MetricTypeCounter,
+			typ: model.MetricTypeCounter,
 		}, {
 			m:    "foo_total",
 			v:    17,
diff --git a/model/textparse/promparse_test.go b/model/textparse/promparse_test.go
index ac79a1394..d34b26ba5 100644
--- a/model/textparse/promparse_test.go
+++ b/model/textparse/promparse_test.go
@@ -65,7 +65,7 @@ testmetric{label="\"bar\""} 1`
 		m       string
 		t       *int64
 		v       float64
-		typ     MetricType
+		typ     model.MetricType
 		help    string
 		comment string
 	}{
@@ -74,7 +74,7 @@ testmetric{label="\"bar\""} 1`
 			help: "A summary of the GC invocation durations.",
 		}, {
 			m:   "go_gc_duration_seconds",
-			typ: MetricTypeSummary,
+			typ: model.MetricTypeSummary,
 		}, {
 			m:    `go_gc_duration_seconds{quantile="0"}`,
 			v:    4.9351e-05,
@@ -142,7 +142,7 @@ testmetric{label="\"bar\""} 1`
 			help: "Number of goroutines that currently exist.",
 		}, {
 			m:   "go_goroutines",
-			typ: MetricTypeGauge,
+			typ: model.MetricTypeGauge,
 		}, {
 			m:    `go_goroutines`,
 			v:    33,
diff --git a/model/textparse/protobufparse_test.go b/model/textparse/protobufparse_test.go
index c5b672dbc..7dcc85f54 100644
--- a/model/textparse/protobufparse_test.go
+++ b/model/textparse/protobufparse_test.go
@@ -21,6 +21,7 @@ import (
 	"testing"
 
 	"github.com/gogo/protobuf/proto"
+	"github.com/prometheus/common/model"
 	"github.com/stretchr/testify/require"
 
 	"github.com/prometheus/prometheus/model/exemplar"
@@ -622,7 +623,7 @@ func TestProtobufParse(t *testing.T) {
 		m       string
 		t       int64
 		v       float64
-		typ     MetricType
+		typ     model.MetricType
 		help    string
 		unit    string
 		comment string
@@ -649,7 +650,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "go_build_info",
-					typ: MetricTypeGauge,
+					typ: model.MetricTypeGauge,
 				},
 				{
 					m: "go_build_info\xFFchecksum\xFF\xFFpath\xFFgithub.com/prometheus/client_golang\xFFversion\xFF(devel)",
@@ -667,7 +668,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "go_memstats_alloc_bytes_total",
-					typ: MetricTypeCounter,
+					typ: model.MetricTypeCounter,
 				},
 				{
 					m: "go_memstats_alloc_bytes_total",
@@ -685,7 +686,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "something_untyped",
-					typ: MetricTypeUnknown,
+					typ: model.MetricTypeUnknown,
 				},
 				{
 					m: "something_untyped",
@@ -701,7 +702,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "test_histogram",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{
 					m: "test_histogram",
@@ -736,7 +737,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "test_gauge_histogram",
-					typ: MetricTypeGaugeHistogram,
+					typ: model.MetricTypeGaugeHistogram,
 				},
 				{
 					m: "test_gauge_histogram",
@@ -772,7 +773,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "test_float_histogram",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{
 					m: "test_float_histogram",
@@ -807,7 +808,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "test_gauge_float_histogram",
-					typ: MetricTypeGaugeHistogram,
+					typ: model.MetricTypeGaugeHistogram,
 				},
 				{
 					m: "test_gauge_float_histogram",
@@ -843,7 +844,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "test_histogram2",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{
 					m: "test_histogram2_count",
@@ -903,7 +904,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "test_histogram_family",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{
 					m: "test_histogram_family\xfffoo\xffbar",
@@ -947,7 +948,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "test_float_histogram_with_zerothreshold_zero",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{
 					m: "test_float_histogram_with_zerothreshold_zero",
@@ -971,7 +972,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "rpc_durations_seconds",
-					typ: MetricTypeSummary,
+					typ: model.MetricTypeSummary,
 				},
 				{
 					m: "rpc_durations_seconds_count\xffservice\xffexponential",
@@ -1022,7 +1023,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "without_quantiles",
-					typ: MetricTypeSummary,
+					typ: model.MetricTypeSummary,
 				},
 				{
 					m: "without_quantiles_count",
@@ -1044,7 +1045,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "empty_histogram",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{
 					m: "empty_histogram",
@@ -1063,7 +1064,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "test_counter_with_createdtimestamp",
-					typ: MetricTypeCounter,
+					typ: model.MetricTypeCounter,
 				},
 				{
 					m:  "test_counter_with_createdtimestamp",
@@ -1079,7 +1080,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "test_summary_with_createdtimestamp",
-					typ: MetricTypeSummary,
+					typ: model.MetricTypeSummary,
 				},
 				{
 					m:  "test_summary_with_createdtimestamp_count",
@@ -1103,7 +1104,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "test_histogram_with_createdtimestamp",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{
 					m:  "test_histogram_with_createdtimestamp",
@@ -1123,7 +1124,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{
 					m:   "test_gaugehistogram_with_createdtimestamp",
-					typ: MetricTypeGaugeHistogram,
+					typ: model.MetricTypeGaugeHistogram,
 				},
 				{
 					m:  "test_gaugehistogram_with_createdtimestamp",
@@ -1149,7 +1150,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 1
 					m:   "go_build_info",
-					typ: MetricTypeGauge,
+					typ: model.MetricTypeGauge,
 				},
 				{ // 2
 					m: "go_build_info\xFFchecksum\xFF\xFFpath\xFFgithub.com/prometheus/client_golang\xFFversion\xFF(devel)",
@@ -1167,7 +1168,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 4
 					m:   "go_memstats_alloc_bytes_total",
-					typ: MetricTypeCounter,
+					typ: model.MetricTypeCounter,
 				},
 				{ // 5
 					m: "go_memstats_alloc_bytes_total",
@@ -1185,7 +1186,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 7
 					m:   "something_untyped",
-					typ: MetricTypeUnknown,
+					typ: model.MetricTypeUnknown,
 				},
 				{ // 8
 					m: "something_untyped",
@@ -1201,7 +1202,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 10
 					m:   "test_histogram",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{ // 11
 					m: "test_histogram",
@@ -1294,7 +1295,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 19
 					m:   "test_gauge_histogram",
-					typ: MetricTypeGaugeHistogram,
+					typ: model.MetricTypeGaugeHistogram,
 				},
 				{ // 20
 					m: "test_gauge_histogram",
@@ -1388,7 +1389,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 28
 					m:   "test_float_histogram",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{ // 29
 					m: "test_float_histogram",
@@ -1481,7 +1482,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 37
 					m:   "test_gauge_float_histogram",
-					typ: MetricTypeGaugeHistogram,
+					typ: model.MetricTypeGaugeHistogram,
 				},
 				{ // 38
 					m: "test_gauge_float_histogram",
@@ -1575,7 +1576,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 46
 					m:   "test_histogram2",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{ // 47
 					m: "test_histogram2_count",
@@ -1635,7 +1636,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 54
 					m:   "test_histogram_family",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{ // 55
 					m: "test_histogram_family\xfffoo\xffbar",
@@ -1765,7 +1766,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 68
 					m:   "test_float_histogram_with_zerothreshold_zero",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{ // 69
 					m: "test_float_histogram_with_zerothreshold_zero",
@@ -1789,7 +1790,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 71
 					m:   "rpc_durations_seconds",
-					typ: MetricTypeSummary,
+					typ: model.MetricTypeSummary,
 				},
 				{ // 72
 					m: "rpc_durations_seconds_count\xffservice\xffexponential",
@@ -1840,7 +1841,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 78
 					m:   "without_quantiles",
-					typ: MetricTypeSummary,
+					typ: model.MetricTypeSummary,
 				},
 				{ // 79
 					m: "without_quantiles_count",
@@ -1862,7 +1863,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 79
 					m:   "empty_histogram",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{ // 80
 					m: "empty_histogram",
@@ -1881,7 +1882,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 82
 					m:   "test_counter_with_createdtimestamp",
-					typ: MetricTypeCounter,
+					typ: model.MetricTypeCounter,
 				},
 				{ // 83
 					m:  "test_counter_with_createdtimestamp",
@@ -1897,7 +1898,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 85
 					m:   "test_summary_with_createdtimestamp",
-					typ: MetricTypeSummary,
+					typ: model.MetricTypeSummary,
 				},
 				{ // 86
 					m:  "test_summary_with_createdtimestamp_count",
@@ -1921,7 +1922,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 89
 					m:   "test_histogram_with_createdtimestamp",
-					typ: MetricTypeHistogram,
+					typ: model.MetricTypeHistogram,
 				},
 				{ // 90
 					m:  "test_histogram_with_createdtimestamp",
@@ -1941,7 +1942,7 @@ func TestProtobufParse(t *testing.T) {
 				},
 				{ // 92
 					m:   "test_gaugehistogram_with_createdtimestamp",
-					typ: MetricTypeGaugeHistogram,
+					typ: model.MetricTypeGaugeHistogram,
 				},
 				{ // 93
 					m:  "test_gaugehistogram_with_createdtimestamp",
diff --git a/promql/bench_test.go b/promql/bench_test.go
index 13eba3714..b7a4978de 100644
--- a/promql/bench_test.go
+++ b/promql/bench_test.go
@@ -296,8 +296,12 @@ func BenchmarkNativeHistograms(b *testing.B) {
 			query: "sum(native_histogram_series)",
 		},
 		{
-			name:  "sum rate",
-			query: "sum(rate(native_histogram_series[1m]))",
+			name:  "sum rate with short rate interval",
+			query: "sum(rate(native_histogram_series[2m]))",
+		},
+		{
+			name:  "sum rate with long rate interval",
+			query: "sum(rate(native_histogram_series[20m]))",
 		},
 	}
 
diff --git a/promql/engine_test.go b/promql/engine_test.go
index 9ab54dd16..b3e7d4094 100644
--- a/promql/engine_test.go
+++ b/promql/engine_test.go
@@ -18,7 +18,6 @@ import (
 	"errors"
 	"fmt"
 	"math"
-	"os"
 	"sort"
 	"testing"
 	"time"
@@ -47,9 +46,7 @@ func TestMain(m *testing.M) {
 func TestQueryConcurrency(t *testing.T) {
 	maxConcurrency := 10
 
-	dir, err := os.MkdirTemp("", "test_concurrency")
-	require.NoError(t, err)
-	defer os.RemoveAll(dir)
+	dir := t.TempDir()
 	queryTracker := NewActiveQueryTracker(dir, maxConcurrency, nil)
 	t.Cleanup(queryTracker.Close)
 
@@ -3169,28 +3166,75 @@ func TestNativeHistogramRate(t *testing.T) {
 	}
 	require.NoError(t, app.Commit())
 
-	queryString := fmt.Sprintf("rate(%s[1m])", seriesName)
-	qry, err := engine.NewInstantQuery(context.Background(), storage, nil, queryString, timestamp.Time(int64(5*time.Minute/time.Millisecond)))
-	require.NoError(t, err)
-	res := qry.Exec(context.Background())
-	require.NoError(t, res.Err)
-	vector, err := res.Vector()
-	require.NoError(t, err)
-	require.Len(t, vector, 1)
-	actualHistogram := vector[0].H
-	expectedHistogram := &histogram.FloatHistogram{
-		CounterResetHint: histogram.GaugeType,
-		Schema:           1,
-		ZeroThreshold:    0.001,
-		ZeroCount:        1. / 15.,
-		Count:            9. / 15.,
-		Sum:              1.226666666666667,
-		PositiveSpans:    []histogram.Span{{Offset: 0, Length: 2}, {Offset: 1, Length: 2}},
-		PositiveBuckets:  []float64{1. / 15., 1. / 15., 1. / 15., 1. / 15.},
-		NegativeSpans:    []histogram.Span{{Offset: 0, Length: 2}, {Offset: 1, Length: 2}},
-		NegativeBuckets:  []float64{1. / 15., 1. / 15., 1. / 15., 1. / 15.},
-	}
-	require.Equal(t, expectedHistogram, actualHistogram)
+	queryString := fmt.Sprintf("rate(%s[45s])", seriesName)
+	t.Run("instant_query", func(t *testing.T) {
+		qry, err := engine.NewInstantQuery(context.Background(), storage, nil, queryString, timestamp.Time(int64(5*time.Minute/time.Millisecond)))
+		require.NoError(t, err)
+		res := qry.Exec(context.Background())
+		require.NoError(t, res.Err)
+		vector, err := res.Vector()
+		require.NoError(t, err)
+		require.Len(t, vector, 1)
+		actualHistogram := vector[0].H
+		expectedHistogram := &histogram.FloatHistogram{
+			CounterResetHint: histogram.GaugeType,
+			Schema:           1,
+			ZeroThreshold:    0.001,
+			ZeroCount:        1. / 15.,
+			Count:            9. / 15.,
+			Sum:              1.2266666666666663,
+			PositiveSpans:    []histogram.Span{{Offset: 0, Length: 2}, {Offset: 1, Length: 2}},
+			PositiveBuckets:  []float64{1. / 15., 1. / 15., 1. / 15., 1. / 15.},
+			NegativeSpans:    []histogram.Span{{Offset: 0, Length: 2}, {Offset: 1, Length: 2}},
+			NegativeBuckets:  []float64{1. / 15., 1. / 15., 1. / 15., 1. / 15.},
+		}
+		require.Equal(t, expectedHistogram, actualHistogram)
+	})
+
+	t.Run("range_query", func(t *testing.T) {
+		step := 30 * time.Second
+		start := timestamp.Time(int64(5 * time.Minute / time.Millisecond))
+		end := start.Add(step)
+		qry, err := engine.NewRangeQuery(context.Background(), storage, nil, queryString, start, end, step)
+		require.NoError(t, err)
+		res := qry.Exec(context.Background())
+		require.NoError(t, res.Err)
+		matrix, err := res.Matrix()
+		require.NoError(t, err)
+		require.Len(t, matrix, 1)
+		require.Len(t, matrix[0].Histograms, 2)
+		actualHistograms := matrix[0].Histograms
+		expectedHistograms := []HPoint{{
+			T: 300000,
+			H: &histogram.FloatHistogram{
+				CounterResetHint: histogram.GaugeType,
+				Schema:           1,
+				ZeroThreshold:    0.001,
+				ZeroCount:        1. / 15.,
+				Count:            9. / 15.,
+				Sum:              1.2266666666666663,
+				PositiveSpans:    []histogram.Span{{Offset: 0, Length: 2}, {Offset: 1, Length: 2}},
+				PositiveBuckets:  []float64{1. / 15., 1. / 15., 1. / 15., 1. / 15.},
+				NegativeSpans:    []histogram.Span{{Offset: 0, Length: 2}, {Offset: 1, Length: 2}},
+				NegativeBuckets:  []float64{1. / 15., 1. / 15., 1. / 15., 1. / 15.},
+			},
+		}, {
+			T: 330000,
+			H: &histogram.FloatHistogram{
+				CounterResetHint: histogram.GaugeType,
+				Schema:           1,
+				ZeroThreshold:    0.001,
+				ZeroCount:        1. / 15.,
+				Count:            9. / 15.,
+				Sum:              1.2266666666666663,
+				PositiveSpans:    []histogram.Span{{Offset: 0, Length: 2}, {Offset: 1, Length: 2}},
+				PositiveBuckets:  []float64{1. / 15., 1. / 15., 1. / 15., 1. / 15.},
+				NegativeSpans:    []histogram.Span{{Offset: 0, Length: 2}, {Offset: 1, Length: 2}},
+				NegativeBuckets:  []float64{1. / 15., 1. / 15., 1. / 15., 1. / 15.},
+			},
+		}}
+		require.Equal(t, expectedHistograms, actualHistograms)
+	})
 }
 
 func TestNativeFloatHistogramRate(t *testing.T) {
diff --git a/scrape/scrape_test.go b/scrape/scrape_test.go
index 90578f2e9..1a416eeb6 100644
--- a/scrape/scrape_test.go
+++ b/scrape/scrape_test.go
@@ -971,19 +971,19 @@ test_metric 1
 
 	md, ok := cache.GetMetadata("test_metric")
 	require.True(t, ok, "expected metadata to be present")
-	require.Equal(t, textparse.MetricTypeCounter, md.Type, "unexpected metric type")
+	require.Equal(t, model.MetricTypeCounter, md.Type, "unexpected metric type")
 	require.Equal(t, "some help text", md.Help)
 	require.Equal(t, "metric", md.Unit)
 
 	md, ok = cache.GetMetadata("test_metric_no_help")
 	require.True(t, ok, "expected metadata to be present")
-	require.Equal(t, textparse.MetricTypeGauge, md.Type, "unexpected metric type")
+	require.Equal(t, model.MetricTypeGauge, md.Type, "unexpected metric type")
 	require.Equal(t, "", md.Help)
 	require.Equal(t, "", md.Unit)
 
 	md, ok = cache.GetMetadata("test_metric_no_type")
 	require.True(t, ok, "expected metadata to be present")
-	require.Equal(t, textparse.MetricTypeUnknown, md.Type, "unexpected metric type")
+	require.Equal(t, model.MetricTypeUnknown, md.Type, "unexpected metric type")
 	require.Equal(t, "other help text", md.Help)
 	require.Equal(t, "", md.Unit)
 }
diff --git a/storage/remote/codec_test.go b/storage/remote/codec_test.go
index d2a7d45be..ac8b0f0b5 100644
--- a/storage/remote/codec_test.go
+++ b/storage/remote/codec_test.go
@@ -20,11 +20,11 @@ import (
 	"testing"
 
 	"github.com/gogo/protobuf/proto"
+	"github.com/prometheus/common/model"
 	"github.com/stretchr/testify/require"
 
 	"github.com/prometheus/prometheus/model/histogram"
 	"github.com/prometheus/prometheus/model/labels"
-	"github.com/prometheus/prometheus/model/textparse"
 	"github.com/prometheus/prometheus/prompb"
 	"github.com/prometheus/prometheus/storage"
 	"github.com/prometheus/prometheus/tsdb/chunkenc"
@@ -488,17 +488,17 @@ func TestMergeLabels(t *testing.T) {
 func TestMetricTypeToMetricTypeProto(t *testing.T) {
 	tc := []struct {
 		desc     string
-		input    textparse.MetricType
+		input    model.MetricType
 		expected prompb.MetricMetadata_MetricType
 	}{
 		{
 			desc:     "with a single-word metric",
-			input:    textparse.MetricTypeCounter,
+			input:    model.MetricTypeCounter,
 			expected: prompb.MetricMetadata_COUNTER,
 		},
 		{
 			desc:     "with a two-word metric",
-			input:    textparse.MetricTypeStateset,
+			input:    model.MetricTypeStateset,
 			expected: prompb.MetricMetadata_STATESET,
 		},
 		{
diff --git a/storage/remote/metadata_watcher_test.go b/storage/remote/metadata_watcher_test.go
index cd664bc8b..0cd6027a8 100644
--- a/storage/remote/metadata_watcher_test.go
+++ b/storage/remote/metadata_watcher_test.go
@@ -22,7 +22,6 @@ import (
 	"github.com/prometheus/common/model"
 	"github.com/stretchr/testify/require"
 
-	"github.com/prometheus/prometheus/model/textparse"
 	"github.com/prometheus/prometheus/scrape"
 )
 
@@ -108,13 +107,13 @@ func TestWatchScrapeManager_ReadyForCollection(t *testing.T) {
 		Metadata: []scrape.MetricMetadata{
 			{
 				Metric: "prometheus_tsdb_head_chunks_created_total",
-				Type:   textparse.MetricTypeCounter,
+				Type:   model.MetricTypeCounter,
 				Help:   "Total number",
 				Unit:   "",
 			},
 			{
 				Metric: "prometheus_remote_storage_retried_samples_total",
-				Type:   textparse.MetricTypeCounter,
+				Type:   model.MetricTypeCounter,
 				Help:   "Total number",
 				Unit:   "",
 			},
@@ -124,7 +123,7 @@ func TestWatchScrapeManager_ReadyForCollection(t *testing.T) {
 		Metadata: []scrape.MetricMetadata{
 			{
 				Metric: "prometheus_tsdb_head_chunks_created_total",
-				Type:   textparse.MetricTypeCounter,
+				Type:   model.MetricTypeCounter,
 				Help:   "Total number",
 				Unit:   "",
 			},
diff --git a/storage/remote/queue_manager_test.go b/storage/remote/queue_manager_test.go
index c878c750b..17a904fcd 100644
--- a/storage/remote/queue_manager_test.go
+++ b/storage/remote/queue_manager_test.go
@@ -38,7 +38,6 @@ import (
 	"github.com/prometheus/prometheus/config"
 	"github.com/prometheus/prometheus/model/histogram"
 	"github.com/prometheus/prometheus/model/labels"
-	"github.com/prometheus/prometheus/model/textparse"
 	"github.com/prometheus/prometheus/model/timestamp"
 	"github.com/prometheus/prometheus/prompb"
 	"github.com/prometheus/prometheus/scrape"
@@ -180,7 +179,7 @@ func TestMetadataDelivery(t *testing.T) {
 	for i := 0; i < numMetadata; i++ {
 		metadata = append(metadata, scrape.MetricMetadata{
 			Metric: "prometheus_remote_storage_sent_metadata_bytes_total_" + strconv.Itoa(i),
-			Type:   textparse.MetricTypeCounter,
+			Type:   model.MetricTypeCounter,
 			Help:   "a nice help text",
 			Unit:   "",
 		})
diff --git a/tsdb/tsdbutil/dir_locker_testutil.go b/tsdb/tsdbutil/dir_locker_testutil.go
index a4cf5abd6..a7f43d8ee 100644
--- a/tsdb/tsdbutil/dir_locker_testutil.go
+++ b/tsdb/tsdbutil/dir_locker_testutil.go
@@ -60,12 +60,8 @@ func TestDirLockerUsage(t *testing.T, open func(t *testing.T, data string, creat
 
 	for _, c := range cases {
 		t.Run(fmt.Sprintf("%+v", c), func(t *testing.T) {
-			tmpdir, err := os.MkdirTemp("", "test")
-			require.NoError(t, err)
-			t.Cleanup(func() {
-				require.NoError(t, os.RemoveAll(tmpdir))
-			})
-
+			tmpdir := t.TempDir()
+			
 			// Test preconditions (file already exists + lockfile option)
 			if c.fileAlreadyExists {
 				tmpLocker, err := NewDirLocker(tmpdir, "tsdb", log.NewNopLogger(), nil)
@@ -82,7 +78,7 @@ func TestDirLockerUsage(t *testing.T, open func(t *testing.T, data string, creat
 
 			// Check that the lockfile is always deleted
 			if !c.lockFileDisabled {
-				_, err = os.Stat(locker.path)
+				_, err := os.Stat(locker.path)
 				require.True(t, os.IsNotExist(err), "lockfile was not deleted")
 			}
 		})
diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go
index d4da05e46..c9ab84087 100644
--- a/web/api/v1/api_test.go
+++ b/web/api/v1/api_test.go
@@ -44,7 +44,7 @@ import (
 	"github.com/prometheus/prometheus/config"
 	"github.com/prometheus/prometheus/model/exemplar"
 	"github.com/prometheus/prometheus/model/labels"
-	"github.com/prometheus/prometheus/model/textparse"
+	"github.com/prometheus/prometheus/model/metadata"
 	"github.com/prometheus/prometheus/model/timestamp"
 	"github.com/prometheus/prometheus/promql"
 	"github.com/prometheus/prometheus/promql/parser"
@@ -1584,7 +1584,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created.",
 							Unit:   "",
 						},
@@ -1597,7 +1597,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 						"job": "test",
 					}),
 					Help: "Number of OS threads created.",
-					Type: textparse.MetricTypeGauge,
+					Type: model.MetricTypeGauge,
 					Unit: "",
 				},
 			},
@@ -1614,7 +1614,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "prometheus_tsdb_storage_blocks_bytes",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "The number of bytes that are currently used for local storage by all blocks.",
 							Unit:   "",
 						},
@@ -1628,7 +1628,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					}),
 					Metric: "prometheus_tsdb_storage_blocks_bytes",
 					Help:   "The number of bytes that are currently used for local storage by all blocks.",
-					Type:   textparse.MetricTypeGauge,
+					Type:   model.MetricTypeGauge,
 					Unit:   "",
 				},
 			},
@@ -1642,7 +1642,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created.",
 							Unit:   "",
 						},
@@ -1653,7 +1653,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "prometheus_tsdb_storage_blocks_bytes",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "The number of bytes that are currently used for local storage by all blocks.",
 							Unit:   "",
 						},
@@ -1667,7 +1667,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					}),
 					Metric: "go_threads",
 					Help:   "Number of OS threads created.",
-					Type:   textparse.MetricTypeGauge,
+					Type:   model.MetricTypeGauge,
 					Unit:   "",
 				},
 				{
@@ -1676,7 +1676,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					}),
 					Metric: "prometheus_tsdb_storage_blocks_bytes",
 					Help:   "The number of bytes that are currently used for local storage by all blocks.",
-					Type:   textparse.MetricTypeGauge,
+					Type:   model.MetricTypeGauge,
 					Unit:   "",
 				},
 			},
@@ -1719,22 +1719,22 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "prometheus_engine_query_duration_seconds",
-							Type:   textparse.MetricTypeSummary,
+							Type:   model.MetricTypeSummary,
 							Help:   "Query timings",
 							Unit:   "",
 						},
 						{
 							Metric: "go_info",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Information about the Go environment.",
 							Unit:   "",
 						},
 					},
 				},
 			},
-			response: map[string][]metadata{
-				"prometheus_engine_query_duration_seconds": {{textparse.MetricTypeSummary, "Query timings", ""}},
-				"go_info": {{textparse.MetricTypeGauge, "Information about the Go environment.", ""}},
+			response: map[string][]metadata.Metadata{
+				"prometheus_engine_query_duration_seconds": {{Type: model.MetricTypeSummary, Help: "Query timings", Unit: ""}},
+				"go_info": {{Type: model.MetricTypeGauge, Help: "Information about the Go environment.", Unit: ""}},
 			},
 		},
 		// With duplicate metadata for a metric that comes from different targets.
@@ -1746,7 +1746,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created",
 							Unit:   "",
 						},
@@ -1757,15 +1757,15 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created",
 							Unit:   "",
 						},
 					},
 				},
 			},
-			response: map[string][]metadata{
-				"go_threads": {{textparse.MetricTypeGauge, "Number of OS threads created", ""}},
+			response: map[string][]metadata.Metadata{
+				"go_threads": {{Type: model.MetricTypeGauge, Help: "Number of OS threads created"}},
 			},
 		},
 		// With non-duplicate metadata for the same metric from different targets.
@@ -1777,7 +1777,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created",
 							Unit:   "",
 						},
@@ -1788,21 +1788,21 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads that were created.",
 							Unit:   "",
 						},
 					},
 				},
 			},
-			response: map[string][]metadata{
+			response: map[string][]metadata.Metadata{
 				"go_threads": {
-					{textparse.MetricTypeGauge, "Number of OS threads created", ""},
-					{textparse.MetricTypeGauge, "Number of OS threads that were created.", ""},
+					{Type: model.MetricTypeGauge, Help: "Number of OS threads created"},
+					{Type: model.MetricTypeGauge, Help: "Number of OS threads that were created."},
 				},
 			},
 			sorter: func(m interface{}) {
-				v := m.(map[string][]metadata)["go_threads"]
+				v := m.(map[string][]metadata.Metadata)["go_threads"]
 
 				sort.Slice(v, func(i, j int) bool {
 					return v[i].Help < v[j].Help
@@ -1821,13 +1821,13 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created",
 							Unit:   "",
 						},
 						{
 							Metric: "prometheus_engine_query_duration_seconds",
-							Type:   textparse.MetricTypeSummary,
+							Type:   model.MetricTypeSummary,
 							Help:   "Query Timmings.",
 							Unit:   "",
 						},
@@ -1838,7 +1838,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_gc_duration_seconds",
-							Type:   textparse.MetricTypeSummary,
+							Type:   model.MetricTypeSummary,
 							Help:   "A summary of the GC invocation durations.",
 							Unit:   "",
 						},
@@ -1857,31 +1857,31 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created",
 							Unit:   "",
 						},
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Repeated metadata",
 							Unit:   "",
 						},
 						{
 							Metric: "go_gc_duration_seconds",
-							Type:   textparse.MetricTypeSummary,
+							Type:   model.MetricTypeSummary,
 							Help:   "A summary of the GC invocation durations.",
 							Unit:   "",
 						},
 					},
 				},
 			},
-			response: map[string][]metadata{
+			response: map[string][]metadata.Metadata{
 				"go_threads": {
-					{textparse.MetricTypeGauge, "Number of OS threads created", ""},
+					{Type: model.MetricTypeGauge, Help: "Number of OS threads created"},
 				},
 				"go_gc_duration_seconds": {
-					{textparse.MetricTypeSummary, "A summary of the GC invocation durations.", ""},
+					{Type: model.MetricTypeSummary, Help: "A summary of the GC invocation durations."},
 				},
 			},
 		},
@@ -1895,19 +1895,19 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created",
 							Unit:   "",
 						},
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Repeated metadata",
 							Unit:   "",
 						},
 						{
 							Metric: "go_gc_duration_seconds",
-							Type:   textparse.MetricTypeSummary,
+							Type:   model.MetricTypeSummary,
 							Help:   "A summary of the GC invocation durations.",
 							Unit:   "",
 						},
@@ -1928,19 +1928,19 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created",
 							Unit:   "",
 						},
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Repeated metadata",
 							Unit:   "",
 						},
 						{
 							Metric: "go_gc_duration_seconds",
-							Type:   textparse.MetricTypeSummary,
+							Type:   model.MetricTypeSummary,
 							Help:   "A summary of the GC invocation durations.",
 							Unit:   "",
 						},
@@ -1951,13 +1951,13 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created, but from a different target",
 							Unit:   "",
 						},
 						{
 							Metric: "go_gc_duration_seconds",
-							Type:   textparse.MetricTypeSummary,
+							Type:   model.MetricTypeSummary,
 							Help:   "A summary of the GC invocation durations, but from a different target.",
 							Unit:   "",
 						},
@@ -1977,7 +1977,7 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created",
 							Unit:   "",
 						},
@@ -1988,27 +1988,27 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_gc_duration_seconds",
-							Type:   textparse.MetricTypeSummary,
+							Type:   model.MetricTypeSummary,
 							Help:   "A summary of the GC invocation durations.",
 							Unit:   "",
 						},
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads that were created.",
 							Unit:   "",
 						},
 					},
 				},
 			},
-			response: map[string][]metadata{
+			response: map[string][]metadata.Metadata{
 				"go_threads": {
-					{textparse.MetricTypeGauge, "Number of OS threads created", ""},
-					{textparse.MetricTypeGauge, "Number of OS threads that were created.", ""},
+					{Type: model.MetricTypeGauge, Help: "Number of OS threads created"},
+					{Type: model.MetricTypeGauge, Help: "Number of OS threads that were created."},
 				},
 			},
 			sorter: func(m interface{}) {
-				v := m.(map[string][]metadata)["go_threads"]
+				v := m.(map[string][]metadata.Metadata)["go_threads"]
 
 				sort.Slice(v, func(i, j int) bool {
 					return v[i].Help < v[j].Help
@@ -2025,19 +2025,19 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
 					metadata: []scrape.MetricMetadata{
 						{
 							Metric: "go_threads",
-							Type:   textparse.MetricTypeGauge,
+							Type:   model.MetricTypeGauge,
 							Help:   "Number of OS threads created",
 							Unit:   "",
 						},
 					},
 				},
 			},
-			response: map[string][]metadata{},
+			response: map[string][]metadata.Metadata{},
 		},
 		// With no available metadata.
 		{
 			endpoint: api.metricMetadata,
-			response: map[string][]metadata{},
+			response: map[string][]metadata.Metadata{},
 		},
 		{
 			endpoint: api.serveConfig,
@@ -2931,7 +2931,7 @@ func assertAPIResponseMetadataLen(t *testing.T, got interface{}, expLen int) {
 	t.Helper()
 
 	var gotLen int
-	response := got.(map[string][]metadata)
+	response := got.(map[string][]metadata.Metadata)
 	for _, m := range response {
 		gotLen += len(m)
 	}