knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/profiling/server.go (about)

     1  /*
     2  Copyright 2019 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package profiling
    18  
    19  import (
    20  	"fmt"
    21  	"net/http"
    22  	"net/http/pprof"
    23  	"os"
    24  	"strconv"
    25  	"sync/atomic"
    26  	"time"
    27  
    28  	"go.uber.org/zap"
    29  	corev1 "k8s.io/api/core/v1"
    30  )
    31  
    32  const (
    33  	// ProfilingPortKey specified the name of an environment variable that
    34  	// may be used to override the default profiling port.
    35  	ProfilingPortKey = "PROFILING_PORT"
    36  
    37  	// ProfilingPort specifies the default port where profiling data is available when profiling is enabled
    38  	ProfilingPort = 8008
    39  
    40  	// profilingKey is the name of the key in config-observability config map
    41  	// that indicates whether profiling is enabled
    42  	profilingKey = "profiling.enable"
    43  )
    44  
    45  // Handler holds the main HTTP handler and a flag indicating
    46  // whether the handler is active
    47  type Handler struct {
    48  	enabled *atomic.Bool
    49  	handler http.Handler
    50  	log     *zap.SugaredLogger
    51  }
    52  
    53  // NewHandler create a new ProfilingHandler which serves runtime profiling data
    54  // according to the given context path
    55  func NewHandler(logger *zap.SugaredLogger, enableProfiling bool) *Handler {
    56  	const pprofPrefix = "/debug/pprof/"
    57  
    58  	mux := http.NewServeMux()
    59  	mux.HandleFunc(pprofPrefix, pprof.Index)
    60  	mux.HandleFunc(pprofPrefix+"cmdline", pprof.Cmdline)
    61  	mux.HandleFunc(pprofPrefix+"profile", pprof.Profile)
    62  	mux.HandleFunc(pprofPrefix+"symbol", pprof.Symbol)
    63  	mux.HandleFunc(pprofPrefix+"trace", pprof.Trace)
    64  
    65  	logger.Info("Profiling enabled: ", enableProfiling)
    66  	var enabled atomic.Bool
    67  	enabled.Store(enableProfiling)
    68  
    69  	return &Handler{
    70  		enabled: &enabled,
    71  		handler: mux,
    72  		log:     logger,
    73  	}
    74  }
    75  
    76  func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    77  	if h.enabled.Load() {
    78  		h.handler.ServeHTTP(w, r)
    79  	} else {
    80  		http.NotFoundHandler().ServeHTTP(w, r)
    81  	}
    82  }
    83  
    84  func ReadProfilingFlag(config map[string]string) (bool, error) {
    85  	profiling, ok := config[profilingKey]
    86  	if !ok {
    87  		return false, nil
    88  	}
    89  	enabled, err := strconv.ParseBool(profiling)
    90  	if err != nil {
    91  		return false, fmt.Errorf("failed to parse the profiling flag: %w", err)
    92  	}
    93  	return enabled, nil
    94  }
    95  
    96  // UpdateFromConfigMap modifies the Enabled flag in the Handler
    97  // according to the value in the given ConfigMap
    98  func (h *Handler) UpdateFromConfigMap(configMap *corev1.ConfigMap) {
    99  	enabled, err := ReadProfilingFlag(configMap.Data)
   100  	if err != nil {
   101  		h.log.Errorw("Failed to update the profiling flag", zap.Error(err))
   102  		return
   103  	}
   104  
   105  	if h.enabled.Swap(enabled) != enabled {
   106  		h.log.Info("Profiling enabled: ", enabled)
   107  	}
   108  }
   109  
   110  // NewServer creates a new http server that exposes profiling data on the default profiling port
   111  func NewServer(handler http.Handler) *http.Server {
   112  	port := os.Getenv(ProfilingPortKey)
   113  	if port == "" {
   114  		port = strconv.Itoa(ProfilingPort)
   115  	}
   116  
   117  	return &http.Server{
   118  		Addr:              ":" + port,
   119  		Handler:           handler,
   120  		ReadHeaderTimeout: time.Minute, // https://medium.com/a-journey-with-go/go-understand-and-mitigate-slowloris-attack-711c1b1403f6
   121  	}
   122  }