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 }