mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 07:35:57 +08:00
chore: update BuilderQuery struct and add PrepareTimeseriesFilterQuery (#4165)
This commit is contained in:
parent
fb1dbdc05e
commit
9360c61dca
86
pkg/query-service/app/metrics/v4/query_builder.go
Normal file
86
pkg/query-service/app/metrics/v4/query_builder.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrepareTimeseriesFilterQuery builds the sub-query to be used for filtering timeseries based on the search criteria
|
||||||
|
func PrepareTimeseriesFilterQuery(mq *v3.BuilderQuery) (string, error) {
|
||||||
|
var conditions []string
|
||||||
|
var fs *v3.FilterSet = mq.Filters
|
||||||
|
var groupTags []v3.AttributeKey = mq.GroupBy
|
||||||
|
|
||||||
|
conditions = append(conditions, fmt.Sprintf("metric_name = %s", utils.ClickHouseFormattedValue(mq.AggregateAttribute.Key)))
|
||||||
|
conditions = append(conditions, fmt.Sprintf("temporality = '%s'", mq.Temporality))
|
||||||
|
|
||||||
|
if fs != nil && len(fs.Items) != 0 {
|
||||||
|
for _, item := range fs.Items {
|
||||||
|
toFormat := item.Value
|
||||||
|
op := v3.FilterOperator(strings.ToLower(strings.TrimSpace(string(item.Operator))))
|
||||||
|
if op == v3.FilterOperatorContains || op == v3.FilterOperatorNotContains {
|
||||||
|
toFormat = fmt.Sprintf("%%%s%%", toFormat)
|
||||||
|
}
|
||||||
|
fmtVal := utils.ClickHouseFormattedValue(toFormat)
|
||||||
|
switch op {
|
||||||
|
case v3.FilterOperatorEqual:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') = %s", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorNotEqual:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') != %s", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorIn:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') IN %s", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorNotIn:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') NOT IN %s", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorLike:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorNotLike:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorRegex:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("match(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorNotRegex:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("not match(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorGreaterThan:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') > %s", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorGreaterThanOrEq:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') >= %s", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorLessThan:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') < %s", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorLessThanOrEq:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("JSONExtractString(labels, '%s') <= %s", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorContains:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("like(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorNotContains:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("notLike(JSONExtractString(labels, '%s'), %s)", item.Key.Key, fmtVal))
|
||||||
|
case v3.FilterOperatorExists:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("has(JSONExtractKeys(labels), '%s')", item.Key.Key))
|
||||||
|
case v3.FilterOperatorNotExists:
|
||||||
|
conditions = append(conditions, fmt.Sprintf("not has(JSONExtractKeys(labels), '%s')", item.Key.Key))
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported filter operator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
whereClause := strings.Join(conditions, " AND ")
|
||||||
|
|
||||||
|
var selectLabels string
|
||||||
|
for _, tag := range groupTags {
|
||||||
|
selectLabels += fmt.Sprintf("JSONExtractString(labels, '%s') as %s, ", tag.Key, tag.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The table JOIN key always exists
|
||||||
|
selectLabels += "fingerprint"
|
||||||
|
|
||||||
|
filterSubQuery := fmt.Sprintf(
|
||||||
|
"SELECT DISTINCT %s FROM %s.%s WHERE %s",
|
||||||
|
selectLabels,
|
||||||
|
constants.SIGNOZ_METRIC_DBNAME,
|
||||||
|
constants.SIGNOZ_TIMESERIES_LOCAL_TABLENAME,
|
||||||
|
whereClause,
|
||||||
|
)
|
||||||
|
|
||||||
|
return filterSubQuery, nil
|
||||||
|
}
|
150
pkg/query-service/app/metrics/v4/query_builder_test.go
Normal file
150
pkg/query-service/app/metrics/v4/query_builder_test.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrepareTimeseriesFilterQuery(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
builderQuery *v3.BuilderQuery
|
||||||
|
expectedQueryContains string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "test prepare time series with no filters and no group by",
|
||||||
|
builderQuery: &v3.BuilderQuery{
|
||||||
|
QueryName: "A",
|
||||||
|
StepInterval: 60,
|
||||||
|
DataSource: v3.DataSourceMetrics,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "http_requests",
|
||||||
|
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||||
|
Type: v3.AttributeKeyTypeUnspecified,
|
||||||
|
IsColumn: true,
|
||||||
|
IsJSON: false,
|
||||||
|
},
|
||||||
|
Temporality: v3.Delta,
|
||||||
|
Expression: "A",
|
||||||
|
Disabled: false,
|
||||||
|
// remaining struct fields are not needed here
|
||||||
|
},
|
||||||
|
expectedQueryContains: "SELECT DISTINCT fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'http_requests' AND temporality = 'Delta'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test prepare time series with no filters and group by",
|
||||||
|
builderQuery: &v3.BuilderQuery{
|
||||||
|
QueryName: "A",
|
||||||
|
StepInterval: 60,
|
||||||
|
DataSource: v3.DataSourceMetrics,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "http_requests",
|
||||||
|
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||||
|
Type: v3.AttributeKeyTypeUnspecified,
|
||||||
|
IsColumn: true,
|
||||||
|
IsJSON: false,
|
||||||
|
},
|
||||||
|
Temporality: v3.Cumulative,
|
||||||
|
GroupBy: []v3.AttributeKey{{
|
||||||
|
Key: "service_name",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
}},
|
||||||
|
Expression: "A",
|
||||||
|
Disabled: false,
|
||||||
|
// remaining struct fields are not needed here
|
||||||
|
},
|
||||||
|
expectedQueryContains: "SELECT DISTINCT JSONExtractString(labels, 'service_name') as service_name, fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'http_requests' AND temporality = 'Cumulative'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test prepare time series with no filters and multiple group by",
|
||||||
|
builderQuery: &v3.BuilderQuery{
|
||||||
|
QueryName: "A",
|
||||||
|
StepInterval: 60,
|
||||||
|
DataSource: v3.DataSourceMetrics,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "http_requests",
|
||||||
|
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||||
|
Type: v3.AttributeKeyTypeUnspecified,
|
||||||
|
IsColumn: true,
|
||||||
|
IsJSON: false,
|
||||||
|
},
|
||||||
|
Temporality: v3.Cumulative,
|
||||||
|
GroupBy: []v3.AttributeKey{
|
||||||
|
{
|
||||||
|
Key: "service_name",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "endpoint",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Expression: "A",
|
||||||
|
Disabled: false,
|
||||||
|
// remaining struct fields are not needed here
|
||||||
|
},
|
||||||
|
expectedQueryContains: "SELECT DISTINCT JSONExtractString(labels, 'service_name') as service_name, JSONExtractString(labels, 'endpoint') as endpoint, fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'http_requests' AND temporality = 'Cumulative'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test prepare time series with filters and multiple group by",
|
||||||
|
builderQuery: &v3.BuilderQuery{
|
||||||
|
QueryName: "A",
|
||||||
|
StepInterval: 60,
|
||||||
|
DataSource: v3.DataSourceMetrics,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "http_requests",
|
||||||
|
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||||
|
Type: v3.AttributeKeyTypeUnspecified,
|
||||||
|
IsColumn: true,
|
||||||
|
IsJSON: false,
|
||||||
|
},
|
||||||
|
Temporality: v3.Cumulative,
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: []v3.FilterItem{
|
||||||
|
{
|
||||||
|
Key: v3.AttributeKey{
|
||||||
|
Key: "service_name",
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
},
|
||||||
|
Operator: v3.FilterOperatorNotEqual,
|
||||||
|
Value: "payment_service",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: v3.AttributeKey{
|
||||||
|
Key: "endpoint",
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
},
|
||||||
|
Operator: v3.FilterOperatorIn,
|
||||||
|
Value: []interface{}{"/paycallback", "/payme", "/paypal"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
GroupBy: []v3.AttributeKey{{
|
||||||
|
Key: "service_name",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
}},
|
||||||
|
Expression: "A",
|
||||||
|
Disabled: false,
|
||||||
|
// remaining struct fields are not needed here
|
||||||
|
},
|
||||||
|
expectedQueryContains: "SELECT DISTINCT JSONExtractString(labels, 'service_name') as service_name, fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'http_requests' AND temporality = 'Cumulative' AND JSONExtractString(labels, 'service_name') != 'payment_service' AND JSONExtractString(labels, 'endpoint') IN ['/paycallback','/payme','/paypal']",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
query, err := PrepareTimeseriesFilterQuery(testCase.builderQuery)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Contains(t, query, testCase.expectedQueryContains)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -447,6 +447,38 @@ const (
|
|||||||
Cumulative Temporality = "Cumulative"
|
Cumulative Temporality = "Cumulative"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TimeAggregation string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TimeAggregationUnspecified TimeAggregation = ""
|
||||||
|
TimeAggregationAnyLast TimeAggregation = "latest"
|
||||||
|
TimeAggregationSum TimeAggregation = "sum"
|
||||||
|
TimeAggregationAvg TimeAggregation = "avg"
|
||||||
|
TimeAggregationMin TimeAggregation = "min"
|
||||||
|
TimeAggregationMax TimeAggregation = "max"
|
||||||
|
TimeAggregationCount TimeAggregation = "count"
|
||||||
|
TimeAggregationCountDistinct TimeAggregation = "count_distinct"
|
||||||
|
TimeAggregationRate TimeAggregation = "rate"
|
||||||
|
TimeAggregationIncrease TimeAggregation = "increase"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SpaceAggregation string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SpaceAggregationUnspecified SpaceAggregation = ""
|
||||||
|
SpaceAggregationSum SpaceAggregation = "sum"
|
||||||
|
SpaceAggregationAvg SpaceAggregation = "avg"
|
||||||
|
SpaceAggregationMin SpaceAggregation = "min"
|
||||||
|
SpaceAggregationMax SpaceAggregation = "max"
|
||||||
|
SpaceAggregationCount SpaceAggregation = "count"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Function struct {
|
||||||
|
Category string `json:"category"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Args []interface{} `json:"args,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type BuilderQuery struct {
|
type BuilderQuery struct {
|
||||||
QueryName string `json:"queryName"`
|
QueryName string `json:"queryName"`
|
||||||
StepInterval int64 `json:"stepInterval"`
|
StepInterval int64 `json:"stepInterval"`
|
||||||
@ -466,6 +498,9 @@ type BuilderQuery struct {
|
|||||||
OrderBy []OrderBy `json:"orderBy,omitempty"`
|
OrderBy []OrderBy `json:"orderBy,omitempty"`
|
||||||
ReduceTo ReduceToOperator `json:"reduceTo,omitempty"`
|
ReduceTo ReduceToOperator `json:"reduceTo,omitempty"`
|
||||||
SelectColumns []AttributeKey `json:"selectColumns,omitempty"`
|
SelectColumns []AttributeKey `json:"selectColumns,omitempty"`
|
||||||
|
TimeAggregation TimeAggregation `json:"timeAggregation,omitempty"`
|
||||||
|
SpaceAggregation SpaceAggregation `json:"spaceAggregation,omitempty"`
|
||||||
|
Functions []Function `json:"functions,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BuilderQuery) Validate() error {
|
func (b *BuilderQuery) Validate() error {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user