github.com/jgbaldwinbrown/perf@v0.1.1/benchproc/sort.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package benchproc 6 7 import ( 8 "math" 9 "regexp" 10 "sort" 11 "strconv" 12 "strings" 13 ) 14 15 // Less reports whether k comes before o in the sort order implied by 16 // their projection. It panics if k and o have different Projections. 17 func (k Key) Less(o Key) bool { 18 if k.k.proj != o.k.proj { 19 panic("cannot compare Keys from different Projections") 20 } 21 return less(k.k.proj.FlattenedFields(), k.k.vals, o.k.vals) 22 } 23 24 func less(flat []*Field, a, b []string) bool { 25 // Walk the tuples in flattened order. 26 for _, node := range flat { 27 var aa, bb string 28 if node.idx < len(a) { 29 aa = a[node.idx] 30 } 31 if node.idx < len(b) { 32 bb = b[node.idx] 33 } 34 if aa != bb { 35 cmp := node.cmp(aa, bb) 36 if cmp != 0 { 37 return cmp < 0 38 } 39 // The values are equal/unordered according to 40 // the comparison function, but the strings 41 // differ. Because Keys are only == if 42 // their string representations are ==, this 43 // means we have to fall back to a secondary 44 // comparison that is only == if the strings 45 // are ==. 46 return aa < bb 47 } 48 } 49 50 // Tuples are equal. 51 return false 52 } 53 54 // SortKeys sorts a slice of Keys using Key.Less. 55 // All Keys must have the same Projection. 56 // 57 // This is equivalent to using Key.Less with the sort package but 58 // more efficient. 59 func SortKeys(keys []Key) { 60 // Check all the Projections so we don't have to do this on every 61 // comparison. 62 if len(keys) == 0 { 63 return 64 } 65 s := commonProjection(keys) 66 flat := s.FlattenedFields() 67 68 sort.Slice(keys, func(i, j int) bool { 69 return less(flat, keys[i].k.vals, keys[j].k.vals) 70 }) 71 } 72 73 // builtinOrders is the built-in comparison functions. 74 var builtinOrders = map[string]func(a, b string) int{ 75 "alpha": func(a, b string) int { 76 return strings.Compare(a, b) 77 }, 78 "num": func(a, b string) int { 79 aa, erra := parseNum(a) 80 bb, errb := parseNum(b) 81 if erra == nil && errb == nil { 82 // Sort numerically, and put NaNs after other 83 // values. 84 if aa < bb || (!math.IsNaN(aa) && math.IsNaN(bb)) { 85 return -1 86 } 87 if aa > bb || (math.IsNaN(aa) && !math.IsNaN(bb)) { 88 return 1 89 } 90 // The values are unordered. 91 return 0 92 } 93 if erra != nil && errb != nil { 94 // The values are unordered. 95 return 0 96 } 97 // Put floats before non-floats. 98 if erra == nil { 99 return -1 100 } 101 return 1 102 }, 103 } 104 105 const numPrefixes = `KMGTPEZY` 106 107 var numRe = regexp.MustCompile(`([0-9.]+)([k` + numPrefixes + `]i?)?[bB]?`) 108 109 // parseNum is a fuzzy number parser. It supports common patterns, 110 // such as SI prefixes. 111 func parseNum(x string) (float64, error) { 112 // Try parsing as a regular float. 113 v, err := strconv.ParseFloat(x, 64) 114 if err == nil { 115 return v, nil 116 } 117 118 // Try a suffixed number. 119 subs := numRe.FindStringSubmatch(x) 120 if subs != nil { 121 v, err := strconv.ParseFloat(subs[1], 64) 122 if err == nil { 123 exp := 0 124 if len(subs[2]) > 0 { 125 pre := subs[2][0] 126 if pre == 'k' { 127 pre = 'K' 128 } 129 exp = 1 + strings.IndexByte(numPrefixes, pre) 130 } 131 iec := strings.HasSuffix(subs[2], "i") 132 if iec { 133 return v * math.Pow(1024, float64(exp)), nil 134 } 135 return v * math.Pow(1000, float64(exp)), nil 136 } 137 } 138 139 return 0, strconv.ErrSyntax 140 }