mirror of
https://github.com/prometheus/prometheus.git
synced 2025-09-21 05:41:01 +02:00
* fix(textparse): implement NHCB parsing in ProtoBuf parser directly The NHCB conversion does some validation, but we can only return error from Parser.Next() not Parser.Histogram(). So the conversion needs to happen in Next(). There are 2 cases: 1. "always_scrape_classic_histograms" is enabled, in which case we convert after returning the classic series. This is to be consistent with the PromParser text parser, which collects NHCB while spitting out classic series; then returns the NHCB. 2. "always_scrape_classic_histograms" is disabled. In which case we never return the classic series. Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com> * refactor(textparse): skip classic series instead of adding NHCB around Do not return the first classic series from the EntryType state, switch to EntrySeries. This means we need to start the histogram field state from -3 , not -2. In EntrySeries, skip classic series if needed. Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com> * reuse nhcb converter Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com> * test(textparse/nhcb): test corrupting metric family name NHCB parse doesn't always copy the metric name from the underlying parser. When called via HELP, UNIT, the string is directly referenced which means that the read-ahead of NHCB can corrupt it. Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com>
243 lines
5.7 KiB
Go
243 lines
5.7 KiB
Go
// Copyright 2017 The Prometheus Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package labels
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"slices"
|
|
"strconv"
|
|
"unsafe"
|
|
|
|
"github.com/prometheus/common/model"
|
|
)
|
|
|
|
const (
|
|
// MetricName is a special label name that represent a metric name.
|
|
// Deprecated: Use schema.Metadata structure and its methods.
|
|
MetricName = "__name__"
|
|
|
|
AlertName = "alertname"
|
|
BucketLabel = "le"
|
|
|
|
labelSep = '\xfe' // Used at beginning of `Bytes` return.
|
|
sep = '\xff' // Used between labels in `Bytes` and `Hash`.
|
|
)
|
|
|
|
var seps = []byte{sep} // Used with Hash, which has no WriteByte method.
|
|
|
|
// Label is a key/value a pair of strings.
|
|
type Label struct {
|
|
Name, Value string
|
|
}
|
|
|
|
func (ls Labels) String() string {
|
|
return ls.stringImpl(true)
|
|
}
|
|
|
|
// StringNoSpace is like String but does not add a space after commas.
|
|
func (ls Labels) StringNoSpace() string {
|
|
return ls.stringImpl(false)
|
|
}
|
|
|
|
func (ls Labels) stringImpl(addSpace bool) string {
|
|
var bytea [1024]byte // On stack to avoid memory allocation while building the output.
|
|
b := bytes.NewBuffer(bytea[:0])
|
|
|
|
b.WriteByte('{')
|
|
i := 0
|
|
ls.Range(func(l Label) {
|
|
if i > 0 {
|
|
b.WriteByte(',')
|
|
if addSpace {
|
|
b.WriteByte(' ')
|
|
}
|
|
}
|
|
if !model.LegacyValidation.IsValidLabelName(l.Name) {
|
|
b.Write(strconv.AppendQuote(b.AvailableBuffer(), l.Name))
|
|
} else {
|
|
b.WriteString(l.Name)
|
|
}
|
|
b.WriteByte('=')
|
|
b.Write(strconv.AppendQuote(b.AvailableBuffer(), l.Value))
|
|
i++
|
|
})
|
|
b.WriteByte('}')
|
|
return b.String()
|
|
}
|
|
|
|
// MarshalJSON implements json.Marshaler.
|
|
func (ls Labels) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(ls.Map())
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
|
func (ls *Labels) UnmarshalJSON(b []byte) error {
|
|
var m map[string]string
|
|
|
|
if err := json.Unmarshal(b, &m); err != nil {
|
|
return err
|
|
}
|
|
|
|
*ls = FromMap(m)
|
|
return nil
|
|
}
|
|
|
|
// MarshalYAML implements yaml.Marshaler.
|
|
func (ls Labels) MarshalYAML() (any, error) {
|
|
return ls.Map(), nil
|
|
}
|
|
|
|
// UnmarshalYAML implements yaml.Unmarshaler.
|
|
func (ls *Labels) UnmarshalYAML(unmarshal func(any) error) error {
|
|
var m map[string]string
|
|
|
|
if err := unmarshal(&m); err != nil {
|
|
return err
|
|
}
|
|
|
|
*ls = FromMap(m)
|
|
return nil
|
|
}
|
|
|
|
// IsValid checks if the metric name or label names are valid.
|
|
func (ls Labels) IsValid(validationScheme model.ValidationScheme) bool {
|
|
err := ls.Validate(func(l Label) error {
|
|
if l.Name == model.MetricNameLabel {
|
|
// If the default validation scheme has been overridden with legacy mode,
|
|
// we need to call the special legacy validation checker.
|
|
if !validationScheme.IsValidMetricName(l.Value) {
|
|
return strconv.ErrSyntax
|
|
}
|
|
}
|
|
if !validationScheme.IsValidLabelName(l.Name) || !model.LabelValue(l.Value).IsValid() {
|
|
return strconv.ErrSyntax
|
|
}
|
|
return nil
|
|
})
|
|
return err == nil
|
|
}
|
|
|
|
// Map returns a string map of the labels.
|
|
func (ls Labels) Map() map[string]string {
|
|
m := make(map[string]string)
|
|
ls.Range(func(l Label) {
|
|
m[l.Name] = l.Value
|
|
})
|
|
return m
|
|
}
|
|
|
|
// FromMap returns new sorted Labels from the given map.
|
|
func FromMap(m map[string]string) Labels {
|
|
l := make([]Label, 0, len(m))
|
|
for k, v := range m {
|
|
l = append(l, Label{Name: k, Value: v})
|
|
}
|
|
return New(l...)
|
|
}
|
|
|
|
// NewBuilder returns a new LabelsBuilder.
|
|
func NewBuilder(base Labels) *Builder {
|
|
b := &Builder{
|
|
del: make([]string, 0, 5),
|
|
add: make([]Label, 0, 5),
|
|
}
|
|
b.Reset(base)
|
|
return b
|
|
}
|
|
|
|
// Del deletes the label of the given name.
|
|
func (b *Builder) Del(ns ...string) *Builder {
|
|
for _, n := range ns {
|
|
for i, a := range b.add {
|
|
if a.Name == n {
|
|
b.add = append(b.add[:i], b.add[i+1:]...)
|
|
}
|
|
}
|
|
b.del = append(b.del, n)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Keep removes all labels from the base except those with the given names.
|
|
func (b *Builder) Keep(ns ...string) *Builder {
|
|
b.base.Range(func(l Label) {
|
|
if slices.Contains(ns, l.Name) {
|
|
return
|
|
}
|
|
b.del = append(b.del, l.Name)
|
|
})
|
|
return b
|
|
}
|
|
|
|
// Set the name/value pair as a label. A value of "" means delete that label.
|
|
func (b *Builder) Set(n, v string) *Builder {
|
|
if v == "" {
|
|
// Empty labels are the same as missing labels.
|
|
return b.Del(n)
|
|
}
|
|
for i, a := range b.add {
|
|
if a.Name == n {
|
|
b.add[i].Value = v
|
|
return b
|
|
}
|
|
}
|
|
b.add = append(b.add, Label{Name: n, Value: v})
|
|
|
|
return b
|
|
}
|
|
|
|
func (b *Builder) Get(n string) string {
|
|
// Del() removes entries from .add but Set() does not remove from .del, so check .add first.
|
|
for _, a := range b.add {
|
|
if a.Name == n {
|
|
return a.Value
|
|
}
|
|
}
|
|
if slices.Contains(b.del, n) {
|
|
return ""
|
|
}
|
|
return b.base.Get(n)
|
|
}
|
|
|
|
// Range calls f on each label in the Builder.
|
|
func (b *Builder) Range(f func(l Label)) {
|
|
// Stack-based arrays to avoid heap allocation in most cases.
|
|
var addStack [128]Label
|
|
var delStack [128]string
|
|
// Take a copy of add and del, so they are unaffected by calls to Set() or Del().
|
|
origAdd, origDel := append(addStack[:0], b.add...), append(delStack[:0], b.del...)
|
|
b.base.Range(func(l Label) {
|
|
if !slices.Contains(origDel, l.Name) && !contains(origAdd, l.Name) {
|
|
f(l)
|
|
}
|
|
})
|
|
for _, a := range origAdd {
|
|
f(a)
|
|
}
|
|
}
|
|
|
|
func contains(s []Label, n string) bool {
|
|
for _, a := range s {
|
|
if a.Name == n {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func yoloString(b []byte) string {
|
|
return unsafe.String(unsafe.SliceData(b), len(b))
|
|
}
|