From 8242cfd2be9c52ef6ccae8ce3fad4dcfac48db7a Mon Sep 17 00:00:00 2001 From: Francesco Giudici Date: Thu, 7 Apr 2022 11:18:46 +0200 Subject: [PATCH] kata-monitor: update the hrefs in the debug/pprof index page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kata-monitor allows to get data profiles from the kata shim instances running on the same node by acting as a proxy (e.g., http://$NODE_ADDRESS:8090/debug/pprof/?sandbox=$MYSANDBOXID). In order to proxy the requests and the responses to the right shim, kata-monitor requires to pass the sandbox id via a query string in the url. The profiling index page proxied by kata-monitor contains the link to all the data profiles available. All the links anyway do not contain the sandbox id included in the request: the links result then broken when accessed through kata-monitor. This happens because the profiling index page comes from the kata shim, which will not include the query string provided in the http request. Let's add on-the-fly the sandbox id in each href tag returned by the kata shim index page before providing the proxied page. Fixes: #4054 Signed-off-by: Francesco Giudici (cherry picked from commit 86977ff7809ce59c66fe7c7bf506a8bbf4b77a20) Signed-off-by: Fabiano FidĂȘncio --- src/runtime/pkg/kata-monitor/pprof.go | 54 ++++++++-- src/runtime/pkg/kata-monitor/pprof_test.go | 117 +++++++++++++++++++++ 2 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 src/runtime/pkg/kata-monitor/pprof_test.go diff --git a/src/runtime/pkg/kata-monitor/pprof.go b/src/runtime/pkg/kata-monitor/pprof.go index 5dac2da03c..62ed70c2e7 100644 --- a/src/runtime/pkg/kata-monitor/pprof.go +++ b/src/runtime/pkg/kata-monitor/pprof.go @@ -10,6 +10,8 @@ import ( "io" "net" "net/http" + "regexp" + "strings" cdshim "github.com/containerd/containerd/runtime/v2/shim" @@ -33,7 +35,13 @@ func (km *KataMonitor) composeSocketAddress(r *http.Request) (string, error) { return shim.SocketAddress(sandbox), nil } -func (km *KataMonitor) proxyRequest(w http.ResponseWriter, r *http.Request) { +func (km *KataMonitor) proxyRequest(w http.ResponseWriter, r *http.Request, + proxyResponse func(req *http.Request, w io.Writer, r io.Reader) error) { + + if proxyResponse == nil { + proxyResponse = copyResponse + } + w.Header().Set("X-Content-Type-Options", "nosniff") socketAddress, err := km.composeSocketAddress(r) @@ -73,38 +81,68 @@ func (km *KataMonitor) proxyRequest(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Disposition", contentDisposition) } - io.Copy(w, output) + err = proxyResponse(r, w, output) + if err != nil { + monitorLog.WithError(err).Errorf("failed proxying %s from %s", uri, socketAddress) + serveError(w, http.StatusInternalServerError, "error retrieving resource") + } } // ExpvarHandler handles other `/debug/vars` requests func (km *KataMonitor) ExpvarHandler(w http.ResponseWriter, r *http.Request) { - km.proxyRequest(w, r) + km.proxyRequest(w, r, nil) } // PprofIndex handles other `/debug/pprof/` requests func (km *KataMonitor) PprofIndex(w http.ResponseWriter, r *http.Request) { - km.proxyRequest(w, r) + if len(strings.TrimPrefix(r.URL.Path, "/debug/pprof/")) == 0 { + km.proxyRequest(w, r, copyResponseAddingSandboxIdToHref) + } else { + km.proxyRequest(w, r, nil) + } } // PprofCmdline handles other `/debug/cmdline` requests func (km *KataMonitor) PprofCmdline(w http.ResponseWriter, r *http.Request) { - km.proxyRequest(w, r) + km.proxyRequest(w, r, nil) } // PprofProfile handles other `/debug/profile` requests func (km *KataMonitor) PprofProfile(w http.ResponseWriter, r *http.Request) { - km.proxyRequest(w, r) + km.proxyRequest(w, r, nil) } // PprofSymbol handles other `/debug/symbol` requests func (km *KataMonitor) PprofSymbol(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") - km.proxyRequest(w, r) + km.proxyRequest(w, r, nil) } // PprofTrace handles other `/debug/trace` requests func (km *KataMonitor) PprofTrace(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", `attachment; filename="trace"`) - km.proxyRequest(w, r) + km.proxyRequest(w, r, nil) +} + +func copyResponse(req *http.Request, w io.Writer, r io.Reader) error { + _, err := io.Copy(w, r) + return err +} + +func copyResponseAddingSandboxIdToHref(req *http.Request, w io.Writer, r io.Reader) error { + sb, err := getSandboxIDFromReq(req) + if err != nil { + monitorLog.WithError(err).Warning("missing sandbox query in pprof url") + return copyResponse(req, w, r) + } + buf, err := io.ReadAll(r) + if err != nil { + return err + } + + re := regexp.MustCompile(``) + outHtml := re.ReplaceAllString(string(buf), fmt.Sprintf("", sb)) + w.Write([]byte(outHtml)) + return nil } diff --git a/src/runtime/pkg/kata-monitor/pprof_test.go b/src/runtime/pkg/kata-monitor/pprof_test.go new file mode 100644 index 0000000000..e02dc00b12 --- /dev/null +++ b/src/runtime/pkg/kata-monitor/pprof_test.go @@ -0,0 +1,117 @@ +// Copyright (c) 2022 Red Hat Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// + +package katamonitor + +import ( + "bytes" + "net/http" + "net/url" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCopyResponseAddingSandboxIdToHref(t *testing.T) { + assert := assert.New(t) + + htmlIn := strings.NewReader(` + + +/debug/pprof/ + + + +/debug/pprof/
+
+Types of profiles available: + + + + + + + + + + + +
CountProfile
27allocs
0block
0cmdline
39goroutine
27heap
0mutex
0profile
10threadcreate
0trace
+
full goroutine stack dump +
+

+Profile Descriptions: +

    +
  • allocs:
    A sampling of all past memory allocations
  • +
  • block:
    Stack traces that led to blocking on synchronization primitives
  • +
  • cmdline:
    The command line invocation of the current program
  • +
  • goroutine:
    Stack traces of all current goroutines
  • +
  • heap:
    A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.
  • +
  • mutex:
    Stack traces of holders of contended mutexes
  • +
  • profile:
    CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.
  • +
  • threadcreate:
    Stack traces that led to the creation of new OS threads
  • +
  • trace:
    A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.
  • +
+

+ +`) + + htmlExpected := bytes.NewBufferString(` + + +/debug/pprof/ + + + +/debug/pprof/
+
+Types of profiles available: + + + + + + + + + + + +
CountProfile
27allocs
0block
0cmdline
39goroutine
27heap
0mutex
0profile
10threadcreate
0trace
+full goroutine stack dump +
+

+Profile Descriptions: +

    +
  • allocs:
    A sampling of all past memory allocations
  • +
  • block:
    Stack traces that led to blocking on synchronization primitives
  • +
  • cmdline:
    The command line invocation of the current program
  • +
  • goroutine:
    Stack traces of all current goroutines
  • +
  • heap:
    A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.
  • +
  • mutex:
    Stack traces of holders of contended mutexes
  • +
  • profile:
    CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.
  • +
  • threadcreate:
    Stack traces that led to the creation of new OS threads
  • +
  • trace:
    A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.
  • +
+

+ +`) + + req := &http.Request{URL: &url.URL{RawQuery: "sandbox=1234567890"}} + buf := bytes.NewBuffer(nil) + copyResponseAddingSandboxIdToHref(req, buf, htmlIn) + assert.Equal(htmlExpected, buf) +}