gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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 "gvisor.dev/gvisor/pkg/log" 26 ) 27 28 // profileCPU returns a CPU profile over HTTP. 29 func (m *metricServer) profileCPU(w *httpResponseWriter, 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 *httpResponseWriter, 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 }