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  }