mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-17 00:26:36 +03:00
The prompb and prompbmarshal share exactly the same models and provide
marshal and unmarshale capabilities for them. This creates duplication
(changes in one model has to be made in another, case with metadata) and
confusion where for example you compare same looking models but golang
says they are not the same (because of the type).
This commit merge prompbmarshal logic into prompb so the rest of the
code is aligned on prompb models.
Moves samplesPool and labelsPool to WriteRequestUnmarshaller.
Make WriteRequest struct clean from unmarshal logic.
The benchmark shows no significant changes:
$benchstat prompbmarshal.bench prompb2.bench
goos: darwin
goarch: arm64
pkg: github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb
cpu: Apple M1 Pro
│ prompbmarshal.bench │ prompb2.bench │
│ sec/op │ sec/op vs base │
WriteRequestUnmarshalProtobuf-10 189.2µ ± 5% 190.8µ ± 8% ~ (p=0.579 n=10)
WriteRequestMarshalProtobuf-10 145.3µ ± 7% 143.6µ ± 2% ~ (p=0.143 n=10)
geomean 165.8µ 165.5µ -0.14%
│ prompbmarshal.bench │ prompb2.bench │
│ B/s │ B/s vs base │
WriteRequestUnmarshalProtobuf-10 50.42Mi ± 5% 49.99Mi ± 8% ~ (p=0.593 n=10)
WriteRequestMarshalProtobuf-10 65.64Mi ± 7% 66.39Mi ± 2% ~ (p=0.143 n=10)
geomean 57.53Mi 57.61Mi +0.14%
│ prompbmarshal.bench │ prompb2.bench │
│ B/op │ B/op vs base │
WriteRequestUnmarshalProtobuf-10 27.70Ki ± 4% 26.90Ki ± 7% ~ (p=0.190 n=10)
WriteRequestMarshalProtobuf-10 3.267Ki ± 12% 3.273Ki ± 12% ~ (p=0.971 n=10)
geomean 9.514Ki 9.383Ki -1.38%
│ prompbmarshal.bench │ prompb2.bench │
│ allocs/op │ allocs/op vs base │
WriteRequestUnmarshalProtobuf-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
WriteRequestMarshalProtobuf-10 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
348 lines
8.1 KiB
Go
348 lines
8.1 KiB
Go
package promutil
|
|
|
|
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/prompb"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/prometheus"
|
|
)
|
|
|
|
// Labels contains Prometheus labels.
|
|
type Labels struct {
|
|
Labels []prompb.Label
|
|
}
|
|
|
|
// NewLabels returns Labels with the given capacity.
|
|
func NewLabels(capacity int) *Labels {
|
|
return &Labels{
|
|
Labels: make([]prompb.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() (any, error) {
|
|
m := x.ToMap()
|
|
return m, nil
|
|
}
|
|
|
|
// UnmarshalYAML implements yaml.Unmarshaler interface.
|
|
func (x *Labels) UnmarshalYAML(unmarshal func(any) error) error {
|
|
var m map[string]string
|
|
if err := unmarshal(&m); err != nil {
|
|
return err
|
|
}
|
|
x.InitFromMap(m)
|
|
return nil
|
|
}
|
|
|
|
// MarshalJSON returns JSON representation 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() []prompb.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() {
|
|
clear(x.Labels)
|
|
x.Labels = x.Labels[:0]
|
|
}
|
|
|
|
// Clone returns a clone of x.
|
|
func (x *Labels) Clone() *Labels {
|
|
srcLabels := x.GetLabels()
|
|
labels := append([]prompb.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, prompb.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 ""
|
|
}
|
|
|
|
// Set label value for label with given name
|
|
// If the label with the given name doesn't exist, it adds as the new label
|
|
func (x *Labels) Set(name, value string) {
|
|
if name == "" || value == "" {
|
|
return
|
|
}
|
|
labels := x.GetLabels()
|
|
for i, label := range labels {
|
|
if label.Name == name {
|
|
labels[i].Value = value
|
|
return
|
|
}
|
|
}
|
|
x.Add(name, value)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|
|
clear(labels[len(tmp):])
|
|
x.Labels = tmp
|
|
}
|
|
|
|
// RemoveMetaLabels removes all the `__meta_` labels from x.
|
|
//
|
|
// See https://www.robustperception.io/life-of-a-label for 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)
|
|
}
|
|
clear(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)
|
|
}
|
|
clear(src[len(dst):])
|
|
x.Labels = dst
|
|
}
|
|
|
|
// 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 non performance-critical code, since it allocates too much
|
|
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
|
|
}
|