github.com/grafana/pyroscope@v1.18.0/pkg/model/labels.go (about)

     1  package model
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"slices"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/cespare/xxhash/v2"
    14  	pmodel "github.com/prometheus/common/model"
    15  	"github.com/prometheus/prometheus/model/labels"
    16  	semconv "go.opentelemetry.io/otel/semconv/v1.27.0"
    17  
    18  	typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
    19  	"github.com/grafana/pyroscope/pkg/util"
    20  )
    21  
    22  var seps = []byte{'\xff'}
    23  
    24  const (
    25  	LabelNameProfileType        = "__profile_type__"
    26  	LabelNameServiceNamePrivate = "__service_name__"
    27  	LabelNameDelta              = "__delta__"
    28  	LabelNameOTEL               = "__otel__"
    29  	LabelNameProfileName        = pmodel.MetricNameLabel
    30  	LabelNamePeriodType         = "__period_type__"
    31  	LabelNamePeriodUnit         = "__period_unit__"
    32  	LabelNameSessionID          = "__session_id__"
    33  	LabelNameType               = "__type__"
    34  	LabelNameUnit               = "__unit__"
    35  
    36  	LabelNameServiceGitRef     = "service_git_ref"
    37  	LabelNameServiceName       = "service_name"
    38  	LabelNameServiceRepository = "service_repository"
    39  	LabelNameServiceRootPath   = "service_root_path"
    40  
    41  	LabelNameOrder     = "__order__"
    42  	LabelOrderEnforced = "enforced"
    43  
    44  	LabelNamePyroscopeSpy = "pyroscope_spy"
    45  
    46  	AttrProcessExecutableName = semconv.ProcessExecutableNameKey
    47  
    48  	AttrServiceName         = semconv.ServiceNameKey
    49  	AttrServiceNameFallback = "unknown_service"
    50  
    51  	labelSep = '\xfe'
    52  
    53  	ProfileNameOffCpu = "off_cpu" // todo better name?
    54  )
    55  
    56  // Labels is a sorted set of labels. Order has to be guaranteed upon
    57  // instantiation.
    58  type Labels []*typesv1.LabelPair
    59  
    60  func (ls Labels) Len() int           { return len(ls) }
    61  func (ls Labels) Swap(i, j int)      { ls[i], ls[j] = ls[j], ls[i] }
    62  func (ls Labels) Less(i, j int) bool { return ls[i].Name < ls[j].Name }
    63  
    64  // Range calls f on each label.
    65  func (ls Labels) Range(f func(l *typesv1.LabelPair)) {
    66  	for _, l := range ls {
    67  		f(l)
    68  	}
    69  }
    70  
    71  // EmptyLabels returns n empty Labels value, for convenience.
    72  func EmptyLabels() Labels {
    73  	return Labels{}
    74  }
    75  
    76  // LabelsEnforcedOrder is a sort order of labels, where profile type and
    77  // service name labels always go first. This is crucial for query performance
    78  // as labels determine the physical order of the profiling data.
    79  type LabelsEnforcedOrder []*typesv1.LabelPair
    80  
    81  func (ls LabelsEnforcedOrder) Len() int      { return len(ls) }
    82  func (ls LabelsEnforcedOrder) Swap(i, j int) { ls[i], ls[j] = ls[j], ls[i] }
    83  
    84  func (ls LabelsEnforcedOrder) Less(i, j int) bool {
    85  	if ls[i].Name[0] == '_' || ls[j].Name[0] == '_' {
    86  		leftType := ls[i].Name == LabelNameProfileType
    87  		rightType := ls[j].Name == LabelNameProfileType
    88  		if leftType || rightType {
    89  			return leftType || !rightType
    90  		}
    91  		leftService := ls[i].Name == LabelNameServiceNamePrivate
    92  		rightService := ls[j].Name == LabelNameServiceNamePrivate
    93  		if leftService || rightService {
    94  			return leftService || !rightService
    95  		}
    96  	}
    97  	return ls[i].Name < ls[j].Name
    98  }
    99  
   100  // Hash returns a hash value for the label set.
   101  func (ls Labels) Hash() uint64 {
   102  	// Use xxhash.Sum64(b) for fast path as it's faster.
   103  	b := make([]byte, 0, 1024)
   104  	for i, v := range ls {
   105  		if len(b)+len(v.Name)+len(v.Value)+2 >= cap(b) {
   106  			// If labels entry is 1KB+ do not allocate whole entry.
   107  			h := xxhash.New()
   108  			_, _ = h.Write(b)
   109  			for _, v := range ls[i:] {
   110  				_, _ = h.WriteString(v.Name)
   111  				_, _ = h.Write(seps)
   112  				_, _ = h.WriteString(v.Value)
   113  				_, _ = h.Write(seps)
   114  			}
   115  			return h.Sum64()
   116  		}
   117  
   118  		b = append(b, v.Name...)
   119  		b = append(b, seps[0])
   120  		b = append(b, v.Value...)
   121  		b = append(b, seps[0])
   122  	}
   123  	return xxhash.Sum64(b)
   124  }
   125  
   126  // BytesWithLabels is just as Bytes(), but only for labels matching names.
   127  // It uses an byte invalid character as a separator and so should not be used for printing.
   128  func (ls Labels) BytesWithLabels(buf []byte, names ...string) []byte {
   129  	buf = buf[:0]
   130  	buf = append(buf, labelSep)
   131  	for _, name := range names {
   132  		for _, l := range ls {
   133  			if l.Name == name {
   134  				if len(buf) > 1 {
   135  					buf = append(buf, seps[0])
   136  				}
   137  				buf = append(buf, l.Name...)
   138  				buf = append(buf, seps[0])
   139  				buf = append(buf, l.Value...)
   140  				break
   141  			}
   142  		}
   143  	}
   144  	return buf
   145  }
   146  
   147  func (ls Labels) ToPrometheusLabels() labels.Labels {
   148  	res := make([]labels.Label, len(ls))
   149  	for i, l := range ls {
   150  		res[i] = labels.Label{Name: l.Name, Value: l.Value}
   151  	}
   152  	return labels.New(res...)
   153  }
   154  
   155  func (ls Labels) WithoutPrivateLabels() Labels {
   156  	res := make([]*typesv1.LabelPair, 0, len(ls))
   157  	for _, l := range ls {
   158  		if !strings.HasPrefix(l.Name, "__") {
   159  			res = append(res, l)
   160  		}
   161  	}
   162  	return res
   163  }
   164  
   165  var allowedPrivateLabels = map[string]struct{}{
   166  	LabelNameSessionID: {},
   167  }
   168  
   169  func IsLabelAllowedForIngestion(name string) bool {
   170  	if !strings.HasPrefix(name, "__") {
   171  		return true
   172  	}
   173  	_, allowed := allowedPrivateLabels[name]
   174  	return allowed
   175  }
   176  
   177  // WithLabels returns a subset of Labels that match with the provided label names.
   178  func (ls Labels) WithLabels(names ...string) Labels {
   179  	matched := make(Labels, 0, len(names))
   180  	for _, name := range names {
   181  		for _, l := range ls {
   182  			if l.Name == name {
   183  				matched = append(matched, l)
   184  				break
   185  			}
   186  		}
   187  	}
   188  	return matched
   189  }
   190  
   191  // Get returns the value for the label with the given name.
   192  // Returns an empty string if the label doesn't exist.
   193  func (ls Labels) Get(name string) string {
   194  	for _, l := range ls {
   195  		if l.Name == name {
   196  			return l.Value
   197  		}
   198  	}
   199  	return ""
   200  }
   201  
   202  // GetLabel returns the label with the given name.
   203  func (ls Labels) GetLabel(name string) (*typesv1.LabelPair, bool) {
   204  	for _, l := range ls {
   205  		if l.Name == name {
   206  			return l, true
   207  		}
   208  	}
   209  	return nil, false
   210  }
   211  
   212  // Delete removes the first label encountered with the name given in place.
   213  func (ls Labels) Delete(name string) Labels {
   214  	for i, l := range ls {
   215  		if l.Name == name {
   216  			return slices.Delete(ls, i, i+1)
   217  		}
   218  	}
   219  	return ls
   220  }
   221  
   222  func (ls Labels) Subtract(labels Labels) Labels {
   223  	var i, j, k int
   224  	for i < len(ls) && j < len(labels) {
   225  		cmp := CompareLabelPairs2(ls[i], labels[j])
   226  		switch {
   227  		case cmp == 0:
   228  			i++
   229  			j++
   230  		case cmp < 0:
   231  			if i != k {
   232  				ls[k] = ls[i]
   233  			}
   234  			k++
   235  			i++
   236  		default:
   237  			j++
   238  		}
   239  	}
   240  	for i < len(ls) {
   241  		if i != k {
   242  			ls[k] = ls[i]
   243  		}
   244  		k++
   245  		i++
   246  	}
   247  	return ls[:k]
   248  }
   249  
   250  func (ls Labels) Intersect(labels Labels) Labels {
   251  	var i, j, k int
   252  	for i < len(ls) && j < len(labels) {
   253  		cmp := CompareLabelPairs2(ls[i], labels[j])
   254  		if cmp == 0 {
   255  			ls[k] = ls[i]
   256  			k++
   257  			i++
   258  			j++
   259  		} else if cmp < 0 {
   260  			i++
   261  		} else {
   262  			j++
   263  		}
   264  	}
   265  	return ls[:k]
   266  }
   267  
   268  // InsertSorted adds the given label to the set of labels.
   269  // It assumes the labels are sorted lexicographically.
   270  func (ls Labels) InsertSorted(name, value string) Labels {
   271  	// Find the index where the new label should be inserted.
   272  	// TODO: Use binary search on large label sets.
   273  	index := -1
   274  	for i, label := range ls {
   275  		if label.Name > name {
   276  			index = i
   277  			break
   278  		}
   279  		if label.Name == name {
   280  			label.Value = value
   281  			return ls
   282  		}
   283  	}
   284  	// Insert the new label at the found index.
   285  	l := &typesv1.LabelPair{
   286  		Name:  name,
   287  		Value: value,
   288  	}
   289  	c := append(ls, l)
   290  	if index == -1 {
   291  		return c
   292  	}
   293  	copy((c)[index+1:], (c)[index:])
   294  	(c)[index] = l
   295  	return c
   296  }
   297  
   298  func (ls Labels) Clone() Labels {
   299  	result := make(Labels, len(ls))
   300  	for i, l := range ls {
   301  		result[i] = &typesv1.LabelPair{
   302  			Name:  l.Name,
   303  			Value: l.Value,
   304  		}
   305  	}
   306  	return result
   307  }
   308  
   309  // Unique returns a set labels with unique keys.
   310  // Labels expected to be sorted: underlying capacity
   311  // is reused and the original order is preserved:
   312  // the first key takes precedence over duplicates.
   313  // Method receiver should not be used after the call.
   314  func (ls Labels) Unique() Labels {
   315  	if len(ls) <= 1 {
   316  		return ls
   317  	}
   318  	var j int
   319  	for i := 1; i < len(ls); i++ {
   320  		if ls[i].Name != ls[j].Name {
   321  			j++
   322  			ls[j] = ls[i]
   323  		}
   324  	}
   325  	return ls[:j+1]
   326  }
   327  
   328  // LabelPairsString returns a string representation of the label pairs.
   329  func LabelPairsString(lbs []*typesv1.LabelPair) string {
   330  	var b bytes.Buffer
   331  	b.WriteByte('{')
   332  	for i, l := range lbs {
   333  		if i > 0 {
   334  			b.WriteByte(',')
   335  			b.WriteByte(' ')
   336  		}
   337  		b.WriteString(l.Name)
   338  		b.WriteByte('=')
   339  		b.WriteString(strconv.Quote(l.Value))
   340  	}
   341  	b.WriteByte('}')
   342  	return b.String()
   343  }
   344  
   345  // LabelsFromMap returns new sorted Labels from the given map.
   346  func LabelsFromMap(m map[string]string) Labels {
   347  	res := make(Labels, 0, len(m))
   348  	for k, v := range m {
   349  		res = append(res, &typesv1.LabelPair{Name: k, Value: v})
   350  	}
   351  	sort.Sort(res)
   352  	return res
   353  }
   354  
   355  // LabelsFromStrings creates new labels from pairs of strings.
   356  func LabelsFromStrings(ss ...string) Labels {
   357  	if len(ss)%2 != 0 {
   358  		panic("invalid number of strings")
   359  	}
   360  	var res Labels
   361  	for i := 0; i < len(ss); i += 2 {
   362  		res = append(res, &typesv1.LabelPair{Name: ss[i], Value: ss[i+1]})
   363  	}
   364  
   365  	sort.Sort(res)
   366  	return res
   367  }
   368  
   369  // CompareLabelPairs compares the two label sets.
   370  // The result will be 0 if a==b, <0 if a < b, and >0 if a > b.
   371  func CompareLabelPairs(a, b []*typesv1.LabelPair) int {
   372  	l := len(a)
   373  	if len(b) < l {
   374  		l = len(b)
   375  	}
   376  
   377  	for i := 0; i < l; i++ {
   378  		if a[i].Name != b[i].Name {
   379  			if a[i].Name < b[i].Name {
   380  				return -1
   381  			}
   382  			return 1
   383  		}
   384  		if a[i].Value != b[i].Value {
   385  			if a[i].Value < b[i].Value {
   386  				return -1
   387  			}
   388  			return 1
   389  		}
   390  	}
   391  	// If all labels so far were in common, the set with fewer labels comes first.
   392  	return len(a) - len(b)
   393  }
   394  
   395  func CompareLabels(a, b *typesv1.Labels) int {
   396  	return CompareLabelPairs(a.Labels, b.Labels)
   397  }
   398  
   399  func CompareLabelPairs2(a, b *typesv1.LabelPair) int {
   400  	if a.Name < b.Name {
   401  		return -1
   402  	} else if a.Name > b.Name {
   403  		return 1
   404  	}
   405  	if a.Value < b.Value {
   406  		return -1
   407  	} else if a.Value > b.Value {
   408  		return 1
   409  	}
   410  	return 0
   411  }
   412  
   413  // LabelsBuilder allows modifying Labels.
   414  type LabelsBuilder struct {
   415  	base Labels
   416  	del  []string
   417  	add  []*typesv1.LabelPair
   418  }
   419  
   420  // NewLabelsBuilder returns a new LabelsBuilder.
   421  func NewLabelsBuilder(base Labels) *LabelsBuilder {
   422  	b := &LabelsBuilder{
   423  		del: make([]string, 0, 5),
   424  		add: make([]*typesv1.LabelPair, 0, 5),
   425  	}
   426  	b.Reset(base)
   427  	return b
   428  }
   429  
   430  // Reset clears all current state for the builder.
   431  func (b *LabelsBuilder) Reset(base Labels) {
   432  	b.base = base
   433  	b.del = b.del[:0]
   434  	b.add = b.add[:0]
   435  	for _, l := range b.base {
   436  		if l.Value == "" {
   437  			b.del = append(b.del, l.Name)
   438  		}
   439  	}
   440  }
   441  
   442  // Del deletes the label of the given name.
   443  func (b *LabelsBuilder) Del(ns ...string) *LabelsBuilder {
   444  	for _, n := range ns {
   445  		for i, a := range b.add {
   446  			if a.Name == n {
   447  				b.add = append(b.add[:i], b.add[i+1:]...)
   448  			}
   449  		}
   450  		b.del = append(b.del, n)
   451  	}
   452  	return b
   453  }
   454  
   455  // Set the name/value pair as a label.
   456  func (b *LabelsBuilder) Set(n, v string) *LabelsBuilder {
   457  	if v == "" {
   458  		// Empty labels are the same as missing labels.
   459  		return b.Del(n)
   460  	}
   461  	for i, a := range b.add {
   462  		if a.Name == n {
   463  			b.add[i].Value = v
   464  			return b
   465  		}
   466  	}
   467  	b.add = append(b.add, &typesv1.LabelPair{Name: n, Value: v})
   468  
   469  	return b
   470  }
   471  
   472  func (b *LabelsBuilder) Get(n string) string {
   473  	// Del() removes entries from .add but Set() does not remove from .del, so check .add first.
   474  	for _, a := range b.add {
   475  		if a.Name == n {
   476  			return a.Value
   477  		}
   478  	}
   479  	if slices.Contains(b.del, n) {
   480  		return ""
   481  	}
   482  	return b.base.Get(n)
   483  }
   484  
   485  // Range calls f on each label in the Builder.
   486  func (b *LabelsBuilder) Range(f func(l *typesv1.LabelPair)) {
   487  	// Stack-based arrays to avoid heap allocation in most cases.
   488  	var addStack [128]*typesv1.LabelPair
   489  	var delStack [128]string
   490  	// Take a copy of add and del, so they are unaffected by calls to Set() or Del().
   491  	origAdd, origDel := append(addStack[:0], b.add...), append(delStack[:0], b.del...)
   492  	b.base.Range(func(l *typesv1.LabelPair) {
   493  		if !slices.Contains(origDel, l.Name) && !contains(origAdd, l.Name) {
   494  			f(l)
   495  		}
   496  	})
   497  	for _, a := range origAdd {
   498  		f(a)
   499  	}
   500  }
   501  
   502  func contains(s []*typesv1.LabelPair, n string) bool {
   503  	for _, a := range s {
   504  		if a.Name == n {
   505  			return true
   506  		}
   507  	}
   508  	return false
   509  }
   510  
   511  // Labels returns the labels from the builder. If no modifications
   512  // were made, the original labels are returned.
   513  func (b *LabelsBuilder) Labels() Labels {
   514  	res := b.LabelsUnsorted()
   515  	sort.Sort(res)
   516  	return res
   517  }
   518  
   519  // LabelsUnsorted returns the labels from the builder. If no modifications
   520  // were made, the original labels are returned.
   521  //
   522  // The order is not deterministic.
   523  func (b *LabelsBuilder) LabelsUnsorted() Labels {
   524  	if len(b.del) == 0 && len(b.add) == 0 {
   525  		return b.base
   526  	}
   527  
   528  	// In the general case, labels are removed, modified or moved
   529  	// rather than added.
   530  	res := make(Labels, 0, len(b.base))
   531  Outer:
   532  	for _, l := range b.base {
   533  		for _, n := range b.del {
   534  			if l.Name == n {
   535  				continue Outer
   536  			}
   537  		}
   538  		for _, la := range b.add {
   539  			if l.Name == la.Name {
   540  				continue Outer
   541  			}
   542  		}
   543  		res = append(res, l)
   544  	}
   545  
   546  	return append(res, b.add...)
   547  }
   548  
   549  type SessionID uint64
   550  
   551  func (s SessionID) String() string {
   552  	var b [8]byte
   553  	binary.LittleEndian.PutUint64(b[:], uint64(s))
   554  	return hex.EncodeToString(b[:])
   555  }
   556  
   557  func ParseSessionID(s string) (SessionID, error) {
   558  	if len(s) != 16 {
   559  		return 0, fmt.Errorf("invalid session id length %d", len(s))
   560  	}
   561  	var b [8]byte
   562  	if _, err := hex.Decode(b[:], util.YoloBuf(s)); err != nil {
   563  		return 0, err
   564  	}
   565  	return SessionID(binary.LittleEndian.Uint64(b[:])), nil
   566  }
   567  
   568  type ServiceVersion struct {
   569  	Repository string `json:"repository,omitempty"`
   570  	GitRef     string `json:"git_ref,omitempty"`
   571  	BuildID    string `json:"build_id,omitempty"`
   572  	RootPath   string `json:"root_path,omitempty"`
   573  }
   574  
   575  // ServiceVersionFromLabels Attempts to extract a service version from the given labels.
   576  // Returns false if no service version was found.
   577  func ServiceVersionFromLabels(lbls Labels) (ServiceVersion, bool) {
   578  	repo := lbls.Get(LabelNameServiceRepository)
   579  	gitref := lbls.Get(LabelNameServiceGitRef)
   580  	rootPath := lbls.Get(LabelNameServiceRootPath)
   581  	return ServiceVersion{
   582  		Repository: repo,
   583  		GitRef:     gitref,
   584  		RootPath:   rootPath,
   585  	}, repo != "" || gitref != "" || rootPath != ""
   586  }
   587  
   588  // IntersectAll returns only the labels that are present in all label sets
   589  // with the same value. Used for merging exemplars with dynamic labels.
   590  // Reuses the existing Intersect method by iteratively intersecting all label sets.
   591  func IntersectAll(labelSets []Labels) Labels {
   592  	if len(labelSets) == 0 {
   593  		return nil
   594  	}
   595  	if len(labelSets) == 1 {
   596  		return labelSets[0]
   597  	}
   598  
   599  	result := labelSets[0].Clone()
   600  	for i := 1; i < len(labelSets); i++ {
   601  		result = result.Intersect(labelSets[i])
   602  		if len(result) == 0 {
   603  			return nil
   604  		}
   605  	}
   606  
   607  	if len(result) == 0 {
   608  		return nil
   609  	}
   610  	return result
   611  }
   612  
   613  // WithoutLabels returns a new Labels with the specified label names removed.
   614  func (ls Labels) WithoutLabels(names ...string) Labels {
   615  	if len(names) == 0 {
   616  		return ls
   617  	}
   618  
   619  	toRemove := make(Labels, 0, len(names))
   620  	for _, name := range names {
   621  		for _, l := range ls {
   622  			if l.Name == name {
   623  				toRemove = append(toRemove, l)
   624  				break
   625  			}
   626  		}
   627  	}
   628  
   629  	if len(toRemove) == 0 {
   630  		return ls
   631  	}
   632  
   633  	result := ls.Clone()
   634  	return result.Subtract(toRemove)
   635  }