gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/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  }