github.com/thanos-io/thanos@v0.32.5/pkg/store/storepb/custom.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package storepb 5 6 import ( 7 "bytes" 8 "encoding/binary" 9 "fmt" 10 "sort" 11 "strconv" 12 "strings" 13 14 "github.com/gogo/protobuf/types" 15 "github.com/pkg/errors" 16 "github.com/prometheus/prometheus/model/labels" 17 "google.golang.org/grpc/codes" 18 19 "github.com/thanos-io/thanos/pkg/store/labelpb" 20 ) 21 22 var PartialResponseStrategyValues = func() []string { 23 var s []string 24 for k := range PartialResponseStrategy_value { 25 s = append(s, k) 26 } 27 sort.Strings(s) 28 return s 29 }() 30 31 func NewWarnSeriesResponse(err error) *SeriesResponse { 32 return &SeriesResponse{ 33 Result: &SeriesResponse_Warning{ 34 Warning: err.Error(), 35 }, 36 } 37 } 38 39 func NewSeriesResponse(series *Series) *SeriesResponse { 40 return &SeriesResponse{ 41 Result: &SeriesResponse_Series{ 42 Series: series, 43 }, 44 } 45 } 46 47 func NewHintsSeriesResponse(hints *types.Any) *SeriesResponse { 48 return &SeriesResponse{ 49 Result: &SeriesResponse_Hints{ 50 Hints: hints, 51 }, 52 } 53 } 54 55 func GRPCCodeFromWarn(warn string) codes.Code { 56 if strings.Contains(warn, "rpc error: code = ResourceExhausted") { 57 return codes.ResourceExhausted 58 } 59 if strings.Contains(warn, "rpc error: code = Code(422)") { 60 return 422 61 } 62 return codes.Unknown 63 } 64 65 type emptySeriesSet struct{} 66 67 func (emptySeriesSet) Next() bool { return false } 68 func (emptySeriesSet) At() (labels.Labels, []AggrChunk) { return nil, nil } 69 func (emptySeriesSet) Err() error { return nil } 70 71 // EmptySeriesSet returns a new series set that contains no series. 72 func EmptySeriesSet() SeriesSet { 73 return emptySeriesSet{} 74 } 75 76 // MergeSeriesSets takes all series sets and returns as a union single series set. 77 // It assumes series are sorted by labels within single SeriesSet, similar to remote read guarantees. 78 // However, they can be partial: in such case, if the single SeriesSet returns the same series within many iterations, 79 // MergeSeriesSets will merge those into one. 80 // 81 // It also assumes in a "best effort" way that chunks are sorted by min time. It's done as an optimization only, so if input 82 // series' chunks are NOT sorted, the only consequence is that the duplicates might be not correctly removed. This is double checked 83 // which on just-before PromQL level as well, so the only consequence is increased network bandwidth. 84 // If all chunks were sorted, MergeSeriesSet ALSO returns sorted chunks by min time. 85 // 86 // Chunks within the same series can also overlap (within all SeriesSet 87 // as well as single SeriesSet alone). If the chunk ranges overlap, the *exact* chunk duplicates will be removed 88 // (except one), and any other overlaps will be appended into on chunks slice. 89 func MergeSeriesSets(all ...SeriesSet) SeriesSet { 90 switch len(all) { 91 case 0: 92 return emptySeriesSet{} 93 case 1: 94 return newUniqueSeriesSet(all[0]) 95 } 96 h := len(all) / 2 97 98 return newMergedSeriesSet( 99 MergeSeriesSets(all[:h]...), 100 MergeSeriesSets(all[h:]...), 101 ) 102 } 103 104 // SeriesSet is a set of series and their corresponding chunks. 105 // The set is sorted by the label sets. Chunks may be overlapping or expected of order. 106 type SeriesSet interface { 107 Next() bool 108 At() (labels.Labels, []AggrChunk) 109 Err() error 110 } 111 112 // mergedSeriesSet takes two series sets as a single series set. 113 type mergedSeriesSet struct { 114 a, b SeriesSet 115 116 lset labels.Labels 117 chunks []AggrChunk 118 adone, bdone bool 119 } 120 121 func newMergedSeriesSet(a, b SeriesSet) *mergedSeriesSet { 122 s := &mergedSeriesSet{a: a, b: b} 123 // Initialize first elements of both sets as Next() needs 124 // one element look-ahead. 125 s.adone = !s.a.Next() 126 s.bdone = !s.b.Next() 127 128 return s 129 } 130 131 func (s *mergedSeriesSet) At() (labels.Labels, []AggrChunk) { 132 return s.lset, s.chunks 133 } 134 135 func (s *mergedSeriesSet) Err() error { 136 if s.a.Err() != nil { 137 return s.a.Err() 138 } 139 return s.b.Err() 140 } 141 142 func (s *mergedSeriesSet) compare() int { 143 if s.adone { 144 return 1 145 } 146 if s.bdone { 147 return -1 148 } 149 lsetA, _ := s.a.At() 150 lsetB, _ := s.b.At() 151 return labels.Compare(lsetA, lsetB) 152 } 153 154 func (s *mergedSeriesSet) Next() bool { 155 if s.adone && s.bdone || s.Err() != nil { 156 return false 157 } 158 159 d := s.compare() 160 if d > 0 { 161 s.lset, s.chunks = s.b.At() 162 s.bdone = !s.b.Next() 163 return true 164 } 165 if d < 0 { 166 s.lset, s.chunks = s.a.At() 167 s.adone = !s.a.Next() 168 return true 169 } 170 171 // Both a and b contains the same series. Go through all chunks, remove duplicates and concatenate chunks from both 172 // series sets. We best effortly assume chunks are sorted by min time. If not, we will not detect all deduplicate which will 173 // be account on select layer anyway. We do it still for early optimization. 174 lset, chksA := s.a.At() 175 _, chksB := s.b.At() 176 s.lset = lset 177 178 // Slice reuse is not generally safe with nested merge iterators. 179 // We err on the safe side an create a new slice. 180 s.chunks = make([]AggrChunk, 0, len(chksA)+len(chksB)) 181 182 b := 0 183 Outer: 184 for a := range chksA { 185 for { 186 if b >= len(chksB) { 187 // No more b chunks. 188 s.chunks = append(s.chunks, chksA[a:]...) 189 break Outer 190 } 191 192 cmp := chksA[a].Compare(chksB[b]) 193 if cmp > 0 { 194 s.chunks = append(s.chunks, chksA[a]) 195 break 196 } 197 if cmp < 0 { 198 s.chunks = append(s.chunks, chksB[b]) 199 b++ 200 continue 201 } 202 203 // Exact duplicated chunks, discard one from b. 204 b++ 205 } 206 } 207 208 if b < len(chksB) { 209 s.chunks = append(s.chunks, chksB[b:]...) 210 } 211 212 s.adone = !s.a.Next() 213 s.bdone = !s.b.Next() 214 return true 215 } 216 217 // uniqueSeriesSet takes one series set and ensures each iteration contains single, full series. 218 type uniqueSeriesSet struct { 219 SeriesSet 220 done bool 221 222 peek *Series 223 224 lset labels.Labels 225 chunks []AggrChunk 226 } 227 228 func newUniqueSeriesSet(wrapped SeriesSet) *uniqueSeriesSet { 229 return &uniqueSeriesSet{SeriesSet: wrapped} 230 } 231 232 func (s *uniqueSeriesSet) At() (labels.Labels, []AggrChunk) { 233 return s.lset, s.chunks 234 } 235 236 func (s *uniqueSeriesSet) Next() bool { 237 if s.Err() != nil { 238 return false 239 } 240 241 for !s.done { 242 if s.done = !s.SeriesSet.Next(); s.done { 243 break 244 } 245 lset, chks := s.SeriesSet.At() 246 if s.peek == nil { 247 s.peek = &Series{Labels: labelpb.ZLabelsFromPromLabels(lset), Chunks: chks} 248 continue 249 } 250 251 if labels.Compare(lset, s.peek.PromLabels()) != 0 { 252 s.lset, s.chunks = s.peek.PromLabels(), s.peek.Chunks 253 s.peek = &Series{Labels: labelpb.ZLabelsFromPromLabels(lset), Chunks: chks} 254 return true 255 } 256 257 // We assume non-overlapping, sorted chunks. This is best effort only, if it's otherwise it 258 // will just be duplicated, but well handled by StoreAPI consumers. 259 s.peek.Chunks = append(s.peek.Chunks, chks...) 260 } 261 262 if s.peek == nil { 263 return false 264 } 265 266 s.lset, s.chunks = s.peek.PromLabels(), s.peek.Chunks 267 s.peek = nil 268 return true 269 } 270 271 // Compare returns positive 1 if chunk is smaller -1 if larger than b by min time, then max time. 272 // It returns 0 if chunks are exactly the same. 273 func (m AggrChunk) Compare(b AggrChunk) int { 274 if m.MinTime < b.MinTime { 275 return 1 276 } 277 if m.MinTime > b.MinTime { 278 return -1 279 } 280 281 // Same min time. 282 if m.MaxTime < b.MaxTime { 283 return 1 284 } 285 if m.MaxTime > b.MaxTime { 286 return -1 287 } 288 289 // We could use proto.Equal, but we need ordering as well. 290 for _, cmp := range []func() int{ 291 func() int { return m.Raw.Compare(b.Raw) }, 292 func() int { return m.Count.Compare(b.Count) }, 293 func() int { return m.Sum.Compare(b.Sum) }, 294 func() int { return m.Min.Compare(b.Min) }, 295 func() int { return m.Max.Compare(b.Max) }, 296 func() int { return m.Counter.Compare(b.Counter) }, 297 } { 298 if c := cmp(); c == 0 { 299 continue 300 } else { 301 return c 302 } 303 } 304 return 0 305 } 306 307 // Compare returns positive 1 if chunk is smaller -1 if larger. 308 // It returns 0 if chunks are exactly the same. 309 func (m *Chunk) Compare(b *Chunk) int { 310 if m == nil && b == nil { 311 return 0 312 } 313 if b == nil { 314 return 1 315 } 316 if m == nil { 317 return -1 318 } 319 320 if m.Type < b.Type { 321 return 1 322 } 323 if m.Type > b.Type { 324 return -1 325 } 326 return bytes.Compare(m.Data, b.Data) 327 } 328 329 func (x *PartialResponseStrategy) UnmarshalJSON(entry []byte) error { 330 fieldStr, err := strconv.Unquote(string(entry)) 331 if err != nil { 332 return errors.Wrapf(err, fmt.Sprintf("failed to unqote %v, in order to unmarshal as 'partial_response_strategy'. Possible values are %s", string(entry), strings.Join(PartialResponseStrategyValues, ","))) 333 } 334 335 if fieldStr == "" { 336 // NOTE: For Rule default is abort as this is recommended for alerting. 337 *x = PartialResponseStrategy_ABORT 338 return nil 339 } 340 341 strategy, ok := PartialResponseStrategy_value[strings.ToUpper(fieldStr)] 342 if !ok { 343 return errors.Errorf(fmt.Sprintf("failed to unmarshal %v as 'partial_response_strategy'. Possible values are %s", string(entry), strings.Join(PartialResponseStrategyValues, ","))) 344 } 345 *x = PartialResponseStrategy(strategy) 346 return nil 347 } 348 349 func (x *PartialResponseStrategy) MarshalJSON() ([]byte, error) { 350 return []byte(strconv.Quote(x.String())), nil 351 } 352 353 // PromMatchersToMatchers returns proto matchers from Prometheus matchers. 354 // NOTE: It allocates memory. 355 func PromMatchersToMatchers(ms ...*labels.Matcher) ([]LabelMatcher, error) { 356 res := make([]LabelMatcher, 0, len(ms)) 357 for _, m := range ms { 358 var t LabelMatcher_Type 359 360 switch m.Type { 361 case labels.MatchEqual: 362 t = LabelMatcher_EQ 363 case labels.MatchNotEqual: 364 t = LabelMatcher_NEQ 365 case labels.MatchRegexp: 366 t = LabelMatcher_RE 367 case labels.MatchNotRegexp: 368 t = LabelMatcher_NRE 369 default: 370 return nil, errors.Errorf("unrecognized matcher type %d", m.Type) 371 } 372 res = append(res, LabelMatcher{Type: t, Name: m.Name, Value: m.Value}) 373 } 374 return res, nil 375 } 376 377 // MatchersToPromMatchers returns Prometheus matchers from proto matchers. 378 // NOTE: It allocates memory. 379 func MatchersToPromMatchers(ms ...LabelMatcher) ([]*labels.Matcher, error) { 380 res := make([]*labels.Matcher, 0, len(ms)) 381 for _, m := range ms { 382 var t labels.MatchType 383 384 switch m.Type { 385 case LabelMatcher_EQ: 386 t = labels.MatchEqual 387 case LabelMatcher_NEQ: 388 t = labels.MatchNotEqual 389 case LabelMatcher_RE: 390 t = labels.MatchRegexp 391 case LabelMatcher_NRE: 392 t = labels.MatchNotRegexp 393 default: 394 return nil, errors.Errorf("unrecognized label matcher type %d", m.Type) 395 } 396 m, err := labels.NewMatcher(t, m.Name, m.Value) 397 if err != nil { 398 return nil, err 399 } 400 res = append(res, m) 401 } 402 return res, nil 403 } 404 405 // MatchersToString converts label matchers to string format. 406 // String should be parsable as a valid PromQL query metric selector. 407 func MatchersToString(ms ...LabelMatcher) string { 408 var res string 409 for i, m := range ms { 410 res += m.PromString() 411 if i < len(ms)-1 { 412 res += ", " 413 } 414 } 415 return "{" + res + "}" 416 } 417 418 // PromMatchersToString converts prometheus label matchers to string format. 419 // String should be parsable as a valid PromQL query metric selector. 420 func PromMatchersToString(ms ...*labels.Matcher) string { 421 var res string 422 for i, m := range ms { 423 res += m.String() 424 if i < len(ms)-1 { 425 res += ", " 426 } 427 } 428 return "{" + res + "}" 429 } 430 431 func (m *LabelMatcher) PromString() string { 432 return fmt.Sprintf("%s%s%q", m.Name, m.Type.PromString(), m.Value) 433 } 434 435 func (x LabelMatcher_Type) PromString() string { 436 typeToStr := map[LabelMatcher_Type]string{ 437 LabelMatcher_EQ: "=", 438 LabelMatcher_NEQ: "!=", 439 LabelMatcher_RE: "=~", 440 LabelMatcher_NRE: "!~", 441 } 442 if str, ok := typeToStr[x]; ok { 443 return str 444 } 445 panic("unknown match type") 446 } 447 448 // PromLabels return Prometheus labels.Labels without extra allocation. 449 func (m *Series) PromLabels() labels.Labels { 450 return labelpb.ZLabelsToPromLabels(m.Labels) 451 } 452 453 // Deprecated. 454 // TODO(bwplotka): Remove this once Cortex dep will stop using it. 455 type Label = labelpb.ZLabel 456 457 // Deprecated. 458 // TODO(bwplotka): Remove this in next PR. Done to reduce diff only. 459 type LabelSet = labelpb.ZLabelSet 460 461 // Deprecated. 462 // TODO(bwplotka): Remove this once Cortex dep will stop using it. 463 func CompareLabels(a, b []Label) int { 464 return labels.Compare(labelpb.ZLabelsToPromLabels(a), labelpb.ZLabelsToPromLabels(b)) 465 } 466 467 // Deprecated. 468 // TODO(bwplotka): Remove this once Cortex dep will stop using it. 469 func LabelsToPromLabelsUnsafe(lset []Label) labels.Labels { 470 return labelpb.ZLabelsToPromLabels(lset) 471 } 472 473 // XORNumSamples return number of samples. Returns 0 if it's not XOR chunk. 474 func (m *Chunk) XORNumSamples() int { 475 if m.Type == Chunk_XOR { 476 return int(binary.BigEndian.Uint16(m.Data)) 477 } 478 return 0 479 } 480 481 type SeriesStatsCounter struct { 482 lastSeriesHash uint64 483 484 Series int 485 Chunks int 486 Samples int 487 } 488 489 func (c *SeriesStatsCounter) CountSeries(seriesLabels []labelpb.ZLabel) { 490 seriesHash := labelpb.HashWithPrefix("", seriesLabels) 491 if c.lastSeriesHash != 0 || seriesHash != c.lastSeriesHash { 492 c.lastSeriesHash = seriesHash 493 c.Series++ 494 } 495 } 496 497 func (c *SeriesStatsCounter) Count(series *Series) { 498 c.CountSeries(series.Labels) 499 for _, chk := range series.Chunks { 500 if chk.Raw != nil { 501 c.Chunks++ 502 c.Samples += chk.Raw.XORNumSamples() 503 } 504 505 if chk.Count != nil { 506 c.Chunks++ 507 c.Samples += chk.Count.XORNumSamples() 508 } 509 510 if chk.Counter != nil { 511 c.Chunks++ 512 c.Samples += chk.Counter.XORNumSamples() 513 } 514 515 if chk.Max != nil { 516 c.Chunks++ 517 c.Samples += chk.Max.XORNumSamples() 518 } 519 520 if chk.Min != nil { 521 c.Chunks++ 522 c.Samples += chk.Min.XORNumSamples() 523 } 524 525 if chk.Sum != nil { 526 c.Chunks++ 527 c.Samples += chk.Sum.XORNumSamples() 528 } 529 } 530 } 531 532 func (m *SeriesRequest) ToPromQL() string { 533 return m.QueryHints.toPromQL(m.Matchers) 534 } 535 536 // IsSafeToExecute returns true if the function or aggregation from the query hint 537 // can be safely executed by the underlying Prometheus instance without affecting the 538 // result of the query. 539 func (m *QueryHints) IsSafeToExecute() bool { 540 distributiveOperations := []string{ 541 "max", 542 "max_over_time", 543 "min", 544 "min_over_time", 545 "group", 546 } 547 for _, op := range distributiveOperations { 548 if m.Func.Name == op { 549 return true 550 } 551 } 552 553 return false 554 }