Skip to content

Commit

Permalink
feat: support human-readable and auto-scaled rate units for output (#194
Browse files Browse the repository at this point in the history
)
  • Loading branch information
r3inbowari authored May 4, 2024
1 parent 1275d4c commit 8da0bfb
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 50 deletions.
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,25 +38,27 @@ Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
-l, --list Show available speedtest.net servers.
-s, --server=SERVER ... Select server id to speedtest.
--custom-url=CUSTOM-URL Specify the url of the server instead of getting a list from speedtest.net.
--custom-url=CUSTOM-URL Specify the url of the server instead of fetching from speedtest.net.
--saving-mode Test with few resources, though low accuracy (especially > 30Mbps).
--json Output results in json format.
--location=LOCATION Change the location with a precise coordinate.
--location=LOCATION Change the location with a precise coordinate (format: lat,lon).
--city=CITY Change the location with a predefined city label.
--city-list List all predefined city labels.
--proxy=PROXY Set a proxy(http[s] or socks) for the speedtest.
eg: --proxy=socks://10.20.0.101:7890
eg: --proxy=http://10.20.0.101:7890
--source=SOURCE Bind a source interface for the speedtest.
--dns-bind-source DNS request binding source.(Experimental)
--dns-bind-source DNS request binding source (experimental).
eg: --source=10.20.0.101
-m --multi Enable multi-server mode.
-t --thread=THREAD Set the number of concurrent connections.
--search=SEARCH Fuzzy search servers by a keyword.
--ua Set the user-agent header for the speedtest.
--no-download Disable download test.
--no-upload Disable upload test.
--ping-mode Select a method for Ping. (support icmp/tcp/http)
--ping-mode Select a method for Ping (support icmp/tcp/http).
-u --unit Set human-readable and auto-scaled rate units for output
(options: decimal-bits/decimal-bytes/binary-bits/binary-bytes).
-d --debug Enable debug mode.
--version Show application version.
```
Expand All @@ -68,15 +70,15 @@ Simply use `speedtest` command. The closest server is selected by default. Use t
```bash
$ speedtest

speedtest-go v1.6.5 @showwin
speedtest-go v1.7.0 @showwin

✓ ISP: 124.27.199.165 (Fujitsu) [34.9769, 138.3831]
✓ Found 20 Public Servers

✓ Test Server: [6691] 9.03km Shizuoka (Japan) by sudosan
✓ Latency: 24.15396ms Jitter: 777.465µs Min: 22.8926ms Max: 25.5387ms
✓ Download: 73.30Mbps (used: 101.48MB)
✓ Upload: 35.26Mbps (used: 47.33MB)
✓ Latency: 4.452963ms Jitter: 41.271µs Min: 4.395179ms Max: 4.517576ms
✓ Download: 115.52 Mbps (Used: 135.75MB) (Latency: 4ms Jitter: 0ms Min: 4ms Max: 4ms)
✓ Upload: 4.02 Mbps (Used: 6.85MB) (Latency: 4ms Jitter: 1ms Min: 3ms Max: 8ms)
```

#### Test with Other Servers
Expand All @@ -98,23 +100,20 @@ and select them by id.
```bash
$ speedtest --server 6691 --server 6087

speedtest-go v1.6.5 @showwin
speedtest-go v1.7.0 @showwin

✓ ISP: 124.27.199.165 (Fujitsu) [34.9769, 138.3831]
✓ Found 20 Public Servers
✓ Found 2 Specified Public Server(s)

✓ Test Server: [6691] 9.03km Shizuoka (Japan) by sudosan
✓ Latency: 21.424ms Jitter: 1.644ms Min: 19.142ms Max: 23.926ms
✓ Download: 65.82Mbps (used: 75.48MB)
✓ Upload: 27.00Mbps (used: 36.33MB)
✓ Download: 65.82Mbps (Used: 75.48MB) (Latency: 22ms Jitter: 2ms Min: 17ms Max: 24ms)
✓ Upload: 27.00Mbps (Used: 36.33MB) (Latency: 23ms Jitter: 2ms Min: 18ms Max: 25ms)

✓ Test Server: [6087] 120.55km Fussa-shi (Japan) by Allied Telesis Capital Corporation
✓ Latency: 38.694699ms Jitter: 2.724ms Min: 36.443ms Max: 39.953ms
✓ Download: 72.24Mbps (used: 83.72MB)
✓ Upload: 29.56Mbps (used: 47.64MB)

