initial working version
parent
e8dc386ba0
commit
10cf19535e
@ -1,18 +1,18 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Ignore the embedded directory
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Ignore the embedded directory
|
||||
embedded/
|
@ -1,107 +1,131 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<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$/execute.go" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/protocol.go" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/server.go" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/shared.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bindata.go" beforeDir="false" afterPath="$PROJECT_DIR$/bindata.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmd/main.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/main.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/go.mod" beforeDir="false" afterPath="$PROJECT_DIR$/go.mod" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/iperf.go" beforeDir="false" afterPath="$PROJECT_DIR$/iperf.go" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Go Application" />
|
||||
<option value="Go File" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="GOROOT" path="C:\Go" />
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="GoLibraries">
|
||||
<option name="indexEntireGoPath" value="false" />
|
||||
</component>
|
||||
<component name="ProjectId" id="1lhjlU9mkZIWjchkp4HkX1szrI1" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="DefaultGoTemplateProperty" value="Go File" />
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="go.import.settings.migrated" value="true" />
|
||||
<property name="go.sdk.automatically.set" value="true" />
|
||||
<property name="go.tried.to.enable.integration.vgo.integrator" value="true" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$/embedded" />
|
||||
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
|
||||
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\BGrewell\repos\go-iperf\embedded" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
<configuration name="go build github.com/BGrewell/go-iperf/cmd" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
|
||||
<module name="go-iperf" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<kind value="PACKAGE" />
|
||||
<filePath value="$PROJECT_DIR$/cmd/main.go" />
|
||||
<package value="github.com/BGrewell/go-iperf/cmd" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Go Build.go build github.com/BGrewell/go-iperf/cmd" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="VgoProject">
|
||||
<integration-enabled>true</integration-enabled>
|
||||
</component>
|
||||
<component name="WindowStateProjectService">
|
||||
<state x="652" y="533" key="#Go_Modules" timestamp="1608060904277">
|
||||
<screen x="0" y="0" width="3440" height="1400" />
|
||||
</state>
|
||||
<state x="652" y="533" key="#Go_Modules/0.0.3440.1400@0.0.3440.1400" timestamp="1608060904277" />
|
||||
<state width="1676" height="403" key="GridCell.Tab.0.bottom" timestamp="1608066744405">
|
||||
<screen x="0" y="0" width="3440" height="1400" />
|
||||
</state>
|
||||
<state width="1676" height="403" key="GridCell.Tab.0.bottom/0.0.3440.1400@0.0.3440.1400" timestamp="1608066744405" />
|
||||
<state width="1676" height="403" key="GridCell.Tab.0.center" timestamp="1608066744404">
|
||||
<screen x="0" y="0" width="3440" height="1400" />
|
||||
</state>
|
||||
<state width="1676" height="403" key="GridCell.Tab.0.center/0.0.3440.1400@0.0.3440.1400" timestamp="1608066744404" />
|
||||
<state width="1676" height="403" key="GridCell.Tab.0.left" timestamp="1608066744404">
|
||||
<screen x="0" y="0" width="3440" height="1400" />
|
||||
</state>
|
||||
<state width="1676" height="403" key="GridCell.Tab.0.left/0.0.3440.1400@0.0.3440.1400" timestamp="1608066744404" />
|
||||
<state width="1676" height="403" key="GridCell.Tab.0.right" timestamp="1608066744404">
|
||||
<screen x="0" y="0" width="3440" height="1400" />
|
||||
</state>
|
||||
<state width="1676" height="403" key="GridCell.Tab.0.right/0.0.3440.1400@0.0.3440.1400" timestamp="1608066744404" />
|
||||
<state x="431" y="297" key="com.intellij.openapi.editor.actions.MultiplePasteAction$ClipboardContentChooser" timestamp="1608062415393">
|
||||
<screen x="0" y="0" width="3440" height="1400" />
|
||||
</state>
|
||||
<state x="431" y="297" key="com.intellij.openapi.editor.actions.MultiplePasteAction$ClipboardContentChooser/0.0.3440.1400@0.0.3440.1400" timestamp="1608062415393" />
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="fc2840de-29dc-4fca-8e0e-a283562f60ca" name="Default Changelist" comment="">
|
||||
<change afterPath="$PROJECT_DIR$/reporter.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bindata.go" beforeDir="false" afterPath="$PROJECT_DIR$/bindata.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/client.go" beforeDir="false" afterPath="$PROJECT_DIR$/client.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/cmd/main.go" beforeDir="false" afterPath="$PROJECT_DIR$/cmd/main.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/execute.go" beforeDir="false" afterPath="$PROJECT_DIR$/execute.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/go.mod" beforeDir="false" afterPath="$PROJECT_DIR$/go.mod" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/go.sum" beforeDir="false" afterPath="$PROJECT_DIR$/go.sum" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/iperf.go" beforeDir="false" afterPath="$PROJECT_DIR$/iperf.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/protocol.go" beforeDir="false" afterPath="$PROJECT_DIR$/protocol.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/report.go" beforeDir="false" afterPath="$PROJECT_DIR$/report.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/sample.json" beforeDir="false" afterPath="$PROJECT_DIR$/sample.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/server.go" beforeDir="false" afterPath="$PROJECT_DIR$/server.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/shared.go" beforeDir="false" afterPath="$PROJECT_DIR$/shared.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/tests/client/client.go" beforeDir="false" afterPath="$PROJECT_DIR$/tests/client/client.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/tests/server/server.go" beforeDir="false" afterPath="$PROJECT_DIR$/tests/server/server.go" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Go Application" />
|
||||
<option value="Go File" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="GOROOT" path="C:\Go" />
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="GoLibraries">
|
||||
<option name="indexEntireGoPath" value="false" />
|
||||
</component>
|
||||
<component name="ProjectId" id="1lhjlU9mkZIWjchkp4HkX1szrI1" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="DefaultGoTemplateProperty" value="Go File" />
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="go.import.settings.migrated" value="true" />
|
||||
<property name="go.sdk.automatically.set" value="true" />
|
||||
<property name="go.tried.to.enable.integration.vgo.integrator" value="true" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
|
||||
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\BGrewell\repos\go-iperf\embedded" />
|
||||
</key>
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\BGrewell\source\repos\go-iperf\tests\server" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager" selected="Go Build.go build github.com/BGrewell/go-iperf/tests/client">
|
||||
<configuration name="go build github.com/BGrewell/go-iperf/cmd" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
|
||||
<module name="go-iperf" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<kind value="PACKAGE" />
|
||||
<filePath value="$PROJECT_DIR$/cmd/main.go" />
|
||||
<package value="github.com/BGrewell/go-iperf/cmd" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="go build github.com/BGrewell/go-iperf/tests/client" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
|
||||
<module name="go-iperf" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<kind value="PACKAGE" />
|
||||
<filePath value="$PROJECT_DIR$/tests/client/client.go" />
|
||||
<package value="github.com/BGrewell/go-iperf/tests/client" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="go build github.com/BGrewell/go-iperf/tests/server" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
|
||||
<module name="go-iperf" />
|
||||
<working_directory value="$PROJECT_DIR$" />
|
||||
<kind value="PACKAGE" />
|
||||
<filePath value="$PROJECT_DIR$/tests/server/server.go" />
|
||||
<package value="github.com/BGrewell/go-iperf/tests/server" />
|
||||
<directory value="$PROJECT_DIR$" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Go Build.go build github.com/BGrewell/go-iperf/tests/client" />
|
||||
<item itemvalue="Go Build.go build github.com/BGrewell/go-iperf/tests/server" />
|
||||
<item itemvalue="Go Build.go build github.com/BGrewell/go-iperf/cmd" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<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>
|
@ -1,25 +1,25 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2020, Ben Grewell
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
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
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2020, Ben Grewell
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
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
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
@ -1,6 +1,6 @@
|
||||
# go-iperf
|
||||
A Go based wrapper around iperf3
|
||||
|
||||
|
||||
```
|
||||
go-bindata -pkg iperf -prefix "embedded/" embedded/```
|
||||
# go-iperf
|
||||
A Go based wrapper around iperf3
|
||||
|
||||
|
||||
```
|
||||
go-bindata -pkg iperf -prefix "embedded/" embedded/```
|
||||
|
@ -1,43 +1,491 @@
|
||||
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
|
||||
proto := Protocol(PROTO_TCP)
|
||||
time := 10
|
||||
length := "128KB"
|
||||
streams := 1
|
||||
c := &Client{
|
||||
JSON: &json,
|
||||
Proto: &proto,
|
||||
TimeSec: &time,
|
||||
Length: &length,
|
||||
Streams: &streams,
|
||||
options: &ClientOptions{
|
||||
JSON: &json,
|
||||
Proto: &proto,
|
||||
TimeSec: &time,
|
||||
Length: &length,
|
||||
Streams: &streams,
|
||||
Host: &host,
|
||||
},
|
||||
}
|
||||
c.Id = uuid.New().String()
|
||||
c.Done = make(chan bool)
|
||||
return c
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
type ClientOptions struct {
|
||||
SharedOptions
|
||||
Host string
|
||||
Proto *Protocol
|
||||
Bandwidth *string
|
||||
TimeSec *int
|
||||
Bytes *string
|
||||
BlockCount *string
|
||||
Length *string
|
||||
Streams *int
|
||||
Reverse *bool
|
||||
Window *string
|
||||
MSS *int
|
||||
NoDelay *bool
|
||||
Version4 *bool
|
||||
Version6 *bool
|
||||
TOS *int
|
||||
ZeroCopy *bool
|
||||
OmitSec *int
|
||||
Prefix *string
|
||||
JSON *bool
|
||||
Host *string
|
||||
Proto *Protocol
|
||||
Bandwidth *string
|
||||
TimeSec *int
|
||||
Bytes *string
|
||||
BlockCount *string
|
||||
Length *string
|
||||
Streams *int
|
||||
Reverse *bool
|
||||
Window *string
|
||||
MSS *int
|
||||
NoDelay *bool
|
||||
Version4 *bool
|
||||
Version6 *bool
|
||||
TOS *int
|
||||
ZeroCopy *bool
|
||||
OmitSec *int
|
||||
Prefix *string
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"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)
|
||||
binary, err := exec.LookPath(cmdParts[0])
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
exe := exec.Command(binary, cmdParts[1:]...)
|
||||
outPipe, err = exe.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
errPipe, err = exe.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
err = exe.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
return 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 {
|
||||
exit <- status.ExitStatus()
|
||||
exitCode <- status.ExitStatus()
|
||||
}
|
||||
}
|
||||
} 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/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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/BGrewell/go-iperf"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
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