Skip to content

Commit

Permalink
x
Browse files Browse the repository at this point in the history
  • Loading branch information
bassosimone committed Jan 29, 2024
1 parent a48af06 commit a496794
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 550 deletions.
88 changes: 49 additions & 39 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"runtime"
"time"

"github.com/m-lab/ndt5-client-go/mlabns"
"github.com/m-lab/locate/api/locate"
v2 "github.com/m-lab/locate/api/v2"
"github.com/neubot/dash/internal"
"github.com/neubot/dash/model"
"github.com/neubot/dash/spec"
Expand All @@ -41,6 +42,11 @@ var (
errHTTPRequestFailed = errors.New("HTTP request failed")
)

// locator is an interface used to locate a server.
type locator interface {
Nearest(ctx context.Context, service string) ([]v2.Target, error)
}

type dependencies struct {
Collect func(ctx context.Context, authorization string) error
Download func(
Expand All @@ -50,7 +56,7 @@ type dependencies struct {
HTTPNewRequest func(method, url string, body io.Reader) (*http.Request, error)
IOUtilReadAll func(r io.Reader) ([]byte, error)
JSONMarshal func(v interface{}) ([]byte, error)
Locate func(ctx context.Context) (string, error)
Locator locator
Loop func(ctx context.Context, ch chan<- model.ClientResults)
Negotiate func(ctx context.Context) (model.NegotiateResponse, error)
}
Expand All @@ -66,10 +72,6 @@ type Client struct {
// initialized by the NewClient constructor.
ClientVersion string

// FQDN is the server of the server to use. If the FQDN is not
// specified, we'll use mlab-ns to discover a server.
FQDN string

// HTTPClient is the HTTP client used by this implementation. This field
// is initialized by the NewClient to http.DefaultClient.
HTTPClient *http.Client
Expand All @@ -78,13 +80,9 @@ type Client struct {
// NewClient constructor to a do-nothing logger.
Logger model.Logger

// MLabNSClient is the mlabns client. We'll configure it with a suitable
// implementation in NewClient, but you may override it.
MLabNSClient *mlabns.Client

// Scheme is the protocol scheme to use. By default NewClient configures
// it to "https", but you can override it to "http".
Scheme string
// NegotiateURL is the URL used to negotiate. If this field is nil, we're
// going to use m-lab/locate's v2 API to obtain a suitable URL.
NegotiateURL *url.URL

begin time.Time
clientResults []model.ClientResults
Expand All @@ -103,10 +101,6 @@ func (c *Client) httpClientDo(req *http.Request) (*http.Response, error) {
return c.HTTPClient.Do(req)
}

func (c *Client) locate(ctx context.Context) (string, error) {
return c.MLabNSClient.Query(ctx)
}

// New creates a new Client instance using the specified
// client application name and version.
func New(clientName, clientVersion string) (client *Client) {
Expand All @@ -116,20 +110,18 @@ func New(clientName, clientVersion string) (client *Client) {
ClientVersion: clientVersion,
HTTPClient: http.DefaultClient,
Logger: internal.NoLogger{},
MLabNSClient: mlabns.NewClient("neubot", ua),
begin: time.Now(),
numIterations: 15,
Scheme: "https",
userAgent: ua,
}
client.deps = dependencies{
Collect: client.collect,
Download: client.download,
HTTPClientDo: client.httpClientDo,
HTTPNewRequest: http.NewRequest,
IOUtilReadAll: ioutil.ReadAll,
IOUtilReadAll: io.ReadAll,
JSONMarshal: json.Marshal,
Locate: client.locate,
Locator: locate.NewClient(ua),
Loop: client.loop,
Negotiate: client.negotiate,
}
Expand All @@ -147,11 +139,8 @@ func (c *Client) negotiate(ctx context.Context) (model.NegotiateResponse, error)
if err != nil {
return negotiateResponse, err
}
URL := c.NegotiateURL
c.Logger.Debugf("dash: body: %s", string(data))
var URL url.URL
URL.Scheme = c.Scheme
URL.Host = c.FQDN
URL.Path = spec.NegotiatePath
req, err := c.deps.HTTPNewRequest("POST", URL.String(), bytes.NewReader(data))
if err != nil {
return negotiateResponse, err
Expand Down Expand Up @@ -190,6 +179,15 @@ func (c *Client) negotiate(ctx context.Context) (model.NegotiateResponse, error)
return negotiateResponse, nil
}

// makeDownloadURL makes the download URL from the negotiate URL.
func makeDownloadURL(negotiateURL *url.URL, path string) *url.URL {
return &url.URL{
Scheme: negotiateURL.Scheme,
Host: negotiateURL.Host,
Path: path,
}
}

// download implements the DASH test proper. We compute the number of bytes
// to request given the current rate, download the fake DASH segment, and
// then we return the measured performance of this segment to the caller. This
Expand All @@ -198,10 +196,7 @@ func (c *Client) download(
ctx context.Context, authorization string, current *model.ClientResults,
) error {
nbytes := (current.Rate * 1000 * current.ElapsedTarget) >> 3
var URL url.URL
URL.Scheme = c.Scheme
URL.Host = c.FQDN
URL.Path = fmt.Sprintf("%s%d", spec.DownloadPath, nbytes)
URL := makeDownloadURL(c.NegotiateURL, fmt.Sprintf("%s%d", spec.DownloadPath, nbytes))
req, err := c.deps.HTTPNewRequest("GET", URL.String(), nil)
if err != nil {
return err
Expand Down Expand Up @@ -238,6 +233,15 @@ func (c *Client) download(
return nil
}

// makeCollectURL makes the collect URL from the negotiate URL.
func makeCollectURL(negotiateURL *url.URL) *url.URL {
return &url.URL{
Scheme: negotiateURL.Scheme,
Host: negotiateURL.Host,
Path: spec.CollectPath,
}
}

// collect is the final phase of the test. We send to the server what we
// measured and we receive back what it has measured.
func (c *Client) collect(ctx context.Context, authorization string) error {
Expand All @@ -246,10 +250,7 @@ func (c *Client) collect(ctx context.Context, authorization string) error {
return err
}
c.Logger.Debugf("dash: body: %s", string(data))
var URL url.URL
URL.Scheme = c.Scheme
URL.Host = c.FQDN
URL.Path = spec.CollectPath
URL := makeCollectURL(c.NegotiateURL)
req, err := c.deps.HTTPNewRequest("POST", URL.String(), bytes.NewReader(data))
if err != nil {
return err
Expand Down Expand Up @@ -327,19 +328,28 @@ func (c *Client) loop(ctx context.Context, ch chan<- model.ClientResults) {
// results on the returned channel, then maybe it means the experiment
// has somehow worked. You can see if there has been any error during
// the experiment by using the Error function.
//
// This method MUTATES the [*Client] by setting .NegotiateURL using
// the mlab/locate v2 API if .NegotiateURL is originally nil.
func (c *Client) StartDownload(ctx context.Context) (<-chan model.ClientResults, error) {
if c.FQDN == "" {
c.Logger.Debug("dash: discovering server with mlabns")
fqdn, err := c.deps.Locate(ctx)
if c.NegotiateURL == nil {
c.Logger.Debug("dash: discovering server with locate v2")
targets, err := c.deps.Locator.Nearest(ctx, "neubot/dash")
if err != nil {
log.Printf("ELLIOT: %s", err.Error())
return nil, err
}
URL := targets[0].URLs["https:///negotiate/dash"]
parsed, err := url.Parse(URL)
if err != nil {
return nil, err
}
c.FQDN = fqdn
c.NegotiateURL = parsed
}
if ctx.Err() != nil {
return nil, ctx.Err() // this line is useful to write better tests
}
c.Logger.Debugf("dash: using server: %s", c.FQDN)
c.Logger.Debugf("dash: using server: %v", c.NegotiateURL)
ch := make(chan model.ClientResults)
go c.deps.Loop(ctx, ch)
return ch, nil
Expand Down
39 changes: 14 additions & 25 deletions cmd/dash-client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@
//
// Usage:
//
// dash-client -y [-hostname <name>] [-timeout <string>] [-scheme <scheme>]
// dash-client -y [-negotiate-url <URL>] [-timeout <string>]
//
// The `-y` flag indicates you have read the data policy and accept it.
//
// The `-hostname <name>` flag specifies to use the `name` hostname for
// performing the dash test. The default is to autodiscover a suitable
// server by using Measurement Lab's locate service.
// The `-negotiate-url <URL>` flag explicitly specifies the negotiate URL
// to use rather than discovering it using the mlab/locate v2 API.
//
// The `-timeout <string>` flag specifies the time after which the
// whole test is interrupted. The `<string>` is a string suitable to
// be passed to time.ParseDuration, e.g., "15s". The default is a large
// enough value that should be suitable for common conditions.
//
// The `-scheme <scheme>` flag allows to override the default scheme
// used for the test, i.e. "http". All DASH servers support that,
// future versions of the Go server will support "https".
//
// Additionally, passing any unrecognized flag, such as `-help`, will
// cause dash-client to print a brief help message.
package main
Expand All @@ -28,11 +23,11 @@ import (
"encoding/json"
"flag"
"fmt"
"net/url"
"os"
"time"

"github.com/apex/log"
"github.com/m-lab/go/flagx"
"github.com/m-lab/go/rtx"
"github.com/neubot/dash/client"
)
Expand All @@ -44,24 +39,12 @@ const (
)

var (
flagHostname = flag.String("hostname", "", "optional ndt7 server hostname")
flagTimeout = flag.Duration(
flagNegotiateURL = flag.String("negotiate-url", "", "optional negotiate URL")
flagTimeout = flag.Duration(
"timeout", defaultTimeout, "time after which the test is aborted")
flagScheme = flagx.Enum{
Options: []string{"https", "http"},
Value: "https",
}
flagY = flag.Bool("y", false, "I have read and accept the privacy policy at https://github.com/neubot/dash/blob/master/PRIVACY.md")
)

func init() {
flag.Var(
&flagScheme,
"scheme",
`Protocol scheme to use: either "https" (the default) or "http"`,
)
}

func realmain(ctx context.Context, client *client.Client, timeout time.Duration, onresult func()) error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
Expand Down Expand Up @@ -102,8 +85,14 @@ func internalmain(ctx context.Context) error {
}
client := client.New(clientName, clientVersion)
client.Logger = log.Log
client.FQDN = *flagHostname
client.Scheme = flagScheme.Value
if *flagNegotiateURL != "" {
URL, err := url.Parse(*flagNegotiateURL)
if err != nil {
fmt.Fprintf(os.Stderr, "dash: invalid URL: %s", *flagNegotiateURL)
os.Exit(1)
}
client.NegotiateURL = URL
}
return realmain(ctx, client, *flagTimeout, nil)
}

Expand Down
26 changes: 13 additions & 13 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
module github.com/neubot/dash

go 1.20
go 1.21

require (
github.com/apex/log v1.9.0
github.com/google/uuid v1.3.0
github.com/google/uuid v1.4.0
github.com/gorilla/handlers v1.5.1
github.com/m-lab/go v0.1.53
github.com/m-lab/ndt5-client-go v0.1.0
github.com/m-lab/go v0.1.69
)

require (
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/m-lab/locate v0.14.43
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.13.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 // indirect
google.golang.org/protobuf v1.28.1 // indirect
github.com/prometheus/client_golang v1.15.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
golang.org/x/sys v0.13.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)
Loading

0 comments on commit a496794

Please sign in to comment.