2021-01-03 18:15:44 +05:30
package app
import (
2023-03-23 19:45:15 +05:30
"bytes"
2024-08-08 09:27:41 +05:30
"encoding/base64"
2021-01-03 18:15:44 +05:30
"encoding/json"
"errors"
"fmt"
2021-08-29 10:28:40 +05:30
"math"
2021-01-03 18:15:44 +05:30
"net/http"
2025-05-30 15:57:29 +05:30
"sort"
2021-01-03 18:15:44 +05:30
"strconv"
2023-01-25 12:35:44 +05:30
"strings"
2023-03-23 19:45:15 +05:30
"text/template"
2021-01-03 18:15:44 +05:30
"time"
2025-03-20 21:01:41 +05:30
"github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/kafka"
queues2 "github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations/thirdPartyApi"
2025-03-06 15:39:45 +05:30
2023-03-23 19:45:15 +05:30
"github.com/SigNoz/govaluate"
2022-05-03 15:26:32 +05:30
"github.com/gorilla/mux"
2021-08-29 10:28:40 +05:30
promModel "github.com/prometheus/common/model"
2023-03-23 19:45:15 +05:30
"go.uber.org/multierr"
2025-03-26 18:28:55 +05:30
"go.uber.org/zap"
2021-08-29 10:28:40 +05:30
2025-03-20 21:01:41 +05:30
"github.com/SigNoz/signoz/pkg/query-service/app/metrics"
"github.com/SigNoz/signoz/pkg/query-service/app/queryBuilder"
"github.com/SigNoz/signoz/pkg/query-service/common"
baseconstants "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/model"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
"github.com/SigNoz/signoz/pkg/query-service/utils"
querytemplate "github.com/SigNoz/signoz/pkg/query-service/utils/queryTemplate"
2025-03-26 18:28:55 +05:30
chVariables "github.com/SigNoz/signoz/pkg/variables/clickhouse"
2021-01-03 18:15:44 +05:30
)
2022-01-26 20:41:59 +05:30
var allowedFunctions = [ ] string { "count" , "ratePerSec" , "sum" , "avg" , "min" , "max" , "p50" , "p90" , "p95" , "p99" }
2022-08-04 11:57:05 +05:30
func parseGetTopOperationsRequest ( r * http . Request ) ( * model . GetTopOperationsParams , error ) {
var postData * model . GetTopOperationsParams
2022-05-03 11:20:57 +05:30
err := json . NewDecoder ( r . Body ) . Decode ( & postData )
2021-01-03 18:15:44 +05:30
if err != nil {
return nil , err
}
2022-05-03 11:20:57 +05:30
postData . Start , err = parseTimeStr ( postData . StartTime , "start" )
2021-01-03 18:15:44 +05:30
if err != nil {
return nil , err
}
2022-05-03 11:20:57 +05:30
postData . End , err = parseTimeMinusBufferStr ( postData . EndTime , "end" )
if err != nil {
return nil , err
2021-01-03 18:15:44 +05:30
}
2022-05-03 11:20:57 +05:30
if len ( postData . ServiceName ) == 0 {
return nil , errors . New ( "serviceName param missing in query" )
2021-01-03 18:15:44 +05:30
}
2022-05-03 11:20:57 +05:30
return postData , nil
2021-01-03 18:15:44 +05:30
}
2024-03-28 21:43:41 +05:30
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
}
2025-03-07 00:23:47 +05:30
// Validate the event type
if ! postData . EventType . IsValid ( ) {
return nil , errors . New ( "eventType param missing/incorrect in query" )
}
if postData . EventType == model . TrackEvent && postData . EventName == "" {
2024-03-28 21:43:41 +05:30
return nil , errors . New ( "eventName param missing in query" )
}
return postData , nil
}
2021-08-29 10:28:40 +05:30
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 {
2022-05-03 11:20:57 +05:30
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
2021-08-29 10:28:40 +05:30
}
} 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 {
2022-05-03 11:20:57 +05:30
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
2021-08-29 10:28:40 +05:30
}
end , err := parseMetricsTime ( r . FormValue ( "end" ) )
if err != nil {
2022-05-03 11:20:57 +05:30
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
2021-08-29 10:28:40 +05:30
}
if end . Before ( start ) {
err := errors . New ( "end timestamp must not be before start time" )
2022-05-03 11:20:57 +05:30
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
2021-08-29 10:28:40 +05:30
}
step , err := parseMetricsDuration ( r . FormValue ( "step" ) )
if err != nil {
2022-05-03 11:20:57 +05:30
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
2021-08-29 10:28:40 +05:30
}
if step <= 0 {
err := errors . New ( "zero or negative query resolution step widths are not accepted. Try a positive integer" )
2022-05-03 11:20:57 +05:30
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
2021-08-29 10:28:40 +05:30
}
// 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)" )
2022-05-03 11:20:57 +05:30
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
2021-08-29 10:28:40 +05:30
}
queryRangeParams := model . QueryRangeParams {
Start : start ,
End : end ,
Step : step ,
Query : r . FormValue ( "query" ) ,
Stats : r . FormValue ( "stats" ) ,
}
return & queryRangeParams , nil
}
2021-01-03 18:15:44 +05:30
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" )
2021-05-31 11:14:11 +05:30
stepHour := stepInt / 3600
2021-01-03 18:15:44 +05:30
getUsageParams := model . GetUsageParams {
2021-04-23 13:37:32 +05:30
StartTime : startTime . Format ( time . RFC3339Nano ) ,
EndTime : endTime . Format ( time . RFC3339Nano ) ,
2021-05-31 11:14:11 +05:30
Start : startTime ,
End : endTime ,
2021-01-03 18:15:44 +05:30
ServiceName : serviceName ,
2021-05-31 11:14:11 +05:30
Period : fmt . Sprintf ( "PT%dH" , stepHour ) ,
StepHour : stepHour ,
2021-01-03 18:15:44 +05:30
}
return & getUsageParams , nil
}
func parseGetServicesRequest ( r * http . Request ) ( * model . GetServicesParams , error ) {
2022-05-03 11:20:57 +05:30
var postData * model . GetServicesParams
err := json . NewDecoder ( r . Body ) . Decode ( & postData )
2021-01-03 18:15:44 +05:30
if err != nil {
return nil , err
}
2022-05-03 11:20:57 +05:30
postData . Start , err = parseTimeStr ( postData . StartTime , "start" )
2021-01-03 18:15:44 +05:30
if err != nil {
return nil , err
}
2022-05-03 11:20:57 +05:30
postData . End , err = parseTimeMinusBufferStr ( postData . EndTime , "end" )
if err != nil {
return nil , err
2021-01-03 18:15:44 +05:30
}
2022-05-03 11:20:57 +05:30
postData . Period = int ( postData . End . Unix ( ) - postData . Start . Unix ( ) )
return postData , nil
2021-01-03 18:15:44 +05:30
}
2024-05-27 17:20:45 +05:30
func ParseSearchTracesParams ( r * http . Request ) ( * model . SearchTracesParams , error ) {
2022-11-24 18:18:19 +05:30
vars := mux . Vars ( r )
2024-05-27 17:20:45 +05:30
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"
2022-11-24 18:18:19 +05:30
}
2024-05-27 17:20:45 +05:30
if SpanRenderLimitStr == "" || SpanRenderLimitStr == "null" {
2024-09-09 23:13:14 +05:30
SpanRenderLimitStr = baseconstants . SpanRenderLimitStr
2022-11-24 18:18:19 +05:30
}
2024-05-27 17:20:45 +05:30
levelUpInt , err := strconv . Atoi ( levelUpStr )
2022-11-24 18:18:19 +05:30
if err != nil {
2024-05-27 17:20:45 +05:30
return nil , err
2022-11-24 18:18:19 +05:30
}
2024-05-27 17:20:45 +05:30
levelDownInt , err := strconv . Atoi ( levelDownStr )
2022-11-24 18:18:19 +05:30
if err != nil {
2024-05-27 17:20:45 +05:30
return nil , err
2022-11-24 18:18:19 +05:30
}
2024-05-27 17:20:45 +05:30
SpanRenderLimitInt , err := strconv . Atoi ( SpanRenderLimitStr )
if err != nil {
return nil , err
}
2024-09-09 23:13:14 +05:30
MaxSpansInTraceInt , err := strconv . Atoi ( baseconstants . MaxSpansInTraceStr )
2024-05-27 17:20:45 +05:30
if err != nil {
return nil , err
}
params . LevelUp = levelUpInt
params . LevelDown = levelDownInt
params . SpansRenderLimit = SpanRenderLimitInt
params . MaxSpansInTrace = MaxSpansInTraceInt
return params , nil
2022-11-24 18:18:19 +05:30
}
2021-01-03 18:15:44 +05:30
func DoesExistInSlice ( item string , list [ ] string ) bool {
for _ , element := range list {
if item == element {
return true
}
}
return false
}
2022-07-13 15:55:43 +05:30
func parseListErrorsRequest ( r * http . Request ) ( * model . ListErrorsParams , error ) {
var allowedOrderParams = [ ] string { "exceptionType" , "exceptionCount" , "firstSeen" , "lastSeen" , "serviceName" }
var allowedOrderDirections = [ ] string { "ascending" , "descending" }
2023-03-28 00:15:15 +05:30
var postData * model . ListErrorsParams
err := json . NewDecoder ( r . Body ) . Decode ( & postData )
2022-07-13 15:55:43 +05:30
if err != nil {
return nil , err
}
2023-03-28 00:15:15 +05:30
postData . Start , err = parseTimeStr ( postData . StartStr , "start" )
2022-07-13 15:55:43 +05:30
if err != nil {
return nil , err
}
2023-03-28 00:15:15 +05:30
postData . End , err = parseTimeMinusBufferStr ( postData . EndStr , "end" )
if err != nil {
return nil , err
2022-07-13 15:55:43 +05:30
}
2023-03-28 00:15:15 +05:30
if postData . Limit == 0 {
return nil , fmt . Errorf ( "limit param cannot be empty from the query" )
2022-07-13 15:55:43 +05:30
}
2023-03-28 00:15:15 +05:30
if len ( postData . Order ) > 0 && ! DoesExistInSlice ( postData . Order , allowedOrderDirections ) {
2024-06-11 20:10:38 +05:30
return nil , fmt . Errorf ( "given order: %s is not allowed in query" , postData . Order )
2022-07-13 15:55:43 +05:30
}
2023-03-28 00:15:15 +05:30
if len ( postData . Order ) > 0 && ! DoesExistInSlice ( postData . OrderParam , allowedOrderParams ) {
2024-06-11 20:10:38 +05:30
return nil , fmt . Errorf ( "given orderParam: %s is not allowed in query" , postData . OrderParam )
2022-07-13 15:55:43 +05:30
}
2023-03-28 00:15:15 +05:30
return postData , nil
2022-07-13 15:55:43 +05:30
}
func parseCountErrorsRequest ( r * http . Request ) ( * model . CountErrorsParams , error ) {
2022-01-21 00:31:58 +05:30
2023-03-28 00:15:15 +05:30
var postData * model . CountErrorsParams
err := json . NewDecoder ( r . Body ) . Decode ( & postData )
2022-01-21 00:31:58 +05:30
if err != nil {
return nil , err
}
2023-03-28 00:15:15 +05:30
postData . Start , err = parseTimeStr ( postData . StartStr , "start" )
2022-01-21 00:31:58 +05:30
if err != nil {
return nil , err
}
2023-03-28 00:15:15 +05:30
postData . End , err = parseTimeMinusBufferStr ( postData . EndStr , "end" )
if err != nil {
return nil , err
2022-07-13 15:55:43 +05:30
}
2023-03-28 00:15:15 +05:30
return postData , nil
2022-07-13 15:55:43 +05:30
}
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 ,
2022-01-21 00:31:58 +05:30
}
return params , nil
}
2022-02-02 11:40:30 +05:30
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
}
2021-01-03 18:15:44 +05:30
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
}
2022-03-21 23:58:56 +05:30
func parseTTLParams ( r * http . Request ) ( * model . TTLParams , error ) {
2021-10-20 13:18:19 +05:30
// make sure either of the query params are present
typeTTL := r . URL . Query ( ) . Get ( "type" )
2022-03-21 23:58:56 +05:30
delDuration := r . URL . Query ( ) . Get ( "duration" )
coldStorage := r . URL . Query ( ) . Get ( "coldStorage" )
toColdDuration := r . URL . Query ( ) . Get ( "toColdDuration" )
2021-10-20 13:18:19 +05:30
2022-03-21 23:58:56 +05:30
if len ( typeTTL ) == 0 || len ( delDuration ) == 0 {
2021-10-20 13:18:19 +05:30
return nil , fmt . Errorf ( "type and duration param cannot be empty from the query" )
}
// Validate the type parameter
2024-05-27 17:20:45 +05:30
if typeTTL != baseconstants . TraceTTL && typeTTL != baseconstants . MetricsTTL && typeTTL != baseconstants . LogsTTL {
2022-08-10 14:27:46 +05:30
return nil , fmt . Errorf ( "type param should be metrics|traces|logs, got %v" , typeTTL )
2021-10-20 13:18:19 +05:30
}
2022-03-21 23:58:56 +05:30
// Validate the TTL duration.
durationParsed , err := time . ParseDuration ( delDuration )
2022-04-06 16:29:10 +05:30
if err != nil || durationParsed . Seconds ( ) <= 0 {
2024-06-11 20:10:38 +05:30
return nil , fmt . Errorf ( "not a valid TTL duration %v" , delDuration )
2022-03-21 23:58:56 +05:30
}
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 )
2022-04-06 16:29:10 +05:30
if err != nil || toColdParsed . Seconds ( ) <= 0 {
2024-06-11 20:10:38 +05:30
return nil , fmt . Errorf ( "not a valid toCold TTL duration %v" , toColdDuration )
2022-03-21 23:58:56 +05:30
}
2022-04-01 11:22:25 +05:30
if toColdParsed . Seconds ( ) != 0 && toColdParsed . Seconds ( ) >= durationParsed . Seconds ( ) {
2024-06-11 20:10:38 +05:30
return nil , fmt . Errorf ( "delete TTL should be greater than cold storage move TTL" )
2022-03-21 23:58:56 +05:30
}
}
return & model . TTLParams {
Type : typeTTL ,
2022-05-03 11:20:57 +05:30
DelDuration : int64 ( durationParsed . Seconds ( ) ) ,
2022-03-21 23:58:56 +05:30
ColdStorageVolume : coldStorage ,
2022-05-03 11:20:57 +05:30
ToColdStorageDuration : int64 ( toColdParsed . Seconds ( ) ) ,
2022-03-21 23:58:56 +05:30
} , nil
2021-10-20 13:18:19 +05:30
}
func parseGetTTL ( r * http . Request ) ( * model . GetTTLParams , error ) {
typeTTL := r . URL . Query ( ) . Get ( "type" )
if len ( typeTTL ) == 0 {
2022-05-25 16:55:30 +05:30
return nil , fmt . Errorf ( "type param cannot be empty from the query" )
2021-10-20 13:18:19 +05:30
} else {
// Validate the type parameter
2024-05-27 17:20:45 +05:30
if typeTTL != baseconstants . TraceTTL && typeTTL != baseconstants . MetricsTTL && typeTTL != baseconstants . LogsTTL {
2022-08-10 14:27:46 +05:30
return nil , fmt . Errorf ( "type param should be metrics|traces|logs, got %v" , typeTTL )
2021-10-20 13:18:19 +05:30
}
}
2022-05-25 16:55:30 +05:30
return & model . GetTTLParams { Type : typeTTL } , nil
2022-04-06 16:29:10 +05:30
}
2023-03-04 00:05:16 +05:30
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
}
2025-08-07 16:50:37 +05:30
if dataSource != v3 . DataSourceMetrics && dataSource != v3 . DataSourceMeter {
2024-03-01 14:51:50 +05:30
if err := aggregateOperator . Validate ( ) ; err != nil {
return nil , err
}
2023-03-04 00:05:16 +05:30
}
if err := dataSource . Validate ( ) ; err != nil {
return nil , err
}
req = v3 . AggregateAttributeRequest {
Operator : aggregateOperator ,
SearchText : aggregateAttribute ,
Limit : limit ,
DataSource : dataSource ,
}
return & req , nil
}
2023-03-10 11:22:34 +05:30
2024-08-08 09:27:41 +05:30
func parseQBFilterSuggestionsRequest ( r * http . Request ) (
* v3 . QBFilterSuggestionsRequest , * model . ApiError ,
) {
dataSource := v3 . DataSource ( r . URL . Query ( ) . Get ( "dataSource" ) )
if err := dataSource . Validate ( ) ; err != nil {
return nil , model . BadRequest ( err )
}
2024-09-09 10:12:36 +05:30
parsePositiveIntQP := func (
queryParam string , defaultValue uint64 , maxValue uint64 ,
) ( uint64 , * model . ApiError ) {
value := defaultValue
qpValue := r . URL . Query ( ) . Get ( queryParam )
if len ( qpValue ) > 0 {
value , err := strconv . Atoi ( qpValue )
if err != nil || value < 1 || value > int ( maxValue ) {
return 0 , model . BadRequest ( fmt . Errorf (
"invalid %s: %s" , queryParam , qpValue ,
) )
}
2024-08-08 09:27:41 +05:30
}
2024-09-09 10:12:36 +05:30
return value , nil
}
attributesLimit , err := parsePositiveIntQP (
"attributesLimit" ,
baseconstants . DefaultFilterSuggestionsAttributesLimit ,
baseconstants . MaxFilterSuggestionsAttributesLimit ,
)
if err != nil {
return nil , err
}
examplesLimit , err := parsePositiveIntQP (
"examplesLimit" ,
baseconstants . DefaultFilterSuggestionsExamplesLimit ,
baseconstants . MaxFilterSuggestionsExamplesLimit ,
)
if err != nil {
return nil , err
2024-08-08 09:27:41 +05:30
}
var existingFilter * v3 . FilterSet
existingFilterB64 := r . URL . Query ( ) . Get ( "existingFilter" )
if len ( existingFilterB64 ) > 0 {
decodedFilterJson , err := base64 . RawURLEncoding . DecodeString ( existingFilterB64 )
if err != nil {
return nil , model . BadRequest ( fmt . Errorf ( "couldn't base64 decode existingFilter: %w" , err ) )
}
existingFilter = & v3 . FilterSet { }
err = json . Unmarshal ( decodedFilterJson , existingFilter )
if err != nil {
return nil , model . BadRequest ( fmt . Errorf ( "couldn't JSON decode existingFilter: %w" , err ) )
}
}
searchText := r . URL . Query ( ) . Get ( "searchText" )
return & v3 . QBFilterSuggestionsRequest {
2024-09-09 10:12:36 +05:30
DataSource : dataSource ,
SearchText : searchText ,
ExistingFilter : existingFilter ,
AttributesLimit : attributesLimit ,
ExamplesLimit : examplesLimit ,
2024-08-08 09:27:41 +05:30
} , nil
}
2023-03-10 11:22:34 +05:30
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" ) )
2025-04-14 18:43:15 +05:30
tagType := v3 . TagType ( r . URL . Query ( ) . Get ( "tagType" ) )
// empty string is a valid tagType
// i.e retrieve all attributes
if tagType != "" {
// what is happening here?
// if tagType is undefined(uh oh javascript) or any invalid value, set it to empty string
// instead of failing the request. Ideally, we should fail the request.
// but we are not doing that to maintain backward compatibility.
if err := tagType . Validate ( ) ; err != nil {
// if the tagType is invalid, set it to empty string
tagType = ""
}
}
2023-03-10 11:22:34 +05:30
if err != nil {
limit = 50
}
if err := dataSource . Validate ( ) ; err != nil {
return nil , err
}
2025-08-07 16:50:37 +05:30
if dataSource != v3 . DataSourceMetrics && dataSource != v3 . DataSourceMeter {
2024-03-01 14:51:50 +05:30
if err := aggregateOperator . Validate ( ) ; err != nil {
return nil , err
}
2023-03-10 11:22:34 +05:30
}
req = v3 . FilterAttributeKeyRequest {
DataSource : dataSource ,
AggregateOperator : aggregateOperator ,
AggregateAttribute : aggregateAttribute ,
Limit : limit ,
SearchText : r . URL . Query ( ) . Get ( "searchText" ) ,
2025-04-14 18:43:15 +05:30
TagType : tagType ,
2023-03-10 11:22:34 +05:30
}
return & req , nil
}
2025-03-08 11:42:20 +05:30
func parseFilterAttributeValueRequestBody ( r * http . Request ) ( * v3 . FilterAttributeValueRequest , error ) {
var req v3 . FilterAttributeValueRequest
if err := json . NewDecoder ( r . Body ) . Decode ( & req ) ; err != nil {
return nil , err
}
if err := req . Validate ( ) ; err != nil {
return nil , err
}
// offset by two windows periods for start for better results
req . StartTimeMillis = req . StartTimeMillis - time . Hour . Milliseconds ( ) * 6 * 2
req . EndTimeMillis = req . EndTimeMillis + time . Hour . Milliseconds ( ) * 6
return & req , nil
}
2023-03-10 11:22:34 +05:30
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" ) )
2023-04-06 13:32:24 +05:30
filterAttributeKeyDataType := v3 . AttributeKeyDataType ( r . URL . Query ( ) . Get ( "filterAttributeKeyDataType" ) ) // can be empty
2023-03-10 11:22:34 +05:30
aggregateAttribute := r . URL . Query ( ) . Get ( "aggregateAttribute" )
2023-04-06 13:32:24 +05:30
tagType := v3 . TagType ( r . URL . Query ( ) . Get ( "tagType" ) ) // can be empty
2023-03-10 11:22:34 +05:30
limit , err := strconv . Atoi ( r . URL . Query ( ) . Get ( "limit" ) )
if err != nil {
limit = 50
}
if err := dataSource . Validate ( ) ; err != nil {
return nil , err
}
2024-03-01 14:51:50 +05:30
if dataSource != v3 . DataSourceMetrics {
if err := aggregateOperator . Validate ( ) ; err != nil {
return nil , err
}
2023-03-10 11:22:34 +05:30
}
req = v3 . FilterAttributeValueRequest {
2023-04-06 13:32:24 +05:30
DataSource : dataSource ,
AggregateOperator : aggregateOperator ,
AggregateAttribute : aggregateAttribute ,
TagType : tagType ,
Limit : limit ,
SearchText : r . URL . Query ( ) . Get ( "searchText" ) ,
FilterAttributeKey : r . URL . Query ( ) . Get ( "attributeKey" ) ,
FilterAttributeKeyDataType : filterAttributeKeyDataType ,
2023-03-10 11:22:34 +05:30
}
return & req , nil
}
2023-03-23 19:45:15 +05:30
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 )
}
2023-05-09 19:16:55 +05:30
errs := validateExpressions ( expressions , queryBuilder . EvalFuncs , qp . CompositeQuery )
2023-03-23 19:45:15 +05:30
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 {
2024-02-11 00:31:47 +05:30
errs = append ( errs , fmt . Errorf ( "invalid expression %s: %v" , exp , err ) )
2023-03-23 19:45:15 +05:30
continue
}
2024-02-11 00:31:47 +05:30
for _ , v := range evalExp . Vars ( ) {
2023-03-23 19:45:15 +05:30
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
}
2025-03-26 18:28:55 +05:30
// chTransformQuery transforms the clickhouse query with the given variables
// it is used to check what would be the query if variables are selected as __all__.
// for now, this is just a pass through, but in the future, we will use it to
// dashboard variables
// TODO(srikanthccv): version based query replacement
func chTransformQuery ( query string , variables map [ string ] interface { } ) {
varsForTransform := make ( [ ] chVariables . VariableValue , 0 , len ( variables ) )
for name := range variables {
varsForTransform = append ( varsForTransform , chVariables . VariableValue {
Name : name ,
Values : [ ] string { "__all__" } ,
IsSelectAll : true ,
FieldType : "scalar" ,
} )
}
transformer := chVariables . NewQueryTransformer ( query , varsForTransform )
transformedQuery , err := transformer . Transform ( )
if err != nil {
2025-04-16 14:40:54 +05:30
zap . L ( ) . Warn ( "failed to transform clickhouse query" , zap . String ( "query" , query ) , zap . Error ( err ) )
2025-03-26 18:28:55 +05:30
}
zap . L ( ) . Info ( "transformed clickhouse query" , zap . String ( "transformedQuery" , transformedQuery ) , zap . String ( "originalQuery" , query ) )
}
2023-03-23 19:45:15 +05:30
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 {
2024-02-11 00:31:47 +05:30
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : fmt . Errorf ( "cannot parse the request body: %v" , err ) }
2023-03-23 19:45:15 +05:30
}
2024-06-19 15:40:34 +05:30
// sanitize the request body
queryRangeParams . CompositeQuery . Sanitize ( )
2023-03-23 19:45:15 +05:30
// validate the request body
if err := validateQueryRangeParamsV3 ( queryRangeParams ) ; err != nil {
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
}
2024-02-11 00:31:47 +05:30
// prepare the variables for the corresponding query type
2023-03-23 19:45:15 +05:30
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 {
2024-02-06 22:29:12 +05:30
// Formula query
2024-05-15 18:52:01 +05:30
// Check if the queries used in the expression can be joined
2024-02-06 22:29:12 +05:30
if query . QueryName != query . Expression {
2024-05-24 12:11:34 +05:30
expression , err := govaluate . NewEvaluableExpressionWithFunctions ( query . Expression , postprocess . EvalFuncs ( ) )
2024-02-06 22:29:12 +05:30
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" ) }
}
}
2024-05-15 18:52:01 +05:30
// If the step interval is less than the minimum allowed step interval, set it to the minimum allowed step interval
2024-05-01 17:03:46 +05:30
if minStep := common . MinAllowedStepInterval ( queryRangeParams . Start , queryRangeParams . End ) ; query . StepInterval < minStep {
query . StepInterval = minStep
}
2024-10-21 14:22:32 +05:30
if query . DataSource == v3 . DataSourceMetrics && baseconstants . UseMetricsPreAggregation ( ) {
// if the time range is greater than 1 day, and less than 1 week set the step interval to be multiple of 5 minutes
// if the time range is greater than 1 week, set the step interval to be multiple of 30 mins
start , end := queryRangeParams . Start , queryRangeParams . End
if end - start >= 24 * time . Hour . Milliseconds ( ) && end - start < 7 * 24 * time . Hour . Milliseconds ( ) {
query . StepInterval = int64 ( math . Round ( float64 ( query . StepInterval ) / 300 ) ) * 300
} else if end - start >= 7 * 24 * time . Hour . Milliseconds ( ) {
query . StepInterval = int64 ( math . Round ( float64 ( query . StepInterval ) / 1800 ) ) * 1800
}
}
2024-10-24 02:20:32 +05:30
query . SetShiftByFromFunc ( )
2024-03-01 14:51:50 +05:30
2023-03-23 19:45:15 +05:30
if query . Filters == nil || len ( query . Filters . Items ) == 0 {
continue
}
2024-05-15 18:52:01 +05:30
2023-03-23 19:45:15 +05:30
for idx := range query . Filters . Items {
item := & query . Filters . Items [ idx ]
value := item . Value
if value != nil {
switch x := value . ( type ) {
case string :
2024-05-15 18:52:01 +05:30
variableName := strings . Trim ( x , "{[.$]}" )
2023-03-23 19:45:15 +05:30
if _ , ok := queryRangeParams . Variables [ variableName ] ; ok {
item . Value = queryRangeParams . Variables [ variableName ]
}
case [ ] interface { } :
if len ( x ) > 0 {
switch x [ 0 ] . ( type ) {
case string :
2024-05-15 18:52:01 +05:30
variableName := strings . Trim ( x [ 0 ] . ( string ) , "{[.$]}" )
2023-03-23 19:45:15 +05:30
if _ , ok := queryRangeParams . Variables [ variableName ] ; ok {
item . Value = queryRangeParams . Variables [ variableName ]
}
}
}
}
}
2024-05-15 18:52:01 +05:30
2024-05-17 07:45:03 +05:30
if v3 . FilterOperator ( strings . ToLower ( ( string ( item . Operator ) ) ) ) != v3 . FilterOperatorIn && v3 . FilterOperator ( strings . ToLower ( ( string ( item . Operator ) ) ) ) != v3 . FilterOperatorNotIn {
2024-05-15 18:52:01 +05:30
// 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 ) }
}
}
}
2023-03-23 19:45:15 +05:30
}
}
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
}
2024-05-15 18:52:01 +05:30
2025-03-26 18:28:55 +05:30
chTransformQuery ( chQuery . Query , queryRangeParams . Variables )
2025-05-30 15:57:29 +05:30
keys := make ( [ ] string , 0 , len ( queryRangeParams . Variables ) )
querytemplate . AssignReservedVarsV3 ( queryRangeParams )
for k := range queryRangeParams . Variables {
keys = append ( keys , k )
}
sort . Slice ( keys , func ( i , j int ) bool {
return len ( keys [ i ] ) > len ( keys [ j ] )
} )
for _ , k := range keys {
chQuery . Query = strings . Replace ( chQuery . Query , fmt . Sprintf ( "{{%s}}" , k ) , fmt . Sprint ( queryRangeParams . Variables [ k ] ) , - 1 )
chQuery . Query = strings . Replace ( chQuery . Query , fmt . Sprintf ( "[[%s]]" , k ) , fmt . Sprint ( queryRangeParams . Variables [ k ] ) , - 1 )
chQuery . Query = strings . Replace ( chQuery . Query , fmt . Sprintf ( "$%s" , k ) , fmt . Sprint ( queryRangeParams . Variables [ k ] ) , - 1 )
2024-05-15 18:52:01 +05:30
}
2023-03-23 19:45:15 +05:30
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
err = tmpl . Execute ( & query , queryRangeParams . Variables )
if err != nil {
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
}
chQuery . Query = query . String ( )
}
}
2023-09-19 17:49:11 +05:30
// replace go template variables in prometheus query
if queryRangeParams . CompositeQuery . QueryType == v3 . QueryTypePromQL {
for _ , promQuery := range queryRangeParams . CompositeQuery . PromQueries {
if promQuery . Disabled {
continue
}
2024-05-15 18:52:01 +05:30
2025-05-30 15:57:29 +05:30
querytemplate . AssignReservedVarsV3 ( queryRangeParams )
keys := make ( [ ] string , 0 , len ( queryRangeParams . Variables ) )
for k := range queryRangeParams . Variables {
keys = append ( keys , k )
}
sort . Slice ( keys , func ( i , j int ) bool {
return len ( keys [ i ] ) > len ( keys [ j ] )
} )
for _ , k := range keys {
promQuery . Query = strings . Replace ( promQuery . Query , fmt . Sprintf ( "{{%s}}" , k ) , fmt . Sprint ( queryRangeParams . Variables [ k ] ) , - 1 )
promQuery . Query = strings . Replace ( promQuery . Query , fmt . Sprintf ( "[[%s]]" , k ) , fmt . Sprint ( queryRangeParams . Variables [ k ] ) , - 1 )
promQuery . Query = strings . Replace ( promQuery . Query , fmt . Sprintf ( "$%s" , k ) , fmt . Sprint ( queryRangeParams . Variables [ k ] ) , - 1 )
2024-05-15 18:52:01 +05:30
}
2023-09-19 17:49:11 +05:30
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
err = tmpl . Execute ( & query , queryRangeParams . Variables )
if err != nil {
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : err }
}
promQuery . Query = query . String ( )
}
}
2023-03-23 19:45:15 +05:30
return queryRangeParams , nil
}
2025-02-19 16:08:58 +05:30
// ParseKafkaQueueBody parse for messaging queue params
func ParseKafkaQueueBody ( r * http . Request ) ( * kafka . MessagingQueue , * model . ApiError ) {
messagingQueue := new ( kafka . MessagingQueue )
if err := json . NewDecoder ( r . Body ) . Decode ( messagingQueue ) ; err != nil {
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : fmt . Errorf ( "cannot parse the request body: %v" , err ) }
}
return messagingQueue , nil
}
// ParseQueueBody parses for any queue
func ParseQueueBody ( r * http . Request ) ( * queues2 . QueueListRequest , * model . ApiError ) {
queue := new ( queues2 . QueueListRequest )
if err := json . NewDecoder ( r . Body ) . Decode ( queue ) ; err != nil {
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : fmt . Errorf ( "cannot parse the request body: %v" , err ) }
}
return queue , nil
}
// ParseRequestBody for third party APIs
func ParseRequestBody ( r * http . Request ) ( * thirdPartyApi . ThirdPartyApis , * model . ApiError ) {
thirdPartApis := new ( thirdPartyApi . ThirdPartyApis )
if err := json . NewDecoder ( r . Body ) . Decode ( thirdPartApis ) ; err != nil {
return nil , & model . ApiError { Typ : model . ErrorBadData , Err : fmt . Errorf ( "cannot parse the request body: %v" , err ) }
}
return thirdPartApis , nil
}