logs-analyzer/signoz/pkg/query-service/app/parser.go

1154 lines
32 KiB
Go
Raw Normal View History

2024-09-02 22:47:30 +03:00
package app
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"math"
"net/http"
"strconv"
"strings"
"text/template"
"time"
"github.com/SigNoz/govaluate"
"github.com/gorilla/mux"
promModel "github.com/prometheus/common/model"
"go.uber.org/multierr"
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/app/metrics"
"go.signoz.io/signoz/pkg/query-service/app/queryBuilder"
"go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/common"
baseconstants "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
"go.signoz.io/signoz/pkg/query-service/postprocess"
"go.signoz.io/signoz/pkg/query-service/utils"
querytemplate "go.signoz.io/signoz/pkg/query-service/utils/queryTemplate"
)
var allowedFunctions = []string{"count", "ratePerSec", "sum", "avg", "min", "max", "p50", "p90", "p95", "p99"}
func parseGetTopOperationsRequest(r *http.Request) (*model.GetTopOperationsParams, error) {
var postData *model.GetTopOperationsParams
err := json.NewDecoder(r.Body).Decode(&postData)
if err != nil {
return nil, err
}
postData.Start, err = parseTimeStr(postData.StartTime, "start")
if err != nil {
return nil, err
}
postData.End, err = parseTimeMinusBufferStr(postData.EndTime, "end")
if err != nil {
return nil, err
}
if len(postData.ServiceName) == 0 {
return nil, errors.New("serviceName param missing in query")
}
return postData, nil
}
func parseRegisterEventRequest(r *http.Request) (*model.RegisterEventParams, error) {
var postData *model.RegisterEventParams
err := json.NewDecoder(r.Body).Decode(&postData)
if err != nil {
return nil, err
}
if postData.EventName == "" {
return nil, errors.New("eventName param missing in query")
}
return postData, nil
}
func parseMetricsTime(s string) (time.Time, error) {
if t, err := strconv.ParseFloat(s, 64); err == nil {
s, ns := math.Modf(t)
return time.Unix(int64(s), int64(ns*float64(time.Second))), nil
// return time.Unix(0, t), nil
}
if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
return t, nil
}
return time.Time{}, fmt.Errorf("cannot parse %q to a valid timestamp", s)
}
func parseMetricsDuration(s string) (time.Duration, error) {
if d, err := strconv.ParseFloat(s, 64); err == nil {
ts := d * float64(time.Second)
if ts > float64(math.MaxInt64) || ts < float64(math.MinInt64) {
return 0, fmt.Errorf("cannot parse %q to a valid duration. It overflows int64", s)
}
return time.Duration(ts), nil
}
if d, err := promModel.ParseDuration(s); err == nil {
return time.Duration(d), nil
}
return 0, fmt.Errorf("cannot parse %q to a valid duration", s)
}
func parseInstantQueryMetricsRequest(r *http.Request) (*model.InstantQueryMetricsParams, *model.ApiError) {
var ts time.Time
if t := r.FormValue("time"); t != "" {
var err error
ts, err = parseMetricsTime(t)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
} else {
ts = time.Now()
}
return &model.InstantQueryMetricsParams{
Time: ts,
Query: r.FormValue("query"),
Stats: r.FormValue("stats"),
}, nil
}
func parseQueryRangeRequest(r *http.Request) (*model.QueryRangeParams, *model.ApiError) {
start, err := parseMetricsTime(r.FormValue("start"))
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
end, err := parseMetricsTime(r.FormValue("end"))
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
if end.Before(start) {
err := errors.New("end timestamp must not be before start time")
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
step, err := parseMetricsDuration(r.FormValue("step"))
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
if step <= 0 {
err := errors.New("zero or negative query resolution step widths are not accepted. Try a positive integer")
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
// For safety, limit the number of returned points per timeseries.
// This is sufficient for 60s resolution for a week or 1h resolution for a year.
if end.Sub(start)/step > 11000 {
err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
queryRangeParams := model.QueryRangeParams{
Start: start,
End: end,
Step: step,
Query: r.FormValue("query"),
Stats: r.FormValue("stats"),
}
return &queryRangeParams, nil
}
func parseGetUsageRequest(r *http.Request) (*model.GetUsageParams, error) {
startTime, err := parseTime("start", r)
if err != nil {
return nil, err
}
endTime, err := parseTime("end", r)
if err != nil {
return nil, err
}
stepStr := r.URL.Query().Get("step")
if len(stepStr) == 0 {
return nil, errors.New("step param missing in query")
}
stepInt, err := strconv.Atoi(stepStr)
if err != nil {
return nil, errors.New("step param is not in correct format")
}
serviceName := r.URL.Query().Get("service")
stepHour := stepInt / 3600
getUsageParams := model.GetUsageParams{
StartTime: startTime.Format(time.RFC3339Nano),
EndTime: endTime.Format(time.RFC3339Nano),
Start: startTime,
End: endTime,
ServiceName: serviceName,
Period: fmt.Sprintf("PT%dH", stepHour),
StepHour: stepHour,
}
return &getUsageParams, nil
}
func parseGetServiceOverviewRequest(r *http.Request) (*model.GetServiceOverviewParams, error) {
var postData *model.GetServiceOverviewParams
err := json.NewDecoder(r.Body).Decode(&postData)
if err != nil {
return nil, err
}
postData.Start, err = parseTimeStr(postData.StartTime, "start")
if err != nil {
return nil, err
}
postData.End, err = parseTimeMinusBufferStr(postData.EndTime, "end")
if err != nil {
return nil, err
}
postData.Period = fmt.Sprintf("PT%dM", postData.StepSeconds/60)
return postData, nil
}
func parseGetServicesRequest(r *http.Request) (*model.GetServicesParams, error) {
var postData *model.GetServicesParams
err := json.NewDecoder(r.Body).Decode(&postData)
if err != nil {
return nil, err
}
postData.Start, err = parseTimeStr(postData.StartTime, "start")
if err != nil {
return nil, err
}
postData.End, err = parseTimeMinusBufferStr(postData.EndTime, "end")
if err != nil {
return nil, err
}
postData.Period = int(postData.End.Unix() - postData.Start.Unix())
return postData, nil
}
func ParseSearchTracesParams(r *http.Request) (*model.SearchTracesParams, error) {
vars := mux.Vars(r)
params := &model.SearchTracesParams{}
params.TraceID = vars["traceId"]
params.SpanID = r.URL.Query().Get("spanId")
levelUpStr := r.URL.Query().Get("levelUp")
levelDownStr := r.URL.Query().Get("levelDown")
SpanRenderLimitStr := r.URL.Query().Get("spanRenderLimit")
if levelUpStr == "" || levelUpStr == "null" {
levelUpStr = "0"
}
if levelDownStr == "" || levelDownStr == "null" {
levelDownStr = "0"
}
if SpanRenderLimitStr == "" || SpanRenderLimitStr == "null" {
SpanRenderLimitStr = constants.SpanRenderLimitStr
}
levelUpInt, err := strconv.Atoi(levelUpStr)
if err != nil {
return nil, err
}
levelDownInt, err := strconv.Atoi(levelDownStr)
if err != nil {
return nil, err
}
SpanRenderLimitInt, err := strconv.Atoi(SpanRenderLimitStr)
if err != nil {
return nil, err
}
MaxSpansInTraceInt, err := strconv.Atoi(constants.MaxSpansInTraceStr)
if err != nil {
return nil, err
}
params.LevelUp = levelUpInt
params.LevelDown = levelDownInt
params.SpansRenderLimit = SpanRenderLimitInt
params.MaxSpansInTrace = MaxSpansInTraceInt
return params, nil
}
func DoesExistInSlice(item string, list []string) bool {
for _, element := range list {
if item == element {
return true
}
}
return false
}
func parseSpanFilterRequestBody(r *http.Request) (*model.SpanFilterParams, error) {
var postData *model.SpanFilterParams
err := json.NewDecoder(r.Body).Decode(&postData)
if err != nil {
return nil, err
}
postData.Start, err = parseTimeStr(postData.StartStr, "start")
if err != nil {
return nil, err
}
postData.End, err = parseTimeMinusBufferStr(postData.EndStr, "end")
if err != nil {
return nil, err
}
return postData, nil
}
func parseFilteredSpansRequest(r *http.Request, aH *APIHandler) (*model.GetFilteredSpansParams, error) {
var postData *model.GetFilteredSpansParams
err := json.NewDecoder(r.Body).Decode(&postData)
if err != nil {
return nil, err
}
postData.Start, err = parseTimeStr(postData.StartStr, "start")
if err != nil {
return nil, err
}
postData.End, err = parseTimeMinusBufferStr(postData.EndStr, "end")
if err != nil {
return nil, err
}
if postData.Limit == 0 {
postData.Limit = 10
}
if len(postData.Order) != 0 {
if postData.Order != baseconstants.Ascending && postData.Order != baseconstants.Descending {
return nil, errors.New("order param is not in correct format")
}
if postData.OrderParam != baseconstants.Duration && postData.OrderParam != baseconstants.Timestamp {
return nil, errors.New("order param is not in correct format")
}
if postData.OrderParam == baseconstants.Duration && !aH.CheckFeature(baseconstants.DurationSort) {
return nil, model.ErrFeatureUnavailable{Key: baseconstants.DurationSort}
} else if postData.OrderParam == baseconstants.Timestamp && !aH.CheckFeature(baseconstants.TimestampSort) {
return nil, model.ErrFeatureUnavailable{Key: baseconstants.TimestampSort}
}
}
tags, err := extractTagKeys(postData.Tags)
if err != nil {
return nil, err
}
postData.Tags = tags
return postData, nil
}
func parseFilteredSpanAggregatesRequest(r *http.Request) (*model.GetFilteredSpanAggregatesParams, error) {
var postData *model.GetFilteredSpanAggregatesParams
err := json.NewDecoder(r.Body).Decode(&postData)
if err != nil {
return nil, err
}
postData.Start, err = parseTimeStr(postData.StartStr, "start")
if err != nil {
return nil, err
}
postData.End, err = parseTimeMinusBufferStr(postData.EndStr, "end")
if err != nil {
return nil, err
}
step := postData.StepSeconds
if step == 0 {
return nil, errors.New("step param missing in query")
}
function := postData.Function
if len(function) == 0 {
return nil, errors.New("function param missing in query")
} else {
if !DoesExistInSlice(function, allowedFunctions) {
return nil, fmt.Errorf("given function: %s is not allowed in query", function)
}
}
var dimension, aggregationOption string
switch function {
case "count":
dimension = "calls"
aggregationOption = "count"
case "ratePerSec":
dimension = "calls"
aggregationOption = "rate_per_sec"
case "avg":
dimension = "duration"
aggregationOption = "avg"
case "sum":
dimension = "duration"
aggregationOption = "sum"
case "p50":
dimension = "duration"
aggregationOption = "p50"
case "p90":
dimension = "duration"
aggregationOption = "p90"
case "p95":
dimension = "duration"
aggregationOption = "p95"
case "p99":
dimension = "duration"
aggregationOption = "p99"
case "min":
dimension = "duration"
aggregationOption = "min"
case "max":
dimension = "duration"
aggregationOption = "max"
}
postData.AggregationOption = aggregationOption
postData.Dimension = dimension
tags, err := extractTagKeys(postData.Tags)
if err != nil {
return nil, err
}
postData.Tags = tags
return postData, nil
}
func extractTagKeys(tags []model.TagQueryParam) ([]model.TagQueryParam, error) {
newTags := make([]model.TagQueryParam, 0)
if len(tags) != 0 {
for _, tag := range tags {
customStr := strings.Split(tag.Key, ".(")
if len(customStr) < 2 {
return nil, fmt.Errorf("TagKey param is not valid in query")
} else {
tag.Key = customStr[0]
}
if tag.Operator == model.ExistsOperator || tag.Operator == model.NotExistsOperator {
if customStr[1] == string(model.TagTypeString)+")" {
tag.StringValues = []string{" "}
} else if customStr[1] == string(model.TagTypeBool)+")" {
tag.BoolValues = []bool{true}
} else if customStr[1] == string(model.TagTypeNumber)+")" {
tag.NumberValues = []float64{0}
} else {
return nil, fmt.Errorf("TagKey param is not valid in query")
}
}
newTags = append(newTags, tag)
}
}
return newTags, nil
}
func parseTagFilterRequest(r *http.Request) (*model.TagFilterParams, error) {
var postData *model.TagFilterParams
err := json.NewDecoder(r.Body).Decode(&postData)
if err != nil {
return nil, err
}
postData.Start, err = parseTimeStr(postData.StartStr, "start")
if err != nil {
return nil, err
}
postData.End, err = parseTimeMinusBufferStr(postData.EndStr, "end")
if err != nil {
return nil, err
}
return postData, nil
}
func parseTagValueRequest(r *http.Request) (*model.TagFilterParams, error) {
var postData *model.TagFilterParams
err := json.NewDecoder(r.Body).Decode(&postData)
if err != nil {
return nil, err
}
if postData.TagKey == (model.TagKey{}) {
return nil, fmt.Errorf("TagKey param missing in query")
}
if postData.TagKey.Type != model.TagTypeString && postData.TagKey.Type != model.TagTypeBool && postData.TagKey.Type != model.TagTypeNumber {
return nil, fmt.Errorf("tag keys type %s is not supported", postData.TagKey.Type)
}
if postData.Limit == 0 {
postData.Limit = 100
}
postData.Start, err = parseTimeStr(postData.StartStr, "start")
if err != nil {
return nil, err
}
postData.End, err = parseTimeMinusBufferStr(postData.EndStr, "end")
if err != nil {
return nil, err
}
return postData, nil
}
func parseListErrorsRequest(r *http.Request) (*model.ListErrorsParams, error) {
var allowedOrderParams = []string{"exceptionType", "exceptionCount", "firstSeen", "lastSeen", "serviceName"}
var allowedOrderDirections = []string{"ascending", "descending"}
var postData *model.ListErrorsParams
err := json.NewDecoder(r.Body).Decode(&postData)
if err != nil {
return nil, err
}
postData.Start, err = parseTimeStr(postData.StartStr, "start")
if err != nil {
return nil, err
}
postData.End, err = parseTimeMinusBufferStr(postData.EndStr, "end")
if err != nil {
return nil, err
}
if postData.Limit == 0 {
return nil, fmt.Errorf("limit param cannot be empty from the query")
}
if len(postData.Order) > 0 && !DoesExistInSlice(postData.Order, allowedOrderDirections) {
return nil, fmt.Errorf("given order: %s is not allowed in query", postData.Order)
}
if len(postData.Order) > 0 && !DoesExistInSlice(postData.OrderParam, allowedOrderParams) {
return nil, fmt.Errorf("given orderParam: %s is not allowed in query", postData.OrderParam)
}
return postData, nil
}
func parseCountErrorsRequest(r *http.Request) (*model.CountErrorsParams, error) {
var postData *model.CountErrorsParams
err := json.NewDecoder(r.Body).Decode(&postData)
if err != nil {
return nil, err
}
postData.Start, err = parseTimeStr(postData.StartStr, "start")
if err != nil {
return nil, err
}
postData.End, err = parseTimeMinusBufferStr(postData.EndStr, "end")
if err != nil {
return nil, err
}
return postData, nil
}
func parseGetErrorRequest(r *http.Request) (*model.GetErrorParams, error) {
timestamp, err := parseTime("timestamp", r)
if err != nil {
return nil, err
}
groupID := r.URL.Query().Get("groupID")
if len(groupID) == 0 {
return nil, fmt.Errorf("groupID param cannot be empty from the query")
}
errorID := r.URL.Query().Get("errorID")
params := &model.GetErrorParams{
Timestamp: timestamp,
GroupID: groupID,
ErrorID: errorID,
}
return params, nil
}
func parseTimeStr(timeStr string, param string) (*time.Time, error) {
if len(timeStr) == 0 {
return nil, fmt.Errorf("%s param missing in query", param)
}
timeUnix, err := strconv.ParseInt(timeStr, 10, 64)
if err != nil || len(timeStr) == 0 {
return nil, fmt.Errorf("%s param is not in correct timestamp format", param)
}
timeFmt := time.Unix(0, timeUnix)
return &timeFmt, nil
}
func parseTimeMinusBufferStr(timeStr string, param string) (*time.Time, error) {
if len(timeStr) == 0 {
return nil, fmt.Errorf("%s param missing in query", param)
}
timeUnix, err := strconv.ParseInt(timeStr, 10, 64)
if err != nil || len(timeStr) == 0 {
return nil, fmt.Errorf("%s param is not in correct timestamp format", param)
}
timeUnixNow := time.Now().UnixNano()
if timeUnix > timeUnixNow-30000000000 {
timeUnix = timeUnix - 30000000000
}
timeFmt := time.Unix(0, timeUnix)
return &timeFmt, nil
}
func parseTime(param string, r *http.Request) (*time.Time, error) {
timeStr := r.URL.Query().Get(param)
if len(timeStr) == 0 {
return nil, fmt.Errorf("%s param missing in query", param)
}
timeUnix, err := strconv.ParseInt(timeStr, 10, 64)
if err != nil || len(timeStr) == 0 {
return nil, fmt.Errorf("%s param is not in correct timestamp format", param)
}
timeFmt := time.Unix(0, timeUnix)
return &timeFmt, nil
}
func parseTTLParams(r *http.Request) (*model.TTLParams, error) {
// make sure either of the query params are present
typeTTL := r.URL.Query().Get("type")
delDuration := r.URL.Query().Get("duration")
coldStorage := r.URL.Query().Get("coldStorage")
toColdDuration := r.URL.Query().Get("toColdDuration")
if len(typeTTL) == 0 || len(delDuration) == 0 {
return nil, fmt.Errorf("type and duration param cannot be empty from the query")
}
// Validate the type parameter
if typeTTL != baseconstants.TraceTTL && typeTTL != baseconstants.MetricsTTL && typeTTL != baseconstants.LogsTTL {
return nil, fmt.Errorf("type param should be metrics|traces|logs, got %v", typeTTL)
}
// Validate the TTL duration.
durationParsed, err := time.ParseDuration(delDuration)
if err != nil || durationParsed.Seconds() <= 0 {
return nil, fmt.Errorf("not a valid TTL duration %v", delDuration)
}
var toColdParsed time.Duration
// If some cold storage is provided, validate the cold storage move TTL.
if len(coldStorage) > 0 {
toColdParsed, err = time.ParseDuration(toColdDuration)
if err != nil || toColdParsed.Seconds() <= 0 {
return nil, fmt.Errorf("not a valid toCold TTL duration %v", toColdDuration)
}
if toColdParsed.Seconds() != 0 && toColdParsed.Seconds() >= durationParsed.Seconds() {
return nil, fmt.Errorf("delete TTL should be greater than cold storage move TTL")
}
}
return &model.TTLParams{
Type: typeTTL,
DelDuration: int64(durationParsed.Seconds()),
ColdStorageVolume: coldStorage,
ToColdStorageDuration: int64(toColdParsed.Seconds()),
}, nil
}
func parseGetTTL(r *http.Request) (*model.GetTTLParams, error) {
typeTTL := r.URL.Query().Get("type")
if len(typeTTL) == 0 {
return nil, fmt.Errorf("type param cannot be empty from the query")
} else {
// Validate the type parameter
if typeTTL != baseconstants.TraceTTL && typeTTL != baseconstants.MetricsTTL && typeTTL != baseconstants.LogsTTL {
return nil, fmt.Errorf("type param should be metrics|traces|logs, got %v", typeTTL)
}
}
return &model.GetTTLParams{Type: typeTTL}, nil
}
func parseUserRequest(r *http.Request) (*model.User, error) {
var req model.User
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
func parseInviteRequest(r *http.Request) (*model.InviteRequest, error) {
var req model.InviteRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
// Trim spaces from email
req.Email = strings.TrimSpace(req.Email)
return &req, nil
}
func parseSetApdexScoreRequest(r *http.Request) (*model.ApdexSettings, error) {
var req model.ApdexSettings
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
func parseInsertIngestionKeyRequest(r *http.Request) (*model.IngestionKey, error) {
var req model.IngestionKey
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
func parseRegisterRequest(r *http.Request) (*auth.RegisterRequest, error) {
var req auth.RegisterRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
if err := auth.ValidatePassword(req.Password); err != nil {
return nil, err
}
return &req, nil
}
func parseLoginRequest(r *http.Request) (*model.LoginRequest, error) {
var req model.LoginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
func parseUserRoleRequest(r *http.Request) (*model.UserRole, error) {
var req model.UserRole
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
func parseEditOrgRequest(r *http.Request) (*model.Organization, error) {
var req model.Organization
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}
func parseResetPasswordRequest(r *http.Request) (*model.ResetPasswordRequest, error) {
var req model.ResetPasswordRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
if err := auth.ValidatePassword(req.Password); err != nil {
return nil, err
}
return &req, nil
}
func parseChangePasswordRequest(r *http.Request) (*model.ChangePasswordRequest, error) {
id := mux.Vars(r)["id"]
var req model.ChangePasswordRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
req.UserId = id
if err := auth.ValidatePassword(req.NewPassword); err != nil {
return nil, err
}
return &req, nil
}
func parseAggregateAttributeRequest(r *http.Request) (*v3.AggregateAttributeRequest, error) {
var req v3.AggregateAttributeRequest
aggregateOperator := v3.AggregateOperator(r.URL.Query().Get("aggregateOperator"))
dataSource := v3.DataSource(r.URL.Query().Get("dataSource"))
aggregateAttribute := r.URL.Query().Get("searchText")
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
if err != nil {
limit = 50
}
if dataSource != v3.DataSourceMetrics {
if err := aggregateOperator.Validate(); err != nil {
return nil, err
}
}
if err := dataSource.Validate(); err != nil {
return nil, err
}
req = v3.AggregateAttributeRequest{
Operator: aggregateOperator,
SearchText: aggregateAttribute,
Limit: limit,
DataSource: dataSource,
}
return &req, nil
}
func parseFilterAttributeKeyRequest(r *http.Request) (*v3.FilterAttributeKeyRequest, error) {
var req v3.FilterAttributeKeyRequest
dataSource := v3.DataSource(r.URL.Query().Get("dataSource"))
aggregateOperator := v3.AggregateOperator(r.URL.Query().Get("aggregateOperator"))
aggregateAttribute := r.URL.Query().Get("aggregateAttribute")
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
if err != nil {
limit = 50
}
if err := dataSource.Validate(); err != nil {
return nil, err
}
if dataSource != v3.DataSourceMetrics {
if err := aggregateOperator.Validate(); err != nil {
return nil, err
}
}
req = v3.FilterAttributeKeyRequest{
DataSource: dataSource,
AggregateOperator: aggregateOperator,
AggregateAttribute: aggregateAttribute,
Limit: limit,
SearchText: r.URL.Query().Get("searchText"),
}
return &req, nil
}
func parseFilterAttributeValueRequest(r *http.Request) (*v3.FilterAttributeValueRequest, error) {
var req v3.FilterAttributeValueRequest
dataSource := v3.DataSource(r.URL.Query().Get("dataSource"))
aggregateOperator := v3.AggregateOperator(r.URL.Query().Get("aggregateOperator"))
filterAttributeKeyDataType := v3.AttributeKeyDataType(r.URL.Query().Get("filterAttributeKeyDataType")) // can be empty
aggregateAttribute := r.URL.Query().Get("aggregateAttribute")
tagType := v3.TagType(r.URL.Query().Get("tagType")) // can be empty
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
if err != nil {
limit = 50
}
if err := dataSource.Validate(); err != nil {
return nil, err
}
if dataSource != v3.DataSourceMetrics {
if err := aggregateOperator.Validate(); err != nil {
return nil, err
}
}
req = v3.FilterAttributeValueRequest{
DataSource: dataSource,
AggregateOperator: aggregateOperator,
AggregateAttribute: aggregateAttribute,
TagType: tagType,
Limit: limit,
SearchText: r.URL.Query().Get("searchText"),
FilterAttributeKey: r.URL.Query().Get("attributeKey"),
FilterAttributeKeyDataType: filterAttributeKeyDataType,
}
return &req, nil
}
func validateQueryRangeParamsV3(qp *v3.QueryRangeParamsV3) error {
err := qp.CompositeQuery.Validate()
if err != nil {
return err
}
var expressions []string
for _, q := range qp.CompositeQuery.BuilderQueries {
expressions = append(expressions, q.Expression)
}
errs := validateExpressions(expressions, queryBuilder.EvalFuncs, qp.CompositeQuery)
if len(errs) > 0 {
return multierr.Combine(errs...)
}
return nil
}
// validateExpressions validates the math expressions using the list of
// allowed functions.
func validateExpressions(expressions []string, funcs map[string]govaluate.ExpressionFunction, cq *v3.CompositeQuery) []error {
var errs []error
for _, exp := range expressions {
evalExp, err := govaluate.NewEvaluableExpressionWithFunctions(exp, funcs)
if err != nil {
errs = append(errs, fmt.Errorf("invalid expression %s: %v", exp, err))
continue
}
for _, v := range evalExp.Vars() {
var hasVariable bool
for _, q := range cq.BuilderQueries {
if q.Expression == v {
hasVariable = true
break
}
}
if !hasVariable {
errs = append(errs, fmt.Errorf("unknown variable %s", v))
}
}
}
return errs
}
func ParseQueryRangeParams(r *http.Request) (*v3.QueryRangeParamsV3, *model.ApiError) {
var queryRangeParams *v3.QueryRangeParamsV3
// parse the request body
if err := json.NewDecoder(r.Body).Decode(&queryRangeParams); err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("cannot parse the request body: %v", err)}
}
// sanitize the request body
queryRangeParams.CompositeQuery.Sanitize()
// validate the request body
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
// prepare the variables for the corresponding query type
formattedVars := make(map[string]interface{})
for name, value := range queryRangeParams.Variables {
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypePromQL {
formattedVars[name] = metrics.PromFormattedValue(value)
} else if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
formattedVars[name] = utils.ClickHouseFormattedValue(value)
}
}
// replace the variables in metrics builder filter item with actual value
// example: {"key": "host", "value": "{{ .host }}", "operator": "equals"} with
// variables {"host": "test"} will be replaced with {"key": "host", "value": "test", "operator": "equals"}
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeBuilder {
for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
// Formula query
// Check if the queries used in the expression can be joined
if query.QueryName != query.Expression {
expression, err := govaluate.NewEvaluableExpressionWithFunctions(query.Expression, postprocess.EvalFuncs())
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
// get the group keys for the vars
groupKeys := make(map[string][]string)
for _, v := range expression.Vars() {
if varQuery, ok := queryRangeParams.CompositeQuery.BuilderQueries[v]; ok {
groupKeys[v] = []string{}
for _, key := range varQuery.GroupBy {
groupKeys[v] = append(groupKeys[v], key.Key)
}
} else {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("unknown variable %s", v)}
}
}
params := make(map[string]interface{})
for k, v := range groupKeys {
params[k] = v
}
can, _, err := expression.CanJoin(params)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
if !can {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("cannot join the given group keys")}
}
}
// If the step interval is less than the minimum allowed step interval, set it to the minimum allowed step interval
if minStep := common.MinAllowedStepInterval(queryRangeParams.Start, queryRangeParams.End); query.StepInterval < minStep {
query.StepInterval = minStep
}
// Remove the time shift function from the list of functions and set the shift by value
var timeShiftBy int64
if len(query.Functions) > 0 {
for idx := range query.Functions {
function := &query.Functions[idx]
if function.Name == v3.FunctionNameTimeShift {
// move the function to the beginning of the list
// so any other function can use the shifted time
var fns []v3.Function
fns = append(fns, *function)
fns = append(fns, query.Functions[:idx]...)
fns = append(fns, query.Functions[idx+1:]...)
query.Functions = fns
timeShiftBy = int64(function.Args[0].(float64))
break
}
}
}
query.ShiftBy = timeShiftBy
if query.Filters == nil || len(query.Filters.Items) == 0 {
continue
}
for idx := range query.Filters.Items {
item := &query.Filters.Items[idx]
value := item.Value
if value != nil {
switch x := value.(type) {
case string:
variableName := strings.Trim(x, "{[.$]}")
if _, ok := queryRangeParams.Variables[variableName]; ok {
item.Value = queryRangeParams.Variables[variableName]
}
case []interface{}:
if len(x) > 0 {
switch x[0].(type) {
case string:
variableName := strings.Trim(x[0].(string), "{[.$]}")
if _, ok := queryRangeParams.Variables[variableName]; ok {
item.Value = queryRangeParams.Variables[variableName]
}
}
}
}
}
if v3.FilterOperator(strings.ToLower((string(item.Operator)))) != v3.FilterOperatorIn && v3.FilterOperator(strings.ToLower((string(item.Operator)))) != v3.FilterOperatorNotIn {
// the value type should not be multiple values
if _, ok := item.Value.([]interface{}); ok {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("multiple values %s are not allowed for operator `%s` for key `%s`", item.Value, item.Operator, item.Key.Key)}
}
}
}
}
}
queryRangeParams.Variables = formattedVars
// prometheus instant query needs same timestamp
if queryRangeParams.CompositeQuery.PanelType == v3.PanelTypeValue &&
queryRangeParams.CompositeQuery.QueryType == v3.QueryTypePromQL {
queryRangeParams.Start = queryRangeParams.End
}
// replace go template variables in clickhouse query
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeClickHouseSQL {
for _, chQuery := range queryRangeParams.CompositeQuery.ClickHouseQueries {
if chQuery.Disabled {
continue
}
for name, value := range queryRangeParams.Variables {
chQuery.Query = strings.Replace(chQuery.Query, fmt.Sprintf("{{%s}}", name), fmt.Sprint(value), -1)
chQuery.Query = strings.Replace(chQuery.Query, fmt.Sprintf("[[%s]]", name), fmt.Sprint(value), -1)
chQuery.Query = strings.Replace(chQuery.Query, fmt.Sprintf("$%s", name), fmt.Sprint(value), -1)
}
tmpl := template.New("clickhouse-query")
tmpl, err := tmpl.Parse(chQuery.Query)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
var query bytes.Buffer
// replace go template variables
querytemplate.AssignReservedVarsV3(queryRangeParams)
err = tmpl.Execute(&query, queryRangeParams.Variables)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
chQuery.Query = query.String()
}
}
// replace go template variables in prometheus query
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypePromQL {
for _, promQuery := range queryRangeParams.CompositeQuery.PromQueries {
if promQuery.Disabled {
continue
}
for name, value := range queryRangeParams.Variables {
promQuery.Query = strings.Replace(promQuery.Query, fmt.Sprintf("{{%s}}", name), fmt.Sprint(value), -1)
promQuery.Query = strings.Replace(promQuery.Query, fmt.Sprintf("[[%s]]", name), fmt.Sprint(value), -1)
promQuery.Query = strings.Replace(promQuery.Query, fmt.Sprintf("$%s", name), fmt.Sprint(value), -1)
}
tmpl := template.New("prometheus-query")
tmpl, err := tmpl.Parse(promQuery.Query)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
var query bytes.Buffer
// replace go template variables
querytemplate.AssignReservedVarsV3(queryRangeParams)
err = tmpl.Execute(&query, queryRangeParams.Variables)
if err != nil {
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err}
}
promQuery.Query = query.String()
}
}
return queryRangeParams, nil
}