internal/telemetry: pass the http.Client to the ocagent

This will allow us to configure the connection at need.
It will also allow us to intercept the content for tests.

Change-Id: Id7d34f2d56f233eae112bea97cccab1f2a88de55
Reviewed-on: https://go-review.googlesource.com/c/tools/+/190798
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go
index e5b1368..d28c329 100644
--- a/internal/lsp/cmd/cmd.go
+++ b/internal/lsp/cmd/cmd.go
@@ -113,7 +113,10 @@
 // If no arguments are passed it will invoke the server sub command, as a
 // temporary measure for compatibility.
 func (app *Application) Run(ctx context.Context, args ...string) error {
-	export.AddExporters(ocagent.Connect(app.name, app.OCAgent))
+	ocConfig := ocagent.Discover()
+	//TODO: we should not need to adjust the discovered configuration
+	ocConfig.Address = app.OCAgent
+	export.AddExporters(ocagent.Connect(ocConfig))
 	app.Serve.app = app
 	if len(args) == 0 {
 		tool.Main(ctx, &app.Serve, args)
diff --git a/internal/telemetry/export/ocagent/ocagent.go b/internal/telemetry/export/ocagent/ocagent.go
index c0a323a..68b5074 100644
--- a/internal/telemetry/export/ocagent/ocagent.go
+++ b/internal/telemetry/export/ocagent/ocagent.go
@@ -14,6 +14,7 @@
 	"fmt"
 	"net/http"
 	"os"
+	"path/filepath"
 	"sync"
 	"time"
 
@@ -23,12 +24,28 @@
 	"golang.org/x/tools/internal/telemetry/tag"
 )
 
-const DefaultAddress = "http://localhost:55678"
-const exportRate = 2 * time.Second
+type Config struct {
+	Start   time.Time
+	Host    string
+	Process uint32
+	Client  *http.Client
+	Service string
+	Address string
+	Rate    time.Duration
+}
+
+// Discover finds the local agent to export to, it will return nil if there
+// is not one running.
+// TODO: Actually implement a discovery protocol rather than a hard coded address
+func Discover() *Config {
+	return &Config{
+		Address: "http://localhost:55678",
+	}
+}
 
 type exporter struct {
 	mu      sync.Mutex
-	address string
+	config  Config
 	node    *wire.Node
 	spans   []*wire.Span
 	metrics []*wire.Metric
@@ -37,35 +54,48 @@
 // Connect creates a process specific exporter with the specified
 // serviceName and the address of the ocagent to which it will upload
 // its telemetry.
-func Connect(service, address string) export.Exporter {
-	if address == "off" {
+func Connect(config *Config) export.Exporter {
+	if config == nil || config.Address == "off" {
 		return nil
 	}
-	hostname, _ := os.Hostname()
-	exporter := &exporter{
-		address: address,
-		node: &wire.Node{
-			Identifier: &wire.ProcessIdentifier{
-				HostName:       hostname,
-				Pid:            uint32(os.Getpid()),
-				StartTimestamp: convertTimestamp(time.Now()),
-			},
-			LibraryInfo: &wire.LibraryInfo{
-				Language:           wire.LanguageGo,
-				ExporterVersion:    "0.0.1",
-				CoreLibraryVersion: "x/tools",
-			},
-			ServiceInfo: &wire.ServiceInfo{
-				Name: service,
-			},
+	exporter := &exporter{config: *config}
+	if exporter.config.Start.IsZero() {
+		exporter.config.Start = time.Now()
+	}
+	if exporter.config.Host == "" {
+		hostname, _ := os.Hostname()
+		exporter.config.Host = hostname
+	}
+	if exporter.config.Process == 0 {
+		exporter.config.Process = uint32(os.Getpid())
+	}
+	if exporter.config.Client == nil {
+		exporter.config.Client = http.DefaultClient
+	}
+	if exporter.config.Service == "" {
+		exporter.config.Service = filepath.Base(os.Args[0])
+	}
+	if exporter.config.Rate == 0 {
+		exporter.config.Rate = 2 * time.Second
+	}
+	exporter.node = &wire.Node{
+		Identifier: &wire.ProcessIdentifier{
+			HostName:       exporter.config.Host,
+			Pid:            exporter.config.Process,
+			StartTimestamp: convertTimestamp(exporter.config.Start),
+		},
+		LibraryInfo: &wire.LibraryInfo{
+			Language:           wire.LanguageGo,
+			ExporterVersion:    "0.0.1",
+			CoreLibraryVersion: "x/tools",
+		},
+		ServiceInfo: &wire.ServiceInfo{
+			Name: exporter.config.Service,
 		},
 	}
-	if exporter.address == "" {
-		exporter.address = DefaultAddress
-	}
 	go func() {
-		for _ = range time.Tick(exportRate) {
-			exporter.flush()
+		for _ = range time.Tick(exporter.config.Rate) {
+			exporter.Flush()
 		}
 	}()
 	return exporter
@@ -87,7 +117,7 @@
 	e.metrics = append(e.metrics, convertMetric(data))
 }
 
-func (e *exporter) flush() {
+func (e *exporter) Flush() {
 	e.mu.Lock()
 	defer e.mu.Unlock()
 	spans := e.spans
@@ -117,19 +147,21 @@
 		errorInExport("ocagent failed to marshal message for %v: %v", endpoint, err)
 		return
 	}
-	uri := e.address + endpoint
+	uri := e.config.Address + endpoint
 	req, err := http.NewRequest("POST", uri, bytes.NewReader(blob))
 	if err != nil {
 		errorInExport("ocagent failed to build request for %v: %v", uri, err)
 		return
 	}
 	req.Header.Set("Content-Type", "application/json")
-	res, err := http.DefaultClient.Do(req)
+	res, err := e.config.Client.Do(req)
 	if err != nil {
 		errorInExport("ocagent failed to send message: %v \n", err)
 		return
 	}
-	res.Body.Close()
+	if res.Body != nil {
+		res.Body.Close()
+	}
 	return
 }