diff options
Diffstat (limited to 'vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/debuglet.go')
| -rw-r--r-- | vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/debuglet.go | 450 |
1 files changed, 0 insertions, 450 deletions
diff --git a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/debuglet.go b/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/debuglet.go deleted file mode 100644 index 6a8702c77..000000000 --- a/vendor/cloud.google.com/go/cmd/go-cloud-debug-agent/debuglet.go +++ /dev/null @@ -1,450 +0,0 @@ -// Copyright 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build linux - -package main - -import ( - "encoding/json" - "flag" - "fmt" - "io/ioutil" - "log" - "math/rand" - "os" - "sync" - "time" - - "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/breakpoints" - debuglet "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/controller" - "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/valuecollector" - "cloud.google.com/go/compute/metadata" - "golang.org/x/debug" - "golang.org/x/debug/local" - "golang.org/x/net/context" - "golang.org/x/oauth2" - "golang.org/x/oauth2/google" - cd "google.golang.org/api/clouddebugger/v2" -) - -var ( - appModule = flag.String("appmodule", "", "Optional application module name.") - appVersion = flag.String("appversion", "", "Optional application module version name.") - sourceContextFile = flag.String("sourcecontext", "", "File containing JSON-encoded source context.") - verbose = flag.Bool("v", false, "Output verbose log messages.") - projectNumber = flag.String("projectnumber", "", "Project number."+ - " If this is not set, it is read from the GCP metadata server.") - projectID = flag.String("projectid", "", "Project ID."+ - " If this is not set, it is read from the GCP metadata server.") - serviceAccountFile = flag.String("serviceaccountfile", "", "File containing JSON service account credentials.") -) - -const ( - maxCapturedStackFrames = 50 - maxCapturedVariables = 1000 -) - -func main() { - flag.Usage = usage - flag.Parse() - args := flag.Args() - if len(args) == 0 { - // The user needs to supply the name of the executable to run. - flag.Usage() - return - } - if *projectNumber == "" { - var err error - *projectNumber, err = metadata.NumericProjectID() - if err != nil { - log.Print("Debuglet initialization: ", err) - } - } - if *projectID == "" { - var err error - *projectID, err = metadata.ProjectID() - if err != nil { - log.Print("Debuglet initialization: ", err) - } - } - sourceContexts, err := readSourceContextFile(*sourceContextFile) - if err != nil { - log.Print("Reading source context file: ", err) - } - var ts oauth2.TokenSource - ctx := context.Background() - if *serviceAccountFile != "" { - if ts, err = serviceAcctTokenSource(ctx, *serviceAccountFile, cd.CloudDebuggerScope); err != nil { - log.Fatalf("Error getting credentials from file %s: %v", *serviceAccountFile, err) - } - } else if ts, err = google.DefaultTokenSource(ctx, cd.CloudDebuggerScope); err != nil { - log.Print("Error getting application default credentials for Cloud Debugger:", err) - os.Exit(103) - } - c, err := debuglet.NewController(ctx, debuglet.Options{ - ProjectNumber: *projectNumber, - ProjectID: *projectID, - AppModule: *appModule, - AppVersion: *appVersion, - SourceContexts: sourceContexts, - Verbose: *verbose, - TokenSource: ts, - }) - if err != nil { - log.Fatal("Error connecting to Cloud Debugger: ", err) - } - prog, err := local.New(args[0]) - if err != nil { - log.Fatal("Error loading program: ", err) - } - // Load the program, but don't actually start it running yet. - if _, err = prog.Run(args[1:]...); err != nil { - log.Fatal("Error loading program: ", err) - } - bs := breakpoints.NewBreakpointStore(prog) - - // Seed the random number generator. - rand.Seed(time.Now().UnixNano()) - - // Now we want to do two things: run the user's program, and start sending - // List requests periodically to the Debuglet Controller to get breakpoints - // to set. - // - // We want to give the Debuglet Controller a chance to give us breakpoints - // before we start the program, otherwise we would miss any breakpoint - // triggers that occur during program startup -- for example, a breakpoint on - // the first line of main. But if the Debuglet Controller is not responding or - // is returning errors, we don't want to delay starting the program - // indefinitely. - // - // We pass a channel to breakpointListLoop, which will close it when the first - // List call finishes. Then we wait until either the channel is closed or a - // 5-second timer has finished before starting the program. - ch := make(chan bool) - // Start a goroutine that sends List requests to the Debuglet Controller, and - // sets any breakpoints it gets back. - go breakpointListLoop(ctx, c, bs, ch) - // Wait until 5 seconds have passed or breakpointListLoop has closed ch. - select { - case <-time.After(5 * time.Second): - case <-ch: - } - // Run the debuggee. - programLoop(ctx, c, bs, prog) -} - -// usage prints a usage message to stderr and exits. -func usage() { - me := "a.out" - if len(os.Args) >= 1 { - me = os.Args[0] - } - fmt.Fprintf(os.Stderr, "Usage of %s:\n", me) - fmt.Fprintf(os.Stderr, "\t%s [flags...] -- <program name> args...\n", me) - fmt.Fprintf(os.Stderr, "Flags:\n") - flag.PrintDefaults() - fmt.Fprintf(os.Stderr, - "See https://cloud.google.com/tools/cloud-debugger/setting-up-on-compute-engine for more information.\n") - os.Exit(2) -} - -// readSourceContextFile reads a JSON-encoded source context from the given file. -// It returns a non-empty slice on success. -func readSourceContextFile(filename string) ([]*cd.SourceContext, error) { - if filename == "" { - return nil, nil - } - scJSON, err := ioutil.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("reading file %q: %v", filename, err) - } - var sc cd.SourceContext - if err = json.Unmarshal(scJSON, &sc); err != nil { - return nil, fmt.Errorf("parsing file %q: %v", filename, err) - } - return []*cd.SourceContext{&sc}, nil -} - -// breakpointListLoop repeatedly calls the Debuglet Controller's List RPC, and -// passes the results to the BreakpointStore so it can set and unset breakpoints -// in the program. -// -// After the first List call finishes, ch is closed. -func breakpointListLoop(ctx context.Context, c *debuglet.Controller, bs *breakpoints.BreakpointStore, first chan bool) { - const ( - avgTimeBetweenCalls = time.Second - errorDelay = 5 * time.Second - ) - - // randomDuration returns a random duration with expected value avg. - randomDuration := func(avg time.Duration) time.Duration { - return time.Duration(rand.Int63n(int64(2*avg + 1))) - } - - var consecutiveFailures uint - - for { - callStart := time.Now() - resp, err := c.List(ctx) - if err != nil && err != debuglet.ErrListUnchanged { - log.Printf("Debuglet controller server error: %v", err) - } - if err == nil { - bs.ProcessBreakpointList(resp.Breakpoints) - } - - if first != nil { - // We've finished one call to List and set any breakpoints we received. - close(first) - first = nil - } - - // Asynchronously send updates for any breakpoints that caused an error when - // the BreakpointStore tried to process them. We don't wait for the update - // to finish before the program can exit, as we do for normal updates. - errorBps := bs.ErrorBreakpoints() - for _, bp := range errorBps { - go func(bp *cd.Breakpoint) { - if err := c.Update(ctx, bp.Id, bp); err != nil { - log.Printf("Failed to send breakpoint update for %s: %s", bp.Id, err) - } - }(bp) - } - - // Make the next call not too soon after the one we just did. - delay := randomDuration(avgTimeBetweenCalls) - - // If the call returned an error other than ErrListUnchanged, wait longer. - if err != nil && err != debuglet.ErrListUnchanged { - // Wait twice as long after each consecutive failure, to a maximum of 16x. - delay += randomDuration(errorDelay * (1 << consecutiveFailures)) - if consecutiveFailures < 4 { - consecutiveFailures++ - } - } else { - consecutiveFailures = 0 - } - - // Sleep until we reach time callStart+delay. If we've already passed that - // time, time.Sleep will return immediately -- this should be the common - // case, since the server will delay responding to List for a while when - // there are no changes to report. - time.Sleep(callStart.Add(delay).Sub(time.Now())) - } -} - -// programLoop runs the program being debugged to completion. When a breakpoint's -// conditions are satisfied, it sends an Update RPC to the Debuglet Controller. -// The function returns when the program exits and all Update RPCs have finished. -func programLoop(ctx context.Context, c *debuglet.Controller, bs *breakpoints.BreakpointStore, prog debug.Program) { - var wg sync.WaitGroup - for { - // Run the program until it hits a breakpoint or exits. - status, err := prog.Resume() - if err != nil { - break - } - - // Get the breakpoints at this address whose conditions were satisfied, - // and remove the ones that aren't logpoints. - bps := bs.BreakpointsAtPC(status.PC) - bps = bpsWithConditionSatisfied(bps, prog) - for _, bp := range bps { - if bp.Action != "LOG" { - bs.RemoveBreakpoint(bp) - } - } - - if len(bps) == 0 { - continue - } - - // Evaluate expressions and get the stack. - vc := valuecollector.NewCollector(prog, maxCapturedVariables) - needStackFrames := false - for _, bp := range bps { - // If evaluating bp's condition didn't return an error, evaluate bp's - // expressions, and later get the stack frames. - if bp.Status == nil { - bp.EvaluatedExpressions = expressionValues(bp.Expressions, prog, vc) - needStackFrames = true - } - } - var ( - stack []*cd.StackFrame - stackFramesStatusMessage *cd.StatusMessage - ) - if needStackFrames { - stack, stackFramesStatusMessage = stackFrames(prog, vc) - } - - // Read variable values from the program. - variableTable := vc.ReadValues() - - // Start a goroutine to send updates to the Debuglet Controller or write - // to logs, concurrently with resuming the program. - // TODO: retry Update on failure. - for _, bp := range bps { - wg.Add(1) - switch bp.Action { - case "LOG": - go func(format string, evaluatedExpressions []*cd.Variable) { - s := valuecollector.LogString(format, evaluatedExpressions, variableTable) - log.Print(s) - wg.Done() - }(bp.LogMessageFormat, bp.EvaluatedExpressions) - bp.Status = nil - bp.EvaluatedExpressions = nil - default: - go func(bp *cd.Breakpoint) { - defer wg.Done() - bp.IsFinalState = true - if bp.Status == nil { - // If evaluating bp's condition didn't return an error, include the - // stack frames, variable table, and any status message produced when - // getting the stack frames. - bp.StackFrames = stack - bp.VariableTable = variableTable - bp.Status = stackFramesStatusMessage - } - if err := c.Update(ctx, bp.Id, bp); err != nil { - log.Printf("Failed to send breakpoint update for %s: %s", bp.Id, err) - } - }(bp) - } - } - } - - // Wait for all updates to finish before returning. - wg.Wait() -} - -// bpsWithConditionSatisfied returns the breakpoints whose conditions are true -// (or that do not have a condition.) -func bpsWithConditionSatisfied(bpsIn []*cd.Breakpoint, prog debug.Program) []*cd.Breakpoint { - var bpsOut []*cd.Breakpoint - for _, bp := range bpsIn { - cond, err := condTruth(bp.Condition, prog) - if err != nil { - bp.Status = errorStatusMessage(err.Error(), refersToBreakpointCondition) - // Include bp in the list to be updated when there's an error, so that - // the user gets a response. - bpsOut = append(bpsOut, bp) - } else if cond { - bpsOut = append(bpsOut, bp) - } - } - return bpsOut -} - -// condTruth evaluates a condition. -func condTruth(condition string, prog debug.Program) (bool, error) { - if condition == "" { - // A condition wasn't set. - return true, nil - } - val, err := prog.Evaluate(condition) - if err != nil { - return false, err - } - if v, ok := val.(bool); !ok { - return false, fmt.Errorf("condition expression has type %T, should be bool", val) - } else { - return v, nil - } -} - -// expressionValues evaluates a slice of expressions and returns a []*cd.Variable -// containing the results. -// If the result of an expression evaluation refers to values from the program's -// memory (e.g., the expression evaluates to a slice) a corresponding variable is -// added to the value collector, to be read later. -func expressionValues(expressions []string, prog debug.Program, vc *valuecollector.Collector) []*cd.Variable { - evaluatedExpressions := make([]*cd.Variable, len(expressions)) - for i, exp := range expressions { - ee := &cd.Variable{Name: exp} - evaluatedExpressions[i] = ee - if val, err := prog.Evaluate(exp); err != nil { - ee.Status = errorStatusMessage(err.Error(), refersToBreakpointExpression) - } else { - vc.FillValue(val, ee) - } - } - return evaluatedExpressions -} - -// stackFrames returns a stack trace for the program. It passes references to -// function parameters and local variables to the value collector, so it can read -// their values later. -func stackFrames(prog debug.Program, vc *valuecollector.Collector) ([]*cd.StackFrame, *cd.StatusMessage) { - frames, err := prog.Frames(maxCapturedStackFrames) - if err != nil { - return nil, errorStatusMessage("Error getting stack: "+err.Error(), refersToUnspecified) - } - stackFrames := make([]*cd.StackFrame, len(frames)) - for i, f := range frames { - frame := &cd.StackFrame{} - frame.Function = f.Function - for _, v := range f.Params { - frame.Arguments = append(frame.Arguments, vc.AddVariable(debug.LocalVar(v))) - } - for _, v := range f.Vars { - frame.Locals = append(frame.Locals, vc.AddVariable(v)) - } - frame.Location = &cd.SourceLocation{ - Path: f.File, - Line: int64(f.Line), - } - stackFrames[i] = frame - } - return stackFrames, nil -} - -// errorStatusMessage returns a *cd.StatusMessage indicating an error, -// with the given message and refersTo field. -func errorStatusMessage(msg string, refersTo int) *cd.StatusMessage { - return &cd.StatusMessage{ - Description: &cd.FormatMessage{Format: "$0", Parameters: []string{msg}}, - IsError: true, - RefersTo: refersToString[refersTo], - } -} - -const ( - // RefersTo values for cd.StatusMessage. - refersToUnspecified = iota - refersToBreakpointCondition - refersToBreakpointExpression -) - -// refersToString contains the strings for each refersTo value. -// See the definition of StatusMessage in the v2/clouddebugger package. -var refersToString = map[int]string{ - refersToUnspecified: "UNSPECIFIED", - refersToBreakpointCondition: "BREAKPOINT_CONDITION", - refersToBreakpointExpression: "BREAKPOINT_EXPRESSION", -} - -func serviceAcctTokenSource(ctx context.Context, filename string, scope ...string) (oauth2.TokenSource, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("cannot read service account file: %v", err) - } - cfg, err := google.JWTConfigFromJSON(data, scope...) - if err != nil { - return nil, fmt.Errorf("google.JWTConfigFromJSON: %v", err) - } - return cfg.TokenSource(ctx), nil -} |
