├── .devcontainer
└── devcontainer.json
├── protocol.go
├── tests
├── extract
│ └── main.go
├── server
│ └── server.go
├── controller
│ └── main.go
└── client
│ └── client.go
├── .idea
├── vcs.xml
├── modules.xml
├── go-iperf.iml
└── workspace.xml
├── .gitignore
├── go.mod
├── shared.go
├── api
├── Dockerfile
├── build.sh
├── proto
│ └── control.proto
└── go
│ └── control.pb.go
├── reporter.go
├── cmd
└── main.go
├── LICENSE
├── README.md
├── reporter_windows.go
├── iperf.go
├── execute.go
├── server.go
├── controller.go
├── reporter_darwin.go
├── reporter_linux.go
├── sample.json
├── go.sum
├── report.go
└── client.go
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "image": "mcr.microsoft.com/devcontainers/go:1"
3 | }
4 |
--------------------------------------------------------------------------------
/protocol.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | type Protocol string
4 |
5 | const (
6 | PROTO_TCP = "tcp"
7 | PROTO_UDP = "udp"
8 | )
9 |
--------------------------------------------------------------------------------
/tests/extract/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "github.com/BGrewell/go-iperf"
4 |
5 | func main() {
6 | iperf.ExtractBinaries()
7 | }
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 |
17 | # Ignore the embedded directory
18 | embedded/
--------------------------------------------------------------------------------
/tests/server/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/BGrewell/go-iperf"
6 | "os"
7 | "time"
8 | )
9 |
10 | func main() {
11 | s := iperf.NewServer()
12 | err := s.Start()
13 | if err != nil {
14 | fmt.Println("failed to start server")
15 | os.Exit(-1)
16 | }
17 |
18 | for s.Running {
19 | time.Sleep(1 * time.Second)
20 | }
21 |
22 | fmt.Println("server has exited")
23 | }
24 |
--------------------------------------------------------------------------------
/.idea/go-iperf.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/BGrewell/go-iperf
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/BGrewell/go-conversions v0.0.0-20201203155646-5e189e4ca087
7 | github.com/BGrewell/tail v1.0.0
8 | github.com/fsnotify/fsnotify v1.4.9 // indirect
9 | github.com/golang/protobuf v1.5.2
10 | github.com/google/uuid v1.1.2
11 | github.com/hpcloud/tail v1.0.0 // indirect
12 | github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect
13 | google.golang.org/grpc v1.36.1
14 | google.golang.org/protobuf v1.26.0
15 | gopkg.in/fsnotify.v1 v1.4.7 // indirect
16 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
17 | )
18 |
--------------------------------------------------------------------------------
/shared.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "io"
7 | )
8 |
9 | type TestMode string
10 |
11 | const (
12 | MODE_JSON TestMode = "json"
13 | MODE_LIVE TestMode = "live"
14 | )
15 |
16 | type DebugScanner struct {
17 | Silent bool
18 | }
19 |
20 | func (ds *DebugScanner) Scan(buff io.ReadCloser) {
21 | if buff == nil {
22 | fmt.Println("unable to read, ReadCloser is nil")
23 | return
24 | }
25 | scanner := bufio.NewScanner(buff)
26 | scanner.Split(bufio.ScanWords)
27 | for scanner.Scan() {
28 | text := scanner.Text()
29 | if !ds.Silent {
30 | fmt.Println(text)
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/api/Dockerfile:
--------------------------------------------------------------------------------
1 | # Dockerfile.protogen
2 | FROM golang:latest
3 |
4 | LABEL maintainer="Benjamin Grewell "
5 |
6 | ENV PROTOC_VERSION 3.6.1
7 | ENV PROTOC_GEN_GO_VERSION v1.2.0
8 |
9 | WORKDIR /go/src/github.com/BGrewell/go-iperf/api
10 |
11 | RUN apt update
12 | RUN apt install -y protobuf-compiler python3 python3-pip
13 | RUN go get -u github.com/golang/protobuf/protoc-gen-go
14 | RUN pip3 install grpcio-tools
15 | RUN export PATH=$PATH:$GOPATH/bin
16 | RUN echo $PATH
17 |
18 | COPY proto/control.proto proto/control.proto
19 |
20 | RUN mkdir go
21 | RUN protoc -I proto/. --go_out=plugins=grpc:go control.proto
22 |
--------------------------------------------------------------------------------
/reporter.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | import (
4 | "github.com/BGrewell/tail"
5 | "time"
6 | )
7 |
8 | type Reporter struct {
9 | ReportingChannel chan *StreamIntervalReport
10 | LogFile string
11 | running bool
12 | tailer *tail.Tail
13 | }
14 |
15 | func (r *Reporter) Start() {
16 | r.running = true
17 | go r.runLogProcessor()
18 | }
19 |
20 | func (r *Reporter) Stop() {
21 | r.running = false
22 | r.tailer.Stop()
23 | r.tailer.Cleanup()
24 | for {
25 | if len(r.ReportingChannel) == 0 {
26 | break
27 | }
28 | time.Sleep(1 * time.Millisecond)
29 | }
30 | close(r.ReportingChannel)
31 | }
32 |
33 | // runLogProcessor is OS specific because of differences in iperf on Windows and Linux
34 |
--------------------------------------------------------------------------------
/api/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [ ! -d go ]; then
4 | echo "[!] Creating go output directory"
5 | mkdir go;
6 | fi
7 |
8 | echo "[+] Building docker container"
9 | docker image build -t go-iperf-builder:1.0 .
10 | docker container run --detach --name builder go-iperf-builder:1.0
11 | docker cp builder:/go/src/github.com/BGrewell/go-iperf/api/go/github.com/BGrewell/go-iperf/api/control.pb.go go/.
12 | echo "[+] Updating of go library complete"
13 |
14 | echo "[+] Removing docker container"
15 | docker rm builder
16 |
17 | echo "[+] Adding new files to source control"
18 | git add go/control.pb.go
19 | git commit -m "regenerated grpc libraries"
20 | git push
21 |
22 | echo "[+] Done. Everything has been rebuilt and the repository has been updated and pushed"
--------------------------------------------------------------------------------
/api/proto/control.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | import "google/protobuf/empty.proto";
4 |
5 | option go_package = "github.com/BGrewell/go-iperf/api";
6 |
7 | // [START java_declaration]
8 | option java_multiple_files = true;
9 | option java_package = "com.bengrewell.go-iperf.control";
10 | option java_outer_classname = "Control";
11 | // [END java_declaration]
12 |
13 | // [START csharp_declaration]
14 | option csharp_namespace = "BenGrewell.GoIperf.Control";
15 | // [END csharp_declaration]
16 |
17 | package api;
18 |
19 | service Command {
20 | rpc GrpcRequestServer(StartServerRequest) returns (StartServerResponse) {}
21 | }
22 |
23 | message StartServerRequest {
24 | }
25 |
26 | message StartServerResponse {
27 | string id = 1;
28 | int32 listen_port = 2;
29 | }
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | //"fmt"
5 | "github.com/BGrewell/go-conversions"
6 | //"github.com/BGrewell/go-iperf"
7 | //"time"
8 | "fmt"
9 | "github.com/BGrewell/go-iperf"
10 | "time"
11 | )
12 |
13 | func main() {
14 |
15 | s := iperf.NewServer()
16 | c := iperf.NewClient("127.0.0.1")
17 | c.SetIncludeServer(true)
18 | fmt.Println(s.Id)
19 | fmt.Println(c.Id)
20 |
21 | err := s.Start()
22 | if err != nil {
23 | fmt.Println(err)
24 | }
25 |
26 | err = c.Start()
27 | if err != nil {
28 | fmt.Println(err)
29 | }
30 |
31 | for c.Running {
32 | time.Sleep(1 * time.Second)
33 | }
34 |
35 | fmt.Println("stopping server")
36 | s.Stop()
37 |
38 | fmt.Printf("Client exit code: %d\n", *c.ExitCode())
39 | fmt.Printf("Server exit code: %d\n", *s.ExitCode)
40 | iperf.Cleanup()
41 | if c.Report().Error != "" {
42 | fmt.Println(c.Report().Error)
43 | } else {
44 | fmt.Printf("Recv Rate: %s\n", conversions.IntBitRateToString(int64(c.Report().End.SumReceived.BitsPerSecond)))
45 | fmt.Printf("Send Rate: %s\n", conversions.IntBitRateToString(int64(c.Report().End.SumSent.BitsPerSecond)))
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/tests/controller/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/BGrewell/go-iperf"
6 | "log"
7 | )
8 |
9 | func main() {
10 |
11 | // First create a controller for the "client" side (would be on a different computer)
12 | cc, err := iperf.NewController(6802)
13 | if err != nil {
14 | log.Fatal(err)
15 | }
16 | cc.Port = 6801 //Note: this is just a hack because we start a listener on the port when we get a new controller and in this case since we are on the same pc we would have a port conflict so we start our listener on the port+1 and then fix that after the listener has started since we won't use it anyway
17 |
18 | // Second create a controller for the "server" side (again would normally be on a different computer)
19 | sc, err := iperf.NewController(6801)
20 | if err != nil {
21 | log.Fatal(err)
22 | }
23 | fmt.Printf("[+] Server side controller listening on %d\n", sc.Port)
24 |
25 | // Test iperf
26 | iperfCli, err := cc.NewClient("127.0.0.1")
27 | if err != nil {
28 | log.Fatal(err)
29 | }
30 |
31 | err = iperfCli.Start()
32 | if err != nil {
33 | log.Fatal(err)
34 | }
35 |
36 | <- iperfCli.Done
37 |
38 | fmt.Println(iperfCli.Report().String())
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 2-Clause License
2 |
3 | Copyright (c) 2020, Ben Grewell
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/tests/client/client.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/BGrewell/go-iperf"
6 | "os"
7 | )
8 |
9 | func main() {
10 |
11 | includeServer := true
12 | proto := "tcp"
13 | runTime := 10
14 | omitSec := 0
15 | length := "65500"
16 |
17 | c := iperf.NewClient("10.254.100.100")
18 | c.SetIncludeServer(includeServer)
19 | c.SetTimeSec(runTime)
20 | c.SetOmitSec(omitSec)
21 | c.SetProto((iperf.Protocol)(proto))
22 | c.SetLength(length)
23 | c.SetJSON(false)
24 | c.SetIncludeServer(false)
25 | c.SetStreams(2)
26 | reports := c.SetModeLive()
27 |
28 | go func() {
29 | for report := range reports {
30 | fmt.Println(report.String())
31 | }
32 | }()
33 |
34 | err := c.Start()
35 | if err != nil {
36 | fmt.Println("failed to start client")
37 | os.Exit(-1)
38 | }
39 |
40 | // Method 1: Wait for the test to finish by pulling from the 'Done' channel which will block until something is put in or it's closed
41 | <-c.Done
42 |
43 | // Method 2: Poll the c.Running state and wait for it to be 'false'
44 | //for c.Running {
45 | // time.Sleep(100 * time.Millisecond)
46 | //}
47 |
48 | //if c.Report().Error != "" {
49 | // fmt.Println(c.Report().Error)
50 | //} else {
51 | // for _, entry := range c.Report().End.Streams {
52 | // fmt.Println(entry.String())
53 | // }
54 | // for _, entry := range c.Report().ServerOutputJson.End.Streams {
55 | // fmt.Println(entry.String())
56 | // }
57 | // fmt.Printf("DL Rate: %s\n", conversions.IntBitRateToString(int64(c.Report().End.SumReceived.BitsPerSecond)))
58 | // fmt.Printf("UL Rate: %s\n", conversions.IntBitRateToString(int64(c.Report().End.SumSent.BitsPerSecond)))
59 | //}
60 | }
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go-iperf
2 | A Go based wrapper around iperf3
3 |
4 | ## Basic Usage
5 |
6 | basic client setup
7 | ```go
8 | func main() {
9 |
10 | c := iperf.NewClient("192.168.0.10")
11 | c.SetJSON(true)
12 | c.SetIncludeServer(true)
13 | c.SetStreams(4)
14 | c.SetTimeSec(30)
15 | c.SetInterval(1)
16 |
17 | err := c.Start()
18 | if err != nil {
19 | fmt.Printf("failed to start client: %v\n", err)
20 | os.Exit(-1)
21 | }
22 |
23 | <- c.Done
24 |
25 | fmt.Println(c.Report().String())
26 | }
27 | ```
28 |
29 | basic server setup
30 | ```go
31 | func main() {
32 |
33 | s := iperf.NewServer()
34 | err := s.Start()
35 | if err != nil {
36 | fmt.Printf("failed to start server: %v\n", err)
37 | os.Exit(-1)
38 | }
39 |
40 | for s.Running {
41 | time.Sleep(100 * time.Millisecond)
42 | }
43 |
44 | fmt.Println("server finished")
45 | }
46 | ```
47 |
48 | client with live results printing
49 | ```go
50 | func main() {
51 |
52 | c := iperf.NewClient("192.168.0.10")
53 | c.SetJSON(true)
54 | c.SetIncludeServer(true)
55 | c.SetStreams(4)
56 | c.SetTimeSec(30)
57 | c.SetInterval(1)
58 | liveReports := c.SetModeLive()
59 |
60 | go func() {
61 | for report := range liveReports {
62 | fmt.Println(report.String())
63 | }
64 | }
65 |
66 | err := c.Start()
67 | if err != nil {
68 | fmt.Printf("failed to start client: %v\n", err)
69 | os.Exit(-1)
70 | }
71 |
72 | <- c.Done
73 |
74 | fmt.Println(c.Report().String())
75 | }
76 | ```
77 |
78 | building binary data package with iperf binaries
79 | ```
80 | go-bindata -pkg iperf -prefix "embedded/" embedded/
81 | ```
82 |
--------------------------------------------------------------------------------
/reporter_windows.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | import (
4 | "fmt"
5 | "github.com/BGrewell/go-conversions"
6 | "github.com/BGrewell/tail"
7 | "log"
8 | "strconv"
9 | "strings"
10 | "time"
11 | )
12 |
13 | func (r *Reporter) runLogProcessor() {
14 | var err error
15 | r.tailer, err = tail.TailFile(r.LogFile, tail.Config{
16 | Follow: true,
17 | ReOpen: true,
18 | Poll: true,
19 | MustExist: true,
20 | })
21 | if err != nil {
22 | log.Fatalf("failed to tail log file: %v", err)
23 | }
24 |
25 | for {
26 | select {
27 | case line := <-r.tailer.Lines:
28 | if line == nil {
29 | continue
30 | }
31 | if len(line.Text) > 5 {
32 | id := line.Text[1:4]
33 | stream, err := strconv.Atoi(strings.TrimSpace(id))
34 | if err != nil {
35 | continue
36 | }
37 | fields := strings.Fields(line.Text[5:])
38 | if len(fields) >= 6 {
39 | if fields[0] == "local" {
40 | continue
41 | }
42 | timeFields := strings.Split(fields[0], "-")
43 | start, err := strconv.ParseFloat(timeFields[0], 32)
44 | if err != nil {
45 | log.Printf("failed to convert start time: %s\n", err)
46 | }
47 | end, err := strconv.ParseFloat(timeFields[1], 32)
48 | transferedStr := fmt.Sprintf("%s%s", fields[2], fields[3])
49 | transferedBytes, err := conversions.StringBitRateToInt(transferedStr)
50 | if err != nil {
51 | log.Printf("failed to convert units: %s\n", err)
52 | }
53 | transferedBytes = transferedBytes / 8
54 | rateStr := fmt.Sprintf("%s%s", fields[4], fields[5])
55 | rate, err := conversions.StringBitRateToInt(rateStr)
56 | if err != nil {
57 | log.Printf("failed to convert units: %s\n", err)
58 | }
59 | omitted := false
60 | if len(fields) >= 7 && fields[6] == "(omitted)" {
61 | omitted = true
62 | }
63 | report := &StreamIntervalReport{
64 | Socket: stream,
65 | StartInterval: float32(start),
66 | EndInterval: float32(end),
67 | Seconds: float32(end - start),
68 | Bytes: int(transferedBytes),
69 | BitsPerSecond: float64(rate),
70 | Omitted: omitted,
71 | }
72 | r.ReportingChannel <- report
73 | }
74 | }
75 | case <-time.After(100 * time.Millisecond):
76 | if !r.running {
77 | return
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/iperf.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "log"
7 | "os"
8 | "path"
9 | "runtime"
10 | )
11 |
12 | var (
13 | Debug = false
14 | binaryDir = ""
15 | binaryLocation = ""
16 | )
17 |
18 | func init() {
19 | // Extract the binaries
20 | if runtime.GOOS == "windows" {
21 | err := extractWindowsEmbeddedBinaries()
22 | if err != nil {
23 | log.Fatalf("error initializing iperf: %v", err)
24 | }
25 | } else if runtime.GOOS == "darwin" {
26 | err := extractMacEmbeddedBinaries()
27 | if err != nil {
28 | log.Fatalf("error initializing iperf: %v\n", err)
29 | }
30 | } else {
31 | err := extractLinuxEmbeddedBinaries()
32 | if err != nil {
33 | log.Fatalf("error initializing iperf: %v", err)
34 | }
35 | }
36 | }
37 |
38 | func Cleanup() {
39 | os.RemoveAll(binaryDir)
40 | }
41 |
42 | func ExtractBinaries() (err error) {
43 | files := []string{"cygwin1.dll", "iperf3.exe", "iperf3", "iperf3.app"}
44 | err = extractEmbeddedBinaries(files)
45 | fmt.Printf("files extracted to %s\n", binaryDir)
46 | return err
47 | }
48 |
49 | func extractWindowsEmbeddedBinaries() (err error) {
50 | files := []string{"cygwin1.dll", "iperf3.exe"}
51 | err = extractEmbeddedBinaries(files)
52 | binaryLocation = path.Join(binaryDir, "iperf3.exe")
53 | return err
54 | }
55 |
56 | func extractLinuxEmbeddedBinaries() (err error) {
57 | files := []string{"iperf3"}
58 | err = extractEmbeddedBinaries(files)
59 | binaryLocation = path.Join(binaryDir, "iperf3")
60 | return err
61 | }
62 |
63 | func extractMacEmbeddedBinaries() (err error) {
64 | files := []string{"iperf3.app"}
65 | err = extractEmbeddedBinaries(files)
66 | binaryLocation = path.Join(binaryDir, "iperf3.app")
67 | return err
68 | }
69 |
70 | func extractEmbeddedBinaries(files []string) (err error) {
71 | binaryDir, err = ioutil.TempDir("", "goiperf")
72 | if err != nil {
73 | return fmt.Errorf("failed to create temporary iperf directory: %v", err)
74 | }
75 | for _, file := range files {
76 | data, err := Asset(file)
77 | if err != nil {
78 | return fmt.Errorf("failed to extract embedded iperf: %v", err)
79 | }
80 | err = ioutil.WriteFile(path.Join(binaryDir, file), data, 0755)
81 | if err != nil {
82 | return fmt.Errorf("failed to save embedded iperf: %v", err)
83 | }
84 | if Debug {
85 | log.Printf("extracted file: %s\n", path.Join(binaryDir, file))
86 | }
87 | }
88 | return nil
89 | }
90 |
--------------------------------------------------------------------------------
/execute.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | import (
4 | "context"
5 | "io"
6 | "os/exec"
7 | "strings"
8 | "syscall"
9 | )
10 |
11 | func ExecuteAsync(cmd string) (outPipe io.ReadCloser, errPipe io.ReadCloser, exitCode chan int, err error) {
12 | exitCode = make(chan int)
13 | cmdParts := strings.Fields(cmd)
14 | binary, err := exec.LookPath(cmdParts[0])
15 | if err != nil {
16 | return nil, nil, nil, err
17 | }
18 | exe := exec.Command(binary, cmdParts[1:]...)
19 | outPipe, err = exe.StdoutPipe()
20 | if err != nil {
21 | return nil, nil, nil, err
22 | }
23 | errPipe, err = exe.StderrPipe()
24 | if err != nil {
25 | return nil, nil, nil, err
26 | }
27 | err = exe.Start()
28 | if err != nil {
29 | return nil, nil, nil, err
30 | }
31 | go func() {
32 | if err := exe.Wait(); err != nil {
33 | if exiterr, ok := err.(*exec.ExitError); ok {
34 | if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
35 | exitCode <- status.ExitStatus()
36 | }
37 | }
38 | } else {
39 | exitCode <- 0
40 | }
41 | }()
42 | return outPipe, errPipe, exitCode, nil
43 | }
44 |
45 | func ExecuteAsyncWithCancel(cmd string) (stdOut io.ReadCloser, stdErr io.ReadCloser, exitCode chan int, cancelToken context.CancelFunc, pid int, err error) {
46 | return ExecuteAsyncWithCancelReadIndicator(cmd, nil)
47 | }
48 |
49 | func ExecuteAsyncWithCancelReadIndicator(cmd string, readIndicator chan interface{}) (stdOut io.ReadCloser, stdErr io.ReadCloser, exitCode chan int, cancelToken context.CancelFunc, pid int, err error) {
50 | return executeAsyncWithCancel(cmd, readIndicator)
51 | }
52 |
53 | func executeAsyncWithCancel(cmd string, readIndicator chan interface{}) (stdOut io.ReadCloser, stdErr io.ReadCloser, exitCode chan int, cancelToken context.CancelFunc, pid int, err error) {
54 | exitCode = make(chan int)
55 | ctx, cancel := context.WithCancel(context.Background())
56 | cmdParts := strings.Fields(cmd)
57 | binary, err := exec.LookPath(cmdParts[0])
58 | if err != nil {
59 | defer cancel()
60 | return nil, nil, nil, nil, -1, err
61 | }
62 | exe := exec.CommandContext(ctx, binary, cmdParts[1:]...)
63 | stdOut, err = exe.StdoutPipe()
64 | if err != nil {
65 | defer cancel()
66 | return nil, nil, nil, nil, -1, err
67 | }
68 | stdErr, err = exe.StderrPipe()
69 | if err != nil {
70 | defer cancel()
71 | return nil, nil, nil, nil, -1, err
72 | }
73 | err = exe.Start()
74 | if err != nil {
75 | defer cancel()
76 | return nil, nil, nil, nil, -1, err
77 | }
78 | go func() {
79 | // Note: Wait() will close the Stdout/Stderr and in some cases can do it before we read. In order to prevent
80 | // this we need to actually wait until the caller has finished reading.
81 | if readIndicator != nil {
82 | <- readIndicator
83 | }
84 | if err := exe.Wait(); err != nil {
85 | if exiterr, ok := err.(*exec.ExitError); ok {
86 | if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
87 | exitCode <- status.ExitStatus()
88 | }
89 | }
90 | } else {
91 | exitCode <- 0
92 | }
93 | }()
94 | return stdOut, stdErr, exitCode, cancel, exe.Process.Pid, nil
95 | }
--------------------------------------------------------------------------------
/server.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/google/uuid"
8 | "io"
9 | "strings"
10 | "time"
11 | )
12 |
13 | var (
14 | defaultPort = 5201
15 | defaultInterval = 1
16 | defaultJSON = true
17 | )
18 |
19 | func NewServer() *Server {
20 | s := &Server{
21 | Options: &ServerOptions{
22 | Port: &defaultPort,
23 | Interval: &defaultInterval,
24 | JSON: &defaultJSON,
25 | },
26 | }
27 | s.Id = uuid.New().String()
28 | return s
29 | }
30 |
31 | type ServerOptions struct {
32 | OneOff *bool `json:"one_off" yaml:"one_off" xml:"one_off"`
33 | Port *int `json:"port" yaml:"port" xml:"port"`
34 | Format *rune `json:"format" yaml:"format" xml:"format"`
35 | Interval *int `json:"interval" yaml:"interval" xml:"interval"`
36 | JSON *bool `json:"json" yaml:"json" xml:"json"`
37 | LogFile *string `json:"log_file" yaml:"log_file" xml:"log_file"`
38 | }
39 |
40 | type Server struct {
41 | Id string `json:"id" yaml:"id" xml:"id"`
42 | Running bool `json:"running" yaml:"running" xml:"running"`
43 | Options *ServerOptions `json:"-" yaml:"-" xml:"-"`
44 | ExitCode *int `json:"exit_code" yaml:"exit_code" xml:"exit_code"`
45 | Debug bool `json:"-" yaml:"-" xml:"-"`
46 | StdOut bool `json:"-" yaml:"-" xml:"-"`
47 | outputStream io.ReadCloser `json:"output_stream" yaml:"output_stream" xml:"output_stream"`
48 | errorStream io.ReadCloser `json:"error_stream" yaml:"error_stream" xml:"error_stream"`
49 | cancel context.CancelFunc `json:"cancel" yaml:"cancel" xml:"cancel"`
50 | }
51 |
52 | func (s *Server) LoadOptionsJSON(jsonStr string) (err error) {
53 | return json.Unmarshal([]byte(jsonStr), s.Options)
54 | }
55 |
56 | func (s *Server) LoadOptions(options *ServerOptions) {
57 | s.Options = options
58 | }
59 |
60 | func (s *Server) commandString() (cmd string, err error) {
61 | builder := strings.Builder{}
62 | fmt.Fprintf(&builder, "%s -s", binaryLocation)
63 |
64 | if s.Options.OneOff != nil && s.OneOff() == true {
65 | builder.WriteString(" --one-off")
66 | }
67 |
68 | if s.Options.Port != nil {
69 | fmt.Fprintf(&builder, " --port %d", s.Port())
70 | }
71 |
72 | if s.Options.Format != nil {
73 | fmt.Fprintf(&builder, " --format %c", s.Format())
74 | }
75 |
76 | if s.Options.Interval != nil {
77 | fmt.Fprintf(&builder, " --interval %d", s.Interval())
78 | }
79 |
80 | if s.Options.JSON != nil && s.JSON() == true {
81 | builder.WriteString(" --json")
82 | }
83 |
84 | if s.Options.LogFile != nil && s.LogFile() != "" {
85 | fmt.Fprintf(&builder, " --logfile %s --forceflush", s.LogFile())
86 | }
87 |
88 | return builder.String(), nil
89 | }
90 |
91 | func (s *Server) OneOff() bool {
92 | if s.Options.OneOff == nil {
93 | return false
94 | }
95 | return *s.Options.OneOff
96 | }
97 |
98 | func (s *Server) SetOneOff(oneOff bool) {
99 | s.Options.OneOff = &oneOff
100 | }
101 |
102 | func (s *Server) Port() int {
103 | if s.Options.Port == nil {
104 | return defaultPort
105 | }
106 | return *s.Options.Port
107 | }
108 |
109 | func (s *Server) SetPort(port int) {
110 | s.Options.Port = &port
111 | }
112 |
113 | func (s *Server) Format() rune {
114 | if s.Options.Format == nil {
115 | return ' '
116 | }
117 | return *s.Options.Format
118 | }
119 |
120 | func (s *Server) SetFormat(format rune) {
121 | s.Options.Format = &format
122 | }
123 |
124 | func (s *Server) Interval() int {
125 | if s.Options.Interval == nil {
126 | return defaultInterval
127 | }
128 | return *s.Options.Interval
129 | }
130 |
131 | func (s *Server) JSON() bool {
132 | if s.Options.JSON == nil {
133 | return false
134 | }
135 | return *s.Options.JSON
136 | }
137 |
138 | func (s *Server) SetJSON(json bool) {
139 | s.Options.JSON = &json
140 | }
141 |
142 | func (s *Server) LogFile() string {
143 | if s.Options.LogFile == nil {
144 | return ""
145 | }
146 | return *s.Options.LogFile
147 | }
148 |
149 | func (s *Server) SetLogFile(filename string) {
150 | s.Options.LogFile = &filename
151 | }
152 |
153 | func (s *Server) Start() (err error) {
154 | _, err = s.start()
155 | return err
156 | }
157 |
158 | func (s *Server) StartEx() (pid int, err error) {
159 | return s.start()
160 | }
161 |
162 | func (s *Server) start() (pid int, err error) {
163 | cmd, err := s.commandString()
164 | if err != nil {
165 | return -1, err
166 | }
167 | var exit chan int
168 |
169 | if s.Debug {
170 | fmt.Printf("executing command: %s\n", cmd)
171 | }
172 | s.outputStream, s.errorStream, exit, s.cancel, pid, err = ExecuteAsyncWithCancel(cmd)
173 |
174 | if err != nil {
175 | return -1, err
176 | }
177 | s.Running = true
178 |
179 | go func() {
180 | ds := DebugScanner{Silent: !s.StdOut}
181 | ds.Scan(s.outputStream)
182 | }()
183 | go func() {
184 | ds := DebugScanner{Silent: !s.Debug}
185 | ds.Scan(s.errorStream)
186 | }()
187 |
188 | go func() {
189 | exitCode := <-exit
190 | s.ExitCode = &exitCode
191 | s.Running = false
192 | }()
193 | return pid,nil
194 | }
195 |
196 | func (s *Server) Stop() {
197 | if s.Running && s.cancel != nil {
198 | s.cancel()
199 | time.Sleep(100 * time.Millisecond)
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/controller.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "net"
8 | "sync"
9 | "time"
10 |
11 | api "github.com/BGrewell/go-iperf/api/go"
12 | "google.golang.org/grpc"
13 | )
14 |
15 | func NewController(port int) (controller *Controller, err error) {
16 | c := &Controller{
17 | Port: port,
18 | clients: make(map[string]*Client),
19 | servers: make(map[string]*Server),
20 | clientLock: sync.Mutex{},
21 | serverLock: sync.Mutex{},
22 | }
23 | err = c.startListener()
24 |
25 | return c, err
26 | }
27 |
28 | // Controller is a helper in the go-iperf package that is designed to run on both the client and the server side. On the
29 | // server side it listens for new gRPC connections, when a connection is made by a client the client can tell it to
30 | // start a new iperf server instance. It will start a instance on an unused port and return the port number to the
31 | // client. This allows the entire iperf setup and session to be performed from the client side.
32 | type Controller struct {
33 | api.UnimplementedCommandServer
34 | Port int
35 | cmdClient api.CommandClient
36 | clientLock sync.Mutex
37 | serverLock sync.Mutex
38 | clients map[string]*Client
39 | servers map[string]*Server
40 | }
41 |
42 | // StartServer is the handler for the gRPC function StartServer()
43 | func (c *Controller) GrpcRequestServer(context.Context, *api.StartServerRequest) (*api.StartServerResponse, error) {
44 | srv, err := c.NewServer()
45 | srv.SetOneOff(true)
46 | if err != nil {
47 | return nil, err
48 | }
49 | err = srv.Start()
50 | if err != nil {
51 | return nil, err
52 | }
53 |
54 | c.serverLock.Lock()
55 | c.servers[srv.Id] = srv
56 | c.serverLock.Unlock()
57 |
58 | reply := &api.StartServerResponse{
59 | Id: srv.Id,
60 | ListenPort: int32(srv.Port()),
61 | }
62 |
63 | return reply, nil
64 | }
65 |
66 | // StartListener starts a command listener which is used to accept gRPC connections from another go-iperf controller
67 | func (c *Controller) startListener() (err error) {
68 |
69 | listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", "0.0.0.0", c.Port))
70 | if err != nil {
71 | return err
72 | }
73 |
74 | gs := grpc.NewServer()
75 | api.RegisterCommandServer(gs, c)
76 |
77 | go func() {
78 | err := gs.Serve(listener)
79 | if err != nil {
80 | log.Fatal(err)
81 | }
82 | }()
83 |
84 | time.Sleep(250 * time.Millisecond)
85 | return nil
86 | }
87 |
88 | // NewServer gets a new instance of an iperf server on a free port
89 | func (c *Controller) NewServer() (server *Server, err error) {
90 | freePort, err := GetUnusedTcpPort()
91 | s := NewServer()
92 | s.SetPort(freePort)
93 | c.serverLock.Lock()
94 | c.servers[s.Id] = s
95 | c.serverLock.Unlock()
96 | return s, nil
97 | }
98 |
99 | // StopServer shuts down an iperf server and frees any actively used resources
100 | func (c *Controller) StopServer(id string) (err error) {
101 | c.serverLock.Lock()
102 | delete(c.servers, id)
103 | c.serverLock.Unlock()
104 | return nil
105 | }
106 |
107 | // NewClient gets a new instance of an iperf client and also starts up a matched iperf server instance on the specified
108 | // serverAddr. If it fails to connect to the gRPC interface of the controller on the remote side it will return an error
109 | func (c *Controller) NewClient(serverAddr string) (client *Client, err error) {
110 | grpc, err := GetConnectedClient(serverAddr, c.Port)
111 | if err != nil {
112 | return nil, err
113 | }
114 |
115 | ctx, cancel := context.WithTimeout(context.Background(), 2 * time.Second)
116 | defer cancel()
117 | reply, err := grpc.GrpcRequestServer(ctx, &api.StartServerRequest{})
118 | srvPort := int(reply.ListenPort)
119 | fmt.Printf("[!] server is listening on port %d\n", srvPort)
120 |
121 | cli := NewClient(serverAddr)
122 | cli.SetPort(srvPort)
123 | c.clientLock.Lock()
124 | c.clients[cli.Id] = cli
125 | c.clientLock.Unlock()
126 |
127 | return cli, nil
128 | }
129 |
130 | // StopClient will clean up the server side connection and shut down any actively used resources
131 | func (c *Controller) StopClient(id string) (err error) {
132 | c.clientLock.Lock()
133 | delete(c.clients, id)
134 | c.clientLock.Unlock()
135 | return nil
136 | }
137 |
138 | func GetConnectedClient(addr string, port int) (client api.CommandClient, err error) {
139 | conn, err := grpc.Dial(fmt.Sprintf("%s:%d", addr, port), grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(time.Second*2))
140 | if err != nil {
141 | return nil, err
142 | }
143 | client = api.NewCommandClient(conn)
144 | return client, nil
145 | }
146 |
147 | func GetUnusedTcpPort() (int, error) {
148 | addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
149 | if err != nil {
150 | return 0, err
151 | }
152 |
153 | l, err := net.ListenTCP("tcp", addr)
154 | if err != nil {
155 | return 0, err
156 | }
157 |
158 | defer l.Close()
159 | return l.Addr().(*net.TCPAddr).Port, nil
160 |
161 | }
162 |
163 | //func GetUnusedUdpPort() (int, error) {
164 | // addr, err := net.ResolveUDPAddr("udp", "localhost:0")
165 | // if err != nil {
166 | // return 0, err
167 | // }
168 | //
169 | // l, err := net.ListenUDP("udp", addr)
170 | // if err != nil {
171 | // return 0, err
172 | // }
173 | //
174 | // defer l.Close()
175 | // return l.LocalAddr().(*net.UDPAddr).Port, nil
176 | //}
--------------------------------------------------------------------------------
/reporter_darwin.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | import (
4 | "fmt"
5 | "github.com/BGrewell/go-conversions"
6 | "github.com/BGrewell/tail"
7 | "log"
8 | "strconv"
9 | "strings"
10 | "time"
11 | )
12 |
13 | /*
14 | Connecting to host 10.254.100.100, port 5201
15 | [ 4] local 192.168.3.182 port 54104 connected to 10.254.100.100 port 5201
16 | [ ID] Interval Transfer Bandwidth Retr Cwnd
17 | [ 4] 0.00-1.00 sec 109 MBytes 913 Mbits/sec 13 634 KBytes (omitted)
18 | [ 4] 1.00-2.00 sec 110 MBytes 927 Mbits/sec 7 550 KBytes (omitted)
19 | [ 4] 2.00-3.00 sec 109 MBytes 918 Mbits/sec 6 559 KBytes (omitted)
20 | [ 4] 3.00-4.00 sec 111 MBytes 930 Mbits/sec 6 690 KBytes (omitted)
21 | [ 4] 4.00-5.00 sec 111 MBytes 933 Mbits/sec 0 803 KBytes (omitted)
22 | [ 4] 5.00-6.00 sec 111 MBytes 933 Mbits/sec 6 673 KBytes (omitted)
23 | [ 4] 6.00-7.00 sec 111 MBytes 932 Mbits/sec 6 605 KBytes (omitted)
24 | [ 4] 7.00-8.00 sec 110 MBytes 925 Mbits/sec 0 732 KBytes (omitted)
25 | [ 4] 8.00-9.00 sec 111 MBytes 932 Mbits/sec 0 840 KBytes (omitted)
26 | [ 4] 9.00-10.00 sec 110 MBytes 923 Mbits/sec 6 690 KBytes (omitted)
27 | [ 4] 0.00-1.00 sec 111 MBytes 928 Mbits/sec 6 618 KBytes
28 | [ 4] 1.00-2.00 sec 111 MBytes 931 Mbits/sec 0 745 KBytes
29 | [ 4] 2.00-3.00 sec 111 MBytes 929 Mbits/sec 11 614 KBytes
30 | [ 4] 3.00-4.00 sec 110 MBytes 922 Mbits/sec 6 551 KBytes
31 | [ 4] 4.00-5.00 sec 111 MBytes 933 Mbits/sec 6 519 KBytes
32 | [ 4] 5.00-6.00 sec 111 MBytes 928 Mbits/sec 0 663 KBytes
33 | [ 4] 6.00-7.00 sec 111 MBytes 932 Mbits/sec 0 783 KBytes
34 | [ 4] 7.00-8.00 sec 111 MBytes 933 Mbits/sec 6 656 KBytes
35 | [ 4] 8.00-9.00 sec 111 MBytes 933 Mbits/sec 6 598 KBytes
36 | [ 4] 9.00-10.00 sec 110 MBytes 925 Mbits/sec 0 728 KBytes
37 | [ 4] 10.00-11.00 sec 111 MBytes 933 Mbits/sec 0 839 KBytes
38 | [ 4] 11.00-12.00 sec 109 MBytes 918 Mbits/sec 6 680 KBytes
39 | [ 4] 12.00-12.24 sec 25.0 MBytes 888 Mbits/sec 0 711 KBytes
40 | - - - - - - - - - - - - - - - - - - - - - - - - -
41 | [ ID] Interval Transfer Bandwidth Retr
42 | [ 4] 0.00-12.24 sec 1.32 GBytes 928 Mbits/sec 47 sender
43 | [ 4] 0.00-12.24 sec 0.00 Bytes 0.00 bits/sec receiver
44 |
45 | iperf Done.
46 |
47 | */
48 | func (r *Reporter) runLogProcessor() {
49 | var err error
50 | r.tailer, err = tail.TailFile(r.LogFile, tail.Config{
51 | Follow: true,
52 | ReOpen: true,
53 | Poll: false, // on linux we don't need to poll as the fsnotify works properly
54 | MustExist: true,
55 | })
56 | if err != nil {
57 | log.Fatalf("failed to tail log file: %v", err)
58 | }
59 |
60 | for {
61 | select {
62 | case line := <-r.tailer.Lines:
63 | if line == nil {
64 | continue
65 | }
66 | if len(line.Text) > 5 {
67 | id := line.Text[1:4]
68 | stream, err := strconv.Atoi(strings.TrimSpace(id))
69 | if err != nil {
70 | continue
71 | }
72 | fields := strings.Fields(line.Text[5:])
73 | if len(fields) >= 9 {
74 | if fields[0] == "local" {
75 | continue
76 | }
77 | timeFields := strings.Split(fields[0], "-")
78 | start, err := strconv.ParseFloat(timeFields[0], 32)
79 | if err != nil {
80 | log.Printf("failed to convert start time: %s\n", err)
81 | }
82 | end, err := strconv.ParseFloat(timeFields[1], 32)
83 | transferedStr := fmt.Sprintf("%s%s", fields[2], fields[3])
84 | transferedBytes, err := conversions.StringBitRateToInt(transferedStr)
85 | if err != nil {
86 | log.Printf("failed to convert units: %s\n", err)
87 | }
88 | transferedBytes = transferedBytes / 8
89 | rateStr := fmt.Sprintf("%s%s", fields[4], fields[5])
90 | rate, err := conversions.StringBitRateToInt(rateStr)
91 | if err != nil {
92 | log.Printf("failed to convert units: %s\n", err)
93 | }
94 | retrans, err := strconv.Atoi(fields[6])
95 | if err != nil {
96 | log.Printf("failed to convert units: %s\n", err)
97 | }
98 | cwndStr := fmt.Sprintf("%s%s", fields[7], fields[8])
99 | cwnd, err := conversions.StringBitRateToInt(cwndStr)
100 | if err != nil {
101 | log.Printf("failed to convert units: %s\n", err)
102 | }
103 | cwnd = cwnd / 8
104 | omitted := false
105 | if len(fields) >= 10 && fields[9] == "(omitted)" {
106 | omitted = true
107 | }
108 | report := &StreamIntervalReport{
109 | Socket: stream,
110 | StartInterval: float32(start),
111 | EndInterval: float32(end),
112 | Seconds: float32(end - start),
113 | Bytes: int(transferedBytes),
114 | BitsPerSecond: float64(rate),
115 | Retransmissions: retrans,
116 | CongestionWindow: int(cwnd),
117 | Omitted: omitted,
118 | }
119 | r.ReportingChannel <- report
120 | }
121 | }
122 | case <-time.After(100 * time.Millisecond):
123 | if !r.running {
124 | return
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/reporter_linux.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | import (
4 | "fmt"
5 | "github.com/BGrewell/go-conversions"
6 | "github.com/BGrewell/tail"
7 | "log"
8 | "strconv"
9 | "strings"
10 | "time"
11 | )
12 |
13 | /*
14 | Connecting to host 10.254.100.100, port 5201
15 | [ 4] local 192.168.3.182 port 54104 connected to 10.254.100.100 port 5201
16 | [ ID] Interval Transfer Bandwidth Retr Cwnd
17 | [ 4] 0.00-1.00 sec 109 MBytes 913 Mbits/sec 13 634 KBytes (omitted)
18 | [ 4] 1.00-2.00 sec 110 MBytes 927 Mbits/sec 7 550 KBytes (omitted)
19 | [ 4] 2.00-3.00 sec 109 MBytes 918 Mbits/sec 6 559 KBytes (omitted)
20 | [ 4] 3.00-4.00 sec 111 MBytes 930 Mbits/sec 6 690 KBytes (omitted)
21 | [ 4] 4.00-5.00 sec 111 MBytes 933 Mbits/sec 0 803 KBytes (omitted)
22 | [ 4] 5.00-6.00 sec 111 MBytes 933 Mbits/sec 6 673 KBytes (omitted)
23 | [ 4] 6.00-7.00 sec 111 MBytes 932 Mbits/sec 6 605 KBytes (omitted)
24 | [ 4] 7.00-8.00 sec 110 MBytes 925 Mbits/sec 0 732 KBytes (omitted)
25 | [ 4] 8.00-9.00 sec 111 MBytes 932 Mbits/sec 0 840 KBytes (omitted)
26 | [ 4] 9.00-10.00 sec 110 MBytes 923 Mbits/sec 6 690 KBytes (omitted)
27 | [ 4] 0.00-1.00 sec 111 MBytes 928 Mbits/sec 6 618 KBytes
28 | [ 4] 1.00-2.00 sec 111 MBytes 931 Mbits/sec 0 745 KBytes
29 | [ 4] 2.00-3.00 sec 111 MBytes 929 Mbits/sec 11 614 KBytes
30 | [ 4] 3.00-4.00 sec 110 MBytes 922 Mbits/sec 6 551 KBytes
31 | [ 4] 4.00-5.00 sec 111 MBytes 933 Mbits/sec 6 519 KBytes
32 | [ 4] 5.00-6.00 sec 111 MBytes 928 Mbits/sec 0 663 KBytes
33 | [ 4] 6.00-7.00 sec 111 MBytes 932 Mbits/sec 0 783 KBytes
34 | [ 4] 7.00-8.00 sec 111 MBytes 933 Mbits/sec 6 656 KBytes
35 | [ 4] 8.00-9.00 sec 111 MBytes 933 Mbits/sec 6 598 KBytes
36 | [ 4] 9.00-10.00 sec 110 MBytes 925 Mbits/sec 0 728 KBytes
37 | [ 4] 10.00-11.00 sec 111 MBytes 933 Mbits/sec 0 839 KBytes
38 | [ 4] 11.00-12.00 sec 109 MBytes 918 Mbits/sec 6 680 KBytes
39 | [ 4] 12.00-12.24 sec 25.0 MBytes 888 Mbits/sec 0 711 KBytes
40 | - - - - - - - - - - - - - - - - - - - - - - - - -
41 | [ ID] Interval Transfer Bandwidth Retr
42 | [ 4] 0.00-12.24 sec 1.32 GBytes 928 Mbits/sec 47 sender
43 | [ 4] 0.00-12.24 sec 0.00 Bytes 0.00 bits/sec receiver
44 |
45 | iperf Done.
46 |
47 | */
48 | func (r *Reporter) runLogProcessor() {
49 | var err error
50 | r.tailer, err = tail.TailFile(r.LogFile, tail.Config{
51 | Follow: true,
52 | ReOpen: true,
53 | Poll: false, // on linux we don't need to poll as the fsnotify works properly
54 | MustExist: true,
55 | })
56 | if err != nil {
57 | log.Fatalf("failed to tail log file: %v", err)
58 | }
59 |
60 | for {
61 | select {
62 | case line := <-r.tailer.Lines:
63 | if line == nil {
64 | continue
65 | }
66 | if len(line.Text) > 5 {
67 | id := line.Text[1:4]
68 | stream, err := strconv.Atoi(strings.TrimSpace(id))
69 | if err != nil {
70 | continue
71 | }
72 | fields := strings.Fields(line.Text[5:])
73 | if len(fields) >= 9 {
74 | if fields[0] == "local" {
75 | continue
76 | }
77 | timeFields := strings.Split(fields[0], "-")
78 | start, err := strconv.ParseFloat(timeFields[0], 32)
79 | if err != nil {
80 | log.Printf("failed to convert start time: %s\n", err)
81 | }
82 | end, err := strconv.ParseFloat(timeFields[1], 32)
83 | transferedStr := fmt.Sprintf("%s%s", fields[2], fields[3])
84 | transferedBytes, err := conversions.StringBitRateToInt(transferedStr)
85 | if err != nil {
86 | log.Printf("failed to convert units: %s\n", err)
87 | }
88 | transferedBytes = transferedBytes / 8
89 | rateStr := fmt.Sprintf("%s%s", fields[4], fields[5])
90 | rate, err := conversions.StringBitRateToInt(rateStr)
91 | if err != nil {
92 | log.Printf("failed to convert units: %s\n", err)
93 | }
94 | retrans, err := strconv.Atoi(fields[6])
95 | if err != nil {
96 | log.Printf("failed to convert units: %s\n", err)
97 | }
98 | cwndStr := fmt.Sprintf("%s%s", fields[7], fields[8])
99 | cwnd, err := conversions.StringBitRateToInt(cwndStr)
100 | if err != nil {
101 | log.Printf("failed to convert units: %s\n", err)
102 | }
103 | cwnd = cwnd / 8
104 | omitted := false
105 | if len(fields) >= 10 && fields[9] == "(omitted)" {
106 | omitted = true
107 | }
108 | report := &StreamIntervalReport{
109 | Socket: stream,
110 | StartInterval: float32(start),
111 | EndInterval: float32(end),
112 | Seconds: float32(end - start),
113 | Bytes: int(transferedBytes),
114 | BitsPerSecond: float64(rate),
115 | Retransmissions: retrans,
116 | CongestionWindow: int(cwnd),
117 | Omitted: omitted,
118 | }
119 | r.ReportingChannel <- report
120 | }
121 | }
122 | case <-time.After(100 * time.Millisecond):
123 | if !r.running {
124 | return
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | 1614708953320
121 |
122 |
123 | 1614708953320
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | true
132 |
133 |
--------------------------------------------------------------------------------
/sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "start":{
3 | "connected":[
4 | {
5 | "socket":4,
6 | "local_host":"127.0.0.1",
7 | "local_port":64200,
8 | "remote_host":"127.0.0.1",
9 | "remote_port":5201
10 | }
11 | ],
12 | "version":"iperf 3.1.3",
13 | "system_info":"CYGWIN_NT-10.0 BGrewell-MOBL3 2.5.1(0.297/5/3) 2016-04-21 22:14 x86_64",
14 | "timestamp":{
15 | "time":"Tue, 02 Mar 2021 18:28:14 GMT",
16 | "timesecs":1614709694
17 | },
18 | "connecting_to":{
19 | "host":"127.0.0.1",
20 | "port":5201
21 | },
22 | "cookie":"BGrewell-MOBL3.1614709694.715824.0bf",
23 | "tcp_mss_default":0,
24 | "test_start":{
25 | "protocol":"TCP",
26 | "num_streams":1,
27 | "blksize":131072,
28 | "omit":0,
29 | "duration":10,
30 | "bytes":0,
31 | "blocks":0,
32 | "reverse":0
33 | }
34 | },
35 | "intervals":[
36 | {
37 | "streams":[
38 | {
39 | "socket":4,
40 | "start":0,
41 | "end":1.000554,
42 | "seconds":1.000554,
43 | "bytes":1635647488,
44 | "bits_per_second":1.307794e+10,
45 | "omitted":false
46 | }
47 | ],
48 | "sum":{
49 | "start":0,
50 | "end":1.000554,
51 | "seconds":1.000554,
52 | "bytes":1635647488,
53 | "bits_per_second":1.307794e+10,
54 | "omitted":false
55 | }
56 | },
57 | {
58 | "streams":[
59 | {
60 | "socket":4,
61 | "start":1.000554,
62 | "end":2.000056,
63 | "seconds":0.999502,
64 | "bytes":1527250944,
65 | "bits_per_second":1.222409e+10,
66 | "omitted":false
67 | }
68 | ],
69 | "sum":{
70 | "start":1.000554,
71 | "end":2.000056,
72 | "seconds":0.999502,
73 | "bytes":1527250944,
74 | "bits_per_second":1.222409e+10,
75 | "omitted":false
76 | }
77 | },
78 | {
79 | "streams":[
80 | {
81 | "socket":4,
82 | "start":2.000056,
83 | "end":3.000353,
84 | "seconds":1.000297,
85 | "bytes":1712062464,
86 | "bits_per_second":1.369244e+10,
87 | "omitted":false
88 | }
89 | ],
90 | "sum":{
91 | "start":2.000056,
92 | "end":3.000353,
93 | "seconds":1.000297,
94 | "bytes":1712062464,
95 | "bits_per_second":1.369244e+10,
96 | "omitted":false
97 | }
98 | },
99 | {
100 | "streams":[
101 | {
102 | "socket":4,
103 | "start":3.000353,
104 | "end":4.000669,
105 | "seconds":1.000316,
106 | "bytes":1730412544,
107 | "bits_per_second":1.383893e+10,
108 | "omitted":false
109 | }
110 | ],
111 | "sum":{
112 | "start":3.000353,
113 | "end":4.000669,
114 | "seconds":1.000316,
115 | "bytes":1730412544,
116 | "bits_per_second":1.383893e+10,
117 | "omitted":false
118 | }
119 | },
120 | {
121 | "streams":[
122 | {
123 | "socket":4,
124 | "start":4.000669,
125 | "end":5.000021,
126 | "seconds":0.999352,
127 | "bytes":1777467392,
128 | "bits_per_second":1.422896e+10,
129 | "omitted":false
130 | }
131 | ],
132 | "sum":{
133 | "start":4.000669,
134 | "end":5.000021,
135 | "seconds":0.999352,
136 | "bytes":1777467392,
137 | "bits_per_second":1.422896e+10,
138 | "omitted":false
139 | }
140 | },
141 | {
142 | "streams":[
143 | {
144 | "socket":4,
145 | "start":5.000021,
146 | "end":6.000122,
147 | "seconds":1.000101,
148 | "bytes":1673920512,
149 | "bits_per_second":1.339001e+10,
150 | "omitted":false
151 | }
152 | ],
153 | "sum":{
154 | "start":5.000021,
155 | "end":6.000122,
156 | "seconds":1.000101,
157 | "bytes":1673920512,
158 | "bits_per_second":1.339001e+10,
159 | "omitted":false
160 | }
161 | },
162 | {
163 | "streams":[
164 | {
165 | "socket":4,
166 | "start":6.000122,
167 | "end":7.000517,
168 | "seconds":1.000395,
169 | "bytes":1585446912,
170 | "bits_per_second":1.267857e+10,
171 | "omitted":false
172 | }
173 | ],
174 | "sum":{
175 | "start":6.000122,
176 | "end":7.000517,
177 | "seconds":1.000395,
178 | "bytes":1585446912,
179 | "bits_per_second":1.267857e+10,
180 | "omitted":false
181 | }
182 | },
183 | {
184 | "streams":[
185 | {
186 | "socket":4,
187 | "start":7.000517,
188 | "end":8.000053,
189 | "seconds":0.999536,
190 | "bytes":1642987520,
191 | "bits_per_second":1.315000e+10,
192 | "omitted":false
193 | }
194 | ],
195 | "sum":{
196 | "start":7.000517,
197 | "end":8.000053,
198 | "seconds":0.999536,
199 | "bytes":1642987520,
200 | "bits_per_second":1.315000e+10,
201 | "omitted":false
202 | }
203 | },
204 | {
205 | "streams":[
206 | {
207 | "socket":4,
208 | "start":8.000053,
209 | "end":9.000105,
210 | "seconds":1.000052,
211 | "bytes":1547173888,
212 | "bits_per_second":1.237675e+10,
213 | "omitted":false
214 | }
215 | ],
216 | "sum":{
217 | "start":8.000053,
218 | "end":9.000105,
219 | "seconds":1.000052,
220 | "bytes":1547173888,
221 | "bits_per_second":1.237675e+10,
222 | "omitted":false
223 | }
224 | },
225 | {
226 | "streams":[
227 | {
228 | "socket":4,
229 | "start":9.000105,
230 | "end":10.000386,
231 | "seconds":1.000281,
232 | "bytes":1773142016,
233 | "bits_per_second":1.418115e+10,
234 | "omitted":false
235 | }
236 | ],
237 | "sum":{
238 | "start":9.000105,
239 | "end":10.000386,
240 | "seconds":1.000281,
241 | "bytes":1773142016,
242 | "bits_per_second":1.418115e+10,
243 | "omitted":false
244 | }
245 | }
246 | ],
247 | "end":{
248 | "streams":[
249 | {
250 | "sender":{
251 | "socket":4,
252 | "start":0,
253 | "end":10.000386,
254 | "seconds":10.000386,
255 | "bytes":16605511680,
256 | "bits_per_second":1.328390e+10
257 | },
258 | "receiver":{
259 | "socket":4,
260 | "start":0,
261 | "end":10.000386,
262 | "seconds":10.000386,
263 | "bytes":16605511680,
264 | "bits_per_second":1.328390e+10
265 | }
266 | }
267 | ],
268 | "sum_sent":{
269 | "start":0,
270 | "end":10.000386,
271 | "seconds":10.000386,
272 | "bytes":16605511680,
273 | "bits_per_second":1.328390e+10
274 | },
275 | "sum_received":{
276 | "start":0,
277 | "end":10.000386,
278 | "seconds":10.000386,
279 | "bytes":16605511680,
280 | "bits_per_second":1.328390e+10
281 | },
282 | "cpu_utilization_percent":{
283 | "host_total":83.983525,
284 | "host_user":5.155306,
285 | "host_system":78.828219,
286 | "remote_total":34.722681,
287 | "remote_user":7.224950,
288 | "remote_system":27.497731
289 | }
290 | }
291 | }
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BGrewell/go-conversions v0.0.0-20201203155646-5e189e4ca087 h1:/ZR3IHtTSPqwzYQSfZYwGjrmDI5c1u2jEDDtT75Fmqw=
3 | github.com/BGrewell/go-conversions v0.0.0-20201203155646-5e189e4ca087/go.mod h1:XtXcz/MP04vhr6c6R/5gZPJZVQpbXlFsKTHr5yy/5sU=
4 | github.com/BGrewell/tail v1.0.0 h1:sG+Uvv+UApHtj5z+AWWB9i5m2SCH0RLfxYqXujYQo+Q=
5 | github.com/BGrewell/tail v1.0.0/go.mod h1:0PFYWAobUZKZLEYIxxmjFgnfvCLA600LkFbGO9KFIRA=
6 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
7 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
8 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
9 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
12 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
13 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
14 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
15 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
16 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
17 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
18 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
19 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
20 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
21 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
22 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
23 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
24 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
25 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
26 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
27 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
28 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
29 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
30 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
31 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
32 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
33 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
34 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
35 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
36 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
37 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
38 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
39 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
40 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
41 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
42 | github.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts=
43 | github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
44 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
45 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
46 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
47 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
48 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
49 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
50 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
51 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
52 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
53 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
54 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
55 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
56 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
57 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
58 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
59 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
60 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
61 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
62 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
63 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
64 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
65 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
66 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
67 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
68 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
69 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
70 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
71 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
72 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
73 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
74 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
75 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
76 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
77 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
78 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
79 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
80 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
81 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
82 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
83 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
84 | google.golang.org/grpc v1.36.1 h1:cmUfbeGKnz9+2DD/UYsMQXeqbHZqZDs4eQwW0sFOpBY=
85 | google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
86 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
87 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
88 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
89 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
90 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
91 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
92 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
93 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
94 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
95 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
96 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
97 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
98 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
99 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
100 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
101 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
102 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
103 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
104 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
105 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
106 |
--------------------------------------------------------------------------------
/report.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "io/ioutil"
7 | )
8 |
9 | type StreamInterval struct {
10 | Streams []*StreamIntervalReport `json:"streams"`
11 | Sum *StreamIntervalSumReport `json:"sum"`
12 | }
13 |
14 | func (si *StreamInterval) String() string {
15 | b, err := json.Marshal(si)
16 | if err != nil {
17 | return "error converting to json"
18 | }
19 |
20 | var pretty bytes.Buffer
21 | err = json.Indent(&pretty, b, "", " ")
22 | if err != nil {
23 | return "error converting json to indented format"
24 | }
25 |
26 | return string(pretty.Bytes())
27 | }
28 |
29 | type StreamIntervalReport struct {
30 | Socket int `json:"socket"`
31 | StartInterval float32 `json:"start"`
32 | EndInterval float32 `json:"end"`
33 | Seconds float32 `json:"seconds"`
34 | Bytes int `json:"bytes"`
35 | BitsPerSecond float64 `json:"bits_per_second"`
36 | Retransmissions int `json:"retransmissions"`
37 | CongestionWindow int `json:"congestion_window"`
38 | Omitted bool `json:"omitted"`
39 | }
40 |
41 | func (sir *StreamIntervalReport) String() string {
42 | b, err := json.Marshal(sir)
43 | if err != nil {
44 | return "error converting to json"
45 | }
46 |
47 | var pretty bytes.Buffer
48 | err = json.Indent(&pretty, b, "", " ")
49 | if err != nil {
50 | return "error converting json to indented format"
51 | }
52 |
53 | return string(pretty.Bytes())
54 | }
55 |
56 | type StreamIntervalSumReport struct {
57 | StartInterval float32 `json:"start"`
58 | EndInterval float32 `json:"end"`
59 | Seconds float32 `json:"seconds"`
60 | Bytes int `json:"bytes"`
61 | BitsPerSecond float64 `json:"bits_per_second"`
62 | Omitted bool `json:"omitted"`
63 | }
64 |
65 | func (sisr *StreamIntervalSumReport) String() string {
66 | b, err := json.Marshal(sisr)
67 | if err != nil {
68 | return "error converting to json"
69 | }
70 |
71 | var pretty bytes.Buffer
72 | err = json.Indent(&pretty, b, "", " ")
73 | if err != nil {
74 | return "error converting json to indented format"
75 | }
76 |
77 | return string(pretty.Bytes())
78 | }
79 |
80 | type StreamEndReport struct {
81 | Sender TcpStreamEndReport `json:"sender"`
82 | Receiver TcpStreamEndReport `json:"receiver"`
83 | Udp UdpStreamEndReport `json:"udp"`
84 | }
85 |
86 | func (ser *StreamEndReport) String() string {
87 | b, err := json.Marshal(ser)
88 | if err != nil {
89 | return "error converting to json"
90 | }
91 |
92 | var pretty bytes.Buffer
93 | err = json.Indent(&pretty, b, "", " ")
94 | if err != nil {
95 | return "error converting json to indented format"
96 | }
97 |
98 | return string(pretty.Bytes())
99 | }
100 |
101 | type UdpStreamEndReport struct {
102 | Socket int `json:"socket"`
103 | Start float32 `json:"start"`
104 | End float32 `json:"end"`
105 | Seconds float32 `json:"seconds"`
106 | Bytes int `json:"bytes"`
107 | BitsPerSecond float64 `json:"bits_per_second"`
108 | JitterMs float32 `json:"jitter_ms"`
109 | LostPackets int `json:"lost_packets"`
110 | Packets int `json:"packets"`
111 | LostPercent float32 `json:"lost_percent"`
112 | OutOfOrder int `json:"out_of_order"`
113 | }
114 |
115 | func (user *UdpStreamEndReport) String() string {
116 | b, err := json.Marshal(user)
117 | if err != nil {
118 | return "error converting to json"
119 | }
120 |
121 | var pretty bytes.Buffer
122 | err = json.Indent(&pretty, b, "", " ")
123 | if err != nil {
124 | return "error converting json to indented format"
125 | }
126 |
127 | return string(pretty.Bytes())
128 | }
129 |
130 | type TcpStreamEndReport struct {
131 | Socket int `json:"socket"`
132 | Start float32 `json:"start"`
133 | End float32 `json:"end"`
134 | Seconds float32 `json:"seconds"`
135 | Bytes int `json:"bytes"`
136 | BitsPerSecond float64 `json:"bits_per_second"`
137 | }
138 |
139 | func (tser *TcpStreamEndReport) String() string {
140 | b, err := json.Marshal(tser)
141 | if err != nil {
142 | return "error converting to json"
143 | }
144 |
145 | var pretty bytes.Buffer
146 | err = json.Indent(&pretty, b, "", " ")
147 | if err != nil {
148 | return "error converting json to indented format"
149 | }
150 |
151 | return string(pretty.Bytes())
152 | }
153 |
154 | type StreamEndSumReport struct {
155 | Start float32 `json:"start"`
156 | End float32 `json:"end"`
157 | Seconds float32 `json:"seconds"`
158 | Bytes int `json:"bytes"`
159 | BitsPerSecond float64 `json:"bits_per_second"`
160 | }
161 |
162 | func (sesr *StreamEndSumReport) String() string {
163 | b, err := json.Marshal(sesr)
164 | if err != nil {
165 | return "error converting to json"
166 | }
167 |
168 | var pretty bytes.Buffer
169 | err = json.Indent(&pretty, b, "", " ")
170 | if err != nil {
171 | return "error converting json to indented format"
172 | }
173 |
174 | return string(pretty.Bytes())
175 | }
176 |
177 | type CpuUtilizationReport struct {
178 | HostTotal float32 `json:"host_total"`
179 | HostUser float32 `json:"host_user"`
180 | HostSystem float32 `json:"host_system"`
181 | RemoteTotal float32 `json:"remote_total"`
182 | RemoteUser float32 `json:"remote_user"`
183 | RemoteSystem float32 `json:"remote_system"`
184 | }
185 |
186 | func (cur *CpuUtilizationReport) String() string {
187 | b, err := json.Marshal(cur)
188 | if err != nil {
189 | return "error converting to json"
190 | }
191 |
192 | var pretty bytes.Buffer
193 | err = json.Indent(&pretty, b, "", " ")
194 | if err != nil {
195 | return "error converting json to indented format"
196 | }
197 |
198 | return string(pretty.Bytes())
199 | }
200 |
201 | type ConnectionInfo struct {
202 | Socket int `json:"socket"`
203 | LocalHost string `json:"local_host"`
204 | LocalPort int `json:"local_port"`
205 | RemoteHost string `json:"remote_host"`
206 | RemotePort int `json:"remote_port"`
207 | }
208 |
209 | func (ci *ConnectionInfo) String() string {
210 | b, err := json.Marshal(ci)
211 | if err != nil {
212 | return "error converting to json"
213 | }
214 |
215 | var pretty bytes.Buffer
216 | err = json.Indent(&pretty, b, "", " ")
217 | if err != nil {
218 | return "error converting json to indented format"
219 | }
220 |
221 | return string(pretty.Bytes())
222 | }
223 |
224 | type TimestampInfo struct {
225 | Time string `json:"time"`
226 | TimeSecs int `json:"timesecs"`
227 | }
228 |
229 | func (tsi *TimestampInfo) String() string {
230 | b, err := json.Marshal(tsi)
231 | if err != nil {
232 | return "error converting to json"
233 | }
234 |
235 | var pretty bytes.Buffer
236 | err = json.Indent(&pretty, b, "", " ")
237 | if err != nil {
238 | return "error converting json to indented format"
239 | }
240 |
241 | return string(pretty.Bytes())
242 | }
243 |
244 | type ConnectingToInfo struct {
245 | Host string `json:"host"`
246 | Port int `json:"port"`
247 | }
248 |
249 | func (cti *ConnectingToInfo) String() string {
250 | b, err := json.Marshal(cti)
251 | if err != nil {
252 | return "error converting to json"
253 | }
254 |
255 | var pretty bytes.Buffer
256 | err = json.Indent(&pretty, b, "", " ")
257 | if err != nil {
258 | return "error converting json to indented format"
259 | }
260 |
261 | return string(pretty.Bytes())
262 | }
263 |
264 | type TestStartInfo struct {
265 | Protocol string `json:"protocol"`
266 | NumStreams int `json:"num_streams"`
267 | BlkSize int `json:"blksize"`
268 | Omit int `json:"omit"`
269 | Duration int `json:"duration"`
270 | Bytes int `json:"bytes"`
271 | Blocks int `json:"blocks"`
272 | Reverse int `json:"reverse"`
273 | }
274 |
275 | func (tsi *TestStartInfo) String() string {
276 | b, err := json.Marshal(tsi)
277 | if err != nil {
278 | return "error converting to json"
279 | }
280 |
281 | var pretty bytes.Buffer
282 | err = json.Indent(&pretty, b, "", " ")
283 | if err != nil {
284 | return "error converting json to indented format"
285 | }
286 |
287 | return string(pretty.Bytes())
288 | }
289 |
290 | type StartInfo struct {
291 | Connected []*ConnectionInfo `json:"connected"`
292 | Version string `json:"version"`
293 | SystemInfo string `json:"system_info"`
294 | Timestamp TimestampInfo `json:"timestamp"`
295 | ConnectingTo ConnectingToInfo `json:"connecting_to"`
296 | Cookie string `json:"cookie"`
297 | TcpMssDefault int `json:"tcp_mss_default"`
298 | TestStart TestStartInfo `json:"test_start"`
299 | }
300 |
301 | func (si *StartInfo) String() string {
302 | b, err := json.Marshal(si)
303 | if err != nil {
304 | return "error converting to json"
305 | }
306 |
307 | var pretty bytes.Buffer
308 | err = json.Indent(&pretty, b, "", " ")
309 | if err != nil {
310 | return "error converting json to indented format"
311 | }
312 |
313 | return string(pretty.Bytes())
314 | }
315 |
316 | type EndInfo struct {
317 | Streams []*StreamEndReport `json:"streams"`
318 | SumSent StreamEndSumReport `json:"sum_sent"`
319 | SumReceived StreamEndSumReport `json:"sum_received"`
320 | CpuReport CpuUtilizationReport `json:"cpu_utilization_percent"`
321 | }
322 |
323 | func (ei *EndInfo) String() string {
324 | b, err := json.Marshal(ei)
325 | if err != nil {
326 | return "error converting to json"
327 | }
328 |
329 | var pretty bytes.Buffer
330 | err = json.Indent(&pretty, b, "", " ")
331 | if err != nil {
332 | return "error converting json to indented format"
333 | }
334 |
335 | return string(pretty.Bytes())
336 | }
337 |
338 | type ServerReport struct {
339 | Start StartInfo `json:"start"`
340 | Intervals []*StreamInterval `json:"intervals"`
341 | End EndInfo `json:"end"`
342 | Error string `json:"error"`
343 | }
344 |
345 | func (sr *ServerReport) String() string {
346 | b, err := json.Marshal(sr)
347 | if err != nil {
348 | return "error converting to json"
349 | }
350 |
351 | var pretty bytes.Buffer
352 | err = json.Indent(&pretty, b, "", " ")
353 | if err != nil {
354 | return "error converting json to indented format"
355 | }
356 |
357 | return string(pretty.Bytes())
358 | }
359 |
360 | type TestReport struct {
361 | Start StartInfo `json:"start"`
362 | Intervals []*StreamInterval `json:"intervals"`
363 | End EndInfo `json:"end"`
364 | Error string `json:"error"`
365 | ServerOutputJson ServerReport `json:"server_output_json"`
366 | }
367 |
368 | func (tr *TestReport) String() string {
369 | b, err := json.Marshal(tr)
370 | if err != nil {
371 | return "error converting to json"
372 | }
373 |
374 | var pretty bytes.Buffer
375 | err = json.Indent(&pretty, b, "", " ")
376 | if err != nil {
377 | return "error converting json to indented format"
378 | }
379 |
380 | return string(pretty.Bytes())
381 | }
382 |
383 | func Loads(jsonStr string) (report *TestReport, err error) {
384 | r := &TestReport{}
385 | err = json.Unmarshal([]byte(jsonStr), r)
386 | return r, err
387 | }
388 |
389 | func Load(filename string) (report *TestReport, err error) {
390 | contents, err := ioutil.ReadFile(filename)
391 | if err != nil {
392 | return nil, err
393 | }
394 | return Loads(string(contents))
395 | }
396 |
--------------------------------------------------------------------------------
/api/go/control.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.26.0-devel
4 | // protoc v3.6.1
5 | // source: control.proto
6 |
7 | package api
8 |
9 | import (
10 | context "context"
11 | _ "github.com/golang/protobuf/ptypes/empty"
12 | grpc "google.golang.org/grpc"
13 | codes "google.golang.org/grpc/codes"
14 | status "google.golang.org/grpc/status"
15 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
16 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
17 | reflect "reflect"
18 | sync "sync"
19 | )
20 |
21 | const (
22 | // Verify that this generated code is sufficiently up-to-date.
23 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
24 | // Verify that runtime/protoimpl is sufficiently up-to-date.
25 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
26 | )
27 |
28 | type StartServerRequest struct {
29 | state protoimpl.MessageState
30 | sizeCache protoimpl.SizeCache
31 | unknownFields protoimpl.UnknownFields
32 | }
33 |
34 | func (x *StartServerRequest) Reset() {
35 | *x = StartServerRequest{}
36 | if protoimpl.UnsafeEnabled {
37 | mi := &file_control_proto_msgTypes[0]
38 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
39 | ms.StoreMessageInfo(mi)
40 | }
41 | }
42 |
43 | func (x *StartServerRequest) String() string {
44 | return protoimpl.X.MessageStringOf(x)
45 | }
46 |
47 | func (*StartServerRequest) ProtoMessage() {}
48 |
49 | func (x *StartServerRequest) ProtoReflect() protoreflect.Message {
50 | mi := &file_control_proto_msgTypes[0]
51 | if protoimpl.UnsafeEnabled && x != nil {
52 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
53 | if ms.LoadMessageInfo() == nil {
54 | ms.StoreMessageInfo(mi)
55 | }
56 | return ms
57 | }
58 | return mi.MessageOf(x)
59 | }
60 |
61 | // Deprecated: Use StartServerRequest.ProtoReflect.Descriptor instead.
62 | func (*StartServerRequest) Descriptor() ([]byte, []int) {
63 | return file_control_proto_rawDescGZIP(), []int{0}
64 | }
65 |
66 | type StartServerResponse struct {
67 | state protoimpl.MessageState
68 | sizeCache protoimpl.SizeCache
69 | unknownFields protoimpl.UnknownFields
70 |
71 | Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
72 | ListenPort int32 `protobuf:"varint,2,opt,name=listen_port,json=listenPort,proto3" json:"listen_port,omitempty"`
73 | }
74 |
75 | func (x *StartServerResponse) Reset() {
76 | *x = StartServerResponse{}
77 | if protoimpl.UnsafeEnabled {
78 | mi := &file_control_proto_msgTypes[1]
79 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
80 | ms.StoreMessageInfo(mi)
81 | }
82 | }
83 |
84 | func (x *StartServerResponse) String() string {
85 | return protoimpl.X.MessageStringOf(x)
86 | }
87 |
88 | func (*StartServerResponse) ProtoMessage() {}
89 |
90 | func (x *StartServerResponse) ProtoReflect() protoreflect.Message {
91 | mi := &file_control_proto_msgTypes[1]
92 | if protoimpl.UnsafeEnabled && x != nil {
93 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
94 | if ms.LoadMessageInfo() == nil {
95 | ms.StoreMessageInfo(mi)
96 | }
97 | return ms
98 | }
99 | return mi.MessageOf(x)
100 | }
101 |
102 | // Deprecated: Use StartServerResponse.ProtoReflect.Descriptor instead.
103 | func (*StartServerResponse) Descriptor() ([]byte, []int) {
104 | return file_control_proto_rawDescGZIP(), []int{1}
105 | }
106 |
107 | func (x *StartServerResponse) GetId() string {
108 | if x != nil {
109 | return x.Id
110 | }
111 | return ""
112 | }
113 |
114 | func (x *StartServerResponse) GetListenPort() int32 {
115 | if x != nil {
116 | return x.ListenPort
117 | }
118 | return 0
119 | }
120 |
121 | var File_control_proto protoreflect.FileDescriptor
122 |
123 | var file_control_proto_rawDesc = []byte{
124 | 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
125 | 0x03, 0x61, 0x70, 0x69, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
126 | 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74,
127 | 0x6f, 0x22, 0x14, 0x0a, 0x12, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
128 | 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x46, 0x0a, 0x13, 0x53, 0x74, 0x61, 0x72, 0x74,
129 | 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e,
130 | 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f,
131 | 0x0a, 0x0b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20,
132 | 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x32,
133 | 0x53, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x48, 0x0a, 0x11, 0x47, 0x72,
134 | 0x70, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12,
135 | 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65,
136 | 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53,
137 | 0x74, 0x61, 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
138 | 0x73, 0x65, 0x22, 0x00, 0x42, 0x6b, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x62, 0x65, 0x6e, 0x67,
139 | 0x72, 0x65, 0x77, 0x65, 0x6c, 0x6c, 0x2e, 0x67, 0x6f, 0x2d, 0x69, 0x70, 0x65, 0x72, 0x66, 0x2e,
140 | 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x42, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c,
141 | 0x50, 0x01, 0x5a, 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x42,
142 | 0x47, 0x72, 0x65, 0x77, 0x65, 0x6c, 0x6c, 0x2f, 0x67, 0x6f, 0x2d, 0x69, 0x70, 0x65, 0x72, 0x66,
143 | 0x2f, 0x61, 0x70, 0x69, 0xaa, 0x02, 0x1a, 0x42, 0x65, 0x6e, 0x47, 0x72, 0x65, 0x77, 0x65, 0x6c,
144 | 0x6c, 0x2e, 0x47, 0x6f, 0x49, 0x70, 0x65, 0x72, 0x66, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
145 | 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
146 | }
147 |
148 | var (
149 | file_control_proto_rawDescOnce sync.Once
150 | file_control_proto_rawDescData = file_control_proto_rawDesc
151 | )
152 |
153 | func file_control_proto_rawDescGZIP() []byte {
154 | file_control_proto_rawDescOnce.Do(func() {
155 | file_control_proto_rawDescData = protoimpl.X.CompressGZIP(file_control_proto_rawDescData)
156 | })
157 | return file_control_proto_rawDescData
158 | }
159 |
160 | var file_control_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
161 | var file_control_proto_goTypes = []interface{}{
162 | (*StartServerRequest)(nil), // 0: api.StartServerRequest
163 | (*StartServerResponse)(nil), // 1: api.StartServerResponse
164 | }
165 | var file_control_proto_depIdxs = []int32{
166 | 0, // 0: api.Command.GrpcRequestServer:input_type -> api.StartServerRequest
167 | 1, // 1: api.Command.GrpcRequestServer:output_type -> api.StartServerResponse
168 | 1, // [1:2] is the sub-list for method output_type
169 | 0, // [0:1] is the sub-list for method input_type
170 | 0, // [0:0] is the sub-list for extension type_name
171 | 0, // [0:0] is the sub-list for extension extendee
172 | 0, // [0:0] is the sub-list for field type_name
173 | }
174 |
175 | func init() { file_control_proto_init() }
176 | func file_control_proto_init() {
177 | if File_control_proto != nil {
178 | return
179 | }
180 | if !protoimpl.UnsafeEnabled {
181 | file_control_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
182 | switch v := v.(*StartServerRequest); i {
183 | case 0:
184 | return &v.state
185 | case 1:
186 | return &v.sizeCache
187 | case 2:
188 | return &v.unknownFields
189 | default:
190 | return nil
191 | }
192 | }
193 | file_control_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
194 | switch v := v.(*StartServerResponse); i {
195 | case 0:
196 | return &v.state
197 | case 1:
198 | return &v.sizeCache
199 | case 2:
200 | return &v.unknownFields
201 | default:
202 | return nil
203 | }
204 | }
205 | }
206 | type x struct{}
207 | out := protoimpl.TypeBuilder{
208 | File: protoimpl.DescBuilder{
209 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
210 | RawDescriptor: file_control_proto_rawDesc,
211 | NumEnums: 0,
212 | NumMessages: 2,
213 | NumExtensions: 0,
214 | NumServices: 1,
215 | },
216 | GoTypes: file_control_proto_goTypes,
217 | DependencyIndexes: file_control_proto_depIdxs,
218 | MessageInfos: file_control_proto_msgTypes,
219 | }.Build()
220 | File_control_proto = out.File
221 | file_control_proto_rawDesc = nil
222 | file_control_proto_goTypes = nil
223 | file_control_proto_depIdxs = nil
224 | }
225 |
226 | // Reference imports to suppress errors if they are not otherwise used.
227 | var _ context.Context
228 | var _ grpc.ClientConnInterface
229 |
230 | // This is a compile-time assertion to ensure that this generated file
231 | // is compatible with the grpc package it is being compiled against.
232 | const _ = grpc.SupportPackageIsVersion6
233 |
234 | // CommandClient is the client API for Command service.
235 | //
236 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
237 | type CommandClient interface {
238 | GrpcRequestServer(ctx context.Context, in *StartServerRequest, opts ...grpc.CallOption) (*StartServerResponse, error)
239 | }
240 |
241 | type commandClient struct {
242 | cc grpc.ClientConnInterface
243 | }
244 |
245 | func NewCommandClient(cc grpc.ClientConnInterface) CommandClient {
246 | return &commandClient{cc}
247 | }
248 |
249 | func (c *commandClient) GrpcRequestServer(ctx context.Context, in *StartServerRequest, opts ...grpc.CallOption) (*StartServerResponse, error) {
250 | out := new(StartServerResponse)
251 | err := c.cc.Invoke(ctx, "/api.Command/GrpcRequestServer", in, out, opts...)
252 | if err != nil {
253 | return nil, err
254 | }
255 | return out, nil
256 | }
257 |
258 | // CommandServer is the server API for Command service.
259 | type CommandServer interface {
260 | GrpcRequestServer(context.Context, *StartServerRequest) (*StartServerResponse, error)
261 | }
262 |
263 | // UnimplementedCommandServer can be embedded to have forward compatible implementations.
264 | type UnimplementedCommandServer struct {
265 | }
266 |
267 | func (*UnimplementedCommandServer) GrpcRequestServer(context.Context, *StartServerRequest) (*StartServerResponse, error) {
268 | return nil, status.Errorf(codes.Unimplemented, "method GrpcRequestServer not implemented")
269 | }
270 |
271 | func RegisterCommandServer(s *grpc.Server, srv CommandServer) {
272 | s.RegisterService(&_Command_serviceDesc, srv)
273 | }
274 |
275 | func _Command_GrpcRequestServer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
276 | in := new(StartServerRequest)
277 | if err := dec(in); err != nil {
278 | return nil, err
279 | }
280 | if interceptor == nil {
281 | return srv.(CommandServer).GrpcRequestServer(ctx, in)
282 | }
283 | info := &grpc.UnaryServerInfo{
284 | Server: srv,
285 | FullMethod: "/api.Command/GrpcRequestServer",
286 | }
287 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
288 | return srv.(CommandServer).GrpcRequestServer(ctx, req.(*StartServerRequest))
289 | }
290 | return interceptor(ctx, in, info, handler)
291 | }
292 |
293 | var _Command_serviceDesc = grpc.ServiceDesc{
294 | ServiceName: "api.Command",
295 | HandlerType: (*CommandServer)(nil),
296 | Methods: []grpc.MethodDesc{
297 | {
298 | MethodName: "GrpcRequestServer",
299 | Handler: _Command_GrpcRequestServer_Handler,
300 | },
301 | },
302 | Streams: []grpc.StreamDesc{},
303 | Metadata: "control.proto",
304 | }
305 |
--------------------------------------------------------------------------------
/client.go:
--------------------------------------------------------------------------------
1 | package iperf
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "github.com/google/uuid"
9 | "io"
10 | "io/ioutil"
11 | "log"
12 | "os"
13 | "strings"
14 | )
15 |
16 | func NewClient(host string) *Client {
17 | json := true
18 | proto := Protocol(PROTO_TCP)
19 | time := 10
20 | length := "128KB"
21 | streams := 1
22 | c := &Client{
23 | Options: &ClientOptions{
24 | JSON: &json,
25 | Proto: &proto,
26 | TimeSec: &time,
27 | Length: &length,
28 | Streams: &streams,
29 | Host: &host,
30 | },
31 | }
32 | c.Id = uuid.New().String()
33 | c.Done = make(chan bool)
34 | return c
35 | }
36 |
37 | type ClientOptions struct {
38 | Port *int `json:"port" yaml:"port" xml:"port"`
39 | Format *rune `json:"format" yaml:"format" xml:"format"`
40 | Interval *int `json:"interval" yaml:"interval" xml:"interval"`
41 | JSON *bool `json:"json" yaml:"json" xml:"json"`
42 | LogFile *string `json:"log_file" yaml:"log_file" xml:"log_file"`
43 | Host *string `json:"host" yaml:"host" xml:"host"`
44 | Proto *Protocol `json:"proto" yaml:"proto" xml:"proto"`
45 | Bandwidth *string `json:"bandwidth" yaml:"bandwidth" xml:"bandwidth"`
46 | TimeSec *int `json:"time_sec" yaml:"time_sec" xml:"time_sec"`
47 | Bytes *string `json:"bytes" yaml:"bytes" xml:"bytes"`
48 | BlockCount *string `json:"block_count" yaml:"block_count" xml:"block_count"`
49 | Length *string `json:"length" yaml:"length" xml:"length"`
50 | Streams *int `json:"streams" yaml:"streams" xml:"streams"`
51 | Reverse *bool `json:"reverse" yaml:"reverse" xml:"reverse"`
52 | Window *string `json:"window" yaml:"window" xml:"window"`
53 | MSS *int `json:"mss" yaml:"mss" xml:"mss"`
54 | NoDelay *bool `json:"no_delay" yaml:"no_delay" xml:"no_delay"`
55 | Version4 *bool `json:"version_4" yaml:"version_4" xml:"version_4"`
56 | Version6 *bool `json:"version_6" yaml:"version_6" xml:"version_6"`
57 | TOS *int `json:"tos" yaml:"tos" xml:"tos"`
58 | ZeroCopy *bool `json:"zero_copy" yaml:"zero_copy" xml:"zero_copy"`
59 | OmitSec *int `json:"omit_sec" yaml:"omit_sec" xml:"omit_sec"`
60 | Prefix *string `json:"prefix" yaml:"prefix" xml:"prefix"`
61 | IncludeServer *bool `json:"include_server" yaml:"include_server" xml:"include_server"`
62 | }
63 |
64 | type Client struct {
65 | Id string `json:"id" yaml:"id" xml:"id"`
66 | Running bool `json:"running" yaml:"running" xml:"running"`
67 | Done chan bool `json:"-" yaml:"-" xml:"-"`
68 | Options *ClientOptions `json:"options" yaml:"options" xml:"options"`
69 | Debug bool `json:"-" yaml:"-" xml:"-"`
70 | StdOut bool `json:"-" yaml:"-" xml:"-"`
71 | exitCode *int
72 | report *TestReport
73 | outputStream io.ReadCloser
74 | errorStream io.ReadCloser
75 | cancel context.CancelFunc
76 | mode TestMode
77 | live bool
78 | reportingChan chan *StreamIntervalReport
79 | reportingFile string
80 | }
81 |
82 | func (c *Client) LoadOptionsJSON(jsonStr string) (err error) {
83 | return json.Unmarshal([]byte(jsonStr), c.Options)
84 | }
85 |
86 | func (c *Client) LoadOptions(options *ClientOptions) {
87 | c.Options = options
88 | }
89 |
90 | func (c *Client) commandString() (cmd string, err error) {
91 | builder := strings.Builder{}
92 | if c.Options.Host == nil || *c.Options.Host == "" {
93 | return "", errors.New("unable to execute client. The field 'host' is required")
94 | }
95 | fmt.Fprintf(&builder, "%s -c %s", binaryLocation, c.Host())
96 |
97 | if c.Options.Port != nil {
98 | fmt.Fprintf(&builder, " -p %d", c.Port())
99 | }
100 |
101 | if c.Options.Format != nil && *c.Options.Format != ' ' {
102 | fmt.Fprintf(&builder, " -f %c", c.Format())
103 | }
104 |
105 | if c.Options.Interval != nil {
106 | fmt.Fprintf(&builder, " -i %d", c.Interval())
107 | }
108 |
109 | if c.Options.Proto != nil && *c.Options.Proto == PROTO_UDP {
110 | fmt.Fprintf(&builder, " -u")
111 | }
112 |
113 | if c.Options.Bandwidth != nil {
114 | fmt.Fprintf(&builder, " -b %s", c.Bandwidth())
115 | }
116 |
117 | if c.Options.TimeSec != nil {
118 | fmt.Fprintf(&builder, " -t %d", c.TimeSec())
119 | }
120 |
121 | if c.Options.Bytes != nil {
122 | fmt.Fprintf(&builder, " -n %s", c.Bytes())
123 | }
124 |
125 | if c.Options.BlockCount != nil {
126 | fmt.Fprintf(&builder, " -k %s", c.BlockCount())
127 | }
128 |
129 | if c.Options.Length != nil {
130 | fmt.Fprintf(&builder, " -l %s", c.Length())
131 | }
132 |
133 | if c.Options.Streams != nil {
134 | fmt.Fprintf(&builder, " -P %d", c.Streams())
135 | }
136 |
137 | if c.Options.Reverse != nil && *c.Options.Reverse {
138 | builder.WriteString(" -R")
139 | }
140 |
141 | if c.Options.Window != nil {
142 | fmt.Fprintf(&builder, " -w %s", c.Window())
143 | }
144 |
145 | if c.Options.MSS != nil {
146 | fmt.Fprintf(&builder, " -M %d", c.MSS())
147 | }
148 |
149 | if c.Options.NoDelay != nil && *c.Options.NoDelay {
150 | builder.WriteString(" -N")
151 | }
152 |
153 | if c.Options.Version6 != nil && *c.Options.Version6 {
154 | builder.WriteString(" -6")
155 | }
156 |
157 | if c.Options.TOS != nil {
158 | fmt.Fprintf(&builder, " -S %d", c.TOS())
159 | }
160 |
161 | if c.Options.ZeroCopy != nil && *c.Options.ZeroCopy {
162 | builder.WriteString(" -Z")
163 | }
164 |
165 | if c.Options.OmitSec != nil {
166 | fmt.Fprintf(&builder, " -O %d", c.OmitSec())
167 | }
168 |
169 | if c.Options.Prefix != nil {
170 | fmt.Fprintf(&builder, " -T %s", c.Prefix())
171 | }
172 |
173 | if c.Options.LogFile != nil && *c.Options.LogFile != "" {
174 | fmt.Fprintf(&builder, " --logfile %s", c.LogFile())
175 | }
176 |
177 | if c.Options.JSON != nil && *c.Options.JSON {
178 | builder.WriteString(" -J")
179 | }
180 |
181 | if c.Options.IncludeServer != nil && *c.Options.IncludeServer {
182 | builder.WriteString(" --get-server-output")
183 | }
184 |
185 | return builder.String(), nil
186 | }
187 |
188 | func (c *Client) Host() string {
189 | if c.Options.Host == nil {
190 | return ""
191 | }
192 | return *c.Options.Host
193 | }
194 |
195 | func (c *Client) SetHost(host string) {
196 | c.Options.Host = &host
197 | }
198 |
199 | func (c *Client) Port() int {
200 | if c.Options.Port == nil {
201 | return 5201
202 | }
203 | return *c.Options.Port
204 | }
205 |
206 | func (c *Client) SetPort(port int) {
207 | c.Options.Port = &port
208 | }
209 |
210 | func (c *Client) Format() rune {
211 | if c.Options.Format == nil {
212 | return ' '
213 | }
214 | return *c.Options.Format
215 | }
216 |
217 | func (c *Client) SetFormat(format rune) {
218 | c.Options.Format = &format
219 | }
220 |
221 | func (c *Client) Interval() int {
222 | if c.Options.Interval == nil {
223 | return 1
224 | }
225 | return *c.Options.Interval
226 | }
227 |
228 | func (c *Client) SetInterval(interval int) {
229 | c.Options.Interval = &interval
230 | }
231 |
232 | func (c *Client) Proto() Protocol {
233 | if c.Options.Proto == nil {
234 | return PROTO_TCP
235 | }
236 | return *c.Options.Proto
237 | }
238 |
239 | func (c *Client) SetProto(proto Protocol) {
240 | c.Options.Proto = &proto
241 | }
242 |
243 | func (c *Client) Bandwidth() string {
244 | if c.Options.Bandwidth == nil && c.Proto() == PROTO_TCP {
245 | return "0"
246 | } else if c.Options.Bandwidth == nil && c.Proto() == PROTO_UDP {
247 | return "1M"
248 | }
249 | return *c.Options.Bandwidth
250 | }
251 |
252 | func (c *Client) SetBandwidth(bandwidth string) {
253 | c.Options.Bandwidth = &bandwidth
254 | }
255 |
256 | func (c *Client) TimeSec() int {
257 | if c.Options.TimeSec == nil {
258 | return 10
259 | }
260 | return *c.Options.TimeSec
261 | }
262 |
263 | func (c *Client) SetTimeSec(timeSec int) {
264 | c.Options.TimeSec = &timeSec
265 | }
266 |
267 | func (c *Client) Bytes() string {
268 | if c.Options.Bytes == nil {
269 | return ""
270 | }
271 | return *c.Options.Bytes
272 | }
273 |
274 | func (c *Client) SetBytes(bytes string) {
275 | c.Options.Bytes = &bytes
276 | }
277 |
278 | func (c *Client) BlockCount() string {
279 | if c.Options.BlockCount == nil {
280 | return ""
281 | }
282 | return *c.Options.BlockCount
283 | }
284 |
285 | func (c *Client) SetBlockCount(blockCount string) {
286 | c.Options.BlockCount = &blockCount
287 | }
288 |
289 | func (c *Client) Length() string {
290 | if c.Options.Length == nil {
291 | if c.Proto() == PROTO_UDP {
292 | return "1460"
293 | } else {
294 | return "128K"
295 | }
296 | }
297 | return *c.Options.Length
298 | }
299 |
300 | func (c *Client) SetLength(length string) {
301 | c.Options.Length = &length
302 | }
303 |
304 | func (c *Client) Streams() int {
305 | if c.Options.Streams == nil {
306 | return 1
307 | }
308 | return *c.Options.Streams
309 | }
310 |
311 | func (c *Client) SetStreams(streamCount int) {
312 | c.Options.Streams = &streamCount
313 | }
314 |
315 | func (c *Client) Reverse() bool {
316 | if c.Options.Reverse == nil {
317 | return false
318 | }
319 | return *c.Options.Reverse
320 | }
321 |
322 | func (c *Client) SetReverse(reverse bool) {
323 | c.Options.Reverse = &reverse
324 | }
325 |
326 | func (c *Client) Window() string {
327 | if c.Options.Window == nil {
328 | return ""
329 | }
330 | return *c.Options.Window
331 | }
332 |
333 | func (c *Client) SetWindow(window string) {
334 | c.Options.Window = &window
335 | }
336 |
337 | func (c *Client) MSS() int {
338 | if c.Options.MSS == nil {
339 | return 1460
340 | }
341 | return *c.Options.MSS
342 | }
343 |
344 | func (c *Client) SetMSS(mss int) {
345 | c.Options.MSS = &mss
346 | }
347 |
348 | func (c *Client) NoDelay() bool {
349 | if c.Options.NoDelay == nil {
350 | return false
351 | }
352 | return *c.Options.NoDelay
353 | }
354 |
355 | func (c *Client) SetNoDelay(noDelay bool) {
356 | c.Options.NoDelay = &noDelay
357 | }
358 |
359 | func (c *Client) Version4() bool {
360 | if c.Options.Version6 == nil && c.Options.Version4 == nil {
361 | return true
362 | } else if c.Options.Version6 != nil && *c.Options.Version6 == true {
363 | return false
364 | }
365 | return *c.Options.Version4
366 | }
367 |
368 | func (c *Client) SetVersion4(set bool) {
369 | c.Options.Version4 = &set
370 | }
371 |
372 | func (c *Client) Version6() bool {
373 | if c.Options.Version6 == nil {
374 | return false
375 | }
376 | return *c.Options.Version6
377 | }
378 |
379 | func (c *Client) SetVersion6(set bool) {
380 | c.Options.Version6 = &set
381 | }
382 |
383 | func (c *Client) TOS() int {
384 | if c.Options.TOS == nil {
385 | return 0
386 | }
387 | return *c.Options.TOS
388 | }
389 |
390 | func (c *Client) SetTOS(value int) {
391 | c.Options.TOS = &value
392 | }
393 |
394 | func (c *Client) ZeroCopy() bool {
395 | if c.Options.ZeroCopy == nil {
396 | return false
397 | }
398 | return *c.Options.ZeroCopy
399 | }
400 |
401 | func (c *Client) SetZeroCopy(set bool) {
402 | c.Options.ZeroCopy = &set
403 | }
404 |
405 | func (c *Client) OmitSec() int {
406 | if c.Options.OmitSec == nil {
407 | return 0
408 | }
409 | return *c.Options.OmitSec
410 | }
411 |
412 | func (c *Client) SetOmitSec(value int) {
413 | c.Options.OmitSec = &value
414 | }
415 |
416 | func (c *Client) Prefix() string {
417 | if c.Options.Prefix == nil {
418 | return ""
419 | }
420 | return *c.Options.Prefix
421 | }
422 |
423 | func (c *Client) SetPrefix(prefix string) {
424 | c.Options.Prefix = &prefix
425 | }
426 |
427 | func (c *Client) LogFile() string {
428 | if c.Options.LogFile == nil {
429 | return ""
430 | }
431 | return *c.Options.LogFile
432 | }
433 |
434 | func (c *Client) SetLogFile(logfile string) {
435 | c.Options.LogFile = &logfile
436 | }
437 |
438 | func (c *Client) JSON() bool {
439 | if c.Options.JSON == nil {
440 | return false
441 | }
442 | return *c.Options.JSON
443 | }
444 |
445 | func (c *Client) SetJSON(set bool) {
446 | c.Options.JSON = &set
447 | }
448 |
449 | func (c *Client) IncludeServer() bool {
450 | if c.Options.IncludeServer == nil {
451 | return false
452 | }
453 | return *c.Options.IncludeServer
454 | }
455 |
456 | func (c *Client) SetIncludeServer(set bool) {
457 | c.Options.IncludeServer = &set
458 | }
459 |
460 | func (c *Client) ExitCode() *int {
461 | return c.exitCode
462 | }
463 |
464 | func (c *Client) Report() *TestReport {
465 | return c.report
466 | }
467 |
468 | func (c *Client) Mode() TestMode {
469 | return c.mode
470 | }
471 |
472 | func (c *Client) SetModeJson() {
473 | c.SetJSON(true)
474 | c.reportingChan = nil
475 | c.reportingFile = ""
476 | }
477 |
478 | func (c *Client) SetModeLive() <-chan *StreamIntervalReport {
479 | c.SetJSON(false) // having JSON == true will cause reporting to fail
480 | c.live = true
481 | c.reportingChan = make(chan *StreamIntervalReport, 10000)
482 | f, err := ioutil.TempFile("", "iperf_")
483 | if err != nil {
484 | log.Fatalf("failed to create logfile: %v", err)
485 | }
486 | c.reportingFile = f.Name()
487 | c.SetLogFile(c.reportingFile)
488 | return c.reportingChan
489 | }
490 |
491 | func (c *Client) Start() (err error) {
492 | _, err = c.start()
493 | return err
494 | }
495 |
496 | func (c *Client) StartEx() (pid int, err error) {
497 | return c.start()
498 | }
499 |
500 | func (c *Client) start() (pid int, err error) {
501 | read := make(chan interface{})
502 | cmd, err := c.commandString()
503 | if err != nil {
504 | return -1, err
505 | }
506 | var exit chan int
507 |
508 | if c.Debug {
509 | fmt.Printf("executing command: %s\n", cmd)
510 | }
511 | c.outputStream, c.errorStream, exit, c.cancel, pid, err = ExecuteAsyncWithCancelReadIndicator(cmd, read)
512 |
513 | if err != nil {
514 | return -1, err
515 | }
516 | c.Running = true
517 |
518 | //go func() {
519 | // ds := DebugScanner{Silent: !c.StdOut}
520 | // ds.Scan(c.outputStream)
521 | //}()
522 | go func() {
523 | ds := DebugScanner{Silent: !c.Debug}
524 | ds.Scan(c.errorStream)
525 | }()
526 |
527 | go func() {
528 | var reporter *Reporter
529 | if c.live {
530 | reporter = &Reporter{
531 | ReportingChannel: c.reportingChan,
532 | LogFile: c.reportingFile,
533 | }
534 | reporter.Start()
535 | } else {
536 | if c.Debug {
537 | fmt.Println("reading output")
538 | }
539 | testOutput, err := ioutil.ReadAll(c.outputStream)
540 | read <- true
541 | if err != nil {
542 | if c.Debug {
543 | fmt.Println(err.Error())
544 | }
545 | }
546 | if c.Debug {
547 | fmt.Println("parsing output")
548 | }
549 | c.report, err = Loads(string(testOutput))
550 | if err != nil && c.Debug {
551 | fmt.Println(err.Error())
552 | }
553 | }
554 | if c.Debug {
555 | fmt.Println("complete")
556 | }
557 | exitCode := <-exit
558 | c.exitCode = &exitCode
559 | c.Running = false
560 | c.Done <- true
561 | if reporter != nil {
562 | reporter.Stop()
563 | }
564 | }()
565 | return pid, nil
566 | }
567 |
568 | func (c *Client) Stop() {
569 | if c.Running && c.cancel != nil {
570 | c.cancel()
571 | os.Remove(c.reportingFile)
572 | }
573 | }
574 |
--------------------------------------------------------------------------------