Download Avg: 69.03 Mbit/s
Upload Avg: 28.28 Mbit/s
✓ Download: 72.24Mbps (Used: 83.72MB) (Latency: 37ms Jitter: 3ms Min: 36ms Max: 40ms)
✓ Upload: 29.56Mbps (Used: 47.64MB) (Latency: 38ms Jitter: 3ms Min: 37ms Max: 41ms)
```

#### Test with a virtual location
Expand Down Expand Up @@ -195,7 +194,8 @@ func main() {
s.PingTest(nil)
s.DownloadTest()
s.UploadTest()
fmt.Printf("Latency: %s, Download: %f, Upload: %f\n", s.Latency, s.DLSpeed, s.ULSpeed)
// Note: The unit of s.DLSpeed, s.ULSpeed is bytes per second, this is a float64.
fmt.Printf("Latency: %s, Download: %s, Upload: %s\n", s.Latency, s.DLSpeed, s.ULSpeed)
s.Context.Reset() // reset counter
}
}
Expand Down
3 changes: 2 additions & 1 deletion example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ func main() {
checkError(s.DownloadTest())
checkError(s.UploadTest())

fmt.Printf("Latency: %s, Download: %f, Upload: %f\n", s.Latency, s.DLSpeed, s.ULSpeed)
// Note: The unit of s.DLSpeed, s.ULSpeed is bytes per second, this is a float64.
fmt.Printf("Latency: %s, Download: %s, Upload: %s\n", s.Latency, s.DLSpeed, s.ULSpeed)
s.Context.Reset()
}
}
Expand Down
42 changes: 30 additions & 12 deletions speedtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@ import (
var (
showList = kingpin.Flag("list", "Show available speedtest.net servers.").Short('l').Bool()
serverIds = kingpin.Flag("server", "Select server id to run speedtest.").Short('s').Ints()
customURL = kingpin.Flag("custom-url", "Specify the url of the server instead of getting a list from speedtest.net.").String()
customURL = kingpin.Flag("custom-url", "Specify the url of the server instead of fetching from speedtest.net.").String()
savingMode = kingpin.Flag("saving-mode", "Test with few resources, though low accuracy (especially > 30Mbps).").Bool()
jsonOutput = kingpin.Flag("json", "Output results in json format.").Bool()
location = kingpin.Flag("location", "Change the location with a precise coordinate. Format: lat,lon").String()
location = kingpin.Flag("location", "Change the location with a precise coordinate (format: lat,lon).").String()
city = kingpin.Flag("city", "Change the location with a predefined city label.").String()
showCityList = kingpin.Flag("city-list", "List all predefined city labels.").Bool()
proxy = kingpin.Flag("proxy", "Set a proxy(http[s] or socks) for the speedtest.").String()
source = kingpin.Flag("source", "Bind a source interface for the speedtest.").String()
dnsBindSource = kingpin.Flag("dns-bind-source", "DNS request binding source.(Experimental)").Bool()
dnsBindSource = kingpin.Flag("dns-bind-source", "DNS request binding source (experimental).").Bool()
multi = kingpin.Flag("multi", "Enable multi-server mode.").Short('m').Bool()
thread = kingpin.Flag("thread", "Set the number of concurrent connections.").Short('t').Int()
search = kingpin.Flag("search", "Fuzzy search servers by a keyword.").String()
userAgent = kingpin.Flag("ua", "Set the user-agent header for the speedtest.").String()
noDownload = kingpin.Flag("no-download", "Disable download test.").Bool()
noUpload = kingpin.Flag("no-upload", "Disable upload test.").Bool()
pingMode = kingpin.Flag("ping-mode", "Select a method for Ping. (support icmp/tcp/http)").Default("http").String()
pingMode = kingpin.Flag("ping-mode", "Select a method for Ping (support icmp/tcp/http).").Default("http").String()
unit = kingpin.Flag("unit", "Set human-readable and auto-scaled rate units for output (options: decimal-bits/decimal-bytes/binary-bits/binary-bytes).").Short('u').String()
debug = kingpin.Flag("debug", "Enable debug mode.").Short('d').Bool()
)

Expand All @@ -41,6 +42,8 @@ func main() {
kingpin.Parse()
AppInfo()

speedtest.SetUnit(parseUnit(*unit))

// 0. speed test setting
var speedtestClient = speedtest.New(speedtest.WithUserConfig(
&speedtest.UserConfig{
Expand Down Expand Up @@ -128,12 +131,12 @@ func main() {
accEcho := newAccompanyEcho(server, time.Millisecond*500)
taskManager.Run("Download", func(task *Task) {
accEcho.Run()
speedtestClient.SetCallbackDownload(func(downRate float64) {
speedtestClient.SetCallbackDownload(func(downRate speedtest.ByteRate) {
lc := accEcho.CurrentLatency()
if lc == 0 {
task.Printf("Download: %.2fMbps (latency: --)", downRate*8/1000000)
task.Printf("Download: %s (Latency: --)", downRate)
} else {
task.Printf("Download: %.2fMbps (latency: %dms)", downRate*8/1000000, lc/1000000)
task.Printf("Download: %s (Latency: %dms)", downRate, lc/1000000)
}
})
if *multi {
Expand All @@ -143,18 +146,18 @@ func main() {
}
accEcho.Stop()
mean, _, std, minL, maxL := speedtest.StandardDeviation(accEcho.Latencies())
task.Printf("Download: %.2fMbps (used: %.2fMB) (latency: %dms jitter: %dms min: %dms max: %dms)", server.DLSpeed*8/1000000, float64(server.Context.Manager.GetTotalDownload())/1000/1000, mean/1000000, std/1000000, minL/1000000, maxL/1000000)
task.Printf("Download: %s (Used: %.2fMB) (Latency: %dms Jitter: %dms Min: %dms Max: %dms)", server.DLSpeed, float64(server.Context.Manager.GetTotalDownload())/1000/1000, mean/1000000, std/1000000, minL/1000000, maxL/1000000)
task.Complete()
})

taskManager.Run("Upload", func(task *Task) {
accEcho.Run()
speedtestClient.SetCallbackUpload(func(upRate float64) {
speedtestClient.SetCallbackUpload(func(upRate speedtest.ByteRate) {
lc := accEcho.CurrentLatency()
if lc == 0 {
task.Printf("Upload: %.2fMbps (latency: --)", upRate*8/1000000)
task.Printf("Upload: %s (Latency: --)", upRate)
} else {
task.Printf("Upload: %.2fMbps (latency: %dms)", upRate*8/1000000, lc/1000000)
task.Printf("Upload: %s (Latency: %dms)", upRate, lc/1000000)
}
})
if *multi {
Expand All @@ -164,7 +167,7 @@ func main() {
}
accEcho.Stop()
mean, _, std, minL, maxL := speedtest.StandardDeviation(accEcho.Latencies())
task.Printf("Upload: %.2fMbps (used: %.2fMB) (latency: %dms jitter: %dms min: %dms max: %dms)", server.ULSpeed*8/1000000, float64(server.Context.Manager.GetTotalUpload())/1000/1000, mean/1000000, std/1000000, minL/1000000, maxL/1000000)
task.Printf("Upload: %s (Used: %.2fMB) (Latency: %dms Jitter: %dms Min: %dms Max: %dms)", server.ULSpeed, float64(server.Context.Manager.GetTotalUpload())/1000/1000, mean/1000000, std/1000000, minL/1000000, maxL/1000000)
task.Complete()
})
taskManager.Reset()
Expand Down Expand Up @@ -243,6 +246,21 @@ func showServerList(servers speedtest.Servers) {
}
}

func parseUnit(str string) speedtest.UnitType {
str = strings.ToLower(str)
if str == "decimal-bits" {
return speedtest.UnitTypeDecimalBits
} else if str == "decimal-bytes" {
return speedtest.UnitTypeDecimalBytes
} else if str == "binary-bits" {
return speedtest.UnitTypeBinaryBits
} else if str == "binary-bytes" {
return speedtest.UnitTypeBinaryBytes
} else {
return speedtest.UnitTypeDefaultMbps
}
}

func parseProto(str string) speedtest.Proto {
str = strings.ToLower(str)
if str == "icmp" {
Expand Down
26 changes: 13 additions & 13 deletions speedtest/data_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ type Manager interface {
GetEWMADownloadRate() float64
GetEWMAUploadRate() float64

SetCallbackDownload(callback func(downRate float64))
SetCallbackUpload(callback func(upRate float64))
SetCallbackDownload(callback func(downRate ByteRate))
SetCallbackUpload(callback func(upRate ByteRate))

RegisterDownloadHandler(fn func()) *TestDirection
RegisterUploadHandler(fn func()) *TestDirection
Expand Down Expand Up @@ -95,14 +95,14 @@ type DataManager struct {
}

type TestDirection struct {
TestType int // test type
manager *DataManager // manager
totalDataVolume int64 // total send/receive data volume
RateSequence []int64 // rate history sequence
welford *internal.Welford // std/EWMA/mean
captureCallback func(realTimeRate float64) // user callback
closeFunc func() // close func
*funcGroup // actually exec function
TestType int // test type
manager *DataManager // manager
totalDataVolume int64 // total send/receive data volume
RateSequence []int64 // rate history sequence
welford *internal.Welford // std/EWMA/mean
captureCallback func(realTimeRate ByteRate) // user callback
closeFunc func() // close func
*funcGroup // actually exec function
}

func (dm *DataManager) NewDataDirection(testType int) *TestDirection {
Expand All @@ -126,13 +126,13 @@ func NewDataManager() *DataManager {
return ret
}

func (dm *DataManager) SetCallbackDownload(callback func(downRate float64)) {
func (dm *DataManager) SetCallbackDownload(callback func(downRate ByteRate)) {
if dm.download != nil {
dm.download.captureCallback = callback
}
}

func (dm *DataManager) SetCallbackUpload(callback func(upRate float64)) {
func (dm *DataManager) SetCallbackUpload(callback func(upRate ByteRate)) {
if dm.upload != nil {
dm.upload.captureCallback = callback
}
Expand Down Expand Up @@ -268,7 +268,7 @@ func (td *TestDirection) rateCapture() chan bool {
}
// reports the current rate at the given rate
if td.captureCallback != nil {
td.captureCallback(td.welford.EWMA())
td.captureCallback(ByteRate(td.welford.EWMA()))
}
case stop := <-stopCapture:
if stop {
Expand Down
8 changes: 4 additions & 4 deletions speedtest/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (s *Server) MultiDownloadTestContext(ctx context.Context, servers Servers)
return ErrorUninitializedManager
}
td.Start(cancel, mainIDIndex) // block here
s.DLSpeed = td.manager.GetEWMADownloadRate()
s.DLSpeed = ByteRate(td.manager.GetEWMADownloadRate())
return nil
}

Expand Down Expand Up @@ -87,7 +87,7 @@ func (s *Server) MultiUploadTestContext(ctx context.Context, servers Servers) er
return ErrorUninitializedManager
}
td.Start(cancel, mainIDIndex) // block here
s.ULSpeed = td.manager.GetEWMAUploadRate()
s.ULSpeed = ByteRate(td.manager.GetEWMAUploadRate())
return nil
}

Expand All @@ -112,7 +112,7 @@ func (s *Server) downloadTestContext(ctx context.Context, downloadRequest downlo
_ = downloadRequest(_context, s, 3)
}).Start(cancel, 0)
duration := time.Since(start)
s.DLSpeed = s.Context.GetEWMADownloadRate()
s.DLSpeed = ByteRate(s.Context.GetEWMADownloadRate())
s.TestDuration.Download = &duration
s.testDurationTotalCount()
return nil
Expand All @@ -139,7 +139,7 @@ func (s *Server) uploadTestContext(ctx context.Context, uploadRequest uploadFunc
_ = uploadRequest(_context, s, 4)
}).Start(cancel, 0)
duration := time.Since(start)
s.ULSpeed = s.Context.GetEWMAUploadRate()
s.ULSpeed = ByteRate(s.Context.GetEWMAUploadRate())
s.TestDuration.Upload = &duration
s.testDurationTotalCount()
return nil
Expand Down
4 changes: 2 additions & 2 deletions speedtest/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ type Server struct {
MaxLatency time.Duration `json:"max_latency"`
MinLatency time.Duration `json:"min_latency"`
Jitter time.Duration `json:"jitter"`
DLSpeed float64 `json:"dl_speed"`
ULSpeed float64 `json:"ul_speed"`
DLSpeed ByteRate `json:"dl_speed"`
ULSpeed ByteRate `json:"ul_speed"`
TestDuration TestDuration `json:"test_duration"`

Context *Speedtest `json:"-"`
Expand Down
Loading

0 comments on commit 8da0bfb

Please sign in to comment.