github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/segment/tracing/utils/lineartimefinding.go (about) 1 package utils 2 3 import ( 4 "math" 5 "math/rand" 6 "sort" 7 8 log "github.com/sirupsen/logrus" 9 ) 10 11 func FindPercentileData(arr []uint64, percentile int) uint64 { 12 if len(arr) == 0 { 13 log.Error("FindPercentileData: no duration exists") 14 return 0 15 } 16 if percentile > 100 || percentile < 0 { 17 log.Error("FindPercentileData: percentile should in this period: [0, 100]") 18 return 0 19 } 20 21 k := math.Floor(float64(percentile*(len(arr))) / float64(100)) 22 return quickSelect(arr, int(k), &rand.Rand{}) 23 } 24 25 // https://rcoh.me/posts/linear-time-median-finding/ 26 func quickSelect(arr []uint64, k int, rand *rand.Rand) uint64 { 27 if len(arr) == 1 { 28 return arr[0] 29 } 30 31 pivot := pickPivot(arr, rand) 32 33 var lows, highs, pivots []uint64 34 for _, el := range arr { 35 switch { 36 case el < pivot: 37 lows = append(lows, el) 38 case el > pivot: 39 highs = append(highs, el) 40 default: 41 pivots = append(pivots, el) 42 } 43 } 44 45 if k < len(lows) { 46 return quickSelect(lows, k, rand) 47 } else if k < len(lows)+len(pivots) { 48 return pivots[0] 49 } else { 50 return quickSelect(highs, k-len(lows)-len(pivots), rand) 51 } 52 } 53 54 func pickPivot(arr []uint64, rand *rand.Rand) uint64 { 55 if len(arr) < 5 { 56 return nLogNMedian(arr) 57 } 58 59 chunks := chunked(arr, 5) 60 var fullChunks [][]uint64 61 for _, chunk := range chunks { 62 if len(chunk) == 5 { 63 fullChunks = append(fullChunks, chunk) 64 } 65 } 66 67 var sortedGroups [][]uint64 68 for _, chunk := range fullChunks { 69 sort.Slice(chunk, func(i, j int) bool { 70 return chunk[i] < chunk[j] 71 }) 72 sortedGroups = append(sortedGroups, chunk) 73 } 74 75 medians := make([]uint64, len(sortedGroups)) 76 for i, group := range sortedGroups { 77 medians[i] = group[2] 78 } 79 80 return QuickSelectMedian(medians, rand) 81 } 82 83 func nLogNMedian(arr []uint64) uint64 { 84 85 sort.Slice(arr, func(i, j int) bool { 86 return arr[i] < arr[j] 87 }) 88 if len(arr)%2 == 1 { 89 return arr[len(arr)/2] 90 } else { 91 return (arr[len(arr)/2-1] + arr[len(arr)/2]) / 2 92 } 93 } 94 95 func QuickSelectMedian(arr []uint64, rand *rand.Rand) uint64 { 96 n := len(arr) 97 if n%2 == 1 { 98 return quickSelect(arr, n/2, rand) 99 } else { 100 return (quickSelect(arr, n/2-1, rand) + quickSelect(arr, n/2, rand)) / 2 101 } 102 } 103 104 func chunked(arr []uint64, chunkSize int) [][]uint64 { 105 numChunks := (len(arr) + chunkSize - 1) / chunkSize 106 result := make([][]uint64, numChunks) 107 108 for i := 0; i < numChunks; i++ { 109 start := i * chunkSize 110 length := chunkSize 111 if len(arr)-start < chunkSize { 112 length = len(arr) - start 113 } 114 result[i] = arr[start : start+length] 115 } 116 117 return result 118 }