1+ package iperf
2+
3+ import (
4+ "bytes"
5+ "encoding/json"
6+ "fmt"
7+ "github.com/edwarnicke/exechelper"
8+ "github.com/networkservicemesh/api/pkg/api/networkservice"
9+ "github.com/pkg/errors"
10+ "github.com/vishvananda/netns"
11+ "io"
12+ "net"
13+ "os"
14+ "strconv"
15+ "strings"
16+ "sync"
17+ )
18+
19+ type IperfResult struct {
20+ Start struct {
21+ Connected []struct {
22+ Socket int `json:"socket"`
23+ LocalHost string `json:"local_host"`
24+ LocalPort int `json:"local_port"`
25+ RemoteHost string `json:"remote_host"`
26+ RemotePort int `json:"remote_port"`
27+ } `json:"connected"`
28+ Version string `json:"version"`
29+ SystemInfo string `json:"system_info"`
30+ Timestamp struct {
31+ Time string `json:"time"`
32+ Timesecs int `json:"timesecs"`
33+ } `json:"timestamp"`
34+ ConnectingTo struct {
35+ Host string `json:"host"`
36+ Port int `json:"port"`
37+ } `json:"connecting_to"`
38+ Cookie string `json:"cookie"`
39+ TcpMssDefault int `json:"tcp_mss_default"`
40+ SockBufsize int `json:"sock_bufsize"`
41+ SndbufActual int `json:"sndbuf_actual"`
42+ RcvbufActual int `json:"rcvbuf_actual"`
43+ TestStart struct {
44+ Protocol string `json:"protocol"`
45+ NumStreams int `json:"num_streams"`
46+ Blksize int `json:"blksize"`
47+ Omit int `json:"omit"`
48+ Duration int `json:"duration"`
49+ Bytes int `json:"bytes"`
50+ Blocks int `json:"blocks"`
51+ Reverse int `json:"reverse"`
52+ Tos int `json:"tos"`
53+ } `json:"test_start"`
54+ } `json:"start"`
55+ Intervals []struct {
56+ Streams []struct {
57+ Socket int `json:"socket"`
58+ Start float64 `json:"start"`
59+ End float64 `json:"end"`
60+ Seconds float64 `json:"seconds"`
61+ Bytes int `json:"bytes"`
62+ BitsPerSecond float64 `json:"bits_per_second"`
63+ Omitted bool `json:"omitted"`
64+ Sender bool `json:"sender"`
65+ } `json:"streams"`
66+ Sum struct {
67+ Start float64 `json:"start"`
68+ End float64 `json:"end"`
69+ Seconds float64 `json:"seconds"`
70+ Bytes int `json:"bytes"`
71+ BitsPerSecond float64 `json:"bits_per_second"`
72+ Omitted bool `json:"omitted"`
73+ Sender bool `json:"sender"`
74+ } `json:"sum"`
75+ } `json:"intervals"`
76+ End struct {
77+ Streams []struct {
78+ Sender struct {
79+ Socket int `json:"socket"`
80+ Start int `json:"start"`
81+ End float64 `json:"end"`
82+ Seconds float64 `json:"seconds"`
83+ Bytes int `json:"bytes"`
84+ BitsPerSecond float64 `json:"bits_per_second"`
85+ Sender bool `json:"sender"`
86+ } `json:"sender"`
87+ Receiver struct {
88+ Socket int `json:"socket"`
89+ Start int `json:"start"`
90+ End float64 `json:"end"`
91+ Seconds float64 `json:"seconds"`
92+ Bytes int `json:"bytes"`
93+ BitsPerSecond float64 `json:"bits_per_second"`
94+ Sender bool `json:"sender"`
95+ } `json:"receiver"`
96+ } `json:"streams"`
97+ SumSent struct {
98+ Start int `json:"start"`
99+ End float64 `json:"end"`
100+ Seconds float64 `json:"seconds"`
101+ Bytes int `json:"bytes"`
102+ BitsPerSecond float64 `json:"bits_per_second"`
103+ Sender bool `json:"sender"`
104+ } `json:"sum_sent"`
105+ SumReceived struct {
106+ Start int `json:"start"`
107+ End float64 `json:"end"`
108+ Seconds float64 `json:"seconds"`
109+ Bytes int `json:"bytes"`
110+ BitsPerSecond float64 `json:"bits_per_second"`
111+ Sender bool `json:"sender"`
112+ } `json:"sum_received"`
113+ CpuUtilizationPercent struct {
114+ HostTotal float64 `json:"host_total"`
115+ HostUser float64 `json:"host_user"`
116+ HostSystem float64 `json:"host_system"`
117+ RemoteTotal float64 `json:"remote_total"`
118+ RemoteUser float64 `json:"remote_user"`
119+ RemoteSystem float64 `json:"remote_system"`
120+ } `json:"cpu_utilization_percent"`
121+ } `json:"end"`
122+ }
123+
124+ type ResultByIP struct {
125+ Ip string
126+ Result IperfResult
127+ }
128+
129+ type ResultTable struct {
130+ IperfTable map [string ][]ResultByIP `json:"iperf_table"`
131+ Mut sync.Mutex
132+ Err []string
133+ }
134+
135+ func WriteFile (clientMech , endpointMech string , cN , eN int ) error {
136+ dir := "results"
137+ _ , err := os .Stat (dir )
138+ if os .IsNotExist (err ){
139+ err := os .Mkdir (dir , 0755 )
140+ if err != nil {
141+ return err
142+ }
143+ }
144+
145+ err = writeJson (& Result , dir , clientMech , endpointMech , cN , eN )
146+ if err != nil {
147+ return err
148+ }
149+
150+ err = writeCSV (& Result , dir , clientMech , endpointMech , cN , eN )
151+ if err != nil {
152+ return err
153+ }
154+
155+ //err = writeServerError(dir, clientMech, endpointMech, cN, eN)
156+ //if err != nil {
157+ // return err
158+ //}
159+
160+ //err = writeServerOutput(dir, clientMech, endpointMech, cN, eN)
161+ //if err != nil {
162+ // return err
163+ //}
164+
165+ return nil
166+ }
167+
168+ // write full result json, just in case
169+ func writeJson (table * ResultTable , dir , clientMech , endpointMech string , cN , eN int ) error {
170+ filename := fmt .Sprintf ("%v/%v_to_%v_%v_to_%v.json" , dir , clientMech , endpointMech , cN , eN )
171+ file , err := os .OpenFile (filename , os .O_APPEND | os .O_CREATE | os .O_WRONLY , os .ModePerm )
172+ if err != nil {
173+ return err
174+ }
175+
176+ b , err := json .Marshal (table .IperfTable )
177+ if err != nil {
178+ return err
179+ }
180+
181+ _ , err = file .Write (b )
182+ if err != nil {
183+ return err
184+ }
185+
186+ return nil
187+ }
188+
189+ // write result in csv processed for plotting in gnuplot
190+ func writeCSV (table * ResultTable , dir , clientMech , endpointMech string , cN , eN int ) error {
191+ filename := fmt .Sprintf ("%v/%v_to_%v_%v_to_%v.csv" , dir , clientMech , endpointMech , cN , eN )
192+ file , err := os .OpenFile (filename , os .O_APPEND | os .O_CREATE | os .O_WRONLY , os .ModePerm )
193+ if err != nil {
194+ return err
195+ }
196+ var columns [][]string
197+ for key , val := range table .IperfTable {
198+ for _ , v := range val {
199+ var sums []string
200+ for _ , i := range v .Result .Intervals {
201+ sums = append (sums , strconv .Itoa (i .Sum .Bytes / (1024 * 1024 )))
202+ }
203+ columns = append (columns , append ([]string {key + "(" + v .Ip + ")" }, sums ... ))
204+ }
205+ }
206+ if len (columns ) == 0 {
207+ return errors .New ("empty result" )
208+ }
209+
210+ var rows = make ([][]string , len (columns [0 ]))
211+ for i , col := range columns {
212+ for j := range col {
213+ if len (rows [j ]) == 0 {
214+ rows [j ] = make ([]string , len (columns [0 ]))
215+ }
216+ rows [j ][i ] = columns [i ][j ]
217+ }
218+ }
219+
220+ str := strings.Builder {}
221+ for i , r := range rows {
222+ str .WriteString (strconv .Itoa (i - 1 ) + "," + strings .Join (r , "," ) + "\n " )
223+ }
224+
225+ _ , err = file .WriteString (str .String ())
226+ if err != nil {
227+ return err
228+ }
229+
230+ return nil
231+ }
232+
233+ var Result = ResultTable {IperfTable : map [string ][]ResultByIP {}, Err : []string {}}
234+ func Cmd (ipnet * net.IPNet , clientHandle netns.NsHandle , conn * networkservice.Connection ) error {
235+ if ipnet == nil {
236+ return nil
237+ }
238+
239+ // start client
240+ var buff bytes.Buffer
241+ var errBuff strings.Builder
242+ iperfStr := fmt .Sprintf ("iperf3 -t 60 -M 1400 -J -c %s" , ipnet .IP .String ())
243+ if err := exechelper .Run (iperfStr ,
244+ exechelper .WithEnvirons (os .Environ ()... ),
245+ exechelper .WithStdout (io .MultiWriter (os .Stdout , & buff )),
246+ exechelper .WithStderr (io .MultiWriter (os .Stderr , & errBuff )),
247+ exechelper .WithNetNS (clientHandle ),
248+ ); err != nil {
249+ return errors .Wrapf (err , "failed to measure throughput with command %q" , iperfStr )
250+ }
251+
252+ var res IperfResult
253+ err := json .Unmarshal (buff .Bytes (), & res )
254+ if err != nil {
255+ return err
256+ }
257+
258+ Result .Mut .Lock ()
259+ Result .IperfTable [conn .Id ] = append (Result .IperfTable [conn .Id ], ResultByIP {Result : res , Ip : ipnet .IP .String ()})
260+ Result .Err = append (Result .Err , errBuff .String ())
261+ Result .Mut .Unlock ()
262+
263+ return nil
264+ }
265+
266+ func StartServer (ipnet * net.IPNet , endpointHandle netns.NsHandle ) error {
267+ iperfSrvStr := fmt .Sprintf ("iperf3 -s --bind %s" , ipnet .IP .String ())
268+ err := exechelper .Run (iperfSrvStr ,
269+ exechelper .WithEnvirons (os .Environ ()... ),
270+ exechelper .WithStdout (bytes .NewBuffer ([]byte {})),
271+ exechelper .WithStderr (bytes .NewBuffer ([]byte {})),
272+ exechelper .WithNetNS (endpointHandle ))
273+ if err != nil {
274+ return err
275+ }
276+
277+ return nil
278+ }
279+
280+ func writeServerError (dir , clientMech , endpointMech string , cN , eN int ) error {
281+ filename := fmt .Sprintf ("%v/errors_%v_to_%v_%v_to_%v.txt" , dir , clientMech , endpointMech , cN , eN )
282+ file , err := os .OpenFile (filename , os .O_APPEND | os .O_CREATE | os .O_WRONLY , os .ModePerm )
283+ if err != nil {
284+ return err
285+ }
286+
287+ _ , err = file .WriteString (strings .Join (Result .Err , ";" ))
288+ if err != nil {
289+ return err
290+ }
291+
292+ //for k,v := range srvr.serverErrors {
293+ // _, err = file.WriteString(k + "\n" + strings.Join(v, "\n"))
294+ // _, err = file.WriteString(strings.Repeat("|", 30))
295+ // if err != nil {
296+ // continue
297+ // }
298+ //}
299+
300+ return nil
301+ }
302+
303+ func writeServerOutput (dir , clientMech , endpointMech string , cN , eN int ) error {
304+ filename := fmt .Sprintf ("%v/server_output_%v_to_%v_%v_to_%v.txt" , dir , clientMech , endpointMech , cN , eN )
305+ file , err := os .OpenFile (filename , os .O_APPEND | os .O_CREATE | os .O_WRONLY , os .ModePerm )
306+ if err != nil {
307+ return err
308+ }
309+
310+ if len (srvr .serverOutput ) == 0 {
311+ return nil
312+ }
313+
314+ for k ,v := range srvr .serverOutput {
315+ _ , err = file .WriteString (k + "\n " + strings .Join (v , "\n " ))
316+ _ , err = file .WriteString (strings .Repeat ("|" , 30 ))
317+ if err != nil {
318+ continue
319+ }
320+ }
321+
322+ return nil
323+ }
324+
325+ var srvr = & srv {
326+ serverOutput : map [string ][]string {},
327+ serverErrors : map [string ][]string {},
328+ }
329+ type srv struct {
330+ serverErrors map [string ][]string
331+ serverOutput map [string ][]string
332+ mut sync.Mutex
333+ }
0 commit comments