mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
This is part of a series of changes to add metric charts in performance benchmarks. This change is meant to do three things: - Remove the interface indirection from the Prometheus library, which is performance-critical due to its use in writing out profiling metrics (although the runsc metric server also benefits from this too). - Use a `StringWriter`-like writer contract, to avoid needless casting between strings and bytes within the Prometheus library. The library only ever needs to deal with strings, so it is up to callers to do the conversion to bytes if they need to (which the runsc metric-server does). - Avoid buffer allocations in the metric server when each snapshot is larger than the buffer size. Instead, buffers are saved and reused. PiperOrigin-RevId: 630500004
104 lines
3.5 KiB
Go
104 lines
3.5 KiB
Go
// Copyright 2023 The gVisor 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 metricserver
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"gvisor.dev/gvisor/pkg/log"
|
|
)
|
|
|
|
// httpTimeout is the timeout used for all connect/read/write operations of the HTTP server.
|
|
const httpTimeout = 1 * time.Minute
|
|
|
|
// httpResult is returned by HTTP handlers.
|
|
type httpResult struct {
|
|
code int
|
|
err error
|
|
}
|
|
|
|
// httpOK is the "everything went fine" HTTP result.
|
|
var httpOK = httpResult{code: http.StatusOK}
|
|
|
|
// serveIndex serves the index page.
|
|
func (m *metricServer) serveIndex(w *httpResponseWriter, req *http.Request) httpResult {
|
|
if req.URL.Path != "/" {
|
|
if strings.HasPrefix(req.URL.Path, "/metrics?") {
|
|
// Prometheus's scrape_config.metrics_path takes in a query path and automatically encodes
|
|
// all special characters in it to %-form, including the "?" character.
|
|
// This can prevent use of query parameters, and we end up here instead.
|
|
// To address this, rewrite the URL to undo this transformation.
|
|
// This means requesting "/metrics%3Ffoo=bar" is rewritten to "/metrics?foo=bar".
|
|
req.URL.RawQuery = strings.TrimPrefix(req.URL.Path, "/metrics?")
|
|
req.URL.Path = "/metrics"
|
|
return m.serveMetrics(w, req)
|
|
}
|
|
return httpResult{http.StatusNotFound, errors.New("path not found")}
|
|
}
|
|
w.WriteString("<html><head><title>runsc metrics</title></head><body>")
|
|
w.WriteString("<p>You have reached the runsc metrics server page!</p>")
|
|
w.WriteString(`<p>To see actual metric data, head over to <a href="/metrics">/metrics</a>.</p>`)
|
|
w.WriteString("</body></html>")
|
|
return httpOK
|
|
}
|
|
|
|
// httpResponseWriter is a ResponseWriter that also implements io.StringWriter.
|
|
type httpResponseWriter struct {
|
|
resp http.ResponseWriter
|
|
}
|
|
|
|
// Header implements http.ResponseWriter.Header.
|
|
func (w *httpResponseWriter) Header() http.Header {
|
|
return w.resp.Header()
|
|
}
|
|
|
|
// Write implements http.ResponseWriter.Write.
|
|
func (w *httpResponseWriter) Write(b []byte) (int, error) {
|
|
return w.resp.Write(b)
|
|
}
|
|
|
|
// WriteHeader implements http.ResponseWriter.WriteHeader.
|
|
func (w *httpResponseWriter) WriteHeader(code int) {
|
|
w.resp.WriteHeader(code)
|
|
}
|
|
|
|
// WriteString implements io.StringWriter.WriteString.
|
|
func (w *httpResponseWriter) WriteString(s string) (int, error) {
|
|
return w.resp.Write([]byte(s))
|
|
}
|
|
|
|
// logRequest wraps an HTTP handler and adds logging to it.
|
|
func logRequest(f func(w *httpResponseWriter, req *http.Request) httpResult) func(w http.ResponseWriter, req *http.Request) {
|
|
return func(w http.ResponseWriter, req *http.Request) {
|
|
log.Infof("Request: %s %s", req.Method, req.URL.Path)
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Warningf("Request: %s %s: Panic:\n%v", req.Method, req.URL.Path, r)
|
|
}
|
|
}()
|
|
result := f(&httpResponseWriter{resp: w}, req)
|
|
if result.err != nil {
|
|
http.Error(w, result.err.Error(), result.code)
|
|
log.Warningf("Request: %s %s: Failed with HTTP code %d: %v", req.Method, req.URL.Path, result.code, result.err)
|
|
}
|
|
// Run GC after every request to keep memory usage as predictable and as flat as possible.
|
|
runtime.GC()
|
|
}
|
|
}
|