initial working version

main
Ben Grewell 4 years ago
parent e8dc386ba0
commit 10cf19535e

34
.gitignore vendored

@ -1,18 +1,18 @@
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe *.exe
*.exe~ *.exe~
*.dll *.dll
*.so *.so
*.dylib *.dylib
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
# Ignore the embedded directory # Ignore the embedded directory
embedded/ embedded/

@ -1,107 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="fc2840de-29dc-4fca-8e0e-a283562f60ca" name="Default Changelist" comment=""> <list default="true" id="fc2840de-29dc-4fca-8e0e-a283562f60ca" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/client.go" afterDir="false" /> <change afterPath="$PROJECT_DIR$/reporter.go" afterDir="false" />
<change afterPath="$PROJECT_DIR$/execute.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/protocol.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/bindata.go" beforeDir="false" afterPath="$PROJECT_DIR$/bindata.go" afterDir="false" />
<change afterPath="$PROJECT_DIR$/server.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/client.go" beforeDir="false" afterPath="$PROJECT_DIR$/client.go" afterDir="false" />
<change afterPath="$PROJECT_DIR$/shared.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/cmd/main.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/main.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/execute.go" beforeDir="false" afterPath="$PROJECT_DIR$/execute.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" /> <change beforePath="$PROJECT_DIR$/go.mod" beforeDir="false" afterPath="$PROJECT_DIR$/go.mod" afterDir="false" />
<change beforePath="$PROJECT_DIR$/bindata.go" beforeDir="false" afterPath="$PROJECT_DIR$/bindata.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/go.sum" beforeDir="false" afterPath="$PROJECT_DIR$/go.sum" afterDir="false" />
<change beforePath="$PROJECT_DIR$/cmd/main.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/main.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/iperf.go" beforeDir="false" afterPath="$PROJECT_DIR$/iperf.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/go.mod" beforeDir="false" afterPath="$PROJECT_DIR$/go.mod" afterDir="false" /> <change beforePath="$PROJECT_DIR$/protocol.go" beforeDir="false" afterPath="$PROJECT_DIR$/protocol.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/iperf.go" beforeDir="false" afterPath="$PROJECT_DIR$/iperf.go" afterDir="false" /> <change beforePath="$PROJECT_DIR$/report.go" beforeDir="false" afterPath="$PROJECT_DIR$/report.go" afterDir="false" />
</list> <change beforePath="$PROJECT_DIR$/sample.json" beforeDir="false" afterPath="$PROJECT_DIR$/sample.json" afterDir="false" />
<option name="SHOW_DIALOG" value="false" /> <change beforePath="$PROJECT_DIR$/server.go" beforeDir="false" afterPath="$PROJECT_DIR$/server.go" afterDir="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <change beforePath="$PROJECT_DIR$/shared.go" beforeDir="false" afterPath="$PROJECT_DIR$/shared.go" afterDir="false" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <change beforePath="$PROJECT_DIR$/tests/client/client.go" beforeDir="false" afterPath="$PROJECT_DIR$/tests/client/client.go" afterDir="false" />
<option name="LAST_RESOLUTION" value="IGNORE" /> <change beforePath="$PROJECT_DIR$/tests/server/server.go" beforeDir="false" afterPath="$PROJECT_DIR$/tests/server/server.go" afterDir="false" />
</component> </list>
<component name="FileTemplateManagerImpl"> <option name="SHOW_DIALOG" value="false" />
<option name="RECENT_TEMPLATES"> <option name="HIGHLIGHT_CONFLICTS" value="true" />
<list> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option value="Go Application" /> <option name="LAST_RESOLUTION" value="IGNORE" />
<option value="Go File" /> </component>
</list> <component name="FileTemplateManagerImpl">
</option> <option name="RECENT_TEMPLATES">
</component> <list>
<component name="GOROOT" path="C:\Go" /> <option value="Go Application" />
<component name="Git.Settings"> <option value="Go File" />
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> </list>
</component> </option>
<component name="GoLibraries"> </component>
<option name="indexEntireGoPath" value="false" /> <component name="GOROOT" path="C:\Go" />
</component> <component name="Git.Settings">
<component name="ProjectId" id="1lhjlU9mkZIWjchkp4HkX1szrI1" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
<component name="ProjectViewState"> </component>
<option name="hideEmptyMiddlePackages" value="true" /> <component name="GoLibraries">
<option name="showLibraryContents" value="true" /> <option name="indexEntireGoPath" value="false" />
</component> </component>
<component name="PropertiesComponent"> <component name="ProjectId" id="1lhjlU9mkZIWjchkp4HkX1szrI1" />
<property name="DefaultGoTemplateProperty" value="Go File" /> <component name="ProjectViewState">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" /> <option name="showLibraryContents" value="true" />
<property name="go.import.settings.migrated" value="true" /> </component>
<property name="go.sdk.automatically.set" value="true" /> <component name="PropertiesComponent">
<property name="go.tried.to.enable.integration.vgo.integrator" value="true" /> <property name="DefaultGoTemplateProperty" value="Go File" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/embedded" /> <property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" /> <property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="nodejs_npm_path_reset_for_default_project" value="true" /> <property name="WebServerToolWindowFactoryState" value="false" />
</component> <property name="go.import.settings.migrated" value="true" />
<component name="RecentsManager"> <property name="go.sdk.automatically.set" value="true" />
<key name="CopyFile.RECENT_KEYS"> <property name="go.tried.to.enable.integration.vgo.integrator" value="true" />
<recent name="C:\Users\BGrewell\repos\go-iperf\embedded" /> <property name="last_opened_file_path" value="$PROJECT_DIR$" />
</key> <property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
</component> <property name="nodejs_npm_path_reset_for_default_project" value="true" />
<component name="RunManager"> </component>
<configuration name="go build github.com/BGrewell/go-iperf/cmd" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true"> <component name="RecentsManager">
<module name="go-iperf" /> <key name="CopyFile.RECENT_KEYS">
<working_directory value="$PROJECT_DIR$" /> <recent name="C:\Users\BGrewell\repos\go-iperf\embedded" />
<kind value="PACKAGE" /> </key>
<filePath value="$PROJECT_DIR$/cmd/main.go" /> <key name="MoveFile.RECENT_KEYS">
<package value="github.com/BGrewell/go-iperf/cmd" /> <recent name="C:\Users\BGrewell\source\repos\go-iperf\tests\server" />
<directory value="$PROJECT_DIR$" /> </key>
<method v="2" /> </component>
</configuration> <component name="RunManager" selected="Go Build.go build github.com/BGrewell/go-iperf/tests/client">
<recent_temporary> <configuration name="go build github.com/BGrewell/go-iperf/cmd" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
<list> <module name="go-iperf" />
<item itemvalue="Go Build.go build github.com/BGrewell/go-iperf/cmd" /> <working_directory value="$PROJECT_DIR$" />
</list> <kind value="PACKAGE" />
</recent_temporary> <filePath value="$PROJECT_DIR$/cmd/main.go" />
</component> <package value="github.com/BGrewell/go-iperf/cmd" />
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" /> <directory value="$PROJECT_DIR$" />
<component name="TypeScriptGeneratedFilesManager"> <method v="2" />
<option name="version" value="3" /> </configuration>
</component> <configuration name="go build github.com/BGrewell/go-iperf/tests/client" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
<component name="VgoProject"> <module name="go-iperf" />
<integration-enabled>true</integration-enabled> <working_directory value="$PROJECT_DIR$" />
</component> <kind value="PACKAGE" />
<component name="WindowStateProjectService"> <filePath value="$PROJECT_DIR$/tests/client/client.go" />
<state x="652" y="533" key="#Go_Modules" timestamp="1608060904277"> <package value="github.com/BGrewell/go-iperf/tests/client" />
<screen x="0" y="0" width="3440" height="1400" /> <directory value="$PROJECT_DIR$" />
</state> <method v="2" />
<state x="652" y="533" key="#Go_Modules/0.0.3440.1400@0.0.3440.1400" timestamp="1608060904277" /> </configuration>
<state width="1676" height="403" key="GridCell.Tab.0.bottom" timestamp="1608066744405"> <configuration name="go build github.com/BGrewell/go-iperf/tests/server" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
<screen x="0" y="0" width="3440" height="1400" /> <module name="go-iperf" />
</state> <working_directory value="$PROJECT_DIR$" />
<state width="1676" height="403" key="GridCell.Tab.0.bottom/0.0.3440.1400@0.0.3440.1400" timestamp="1608066744405" /> <kind value="PACKAGE" />
<state width="1676" height="403" key="GridCell.Tab.0.center" timestamp="1608066744404"> <filePath value="$PROJECT_DIR$/tests/server/server.go" />
<screen x="0" y="0" width="3440" height="1400" /> <package value="github.com/BGrewell/go-iperf/tests/server" />
</state> <directory value="$PROJECT_DIR$" />
<state width="1676" height="403" key="GridCell.Tab.0.center/0.0.3440.1400@0.0.3440.1400" timestamp="1608066744404" /> <method v="2" />
<state width="1676" height="403" key="GridCell.Tab.0.left" timestamp="1608066744404"> </configuration>
<screen x="0" y="0" width="3440" height="1400" /> <recent_temporary>
</state> <list>
<state width="1676" height="403" key="GridCell.Tab.0.left/0.0.3440.1400@0.0.3440.1400" timestamp="1608066744404" /> <item itemvalue="Go Build.go build github.com/BGrewell/go-iperf/tests/client" />
<state width="1676" height="403" key="GridCell.Tab.0.right" timestamp="1608066744404"> <item itemvalue="Go Build.go build github.com/BGrewell/go-iperf/tests/server" />
<screen x="0" y="0" width="3440" height="1400" /> <item itemvalue="Go Build.go build github.com/BGrewell/go-iperf/cmd" />
</state> </list>
<state width="1676" height="403" key="GridCell.Tab.0.right/0.0.3440.1400@0.0.3440.1400" timestamp="1608066744404" /> </recent_temporary>
<state x="431" y="297" key="com.intellij.openapi.editor.actions.MultiplePasteAction$ClipboardContentChooser" timestamp="1608062415393"> </component>
<screen x="0" y="0" width="3440" height="1400" /> <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
</state> <component name="TaskManager">
<state x="431" y="297" key="com.intellij.openapi.editor.actions.MultiplePasteAction$ClipboardContentChooser/0.0.3440.1400@0.0.3440.1400" timestamp="1608062415393" /> <task active="true" id="Default" summary="Default task">
</component> <changelist id="fc2840de-29dc-4fca-8e0e-a283562f60ca" name="Default Changelist" comment="" />
<created>1614708953320</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1614708953320</updated>
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="VgoProject">
<integration-enabled>true</integration-enabled>
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" type="DlvLineBreakpoint">
<url>file://$PROJECT_DIR$/tests/client/client.go</url>
<line>57</line>
<option name="timeStamp" value="5" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
</project> </project>

