The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/expr/ml/outlier.go

120 lines
3.8 KiB

package ml
import (
"fmt"
"net/http"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/response"
)
// OutlierCommand implements Command that sends a request to outlier proxy API and converts response to backend.QueryDataResponse
type OutlierCommand struct {
config OutlierCommandConfiguration
appURL string
interval time.Duration
}
var _ Command = OutlierCommand{}
func (c OutlierCommand) Type() string {
return "outlier"
}
func (c OutlierCommand) DatasourceUID() string {
return c.config.DatasourceUID
}
// Execute copies the original configuration JSON and appends (overwrites) a field "start_end_attributes" and "grafana_url" to the root object.
// The value of "start_end_attributes" is JSON object that configures time range and interval.
// The value of "grafana_url" is app URL that should be used by ML to query the data source.
// After payload is generated it sends it to POST /proxy/api/v1/outlier endpoint and parses the response.
// The proxy API normally responds with a structured data. It recognizes status 200 and 204 as successful result.
// Other statuses are considered unsuccessful and result in error. Tries to extract error from the structured payload.
// Otherwise, mentions the full message in error
func (c OutlierCommand) Execute(from, to time.Time, sendRequest func(method string, path string, payload []byte) (response.Response, error)) (*backend.QueryDataResponse, error) {
payload := outlierRequestBody{
Data: outlierData{
Attributes: outlierAttributes{
OutlierCommandConfiguration: c.config,
GrafanaURL: c.appURL,
StartEndAttributes: newTimeRangeAndInterval(from, to, c.interval),
},
},
}
requestBody, err := json.Marshal(payload)
if err != nil {
return nil, err
}
resp, err := sendRequest(http.MethodPost, "/proxy/api/v1/outlier", requestBody)
if err != nil {
return nil, fmt.Errorf("failed to call ML API: %w", err)
}
if resp == nil {
return nil, fmt.Errorf("response is nil")
}
// Outlier proxy API usually returns all responses with this body.
var respData outlierResponse
respBody := resp.Body()
err = json.Unmarshal(respBody, &respData)
if err != nil {
return nil, fmt.Errorf("unexpected format of the response from ML API, status: %d, response: %s", resp.Status(), respBody)
}
if respData.Status == "error" {
return nil, fmt.Errorf("ML API responded with error: %s", respData.Error)
}
if resp.Status() == http.StatusNoContent {
return nil, nil
}
if resp.Status() == http.StatusOK {
return respData.Data, nil
}
return nil, fmt.Errorf("unexpected status %d returned by ML API, response: %s", resp.Status(), respBody)
}
// unmarshalOutlierCommand parses the CommandConfiguration.Config, validates data and produces OutlierCommand.
func unmarshalOutlierCommand(expr CommandConfiguration, appURL string) (*OutlierCommand, error) {
var cfg OutlierCommandConfiguration
err := json.Unmarshal(expr.Config, &cfg)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal outlier command: %w", err)
}
if len(cfg.DatasourceUID) == 0 {
return nil, fmt.Errorf("required field `config.datasource_uid` is not specified")
}
if len(cfg.Query) == 0 && len(cfg.QueryParams) == 0 {
return nil, fmt.Errorf("neither of required fields `config.query_params` or `config.query` are specified")
}
if len(cfg.ResponseType) == 0 {
return nil, fmt.Errorf("required field `config.response_type` is not specified")
}
if len(cfg.Algorithm) == 0 {
return nil, fmt.Errorf("required field `config.algorithm` is not specified")
}
interval := defaultInterval
if expr.IntervalMs != nil {
i := time.Duration(*expr.IntervalMs) * time.Millisecond
if i > 0 {
interval = i
}
}
return &OutlierCommand{
config: cfg,
interval: interval,
appURL: appURL,
}, nil
}