github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/benchmark/stats/curve.go (about) 1 /* 2 * 3 * Copyright 2019 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package stats 20 21 import ( 22 "crypto/sha256" 23 "encoding/csv" 24 "encoding/hex" 25 "fmt" 26 "io/ioutil" 27 "math" 28 "math/rand" 29 "os" 30 "sort" 31 "strconv" 32 ) 33 34 // payloadCurveRange represents a line within a payload curve CSV file. 35 type payloadCurveRange struct { 36 from, to int32 37 weight float64 38 } 39 40 // newPayloadCurveRange receives a line from a payload curve CSV file and 41 // returns a *payloadCurveRange if the values are acceptable. 42 func newPayloadCurveRange(line []string) (*payloadCurveRange, error) { 43 if len(line) != 3 { 44 return nil, fmt.Errorf("invalid number of entries in line %v (expected 3)", line) 45 } 46 47 var from, to int64 48 var weight float64 49 var err error 50 if from, err = strconv.ParseInt(line[0], 10, 32); err != nil { 51 return nil, err 52 } 53 if from <= 0 { 54 return nil, fmt.Errorf("line %v: field (%d) must be in (0, %d]", line, from, math.MaxInt32) 55 } 56 if to, err = strconv.ParseInt(line[1], 10, 32); err != nil { 57 return nil, err 58 } 59 if to <= 0 { 60 return nil, fmt.Errorf("line %v: field %d must be in (0, %d]", line, to, math.MaxInt32) 61 } 62 if from > to { 63 return nil, fmt.Errorf("line %v: from (%d) > to (%d)", line, from, to) 64 } 65 if weight, err = strconv.ParseFloat(line[2], 64); err != nil { 66 return nil, err 67 } 68 return &payloadCurveRange{from: int32(from), to: int32(to), weight: weight}, nil 69 } 70 71 // chooseRandom picks a payload size (in bytes) for a particular range. This is 72 // done with a uniform distribution. 73 func (pcr *payloadCurveRange) chooseRandom() int { 74 if pcr.from == pcr.to { // fast path 75 return int(pcr.from) 76 } 77 78 return int(rand.Int31n(pcr.to-pcr.from+1) + pcr.from) 79 } 80 81 // sha256file is a helper function that returns a hex string matching the 82 // SHA-256 sum of the input file. 83 func sha256file(file string) (string, error) { 84 data, err := ioutil.ReadFile(file) 85 if err != nil { 86 return "", err 87 } 88 sum := sha256.Sum256(data) 89 return hex.EncodeToString(sum[:]), nil 90 } 91 92 // PayloadCurve is an internal representation of a weighted random distribution 93 // CSV file. Once a *PayloadCurve is created with NewPayloadCurve, the 94 // ChooseRandom function should be called to generate random payload sizes. 95 type PayloadCurve struct { 96 pcrs []*payloadCurveRange 97 // Sha256 must be a public field so that the gob encoder can write it to 98 // disk. This will be needed at decode-time by the Hash function. 99 Sha256 string 100 } 101 102 // NewPayloadCurve parses a .csv file and returns a *PayloadCurve if no errors 103 // were encountered in parsing and initialization. 104 func NewPayloadCurve(file string) (*PayloadCurve, error) { 105 f, err := os.Open(file) 106 if err != nil { 107 return nil, err 108 } 109 defer f.Close() 110 111 r := csv.NewReader(f) 112 lines, err := r.ReadAll() 113 if err != nil { 114 return nil, err 115 } 116 117 ret := &PayloadCurve{} 118 var total float64 119 for _, line := range lines { 120 pcr, err := newPayloadCurveRange(line) 121 if err != nil { 122 return nil, err 123 } 124 125 ret.pcrs = append(ret.pcrs, pcr) 126 total += pcr.weight 127 } 128 129 ret.Sha256, err = sha256file(file) 130 if err != nil { 131 return nil, err 132 } 133 for _, pcr := range ret.pcrs { 134 pcr.weight /= total 135 } 136 137 sort.Slice(ret.pcrs, func(i, j int) bool { 138 if ret.pcrs[i].from == ret.pcrs[j].from { 139 return ret.pcrs[i].to < ret.pcrs[j].to 140 } 141 return ret.pcrs[i].from < ret.pcrs[j].from 142 }) 143 144 var lastTo int32 145 for _, pcr := range ret.pcrs { 146 if lastTo >= pcr.from { 147 return nil, fmt.Errorf("[%d, %d] overlaps with a different line", pcr.from, pcr.to) 148 } 149 lastTo = pcr.to 150 } 151 152 return ret, nil 153 } 154 155 // ChooseRandom picks a random payload size (in bytes) that follows the 156 // underlying weighted random distribution. 157 func (pc *PayloadCurve) ChooseRandom() int { 158 target := rand.Float64() 159 var seen float64 160 for _, pcr := range pc.pcrs { 161 seen += pcr.weight 162 if seen >= target { 163 return pcr.chooseRandom() 164 } 165 } 166 167 // This should never happen, but if it does, return a sane default. 168 return 1 169 } 170 171 // Hash returns a string uniquely identifying a payload curve file for feature 172 // matching purposes. 173 func (pc *PayloadCurve) Hash() string { 174 return pc.Sha256 175 } 176 177 // ShortHash returns a shortened version of Hash for display purposes. 178 func (pc *PayloadCurve) ShortHash() string { 179 return pc.Sha256[:8] 180 }