github.com/go-graphite/carbonapi@v0.17.0/expr/sort.go (about) 1 package expr 2 3 import ( 4 "path/filepath" 5 "sort" 6 "strings" 7 8 "github.com/go-graphite/carbonapi/expr/types" 9 "github.com/go-graphite/carbonapi/pkg/parser" 10 ) 11 12 // type for sorting a list of metrics by the nth part of the metric name. 13 // Implements sort.Interface minus Less, which needs to be provided by a struct 14 // that embeds this one. Provides compareBy for the benefit of that struct, which 15 // turns a function that compares two strings into a suitable Less function. Caches 16 // the relevant metric name part to avoid excessive calls to strings.Split. 17 type byPartBase struct { 18 // the metrics to be sorted 19 metrics []*types.MetricData 20 // which part of the name we are sorting on 21 part int 22 // a cache of the relevant part of the name for each metric in metrics 23 keys []*string 24 } 25 26 func (b byPartBase) Len() int { return len(b.metrics) } 27 28 func (b byPartBase) Swap(i, j int) { 29 b.metrics[i], b.metrics[j] = b.metrics[j], b.metrics[i] 30 b.keys[i], b.keys[j] = b.keys[j], b.keys[i] 31 } 32 33 func getPart(metric *types.MetricData, part int) string { 34 parts := strings.Split(metric.Name, ".") 35 if part >= len(parts) { 36 return "" 37 } 38 return parts[part] 39 } 40 41 // Given two indices, i and j, and a comparator function that returns whether 42 // one metric name segment should sort before another, extracts the 'part'th part 43 // of the metric names, consults the comparator function, and returns a boolean 44 // suitable for use as the Less() method of a sort.Interface. 45 func (b byPartBase) compareBy(i, j int, comparator func(string, string) bool) bool { 46 if b.keys[i] == nil { 47 part := getPart(b.metrics[i], b.part) 48 b.keys[i] = &part 49 } 50 if b.keys[j] == nil { 51 part := getPart(b.metrics[j], b.part) 52 b.keys[j] = &part 53 } 54 return comparator(*b.keys[i], *b.keys[j]) 55 } 56 57 // byPart returns a byPartBase suitable for sorting 'metrics' by 'part'. 58 func byPart(metrics []*types.MetricData, part int) byPartBase { 59 return byPartBase{ 60 metrics: metrics, 61 keys: make([]*string, len(metrics)), 62 part: part, 63 } 64 } 65 66 // type for sorting a list of metrics 'alphabetically' (go string compare order) 67 type byPartAlphabetical struct { 68 byPartBase 69 } 70 71 func (b byPartAlphabetical) Less(i, j int) bool { 72 return b.compareBy(i, j, func(first, second string) bool { 73 return first < second 74 }) 75 } 76 77 // AlphabeticallyByPart returns a byPartAlphabetical that will sort 'metrics' alphabetically by 'part'. 78 func AlphabeticallyByPart(metrics []*types.MetricData, part int) sort.Interface { 79 return byPartAlphabetical{byPart(metrics, part)} 80 } 81 82 func sortByBraces(metrics []*types.MetricData, part int, pattern string) { 83 bStart := strings.IndexRune(pattern, '{') 84 bEnd := strings.IndexRune(pattern, '}') 85 if bStart == -1 || bEnd <= bStart { 86 return 87 } 88 89 parts := make([]string, len(metrics)) 90 for i, metric := range metrics { 91 parts[i] = getPart(metric, part) 92 } 93 src := make([]*types.MetricData, len(metrics)) 94 used := make([]bool, len(metrics)) 95 copy(src, metrics) 96 j := 0 97 98 alternatives := strings.Split(pattern[bStart+1:bEnd], ",") 99 for _, alternative := range alternatives { 100 glob := pattern[:bStart] + alternative + pattern[bEnd+1:] 101 for i := 0; i < len(src); i++ { 102 if used[i] { 103 continue 104 } 105 if match, _ := filepath.Match(glob, parts[i]); match { 106 metrics[j] = src[i] 107 j = j + 1 108 used[i] = true 109 } 110 } 111 } 112 for i, metric := range src { // catch any leftovers 113 if !used[i] { 114 metrics[j] = metric 115 j = j + 1 116 } 117 } 118 } 119 120 // SortMetrics sort metric data alphabetically. 121 func SortMetrics(metrics []*types.MetricData, mfetch parser.MetricRequest) { 122 // Don't do any work if there are no globs in the metric name 123 if !strings.ContainsAny(mfetch.Metric, "*?[{") { 124 return 125 } 126 parts := strings.Split(mfetch.Metric, ".") 127 // Proceed backwards by segments, sorting once for each segment that has a glob that calls for sorting. 128 // By using a stable sort, the rightmost segments will be preserved as "sub-sorts" of any more leftward segments. 129 for i := len(parts) - 1; i >= 0; i-- { 130 if strings.ContainsAny(parts[i], "*?[{") { 131 sort.Stable(AlphabeticallyByPart(metrics, i)) 132 } 133 if strings.ContainsRune(parts[i], '{') { 134 sortByBraces(metrics, i, parts[i]) 135 } 136 } 137 }