speedtest.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. // Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // Program speedtest provides the speedtest command. The reason to keep it separate from
  5. // the normal tailscale cli is because it is not yet ready to go in the tailscale binary.
  6. // It will be included in the tailscale cli after it has been added to tailscaled.
  7. // Example usage for client command: go run cmd/speedtest -host 127.0.0.1:20333 -t 5s
  8. // This will connect to the server on 127.0.0.1:20333 and start a 5 second download speedtest.
  9. // Example usage for server command: go run cmd/speedtest -s -host :20333
  10. // This will start a speedtest server on port 20333.
  11. package main
  12. import (
  13. "context"
  14. "errors"
  15. "flag"
  16. "fmt"
  17. "net"
  18. "os"
  19. "strconv"
  20. "text/tabwriter"
  21. "time"
  22. "github.com/peterbourgon/ff/v2/ffcli"
  23. "tailscale.com/net/speedtest"
  24. )
  25. // Runs the speedtest command as a commandline program
  26. func main() {
  27. args := os.Args[1:]
  28. if err := speedtestCmd.Parse(args); err != nil {
  29. fmt.Fprintln(os.Stderr, err.Error())
  30. os.Exit(1)
  31. }
  32. err := speedtestCmd.Run(context.Background())
  33. if errors.Is(err, flag.ErrHelp) {
  34. fmt.Fprintln(os.Stderr, speedtestCmd.ShortUsage)
  35. os.Exit(2)
  36. }
  37. if err != nil {
  38. fmt.Fprintln(os.Stderr, err.Error())
  39. os.Exit(1)
  40. }
  41. }
  42. // speedtestCmd is the root command. It runs either the server or client depending on the
  43. // flags passed to it.
  44. var speedtestCmd = &ffcli.Command{
  45. Name: "speedtest",
  46. ShortUsage: "speedtest [-host <host:port>] [-s] [-r] [-t <test duration>]",
  47. ShortHelp: "Run a speed test",
  48. FlagSet: (func() *flag.FlagSet {
  49. fs := flag.NewFlagSet("speedtest", flag.ExitOnError)
  50. fs.StringVar(&speedtestArgs.host, "host", ":20333", "host:port pair to connect to or listen on")
  51. fs.DurationVar(&speedtestArgs.testDuration, "t", speedtest.DefaultDuration, "duration of the speed test")
  52. fs.BoolVar(&speedtestArgs.runServer, "s", false, "run a speedtest server")
  53. fs.BoolVar(&speedtestArgs.reverse, "r", false, "run in reverse mode (server sends, client receives)")
  54. return fs
  55. })(),
  56. Exec: runSpeedtest,
  57. }
  58. var speedtestArgs struct {
  59. host string
  60. testDuration time.Duration
  61. runServer bool
  62. reverse bool
  63. }
  64. func runSpeedtest(ctx context.Context, args []string) error {
  65. if _, _, err := net.SplitHostPort(speedtestArgs.host); err != nil {
  66. var addrErr *net.AddrError
  67. if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
  68. // if no port is provided, append the default port
  69. speedtestArgs.host = net.JoinHostPort(speedtestArgs.host, strconv.Itoa(speedtest.DefaultPort))
  70. }
  71. }
  72. if speedtestArgs.runServer {
  73. listener, err := net.Listen("tcp", speedtestArgs.host)
  74. if err != nil {
  75. return err
  76. }
  77. fmt.Printf("listening on %v\n", listener.Addr())
  78. return speedtest.Serve(listener)
  79. }
  80. // Ensure the duration is within the allowed range
  81. if speedtestArgs.testDuration < speedtest.MinDuration || speedtestArgs.testDuration > speedtest.MaxDuration {
  82. return fmt.Errorf("test duration must be within %v and %v", speedtest.MinDuration, speedtest.MaxDuration)
  83. }
  84. dir := speedtest.Download
  85. if speedtestArgs.reverse {
  86. dir = speedtest.Upload
  87. }
  88. fmt.Printf("Starting a %s test with %s\n", dir, speedtestArgs.host)
  89. results, err := speedtest.RunClient(dir, speedtestArgs.testDuration, speedtestArgs.host)
  90. if err != nil {
  91. return err
  92. }
  93. w := tabwriter.NewWriter(os.Stdout, 12, 0, 0, ' ', tabwriter.TabIndent)
  94. fmt.Println("Results:")
  95. fmt.Fprintln(w, "Interval\t\tTransfer\t\tBandwidth\t\t")
  96. for _, r := range results {
  97. if r.Total {
  98. fmt.Fprintln(w, "-------------------------------------------------------------------------")
  99. }
  100. fmt.Fprintf(w, "%.2f-%.2f\tsec\t%.4f\tMBits\t%.4f\tMbits/sec\t\n", r.IntervalStart.Seconds(), r.IntervalEnd.Seconds(), r.MegaBits(), r.MBitsPerSecond())
  101. }
  102. w.Flush()
  103. return nil
  104. }