@ -1,25 +1,25 @@
BSD 2-Clause License BSD 2-Clause License
Copyright (c) 2020, Ben Grewell Copyright (c) 2020, Ben Grewell
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this 1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, 2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -1,6 +1,6 @@
# go-iperf # go-iperf
A Go based wrapper around iperf3 A Go based wrapper around iperf3
``` ```
go-bindata -pkg iperf -prefix "embedded/" embedded/``` go-bindata -pkg iperf -prefix "embedded/" embedded/```

@ -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
}
} }

@ -2,13 +2,17 @@ package main
import ( import (
"fmt" "fmt"
"github.com/BGrewell/go-conversions"
"github.com/BGrewell/go-iperf" "github.com/BGrewell/go-iperf"
"time" "time"
) )
func main() { func main() {
s := iperf.NewServer() s := iperf.NewServer()
c := iperf.NewClient() c := iperf.NewClient("127.0.0.1")
includeServer := true
c.IncludeServer = &includeServer
fmt.Println(s.Id) fmt.Println(s.Id)
fmt.Println(c.Id) fmt.Println(c.Id)
@ -16,10 +20,27 @@ func main() {
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
for s.Running {
err = c.Start()
if err != nil {
fmt.Println(err)
}
for c.Running {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
fmt.Println("stopping server")
s.Stop()
fmt.Printf("Client exit code: %d\n", *c.ExitCode)
fmt.Printf("Server exit code: %d\n", *s.ExitCode) fmt.Printf("Server exit code: %d\n", *s.ExitCode)
iperf.Cleanup() iperf.Cleanup()
if c.Report.Error != "" {
fmt.Println(c.Report.Error)
} else {
fmt.Printf("Recv Rate: %s\n", conversions.IntBitRateToString(int64(c.Report.End.SumReceived.BitsPerSecond)))
fmt.Printf("Send Rate: %s\n", conversions.IntBitRateToString(int64(c.Report.End.SumSent.BitsPerSecond)))
}
} }

@ -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
} }

@ -2,4 +2,14 @@ module github.com/BGrewell/go-iperf
go 1.15 go 1.15
require github.com/google/uuid v1.1.2 require (
github.com/BGrewell/go-conversions v0.0.0-20201203155646-5e189e4ca087
github.com/BGrewell/go-execute v0.0.0-20201203155726-b7c037ebde49 // indirect
github.com/BGrewell/tail v1.0.0
github.com/fatih/gomodifytags v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/google/uuid v1.1.2
github.com/hpcloud/tail v1.0.0 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
)

@ -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=

@ -10,8 +10,8 @@ import (
) )
var ( var (
Debug = true Debug = true
binaryDir = "" binaryDir = ""
binaryLocation = "" binaryLocation = ""
) )
@ -67,4 +67,4 @@ func extractEmbeddedBinaries(files []string) (err error) {
} }
} }
return nil return nil
} }

@ -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,44 +1,47 @@
package iperf package iperf
import ( import (
"context"
"fmt" "fmt"
"github.com/google/uuid" "github.com/google/uuid"
"io" "io"
"time"
) )
func NewServer() *Server { func NewServer() *Server {
s := &Server{ s := &Server{}
}
s.Id = uuid.New().String() s.Id = uuid.New().String()
return s return s
} }
type Server struct { type Server struct {
SharedOptions SharedOptions
OneOff *bool Id string
ExitCode *int OneOff *bool
Running bool ExitCode *int
Running bool
outputStream io.ReadCloser outputStream io.ReadCloser
errorStream io.ReadCloser errorStream io.ReadCloser
cancel context.CancelFunc
} }
func (s *Server) Start() (err error) { func (s *Server) Start() (err error) {
exit := make(chan int, 0) var exit chan int
err = Execute(fmt.Sprintf("%s -s", binaryLocation), s.outputStream, s.errorStream, exit) s.outputStream, s.errorStream, exit, s.cancel, err = ExecuteAsyncWithCancel(fmt.Sprintf("%s -s -J", binaryLocation))
if err != nil { if err != nil {
return err return err
} }
s.Running = true s.Running = true
go func() { go func() {
ds := DebugScanner{} ds := DebugScanner{Silent: true}
ds.Scan(s.outputStream) ds.Scan(s.outputStream)
}() }()
go func() { go func() {
ds := DebugScanner{} ds := DebugScanner{Silent: true}
ds.Scan(s.errorStream) ds.Scan(s.errorStream)
}() }()
go func() { go func() {
exitCode := <- exit exitCode := <-exit
s.ExitCode = &exitCode s.ExitCode = &exitCode
s.Running = false s.Running = false
}() }()
@ -46,5 +49,8 @@ func (s *Server) Start() (err error) {
} }
func (s *Server) Stop() { func (s *Server) Stop() {
if s.Running && s.cancel != nil {
s.cancel()
time.Sleep(100 * time.Millisecond)
}
} }

@ -6,15 +6,20 @@ import (
"io" "io"
) )
type TestMode string
const (
MODE_JSON TestMode = "json"
MODE_LIVE TestMode = "live"
)
type SharedOptions struct { type SharedOptions struct {
Id string Port *int
Port *int Format *rune
Format *rune
Interval *int Interval *int
} }
type DebugScanner struct { type DebugScanner struct {
Silent bool
} }
func (ds *DebugScanner) Scan(buff io.ReadCloser) { func (ds *DebugScanner) Scan(buff io.ReadCloser) {
@ -26,6 +31,8 @@ func (ds *DebugScanner) Scan(buff io.ReadCloser) {
scanner.Split(bufio.ScanWords) scanner.Split(bufio.ScanWords)
for scanner.Scan() { for scanner.Scan() {
text := scanner.Text() text := scanner.Text()
fmt.Println(text) if !ds.Silent {
fmt.Println(text)
}
} }
} }

@ -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…
Cancel
Save