mirror of
https://github.com/openfaas/faas.git
synced 2025-06-08 16:26:47 +00:00
Return the original upstream response body when the the list request returns an error. In general, the provider is returning useful and actionable error messages for the user, the previous code hid this in the logs and this is easy for user to overlook. Additionally, remove an early return from error case after fetching metrics. This looked like a bug and could result in empty api responses if there was a prometheus error. Signed-off-by: Lucas Roesler <roesler.lucas@gmail.com>
107 lines
2.7 KiB
Go
107 lines
2.7 KiB
Go
package metrics
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strconv"
|
|
|
|
types "github.com/openfaas/faas-provider/types"
|
|
)
|
|
|
|
// AddMetricsHandler wraps a http.HandlerFunc with Prometheus metrics
|
|
func AddMetricsHandler(handler http.HandlerFunc, prometheusQuery PrometheusQueryFetcher) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
recorder := httptest.NewRecorder()
|
|
handler.ServeHTTP(recorder, r)
|
|
upstreamCall := recorder.Result()
|
|
|
|
if upstreamCall.Body == nil {
|
|
log.Println("Upstream call had empty body.")
|
|
return
|
|
}
|
|
|
|
defer upstreamCall.Body.Close()
|
|
upstreamBody, _ := ioutil.ReadAll(upstreamCall.Body)
|
|
|
|
if recorder.Code != http.StatusOK {
|
|
log.Printf("List functions responded with code %d, body: %s",
|
|
recorder.Code,
|
|
string(upstreamBody))
|
|
http.Error(w, string(upstreamBody), recorder.Code)
|
|
return
|
|
}
|
|
|
|
var functions []types.FunctionStatus
|
|
|
|
err := json.Unmarshal(upstreamBody, &functions)
|
|
if err != nil {
|
|
log.Printf("Metrics upstream error: %s, value: %s", err, string(upstreamBody))
|
|
|
|
http.Error(w, "Unable to parse list of functions from provider", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Ensure values are empty first.
|
|
for i := range functions {
|
|
functions[i].InvocationCount = 0
|
|
}
|
|
|
|
if len(functions) > 0 {
|
|
|
|
ns := functions[0].Namespace
|
|
q := fmt.Sprintf(`sum(gateway_function_invocation_total{function_name=~".*.%s"}) by (function_name)`, ns)
|
|
// Restrict query results to only function names matching namespace suffix.
|
|
|
|
results, err := prometheusQuery.Fetch(url.QueryEscape(q))
|
|
if err != nil {
|
|
// log the error but continue, the mixIn will correctly handle the empty results.
|
|
log.Printf("Error querying Prometheus: %s\n", err.Error())
|
|
}
|
|
mixIn(&functions, results)
|
|
}
|
|
|
|
bytesOut, err := json.Marshal(functions)
|
|
if err != nil {
|
|
log.Printf("Error serializing functions: %s", err)
|
|
http.Error(w, "Error writing response after adding metrics", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(bytesOut)
|
|
}
|
|
}
|
|
|
|
func mixIn(functions *[]types.FunctionStatus, metrics *VectorQueryResponse) {
|
|
|
|
if functions == nil {
|
|
return
|
|
}
|
|
|
|
for i, function := range *functions {
|
|
for _, v := range metrics.Data.Result {
|
|
|
|
if v.Metric.FunctionName == fmt.Sprintf("%s.%s", function.Name, function.Namespace) {
|
|
metricValue := v.Value[1]
|
|
switch value := metricValue.(type) {
|
|
case string:
|
|
f, err := strconv.ParseFloat(value, 64)
|
|
if err != nil {
|
|
log.Printf("add_metrics: unable to convert value %q for metric: %s", value, err)
|
|
continue
|
|
}
|
|
(*functions)[i].InvocationCount += f
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|