Initialize module and dependencies
This commit is contained in:
354
vendor/golang.org/x/telemetry/start.go
generated
vendored
Normal file
354
vendor/golang.org/x/telemetry/start.go
generated
vendored
Normal file
@@ -0,0 +1,354 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/telemetry/counter"
|
||||
"golang.org/x/telemetry/internal/crashmonitor"
|
||||
"golang.org/x/telemetry/internal/telemetry"
|
||||
"golang.org/x/telemetry/internal/upload"
|
||||
)
|
||||
|
||||
// Config controls the behavior of [Start].
|
||||
type Config struct {
|
||||
// ReportCrashes, if set, will enable crash reporting.
|
||||
// ReportCrashes uses the [debug.SetCrashOutput] mechanism, which is a
|
||||
// process-wide resource.
|
||||
// Do not make other calls to that function within your application.
|
||||
// ReportCrashes is a non-functional unless the program is built with go1.23+.
|
||||
ReportCrashes bool
|
||||
|
||||
// Upload causes this program to periodically upload approved counters
|
||||
// from the local telemetry database to telemetry.go.dev.
|
||||
//
|
||||
// This option has no effect unless the user has given consent
|
||||
// to enable data collection, for example by running
|
||||
// cmd/gotelemetry or affirming the gopls dialog.
|
||||
//
|
||||
// (This feature is expected to be used only by gopls.
|
||||
// Longer term, the go command may become the sole program
|
||||
// responsible for uploading.)
|
||||
Upload bool
|
||||
|
||||
// TelemetryDir, if set, will specify an alternate telemetry
|
||||
// directory to write data to. If not set, it uses the default
|
||||
// directory.
|
||||
// This field is intended to be used for isolating testing environments.
|
||||
TelemetryDir string
|
||||
|
||||
// UploadStartTime, if set, overrides the time used as the upload start time,
|
||||
// which is the time used by the upload logic to determine whether counter
|
||||
// file data should be uploaded. Only counter files that have expired before
|
||||
// the start time are considered for upload.
|
||||
//
|
||||
// This field can be used to simulate a future upload that collects recently
|
||||
// modified counters.
|
||||
UploadStartTime time.Time
|
||||
|
||||
// UploadURL, if set, overrides the URL used to receive uploaded reports. If
|
||||
// unset, this URL defaults to https://telemetry.go.dev/upload.
|
||||
UploadURL string
|
||||
}
|
||||
|
||||
// Start initializes telemetry using the specified configuration.
|
||||
//
|
||||
// Start opens the local telemetry database so that counter increment
|
||||
// operations are durably recorded in the local file system.
|
||||
//
|
||||
// If [Config.Upload] is set, and the user has opted in to telemetry
|
||||
// uploading, this process may attempt to upload approved counters
|
||||
// to telemetry.go.dev.
|
||||
//
|
||||
// If [Config.ReportCrashes] is set, any fatal crash will be
|
||||
// recorded by incrementing a counter named for the stack of the
|
||||
// first running goroutine in the traceback.
|
||||
//
|
||||
// If either of these flags is set, Start re-executes the current
|
||||
// executable as a child process, in a special mode in which it
|
||||
// acts as a telemetry sidecar for the parent process (the application).
|
||||
// In that mode, the call to Start will never return, so Start must
|
||||
// be called immediately within main, even before such things as
|
||||
// inspecting the command line. The application should avoid expensive
|
||||
// steps or external side effects in init functions, as they will
|
||||
// be executed twice (parent and child).
|
||||
//
|
||||
// Start returns a StartResult, which may be awaited via [StartResult.Wait] to
|
||||
// wait for all work done by Start to complete.
|
||||
func Start(config Config) *StartResult {
|
||||
switch v := os.Getenv(telemetryChildVar); v {
|
||||
case "":
|
||||
// The subprocess started by parent has GO_TELEMETRY_CHILD=1.
|
||||
return parent(config)
|
||||
case "1":
|
||||
child(config) // child will exit the process when it's done.
|
||||
case "2":
|
||||
// Do nothing: this was executed directly or indirectly by a child.
|
||||
default:
|
||||
log.Fatalf("unexpected value for %q: %q", telemetryChildVar, v)
|
||||
}
|
||||
|
||||
return &StartResult{}
|
||||
}
|
||||
|
||||
// MaybeChild executes the telemetry child logic if the calling program is
|
||||
// the telemetry child process, and does nothing otherwise. It is meant to be
|
||||
// called as the first thing in a program that uses telemetry.Start but cannot
|
||||
// call telemetry.Start immediately when it starts.
|
||||
func MaybeChild(config Config) {
|
||||
if v := os.Getenv(telemetryChildVar); v == "1" {
|
||||
child(config) // child will exit the process when it's done.
|
||||
}
|
||||
// other values of the telemetryChildVar environment variable
|
||||
// will be handled by telemetry.Start.
|
||||
}
|
||||
|
||||
// A StartResult is a handle to the result of a call to [Start]. Call
|
||||
// [StartResult.Wait] to wait for the completion of all work done on behalf of
|
||||
// Start.
|
||||
type StartResult struct {
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// Wait waits for the completion of all work initiated by [Start].
|
||||
func (res *StartResult) Wait() {
|
||||
if res == nil {
|
||||
return
|
||||
}
|
||||
res.wg.Wait()
|
||||
}
|
||||
|
||||
var daemonize = func(cmd *exec.Cmd) {}
|
||||
|
||||
// If telemetryChildVar is set to "1" in the environment, this is the telemetry
|
||||
// child.
|
||||
//
|
||||
// If telemetryChildVar is set to "2", this is a child of the child, and no
|
||||
// further forking should occur.
|
||||
const telemetryChildVar = "GO_TELEMETRY_CHILD"
|
||||
|
||||
// If telemetryUploadVar is set to "1" in the environment, the upload token has been
|
||||
// acquired by the parent, and the child should attempt an upload.
|
||||
const telemetryUploadVar = "GO_TELEMETRY_CHILD_UPLOAD"
|
||||
|
||||
func parent(config Config) *StartResult {
|
||||
if config.TelemetryDir != "" {
|
||||
telemetry.Default = telemetry.NewDir(config.TelemetryDir)
|
||||
}
|
||||
result := new(StartResult)
|
||||
|
||||
mode, _ := telemetry.Default.Mode()
|
||||
if mode == "off" {
|
||||
// Telemetry is turned off. Crash reporting doesn't work without telemetry
|
||||
// at least set to "local". The upload process runs in both "on" and "local" modes.
|
||||
// In local mode the upload process builds local reports but does not do the upload.
|
||||
return result
|
||||
}
|
||||
|
||||
counter.Open()
|
||||
|
||||
if _, err := os.Stat(telemetry.Default.LocalDir()); err != nil {
|
||||
// There was a problem statting LocalDir, which is needed for both
|
||||
// crash monitoring and counter uploading. Most likely, there was an
|
||||
// error creating telemetry.LocalDir in the counter.Open call above.
|
||||
// Don't start the child.
|
||||
return result
|
||||
}
|
||||
|
||||
childShouldUpload := config.Upload && acquireUploadToken()
|
||||
reportCrashes := config.ReportCrashes
|
||||
|
||||
if reportCrashes || childShouldUpload {
|
||||
startChild(reportCrashes, childShouldUpload, result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func startChild(reportCrashes, upload bool, result *StartResult) {
|
||||
// This process is the application (parent).
|
||||
// Fork+exec the telemetry child.
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
// There was an error getting os.Executable. It's possible
|
||||
// for this to happen on AIX if os.Args[0] is not an absolute
|
||||
// path and we can't find os.Args[0] in PATH.
|
||||
log.Printf("failed to start telemetry sidecar: os.Executable: %v", err)
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(exe, "** telemetry **") // this unused arg is just for ps(1)
|
||||
daemonize(cmd)
|
||||
cmd.Env = append(os.Environ(), telemetryChildVar+"=1")
|
||||
if upload {
|
||||
cmd.Env = append(cmd.Env, telemetryUploadVar+"=1")
|
||||
}
|
||||
cmd.Dir = telemetry.Default.LocalDir()
|
||||
|
||||
// The child process must write to a log file, not
|
||||
// the stderr file it inherited from the parent, as
|
||||
// the child may outlive the parent but should not prolong
|
||||
// the life of any pipes created (by the grandparent)
|
||||
// to gather the output of the parent.
|
||||
//
|
||||
// By default, we discard the child process's stderr,
|
||||
// but in line with the uploader, log to a file in debug
|
||||
// only if that directory was created by the user.
|
||||
fd, err := os.Stat(telemetry.Default.DebugDir())
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log.Printf("failed to stat debug directory: %v", err)
|
||||
return
|
||||
}
|
||||
} else if fd.IsDir() {
|
||||
// local/debug exists and is a directory. Set stderr to a log file path
|
||||
// in local/debug.
|
||||
childLogPath := filepath.Join(telemetry.Default.DebugDir(), "sidecar.log")
|
||||
childLog, err := os.OpenFile(childLogPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
log.Printf("opening sidecar log file for child: %v", err)
|
||||
return
|
||||
}
|
||||
defer childLog.Close()
|
||||
cmd.Stderr = childLog
|
||||
}
|
||||
|
||||
var crashOutputFile *os.File
|
||||
if reportCrashes {
|
||||
pipe, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
log.Printf("StdinPipe: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
crashOutputFile = pipe.(*os.File) // (this conversion is safe)
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
// The child couldn't be started. Log the failure.
|
||||
log.Printf("can't start telemetry child process: %v", err)
|
||||
return
|
||||
}
|
||||
if reportCrashes {
|
||||
crashmonitor.Parent(crashOutputFile)
|
||||
}
|
||||
result.wg.Add(1)
|
||||
go func() {
|
||||
cmd.Wait() // Release resources if cmd happens not to outlive this process.
|
||||
result.wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
func child(config Config) {
|
||||
log.SetPrefix(fmt.Sprintf("telemetry-sidecar (pid %v): ", os.Getpid()))
|
||||
|
||||
if config.TelemetryDir != "" {
|
||||
telemetry.Default = telemetry.NewDir(config.TelemetryDir)
|
||||
}
|
||||
|
||||
// golang/go#67211: be sure to set telemetryChildVar before running the
|
||||
// child, because the child itself invokes the go command to download the
|
||||
// upload config. If the telemetryChildVar variable is still set to "1",
|
||||
// that delegated go command may think that it is itself a telemetry
|
||||
// child.
|
||||
//
|
||||
// On the other hand, if telemetryChildVar were simply unset, then the
|
||||
// delegated go commands would fork themselves recursively. Short-circuit
|
||||
// this recursion.
|
||||
os.Setenv(telemetryChildVar, "2")
|
||||
upload := os.Getenv(telemetryUploadVar) == "1"
|
||||
|
||||
// The crashmonitor and/or upload process may themselves record counters.
|
||||
counter.Open()
|
||||
|
||||
// Start crashmonitoring and uploading depending on what's requested
|
||||
// and wait for the longer running child to complete before exiting:
|
||||
// if we collected a crash before the upload finished, wait for the
|
||||
// upload to finish before exiting
|
||||
var g errgroup.Group
|
||||
|
||||
if config.ReportCrashes {
|
||||
g.Go(func() error {
|
||||
crashmonitor.Child()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if upload {
|
||||
g.Go(func() error {
|
||||
uploaderChild(config.UploadStartTime, config.UploadURL)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
g.Wait()
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func uploaderChild(asof time.Time, uploadURL string) {
|
||||
if err := upload.Run(upload.RunConfig{
|
||||
UploadURL: uploadURL,
|
||||
LogWriter: os.Stderr,
|
||||
StartTime: asof,
|
||||
}); err != nil {
|
||||
log.Printf("upload failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// acquireUploadToken acquires a token permitting the caller to upload.
|
||||
// To limit the frequency of uploads, only one token is issue per
|
||||
// machine per time period.
|
||||
// The boolean indicates whether the token was acquired.
|
||||
func acquireUploadToken() bool {
|
||||
if telemetry.Default.LocalDir() == "" {
|
||||
// The telemetry dir wasn't initialized properly, probably because
|
||||
// os.UserConfigDir did not complete successfully. In that case
|
||||
// there are no counters to upload, so we should just do nothing.
|
||||
return false
|
||||
}
|
||||
tokenfile := filepath.Join(telemetry.Default.LocalDir(), "upload.token")
|
||||
const period = 24 * time.Hour
|
||||
|
||||
// A process acquires a token by successfully creating a
|
||||
// well-known file. If the file already exists and has an
|
||||
// mtime age less then than the period, the process does
|
||||
// not acquire the token. If the file is older than the
|
||||
// period, the process is allowed to remove the file and
|
||||
// try to re-create it.
|
||||
fi, err := os.Stat(tokenfile)
|
||||
if err == nil {
|
||||
if time.Since(fi.ModTime()) < period {
|
||||
return false
|
||||
}
|
||||
// There's a possible race here where two processes check the
|
||||
// token file and see that it's older than the period, then the
|
||||
// first one removes it and creates another, and then a second one
|
||||
// removes the newly created file and creates yet another
|
||||
// file. Then both processes would act as though they had the token.
|
||||
// This is very rare, but it's also okay because we're only grabbing
|
||||
// the token to do rate limiting, not for correctness.
|
||||
_ = os.Remove(tokenfile)
|
||||
} else if !os.IsNotExist(err) {
|
||||
log.Printf("error acquiring upload taken: statting token file: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(tokenfile, os.O_CREATE|os.O_EXCL, 0666)
|
||||
if err != nil {
|
||||
if os.IsExist(err) {
|
||||
return false
|
||||
}
|
||||
log.Printf("error acquiring upload token: creating token file: %v", err)
|
||||
return false
|
||||
}
|
||||
_ = f.Close()
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user