Skip to content

Commit

Permalink
Basic initial instrumentation of the apiserver. This links in the
Browse files Browse the repository at this point in the history
prometheus library for monitoring, which exports some basic resource
usage metrics by default, like number of goroutines, open file
descriptors, resident and virtual memory, etc. I've also started adding
in request counters and latency histograms, but have only added them to two
of our HTTP handlers. If this looks reasonable, I'll add them to the rest
in a second PR.
  • Loading branch information
a-robinson committed Feb 10, 2015
1 parent 2c2a595 commit 2463cff
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 63 deletions.
1 change: 0 additions & 1 deletion pkg/apiserver/api_installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ func (a *APIInstaller) newWebService() *restful.WebService {
}

func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage, ws *restful.WebService, watchHandler http.Handler, redirectHandler http.Handler, proxyHandler http.Handler) error {

// Handler for standard REST verbs (GET, PUT, POST and DELETE).
restVerbHandler := restfulStripPrefix(a.prefix, a.restHandler)
object := storage.New()
Expand Down
31 changes: 25 additions & 6 deletions pkg/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,23 @@ import (

"github.com/emicklei/go-restful"
"github.com/golang/glog"
"github.com/prometheus/client_golang/prometheus"
)

var (
apiserverLatencies = prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Name: "apiserver_request_latencies",
Help: "Response latency summary in microseconds for each request handler, verb, and HTTP response code.",
},
[]string{"handler", "verb", "code"},
)
)

func init() {
prometheus.MustRegister(apiserverLatencies)
}

// mux is an object that can register http handlers.
type Mux interface {
Handle(pattern string, handler http.Handler)
Expand Down Expand Up @@ -126,8 +141,9 @@ func InstallValidator(mux Mux, servers func() map[string]Server) {
// TODO: document all handlers
// InstallSupport registers the APIServer support functions
func InstallSupport(mux Mux, ws *restful.WebService) {
// TODO: convert healthz to restful and remove container arg
// TODO: convert healthz and metrics to restful and remove container arg
healthz.InstallHandler(mux)
mux.Handle("/metrics", prometheus.Handler())

// Set up a service to return the git code version.
ws.Path("/version")
Expand Down Expand Up @@ -196,25 +212,28 @@ func writeJSON(statusCode int, codec runtime.Codec, object runtime.Object, w htt
w.Write(formatted.Bytes())
}

// errorJSON renders an error to the response.
func errorJSON(err error, codec runtime.Codec, w http.ResponseWriter) {
// errorJSON renders an error to the response. Returns the HTTP status code of the error.
func errorJSON(err error, codec runtime.Codec, w http.ResponseWriter) int {
status := errToAPIStatus(err)
writeJSON(status.Code, codec, status, w)
return status.Code
}

// errorJSONFatal renders an error to the response, and if codec fails will render plaintext
func errorJSONFatal(err error, codec runtime.Codec, w http.ResponseWriter) {
// errorJSONFatal renders an error to the response, and if codec fails will render plaintext.
// Returns the HTTP status code of the error.
func errorJSONFatal(err error, codec runtime.Codec, w http.ResponseWriter) int {
util.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err))
status := errToAPIStatus(err)
output, err := codec.Encode(status)
if err != nil {
w.WriteHeader(status.Code)
fmt.Fprintf(w, "%s: %s", status.Reason, status.Message)
return
return status.Code
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status.Code)
w.Write(output)
return status.Code
}

// writeRawJSON writes a non-API object in JSON.
Expand Down
35 changes: 34 additions & 1 deletion pkg/apiserver/redirect.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,50 @@ package apiserver

import (
"net/http"
"strconv"
"time"

"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"

"github.com/prometheus/client_golang/prometheus"
)

var (
redirectCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "apiserver_redirect_count",
Help: "Counter of redirect requests broken out by apiserver resource and HTTP response code.",
},
[]string{"resource", "code"},
)
)

func init() {
prometheus.MustRegister(redirectCounter)
}

type RedirectHandler struct {
storage map[string]RESTStorage
codec runtime.Codec
apiRequestInfoResolver *APIRequestInfoResolver
}

func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
apiResource := ""
var httpCode int
reqStart := time.Now()
defer func() {
redirectCounter.WithLabelValues(apiResource, strconv.Itoa(httpCode)).Inc()
apiserverLatencies.WithLabelValues("redirect", "get", strconv.Itoa(httpCode)).Observe(float64((time.Since(reqStart)) / time.Microsecond))
}()

requestInfo, err := r.apiRequestInfoResolver.GetAPIRequestInfo(req)
if err != nil {
notFound(w, req)
httpCode = http.StatusNotFound
return
}
resource, parts := requestInfo.Resource, requestInfo.Parts
Expand All @@ -43,30 +70,36 @@ func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// redirection requires /resource/resourceName path parts
if len(parts) != 2 || req.Method != "GET" {
notFound(w, req)
httpCode = http.StatusNotFound
return
}
id := parts[1]
storage, ok := r.storage[resource]
if !ok {
httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
notFound(w, req)
apiResource = "invalidResource"
httpCode = http.StatusNotFound
return
}
apiResource = resource

redirector, ok := storage.(Redirector)
if !ok {
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
errorJSON(errors.NewMethodNotSupported(resource, "redirect"), r.codec, w)
httpCode = errorJSON(errors.NewMethodNotSupported(resource, "redirect"), r.codec, w)
return
}

location, err := redirector.ResourceLocation(ctx, id)
if err != nil {
status := errToAPIStatus(err)
writeJSON(status.Code, r.codec, status, w)
httpCode = status.Code
return
}

w.Header().Set("Location", location)
w.WriteHeader(http.StatusTemporaryRedirect)
httpCode = http.StatusTemporaryRedirect
}
Loading

0 comments on commit 2463cff

Please sign in to comment.