Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
The prober hits the frontend with a fixed set of URLs. It is designed to be run periodically and to export metrics for altering and performance tracking.
package main

import (
	
	
	
	
	
	
	
	
	

	
	
	
	
	
	
	
	
	
	
)

var credsFile = flag.String("creds", "", "filename for credentials, when running locally")
A Probe represents a single HTTP GET request.
A short, stable name for the probe. Since it is used in metrics, it shouldn't be too long and should stay the same even if actual URL changes.
The part of the URL after the host:port.
Whether or not to set a header that causes the frontend to skip the redis cache.
	BypassCache bool
}

var probes = []*Probe{
	{
		Name:        "home",
		RelativeURL: "",
	},
	{
		Name:        "search-help",
		RelativeURL: "search-help",
	},
	{
		Name:        "license-policy",
		RelativeURL: "license-policy",
	},
	{
		Name:        "pkg-firestore",
		RelativeURL: "cloud.google.com/go/firestore",
	},
	{
		Name:        "pkg-firestore-nocache",
		RelativeURL: "cloud.google.com/go/firestore",
		BypassCache: true,
	},
	{
		Name:        "pkg-firestore-versions",
		RelativeURL: "cloud.google.com/go/firestore?tab=versions",
	},
	{
		Name:        "pkg-firestore-versions-nocache",
		RelativeURL: "cloud.google.com/go/firestore?tab=versions",
		BypassCache: true,
	},
	{
		Name:        "pkg-firestore-imports",
		RelativeURL: "cloud.google.com/go/firestore?tab=imports",
	},
	{
		Name:        "pkg-firestore-imports-nocache",
		RelativeURL: "cloud.google.com/go/firestore?tab=imports",
		BypassCache: true,
	},
	{
		Name:        "pkg-firestore-importedby",
		RelativeURL: "cloud.google.com/go/firestore?tab=importedby",
	},
	{
		Name:        "pkg-firestore-importedby-nocache",
		RelativeURL: "cloud.google.com/go/firestore?tab=importedby",
		BypassCache: true,
	},
	{
		Name:        "pkg-firestore-licenses",
		RelativeURL: "cloud.google.com/go/firestore?tab=licenses",
	},
	{
		Name:        "pkg-firestore-licenses-nocache",
		RelativeURL: "cloud.google.com/go/firestore?tab=licenses",
		BypassCache: true,
	},
	{
		Name:        "pkg-errors-importedby",
		RelativeURL: "github.com/pkg/errors?tab=importedby",
	},
	{
		Name:        "pkg-errors-importedby-nocache",
		RelativeURL: "github.com/pkg/errors?tab=importedby",
		BypassCache: true,
	},
	{
		Name:        "pkg-hortonworks-versions",
		RelativeURL: "github.com/hortonworks/cb-cli?tab=versions",
		BypassCache: true,
	},
	{
		Name:        "pkg-xtoolsgo-directory",
		RelativeURL: "golang.org/x/tools/go",
		BypassCache: true,
	},
	{
		Name:        "xtools-nocache",
		RelativeURL: "golang.org/x/tools",
		BypassCache: true,
	},
	{
		Name:        "xtools-versions-nocache",
		RelativeURL: "golang.org/x/tools?tab=versions",
		BypassCache: true,
	},
	{
		Name:        "xtools-licenses-nocache",
		RelativeURL: "golang.org/x/tools?tab=licenses",
		BypassCache: true,
	},
	{
		Name:        "search-github",
		RelativeURL: "search?q=github",
	},
	{
		Name:        "search-github-nocache",
		RelativeURL: "search?q=github",
		BypassCache: true,
	},
}

Validate that probe names are unique.
	 := map[string]bool{}
	for ,  := range probes {
		if [.Name] {
			log.Fatalf(context.Background(), "duplicate probe name %q", .Name)
		}
		[.Name] = true
	}
}

