mirror of https://github.com/grafana/loki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
833 lines
20 KiB
833 lines
20 KiB
package log
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/prometheus/prometheus/model/labels"
|
|
|
|
"github.com/grafana/loki/v3/pkg/logqlmodel"
|
|
)
|
|
|
|
const MaxInternedStrings = 1024
|
|
|
|
var EmptyLabelsResult = NewLabelsResult(labels.EmptyLabels().String(), labels.EmptyLabels().Hash(), labels.EmptyLabels(), labels.EmptyLabels(), labels.EmptyLabels())
|
|
|
|
// LabelsResult is a computed labels result that contains the labels set with associated string and hash.
|
|
// The is mainly used for caching and returning labels computations out of pipelines and stages.
|
|
type LabelsResult interface {
|
|
String() string
|
|
Labels() labels.Labels
|
|
Stream() labels.Labels
|
|
StructuredMetadata() labels.Labels
|
|
Parsed() labels.Labels
|
|
Hash() uint64
|
|
}
|
|
|
|
// NewLabelsResult creates a new LabelsResult.
|
|
// It takes the string representation of the labels, the hash of the labels and the labels categorized.
|
|
func NewLabelsResult(allLabelsStr string, hash uint64, stream, structuredMetadata, parsed labels.Labels) LabelsResult {
|
|
return &labelsResult{
|
|
s: allLabelsStr,
|
|
h: hash,
|
|
stream: stream,
|
|
structuredMetadata: structuredMetadata,
|
|
parsed: parsed,
|
|
}
|
|
}
|
|
|
|
type labelsResult struct {
|
|
s string
|
|
h uint64
|
|
|
|
stream labels.Labels
|
|
structuredMetadata labels.Labels
|
|
parsed labels.Labels
|
|
}
|
|
|
|
func (l labelsResult) String() string {
|
|
return l.s
|
|
}
|
|
|
|
func (l labelsResult) Labels() labels.Labels {
|
|
return flattenLabels(nil, l.stream, l.structuredMetadata, l.parsed)
|
|
}
|
|
|
|
func (l labelsResult) Hash() uint64 {
|
|
return l.h
|
|
}
|
|
|
|
func (l labelsResult) Stream() labels.Labels {
|
|
if len(l.stream) == 0 {
|
|
return nil
|
|
}
|
|
return l.stream
|
|
}
|
|
|
|
func (l labelsResult) StructuredMetadata() labels.Labels {
|
|
if len(l.structuredMetadata) == 0 {
|
|
return nil
|
|
}
|
|
return l.structuredMetadata
|
|
}
|
|
|
|
func (l labelsResult) Parsed() labels.Labels {
|
|
if len(l.parsed) == 0 {
|
|
return nil
|
|
}
|
|
return l.parsed
|
|
}
|
|
|
|
type hasher struct {
|
|
buf []byte // buffer for computing hash without bytes slice allocation.
|
|
}
|
|
|
|
// newHasher allow to compute hashes for labels by reusing the same buffer.
|
|
func newHasher() *hasher {
|
|
return &hasher{
|
|
buf: make([]byte, 0, 1024),
|
|
}
|
|
}
|
|
|
|
// Hash hashes the labels
|
|
func (h *hasher) Hash(lbs labels.Labels) uint64 {
|
|
var hash uint64
|
|
hash, h.buf = lbs.HashWithoutLabels(h.buf, []string(nil)...)
|
|
return hash
|
|
}
|
|
|
|
type LabelCategory int
|
|
|
|
const (
|
|
StreamLabel LabelCategory = iota
|
|
StructuredMetadataLabel
|
|
ParsedLabel
|
|
InvalidCategory
|
|
|
|
numValidCategories = 3
|
|
)
|
|
|
|
var allCategories = []LabelCategory{
|
|
StreamLabel,
|
|
StructuredMetadataLabel,
|
|
ParsedLabel,
|
|
}
|
|
|
|
func categoriesContain(categories []LabelCategory, category LabelCategory) bool {
|
|
for _, c := range categories {
|
|
if c == category {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// BaseLabelsBuilder is a label builder used by pipeline and stages.
|
|
// Only one base builder is used and it contains cache for each LabelsBuilders.
|
|
type BaseLabelsBuilder struct {
|
|
del []string
|
|
add [numValidCategories]labels.Labels
|
|
// nolint:structcheck
|
|
// https://github.com/golangci/golangci-lint/issues/826
|
|
err string
|
|
// nolint:structcheck
|
|
errDetails string
|
|
|
|
groups []string
|
|
baseMap map[string]string
|
|
parserKeyHints ParserHint // label key hints for metric queries that allows to limit parser extractions to only this list of labels.
|
|
without, noLabels bool
|
|
referencedStructuredMetadata bool
|
|
jsonPaths map[string][]string // Maps label names to their original JSON paths
|
|
|
|
resultCache map[uint64]LabelsResult
|
|
*hasher
|
|
}
|
|
|
|
// LabelsBuilder is the same as labels.Builder but tailored for this package.
|
|
type LabelsBuilder struct {
|
|
base labels.Labels
|
|
buf labels.Labels
|
|
currentResult LabelsResult
|
|
groupedResult LabelsResult
|
|
|
|
*BaseLabelsBuilder
|
|
}
|
|
|
|
// NewBaseLabelsBuilderWithGrouping creates a new base labels builder with grouping to compute results.
|
|
func NewBaseLabelsBuilderWithGrouping(groups []string, parserKeyHints ParserHint, without, noLabels bool) *BaseLabelsBuilder {
|
|
if parserKeyHints == nil {
|
|
parserKeyHints = NoParserHints()
|
|
}
|
|
|
|
const labelsCapacity = 16
|
|
return &BaseLabelsBuilder{
|
|
del: make([]string, 0, 5),
|
|
add: [numValidCategories]labels.Labels{
|
|
StreamLabel: make(labels.Labels, 0, labelsCapacity),
|
|
StructuredMetadataLabel: make(labels.Labels, 0, labelsCapacity),
|
|
ParsedLabel: make(labels.Labels, 0, labelsCapacity),
|
|
},
|
|
resultCache: make(map[uint64]LabelsResult),
|
|
hasher: newHasher(),
|
|
groups: groups,
|
|
parserKeyHints: parserKeyHints,
|
|
noLabels: noLabels,
|
|
without: without,
|
|
jsonPaths: make(map[string][]string),
|
|
}
|
|
}
|
|
|
|
// NewBaseLabelsBuilder creates a new base labels builder.
|
|
func NewBaseLabelsBuilder() *BaseLabelsBuilder {
|
|
return NewBaseLabelsBuilderWithGrouping(nil, NoParserHints(), false, false)
|
|
}
|
|
|
|
// ForLabels creates a labels builder for a given labels set as base.
|
|
// The labels cache is shared across all created LabelsBuilders.
|
|
func (b *BaseLabelsBuilder) ForLabels(lbs labels.Labels, hash uint64) *LabelsBuilder {
|
|
if labelResult, ok := b.resultCache[hash]; ok {
|
|
res := &LabelsBuilder{
|
|
base: lbs,
|
|
currentResult: labelResult,
|
|
BaseLabelsBuilder: b,
|
|
}
|
|
return res
|
|
}
|
|
labelResult := NewLabelsResult(lbs.String(), hash, lbs, labels.EmptyLabels(), labels.EmptyLabels())
|
|
b.resultCache[hash] = labelResult
|
|
res := &LabelsBuilder{
|
|
base: lbs,
|
|
currentResult: labelResult,
|
|
BaseLabelsBuilder: b,
|
|
}
|
|
return res
|
|
}
|
|
|
|
// Reset clears all current state for the builder.
|
|
func (b *BaseLabelsBuilder) Reset() {
|
|
b.del = b.del[:0]
|
|
for k := range b.add {
|
|
b.add[k] = b.add[k][:0]
|
|
}
|
|
b.err = ""
|
|
b.errDetails = ""
|
|
b.baseMap = nil
|
|
b.parserKeyHints.Reset()
|
|
}
|
|
|
|
// ParserLabelHints returns a limited list of expected labels to extract for metric queries.
|
|
// Returns nil when it's impossible to hint labels extractions.
|
|
func (b *BaseLabelsBuilder) ParserLabelHints() ParserHint {
|
|
return b.parserKeyHints
|
|
}
|
|
|
|
func (b *BaseLabelsBuilder) hasDel() bool {
|
|
return len(b.del) > 0
|
|
}
|
|
|
|
func (b *BaseLabelsBuilder) hasAdd() bool {
|
|
for _, lbls := range b.add {
|
|
if len(lbls) > 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (b *BaseLabelsBuilder) sizeAdd() int {
|
|
var length int
|
|
for _, lbls := range b.add {
|
|
length += len(lbls)
|
|
}
|
|
return length
|
|
}
|
|
|
|
// SetErr sets the error label.
|
|
func (b *LabelsBuilder) SetErr(err string) *LabelsBuilder {
|
|
b.err = err
|
|
return b
|
|
}
|
|
|
|
// GetErr return the current error label value.
|
|
func (b *LabelsBuilder) GetErr() string {
|
|
return b.err
|
|
}
|
|
|
|
// HasErr tells if the error label has been set.
|
|
func (b *LabelsBuilder) HasErr() bool {
|
|
return b.err != ""
|
|
}
|
|
|
|
func (b *LabelsBuilder) SetErrorDetails(desc string) *LabelsBuilder {
|
|
b.errDetails = desc
|
|
return b
|
|
}
|
|
|
|
func (b *LabelsBuilder) ResetError() *LabelsBuilder {
|
|
b.err = ""
|
|
return b
|
|
}
|
|
|
|
func (b *LabelsBuilder) ResetErrorDetails() *LabelsBuilder {
|
|
b.errDetails = ""
|
|
return b
|
|
}
|
|
|
|
func (b *LabelsBuilder) GetErrorDetails() string {
|
|
return b.errDetails
|
|
}
|
|
|
|
func (b *LabelsBuilder) HasErrorDetails() bool {
|
|
return b.errDetails != ""
|
|
}
|
|
|
|
// BaseHas returns the base labels have the given key
|
|
func (b *LabelsBuilder) BaseHas(key string) bool {
|
|
return b.base.Has(key)
|
|
}
|
|
|
|
// GetWithCategory returns the value and the category of a labels key if it exists.
|
|
func (b *LabelsBuilder) GetWithCategory(key string) (string, LabelCategory, bool) {
|
|
v, category, ok := b.getWithCategory(key)
|
|
if category == StructuredMetadataLabel {
|
|
b.referencedStructuredMetadata = true
|
|
}
|
|
|
|
return v, category, ok
|
|
}
|
|
|
|
// GetWithCategory returns the value and the category of a labels key if it exists.
|
|
func (b *LabelsBuilder) getWithCategory(key string) (string, LabelCategory, bool) {
|
|
for category, lbls := range b.add {
|
|
for _, l := range lbls {
|
|
if l.Name == key {
|
|
return l.Value, LabelCategory(category), true
|
|
}
|
|
}
|
|
}
|
|
for _, d := range b.del {
|
|
if d == key {
|
|
return "", InvalidCategory, false
|
|
}
|
|
}
|
|
|
|
for _, l := range b.base {
|
|
if l.Name == key {
|
|
return l.Value, StreamLabel, true
|
|
}
|
|
}
|
|
return "", InvalidCategory, false
|
|
}
|
|
|
|
func (b *LabelsBuilder) Get(key string) (string, bool) {
|
|
v, _, ok := b.GetWithCategory(key)
|
|
return v, ok
|
|
}
|
|
|
|
// Del deletes the label of the given name.
|
|
func (b *LabelsBuilder) Del(ns ...string) *LabelsBuilder {
|
|
for _, n := range ns {
|
|
for category := range b.add {
|
|
b.deleteWithCategory(LabelCategory(category), n)
|
|
}
|
|
b.del = append(b.del, n)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// deleteWithCategory removes the label from the specified category
|
|
func (b *LabelsBuilder) deleteWithCategory(category LabelCategory, n string) {
|
|
for i, l := range b.add[category] {
|
|
if l.Name == n {
|
|
b.add[category] = append(b.add[category][:i], b.add[category][i+1:]...)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the name/value pair as a label.
|
|
// The value `v` may not be set if a category with higher preference already contains `n`.
|
|
// Category preference goes as Parsed > Structured Metadata > Stream.
|
|
func (b *LabelsBuilder) Set(category LabelCategory, n, v string) *LabelsBuilder {
|
|
// Parsed takes precedence over Structured Metadata and Stream labels.
|
|
// If category is Parsed, we delete `n` from the structured metadata and stream labels.
|
|
if category == ParsedLabel {
|
|
b.deleteWithCategory(StructuredMetadataLabel, n)
|
|
b.deleteWithCategory(StreamLabel, n)
|
|
}
|
|
|
|
// Structured Metadata takes precedence over Stream labels.
|
|
// If category is `StructuredMetadataLabel`,we delete `n` from the stream labels.
|
|
// If `n` exists in the parsed labels, we won't overwrite it's value and we just return what we have.
|
|
if category == StructuredMetadataLabel {
|
|
b.deleteWithCategory(StreamLabel, n)
|
|
if labelsContain(b.add[ParsedLabel], n) {
|
|
return b
|
|
}
|
|
}
|
|
|
|
// Finally, if category is `StreamLabel` and `n` already exists in either the structured metadata or
|
|
// parsed labels, the `Set` operation is a noop and we return the unmodified labels builder.
|
|
if category == StreamLabel {
|
|
if labelsContain(b.add[StructuredMetadataLabel], n) || labelsContain(b.add[ParsedLabel], n) {
|
|
return b
|
|
}
|
|
}
|
|
|
|
for i, a := range b.add[category] {
|
|
if a.Name == n {
|
|
b.add[category][i].Value = v
|
|
return b
|
|
}
|
|
}
|
|
b.add[category] = append(b.add[category], labels.Label{Name: n, Value: v})
|
|
|
|
if category == ParsedLabel {
|
|
// We record parsed labels as extracted so that future parse stages can
|
|
// quickly bypass any existing extracted fields.
|
|
//
|
|
// Note that because this is used for bypassing extracted fields, and
|
|
// because parsed labels always take precedence over structured metadata
|
|
// and stream labels, we must only call RecordExtracted for parsed labels.
|
|
b.parserKeyHints.RecordExtracted(n)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Add the labels to the builder. If a label with the same name
|
|
// already exists in the base labels, a suffix is added to the name.
|
|
func (b *LabelsBuilder) Add(category LabelCategory, labels ...labels.Label) *LabelsBuilder {
|
|
for _, l := range labels {
|
|
name := l.Name
|
|
if b.BaseHas(name) {
|
|
name = fmt.Sprintf("%s%s", name, duplicateSuffix)
|
|
}
|
|
|
|
if name == logqlmodel.ErrorLabel {
|
|
b.err = l.Value
|
|
continue
|
|
}
|
|
|
|
if name == logqlmodel.ErrorDetailsLabel {
|
|
b.errDetails = l.Value
|
|
continue
|
|
}
|
|
|
|
b.Set(category, name, l.Value)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// SetJSONPath sets the original JSON path parts that a label came from
|
|
func (b *LabelsBuilder) SetJSONPath(labelName string, jsonPath []string) *LabelsBuilder {
|
|
b.jsonPaths[labelName] = jsonPath
|
|
return b
|
|
}
|
|
|
|
// GetJSONPath gets the original JSON path parts for a given label if available
|
|
func (b *LabelsBuilder) GetJSONPath(labelName string) []string {
|
|
path, ok := b.jsonPaths[labelName]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return path
|
|
}
|
|
|
|
// Labels returns the labels from the builder. If no modifications
|
|
// were made, the original labels are returned.
|
|
func (b *LabelsBuilder) labels(categories ...LabelCategory) labels.Labels {
|
|
b.buf = b.UnsortedLabels(b.buf, categories...)
|
|
sort.Sort(b.buf)
|
|
return b.buf
|
|
}
|
|
|
|
func (b *LabelsBuilder) appendErrors(buf labels.Labels) labels.Labels {
|
|
if b.err != "" {
|
|
buf = append(buf, labels.Label{
|
|
Name: logqlmodel.ErrorLabel,
|
|
Value: b.err,
|
|
})
|
|
}
|
|
if b.errDetails != "" {
|
|
buf = append(buf, labels.Label{
|
|
Name: logqlmodel.ErrorDetailsLabel,
|
|
Value: b.errDetails,
|
|
})
|
|
}
|
|
return buf
|
|
}
|
|
|
|
func (b *LabelsBuilder) UnsortedLabels(buf labels.Labels, categories ...LabelCategory) labels.Labels {
|
|
if categories == nil {
|
|
categories = allCategories
|
|
}
|
|
|
|
if !b.hasDel() && !b.hasAdd() && categoriesContain(categories, StreamLabel) {
|
|
if buf == nil {
|
|
buf = make(labels.Labels, 0, len(b.base)+1) // +1 for error label.
|
|
} else {
|
|
buf = buf[:0]
|
|
}
|
|
buf = append(buf, b.base...)
|
|
if categoriesContain(categories, ParsedLabel) {
|
|
buf = b.appendErrors(buf)
|
|
}
|
|
|
|
return buf
|
|
}
|
|
|
|
// In the general case, labels are removed, modified or moved
|
|
// rather than added.
|
|
if buf == nil {
|
|
size := len(b.base) + b.sizeAdd() + 1
|
|
buf = make(labels.Labels, 0, size)
|
|
} else {
|
|
buf = buf[:0]
|
|
}
|
|
|
|
if categoriesContain(categories, StreamLabel) {
|
|
Outer:
|
|
for _, l := range b.base {
|
|
// Skip stream labels to be deleted
|
|
for _, n := range b.del {
|
|
if l.Name == n {
|
|
continue Outer
|
|
}
|
|
}
|
|
|
|
// Skip stream labels which value will be replaced by structured metadata
|
|
if labelsContain(b.add[StructuredMetadataLabel], l.Name) {
|
|
continue
|
|
}
|
|
|
|
// Skip stream labels which value will be replaced by parsed labels
|
|
if labelsContain(b.add[ParsedLabel], l.Name) {
|
|
continue
|
|
}
|
|
|
|
// Take value from stream label if present
|
|
if labelsContain(b.add[StreamLabel], l.Name) {
|
|
buf = append(buf, labels.Label{Name: l.Name, Value: b.add[StreamLabel].Get(l.Name)})
|
|
} else {
|
|
buf = append(buf, l)
|
|
}
|
|
}
|
|
}
|
|
|
|
if categoriesContain(categories, StructuredMetadataLabel) {
|
|
for _, l := range b.add[StructuredMetadataLabel] {
|
|
if labelsContain(b.add[ParsedLabel], l.Name) {
|
|
continue
|
|
}
|
|
|
|
buf = append(buf, l)
|
|
}
|
|
}
|
|
|
|
if categoriesContain(categories, ParsedLabel) {
|
|
buf = append(buf, b.add[ParsedLabel]...)
|
|
}
|
|
if (b.HasErr() || b.HasErrorDetails()) && categoriesContain(categories, ParsedLabel) {
|
|
buf = b.appendErrors(buf)
|
|
}
|
|
|
|
return buf
|
|
}
|
|
|
|
type stringMapPool struct {
|
|
pool sync.Pool
|
|
}
|
|
|
|
func newStringMapPool() *stringMapPool {
|
|
return &stringMapPool{
|
|
pool: sync.Pool{
|
|
New: func() interface{} {
|
|
return make(map[string]string)
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (s *stringMapPool) Get() map[string]string {
|
|
m := s.pool.Get().(map[string]string)
|
|
clear(m)
|
|
return m
|
|
}
|
|
|
|
func (s *stringMapPool) Put(m map[string]string) {
|
|
s.pool.Put(m)
|
|
}
|
|
|
|
var smp = newStringMapPool()
|
|
|
|
// puts labels entries into an existing map, it is up to the caller to
|
|
// properly clear the map if it is going to be reused
|
|
func (b *LabelsBuilder) IntoMap(m map[string]string) {
|
|
if !b.hasDel() && !b.hasAdd() && !b.HasErr() {
|
|
if b.baseMap == nil {
|
|
b.baseMap = b.base.Map()
|
|
}
|
|
for k, v := range b.baseMap {
|
|
m[k] = v
|
|
}
|
|
return
|
|
}
|
|
b.buf = b.UnsortedLabels(b.buf)
|
|
// todo should we also cache maps since limited by the result ?
|
|
// Maps also don't create a copy of the labels.
|
|
for _, l := range b.buf {
|
|
m[l.Name] = l.Value
|
|
}
|
|
}
|
|
|
|
func (b *LabelsBuilder) Map() (map[string]string, bool) {
|
|
if !b.hasDel() && !b.hasAdd() && !b.HasErr() {
|
|
if b.baseMap == nil {
|
|
b.baseMap = b.base.Map()
|
|
}
|
|
return b.baseMap, false
|
|
}
|
|
b.buf = b.UnsortedLabels(b.buf)
|
|
// todo should we also cache maps since limited by the result ?
|
|
// Maps also don't create a copy of the labels.
|
|
res := smp.Get()
|
|
for _, l := range b.buf {
|
|
res[l.Name] = l.Value
|
|
}
|
|
return res, true
|
|
}
|
|
|
|
// LabelsResult returns the LabelsResult from the builder.
|
|
// No grouping is applied and the cache is used when possible.
|
|
func (b *LabelsBuilder) LabelsResult() LabelsResult {
|
|
// unchanged path.
|
|
if !b.hasDel() && !b.hasAdd() && !b.HasErr() {
|
|
return b.currentResult
|
|
}
|
|
|
|
// Get all labels at once and sort them
|
|
b.buf = b.UnsortedLabels(b.buf)
|
|
// sort.Sort(b.buf)
|
|
slices.SortFunc(b.buf, func(a, b labels.Label) int { return strings.Compare(a.Name, b.Name) })
|
|
hash := b.hasher.Hash(b.buf)
|
|
|
|
if cached, ok := b.resultCache[hash]; ok {
|
|
return cached
|
|
}
|
|
|
|
// Now segregate the sorted labels into their categories
|
|
var stream, meta, parsed []labels.Label
|
|
|
|
for _, l := range b.buf {
|
|
// Skip error labels for stream and meta categories
|
|
if l.Name == logqlmodel.ErrorLabel || l.Name == logqlmodel.ErrorDetailsLabel {
|
|
parsed = append(parsed, l)
|
|
continue
|
|
}
|
|
|
|
// Check which category this label belongs to
|
|
if labelsContain(b.add[ParsedLabel], l.Name) {
|
|
parsed = append(parsed, l)
|
|
} else if labelsContain(b.add[StructuredMetadataLabel], l.Name) {
|
|
meta = append(meta, l)
|
|
} else {
|
|
stream = append(stream, l)
|
|
}
|
|
}
|
|
|
|
result := NewLabelsResult(b.buf.String(), hash, labels.New(stream...), labels.New(meta...), labels.New(parsed...))
|
|
b.resultCache[hash] = result
|
|
|
|
return result
|
|
}
|
|
|
|
func flattenLabels(buf labels.Labels, many ...labels.Labels) labels.Labels {
|
|
var size int
|
|
for _, lbls := range many {
|
|
size += len(lbls)
|
|
}
|
|
|
|
if buf == nil || cap(buf) < size {
|
|
buf = make(labels.Labels, 0, size)
|
|
} else {
|
|
buf = buf[:0]
|
|
}
|
|
|
|
for _, lbls := range many {
|
|
buf = append(buf, lbls...)
|
|
}
|
|
sort.Sort(buf)
|
|
return buf
|
|
}
|
|
|
|
func labelsContain(labels labels.Labels, name string) bool {
|
|
for _, l := range labels {
|
|
if l.Name == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (b *BaseLabelsBuilder) toUncategorizedResult(buf labels.Labels) LabelsResult {
|
|
hash := b.hasher.Hash(buf)
|
|
if cached, ok := b.resultCache[hash]; ok {
|
|
return cached
|
|
}
|
|
|
|
res := NewLabelsResult(buf.String(), hash, buf.Copy(), nil, nil)
|
|
b.resultCache[hash] = res
|
|
return res
|
|
}
|
|
|
|
// GroupedLabels returns the LabelsResult from the builder.
|
|
// Groups are applied and the cache is used when possible.
|
|
func (b *LabelsBuilder) GroupedLabels() LabelsResult {
|
|
if b.HasErr() {
|
|
// We need to return now before applying grouping otherwise the error might get lost.
|
|
return b.LabelsResult()
|
|
}
|
|
if b.noLabels {
|
|
return EmptyLabelsResult
|
|
}
|
|
// unchanged path.
|
|
if !b.hasDel() && !b.hasAdd() {
|
|
if len(b.groups) == 0 {
|
|
return b.currentResult
|
|
}
|
|
return b.toBaseGroup()
|
|
}
|
|
// no grouping
|
|
if len(b.groups) == 0 {
|
|
return b.LabelsResult()
|
|
}
|
|
|
|
if b.without {
|
|
return b.withoutResult()
|
|
}
|
|
return b.withResult()
|
|
}
|
|
|
|
func (b *LabelsBuilder) withResult() LabelsResult {
|
|
if b.buf == nil {
|
|
b.buf = make(labels.Labels, 0, len(b.groups))
|
|
} else {
|
|
b.buf = b.buf[:0]
|
|
}
|
|
Outer:
|
|
for _, g := range b.groups {
|
|
for _, n := range b.del {
|
|
if g == n {
|
|
continue Outer
|
|
}
|
|
}
|
|
for category, la := range b.add {
|
|
for _, l := range la {
|
|
if g == l.Name {
|
|
if LabelCategory(category) == StructuredMetadataLabel {
|
|
b.referencedStructuredMetadata = true
|
|
}
|
|
b.buf = append(b.buf, l)
|
|
continue Outer
|
|
}
|
|
}
|
|
}
|
|
for _, l := range b.base {
|
|
if g == l.Name {
|
|
b.buf = append(b.buf, l)
|
|
continue Outer
|
|
}
|
|
}
|
|
}
|
|
return b.toUncategorizedResult(b.buf)
|
|
}
|
|
|
|
func (b *LabelsBuilder) withoutResult() LabelsResult {
|
|
if b.buf == nil {
|
|
size := len(b.base) + b.sizeAdd() - len(b.del) - len(b.groups)
|
|
if size < 0 {
|
|
size = 0
|
|
}
|
|
b.buf = make(labels.Labels, 0, size)
|
|
} else {
|
|
b.buf = b.buf[:0]
|
|
}
|
|
Outer:
|
|
for _, l := range b.base {
|
|
for _, n := range b.del {
|
|
if l.Name == n {
|
|
continue Outer
|
|
}
|
|
}
|
|
for _, lbls := range b.add {
|
|
for _, la := range lbls {
|
|
if l.Name == la.Name {
|
|
continue Outer
|
|
}
|
|
}
|
|
}
|
|
for _, lg := range b.groups {
|
|
if l.Name == lg {
|
|
continue Outer
|
|
}
|
|
}
|
|
b.buf = append(b.buf, l)
|
|
}
|
|
|
|
for category, lbls := range b.add {
|
|
OuterAdd:
|
|
for _, la := range lbls {
|
|
for _, lg := range b.groups {
|
|
if la.Name == lg {
|
|
if LabelCategory(category) == StructuredMetadataLabel {
|
|
b.referencedStructuredMetadata = true
|
|
}
|
|
continue OuterAdd
|
|
}
|
|
}
|
|
b.buf = append(b.buf, la)
|
|
}
|
|
}
|
|
sort.Sort(b.buf)
|
|
return b.toUncategorizedResult(b.buf)
|
|
}
|
|
|
|
func (b *LabelsBuilder) toBaseGroup() LabelsResult {
|
|
if b.groupedResult != nil {
|
|
return b.groupedResult
|
|
}
|
|
var lbs labels.Labels
|
|
if b.without {
|
|
lbs = labels.NewBuilder(b.base).Del(b.groups...).Labels()
|
|
} else {
|
|
lbs = labels.NewBuilder(b.base).Keep(b.groups...).Labels()
|
|
}
|
|
res := NewLabelsResult(lbs.String(), lbs.Hash(), lbs, nil, nil)
|
|
b.groupedResult = res
|
|
return res
|
|
}
|
|
|
|
type internedStringSet map[string]struct {
|
|
s string
|
|
ok bool
|
|
}
|
|
|
|
func (i internedStringSet) Get(data []byte, createNew func() (string, bool)) (string, bool) {
|
|
s, ok := i[string(data)]
|
|
if ok {
|
|
return s.s, s.ok
|
|
}
|
|
newStr, ok := createNew()
|
|
if len(i) >= MaxInternedStrings {
|
|
return newStr, ok
|
|
}
|
|
i[string(data)] = struct {
|
|
s string
|
|
ok bool
|
|
}{s: newStr, ok: ok}
|
|
return newStr, ok
|
|
}
|
|
|