github.com/thanos-io/thanos@v0.32.5/pkg/store/labelpb/label.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  // Package containing proto and JSON serializable Labels and ZLabels (no copy) structs used to
     5  // identify series. This package expose no-copy converters to Prometheus labels.Labels.
     6  
     7  package labelpb
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"sort"
    14  	"strings"
    15  	"unsafe"
    16  
    17  	"github.com/cespare/xxhash/v2"
    18  	"github.com/pkg/errors"
    19  	"github.com/prometheus/prometheus/model/labels"
    20  	"go4.org/intern"
    21  )
    22  
    23  var (
    24  	ErrOutOfOrderLabels = errors.New("out of order labels")
    25  	ErrEmptyLabels      = errors.New("label set contains a label with empty name or value")
    26  	ErrDuplicateLabels  = errors.New("label set contains duplicate label names")
    27  
    28  	sep = []byte{'\xff'}
    29  )
    30  
    31  func noAllocString(buf []byte) string {
    32  	return *(*string)(unsafe.Pointer(&buf))
    33  }
    34  
    35  func noAllocBytes(buf string) []byte {
    36  	return *(*[]byte)(unsafe.Pointer(&buf))
    37  }
    38  
    39  // ZLabelsFromPromLabels converts Prometheus labels to slice of labelpb.ZLabel in type unsafe manner.
    40  // It reuses the same memory. Caller should abort using passed labels.Labels.
    41  func ZLabelsFromPromLabels(lset labels.Labels) []ZLabel {
    42  	return *(*[]ZLabel)(unsafe.Pointer(&lset))
    43  }
    44  
    45  // ZLabelsToPromLabels convert slice of labelpb.ZLabel to Prometheus labels in type unsafe manner.
    46  // It reuses the same memory. Caller should abort using passed []ZLabel.
    47  // NOTE: Use with care. ZLabels holds memory from the whole protobuf unmarshal, so the returned
    48  // Prometheus Labels will hold this memory as well.
    49  func ZLabelsToPromLabels(lset []ZLabel) labels.Labels {
    50  	return *(*labels.Labels)(unsafe.Pointer(&lset))
    51  }
    52  
    53  // ReAllocAndInternZLabelsStrings re-allocates all underlying bytes for string, detaching it from bigger memory pool.
    54  // If `intern` is set to true, the method will use interning, i.e. reuse already allocated strings, to make the reallocation
    55  // method more efficient.
    56  //
    57  // This is primarily intended to be used before labels are written into TSDB which can hold label strings in the memory long term.
    58  func ReAllocZLabelsStrings(lset *[]ZLabel, intern bool) {
    59  	if intern {
    60  		for j, l := range *lset {
    61  			(*lset)[j].Name = detachAndInternLabelString(l.Name)
    62  			(*lset)[j].Value = detachAndInternLabelString(l.Value)
    63  		}
    64  		return
    65  	}
    66  
    67  	for j, l := range *lset {
    68  		(*lset)[j].Name = string(noAllocBytes(l.Name))
    69  		(*lset)[j].Value = string(noAllocBytes(l.Value))
    70  	}
    71  }
    72  
    73  // internLabelString is a helper method to intern a label string or,
    74  // if the string was previously interned, it returns the existing
    75  // reference and asserts it to a string.
    76  func internLabelString(s string) string {
    77  	return intern.GetByString(s).Get().(string)
    78  }
    79  
    80  // detachAndInternLabelString reallocates the label string to detach it
    81  // from a bigger memory pool and interns the string.
    82  func detachAndInternLabelString(s string) string {
    83  	return internLabelString(string(noAllocBytes(s)))
    84  }
    85  
    86  // ZLabelSetsToPromLabelSets converts slice of labelpb.ZLabelSet to slice of Prometheus labels.
    87  func ZLabelSetsToPromLabelSets(lss ...ZLabelSet) []labels.Labels {
    88  	res := make([]labels.Labels, 0, len(lss))
    89  	for _, ls := range lss {
    90  		res = append(res, ls.PromLabels())
    91  	}
    92  	return res
    93  }
    94  
    95  // ZLabelSetsFromPromLabels converts []labels.labels to []labelpb.ZLabelSet.
    96  func ZLabelSetsFromPromLabels(lss ...labels.Labels) []ZLabelSet {
    97  	sets := make([]ZLabelSet, 0, len(lss))
    98  	for _, ls := range lss {
    99  		set := ZLabelSet{
   100  			Labels: make([]ZLabel, 0, len(ls)),
   101  		}
   102  		for _, lbl := range ls {
   103  			set.Labels = append(set.Labels, ZLabel{
   104  				Name:  lbl.Name,
   105  				Value: lbl.Value,
   106  			})
   107  		}
   108  		sets = append(sets, set)
   109  	}
   110  
   111  	return sets
   112  }
   113  
   114  // ZLabel is a Label (also easily transformable to Prometheus labels.Labels) that can be unmarshalled from protobuf
   115  // reusing the same memory address for string bytes.
   116  // NOTE: While unmarshalling it uses exactly same bytes that were allocated for protobuf. This mean that *whole* protobuf
   117  // bytes will be not GC-ed as long as ZLabels are referenced somewhere. Use it carefully, only for short living
   118  // protobuf message processing.
   119  type ZLabel Label
   120  
   121  func (m *ZLabel) MarshalTo(data []byte) (int, error) {
   122  	f := Label(*m)
   123  	return f.MarshalTo(data)
   124  }
   125  
   126  func (m *ZLabel) MarshalToSizedBuffer(data []byte) (int, error) {
   127  	f := Label(*m)
   128  	return f.MarshalToSizedBuffer(data)
   129  }
   130  
   131  // Unmarshal unmarshalls gRPC protobuf into ZLabel struct. ZLabel string is directly using bytes passed in `data`.
   132  // To use it add (gogoproto.customtype) = "github.com/thanos-io/thanos/pkg/store/labelpb.ZLabel" to proto field tag.
   133  // NOTE: This exists in internal Google protobuf implementation, but not in open source one: https://news.ycombinator.com/item?id=23588882
   134  func (m *ZLabel) Unmarshal(data []byte) error {
   135  	l := len(data)
   136  
   137  	iNdEx := 0
   138  	for iNdEx < l {
   139  		preIndex := iNdEx
   140  		var wire uint64
   141  		for shift := uint(0); ; shift += 7 {
   142  			if shift >= 64 {
   143  				return ErrIntOverflowTypes
   144  			}
   145  			if iNdEx >= l {
   146  				return io.ErrUnexpectedEOF
   147  			}
   148  			b := data[iNdEx]
   149  			iNdEx++
   150  			wire |= uint64(b&0x7F) << shift
   151  			if b < 0x80 {
   152  				break
   153  			}
   154  		}
   155  		fieldNum := int32(wire >> 3)
   156  		wireType := int(wire & 0x7)
   157  		if wireType == 4 {
   158  			return fmt.Errorf("proto: ZLabel: wiretype end group for non-group")
   159  		}
   160  		if fieldNum <= 0 {
   161  			return fmt.Errorf("proto: ZLabel: illegal tag %d (wire type %d)", fieldNum, wire)
   162  		}
   163  		switch fieldNum {
   164  		case 1:
   165  			if wireType != 2 {
   166  				return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
   167  			}
   168  			var stringLen uint64
   169  			for shift := uint(0); ; shift += 7 {
   170  				if shift >= 64 {
   171  					return ErrIntOverflowTypes
   172  				}
   173  				if iNdEx >= l {
   174  					return io.ErrUnexpectedEOF
   175  				}
   176  				b := data[iNdEx]
   177  				iNdEx++
   178  				stringLen |= uint64(b&0x7F) << shift
   179  				if b < 0x80 {
   180  					break
   181  				}
   182  			}
   183  			intStringLen := int(stringLen)
   184  			if intStringLen < 0 {
   185  				return ErrInvalidLengthTypes
   186  			}
   187  			postIndex := iNdEx + intStringLen
   188  			if postIndex < 0 {
   189  				return ErrInvalidLengthTypes
   190  			}
   191  			if postIndex > l {
   192  				return io.ErrUnexpectedEOF
   193  			}
   194  			m.Name = noAllocString(data[iNdEx:postIndex])
   195  			iNdEx = postIndex
   196  		case 2:
   197  			if wireType != 2 {
   198  				return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
   199  			}
   200  			var stringLen uint64
   201  			for shift := uint(0); ; shift += 7 {
   202  				if shift >= 64 {
   203  					return ErrIntOverflowTypes
   204  				}
   205  				if iNdEx >= l {
   206  					return io.ErrUnexpectedEOF
   207  				}
   208  				b := data[iNdEx]
   209  				iNdEx++
   210  				stringLen |= uint64(b&0x7F) << shift
   211  				if b < 0x80 {
   212  					break
   213  				}
   214  			}
   215  			intStringLen := int(stringLen)
   216  			if intStringLen < 0 {
   217  				return ErrInvalidLengthTypes
   218  			}
   219  			postIndex := iNdEx + intStringLen
   220  			if postIndex < 0 {
   221  				return ErrInvalidLengthTypes
   222  			}
   223  			if postIndex > l {
   224  				return io.ErrUnexpectedEOF
   225  			}
   226  			m.Value = noAllocString(data[iNdEx:postIndex])
   227  			iNdEx = postIndex
   228  		default:
   229  			iNdEx = preIndex
   230  			skippy, err := skipTypes(data[iNdEx:])
   231  			if err != nil {
   232  				return err
   233  			}
   234  			if skippy < 0 {
   235  				return ErrInvalidLengthTypes
   236  			}
   237  			if (iNdEx + skippy) < 0 {
   238  				return ErrInvalidLengthTypes
   239  			}
   240  			if (iNdEx + skippy) > l {
   241  				return io.ErrUnexpectedEOF
   242  			}
   243  			iNdEx += skippy
   244  		}
   245  	}
   246  
   247  	if iNdEx > l {
   248  		return io.ErrUnexpectedEOF
   249  	}
   250  	return nil
   251  }
   252  
   253  func (m *ZLabel) UnmarshalJSON(entry []byte) error {
   254  	f := Label(*m)
   255  	if err := json.Unmarshal(entry, &f); err != nil {
   256  		return errors.Wrapf(err, "labels: label field unmarshal: %v", string(entry))
   257  	}
   258  	*m = ZLabel(f)
   259  	return nil
   260  }
   261  
   262  func (m *ZLabel) Marshal() ([]byte, error) {
   263  	f := Label(*m)
   264  	return f.Marshal()
   265  }
   266  
   267  func (m *ZLabel) MarshalJSON() ([]byte, error) {
   268  	return json.Marshal(Label(*m))
   269  }
   270  
   271  // Size implements proto.Sizer.
   272  func (m *ZLabel) Size() (n int) {
   273  	f := Label(*m)
   274  	return f.Size()
   275  }
   276  
   277  // Equal implements proto.Equaler.
   278  func (m *ZLabel) Equal(other ZLabel) bool {
   279  	return m.Name == other.Name && m.Value == other.Value
   280  }
   281  
   282  // Compare implements proto.Comparer.
   283  func (m *ZLabel) Compare(other ZLabel) int {
   284  	if c := strings.Compare(m.Name, other.Name); c != 0 {
   285  		return c
   286  	}
   287  	return strings.Compare(m.Value, other.Value)
   288  }
   289  
   290  // ExtendSortedLabels extend given labels by extend in labels format.
   291  // The type conversion is done safely, which means we don't modify extend labels underlying array.
   292  //
   293  // In case of existing labels already present in given label set, it will be overwritten by external one.
   294  // NOTE: Labels and extend has to be sorted.
   295  func ExtendSortedLabels(lset, extend labels.Labels) labels.Labels {
   296  	ret := make(labels.Labels, 0, len(lset)+len(extend))
   297  
   298  	// Inject external labels in place.
   299  	for len(lset) > 0 && len(extend) > 0 {
   300  		d := strings.Compare(lset[0].Name, extend[0].Name)
   301  		if d == 0 {
   302  			// Duplicate, prefer external labels.
   303  			// NOTE(fabxc): Maybe move it to a prefixed version to still ensure uniqueness of series?
   304  			ret = append(ret, extend[0])
   305  			lset, extend = lset[1:], extend[1:]
   306  		} else if d < 0 {
   307  			ret = append(ret, lset[0])
   308  			lset = lset[1:]
   309  		} else if d > 0 {
   310  			ret = append(ret, extend[0])
   311  			extend = extend[1:]
   312  		}
   313  	}
   314  
   315  	// Append all remaining elements.
   316  	ret = append(ret, lset...)
   317  	ret = append(ret, extend...)
   318  	return ret
   319  }
   320  
   321  func PromLabelSetsToString(lsets []labels.Labels) string {
   322  	s := []string{}
   323  	for _, ls := range lsets {
   324  		s = append(s, ls.String())
   325  	}
   326  	sort.Strings(s)
   327  	return strings.Join(s, ",")
   328  }
   329  
   330  func (m *ZLabelSet) UnmarshalJSON(entry []byte) error {
   331  	lbls := labels.Labels{}
   332  	if err := lbls.UnmarshalJSON(entry); err != nil {
   333  		return errors.Wrapf(err, "labels: labels field unmarshal: %v", string(entry))
   334  	}
   335  	sort.Sort(lbls)
   336  	m.Labels = ZLabelsFromPromLabels(lbls)
   337  	return nil
   338  }
   339  
   340  func (m *ZLabelSet) MarshalJSON() ([]byte, error) {
   341  	return m.PromLabels().MarshalJSON()
   342  }
   343  
   344  // PromLabels return Prometheus labels.Labels without extra allocation.
   345  func (m *ZLabelSet) PromLabels() labels.Labels {
   346  	return ZLabelsToPromLabels(m.Labels)
   347  }
   348  
   349  // DeepCopy copies labels and each label's string to separate bytes.
   350  func DeepCopy(lbls []ZLabel) []ZLabel {
   351  	ret := make([]ZLabel, len(lbls))
   352  	for i := range lbls {
   353  		ret[i].Name = string(noAllocBytes(lbls[i].Name))
   354  		ret[i].Value = string(noAllocBytes(lbls[i].Value))
   355  	}
   356  	return ret
   357  }
   358  
   359  // HashWithPrefix returns a hash for the given prefix and labels.
   360  func HashWithPrefix(prefix string, lbls []ZLabel) uint64 {
   361  	// Use xxhash.Sum64(b) for fast path as it's faster.
   362  	b := make([]byte, 0, 1024)
   363  	b = append(b, prefix...)
   364  	b = append(b, sep[0])
   365  
   366  	for i, v := range lbls {
   367  		if len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) {
   368  			// If labels entry is 1KB allocate do not allocate whole entry.
   369  			h := xxhash.New()
   370  			_, _ = h.Write(b)
   371  			for _, v := range lbls[i:] {
   372  				_, _ = h.WriteString(v.Name)
   373  				_, _ = h.Write(sep)
   374  				_, _ = h.WriteString(v.Value)
   375  				_, _ = h.Write(sep)
   376  			}
   377  			return h.Sum64()
   378  		}
   379  		b = append(b, v.Name...)
   380  		b = append(b, sep[0])
   381  		b = append(b, v.Value...)
   382  		b = append(b, sep[0])
   383  	}
   384  	return xxhash.Sum64(b)
   385  }
   386  
   387  // ValidateLabels validates label names and values (checks for empty
   388  // names and values, out of order labels and duplicate label names)
   389  // Returns appropriate error if validation fails on a label.
   390  func ValidateLabels(lbls []ZLabel) error {
   391  	if len(lbls) == 0 {
   392  		return ErrEmptyLabels
   393  	}
   394  
   395  	// Check first label.
   396  	l0 := lbls[0]
   397  	if l0.Name == "" || l0.Value == "" {
   398  		return ErrEmptyLabels
   399  	}
   400  
   401  	// Iterate over the rest, check each for empty / duplicates and
   402  	// check lexicographical (alphabetically) ordering.
   403  	for _, l := range lbls[1:] {
   404  		if l.Name == "" || l.Value == "" {
   405  			return ErrEmptyLabels
   406  		}
   407  
   408  		if l.Name == l0.Name {
   409  			return ErrDuplicateLabels
   410  		}
   411  
   412  		if l.Name < l0.Name {
   413  			return ErrOutOfOrderLabels
   414  		}
   415  		l0 = l
   416  	}
   417  
   418  	return nil
   419  }
   420  
   421  // ZLabelSets is a sortable list of ZLabelSet. It assumes the label pairs in each ZLabelSet element are already sorted.
   422  type ZLabelSets []ZLabelSet
   423  
   424  func (z ZLabelSets) Len() int { return len(z) }
   425  
   426  func (z ZLabelSets) Swap(i, j int) { z[i], z[j] = z[j], z[i] }
   427  
   428  func (z ZLabelSets) Less(i, j int) bool {
   429  	l := 0
   430  	r := 0
   431  	var result int
   432  	lenI, lenJ := len(z[i].Labels), len(z[j].Labels)
   433  	for l < lenI && r < lenJ {
   434  		result = z[i].Labels[l].Compare(z[j].Labels[r])
   435  		if result == 0 {
   436  			l++
   437  			r++
   438  			continue
   439  		}
   440  		return result < 0
   441  	}
   442  
   443  	return l == lenI
   444  }