diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 095f57a8e..bc89efdf3 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -593,7 +593,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkM lastDERPMap = resp.DERPMap } if resp.Debug != nil && resp.Debug.LogHeapPprof { - logheap.LogHeap() + go logheap.LogHeap(resp.Debug.LogHeapURL) } nm := &NetworkMap{ diff --git a/log/logheap/logheap.go b/log/logheap/logheap.go index a97d7f6b9..af48a1f64 100644 --- a/log/logheap/logheap.go +++ b/log/logheap/logheap.go @@ -7,9 +7,9 @@ package logheap import ( "bytes" - "encoding/json" - "io" - "os" + "context" + "log" + "net/http" "runtime" "runtime/pprof" "time" @@ -17,29 +17,26 @@ import ( // LogHeap writes a JSON logtail record with the base64 heap pprof to // os.Stderr. -func LogHeap() { - logHeap(os.Stderr) -} - -type logTail struct { - ClientTime string `json:"client_time"` -} - -type pprofRec struct { - Heap []byte `json:"heap,omitempty"` -} - -type logLine struct { - LogTail logTail `json:"logtail"` - Pprof pprofRec `json:"pprof"` -} - -func logHeap(w io.Writer) error { +func LogHeap(postURL string) { + if postURL == "" { + return + } runtime.GC() buf := new(bytes.Buffer) pprof.WriteHeapProfile(buf) - return json.NewEncoder(w).Encode(logLine{ - LogTail: logTail{ClientTime: time.Now().Format(time.RFC3339Nano)}, - Pprof: pprofRec{Heap: buf.Bytes()}, - }) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + req, err := http.NewRequestWithContext(ctx, "POST", postURL, buf) + if err != nil { + log.Printf("LogHeap: %v", err) + return + } + res, err := http.DefaultClient.Do(req) + if err != nil { + log.Printf("LogHeap: %v", err) + return + } + defer res.Body.Close() + return } diff --git a/log/logheap/logheap_test.go b/log/logheap/logheap_test.go deleted file mode 100644 index d55cb5d35..000000000 --- a/log/logheap/logheap_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2020 Tailscale Inc & 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 logheap - -import ( - "bytes" - "compress/gzip" - "encoding/json" - "io/ioutil" - "testing" -) - -func TestLogHeap(t *testing.T) { - var buf bytes.Buffer - if err := logHeap(&buf); err != nil { - t.Fatal(err) - } - t.Logf("Got line: %s", buf.Bytes()) - - var ll logLine - if err := json.Unmarshal(buf.Bytes(), &ll); err != nil { - t.Fatal(err) - } - - zr, err := gzip.NewReader(bytes.NewReader(ll.Pprof.Heap)) - if err != nil { - t.Fatal(err) - } - rawProto, err := ioutil.ReadAll(zr) - if err != nil { - t.Fatal(err) - } - // Just sanity check it. Too lazy to properly decode the protobuf. But see that - // it contains an expected sample name. - if !bytes.Contains(rawProto, []byte("alloc_objects")) { - t.Errorf("raw proto didn't contain `alloc_objects`: %q", rawProto) - } -} diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 0a9ac18eb..3bce4d8e1 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -509,10 +509,14 @@ type MapResponse struct { // Debug are instructions from the control server to the client // to adjust debug settings. type Debug struct { - // LogHeapPprof controls whether the client should logs + // LogHeapPprof controls whether the client should log // its heap pprof data. Each true value sent from the server // means that client should do one more log. LogHeapPprof bool `json:",omitempty"` + + // LogHeapURL is the URL to POST its heap pprof to. + // Empty means to not log. + LogHeapURL string `json:",omitempty"` } func (k MachineKey) String() string { return fmt.Sprintf("mkey:%x", k[:]) }