diff --git a/gateway/handlers/asyncreport.go b/gateway/handlers/asyncreport.go index 47e3abaa..0003c40d 100644 --- a/gateway/handlers/asyncreport.go +++ b/gateway/handlers/asyncreport.go @@ -27,5 +27,7 @@ func MakeAsyncReport(metrics metrics.MetricOptions) http.HandlerFunc { var taken time.Duration taken = time.Duration(report.TimeTaken) trackTimeExact(taken, metrics, report.FunctionName) + + w.WriteHeader(http.StatusAccepted) } } diff --git a/gateway/handlers/forwarding_proxy.go b/gateway/handlers/forwarding_proxy.go index 8f3d0e22..4e27fc3f 100644 --- a/gateway/handlers/forwarding_proxy.go +++ b/gateway/handlers/forwarding_proxy.go @@ -11,13 +11,10 @@ import ( "net/http" "os" "regexp" - "strconv" "strings" "time" - "github.com/openfaas/faas/gateway/metrics" "github.com/openfaas/faas/gateway/types" - "github.com/prometheus/client_golang/prometheus" ) // functionMatcher parses out the service name (group 1) and rest of path (group 2). @@ -31,11 +28,6 @@ const ( pathIndex = 2 // pathIndex is the path i.e. /employee/:id/ ) -// HTTPNotifier notify about HTTP request/response -type HTTPNotifier interface { - Notify(method string, URL string, originalURL string, statusCode int, duration time.Duration) -} - // BaseURLResolver URL resolver for upstream requests type BaseURLResolver interface { Resolve(r *http.Request) string @@ -140,78 +132,6 @@ func copyHeaders(destination http.Header, source *http.Header) { } } -type PrometheusServiceNotifier struct { - ServiceMetrics *metrics.ServiceMetricOptions -} - -// Notify about service metrics -func (psn PrometheusServiceNotifier) Notify(method string, URL string, originalURL string, statusCode int, duration time.Duration) { - code := fmt.Sprintf("%d", statusCode) - psn.ServiceMetrics.Counter.WithLabelValues(code).Inc() - - psn.ServiceMetrics.Histogram.WithLabelValues(method, urlToLabel(URL), code).Observe(duration.Seconds()) -} - -var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9]+`) - -// converts a URL path to a string compatible with Prometheus label value. -func urlToLabel(path string) string { - result := invalidChars.ReplaceAllString(path, "_") - result = strings.ToLower(strings.Trim(result, "_")) - if result == "" { - result = "root" - } - return result -} - -// PrometheusFunctionNotifier records metrics to Prometheus -type PrometheusFunctionNotifier struct { - Metrics *metrics.MetricOptions -} - -// Notify records metrics in Prometheus -func (p PrometheusFunctionNotifier) Notify(method string, URL string, originalURL string, statusCode int, duration time.Duration) { - seconds := duration.Seconds() - serviceName := getServiceName(originalURL) - - p.Metrics.GatewayFunctionsHistogram. - WithLabelValues(serviceName). - Observe(seconds) - - code := strconv.Itoa(statusCode) - - p.Metrics.GatewayFunctionInvocation. - With(prometheus.Labels{"function_name": serviceName, "code": code}). - Inc() -} - -func getServiceName(urlValue string) string { - var serviceName string - forward := "/function/" - if strings.HasPrefix(urlValue, forward) { - // With a path like `/function/xyz/rest/of/path?q=a`, the service - // name we wish to locate is just the `xyz` portion. With a postive - // match on the regex below, it will return a three-element slice. - // The item at index `0` is the same as `urlValue`, at `1` - // will be the service name we need, and at `2` the rest of the path. - matcher := functionMatcher.Copy() - matches := matcher.FindStringSubmatch(urlValue) - if len(matches) == hasPathCount { - serviceName = matches[nameIndex] - } - } - return strings.Trim(serviceName, "/") -} - -// LoggingNotifier notifies a log about a request -type LoggingNotifier struct { -} - -// Notify a log about a request -func (LoggingNotifier) Notify(method string, URL string, originalURL string, statusCode int, duration time.Duration) { - log.Printf("Forwarded [%s] to %s - [%d] - %f seconds", method, originalURL, statusCode, duration.Seconds()) -} - // SingleHostBaseURLResolver resolves URLs against a single BaseURL type SingleHostBaseURLResolver struct { BaseURL string diff --git a/gateway/handlers/notifiers.go b/gateway/handlers/notifiers.go new file mode 100644 index 00000000..ed11c2a7 --- /dev/null +++ b/gateway/handlers/notifiers.go @@ -0,0 +1,91 @@ +package handlers + +import ( + "fmt" + "log" + "regexp" + "strconv" + "strings" + "time" + + "github.com/openfaas/faas/gateway/metrics" + "github.com/prometheus/client_golang/prometheus" +) + +// HTTPNotifier notify about HTTP request/response +type HTTPNotifier interface { + Notify(method string, URL string, originalURL string, statusCode int, duration time.Duration) +} + +// PrometheusServiceNotifier notifier for core service endpoints +type PrometheusServiceNotifier struct { + ServiceMetrics *metrics.ServiceMetricOptions +} + +// Notify about service metrics +func (psn PrometheusServiceNotifier) Notify(method string, URL string, originalURL string, statusCode int, duration time.Duration) { + code := fmt.Sprintf("%d", statusCode) + path := urlToLabel(URL) + psn.ServiceMetrics.Counter.WithLabelValues(code, method, path).Inc() + psn.ServiceMetrics.Histogram.WithLabelValues(method, path, code).Observe(duration.Seconds()) +} + +var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9]+`) + +// converts a URL path to a string compatible with Prometheus label value. +func urlToLabel(path string) string { + result := invalidChars.ReplaceAllString(path, "_") + result = strings.ToLower(strings.Trim(result, "_")) + if result == "" { + result = "root" + } + return result +} + +// PrometheusFunctionNotifier records metrics to Prometheus +type PrometheusFunctionNotifier struct { + Metrics *metrics.MetricOptions +} + +// Notify records metrics in Prometheus +func (p PrometheusFunctionNotifier) Notify(method string, URL string, originalURL string, statusCode int, duration time.Duration) { + seconds := duration.Seconds() + serviceName := getServiceName(originalURL) + + p.Metrics.GatewayFunctionsHistogram. + WithLabelValues(serviceName). + Observe(seconds) + + code := strconv.Itoa(statusCode) + + p.Metrics.GatewayFunctionInvocation. + With(prometheus.Labels{"function_name": serviceName, "code": code}). + Inc() +} + +func getServiceName(urlValue string) string { + var serviceName string + forward := "/function/" + if strings.HasPrefix(urlValue, forward) { + // With a path like `/function/xyz/rest/of/path?q=a`, the service + // name we wish to locate is just the `xyz` portion. With a postive + // match on the regex below, it will return a three-element slice. + // The item at index `0` is the same as `urlValue`, at `1` + // will be the service name we need, and at `2` the rest of the path. + matcher := functionMatcher.Copy() + matches := matcher.FindStringSubmatch(urlValue) + if len(matches) == hasPathCount { + serviceName = matches[nameIndex] + } + } + return strings.Trim(serviceName, "/") +} + +// LoggingNotifier notifies a log about a request +type LoggingNotifier struct { +} + +// Notify a log about a request +func (LoggingNotifier) Notify(method string, URL string, originalURL string, statusCode int, duration time.Duration) { + log.Printf("Forwarded [%s] to %s - [%d] - %f seconds", method, originalURL, statusCode, duration.Seconds()) +} diff --git a/gateway/handlers/queue_proxy.go b/gateway/handlers/queue_proxy.go index c6dc839c..3818a58c 100644 --- a/gateway/handlers/queue_proxy.go +++ b/gateway/handlers/queue_proxy.go @@ -17,12 +17,15 @@ import ( // MakeQueuedProxy accepts work onto a queue func MakeQueuedProxy(metrics metrics.MetricOptions, wildcard bool, canQueueRequests queue.CanQueueRequests, pathTransformer URLPathTransformer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() + if r.Body != nil { + defer r.Body.Close() + } body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) return } @@ -37,6 +40,7 @@ func MakeQueuedProxy(metrics metrics.MetricOptions, wildcard bool, canQueueReque urlVal, urlErr := url.Parse(callbackURLHeader) if urlErr != nil { w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(urlErr.Error())) return } @@ -55,9 +59,7 @@ func MakeQueuedProxy(metrics metrics.MetricOptions, wildcard bool, canQueueReque CallbackURL: callbackURL, } - err = canQueueRequests.Queue(req) - - if err != nil { + if err = canQueueRequests.Queue(req); err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) fmt.Println(err) diff --git a/gateway/metrics/exporter.go b/gateway/metrics/exporter.go index 0d867534..1585b5bc 100644 --- a/gateway/metrics/exporter.go +++ b/gateway/metrics/exporter.go @@ -57,6 +57,7 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { WithLabelValues(service.Name). Set(float64(service.Replicas)) } + e.metricOptions.ServiceReplicasGauge.Collect(ch) e.metricOptions.ServiceMetrics.Counter.Collect(ch) diff --git a/gateway/metrics/metrics.go b/gateway/metrics/metrics.go index c25934f2..1bb79658 100644 --- a/gateway/metrics/metrics.go +++ b/gateway/metrics/metrics.go @@ -78,7 +78,7 @@ func BuildMetricsOptions() MetricOptions { Name: "requests_total", Help: "The total number of HTTP requests.", }, - []string{"status"}, + []string{"method", "path", "status"}, ) serviceMetricOptions := &ServiceMetricOptions{