initial working version
parent
e8dc386ba0
commit
10cf19535e
@ -1,43 +1,491 @@
|
|||||||
package iperf
|
package iperf
|
||||||
|
|
||||||
import "github.com/google/uuid"
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
func NewClient() *Client {
|
func NewClient(host string) *Client {
|
||||||
json := true
|
json := true
|
||||||
proto := Protocol(PROTO_TCP)
|
proto := Protocol(PROTO_TCP)
|
||||||
time := 10
|
time := 10
|
||||||
length := "128KB"
|
length := "128KB"
|
||||||
streams := 1
|
streams := 1
|
||||||
c := &Client{
|
c := &Client{
|
||||||
JSON: &json,
|
options: &ClientOptions{
|
||||||
Proto: &proto,
|
JSON: &json,
|
||||||
TimeSec: &time,
|
Proto: &proto,
|
||||||
Length: &length,
|
TimeSec: &time,
|
||||||
Streams: &streams,
|
Length: &length,
|
||||||
|
Streams: &streams,
|
||||||
|
Host: &host,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
c.Id = uuid.New().String()
|
c.Id = uuid.New().String()
|
||||||
|
c.Done = make(chan bool)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type ClientOptions struct {
|
||||||
SharedOptions
|
SharedOptions
|
||||||
Host string
|
Host *string
|
||||||
Proto *Protocol
|
Proto *Protocol
|
||||||
Bandwidth *string
|
Bandwidth *string
|
||||||
TimeSec *int
|
TimeSec *int
|
||||||
Bytes *string
|
Bytes *string
|
||||||
BlockCount *string
|
BlockCount *string
|
||||||
Length *string
|
Length *string
|
||||||
Streams *int
|
Streams *int
|
||||||
Reverse *bool
|
Reverse *bool
|
||||||
Window *string
|
Window *string
|
||||||
MSS *int
|
MSS *int
|
||||||
NoDelay *bool
|
NoDelay *bool
|
||||||
Version4 *bool
|
Version4 *bool
|
||||||
Version6 *bool
|
Version6 *bool
|
||||||
TOS *int
|
TOS *int
|
||||||
ZeroCopy *bool
|
ZeroCopy *bool
|
||||||
OmitSec *int
|
OmitSec *int
|
||||||
Prefix *string
|
Prefix *string
|
||||||
JSON *bool
|
JSON *bool
|
||||||
|
LogFile *string
|
||||||
|
IncludeServer *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
Id string
|
||||||
|
Running bool
|
||||||
|
Done chan bool
|
||||||
|
options *ClientOptions
|
||||||
|
exitCode *int
|
||||||
|
report *TestReport
|
||||||
|
outputStream io.ReadCloser
|
||||||
|
errorStream io.ReadCloser
|
||||||
|
cancel context.CancelFunc
|
||||||
|
mode TestMode
|
||||||
|
live bool
|
||||||
|
reportingChan chan *StreamIntervalReport
|
||||||
|
reportingFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) LoadOptionsJSON(jsonStr string) (err error) {
|
||||||
|
return json.Unmarshal([]byte(jsonStr), c.options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) commandString() (cmd string, err error) {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
if c.options.Host == nil || *c.options.Host == "" {
|
||||||
|
return "", errors.New("unable to execute client. The field 'host' is required")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&builder, "%s -c %s", binaryLocation, c.Host())
|
||||||
|
|
||||||
|
if c.options.Proto != nil && *c.options.Proto == PROTO_UDP {
|
||||||
|
fmt.Fprintf(&builder, " -u")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.Bandwidth != nil {
|
||||||
|
fmt.Fprintf(&builder, " -b %s", c.Bandwidth())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.TimeSec != nil {
|
||||||
|
fmt.Fprintf(&builder, " -t %d", c.TimeSec())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.Bytes != nil {
|
||||||
|
fmt.Fprintf(&builder, " -n %s", c.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.BlockCount != nil {
|
||||||
|
fmt.Fprintf(&builder, " -k %s", c.BlockCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.Length != nil {
|
||||||
|
fmt.Fprintf(&builder, " -l %s", c.Length())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.Streams != nil {
|
||||||
|
fmt.Fprintf(&builder, " -P %d", c.Streams())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.Reverse != nil && *c.options.Reverse {
|
||||||
|
builder.WriteString(" -R")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.Window != nil {
|
||||||
|
fmt.Fprintf(&builder, " -w %s", c.Window())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.MSS != nil {
|
||||||
|
fmt.Fprintf(&builder, " -M %d", c.MSS())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.NoDelay != nil && *c.options.NoDelay {
|
||||||
|
builder.WriteString(" -N")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.Version6 != nil && *c.options.Version6 {
|
||||||
|
builder.WriteString(" -6")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.TOS != nil {
|
||||||
|
fmt.Fprintf(&builder, " -S %d", c.TOS())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.ZeroCopy != nil && *c.options.ZeroCopy {
|
||||||
|
builder.WriteString(" -Z")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.OmitSec != nil {
|
||||||
|
fmt.Fprintf(&builder, " -O %d", c.OmitSec())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.Prefix != nil {
|
||||||
|
fmt.Fprintf(&builder, " -T %s", c.Prefix())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.LogFile != nil && *c.options.LogFile != "" {
|
||||||
|
fmt.Fprintf(&builder, " --logfile %s", c.LogFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.JSON != nil && *c.options.JSON {
|
||||||
|
builder.WriteString(" -J")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.IncludeServer != nil && *c.options.IncludeServer {
|
||||||
|
builder.WriteString(" --get-server-output")
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Host() string {
|
||||||
|
if c.options.Host == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *c.options.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetHost(host string) {
|
||||||
|
c.options.Host = &host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Proto() Protocol {
|
||||||
|
if c.options.Proto == nil {
|
||||||
|
return PROTO_TCP
|
||||||
|
}
|
||||||
|
return *c.options.Proto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetProto(proto Protocol) {
|
||||||
|
c.options.Proto = &proto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Bandwidth() string {
|
||||||
|
if c.options.Bandwidth == nil && c.Proto() == PROTO_TCP {
|
||||||
|
return "0"
|
||||||
|
} else if c.options.Bandwidth == nil && c.Proto() == PROTO_UDP {
|
||||||
|
return "1M"
|
||||||
|
}
|
||||||
|
return *c.options.Bandwidth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetBandwidth(bandwidth string) {
|
||||||
|
c.options.Bandwidth = &bandwidth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TimeSec() int {
|
||||||
|
if c.options.TimeSec == nil {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
return *c.options.TimeSec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetTimeSec(timeSec int) {
|
||||||
|
c.options.TimeSec = &timeSec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Bytes() string {
|
||||||
|
if c.options.Bytes == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *c.options.Bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetBytes(bytes string) {
|
||||||
|
c.options.Bytes = &bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) BlockCount() string {
|
||||||
|
if c.options.BlockCount == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *c.options.BlockCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetBlockCount(blockCount string) {
|
||||||
|
c.options.BlockCount = &blockCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Length() string {
|
||||||
|
if c.options.Length == nil {
|
||||||
|
if c.Proto() == PROTO_UDP {
|
||||||
|
return "1460"
|
||||||
|
} else {
|
||||||
|
return "128K"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *c.options.Length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetLength(length string) {
|
||||||
|
c.options.Length = &length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Streams() int {
|
||||||
|
if c.options.Streams == nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return *c.options.Streams
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetStreams(streamCount int) {
|
||||||
|
c.options.Streams = &streamCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Reverse() bool {
|
||||||
|
if c.options.Reverse == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *c.options.Reverse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetReverse(reverse bool) {
|
||||||
|
c.options.Reverse = &reverse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Window() string {
|
||||||
|
if c.options.Window == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *c.options.Window
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetWindow(window string) {
|
||||||
|
c.options.Window = &window
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) MSS() int {
|
||||||
|
if c.options.MSS == nil {
|
||||||
|
return 1460
|
||||||
|
}
|
||||||
|
return *c.options.MSS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetMSS(mss int) {
|
||||||
|
c.options.MSS = &mss
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) NoDelay() bool {
|
||||||
|
if c.options.NoDelay == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *c.options.NoDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetNoDelay(noDelay bool) {
|
||||||
|
c.options.NoDelay = &noDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Version4() bool {
|
||||||
|
if c.options.Version6 == nil && c.options.Version4 == nil {
|
||||||
|
return true
|
||||||
|
} else if c.options.Version6 != nil && *c.options.Version6 == true {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *c.options.Version4
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetVersion4(set bool) {
|
||||||
|
c.options.Version4 = &set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Version6() bool {
|
||||||
|
if c.options.Version6 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *c.options.Version6
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetVersion6(set bool) {
|
||||||
|
c.options.Version6 = &set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TOS() int {
|
||||||
|
if c.options.TOS == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *c.options.TOS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetTOS(value int) {
|
||||||
|
c.options.TOS = &value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ZeroCopy() bool {
|
||||||
|
if c.options.ZeroCopy == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *c.options.ZeroCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetZeroCopy(set bool) {
|
||||||
|
c.options.ZeroCopy = &set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) OmitSec() int {
|
||||||
|
if c.options.OmitSec == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *c.options.OmitSec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetOmitSec(value int) {
|
||||||
|
c.options.OmitSec = &value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Prefix() string {
|
||||||
|
if c.options.Prefix == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *c.options.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetPrefix(prefix string) {
|
||||||
|
c.options.Prefix = &prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) LogFile() string {
|
||||||
|
if c.options.LogFile == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return *c.options.LogFile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetLogFile(logfile string) {
|
||||||
|
c.options.LogFile = &logfile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) JSON() bool {
|
||||||
|
if c.options.JSON == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *c.options.JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetJSON(set bool) {
|
||||||
|
c.options.JSON = &set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) IncludeServer() bool {
|
||||||
|
if c.options.IncludeServer == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *c.options.IncludeServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetIncludeServer(set bool) {
|
||||||
|
c.options.IncludeServer = &set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ExitCode() *int {
|
||||||
|
return c.exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Report() *TestReport {
|
||||||
|
return c.report
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Mode() TestMode {
|
||||||
|
return c.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetModeJson() {
|
||||||
|
c.SetJSON(true)
|
||||||
|
c.reportingChan = nil
|
||||||
|
c.reportingFile = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetModeLive() <-chan *StreamIntervalReport {
|
||||||
|
c.SetJSON(false) // having JSON == true will cause reporting to fail
|
||||||
|
c.live = true
|
||||||
|
c.reportingChan = make(chan *StreamIntervalReport, 10000)
|
||||||
|
f, err := ioutil.TempFile("", "iperf_")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create logfile: %v", err)
|
||||||
|
}
|
||||||
|
c.reportingFile = f.Name()
|
||||||
|
c.SetLogFile(c.reportingFile)
|
||||||
|
return c.reportingChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Start() (err error) {
|
||||||
|
//todo: Need to build the string based on the options above that are set
|
||||||
|
cmd, err := c.commandString()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(cmd)
|
||||||
|
var exit chan int
|
||||||
|
c.outputStream, c.errorStream, exit, c.cancel, err = ExecuteAsyncWithCancel(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Running = true
|
||||||
|
//go func() {
|
||||||
|
// ds := DebugScanner{Silent: false}
|
||||||
|
// ds.Scan(c.outputStream)
|
||||||
|
//}()
|
||||||
|
//go func() {
|
||||||
|
// ds := DebugScanner{Silent: false}
|
||||||
|
// ds.Scan(c.errorStream)
|
||||||
|
//}()
|
||||||
|
go func() {
|
||||||
|
var reporter *Reporter
|
||||||
|
if c.live {
|
||||||
|
reporter = &Reporter{
|
||||||
|
ReportingChannel: c.reportingChan,
|
||||||
|
LogFile: c.reportingFile,
|
||||||
|
}
|
||||||
|
reporter.Start()
|
||||||
|
} else {
|
||||||
|
testOutput, err := ioutil.ReadAll(c.outputStream)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.report, err = Loads(string(testOutput))
|
||||||
|
}
|
||||||
|
exitCode := <-exit
|
||||||
|
c.exitCode = &exitCode
|
||||||
|
c.Running = false
|
||||||
|
c.Done <- true
|
||||||
|
if reporter != nil {
|
||||||
|
reporter.Stop()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Stop() {
|
||||||
|
if c.Running && c.cancel != nil {
|
||||||
|
c.cancel()
|
||||||
|
os.Remove(c.reportingFile)
|
||||||
|
c.Done <- true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,82 @@
|
|||||||
package iperf
|
package iperf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Execute(cmd string, outPipe io.ReadCloser, errPipe io.ReadCloser, exit chan <- int) (err error) {
|
func ExecuteAsync(cmd string) (outPipe io.ReadCloser, errPipe io.ReadCloser, exitCode chan int, err error) {
|
||||||
|
exitCode = make(chan int)
|
||||||
cmdParts := strings.Fields(cmd)
|
cmdParts := strings.Fields(cmd)
|
||||||
binary, err := exec.LookPath(cmdParts[0])
|
binary, err := exec.LookPath(cmdParts[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
exe := exec.Command(binary, cmdParts[1:]...)
|
exe := exec.Command(binary, cmdParts[1:]...)
|
||||||
outPipe, err = exe.StdoutPipe()
|
outPipe, err = exe.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
errPipe, err = exe.StderrPipe()
|
errPipe, err = exe.StderrPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
err = exe.Start()
|
err = exe.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
if err := exe.Wait(); err != nil {
|
if err := exe.Wait(); err != nil {
|
||||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||||
exit <- status.ExitStatus()
|
exitCode <- status.ExitStatus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
exit <- 0
|
exitCode <- 0
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return nil
|
return outPipe, errPipe, exitCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExecuteAsyncWithCancel(cmd string) (stdOut io.ReadCloser, stdErr io.ReadCloser, exitCode chan int, cancelToken context.CancelFunc, err error) {
|
||||||
|
exitCode = make(chan int)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cmdParts := strings.Fields(cmd)
|
||||||
|
binary, err := exec.LookPath(cmdParts[0])
|
||||||
|
if err != nil {
|
||||||
|
defer cancel()
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
exe := exec.CommandContext(ctx, binary, cmdParts[1:]...)
|
||||||
|
stdOut, err = exe.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
defer cancel()
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
stdErr, err = exe.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
defer cancel()
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
err = exe.Start()
|
||||||
|
if err != nil {
|
||||||
|
defer cancel()
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if err := exe.Wait(); err != nil {
|
||||||
|
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||||
|
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||||
|
exitCode <- status.ExitStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exitCode <- 0
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return stdOut, stdErr, exitCode, cancel, nil
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,25 @@
|
|||||||
|
github.com/BGrewell/go-conversions v0.0.0-20201203155646-5e189e4ca087 h1:/ZR3IHtTSPqwzYQSfZYwGjrmDI5c1u2jEDDtT75Fmqw=
|
||||||
|
github.com/BGrewell/go-conversions v0.0.0-20201203155646-5e189e4ca087/go.mod h1:XtXcz/MP04vhr6c6R/5gZPJZVQpbXlFsKTHr5yy/5sU=
|
||||||
|
github.com/BGrewell/go-execute v0.0.0-20201203155726-b7c037ebde49 h1:HV+WdlqXjmzP59lhgb100vTSIcDuKuLmK11KdnPY0cg=
|
||||||
|
github.com/BGrewell/go-execute v0.0.0-20201203155726-b7c037ebde49/go.mod h1:vQZr3vuuuKuknvi74K22ne7NzOlwKTPKXOFMVr9Qa6A=
|
||||||
|
github.com/BGrewell/tail v1.0.0 h1:sG+Uvv+UApHtj5z+AWWB9i5m2SCH0RLfxYqXujYQo+Q=
|
||||||
|
github.com/BGrewell/tail v1.0.0/go.mod h1:0PFYWAobUZKZLEYIxxmjFgnfvCLA600LkFbGO9KFIRA=
|
||||||
|
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||||
|
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||||
|
github.com/fatih/gomodifytags v1.13.0 h1:fmhwoecjZ5c34Q2chjRB9cL8Rgag+1TOSMy+grissMc=
|
||||||
|
github.com/fatih/gomodifytags v1.13.0/go.mod h1:TbUyEjH1Zo0GkJd2Q52oVYqYcJ0eGNqG8bsiOb75P9c=
|
||||||
|
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||||
|
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/tools v0.0.0-20180824175216-6c1c5e93cdc1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
@ -1 +1,393 @@
|
|||||||
package iperf
|
package iperf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StreamInterval struct {
|
||||||
|
Streams []*StreamIntervalReport `json:"streams"`
|
||||||
|
Sum *StreamIntervalSumReport `json:"sum"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (si *StreamInterval) String() string {
|
||||||
|
b, err := json.Marshal(si)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamIntervalReport struct {
|
||||||
|
Socket int `json:"socket"`
|
||||||
|
StartInterval float32 `json:"start"`
|
||||||
|
EndInterval float32 `json:"end"`
|
||||||
|
Seconds float32 `json:"seconds"`
|
||||||
|
Bytes int `json:"bytes"`
|
||||||
|
BitsPerSecond float64 `json:"bits_per_second"`
|
||||||
|
Omitted bool `json:"omitted"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sir *StreamIntervalReport) String() string {
|
||||||
|
b, err := json.Marshal(sir)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamIntervalSumReport struct {
|
||||||
|
StartInterval float32 `json:"start"`
|
||||||
|
EndInterval float32 `json:"end"`
|
||||||
|
Seconds float32 `json:"seconds"`
|
||||||
|
Bytes int `json:"bytes"`
|
||||||
|
BitsPerSecond float64 `json:"bits_per_second"`
|
||||||
|
Omitted bool `json:"omitted"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sisr *StreamIntervalSumReport) String() string {
|
||||||
|
b, err := json.Marshal(sisr)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamEndReport struct {
|
||||||
|
Sender TcpStreamEndReport `json:"sender"`
|
||||||
|
Receiver TcpStreamEndReport `json:"receiver"`
|
||||||
|
Udp UdpStreamEndReport `json:"udp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ser *StreamEndReport) String() string {
|
||||||
|
b, err := json.Marshal(ser)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type UdpStreamEndReport struct {
|
||||||
|
Socket int `json:"socket"`
|
||||||
|
Start float32 `json:"start"`
|
||||||
|
End float32 `json:"end"`
|
||||||
|
Seconds float32 `json:"seconds"`
|
||||||
|
Bytes int `json:"bytes"`
|
||||||
|
BitsPerSecond float64 `json:"bits_per_second"`
|
||||||
|
JitterMs float32 `json:"jitter_ms"`
|
||||||
|
LostPackets int `json:"lost_packets"`
|
||||||
|
Packets int `json:"packets"`
|
||||||
|
LostPercent float32 `json:"lost_percent"`
|
||||||
|
OutOfOrder int `json:"out_of_order"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (user *UdpStreamEndReport) String() string {
|
||||||
|
b, err := json.Marshal(user)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type TcpStreamEndReport struct {
|
||||||
|
Socket int `json:"socket"`
|
||||||
|
Start float32 `json:"start"`
|
||||||
|
End float32 `json:"end"`
|
||||||
|
Seconds float32 `json:"seconds"`
|
||||||
|
Bytes int `json:"bytes"`
|
||||||
|
BitsPerSecond float64 `json:"bits_per_second"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tser *TcpStreamEndReport) String() string {
|
||||||
|
b, err := json.Marshal(tser)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamEndSumReport struct {
|
||||||
|
Start float32 `json:"start"`
|
||||||
|
End float32 `json:"end"`
|
||||||
|
Seconds float32 `json:"seconds"`
|
||||||
|
Bytes int `json:"bytes"`
|
||||||
|
BitsPerSecond float64 `json:"bits_per_second"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sesr *StreamEndSumReport) String() string {
|
||||||
|
b, err := json.Marshal(sesr)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type CpuUtilizationReport struct {
|
||||||
|
HostTotal float32 `json:"host_total"`
|
||||||
|
HostUser float32 `json:"host_user"`
|
||||||
|
HostSystem float32 `json:"host_system"`
|
||||||
|
RemoteTotal float32 `json:"remote_total"`
|
||||||
|
RemoteUser float32 `json:"remote_user"`
|
||||||
|
RemoteSystem float32 `json:"remote_system"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cur *CpuUtilizationReport) String() string {
|
||||||
|
b, err := json.Marshal(cur)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionInfo struct {
|
||||||
|
Socket int `json:"socket"`
|
||||||
|
LocalHost string `json:"local_host"`
|
||||||
|
LocalPort int `json:"local_port"`
|
||||||
|
RemoteHost string `json:"remote_host"`
|
||||||
|
RemotePort int `json:"remote_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *ConnectionInfo) String() string {
|
||||||
|
b, err := json.Marshal(ci)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimestampInfo struct {
|
||||||
|
Time string `json:"time"`
|
||||||
|
TimeSecs int `json:"timesecs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tsi *TimestampInfo) String() string {
|
||||||
|
b, err := json.Marshal(tsi)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectingToInfo struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cti *ConnectingToInfo) String() string {
|
||||||
|
b, err := json.Marshal(cti)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestStartInfo struct {
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
NumStreams int `json:"num_streams"`
|
||||||
|
BlkSize int `json:"blksize"`
|
||||||
|
Omit int `json:"omit"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
Bytes int `json:"bytes"`
|
||||||
|
Blocks int `json:"blocks"`
|
||||||
|
Reverse int `json:"reverse"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tsi *TestStartInfo) String() string {
|
||||||
|
b, err := json.Marshal(tsi)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartInfo struct {
|
||||||
|
Connected []*ConnectionInfo `json:"connected"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
SystemInfo string `json:"system_info"`
|
||||||
|
Timestamp TimestampInfo `json:"timestamp"`
|
||||||
|
ConnectingTo ConnectingToInfo `json:"connecting_to"`
|
||||||
|
Cookie string `json:"cookie"`
|
||||||
|
TcpMssDefault int `json:"tcp_mss_default"`
|
||||||
|
TestStart TestStartInfo `json:"test_start"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (si *StartInfo) String() string {
|
||||||
|
b, err := json.Marshal(si)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type EndInfo struct {
|
||||||
|
Streams []*StreamEndReport `json:"streams"`
|
||||||
|
SumSent StreamEndSumReport `json:"sum_sent"`
|
||||||
|
SumReceived StreamEndSumReport `json:"sum_received"`
|
||||||
|
CpuReport CpuUtilizationReport `json:"cpu_utilization_percent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ei *EndInfo) String() string {
|
||||||
|
b, err := json.Marshal(ei)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerReport struct {
|
||||||
|
Start StartInfo `json:"start"`
|
||||||
|
Intervals []*StreamInterval `json:"intervals"`
|
||||||
|
End EndInfo `json:"end"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *ServerReport) String() string {
|
||||||
|
b, err := json.Marshal(sr)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestReport struct {
|
||||||
|
Start StartInfo `json:"start"`
|
||||||
|
Intervals []*StreamInterval `json:"intervals"`
|
||||||
|
End EndInfo `json:"end"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
ServerOutputJson ServerReport `json:"server_output_json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *TestReport) String() string {
|
||||||
|
b, err := json.Marshal(tr)
|
||||||
|
if err != nil {
|
||||||
|
return "error converting to json"
|
||||||
|
}
|
||||||
|
|
||||||
|
var pretty bytes.Buffer
|
||||||
|
err = json.Indent(&pretty, b, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "error converting json to indented format"
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(pretty.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Loads(jsonStr string) (report *TestReport, err error) {
|
||||||
|
r := &TestReport{}
|
||||||
|
err = json.Unmarshal([]byte(jsonStr), r)
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load(filename string) (report *TestReport, err error) {
|
||||||
|
contents, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return Loads(string(contents))
|
||||||
|
}
|
||||||
|
@ -1 +1,92 @@
|
|||||||
package iperf
|
package iperf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/BGrewell/tail"
|
||||||
|
"github.com/BGrewell/go-conversions"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Reporter struct {
|
||||||
|
ReportingChannel chan *StreamIntervalReport
|
||||||
|
LogFile string
|
||||||
|
running bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reporter) Start() {
|
||||||
|
r.running = true
|
||||||
|
go r.runLogProcessor()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reporter) Stop() {
|
||||||
|
r.running = false
|
||||||
|
close(r.ReportingChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reporter) runLogProcessor() {
|
||||||
|
tailer, err := tail.TailFile(r.LogFile, tail.Config{
|
||||||
|
Follow: true,
|
||||||
|
ReOpen: true,
|
||||||
|
Poll: true,
|
||||||
|
MustExist: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to tail log file: %v", err)
|
||||||
|
}
|
||||||
|
for line := range tailer.Lines {
|
||||||
|
// TODO: For now this only cares about individual streams it ignores the sum lines
|
||||||
|
if len(line.Text) > 5 {
|
||||||
|
id := line.Text[1:4]
|
||||||
|
stream, err := strconv.Atoi(strings.TrimSpace(id))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields := strings.Fields(line.Text[5:])
|
||||||
|
if len(fields) >= 6 {
|
||||||
|
if fields[0] == "local" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
timeFields := strings.Split(fields[0], "-")
|
||||||
|
start, err := strconv.ParseFloat(timeFields[0], 32)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to convert start time: %s\n", err)
|
||||||
|
}
|
||||||
|
end, err := strconv.ParseFloat(timeFields[1], 32)
|
||||||
|
transferedStr := fmt.Sprintf("%s%s", fields[2], fields[3])
|
||||||
|
transferedBytes, err := conversions.StringBitRateToInt(transferedStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to convert units: %s\n", err)
|
||||||
|
}
|
||||||
|
transferedBytes = transferedBytes / 8
|
||||||
|
rateStr := fmt.Sprintf("%s%s", fields[4], fields[5])
|
||||||
|
rate, err := conversions.StringBitRateToInt(rateStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to convert units: %s\n", err)
|
||||||
|
}
|
||||||
|
omitted := false
|
||||||
|
if len(fields) >= 7 && fields[6] == "(omitted)" {
|
||||||
|
omitted = true
|
||||||
|
}
|
||||||
|
report := &StreamIntervalReport{
|
||||||
|
Socket: stream,
|
||||||
|
StartInterval: float32(start),
|
||||||
|
EndInterval: float32(end),
|
||||||
|
Seconds: float32(end - start),
|
||||||
|
Bytes: int(transferedBytes),
|
||||||
|
BitsPerSecond: float64(rate),
|
||||||
|
Omitted: omitted,
|
||||||
|
}
|
||||||
|
r.ReportingChannel <- report
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,291 @@
|
|||||||
|
{
|
||||||
|
"start":{
|
||||||
|
"connected":[
|
||||||
|
{
|
||||||
|
"socket":4,
|
||||||
|
"local_host":"127.0.0.1",
|
||||||
|
"local_port":64200,
|
||||||
|
"remote_host":"127.0.0.1",
|
||||||
|
"remote_port":5201
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version":"iperf 3.1.3",
|
||||||
|
"system_info":"CYGWIN_NT-10.0 BGrewell-MOBL3 2.5.1(0.297/5/3) 2016-04-21 22:14 x86_64",
|
||||||
|
"timestamp":{
|
||||||
|
"time":"Tue, 02 Mar 2021 18:28:14 GMT",
|
||||||
|
"timesecs":1614709694
|
||||||
|
},
|
||||||
|
"connecting_to":{
|
||||||
|
"host":"127.0.0.1",
|
||||||
|
"port":5201
|
||||||
|
},
|
||||||
|
"cookie":"BGrewell-MOBL3.1614709694.715824.0bf",
|
||||||
|
"tcp_mss_default":0,
|
||||||
|
"test_start":{
|
||||||
|
"protocol":"TCP",
|
||||||
|
"num_streams":1,
|
||||||
|
"blksize":131072,
|
||||||
|
"omit":0,
|
||||||
|
"duration":10,
|
||||||
|
"bytes":0,
|
||||||
|
"blocks":0,
|
||||||
|
"reverse":0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"intervals":[
|
||||||
|
{
|
||||||
|
"streams":[
|
||||||
|
{
|
||||||
|
"socket":4,
|
||||||
|
"start":0,
|
||||||
|
"end":1.000554,
|
||||||
|
"seconds":1.000554,
|
||||||
|
"bytes":1635647488,
|
||||||
|
"bits_per_second":1.307794e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sum":{
|
||||||
|
"start":0,
|
||||||
|
"end":1.000554,
|
||||||
|
"seconds":1.000554,
|
||||||
|
"bytes":1635647488,
|
||||||
|
"bits_per_second":1.307794e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"streams":[
|
||||||
|
{
|
||||||
|
"socket":4,
|
||||||
|
"start":1.000554,
|
||||||
|
"end":2.000056,
|
||||||
|
"seconds":0.999502,
|
||||||
|
"bytes":1527250944,
|
||||||
|
"bits_per_second":1.222409e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sum":{
|
||||||
|
"start":1.000554,
|
||||||
|
"end":2.000056,
|
||||||
|
"seconds":0.999502,
|
||||||
|
"bytes":1527250944,
|
||||||
|
"bits_per_second":1.222409e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"streams":[
|
||||||
|
{
|
||||||
|
"socket":4,
|
||||||
|
"start":2.000056,
|
||||||
|
"end":3.000353,
|
||||||
|
"seconds":1.000297,
|
||||||
|
"bytes":1712062464,
|
||||||
|
"bits_per_second":1.369244e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sum":{
|
||||||
|
"start":2.000056,
|
||||||
|
"end":3.000353,
|
||||||
|
"seconds":1.000297,
|
||||||
|
"bytes":1712062464,
|
||||||
|
"bits_per_second":1.369244e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"streams":[
|
||||||
|
{
|
||||||
|
"socket":4,
|
||||||
|
"start":3.000353,
|
||||||
|
"end":4.000669,
|
||||||
|
"seconds":1.000316,
|
||||||
|
"bytes":1730412544,
|
||||||
|
"bits_per_second":1.383893e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sum":{
|
||||||
|
"start":3.000353,
|
||||||
|
"end":4.000669,
|
||||||
|
"seconds":1.000316,
|
||||||
|
"bytes":1730412544,
|
||||||
|
"bits_per_second":1.383893e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"streams":[
|
||||||
|
{
|
||||||
|
"socket":4,
|
||||||
|
"start":4.000669,
|
||||||
|
"end":5.000021,
|
||||||
|
"seconds":0.999352,
|
||||||
|
"bytes":1777467392,
|
||||||
|
"bits_per_second":1.422896e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sum":{
|
||||||
|
"start":4.000669,
|
||||||
|
"end":5.000021,
|
||||||
|
"seconds":0.999352,
|
||||||
|
"bytes":1777467392,
|
||||||
|
"bits_per_second":1.422896e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"streams":[
|
||||||
|
{
|
||||||
|
"socket":4,
|
||||||
|
"start":5.000021,
|
||||||
|
"end":6.000122,
|
||||||
|
"seconds":1.000101,
|
||||||
|
"bytes":1673920512,
|
||||||
|
"bits_per_second":1.339001e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sum":{
|
||||||
|
"start":5.000021,
|
||||||
|
"end":6.000122,
|
||||||
|
"seconds":1.000101,
|
||||||
|
"bytes":1673920512,
|
||||||
|
"bits_per_second":1.339001e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"streams":[
|
||||||
|
{
|
||||||
|
"socket":4,
|
||||||
|
"start":6.000122,
|
||||||
|
"end":7.000517,
|
||||||
|
"seconds":1.000395,
|
||||||
|
"bytes":1585446912,
|
||||||
|
"bits_per_second":1.267857e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sum":{
|
||||||
|
"start":6.000122,
|
||||||
|
"end":7.000517,
|
||||||
|
"seconds":1.000395,
|
||||||
|
"bytes":1585446912,
|
||||||
|
"bits_per_second":1.267857e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"streams":[
|
||||||
|
{
|
||||||
|
"socket":4,
|
||||||
|
"start":7.000517,
|
||||||
|
"end":8.000053,
|
||||||
|
"seconds":0.999536,
|
||||||
|
"bytes":1642987520,
|
||||||
|
"bits_per_second":1.315000e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sum":{
|
||||||
|
"start":7.000517,
|
||||||
|
"end":8.000053,
|
||||||
|
"seconds":0.999536,
|
||||||
|
"bytes":1642987520,
|
||||||
|
"bits_per_second":1.315000e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"streams":[
|
||||||
|
{
|
||||||
|
"socket":4,
|
||||||
|
"start":8.000053,
|
||||||
|
"end":9.000105,
|
||||||
|
"seconds":1.000052,
|
||||||
|
"bytes":1547173888,
|
||||||
|
"bits_per_second":1.237675e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sum":{
|
||||||
|
"start":8.000053,
|
||||||
|
"end":9.000105,
|
||||||
|
"seconds":1.000052,
|
||||||
|
"bytes":1547173888,
|
||||||
|
"bits_per_second":1.237675e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"streams":[
|
||||||
|
{
|
||||||
|
"socket":4,
|
||||||
|
"start":9.000105,
|
||||||
|
"end":10.000386,
|
||||||
|
"seconds":1.000281,
|
||||||
|
"bytes":1773142016,
|
||||||
|
"bits_per_second":1.418115e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sum":{
|
||||||
|
"start":9.000105,
|
||||||
|
"end":10.000386,
|
||||||
|
"seconds":1.000281,
|
||||||
|
"bytes":1773142016,
|
||||||
|
"bits_per_second":1.418115e+10,
|
||||||
|
"omitted":false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end":{
|
||||||
|
"streams":[
|
||||||
|
{
|
||||||
|
"sender":{
|
||||||
|
"socket":4,
|
||||||
|
"start":0,
|
||||||
|
"end":10.000386,
|
||||||
|
"seconds":10.000386,
|
||||||
|
"bytes":16605511680,
|
||||||
|
"bits_per_second":1.328390e+10
|
||||||
|
},
|
||||||
|
"receiver":{
|
||||||
|
"socket":4,
|
||||||
|
"start":0,
|
||||||
|
"end":10.000386,
|
||||||
|
"seconds":10.000386,
|
||||||
|
"bytes":16605511680,
|
||||||
|
"bits_per_second":1.328390e+10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sum_sent":{
|
||||||
|
"start":0,
|
||||||
|
"end":10.000386,
|
||||||
|
"seconds":10.000386,
|
||||||
|
"bytes":16605511680,
|
||||||
|
"bits_per_second":1.328390e+10
|
||||||
|
},
|
||||||
|
"sum_received":{
|
||||||
|
"start":0,
|
||||||
|
"end":10.000386,
|
||||||
|
"seconds":10.000386,
|
||||||
|
"bytes":16605511680,
|
||||||
|
"bits_per_second":1.328390e+10
|
||||||
|
},
|
||||||
|
"cpu_utilization_percent":{
|
||||||
|
"host_total":83.983525,
|
||||||
|
"host_user":5.155306,
|
||||||
|
"host_system":78.828219,
|
||||||
|
"remote_total":34.722681,
|
||||||
|
"remote_user":7.224950,
|
||||||
|
"remote_system":27.497731
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,23 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/BGrewell/go-iperf"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
$END$
|
s := iperf.NewServer()
|
||||||
|
err := s.Start()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("failed to start server")
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for s.Running {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("server has exited")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue