kubernetes/test/e2e/network/util_iperf.go
2021-06-23 22:56:48 +08:00

171 lines
5.6 KiB
Go

/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package network
// Tests network performance using iperf or other containers.
import (
"bytes"
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
"k8s.io/kubernetes/test/e2e/framework"
)
const (
megabyte = 1024 * 1024
)
// IPerfResults is a struct that stores some IPerfCSVResult
type IPerfResults struct {
BandwidthMap map[string]int64
}
// IPerfCSVResult struct modelling an iperf record....
// 20160314154239,172.17.0.3,34152,172.17.0.2,5001,3,0.0-10.0,33843707904,27074774092
type IPerfCSVResult struct {
date string // field 1 in the csv
cli string // field 2 in the csv
cliPort int64 // ...
server string
servPort int64
id string
interval string
transferBits int64
bandwidthBits int64
}
func (i *IPerfCSVResult) bandwidthMB() int64 {
return int64(math.Round(float64(i.bandwidthBits) / float64(megabyte) / 8))
}
// Add adds a new result to the Results struct.
func (i *IPerfResults) Add(ipr *IPerfCSVResult) {
if i.BandwidthMap == nil {
i.BandwidthMap = map[string]int64{}
}
i.BandwidthMap[ipr.cli] = ipr.bandwidthBits
}
// ToTSV exports an easily readable tab delimited format of all IPerfResults.
func (i *IPerfResults) ToTSV() string {
if len(i.BandwidthMap) < 1 {
framework.Logf("Warning: no data in bandwidth map")
}
var buffer bytes.Buffer
for node, bandwidth := range i.BandwidthMap {
asJSON, _ := json.Marshal(node)
buffer.WriteString("\t " + string(asJSON) + "\t " + fmt.Sprintf("%E", float64(bandwidth)))
}
return buffer.String()
}
// NewIPerf parses an IPerf CSV output line into an IPerfCSVResult.
func NewIPerf(csvLine string) (*IPerfCSVResult, error) {
if len(csvLine) == 0 {
return nil, fmt.Errorf("No iperf output received in csv line")
}
csvLine = strings.Trim(csvLine, "\n")
slice := StrSlice(strings.Split(csvLine, ","))
if len(slice) != 9 {
return nil, fmt.Errorf("Incorrect fields in the output: %v (%v out of 9)", slice, len(slice))
}
i := IPerfCSVResult{}
i.date = slice.get(0)
i.cli = slice.get(1)
i.cliPort = intOrFail("client port", slice.get(2))
i.server = slice.get(3)
i.servPort = intOrFail("server port", slice.get(4))
i.id = slice.get(5)
i.interval = slice.get(6)
i.transferBits = intOrFail("transfer port", slice.get(7))
i.bandwidthBits = intOrFail("bandwidth port", slice.get(8))
return &i, nil
}
// StrSlice represents a string slice
type StrSlice []string
func (s StrSlice) get(i int) string {
if i >= 0 && i < len(s) {
return s[i]
}
return ""
}
// intOrFail is a convenience function for parsing integers.
func intOrFail(debugName string, rawValue string) int64 {
value, err := strconv.ParseInt(rawValue, 10, 64)
if err != nil {
framework.Failf("Failed parsing value %v from the string '%v' as an integer", debugName, rawValue)
}
return value
}
// IPerf2EnhancedCSVResults models the results produced by iperf2 when run with the -e (--enhancedreports) flag.
type IPerf2EnhancedCSVResults struct {
Intervals []*IPerfCSVResult
Total *IPerfCSVResult
}
// ParseIPerf2EnhancedResultsFromCSV parses results from iperf2 when given the -e (--enhancedreports)
// and `--reportstyle C` options.
// Example output:
// 20201210141800.884,10.244.2.24,47880,10.96.114.79,6789,3,0.0-1.0,1677852672,13422821376
// 20201210141801.881,10.244.2.24,47880,10.96.114.79,6789,3,1.0-2.0,1980760064,15846080512
// 20201210141802.883,10.244.2.24,47880,10.96.114.79,6789,3,2.0-3.0,1886650368,15093202944
// 20201210141803.882,10.244.2.24,47880,10.96.114.79,6789,3,3.0-4.0,2035417088,16283336704
// 20201210141804.879,10.244.2.24,47880,10.96.114.79,6789,3,4.0-5.0,1922957312,15383658496
// 20201210141805.881,10.244.2.24,47880,10.96.114.79,6789,3,5.0-6.0,2095316992,16762535936
// 20201210141806.882,10.244.2.24,47880,10.96.114.79,6789,3,6.0-7.0,1741291520,13930332160
// 20201210141807.879,10.244.2.24,47880,10.96.114.79,6789,3,7.0-8.0,1862926336,14903410688
// 20201210141808.878,10.244.2.24,47880,10.96.114.79,6789,3,8.0-9.0,1821245440,14569963520
// 20201210141809.849,10.244.2.24,47880,10.96.114.79,6789,3,0.0-10.0,18752208896,15052492511
func ParseIPerf2EnhancedResultsFromCSV(output string) (*IPerf2EnhancedCSVResults, error) {
var parsedResults []*IPerfCSVResult
for _, line := range strings.Split(output, "\n") {
parsed, err := NewIPerf(line)
if err != nil {
return nil, err
}
parsedResults = append(parsedResults, parsed)
}
if parsedResults == nil || len(parsedResults) == 0 {
return nil, fmt.Errorf("no results parsed from iperf2 output")
}
// format:
// all but last lines are intervals
intervals := parsedResults[:len(parsedResults)-1]
// last line is an aggregation
total := parsedResults[len(parsedResults)-1]
return &IPerf2EnhancedCSVResults{
Intervals: intervals,
Total: total,
}, nil
}
// IPerf2NodeToNodeCSVResults models the results of running iperf2 between a daemonset of clients and
// a single server. The node name of the server is captured, along with a map of client node name
// to iperf2 results.
type IPerf2NodeToNodeCSVResults struct {
ServerNode string
Results map[string]*IPerf2EnhancedCSVResults
}