diff --git a/event-exporter/Makefile b/event-exporter/Makefile index b9daab3be..e873ee8ed 100644 --- a/event-exporter/Makefile +++ b/event-exporter/Makefile @@ -20,7 +20,7 @@ ALL_ARCH=amd64 arm64 IMAGE_NAME = event-exporter PREFIX ?= staging-k8s.gcr.io -TAG ?= v0.5.11 +TAG ?= v0.5.13 IMAGE=$(PREFIX)/$(IMAGE_NAME) diff --git a/event-exporter/README.md b/event-exporter/README.md index cfe2ddc06..351179abf 100644 --- a/event-exporter/README.md +++ b/event-exporter/README.md @@ -37,6 +37,14 @@ Event exporter has following options: Parameters for configuring sink ``` +Environment variables: + +``` +PPROF_SERVER_PORT string + Port on which to expose Go pprof HTTP profiling handler (e.g. "11123" or ":11123"). + When unset, pprof server is disabled. +``` + Set of flags for configuring sink is the following: ``` diff --git a/event-exporter/event_exporter_test.go b/event-exporter/event_exporter_test.go new file mode 100644 index 000000000..8d4f58c4e --- /dev/null +++ b/event-exporter/event_exporter_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2017 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "net/http" + "testing" +) + +func TestStartPprofServer(t *testing.T) { + if s := startPprofServer(""); s != nil { + t.Errorf("startPprofServer(\"\") expected nil, got %v", s) + } + + server := startPprofServer("11123") + if server == nil { + t.Fatalf("startPprofServer(\"11123\") returned nil") + } + defer server.Close() + + if server.Addr != ":11123" { + t.Errorf("server.Addr = %q, want \":11123\"", server.Addr) + } + + endpoints := []string{ + "/debug/pprof/", + "/debug/pprof/cmdline", + "/debug/pprof/profile", + "/debug/pprof/symbol", + "/debug/pprof/trace", + "/debug/pprof/heap", + "/debug/pprof/goroutine", + } + + for _, endpoint := range endpoints { + req, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + t.Fatalf("Failed to create request for %s: %v", endpoint, err) + } + _, pattern := server.Handler.(*http.ServeMux).Handler(req) + if pattern == "" { + t.Errorf("pprof endpoint %q matched empty pattern on server handler", endpoint) + } + } +} diff --git a/event-exporter/main.go b/event-exporter/main.go index 343838ec5..143e385ce 100644 --- a/event-exporter/main.go +++ b/event-exporter/main.go @@ -17,9 +17,11 @@ limitations under the License. package main import ( + "context" "flag" "fmt" "net/http" + "net/http/pprof" "os" "os/signal" "strings" @@ -38,6 +40,10 @@ import ( "k8s.io/client-go/rest" ) +const ( + pprofServerPortEnv = "PPROF_SERVER_PORT" +) + var ( resyncPeriod = flag.Duration("resync-period", 1*time.Minute, "Reflector resync period") sinkOpts = flag.String("sink-opts", "", "Parameters for configuring sink") @@ -140,9 +146,50 @@ func main() { // Expose the Prometheus http endpoint go func() { - http.Handle("/metrics", promhttp.Handler()) - glog.Fatalf("Prometheus monitoring failed: %v", http.ListenAndServe(*prometheusEndpoint, nil)) + promMux := http.NewServeMux() + promMux.Handle("/metrics", promhttp.Handler()) + glog.Fatalf("Prometheus monitoring failed: %v", http.ListenAndServe(*prometheusEndpoint, promMux)) }() + // Start pprof server if PPROF_SERVER_PORT is specified + if pprofPort := os.Getenv(pprofServerPortEnv); pprofPort != "" { + pprofServer := startPprofServer(pprofPort) + if pprofServer != nil { + go func() { + <-stopCh + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + pprofServer.Shutdown(ctx) + }() + } + } + eventExporter.Run(stopCh) } + +func startPprofServer(port string) *http.Server { + port = strings.TrimSpace(port) + if port == "" { + return nil + } + if !strings.HasPrefix(port, ":") { + port = ":" + port + } + mux := http.NewServeMux() + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + server := &http.Server{ + Addr: port, + Handler: mux, + } + go func() { + glog.Infof("Starting pprof HTTP server on %s", port) + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + glog.Errorf("pprof server failed: %v", err) + } + }() + return server +}