diff --git a/staging/src/k8s.io/component-base/metrics/http.go b/staging/src/k8s.io/component-base/metrics/http.go index 3394a8f7114..2a0d249c205 100644 --- a/staging/src/k8s.io/component-base/metrics/http.go +++ b/staging/src/k8s.io/component-base/metrics/http.go @@ -19,19 +19,28 @@ package metrics import ( "io" "net/http" + "time" "github.com/prometheus/client_golang/prometheus/promhttp" ) +var ( + processStartedAt time.Time +) + +func init() { + processStartedAt = time.Now() +} + // These constants cause handlers serving metrics to behave as described if // errors are encountered. const ( - // Serve an HTTP status code 500 upon the first error + // HTTPErrorOnError serve an HTTP status code 500 upon the first error // encountered. Report the error message in the body. HTTPErrorOnError promhttp.HandlerErrorHandling = iota - // Ignore errors and try to serve as many metrics as possible. However, - // if no metrics can be served, serve an HTTP status code 500 and the + // ContinueOnError ignore errors and try to serve as many metrics as possible. + // However, if no metrics can be served, serve an HTTP status code 500 and the // last error message in the body. Only use this in deliberate "best // effort" metrics collection scenarios. In this case, it is highly // recommended to provide other means of detecting errors: By setting an @@ -41,7 +50,7 @@ const ( // alerts. ContinueOnError - // Panic upon the first error encountered (useful for "crash only" apps). + // PanicOnError panics upon the first error encountered (useful for "crash only" apps). PanicOnError ) @@ -50,6 +59,7 @@ const ( type HandlerOpts promhttp.HandlerOpts func (ho *HandlerOpts) toPromhttpHandlerOpts() promhttp.HandlerOpts { + ho.ProcessStartTime = processStartedAt return promhttp.HandlerOpts(*ho) } diff --git a/staging/src/k8s.io/component-base/metrics/legacyregistry/registry.go b/staging/src/k8s.io/component-base/metrics/legacyregistry/registry.go index 79c806d8b2a..64a430b7964 100644 --- a/staging/src/k8s.io/component-base/metrics/legacyregistry/registry.go +++ b/staging/src/k8s.io/component-base/metrics/legacyregistry/registry.go @@ -18,6 +18,7 @@ package legacyregistry import ( "net/http" + "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" @@ -45,19 +46,22 @@ var ( // Registerer exposes the global registerer Registerer = defaultRegistry.Registerer + + processStart time.Time ) func init() { RawMustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})) RawMustRegister(collectors.NewGoCollector(collectors.WithGoCollectorRuntimeMetrics(collectors.MetricsAll))) defaultRegistry.RegisterMetaMetrics() + processStart = time.Now() } // Handler returns an HTTP handler for the DefaultGatherer. It is // already instrumented with InstrumentHandler (using "prometheus" as handler // name). func Handler() http.Handler { - return promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, promhttp.HandlerFor(defaultRegistry, promhttp.HandlerOpts{})) + return promhttp.InstrumentMetricHandler(prometheus.DefaultRegisterer, promhttp.HandlerFor(defaultRegistry, promhttp.HandlerOpts{ProcessStartTime: processStart})) } // HandlerWithReset returns an HTTP handler for the DefaultGatherer but invokes @@ -65,7 +69,7 @@ func Handler() http.Handler { func HandlerWithReset() http.Handler { return promhttp.InstrumentMetricHandler( prometheus.DefaultRegisterer, - metrics.HandlerWithReset(defaultRegistry, metrics.HandlerOpts{})) + metrics.HandlerWithReset(defaultRegistry, metrics.HandlerOpts{ProcessStartTime: processStart})) } // CustomRegister registers a custom collector but uses the global registry. diff --git a/staging/src/k8s.io/component-base/metrics/legacyregistry/registry_test.go b/staging/src/k8s.io/component-base/metrics/legacyregistry/registry_test.go new file mode 100644 index 00000000000..fa279c35a93 --- /dev/null +++ b/staging/src/k8s.io/component-base/metrics/legacyregistry/registry_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2023 The Kubernetes Authors. + +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 legacyregistry + +import ( + "net/http" + "net/http/httptest" + "strconv" + "testing" + "time" +) + +const ( + processStartTimeHeader = "Process-Start-Time-Unix" +) + +func TestProcessStartTimeHeader(t *testing.T) { + now := time.Now() + handler := Handler() + + request, _ := http.NewRequest("GET", "/", nil) + writer := httptest.NewRecorder() + handler.ServeHTTP(writer, request) + got := writer.Header().Get(processStartTimeHeader) + gotInt, _ := strconv.ParseInt(got, 10, 64) + if gotInt != now.Unix() { + t.Errorf("got %d, wanted %d", gotInt, now.Unix()) + } +}