Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support unix like output #201

Merged
merged 1 commit into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading