Commit 5bbf4932 authored by Nick Thomas's avatar Nick Thomas

Merge branch 'mk/image-scaler-total-requests-metric' into 'master'

Add a total-requests metric for the image scaler

See merge request gitlab-org/gitlab-workhorse!638
parents b5dab707 5d736d9b
---
title: Add a total requests metric for image scaling
merge_request: 638
author:
type: other
...@@ -43,6 +43,15 @@ type processCounter struct { ...@@ -43,6 +43,15 @@ type processCounter struct {
n int32 n int32
} }
type resizeStatus = string
const (
statusSuccess = "success" // a rescaled image was served
statusScalingFailure = "scaling-failed" // scaling failed but the original image was served
statusRequestFailure = "request-failed" // no image was served
statusUnknown = "unknown" // indicates an unhandled status case
)
var envInjector = tracing.NewEnvInjector() var envInjector = tracing.NewEnvInjector()
// Images might be located remotely in object storage, in which case we need to stream // Images might be located remotely in object storage, in which case we need to stream
...@@ -86,13 +95,14 @@ var ( ...@@ -86,13 +95,14 @@ var (
Help: "Amount of image resizing scaler processes working now", Help: "Amount of image resizing scaler processes working now",
}, },
) )
imageResizeCompleted = prometheus.NewCounter( imageResizeRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Namespace: namespace, Namespace: namespace,
Subsystem: subsystem, Subsystem: subsystem,
Name: "completed_total", Name: "requests_total",
Help: "Amount of image resizing processes sucessfully completed", Help: "Image resizing operations requested",
}, },
[]string{"status"},
) )
imageResizeDurations = prometheus.NewHistogramVec( imageResizeDurations = prometheus.NewHistogramVec(
prometheus.HistogramOpts{ prometheus.HistogramOpts{
...@@ -116,7 +126,7 @@ var ( ...@@ -116,7 +126,7 @@ var (
func init() { func init() {
prometheus.MustRegister(imageResizeConcurrencyLimitExceeds) prometheus.MustRegister(imageResizeConcurrencyLimitExceeds)
prometheus.MustRegister(imageResizeProcesses) prometheus.MustRegister(imageResizeProcesses)
prometheus.MustRegister(imageResizeCompleted) prometheus.MustRegister(imageResizeRequests)
prometheus.MustRegister(imageResizeDurations) prometheus.MustRegister(imageResizeDurations)
} }
...@@ -127,12 +137,18 @@ func NewResizer(cfg config.Config) *Resizer { ...@@ -127,12 +137,18 @@ func NewResizer(cfg config.Config) *Resizer {
// This Injecter forks into a dedicated scaler process to resize an image identified by path or URL // This Injecter forks into a dedicated scaler process to resize an image identified by path or URL
// and streams the resized image back to the client // and streams the resized image back to the client
func (r *Resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData string) { func (r *Resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData string) {
var status resizeStatus = statusUnknown
defer func() {
imageResizeRequests.WithLabelValues(status).Inc()
}()
start := time.Now() start := time.Now()
logger := log.ContextLogger(req.Context()) logger := log.ContextLogger(req.Context())
params, err := r.unpackParameters(paramsData) params, err := r.unpackParameters(paramsData)
if err != nil { if err != nil {
// This means the response header coming from Rails was malformed; there is no way // This means the response header coming from Rails was malformed; there is no way
// to sensibly recover from this other than failing fast // to sensibly recover from this other than failing fast
status = statusRequestFailure
helper.Fail500(w, req, fmt.Errorf("ImageResizer: Failed reading image resize params: %v", err)) helper.Fail500(w, req, fmt.Errorf("ImageResizer: Failed reading image resize params: %v", err))
return return
} }
...@@ -140,6 +156,7 @@ func (r *Resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st ...@@ -140,6 +156,7 @@ func (r *Resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st
sourceImageReader, fileSize, err := openSourceImage(params.Location) sourceImageReader, fileSize, err := openSourceImage(params.Location)
if err != nil { if err != nil {
// This means we cannot even read the input image; fail fast. // This means we cannot even read the input image; fail fast.
status = statusRequestFailure
helper.Fail500(w, req, fmt.Errorf("ImageResizer: Failed opening image data stream: %v", err)) helper.Fail500(w, req, fmt.Errorf("ImageResizer: Failed opening image data stream: %v", err))
return return
} }
...@@ -159,22 +176,28 @@ func (r *Resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st ...@@ -159,22 +176,28 @@ func (r *Resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st
// will point to the original image, i.e. we render it unchanged. // will point to the original image, i.e. we render it unchanged.
imageReader, resizeCmd, err := r.tryResizeImage(req, sourceImageReader, logger.Writer(), params, fileSize, r.Config.ImageResizerConfig) imageReader, resizeCmd, err := r.tryResizeImage(req, sourceImageReader, logger.Writer(), params, fileSize, r.Config.ImageResizerConfig)
if err != nil { if err != nil {
// something failed, but we can still write out the original image, do don't return early // Something failed, but we can still write out the original image, so don't return early.
helper.LogErrorWithFields(req, err, *logFields(0)) helper.LogErrorWithFields(req, err, *logFields(0))
} }
defer helper.CleanUpProcessGroup(resizeCmd) defer helper.CleanUpProcessGroup(resizeCmd)
imageResizeCompleted.Inc()
w.Header().Del("Content-Length") w.Header().Del("Content-Length")
bytesWritten, err := serveImage(imageReader, w, resizeCmd) bytesWritten, err := serveImage(imageReader, w, resizeCmd)
// We failed serving image data; this is a hard failure.
if err != nil { if err != nil {
handleFailedCommand(w, req, bytesWritten, err, logFields(bytesWritten)) status = statusRequestFailure
if bytesWritten <= 0 {
helper.Fail500(w, req, err)
} else {
helper.LogErrorWithFields(req, err, *logFields(bytesWritten))
}
return return
} }
// This means we served the original image because rescaling failed; this is a soft failure
if resizeCmd == nil { if resizeCmd == nil {
// This means we served the original image because rescaling failed status = statusScalingFailure
logger.WithFields(*logFields(bytesWritten)).Printf("ImageResizer: Served original") logger.WithFields(*logFields(bytesWritten)).Printf("ImageResizer: Served original")
return return
} }
...@@ -183,6 +206,8 @@ func (r *Resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st ...@@ -183,6 +206,8 @@ func (r *Resizer) Inject(w http.ResponseWriter, req *http.Request, paramsData st
imageResizeDurations.WithLabelValues(params.ContentType, widthLabelVal).Observe(time.Since(start).Seconds()) imageResizeDurations.WithLabelValues(params.ContentType, widthLabelVal).Observe(time.Since(start).Seconds())
logger.WithFields(*logFields(bytesWritten)).Printf("ImageResizer: Success") logger.WithFields(*logFields(bytesWritten)).Printf("ImageResizer: Success")
status = statusSuccess
} }
// Streams image data from the given reader to the given writer and returns the number of bytes written. // Streams image data from the given reader to the given writer and returns the number of bytes written.
...@@ -201,14 +226,6 @@ func serveImage(r io.Reader, w io.Writer, resizeCmd *exec.Cmd) (int64, error) { ...@@ -201,14 +226,6 @@ func serveImage(r io.Reader, w io.Writer, resizeCmd *exec.Cmd) (int64, error) {
return bytesWritten, nil return bytesWritten, nil
} }
func handleFailedCommand(w http.ResponseWriter, req *http.Request, bytesWritten int64, err error, logFields *log.Fields) {
if bytesWritten <= 0 {
helper.Fail500(w, req, err)
} else {
helper.LogErrorWithFields(req, err, *logFields)
}
}
func (r *Resizer) unpackParameters(paramsData string) (*resizeParams, error) { func (r *Resizer) unpackParameters(paramsData string) (*resizeParams, error) {
var params resizeParams var params resizeParams
if err := r.Unpack(&params, paramsData); err != nil { if err := r.Unpack(&params, paramsData); err != nil {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment