github.com/m3db/m3@v1.5.0/src/query/functions/utils/group.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package utils 22 23 import ( 24 "bytes" 25 "sort" 26 27 "github.com/m3db/m3/src/query/block" 28 "github.com/m3db/m3/src/query/models" 29 ) 30 31 type group struct { 32 buckets []int 33 tags models.Tags 34 } 35 36 type withKeysTags func(tags models.Tags, matchingTags [][]byte) models.Tags 37 38 func includeKeysTags(tags models.Tags, matchingTags [][]byte) models.Tags { 39 return tags.TagsWithKeys(matchingTags) 40 } 41 42 func excludeKeysTags(tags models.Tags, matchingTags [][]byte) models.Tags { 43 return tags.TagsWithoutKeys(matchingTags).WithoutName() 44 } 45 46 // GroupSeries groups series by tags. 47 // It gives a list of seriesMeta for the grouped series, 48 // and a list of corresponding buckets which signify which 49 // series are mapped to which grouped outputs 50 func GroupSeries( 51 matchingTags [][]byte, 52 without bool, 53 opName []byte, 54 metas []block.SeriesMeta, 55 ) ([][]int, []block.SeriesMeta) { 56 var tagsFunc withKeysTags 57 if without { 58 tagsFunc = excludeKeysTags 59 } else { 60 tagsFunc = includeKeysTags 61 } 62 63 groups := make(map[uint64]*group) 64 for i, meta := range metas { 65 tags := tagsFunc(meta.Tags, matchingTags) 66 // NB(arnikola): Get the ID of the series with relevant tags 67 id := tags.HashedID() 68 if val, ok := groups[id]; ok { 69 // If ID has been seen, the corresponding grouped 70 // series for this index already exists; add the 71 // current index to the bucket for that 72 // grouped series 73 val.buckets = append(val.buckets, i) 74 } else { 75 // If ID has not been seen, create a grouped series 76 // with the appropriate tags, and add the current index 77 // to the bucket for that grouped series 78 groups[id] = &group{ 79 buckets: []int{i}, 80 tags: tags, 81 } 82 } 83 } 84 85 sortedGroups := sortGroups(groups) 86 87 groupedBuckets := make([][]int, len(groups)) 88 groupedMetas := make([]block.SeriesMeta, len(groups)) 89 90 for i, group := range sortedGroups { 91 groupedBuckets[i] = group.buckets 92 groupedMetas[i] = block.SeriesMeta{ 93 Tags: group.tags, 94 Name: opName, 95 } 96 } 97 98 return groupedBuckets, groupedMetas 99 } 100 101 func sortGroups(groups map[uint64]*group) []*group { 102 result := make([]*group, 0, len(groups)) 103 104 for _, group := range groups { 105 result = append(result, group) 106 } 107 108 sort.Slice(result, func(i int, j int) bool { 109 return compareTagSets(result[i].tags, result[j].tags) < 0 110 }) 111 112 return result 113 } 114 115 func compareTagSets(a, b models.Tags) int { 116 l := a.Len() 117 118 if b.Len() < l { 119 l = b.Len() 120 } 121 122 for i := 0; i < l; i++ { 123 byName := bytes.Compare(a.Tags[i].Name, b.Tags[i].Name) 124 if byName != 0 { 125 return byName 126 } 127 byValue := bytes.Compare(a.Tags[i].Value, b.Tags[i].Value) 128 if byValue != 0 { 129 return byValue 130 } 131 } 132 // If all tags so far were in common, the set with fewer tags comes first. 133 return a.Len() - b.Len() 134 }