Add usage metrics for describe command

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
This commit is contained in:
Alex Ellis (OpenFaaS Ltd)
2022-01-26 13:28:48 +00:00
committed by Alex Ellis
parent d94600d2d2
commit b1c09c0243
13 changed files with 200 additions and 95 deletions

View File

@@ -115,6 +115,9 @@ func runDescribe(cmd *cobra.Command, args []string) error {
Labels: function.Labels,
Annotations: function.Annotations,
}
if function.Usage != nil {
funcDesc.Usage = function.Usage
}
printFunctionDescription(funcDesc)
@@ -148,6 +151,17 @@ func printFunctionDescription(funcDesc schema.FunctionDescription) {
fmt.Fprintln(w, "Async URL:\t "+funcDesc.AsyncURL)
printMap(w, "Labels", *funcDesc.Labels)
printMap(w, "Annotations", *funcDesc.Annotations)
if funcDesc.Usage != nil {
fmt.Println()
fmt.Fprintf(w, "RAM:\t %.2f MB\n", (funcDesc.Usage.TotalMemoryBytes / 1024 / 1024))
cpu := funcDesc.Usage.CPU
if cpu < 0 {
cpu = 1
}
fmt.Fprintf(w, "CPU:\t %.0f Mi\n", (cpu))
}
w.Flush()
}

2
go.mod
View File

