VictoriaMetrics/lib/promutils/labels.go
Aliaksandr Valialkin f325410c26
lib/promscrape: optimize service discovery speed
- Return meta-labels for the discovered targets via promutils.Labels
  instead of map[string]string. This improves the speed of generating
  meta-labels for discovered targets by up to 5x.

- Remove memory allocations in hot paths during ScrapeWork generation.
  The ScrapeWork contains scrape settings for a single discovered target.
  This improves the service discovery speed by up to 2x.
2022-11-29 21:26:00 -08:00

329 lines
7.6 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()
}
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
// A hack: do not delete __vm_filepath label, since it is used by internal logic for FileSDConfig.
if strings.HasPrefix(name, "__") && name != "__vm_filepath" {
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
// 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 {
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 {
logger.Panicf("BUG: cannot parse %q: %s", metricWithLabels, err)
}
if len(rows.Rows) != 1 {
logger.Panicf("BUG: 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
}