/* 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 }