var (
	baseURL        string
	authValue      string
	client         *http.Client
	metricExporter *stackdriver.Exporter
	metricReader   *metricexport.Reader
	keyName        = tag.MustNewKey("probe.name")
	keyStatus      = tag.MustNewKey("probe.status")

	firstByteLatency = stats.Float64(
		"go-discovery/first_byte_latency",
		"Time between first byte of request headers sent to first byte of response received, or error",
		stats.UnitMilliseconds,
	)

	firstByteLatencyDistribution = &view.View{
		Name:        "go-discovery/prober/first_byte_latency",
		Measure:     firstByteLatency,
		Aggregation: ochttp.DefaultLatencyDistribution,
		Description: "first-byte latency, by probe name and response status",
		TagKeys:     []tag.Key{keyName, keyStatus},
	}

	probeCount = &view.View{
		Name:        "go-discovery/prober/probe_count",
		Measure:     firstByteLatency,
		Aggregation: view.Count(),
		Description: "probe count, by probe name and response status",
		TagKeys:     []tag.Key{keyName, keyStatus},
	}
)

func () {
	flag.Usage = func() {
		fmt.Fprintf(flag.CommandLine.Output(), "usage: %s [flags]\n", os.Args[0])
		flag.PrintDefaults()
	}
	flag.Parse()
	 := context.Background()

	baseURL = config.GetEnv("PROBER_BASE_URL", "")
	if baseURL == "" {
		log.Fatal(, "must set PROBER_BASE_URL")
	}
	log.Infof(, "base URL %s", baseURL)

	authValue = config.GetEnv("GO_DISCOVERY_PROBER_AUTH_VALUE", "")
	if authValue == "" {
		log.Warningf(, "missing GO_DISCOVERY_PROBER_AUTH_VALUE; won't bypass cache or quota")
	}

	,  := config.Init()
	if  != nil {
		log.Fatal(, )
	}
	.Dump(os.Stderr)

	log.SetLevel(.LogLevel)
	if .OnGCP() {
		if ,  := log.UseStackdriver(, , "prober-log");  != nil {
			log.Fatal(, )
		}
	}

	var  []byte
	if *credsFile != "" {
		,  = ioutil.ReadFile(*credsFile)
		if  != nil {
			log.Fatal(, )
		}
If there is no creds file, use application default credentials. On AppEngine, this will use the AppEngine service account, which has the necessary IAP permission.
	client,  = auth.NewClient(, , os.Getenv("GO_DISCOVERY_USE_EXP_AUTH") == "true")
	if  != nil {
		log.Fatal(, )
	}

	if  := view.Register(firstByteLatencyDistribution, probeCount);  != nil {
		log.Fatalf(, "view.Register: %v", )
	}
	metricExporter,  = dcensus.NewViewExporter()
	if  != nil {
		log.Fatal(, )
	}
To export metrics immediately, we use a metric reader. See runProbes, below.
	metricReader = metricexport.NewReader()

	http.HandleFunc("/favicon.ico", func( http.ResponseWriter,  *http.Request) {
		http.ServeFile(, , "content/static/img/favicon.ico")
	})
	http.HandleFunc("/", handleProbe)
	http.HandleFunc("/check", handleCheck)

	 := .HostAddr("localhost:8080")
	log.Infof(, "Listening on addr %s", )
	log.Fatal(, http.ListenAndServe(, nil))
}
ProbeStatus records the result if a single probe attempt
type ProbeStatus struct {
	Probe   *Probe
	Code    int    // status code of response
	Text    string // describes what happened: "OK", or "FAILED" with a reason
	Latency int    // in milliseconds
}
handleProbe runs probes and displays their results. It always returns a 200.
func ( http.ResponseWriter,  *http.Request) {
	 := runProbes(.Context())
	var  = struct {
		    time.Time
		  string
		 []*ProbeStatus
	}{
		:    time.Now(),
		:  baseURL,
		: ,
	}
	var  bytes.Buffer
	 := statusTemplate.Execute(&, )
	if  != nil {
		http.Error(, fmt.Sprintf("template execution failed: %v", ), http.StatusInternalServerError)
	} else {
		.WriteTo() // ignore error; nothing we can do about it
	}
}
handleCheck runs probes, and returns a 200 only if they all succeed. Otherwise it returns the status code of the first failing response.
func ( http.ResponseWriter,  *http.Request) {
	 := runProbes(.Context())
	var  []*ProbeStatus
	for ,  := range  {
		if .Code != http.StatusOK {
			 = append(, )
		}
	}
	.Header().Set("Content-Type", "text/plain")
	if len() == 0 {
		fmt.Fprintf(, "All probes succeeded.\n")
	} else {
		.WriteHeader([0].Code)
		fmt.Fprintf(, "SOME PROBES FAILED:\n")
		for ,  := range  {
			fmt.Fprintf(, "%3d /%s\n", .Code, .Probe.RelativeURL)
		}
	}
}

func ( context.Context) []*ProbeStatus {
	var  []*ProbeStatus
	for ,  := range probes {
		 := runProbe(, )
		 = append(, )
	}
	metricReader.ReadAndExport(metricExporter)
	metricExporter.Flush()
	log.Info(, "metrics exported to StackDriver")
	return 
}

func ( context.Context,  *Probe) *ProbeStatus {
	 := &ProbeStatus{
		Probe: ,
		Code:  499, // not a real code; means request never sent
	}
	 := baseURL + "/" + .RelativeURL
	log.Infof(, "running %s = %s", .Name, )
	defer func() {
		log.Infof(, "%s in %dms", .Text, .Latency)
	}()

	,  := context.WithTimeout(context.Background(), 15*time.Second)
	defer ()
	,  := http.NewRequest(http.MethodGet, , nil)
	if authValue != "" {
		if .BypassCache {
			.Header.Set(config.BypassCacheAuthHeader, authValue)
		}
		.Header.Set(config.BypassQuotaAuthHeader, authValue)
	}
	if  != nil {
		.Text = fmt.Sprintf("FAILED making request: %v", )
		return 
	}
	 := time.Now()
	,  := client.Do(.WithContext())

	 := float64(time.Since()) / float64(time.Millisecond)
	.Latency = int()
	 := func( string) {
		stats.RecordWithTags(, []tag.Mutator{
			tag.Upsert(keyName, .Name),
			tag.Upsert(keyStatus, ),
		}, firstByteLatency.M())
	}

	if  != nil {
		.Text = fmt.Sprintf("FAILED call: %v", )
		("FAILED call")
		return 
	}
	defer .Body.Close()
	.Code = .StatusCode
	if .StatusCode != http.StatusOK {
		.Text = fmt.Sprintf("FAILED with status %s", .Status)
		(.Status)
		return 
	}
	,  := ioutil.ReadAll(.Body)
	if  != nil {
		.Text = fmt.Sprintf("FAILED reading body: %v", )
		("FAILED read body")
		return 
	}
	if !bytes.Contains(, []byte("go.dev")) {
		.Text = "FAILED: body does not contain 'go.dev'"
		("FAILED wrong body")
		return 
	}
	.Text = "OK"
	("200 OK")
	return 
}

var statusTemplate = template.Must(template.New("").Parse(`
<html>
  <head>
    <title>Go Discovery Prober</title>
  </head>
  <body>
    <h1>Probes at at {{with .Start}}{{.Format "2006-1-2 15:04"}}{{end}}</h1>
    Base URL: {{.BaseURL}}<br/>
    <table cellspacing="10rem">
      <tr><th>Name</th><th>URL</th><th>Latency (ms)</th><th>Status</th></tr>
      {{range .Statuses}}
        <tr>
          <td>{{.Probe.Name}}</td>
          <td>{{.Probe.RelativeURL}}</td>
          <td>{{.Latency}}</td>
          <td>{{.Text}}</td>
        </tr>
      {{end}}
    </table>
  </body>
</html>