github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/runsc/metricserver/metricserver_profile.go (about)

     1  // Copyright 2023 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package metricserver
    16  
    17  import (
    18  	"errors"
    19  	"net/http"
    20  	"runtime"
    21  	"runtime/pprof"
    22  	"strconv"
    23  	"time"
    24  
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/log"
    26  )
    27  
    28  // profileCPU returns a CPU profile over HTTP.
    29  func (m *metricServer) profileCPU(w http.ResponseWriter, req *http.Request) httpResult {
    30  	// Time to finish up profiling and flush out the results to the client.
    31  	const finishProfilingBuffer = 250 * time.Millisecond
    32  
    33  	m.mu.Lock()
    34  	if m.shuttingDown {
    35  		m.mu.Unlock()
    36  		return httpResult{http.StatusServiceUnavailable, errors.New("server is shutting down already")}
    37  	}
    38  	m.mu.Unlock()
    39  	w.WriteHeader(http.StatusOK)
    40  	if err := pprof.StartCPUProfile(w); err != nil {
    41  		// We cannot return this as an error, because we've already sent the HTTP 200 OK status.
    42  		log.Warningf("Failed to start recording CPU profile: %v", err)
    43  		return httpOK
    44  	}
    45  	deadline := time.Now().Add(httpTimeout - finishProfilingBuffer)
    46  	if seconds, err := strconv.Atoi(req.URL.Query().Get("seconds")); err == nil && time.Duration(seconds)*time.Second < httpTimeout {
    47  		deadline = time.Now().Add(time.Duration(seconds) * time.Second)
    48  	} else if ctxDeadline, hasDeadline := req.Context().Deadline(); hasDeadline {
    49  		deadline = ctxDeadline.Add(-finishProfilingBuffer)
    50  	}
    51  	log.Infof("Profiling CPU until %v...", deadline)
    52  	var wasInterrupted bool
    53  	select {
    54  	case <-time.After(time.Until(deadline)):
    55  		wasInterrupted = false
    56  	case <-req.Context().Done():
    57  		wasInterrupted = true
    58  	}
    59  	pprof.StopCPUProfile()
    60  	if wasInterrupted {
    61  		log.Warningf("Profiling CPU interrupted.")
    62  	} else {
    63  		log.Infof("Profiling CPU done.")
    64  	}
    65  	return httpOK
    66  }
    67  
    68  // profileHeap returns a heap profile over HTTP.
    69  func (m *metricServer) profileHeap(w http.ResponseWriter, req *http.Request) httpResult {
    70  	m.mu.Lock()
    71  	if m.shuttingDown {
    72  		m.mu.Unlock()
    73  		return httpResult{http.StatusServiceUnavailable, errors.New("server is shutting down already")}
    74  	}
    75  	m.mu.Unlock()
    76  	w.WriteHeader(http.StatusOK)
    77  	runtime.GC() // Run GC just before looking at the heap to get a clean view.
    78  	if err := pprof.Lookup("heap").WriteTo(w, 0); err != nil {
    79  		// We cannot return this as an error, because we've already sent the HTTP 200 OK status.
    80  		log.Warningf("Failed to record heap profile: %v", err)
    81  	}
    82  	return httpOK
    83  }