@@ -16,7 +16,7 @@ require (
github.com/gotestyourself/gotestyourself v1.4.0 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0
github.com/openfaas/faas-provider v0.18.5
github.com/openfaas/faas-provider v0.18.9
github.com/openfaas/faas/gateway v0.0.0-20210311210633-a6dbb4cd0285
github.com/pkg/errors v0.9.1
github.com/ryanuber/go-glob v1.0.0

5
go.sum
View File

@@ -246,10 +246,9 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/openfaas/faas-provider v0.17.3 h1:LN76lrXUKAx27o5X8l+daKWEzsdiW2E99jMOlI1SO5Q=
github.com/openfaas/faas-provider v0.17.3/go.mod h1:fq1JL0mX4rNvVVvRLaLRJ3H6o667sHuyP5p/7SZEe98=
github.com/openfaas/faas-provider v0.18.5 h1:y7CCkbh0dW9aWpRisXbjgG9MTZVrdiDKLNt7qqo8M5c=
github.com/openfaas/faas-provider v0.18.5/go.mod h1:fq1JL0mX4rNvVVvRLaLRJ3H6o667sHuyP5p/7SZEe98=
github.com/openfaas/faas-provider v0.18.9 h1:nHPlq9PYLGLuyhuXfASlBPOvXiZC/fJqHOr6m+0Fn1s=
github.com/openfaas/faas-provider v0.18.9/go.mod h1:S217qfIaMrv+XKJxgbhBzJzCfyFvoIF+BvYdDo6XIDQ=
github.com/openfaas/faas/gateway v0.0.0-20210311210633-a6dbb4cd0285 h1:oLrTwALS1EyjvG5AXZQM43CDZPNXoIcjDpeAK3UG/yM=
github.com/openfaas/faas/gateway v0.0.0-20210311210633-a6dbb4cd0285/go.mod h1:ZZUyq1Ghd3zvhKvSWfXelAsvbUxWP1TqWtvapP4701Q=
github.com/openfaas/nats-queue-worker v0.0.0-20200512211843-8e9eefd5a320/go.mod h1:BfT8MB890hbhbtPid+X/oU0HAznGFS581NiG2hkr8Rc=

View File

@@ -54,6 +54,26 @@ func NewClient(auth ClientAuth, gatewayURL string, transport http.RoundTripper,
}, nil
}
func (c *Client) newRequestByURL(method string, uri *url.URL, body io.Reader) (*http.Request, error) {
req, err := http.NewRequest(method, uri.String(), body)
if err != nil {
return nil, err
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
c.ClientAuth.Set(req)
return req, err
}
//newRequest create a new HTTP request with authentication
func (c *Client) newRequest(method, path string, body io.Reader) (*http.Request, error) {
u, err := url.Parse(path)

View File

@@ -9,6 +9,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"path"
types "github.com/openfaas/faas-provider/types"
)
@@ -20,23 +21,26 @@ func (c *Client) GetFunctionInfo(ctx context.Context, functionName string, names
err error
)
functionPath := fmt.Sprintf("%s/%s", functionPath, functionName)
u := c.GatewayURL
v := u.Query()
if len(namespace) > 0 {
functionPath, err = addQueryParams(functionPath, map[string]string{namespaceKey: namespace})
if err != nil {
return result, err
}
v.Set("namespace", namespace)
}
getRequest, err := c.newRequest(http.MethodGet, functionPath, nil)
// Request CPU/RAM usage if available
v.Set("usage", "1")
u.Path = path.Join(functionPath, functionName)
u.RawQuery = v.Encode()
req, err := c.newRequestByURL(http.MethodGet, u, nil)
if err != nil {
return result, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String())
return result, fmt.Errorf("cannot create URL: %s, error: %w", u.String(), err)
}
res, err := c.doRequest(ctx, getRequest)
res, err := c.doRequest(ctx, req)
if err != nil {
return result, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String())
return result, fmt.Errorf("cannot connect to URL: %s, error: %w", c.GatewayURL.String(), err)
}
if res.Body != nil {
@@ -50,14 +54,15 @@ func (c *Client) GetFunctionInfo(ctx context.Context, functionName string, names
return result, fmt.Errorf("cannot read result from OpenFaaS on URL: %s", c.GatewayURL.String())
}
jsonErr := json.Unmarshal(bytesOut, &result)
if jsonErr != nil {
return result, fmt.Errorf("cannot parse result from OpenFaaS on URL: %s\n%s", c.GatewayURL.String(), jsonErr.Error())
if err := json.Unmarshal(bytesOut, &result); err != nil {
return result, fmt.Errorf("cannot parse result from OpenFaaS on URL: %s\n%w",
c.GatewayURL.String(), err)
}
case http.StatusUnauthorized:
return result, fmt.Errorf("unauthorized access, run \"faas-cli login\" to setup authentication for this server")
case http.StatusNotFound:
return result, fmt.Errorf("No such function: %s", functionName)
return result, fmt.Errorf("no such function: %s", functionName)
default:
bytesOut, err := ioutil.ReadAll(res.Body)
if err == nil {

View File

@@ -77,7 +77,7 @@ func Test_GetFunctionInfo_NotFound(t *testing.T) {
t.Fatalf("Error was not returned")
}
expectedErrMsg := fmt.Sprintf("No such function: %s", functionName)
expectedErrMsg := fmt.Sprintf("no such function: %s", functionName)
if err.Error() != expectedErrMsg {
t.Fatalf("Want: %s, Got: %s", expectedErrMsg, err.Error())
}

View File

@@ -6,6 +6,7 @@ package proxy
import (
"context"
"encoding/json"
"path"
"fmt"
"io/ioutil"
@@ -17,24 +18,23 @@ import (
// ListFunctions list deployed functions
func (c *Client) ListFunctions(ctx context.Context, namespace string) ([]types.FunctionStatus, error) {
var (
results []types.FunctionStatus
listEndpoint string
err error
results []types.FunctionStatus
err error
)
c.AddCheckRedirect(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
})
listEndpoint = systemPath
u := c.GatewayURL
u.Path = path.Join(systemPath)
v := u.Query()
if len(namespace) > 0 {
listEndpoint, err = addQueryParams(listEndpoint, map[string]string{namespaceKey: namespace})
if err != nil {
return results, err
}
v.Set("namespace", namespace)
}
u.RawQuery = v.Encode()
getRequest, err := c.newRequest(http.MethodGet, listEndpoint, nil)
getRequest, err := c.newRequestByURL(http.MethodGet, u, nil)
if err != nil {
return nil, fmt.Errorf("cannot connect to OpenFaaS on URL: %s", c.GatewayURL.String())
}

View File

@@ -3,6 +3,8 @@
package schema
import "github.com/openfaas/faas-provider/types"
//FunctionDescription information related to a function
type FunctionDescription struct {
Name string
@@ -16,4 +18,5 @@ type FunctionDescription struct {
AsyncURL string
Labels *map[string]string
Annotations *map[string]string
Usage *types.FunctionUsage
}

View File

@@ -0,0 +1,51 @@
package types
// FunctionDeployment represents a request to create or update a Function.
type FunctionDeployment struct {
// Service is the name of the function deployment
Service string `json:"service"`
// Image is a fully-qualified container image
Image string `json:"image"`
// Namespace for the function, if supported by the faas-provider
Namespace string `json:"namespace,omitempty"`
// EnvProcess overrides the fprocess environment variable and can be used
// with the watchdog
EnvProcess string `json:"envProcess,omitempty"`
// EnvVars can be provided to set environment variables for the function runtime.
EnvVars map[string]string `json:"envVars,omitempty"`
// Constraints are specific to the faas-provider.
Constraints []string `json:"constraints,omitempty"`
// Secrets list of secrets to be made available to function
Secrets []string `json:"secrets,omitempty"`
// Labels are metadata for functions which may be used by the
// faas-provider or the gateway
Labels *map[string]string `json:"labels,omitempty"`
// Annotations are metadata for functions which may be used by the
// faas-provider or the gateway
Annotations *map[string]string `json:"annotations,omitempty"`
// Limits for function
Limits *FunctionResources `json:"limits,omitempty"`
// Requests of resources requested by function
Requests *FunctionResources `json:"requests,omitempty"`
// ReadOnlyRootFilesystem removes write-access from the root filesystem
// mount-point.
ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem,omitempty"`
}
// FunctionResources Memory and CPU
type FunctionResources struct {
Memory string `json:"memory,omitempty"`
CPU string `json:"cpu,omitempty"`
}

View File

@@ -2,72 +2,6 @@ package types
import "time"
// Secret for underlying orchestrator
type Secret struct {
// Name of the secret
Name string `json:"name"`
// Namespace if applicable for the secret
Namespace string `json:"namespace,omitempty"`
// Value is a string representing the string's value
Value string `json:"value,omitempty"`
// RawValue can be used to provide binary data when
// Value is not set
RawValue []byte `json:"rawValue,omitempty"`
}
// FunctionDeployment represents a request to create or update a Function.
type FunctionDeployment struct {
// Service is the name of the function deployment
Service string `json:"service"`
// Image is a fully-qualified container image
Image string `json:"image"`
// Namespace for the function, if supported by the faas-provider
Namespace string `json:"namespace,omitempty"`
// EnvProcess overrides the fprocess environment variable and can be used
// with the watchdog
EnvProcess string `json:"envProcess,omitempty"`
// EnvVars can be provided to set environment variables for the function runtime.
EnvVars map[string]string `json:"envVars,omitempty"`
// Constraints are specific to the faas-provider.
Constraints []string `json:"constraints,omitempty"`
// Secrets list of secrets to be made available to function
Secrets []string `json:"secrets,omitempty"`
// Labels are metadata for functions which may be used by the
// faas-provider or the gateway
Labels *map[string]string `json:"labels,omitempty"`
// Annotations are metadata for functions which may be used by the
// faas-provider or the gateway
Annotations *map[string]string `json:"annotations,omitempty"`
// Limits for function
Limits *FunctionResources `json:"limits,omitempty"`
// Requests of resources requested by function
Requests *FunctionResources `json:"requests,omitempty"`
// ReadOnlyRootFilesystem removes write-access from the root filesystem
// mount-point.
ReadOnlyRootFilesystem bool `json:"readOnlyRootFilesystem,omitempty"`
}
// FunctionResources Memory and CPU
type FunctionResources struct {
Memory string `json:"memory,omitempty"`
CPU string `json:"cpu,omitempty"`
}
// FunctionStatus exported for system/functions endpoint
type FunctionStatus struct {
@@ -128,4 +62,24 @@ type FunctionStatus struct {
// CreatedAt is the time read back from the faas backend's
// data store for when the function or its container was created.
CreatedAt time.Time `json:"createdAt,omitempty"`
// Usage represents CPU and RAM used by all of the
// functions' replicas. Divide by AvailableReplicas for an
// average value per replica.
Usage *FunctionUsage `json:"usage,omitempty"`
}
// FunctionUsage represents CPU and RAM used by all of the
// functions' replicas.
//
// CPU is measured in seconds consumed since the last measurement
// RAM is measured in total bytes consumed
//
type FunctionUsage struct {
// CPU is the increase in CPU usage since the last measurement
// equivalent to Kubernetes' concept of millicores.
CPU float64 `json:"cpu,omitempty"`
//TotalMemoryBytes is the total memory usage in bytes.
TotalMemoryBytes float64 `json:"totalMemoryBytes,omitempty"`
}

View File

@@ -0,0 +1,42 @@
package types
import (
"net/http"
"net/url"
)
// Request for asynchronous processing
type QueueRequest struct {
// Header from HTTP request
Header http.Header
// Host from HTTP request
Host string
// Body from HTTP request to use for invocation
Body []byte
// Method from HTTP request
Method string
// Path from HTTP request
Path string
// QueryString from HTTP request
QueryString string
// Function name to invoke
Function string
// QueueName to publish the request to, leave blank
// for default.
QueueName string
// Used by queue worker to submit a result
CallbackURL *url.URL `json:"CallbackUrl"`
}
// RequestQueuer can public a request to be executed asynchronously
type RequestQueuer interface {
Queue(req *QueueRequest) error
}

View File

@@ -0,0 +1,17 @@
package types
// Secret for underlying orchestrator
type Secret struct {
// Name of the secret
Name string `json:"name"`
// Namespace if applicable for the secret
Namespace string `json:"namespace,omitempty"`
// Value is a string representing the string's value
Value string `json:"value,omitempty"`
// RawValue can be used to provide binary data when
// Value is not set
RawValue []byte `json:"rawValue,omitempty"`
}

2
vendor/modules.txt vendored
View File

@@ -36,7 +36,7 @@ github.com/mitchellh/go-homedir
# github.com/morikuni/aec v1.0.0
## explicit
github.com/morikuni/aec
# github.com/openfaas/faas-provider v0.18.5
# github.com/openfaas/faas-provider v0.18.9
## explicit
github.com/openfaas/faas-provider/httputil
github.com/openfaas/faas-provider/logs