2017-04-19 12:43:09 +00:00
|
|
|
// 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.
|
|
|
|
|
2023-11-23 19:14:24 +00:00
|
|
|
//go:build !stringlabels && !dedupelabels
|
2022-12-20 17:38:57 +00:00
|
|
|
|
2016-12-24 13:01:10 +00:00
|
|
|
package labels
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2024-01-15 16:24:46 +00:00
|
|
|
"slices"
|
2023-09-21 20:53:51 +00:00
|
|
|
"strings"
|
2016-12-24 13:01:10 +00:00
|
|
|
|
2020-10-15 10:31:28 +00:00
|
|
|
"github.com/cespare/xxhash/v2"
|
2016-12-24 13:01:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Labels is a sorted set of labels. Order has to be guaranteed upon
|
|
|
|
// instantiation.
|
|
|
|
type Labels []Label
|
|
|
|
|
|
|
|
func (ls Labels) Len() int { return len(ls) }
|
|
|
|
func (ls Labels) Swap(i, j int) { ls[i], ls[j] = ls[j], ls[i] }
|
|
|
|
func (ls Labels) Less(i, j int) bool { return ls[i].Name < ls[j].Name }
|
|
|
|
|
2020-05-12 21:03:15 +00:00
|
|
|
// Bytes returns ls as a byte slice.
|
|
|
|
// It uses an byte invalid character as a separator and so should not be used for printing.
|
|
|
|
func (ls Labels) Bytes(buf []byte) []byte {
|
|
|
|
b := bytes.NewBuffer(buf[:0])
|
|
|
|
b.WriteByte(labelSep)
|
|
|
|
for i, l := range ls {
|
|
|
|
if i > 0 {
|
2024-07-15 08:47:16 +00:00
|
|
|
b.WriteByte(sep)
|
2020-05-12 21:03:15 +00:00
|
|
|
}
|
|
|
|
b.WriteString(l.Name)
|
2024-07-15 08:47:16 +00:00
|
|
|
b.WriteByte(sep)
|
2020-05-12 21:03:15 +00:00
|
|
|
b.WriteString(l.Value)
|
|
|
|
}
|
|
|
|
return b.Bytes()
|
|
|
|
}
|
|
|
|
|
2019-02-09 09:17:52 +00:00
|
|
|
// MatchLabels returns a subset of Labels that matches/does not match with the provided label names based on the 'on' boolean.
|
|
|
|
// If on is set to true, it returns the subset of labels that match with the provided label names and its inverse when 'on' is set to false.
|
|
|
|
func (ls Labels) MatchLabels(on bool, names ...string) Labels {
|
|
|
|
matchedLabels := Labels{}
|
|
|
|
|
2022-05-20 08:18:31 +00:00
|
|
|
nameSet := make(map[string]struct{}, len(names))
|
2019-02-09 09:17:52 +00:00
|
|
|
for _, n := range names {
|
|
|
|
nameSet[n] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range ls {
|
2019-12-27 09:32:19 +00:00
|
|
|
if _, ok := nameSet[v.Name]; on == ok && (on || v.Name != MetricName) {
|
2019-02-09 09:17:52 +00:00
|
|
|
matchedLabels = append(matchedLabels, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return matchedLabels
|
|
|
|
}
|
|
|
|
|
2016-12-24 13:01:10 +00:00
|
|
|
// Hash returns a hash value for the label set.
|
2022-11-28 16:16:55 +00:00
|
|
|
// Note: the result is not guaranteed to be consistent across different runs of Prometheus.
|
2016-12-24 13:01:10 +00:00
|
|
|
func (ls Labels) Hash() uint64 {
|
2020-10-15 10:31:28 +00:00
|
|
|
// Use xxhash.Sum64(b) for fast path as it's faster.
|
2016-12-24 13:01:10 +00:00
|
|
|
b := make([]byte, 0, 1024)
|
2020-10-15 10:31:28 +00:00
|
|
|
for i, v := range ls {
|
|
|
|
if len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) {
|
|
|
|
// If labels entry is 1KB+ do not allocate whole entry.
|
|
|
|
h := xxhash.New()
|
|
|
|
_, _ = h.Write(b)
|
|
|
|
for _, v := range ls[i:] {
|
|
|
|
_, _ = h.WriteString(v.Name)
|
|
|
|
_, _ = h.Write(seps)
|
|
|
|
_, _ = h.WriteString(v.Value)
|
|
|
|
_, _ = h.Write(seps)
|
|
|
|
}
|
|
|
|
return h.Sum64()
|
|
|
|
}
|
2016-12-24 13:01:10 +00:00
|
|
|
|
|
|
|
b = append(b, v.Name...)
|
2024-07-15 08:47:16 +00:00
|
|
|
b = append(b, sep)
|
2016-12-24 13:01:10 +00:00
|
|
|
b = append(b, v.Value...)
|
2024-07-15 08:47:16 +00:00
|
|
|
b = append(b, sep)
|
2016-12-24 13:01:10 +00:00
|
|
|
}
|
|
|
|
return xxhash.Sum64(b)
|
|
|
|
}
|
|
|
|
|
2018-07-18 03:56:27 +00:00
|
|
|
// HashForLabels returns a hash value for the labels matching the provided names.
|
2019-06-28 12:52:51 +00:00
|
|
|
// 'names' have to be sorted in ascending order.
|
|
|
|
func (ls Labels) HashForLabels(b []byte, names ...string) (uint64, []byte) {
|
|
|
|
b = b[:0]
|
|
|
|
i, j := 0, 0
|
|
|
|
for i < len(ls) && j < len(names) {
|
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 14:14:31 +00:00
|
|
|
switch {
|
|
|
|
case names[j] < ls[i].Name:
|
2019-06-28 12:52:51 +00:00
|
|
|
j++
|
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 14:14:31 +00:00
|
|
|
case ls[i].Name < names[j]:
|
2019-06-28 12:52:51 +00:00
|
|
|
i++
|
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 14:14:31 +00:00
|
|
|
default:
|
2019-06-28 12:52:51 +00:00
|
|
|
b = append(b, ls[i].Name...)
|
2024-07-15 08:47:16 +00:00
|
|
|
b = append(b, sep)
|
2019-06-28 12:52:51 +00:00
|
|
|
b = append(b, ls[i].Value...)
|
2024-07-15 08:47:16 +00:00
|
|
|
b = append(b, sep)
|
2019-06-28 12:52:51 +00:00
|
|
|
i++
|
|
|
|
j++
|
2018-07-18 03:56:27 +00:00
|
|
|
}
|
|
|
|
}
|
2019-06-28 12:52:51 +00:00
|
|
|
return xxhash.Sum64(b), b
|
2018-07-18 03:56:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// HashWithoutLabels returns a hash value for all labels except those matching
|
|
|
|
// the provided names.
|
2019-06-28 12:52:51 +00:00
|
|
|
// 'names' have to be sorted in ascending order.
|
|
|
|
func (ls Labels) HashWithoutLabels(b []byte, names ...string) (uint64, []byte) {
|
|
|
|
b = b[:0]
|
|
|
|
j := 0
|
|
|
|
for i := range ls {
|
|
|
|
for j < len(names) && names[j] < ls[i].Name {
|
|
|
|
j++
|
2018-07-18 03:56:27 +00:00
|
|
|
}
|
2019-06-28 12:52:51 +00:00
|
|
|
if ls[i].Name == MetricName || (j < len(names) && ls[i].Name == names[j]) {
|
|
|
|
continue
|
2018-07-18 03:56:27 +00:00
|
|
|
}
|
2019-06-28 12:52:51 +00:00
|
|
|
b = append(b, ls[i].Name...)
|
2024-07-15 08:47:16 +00:00
|
|
|
b = append(b, sep)
|
2019-06-28 12:52:51 +00:00
|
|
|
b = append(b, ls[i].Value...)
|
2024-07-15 08:47:16 +00:00
|
|
|
b = append(b, sep)
|
2018-07-18 03:56:27 +00:00
|
|
|
}
|
2019-06-28 12:52:51 +00:00
|
|
|
return xxhash.Sum64(b), b
|
2018-07-18 03:56:27 +00:00
|
|
|
}
|
|
|
|
|
2022-06-07 04:38:27 +00:00
|
|
|
// BytesWithLabels is just as Bytes(), but only for labels matching names.
|
2020-05-12 21:03:15 +00:00
|
|
|
// 'names' have to be sorted in ascending order.
|
2022-06-07 04:38:27 +00:00
|
|
|
func (ls Labels) BytesWithLabels(buf []byte, names ...string) []byte {
|
|
|
|
b := bytes.NewBuffer(buf[:0])
|
|
|
|
b.WriteByte(labelSep)
|
2020-05-12 21:03:15 +00:00
|
|
|
i, j := 0, 0
|
|
|
|
for i < len(ls) && j < len(names) {
|
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 14:14:31 +00:00
|
|
|
switch {
|
|
|
|
case names[j] < ls[i].Name:
|
2020-05-12 21:03:15 +00:00
|
|
|
j++
|
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 14:14:31 +00:00
|
|
|
case ls[i].Name < names[j]:
|
2020-05-12 21:03:15 +00:00
|
|
|
i++
|
style: Replace `else if` cascades with `switch`
Wiser coders than myself have come to the conclusion that a `switch`
statement is almost always superior to a statement that includes any
`else if`.
The exceptions that I have found in our codebase are just these two:
* The `if else` is followed by an additional statement before the next
condition (separated by a `;`).
* The whole thing is within a `for` loop and `break` statements are
used. In this case, using `switch` would require tagging the `for`
loop, which probably tips the balance.
Why are `switch` statements more readable?
For one, fewer curly braces. But more importantly, the conditions all
have the same alignment, so the whole thing follows the natural flow
of going down a list of conditions. With `else if`, in contrast, all
conditions but the first are "hidden" behind `} else if `, harder to
spot and (for no good reason) presented differently from the first
condition.
I'm sure the aforemention wise coders can list even more reasons.
In any case, I like it so much that I have found myself recommending
it in code reviews. I would like to make it a habit in our code base,
without making it a hard requirement that we would test on the CI. But
for that, there has to be a role model, so this commit eliminates all
`if else` occurrences, unless it is autogenerated code or fits one of
the exceptions above.
Signed-off-by: beorn7 <beorn@grafana.com>
2023-04-12 14:14:31 +00:00
|
|
|
default:
|
2022-06-07 04:38:27 +00:00
|
|
|
if b.Len() > 1 {
|
2024-07-15 08:47:16 +00:00
|
|
|
b.WriteByte(sep)
|
2022-06-07 04:38:27 +00:00
|
|
|
}
|
|
|
|
b.WriteString(ls[i].Name)
|
2024-07-15 08:47:16 +00:00
|
|
|
b.WriteByte(sep)
|
2022-06-07 04:38:27 +00:00
|
|
|
b.WriteString(ls[i].Value)
|
2020-05-12 21:03:15 +00:00
|
|
|
i++
|
|
|
|
j++
|
|
|
|
}
|
|
|
|
}
|
2022-06-07 04:38:27 +00:00
|
|
|
return b.Bytes()
|
2020-05-12 21:03:15 +00:00
|
|
|
}
|
|
|
|
|
2022-06-07 04:38:27 +00:00
|
|
|
// BytesWithoutLabels is just as Bytes(), but only for labels not matching names.
|
2020-05-12 21:03:15 +00:00
|
|
|
// 'names' have to be sorted in ascending order.
|
2022-06-07 04:38:27 +00:00
|
|
|
func (ls Labels) BytesWithoutLabels(buf []byte, names ...string) []byte {
|
|
|
|
b := bytes.NewBuffer(buf[:0])
|
|
|
|
b.WriteByte(labelSep)
|
2020-05-12 21:03:15 +00:00
|
|
|
j := 0
|
|
|
|
for i := range ls {
|
|
|
|
for j < len(names) && names[j] < ls[i].Name {
|
|
|
|
j++
|
|
|
|
}
|
2022-06-07 04:38:27 +00:00
|
|
|
if j < len(names) && ls[i].Name == names[j] {
|
2020-05-12 21:03:15 +00:00
|
|
|
continue
|
|
|
|
}
|
2022-06-07 04:38:27 +00:00
|
|
|
if b.Len() > 1 {
|
2024-07-15 08:47:16 +00:00
|
|
|
b.WriteByte(sep)
|
2022-06-07 04:38:27 +00:00
|
|
|
}
|
|
|
|
b.WriteString(ls[i].Name)
|
2024-07-15 08:47:16 +00:00
|
|
|
b.WriteByte(sep)
|
2022-06-07 04:38:27 +00:00
|
|
|
b.WriteString(ls[i].Value)
|
2020-05-12 21:03:15 +00:00
|
|
|
}
|
2022-06-07 04:38:27 +00:00
|
|
|
return b.Bytes()
|
2020-05-12 21:03:15 +00:00
|
|
|
}
|
|
|
|
|
2016-12-24 23:37:46 +00:00
|
|
|
// Copy returns a copy of the labels.
|
|
|
|
func (ls Labels) Copy() Labels {
|
|
|
|
res := make(Labels, len(ls))
|
|
|
|
copy(res, ls)
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2016-12-24 13:01:10 +00:00
|
|
|
// Get returns the value for the label with the given name.
|
|
|
|
// Returns an empty string if the label doesn't exist.
|
|
|
|
func (ls Labels) Get(name string) string {
|
|
|
|
for _, l := range ls {
|
|
|
|
if l.Name == name {
|
|
|
|
return l.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2018-02-15 14:26:24 +00:00
|
|
|
// Has returns true if the label with the given name is present.
|
2018-02-14 17:03:58 +00:00
|
|
|
func (ls Labels) Has(name string) bool {
|
|
|
|
for _, l := range ls {
|
|
|
|
if l.Name == name {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-02-19 06:25:44 +00:00
|
|
|
// HasDuplicateLabelNames returns whether ls has duplicate label names.
|
2020-01-20 11:05:27 +00:00
|
|
|
// It assumes that the labelset is sorted.
|
|
|
|
func (ls Labels) HasDuplicateLabelNames() (string, bool) {
|
|
|
|
for i, l := range ls {
|
|
|
|
if i == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if l.Name == ls[i-1].Name {
|
|
|
|
return l.Name, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
2019-11-18 19:53:33 +00:00
|
|
|
// WithoutEmpty returns the labelset without empty labels.
|
|
|
|
// May return the same labelset.
|
|
|
|
func (ls Labels) WithoutEmpty() Labels {
|
|
|
|
for _, v := range ls {
|
|
|
|
if v.Value != "" {
|
|
|
|
continue
|
|
|
|
}
|
2020-10-13 07:57:53 +00:00
|
|
|
// Do not copy the slice until it's necessary.
|
2019-11-18 19:53:33 +00:00
|
|
|
els := make(Labels, 0, len(ls)-1)
|
|
|
|
for _, v := range ls {
|
|
|
|
if v.Value != "" {
|
|
|
|
els = append(els, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return els
|
|
|
|
}
|
|
|
|
return ls
|
|
|
|
}
|
|
|
|
|
2016-12-24 23:37:46 +00:00
|
|
|
// Equal returns whether the two label sets are equal.
|
|
|
|
func Equal(ls, o Labels) bool {
|
2016-12-24 13:01:10 +00:00
|
|
|
if len(ls) != len(o) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for i, l := range ls {
|
2022-03-14 23:30:04 +00:00
|
|
|
if l != o[i] {
|
2016-12-24 13:01:10 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-05-26 14:58:06 +00:00
|
|
|
// EmptyLabels returns n empty Labels value, for convenience.
|
|
|
|
func EmptyLabels() Labels {
|
|
|
|
return Labels{}
|
|
|
|
}
|
|
|
|
|
2016-12-24 13:01:10 +00:00
|
|
|
// New returns a sorted Labels from the given labels.
|
|
|
|
// The caller has to guarantee that all label names are unique.
|
|
|
|
func New(ls ...Label) Labels {
|
|
|
|
set := make(Labels, 0, len(ls))
|
2022-12-13 18:14:58 +00:00
|
|
|
set = append(set, ls...)
|
2023-09-21 20:53:51 +00:00
|
|
|
slices.SortFunc(set, func(a, b Label) int { return strings.Compare(a.Name, b.Name) })
|
2016-12-24 13:01:10 +00:00
|
|
|
|
|
|
|
return set
|
|
|
|
}
|
|
|
|
|
|
|
|
// FromStrings creates new labels from pairs of strings.
|
|
|
|
func FromStrings(ss ...string) Labels {
|
|
|
|
if len(ss)%2 != 0 {
|
|
|
|
panic("invalid number of strings")
|
|
|
|
}
|
2022-05-25 14:22:47 +00:00
|
|
|
res := make(Labels, 0, len(ss)/2)
|
2016-12-24 13:01:10 +00:00
|
|
|
for i := 0; i < len(ss); i += 2 {
|
|
|
|
res = append(res, Label{Name: ss[i], Value: ss[i+1]})
|
|
|
|
}
|
|
|
|
|
2023-09-21 20:53:51 +00:00
|
|
|
slices.SortFunc(res, func(a, b Label) int { return strings.Compare(a.Name, b.Name) })
|
2016-12-24 13:01:10 +00:00
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare compares the two label sets.
|
|
|
|
// The result will be 0 if a==b, <0 if a < b, and >0 if a > b.
|
|
|
|
func Compare(a, b Labels) int {
|
|
|
|
l := len(a)
|
|
|
|
if len(b) < l {
|
|
|
|
l = len(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < l; i++ {
|
2020-01-01 15:45:01 +00:00
|
|
|
if a[i].Name != b[i].Name {
|
|
|
|
if a[i].Name < b[i].Name {
|
|
|
|
return -1
|
|
|
|
}
|
2020-03-23 14:47:11 +00:00
|
|
|
return 1
|
2016-12-24 13:01:10 +00:00
|
|
|
}
|
2020-01-01 15:45:01 +00:00
|
|
|
if a[i].Value != b[i].Value {
|
|
|
|
if a[i].Value < b[i].Value {
|
|
|
|
return -1
|
|
|
|
}
|
2020-03-23 14:47:11 +00:00
|
|
|
return 1
|
2016-12-24 13:01:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// If all labels so far were in common, the set with fewer labels comes first.
|
|
|
|
return len(a) - len(b)
|
|
|
|
}
|
|
|
|
|
lint: Revamp our linting rules, mostly around doc comments
Several things done here:
- Set `max-issues-per-linter` to 0 so that we actually see all linter
warnings and not just 50 per linter. (As we also set
`max-same-issues` to 0, I assume this was the intention from the
beginning.)
- Stop using the golangci-lint default excludes (by setting
`exclude-use-default: false`. Those are too generous and don't match
our style conventions. (I have re-added some of the excludes
explicitly in this commit. See below.)
- Re-add the `errcheck` exclusion we have used so far via the
defaults.
- Exclude the signature requirement `govet` has for `Seek` methods
because we use non-standard `Seek` methods a lot. (But we keep other
requirements, while the default excludes completely disabled the
check for common method segnatures.)
- Exclude warnings about missing doc comments on exported symbols. (We
used to be pretty adamant about doc comments, but stopped that at
some point in the past. By now, we have about 500 missing doc
comments. We may consider reintroducing this check, but that's
outside of the scope of this commit. The default excludes of
golangci-lint essentially ignore doc comments completely.)
- By stop using the default excludes, we now get warnings back on
malformed doc comments. That's the most impactful change in this
commit. It does not enforce doc comments (again), but _if_ there is
a doc comment, it has to have the recommended form. (Most of the
changes in this commit are fixing this form.)
- Improve wording/spelling of some comments in .golangci.yml, and
remove an outdated comment.
- Leave `package-comments` inactive, but add a TODO asking if we
should change that.
- Add a new sub-linter `comment-spacings` (and fix corresponding
comments), which avoids missing spaces after the leading `//`.
Signed-off-by: beorn7 <beorn@grafana.com>
2024-08-22 11:59:36 +00:00
|
|
|
// CopyFrom copies labels from b on top of whatever was in ls previously,
|
|
|
|
// reusing memory or expanding if needed.
|
2022-05-26 14:59:13 +00:00
|
|
|
func (ls *Labels) CopyFrom(b Labels) {
|
|
|
|
(*ls) = append((*ls)[:0], b...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsEmpty returns true if ls represents an empty set of labels.
|
|
|
|
func (ls Labels) IsEmpty() bool {
|
|
|
|
return len(ls) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Range calls f on each label.
|
|
|
|
func (ls Labels) Range(f func(l Label)) {
|
|
|
|
for _, l := range ls {
|
|
|
|
f(l)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate calls f on each label. If f returns a non-nil error, then it returns that error cancelling the iteration.
|
|
|
|
func (ls Labels) Validate(f func(l Label) error) error {
|
|
|
|
for _, l := range ls {
|
|
|
|
if err := f(l); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-25 10:48:49 +00:00
|
|
|
// DropMetricName returns Labels with "__name__" removed.
|
|
|
|
func (ls Labels) DropMetricName() Labels {
|
|
|
|
for i, l := range ls {
|
|
|
|
if l.Name == MetricName {
|
|
|
|
if i == 0 { // Make common case fast with no allocations.
|
|
|
|
return ls[1:]
|
|
|
|
}
|
2024-03-27 10:35:17 +00:00
|
|
|
// Avoid modifying original Labels - use [:i:i] so that left slice would not
|
|
|
|
// have any spare capacity and append would have to allocate a new slice for the result.
|
|
|
|
return append(ls[:i:i], ls[i+1:]...)
|
2024-01-25 10:48:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ls
|
|
|
|
}
|
|
|
|
|
2022-05-26 14:59:13 +00:00
|
|
|
// InternStrings calls intern on every string value inside ls, replacing them with what it returns.
|
|
|
|
func (ls *Labels) InternStrings(intern func(string) string) {
|
|
|
|
for i, l := range *ls {
|
|
|
|
(*ls)[i].Name = intern(l.Name)
|
|
|
|
(*ls)[i].Value = intern(l.Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReleaseStrings calls release on every string value inside ls.
|
|
|
|
func (ls Labels) ReleaseStrings(release func(string)) {
|
|
|
|
for _, l := range ls {
|
|
|
|
release(l.Name)
|
|
|
|
release(l.Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-24 12:17:28 +00:00
|
|
|
// Builder allows modifying Labels.
|
|
|
|
type Builder struct {
|
|
|
|
base Labels
|
|
|
|
del []string
|
|
|
|
add []Label
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset clears all current state for the builder.
|
|
|
|
func (b *Builder) Reset(base Labels) {
|
|
|
|
b.base = base
|
|
|
|
b.del = b.del[:0]
|
|
|
|
b.add = b.add[:0]
|
|
|
|
b.base.Range(func(l Label) {
|
|
|
|
if l.Value == "" {
|
|
|
|
b.del = append(b.del, l.Name)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-03-22 15:46:02 +00:00
|
|
|
// Labels returns the labels from the builder.
|
2022-08-19 09:57:52 +00:00
|
|
|
// If no modifications were made, the original labels are returned.
|
2023-03-22 15:46:02 +00:00
|
|
|
func (b *Builder) Labels() Labels {
|
2016-12-24 13:35:24 +00:00
|
|
|
if len(b.del) == 0 && len(b.add) == 0 {
|
|
|
|
return b.base
|
|
|
|
}
|
|
|
|
|
2023-04-02 10:17:05 +00:00
|
|
|
expectedSize := len(b.base) + len(b.add) - len(b.del)
|
|
|
|
if expectedSize < 1 {
|
|
|
|
expectedSize = 1
|
2022-08-19 09:57:52 +00:00
|
|
|
}
|
2023-04-02 10:17:05 +00:00
|
|
|
res := make(Labels, 0, expectedSize)
|
2016-12-24 13:35:24 +00:00
|
|
|
for _, l := range b.base {
|
2023-02-28 18:10:21 +00:00
|
|
|
if slices.Contains(b.del, l.Name) || contains(b.add, l.Name) {
|
|
|
|
continue
|
2016-12-24 13:35:24 +00:00
|
|
|
}
|
|
|
|
res = append(res, l)
|
|
|
|
}
|
2022-06-07 04:38:27 +00:00
|
|
|
if len(b.add) > 0 { // Base is already in order, so we only need to sort if we add to it.
|
|
|
|
res = append(res, b.add...)
|
2023-09-21 20:53:51 +00:00
|
|
|
slices.SortFunc(res, func(a, b Label) int { return strings.Compare(a.Name, b.Name) })
|
2022-06-07 04:38:27 +00:00
|
|
|
}
|
2016-12-24 13:35:24 +00:00
|
|
|
return res
|
2016-12-24 13:01:10 +00:00
|
|
|
}
|
2022-05-26 14:59:13 +00:00
|
|
|
|
|
|
|
// ScratchBuilder allows efficient construction of a Labels from scratch.
|
|
|
|
type ScratchBuilder struct {
|
|
|
|
add Labels
|
|
|
|
}
|
|
|
|
|
lint: Revamp our linting rules, mostly around doc comments
Several things done here:
- Set `max-issues-per-linter` to 0 so that we actually see all linter
warnings and not just 50 per linter. (As we also set
`max-same-issues` to 0, I assume this was the intention from the
beginning.)
- Stop using the golangci-lint default excludes (by setting
`exclude-use-default: false`. Those are too generous and don't match
our style conventions. (I have re-added some of the excludes
explicitly in this commit. See below.)
- Re-add the `errcheck` exclusion we have used so far via the
defaults.
- Exclude the signature requirement `govet` has for `Seek` methods
because we use non-standard `Seek` methods a lot. (But we keep other
requirements, while the default excludes completely disabled the
check for common method segnatures.)
- Exclude warnings about missing doc comments on exported symbols. (We
used to be pretty adamant about doc comments, but stopped that at
some point in the past. By now, we have about 500 missing doc
comments. We may consider reintroducing this check, but that's
outside of the scope of this commit. The default excludes of
golangci-lint essentially ignore doc comments completely.)
- By stop using the default excludes, we now get warnings back on
malformed doc comments. That's the most impactful change in this
commit. It does not enforce doc comments (again), but _if_ there is
a doc comment, it has to have the recommended form. (Most of the
changes in this commit are fixing this form.)
- Improve wording/spelling of some comments in .golangci.yml, and
remove an outdated comment.
- Leave `package-comments` inactive, but add a TODO asking if we
should change that.
- Add a new sub-linter `comment-spacings` (and fix corresponding
comments), which avoids missing spaces after the leading `//`.
Signed-off-by: beorn7 <beorn@grafana.com>
2024-08-22 11:59:36 +00:00
|
|
|
// SymbolTable is no-op, just for api parity with dedupelabels.
|
2023-11-23 18:17:45 +00:00
|
|
|
type SymbolTable struct{}
|
|
|
|
|
|
|
|
func NewSymbolTable() *SymbolTable { return nil }
|
|
|
|
|
|
|
|
func (t *SymbolTable) Len() int { return 0 }
|
|
|
|
|
2022-05-26 14:59:13 +00:00
|
|
|
// NewScratchBuilder creates a ScratchBuilder initialized for Labels with n entries.
|
|
|
|
func NewScratchBuilder(n int) ScratchBuilder {
|
|
|
|
return ScratchBuilder{add: make([]Label, 0, n)}
|
|
|
|
}
|
|
|
|
|
2023-11-23 18:17:45 +00:00
|
|
|
// NewBuilderWithSymbolTable creates a Builder, for api parity with dedupelabels.
|
|
|
|
func NewBuilderWithSymbolTable(_ *SymbolTable) *Builder {
|
|
|
|
return NewBuilder(EmptyLabels())
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewScratchBuilderWithSymbolTable creates a ScratchBuilder, for api parity with dedupelabels.
|
|
|
|
func NewScratchBuilderWithSymbolTable(_ *SymbolTable, n int) ScratchBuilder {
|
|
|
|
return NewScratchBuilder(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *ScratchBuilder) SetSymbolTable(_ *SymbolTable) {
|
|
|
|
// no-op
|
|
|
|
}
|
|
|
|
|
2022-05-26 14:59:13 +00:00
|
|
|
func (b *ScratchBuilder) Reset() {
|
|
|
|
b.add = b.add[:0]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a name/value pair.
|
|
|
|
// Note if you Add the same name twice you will get a duplicate label, which is invalid.
|
|
|
|
func (b *ScratchBuilder) Add(name, value string) {
|
|
|
|
b.add = append(b.add, Label{Name: name, Value: value})
|
|
|
|
}
|
|
|
|
|
lint: Revamp our linting rules, mostly around doc comments
Several things done here:
- Set `max-issues-per-linter` to 0 so that we actually see all linter
warnings and not just 50 per linter. (As we also set
`max-same-issues` to 0, I assume this was the intention from the
beginning.)
- Stop using the golangci-lint default excludes (by setting
`exclude-use-default: false`. Those are too generous and don't match
our style conventions. (I have re-added some of the excludes
explicitly in this commit. See below.)
- Re-add the `errcheck` exclusion we have used so far via the
defaults.
- Exclude the signature requirement `govet` has for `Seek` methods
because we use non-standard `Seek` methods a lot. (But we keep other
requirements, while the default excludes completely disabled the
check for common method segnatures.)
- Exclude warnings about missing doc comments on exported symbols. (We
used to be pretty adamant about doc comments, but stopped that at
some point in the past. By now, we have about 500 missing doc
comments. We may consider reintroducing this check, but that's
outside of the scope of this commit. The default excludes of
golangci-lint essentially ignore doc comments completely.)
- By stop using the default excludes, we now get warnings back on
malformed doc comments. That's the most impactful change in this
commit. It does not enforce doc comments (again), but _if_ there is
a doc comment, it has to have the recommended form. (Most of the
changes in this commit are fixing this form.)
- Improve wording/spelling of some comments in .golangci.yml, and
remove an outdated comment.
- Leave `package-comments` inactive, but add a TODO asking if we
should change that.
- Add a new sub-linter `comment-spacings` (and fix corresponding
comments), which avoids missing spaces after the leading `//`.
Signed-off-by: beorn7 <beorn@grafana.com>
2024-08-22 11:59:36 +00:00
|
|
|
// UnsafeAddBytes adds a name/value pair, using []byte instead of string.
|
2023-11-14 11:36:35 +00:00
|
|
|
// The '-tags stringlabels' version of this function is unsafe, hence the name.
|
|
|
|
// This version is safe - it copies the strings immediately - but we keep the same name so everything compiles.
|
|
|
|
func (b *ScratchBuilder) UnsafeAddBytes(name, value []byte) {
|
|
|
|
b.add = append(b.add, Label{Name: string(name), Value: string(value)})
|
|
|
|
}
|
|
|
|
|
2022-05-26 14:59:13 +00:00
|
|
|
// Sort the labels added so far by name.
|
|
|
|
func (b *ScratchBuilder) Sort() {
|
2023-09-21 20:53:51 +00:00
|
|
|
slices.SortFunc(b.add, func(a, b Label) int { return strings.Compare(a.Name, b.Name) })
|
2022-05-26 14:59:13 +00:00
|
|
|
}
|
|
|
|
|
2023-04-25 06:19:16 +00:00
|
|
|
// Assign is for when you already have a Labels which you want this ScratchBuilder to return.
|
2022-12-15 18:19:15 +00:00
|
|
|
func (b *ScratchBuilder) Assign(ls Labels) {
|
|
|
|
b.add = append(b.add[:0], ls...) // Copy on top of our slice, so we don't retain the input slice.
|
|
|
|
}
|
|
|
|
|
lint: Revamp our linting rules, mostly around doc comments
Several things done here:
- Set `max-issues-per-linter` to 0 so that we actually see all linter
warnings and not just 50 per linter. (As we also set
`max-same-issues` to 0, I assume this was the intention from the
beginning.)
- Stop using the golangci-lint default excludes (by setting
`exclude-use-default: false`. Those are too generous and don't match
our style conventions. (I have re-added some of the excludes
explicitly in this commit. See below.)
- Re-add the `errcheck` exclusion we have used so far via the
defaults.
- Exclude the signature requirement `govet` has for `Seek` methods
because we use non-standard `Seek` methods a lot. (But we keep other
requirements, while the default excludes completely disabled the
check for common method segnatures.)
- Exclude warnings about missing doc comments on exported symbols. (We
used to be pretty adamant about doc comments, but stopped that at
some point in the past. By now, we have about 500 missing doc
comments. We may consider reintroducing this check, but that's
outside of the scope of this commit. The default excludes of
golangci-lint essentially ignore doc comments completely.)
- By stop using the default excludes, we now get warnings back on
malformed doc comments. That's the most impactful change in this
commit. It does not enforce doc comments (again), but _if_ there is
a doc comment, it has to have the recommended form. (Most of the
changes in this commit are fixing this form.)
- Improve wording/spelling of some comments in .golangci.yml, and
remove an outdated comment.
- Leave `package-comments` inactive, but add a TODO asking if we
should change that.
- Add a new sub-linter `comment-spacings` (and fix corresponding
comments), which avoids missing spaces after the leading `//`.
Signed-off-by: beorn7 <beorn@grafana.com>
2024-08-22 11:59:36 +00:00
|
|
|
// Labels returns the name/value pairs added so far as a Labels object.
|
2022-05-26 14:59:13 +00:00
|
|
|
// Note: if you want them sorted, call Sort() first.
|
|
|
|
func (b *ScratchBuilder) Labels() Labels {
|
|
|
|
// Copy the slice, so the next use of ScratchBuilder doesn't overwrite.
|
|
|
|
return append([]Label{}, b.add...)
|
|
|
|
}
|
2023-04-13 11:07:54 +00:00
|
|
|
|
lint: Revamp our linting rules, mostly around doc comments
Several things done here:
- Set `max-issues-per-linter` to 0 so that we actually see all linter
warnings and not just 50 per linter. (As we also set
`max-same-issues` to 0, I assume this was the intention from the
beginning.)
- Stop using the golangci-lint default excludes (by setting
`exclude-use-default: false`. Those are too generous and don't match
our style conventions. (I have re-added some of the excludes
explicitly in this commit. See below.)
- Re-add the `errcheck` exclusion we have used so far via the
defaults.
- Exclude the signature requirement `govet` has for `Seek` methods
because we use non-standard `Seek` methods a lot. (But we keep other
requirements, while the default excludes completely disabled the
check for common method segnatures.)
- Exclude warnings about missing doc comments on exported symbols. (We
used to be pretty adamant about doc comments, but stopped that at
some point in the past. By now, we have about 500 missing doc
comments. We may consider reintroducing this check, but that's
outside of the scope of this commit. The default excludes of
golangci-lint essentially ignore doc comments completely.)
- By stop using the default excludes, we now get warnings back on
malformed doc comments. That's the most impactful change in this
commit. It does not enforce doc comments (again), but _if_ there is
a doc comment, it has to have the recommended form. (Most of the
changes in this commit are fixing this form.)
- Improve wording/spelling of some comments in .golangci.yml, and
remove an outdated comment.
- Leave `package-comments` inactive, but add a TODO asking if we
should change that.
- Add a new sub-linter `comment-spacings` (and fix corresponding
comments), which avoids missing spaces after the leading `//`.
Signed-off-by: beorn7 <beorn@grafana.com>
2024-08-22 11:59:36 +00:00
|
|
|
// Overwrite the newly-built Labels out to ls.
|
2023-04-13 11:07:54 +00:00
|
|
|
// Callers must ensure that there are no other references to ls, or any strings fetched from it.
|
|
|
|
func (b *ScratchBuilder) Overwrite(ls *Labels) {
|
|
|
|
*ls = append((*ls)[:0], b.add...)
|
|
|
|
}
|