Skip to content

Commit

Permalink
feat: support unix like output (#201)
Browse files Browse the repository at this point in the history
  • Loading branch information
r3inbowari authored May 9, 2024
1 parent 300b4d4 commit e212e1f
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 83 deletions.
62 changes: 35 additions & 27 deletions speedtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ var (
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 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()
jsonOutput = kingpin.Flag("json", "Output results in json like format.").Bool()
unixOutput = kingpin.Flag("unix", "Output results in unix like format.").Bool()
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()
Expand All @@ -46,6 +47,11 @@ func main() {

speedtest.SetUnit(parseUnit(*unit))

// start unix output for saving mode by default.
if *savingMode && !*jsonOutput && !*unixOutput {
*unixOutput = true
}

// 0. speed test setting
var speedtestClient = speedtest.New(speedtest.WithUserConfig(
&speedtest.UserConfig{
Expand All @@ -60,8 +66,6 @@ func main() {
CityFlag: *city,
LocationFlag: *location,
Keyword: *search,
NoDownload: *noDownload,
NoUpload: *noUpload,
}))

if *showCityList {
Expand All @@ -70,7 +74,7 @@ func main() {
}

// 1. retrieving user information
taskManager := InitTaskManager(!*jsonOutput)
taskManager := InitTaskManager(*jsonOutput, *unixOutput)
taskManager.AsyncRun("Retrieving User Information", func(task *Task) {
u, err := speedtestClient.FetchUserInfo()
task.CheckError(err)
Expand Down Expand Up @@ -125,7 +129,7 @@ func main() {
taskManager.Println("Test Server: " + server.String())
taskManager.Run("Latency: --", func(task *Task) {
task.CheckError(server.PingTest(func(latency time.Duration) {
task.Printf("Latency: %v", latency)
task.Updatef("Latency: %v", latency)
}))
task.Printf("Latency: %v Jitter: %v Min: %v Max: %v", server.Latency, server.Jitter, server.MinLatency, server.MaxLatency)
task.Complete()
Expand All @@ -136,27 +140,31 @@ func main() {
analyzer, err = speedtest.NewPacketLossAnalyzer(&speedtest.PacketLossAnalyzerOptions{
SourceInterface: *source,
})
server.PacketLoss = -1.0 // N/A as default

packetLossAnalyzerCtx, packetLossAnalyzerCancel := context.WithTimeout(context.Background(), time.Second*40)
go func() {
err = analyzer.RunWithContext(packetLossAnalyzerCtx, server.Host, func(packetLoss *transport.PLoss) {
server.PacketLoss = packetLoss.Loss()
})
if errors.Is(err, transport.ErrUnsupported) {
packetLossAnalyzerCancel() // cancel early
}
}()
taskManager.Run("Packet Loss Analyzer", func(task *Task) {
go func() {
err = analyzer.RunWithContext(packetLossAnalyzerCtx, server.Host, func(packetLoss *transport.PLoss) {
server.PacketLoss = *packetLoss
})
if errors.Is(err, transport.ErrUnsupported) {
packetLossAnalyzerCancel() // cancel early
}
}()
task.Println("Packet Loss Analyzer: Running in background (<= 30 Sec)")
task.Complete()
})

// 3.1 create accompany Echo
accEcho := newAccompanyEcho(server, time.Millisecond*500)
taskManager.Run("Download", func(task *Task) {
taskManager.RunWithTrigger(!*noDownload, "Download", func(task *Task) {
accEcho.Run()
speedtestClient.SetCallbackDownload(func(downRate speedtest.ByteRate) {
lc := accEcho.CurrentLatency()
if lc == 0 {
task.Printf("Download: %s (Latency: --)", downRate)
task.Updatef("Download: %s (Latency: --)", downRate)
} else {
task.Printf("Download: %s (Latency: %dms)", downRate, lc/1000000)
task.Updatef("Download: %s (Latency: %dms)", downRate, lc/1000000)
}
})
if *multi {
Expand All @@ -170,14 +178,14 @@ func main() {
task.Complete()
})

taskManager.Run("Upload", func(task *Task) {
taskManager.RunWithTrigger(!*noUpload, "Upload", func(task *Task) {
accEcho.Run()
speedtestClient.SetCallbackUpload(func(upRate speedtest.ByteRate) {
lc := accEcho.CurrentLatency()
if lc == 0 {
task.Printf("Upload: %s (Latency: --)", upRate)
task.Updatef("Upload: %s (Latency: --)", upRate)
} else {
task.Printf("Upload: %s (Latency: %dms)", upRate, lc/1000000)
task.Updatef("Upload: %s (Latency: %dms)", upRate, lc/1000000)
}
})
if *multi {
Expand All @@ -190,16 +198,16 @@ func main() {
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()
speedtestClient.Manager.Reset()

if *noUpload && *noDownload {
time.Sleep(time.Second * 30)
}
packetLossAnalyzerCancel()
if !*jsonOutput {
if server.PacketLoss != -1 {
fmt.Printf(" Packet Loss: %.2f%%", server.PacketLoss*100)
} else {
fmt.Printf(" Packet Loss: N/A")
}
taskManager.Println(server.PacketLoss.String())
}
taskManager.Reset()
speedtestClient.Manager.Reset()
}
taskManager.Stop()

Expand Down
16 changes: 0 additions & 16 deletions speedtest/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ var (
)

func (s *Server) MultiDownloadTestContext(ctx context.Context, servers Servers) error {
if s.Context.config.NoDownload {
dbg.Println("Download test disabled")
return nil
}
ss := servers.Available()
if ss.Len() == 0 {
return errors.New("not found available servers")
Expand Down Expand Up @@ -69,10 +65,6 @@ func (s *Server) MultiDownloadTestContext(ctx context.Context, servers Servers)
}

func (s *Server) MultiUploadTestContext(ctx context.Context, servers Servers) error {
if s.Context.config.NoUpload {
dbg.Println("Upload test disabled")
return nil
}
ss := servers.Available()
if ss.Len() == 0 {
return errors.New("not found available servers")
Expand Down Expand Up @@ -118,10 +110,6 @@ func (s *Server) DownloadTestContext(ctx context.Context) error {
}

func (s *Server) downloadTestContext(ctx context.Context, downloadRequest downloadFunc) error {
if s.Context.config.NoDownload {
dbg.Println("Download test disabled")
return nil
}
var errorTimes int64 = 0
var requestTimes int64 = 0
start := time.Now()
Expand Down Expand Up @@ -153,10 +141,6 @@ func (s *Server) UploadTestContext(ctx context.Context) error {
}

func (s *Server) uploadTestContext(ctx context.Context, uploadRequest uploadFunc) error {
if s.Context.config.NoUpload {
dbg.Println("Upload test disabled")
return nil
}
var errorTimes int64 = 0
var requestTimes int64 = 0
start := time.Now()
Expand Down
35 changes: 18 additions & 17 deletions speedtest/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/xml"
"errors"
"fmt"
"github.com/showwin/speedtest-go/speedtest/transport"
"math"
"net/http"
"net/url"
Expand Down Expand Up @@ -35,23 +36,23 @@ var (

// Server information
type Server struct {
URL string `xml:"url,attr" json:"url"`
Lat string `xml:"lat,attr" json:"lat"`
Lon string `xml:"lon,attr" json:"lon"`
Name string `xml:"name,attr" json:"name"`
Country string `xml:"country,attr" json:"country"`
Sponsor string `xml:"sponsor,attr" json:"sponsor"`
ID string `xml:"id,attr" json:"id"`
Host string `xml:"host,attr" json:"host"`
Distance float64 `json:"distance"`
Latency time.Duration `json:"latency"`
MaxLatency time.Duration `json:"max_latency"`
MinLatency time.Duration `json:"min_latency"`
Jitter time.Duration `json:"jitter"`
DLSpeed ByteRate `json:"dl_speed"`
ULSpeed ByteRate `json:"ul_speed"`
TestDuration TestDuration `json:"test_duration"`
PacketLoss float64 `json:"packet_loss"`
URL string `xml:"url,attr" json:"url"`
Lat string `xml:"lat,attr" json:"lat"`
Lon string `xml:"lon,attr" json:"lon"`
Name string `xml:"name,attr" json:"name"`
Country string `xml:"country,attr" json:"country"`
Sponsor string `xml:"sponsor,attr" json:"sponsor"`
ID string `xml:"id,attr" json:"id"`
Host string `xml:"host,attr" json:"host"`
Distance float64 `json:"distance"`
Latency time.Duration `json:"latency"`
MaxLatency time.Duration `json:"max_latency"`
MinLatency time.Duration `json:"min_latency"`
Jitter time.Duration `json:"jitter"`
DLSpeed ByteRate `json:"dl_speed"`
ULSpeed ByteRate `json:"ul_speed"`
TestDuration TestDuration `json:"test_duration"`
PacketLoss transport.PLoss `json:"packet_loss"`

Context *Speedtest `json:"-"`
}
Expand Down
3 changes: 0 additions & 3 deletions speedtest/speedtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ type UserConfig struct {
Location *Location

Keyword string // Fuzzy search

NoDownload bool
NoUpload bool
}

func parseAddr(addr string) (string, string) {
Expand Down
29 changes: 19 additions & 10 deletions speedtest/transport/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,27 @@ func (client *Client) InitPacketLoss() error {
return client.Write(initPacket)
}

// PLoss Packet loss statistics
// The packet loss here generally refers to uplink packet loss.
// We use the following formula to calculate the packet loss:
// packetLoss = [1 - (Sent - Dup) / (Max + 1)] * 100%
type PLoss struct {
Sent int
Dup int
MaximumReceived int
Sent int `json:"sent"` // Number of sent packets acknowledged by the remote.
Dup int `json:"dup"` // Number of duplicate packets acknowledged by the remote.
Max int `json:"max"` // The maximum index value received by the remote.
}

func (p *PLoss) String() string {
return fmt.Sprintf("Sent: %d, DupPacket: %d, MaximumReceived: %d", p.Sent, p.Dup, p.MaximumReceived)
func (p PLoss) String() string {
if p.Sent == 0 {
// if p.Sent == 0, maybe all data is dropped by the upper gateway.
// we believe this feature is not applicable on this server now.
return "Packet Loss: N/A"
}
return fmt.Sprintf("Packet Loss: %.2f%% (Sent: %d/Dup: %d/Max: %d)", p.Loss()*100, p.Sent, p.Dup, p.Max)
}

func (p *PLoss) Loss() float64 {
return 1 - (float64(p.Sent-p.Dup))/float64(p.MaximumReceived+1)
func (p PLoss) Loss() float64 {
return 1 - (float64(p.Sent-p.Dup))/float64(p.Max+1)
}

func (client *Client) PacketLoss() (*PLoss, error) {
Expand Down Expand Up @@ -211,9 +220,9 @@ func (client *Client) PacketLoss() (*PLoss, error) {
return nil, err
}
return &PLoss{
Sent: x0,
Dup: x1,
MaximumReceived: x2,
Sent: x0,
Dup: x1,
Max: x2,
}, nil
}

Expand Down
Loading

0 comments on commit e212e1f

Please sign in to comment.