VictoriaMetrics/lib/promutils/labels.go
Aliaksandr Valialkin 9bc565663b
lib/promscrape: do not re-use previously loaded scrape targets on failed attempt to load updated scrape targets at file_sd_configs
The logic employed for re-using the previously loaded scrape target was broken initially.
The commit cc0427897c tried to fix it, but the new logic
became too complex and fragile. So it is better to just remove this logic,
since the targets from temporarily broken file should be eventually loaded on next
attempts every -promscrape.fileSDCheckInterval

This also allows removing fragile hacks around __vm_filepath label.

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3989
2023-04-02 21:13:22 -07:00

339 lines
7.9 KiB
Go

package promutils
import (
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
"sync"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
)
// Labels contains Prometheus labels.
type Labels struct {
Labels []prompbmarshal.Label
}
// NewLabels returns Labels with the given capacity.
func NewLabels(capacity int) *Labels {
return &Labels{
Labels: make([]prompbmarshal.Label, 0, capacity),
}
}
// NewLabelsFromMap returns Labels generated from m.
func NewLabelsFromMap(m map[string]string) *Labels {
var x Labels
x.InitFromMap(m)
return &x
}
// MarshalYAML implements yaml.Marshaler interface.
func (x *Labels) MarshalYAML() (interface{}, error) {
m := x.ToMap()
return m, nil
}
// UnmarshalYAML implements yaml.Unmarshaler interface.
func (x *Labels) UnmarshalYAML(unmarshal func(interface{}) error) error {
var m map[string]string
if err := unmarshal(&m); err != nil {
return err
}
x.InitFromMap(m)
return nil
}
// MarshalJSON returns JSON respresentation for x.
func (x *Labels) MarshalJSON() ([]byte, error) {
m := x.ToMap()
return json.Marshal(m)
}
// UnmarshalJSON unmarshals JSON from data.
func (x *Labels) UnmarshalJSON(data []byte) error {
var m map[string]string
if err := json.Unmarshal(data, &m); err != nil {
return err
}
x.InitFromMap(m)
return nil
}
// InitFromMap initializes x from m.
func (x *Labels) InitFromMap(m map[string]string) {
x.Reset()
for name, value := range m {
x.Add(name, value)
}
x.Sort()
}
// ToMap returns a map for the given labels x.
func (x *Labels) ToMap() map[string]string {
labels := x.GetLabels()
m := make(map[string]string, len(labels))
for _, label := range labels {
m[label.Name] = label.Value
}
return m
}
// GetLabels returns the list of labels from x.
func (x *Labels) GetLabels() []prompbmarshal.Label {
if x == nil {
return nil
}
return x.Labels
}
// String returns string representation of x.
func (x *Labels) String() string {
labels := x.GetLabels()
// Calculate the required memory for storing serialized labels.
n := 2 // for `{...}`
for _, label := range labels {
n += len(label.Name) + len(label.Value)
n += 4 // for `="...",`
}
b := make([]byte, 0, n)
b = append(b, '{')
for i, label := range labels {
b = append(b, label.Name...)
b = append(b, '=')
b = strconv.AppendQuote(b, label.Value)
if i+1 < len(labels) {
b = append(b, ',')
}
}
b = append(b, '}')
return bytesutil.ToUnsafeString(b)
}
// Reset resets x.
func (x *Labels) Reset() {
cleanLabels(x.Labels)
x.Labels = x.Labels[:0]
}
// Clone returns a clone of x.
func (x *Labels) Clone() *Labels {
srcLabels := x.GetLabels()
labels := append([]prompbmarshal.Label{}, srcLabels...)
return &Labels{
Labels: labels,
}
}
// Sort sorts x labels in alphabetical order of their names.
func (x *Labels) Sort() {
if !sort.IsSorted(x) {
sort.Sort(x)
}
}
// SortStable sorts x labels in alphabetical order of their name using stable sort.
func (x *Labels) SortStable() {
if !sort.IsSorted(x) {
sort.Stable(x)
}
}
// Len returns the number of labels in x.
func (x *Labels) Len() int {
labels := x.GetLabels()
return len(labels)
}
// Less compares label names at i and j index.
func (x *Labels) Less(i, j int) bool {
labels := x.Labels
return labels[i].Name < labels[j].Name
}
// Swap swaps labels at i and j index.
func (x *Labels) Swap(i, j int) {
labels := x.Labels
labels[i], labels[j] = labels[j], labels[i]
}
// Add adds name=value label to x.
func (x *Labels) Add(name, value string) {
x.Labels = append(x.Labels, prompbmarshal.Label{
Name: name,
Value: value,
})
}
// AddFrom adds src labels to x.
func (x *Labels) AddFrom(src *Labels) {
for _, label := range src.GetLabels() {
x.Add(label.Name, label.Value)
}
}
// Get returns value for label with the given name.
func (x *Labels) Get(name string) string {
labels := x.GetLabels()
for _, label := range labels {
if label.Name == name {
return label.Value
}
}
return ""
}
// InternStrings interns all the strings used in x labels.
func (x *Labels) InternStrings() {
labels := x.GetLabels()
for _, label := range labels {
label.Name = bytesutil.InternString(label.Name)
label.Value = bytesutil.InternString(label.Value)
}
}
// RemoveDuplicates removes labels with duplicate names.
func (x *Labels) RemoveDuplicates() {
if x.Len() < 2 {
return
}
// Remove duplicate labels if any.
// Stable sorting is needed in order to preserve the order for labels with identical names.
// This is needed in order to remove labels with duplicate names other than the last one.
x.SortStable()
labels := x.Labels
prevName := labels[0].Name
hasDuplicateLabels := false
for _, label := range labels[1:] {
if label.Name == prevName {
hasDuplicateLabels = true
break
}
prevName = label.Name
}
if !hasDuplicateLabels {
return
}
prevName = labels[0].Name
tmp := labels[:1]
for _, label := range labels[1:] {
if label.Name == prevName {
tmp[len(tmp)-1] = label
} else {
tmp = append(tmp, label)
prevName = label.Name
}
}
cleanLabels(labels[len(tmp):])
x.Labels = tmp
}
// RemoveMetaLabels removes all the `__meta_` labels from x.
//
// See https://www.robustperception.io/life-of-a-label fo details.
func (x *Labels) RemoveMetaLabels() {
src := x.Labels
dst := x.Labels[:0]
for _, label := range src {
if strings.HasPrefix(label.Name, "__meta_") {
continue
}
dst = append(dst, label)
}
cleanLabels(src[len(dst):])
x.Labels = dst
}
// RemoveLabelsWithDoubleUnderscorePrefix removes labels with "__" prefix from x.
func (x *Labels) RemoveLabelsWithDoubleUnderscorePrefix() {
src := x.Labels
dst := x.Labels[:0]
for _, label := range src {
name := label.Name
if strings.HasPrefix(name, "__") {
continue
}
dst = append(dst, label)
}
cleanLabels(src[len(dst):])
x.Labels = dst
}
func cleanLabels(labels []prompbmarshal.Label) {
for i := range labels {
label := &labels[i]
label.Name = ""
label.Value = ""
}
}
// GetLabels returns and empty Labels instance from the pool.
//
// The returned Labels instance must be returned to pool via PutLabels() when no longer needed.
func GetLabels() *Labels {
v := labelsPool.Get()
if v == nil {
return &Labels{}
}
return v.(*Labels)
}
// PutLabels returns x, which has been obtained via GetLabels(), to the pool.
//
// The x mustn't be used after returning to the pool.
func PutLabels(x *Labels) {
x.Reset()
labelsPool.Put(x)
}
var labelsPool sync.Pool
// MustNewLabelsFromString creates labels from s, which can have the form `metric{labels}`.
//
// This function must be used only in tests. Use NewLabelsFromString in production code.
func MustNewLabelsFromString(metricWithLabels string) *Labels {
labels, err := NewLabelsFromString(metricWithLabels)
if err != nil {
logger.Panicf("BUG: cannot parse %q: %s", metricWithLabels, err)
}
return labels
}
// NewLabelsFromString creates labels from s, which can have the form `metric{labels}`.
//
// This function must be used only in tests
func NewLabelsFromString(metricWithLabels string) (*Labels, error) {
stripDummyMetric := false
if strings.HasPrefix(metricWithLabels, "{") {
// Add a dummy metric name, since the parser needs it
metricWithLabels = "dummy_metric" + metricWithLabels
stripDummyMetric = true
}
// add a value to metricWithLabels, so it could be parsed by prometheus protocol parser.
s := metricWithLabels + " 123"
var rows prometheus.Rows
var err error
rows.UnmarshalWithErrLogger(s, func(s string) {
err = fmt.Errorf("error during metric parse: %s", s)
})
if err != nil {
return nil, err
}
if len(rows.Rows) != 1 {
return nil, fmt.Errorf("unexpected number of rows parsed; got %d; want 1", len(rows.Rows))
}
r := rows.Rows[0]
var x Labels
if !stripDummyMetric {
x.Add("__name__", r.Metric)
}
for _, tag := range r.Tags {
x.Add(tag.Key, tag.Value)
}
return &x, nil
}