mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 21:35:59 +08:00
feat: trace V4 QB (#6407)
* feat: trace V4 QB * fix: update get column name and remove id * fix: handle contains and update tests * fix: remove unwanted step interval calculation * fix: add test cases * fix: add tests for static columns in QB * fix: add more order by tests * fix: update order by logic
This commit is contained in:
parent
969ac5028e
commit
2faa0c6d4f
@ -436,8 +436,6 @@ func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.Build
|
||||
} else if panelType == v3.PanelTypeTable {
|
||||
queryTmplPrefix =
|
||||
"SELECT"
|
||||
// step or aggregate interval is whole time period in case of table panel
|
||||
step = (utils.GetEpochNanoSecs(end) - utils.GetEpochNanoSecs(start)) / NANOSECOND
|
||||
} else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue {
|
||||
// Select the aggregate value for interval
|
||||
queryTmplPrefix =
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||
)
|
||||
|
||||
var aggregateOperatorToPercentile = map[v3.AggregateOperator]float64{
|
||||
var AggregateOperatorToPercentile = map[v3.AggregateOperator]float64{
|
||||
v3.AggregateOperatorP05: 0.05,
|
||||
v3.AggregateOperatorP10: 0.10,
|
||||
v3.AggregateOperatorP20: 0.20,
|
||||
@ -22,7 +22,7 @@ var aggregateOperatorToPercentile = map[v3.AggregateOperator]float64{
|
||||
v3.AggregateOperatorP99: 0.99,
|
||||
}
|
||||
|
||||
var aggregateOperatorToSQLFunc = map[v3.AggregateOperator]string{
|
||||
var AggregateOperatorToSQLFunc = map[v3.AggregateOperator]string{
|
||||
v3.AggregateOperatorAvg: "avg",
|
||||
v3.AggregateOperatorMax: "max",
|
||||
v3.AggregateOperatorMin: "min",
|
||||
@ -109,7 +109,7 @@ func getSelectLabels(aggregatorOperator v3.AggregateOperator, groupBy []v3.Attri
|
||||
return selectLabels
|
||||
}
|
||||
|
||||
func getSelectKeys(aggregatorOperator v3.AggregateOperator, groupBy []v3.AttributeKey) string {
|
||||
func GetSelectKeys(aggregatorOperator v3.AggregateOperator, groupBy []v3.AttributeKey) string {
|
||||
var selectLabels []string
|
||||
if aggregatorOperator == v3.AggregateOperatorNoOp {
|
||||
return ""
|
||||
@ -173,7 +173,7 @@ func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) {
|
||||
conditions = append(conditions, fmt.Sprintf(operator, columnName, fmtVal))
|
||||
case v3.FilterOperatorExists, v3.FilterOperatorNotExists:
|
||||
if item.Key.IsColumn {
|
||||
subQuery, err := existsSubQueryForFixedColumn(item.Key, item.Operator)
|
||||
subQuery, err := ExistsSubQueryForFixedColumn(item.Key, item.Operator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -199,7 +199,7 @@ func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) {
|
||||
return queryString, nil
|
||||
}
|
||||
|
||||
func existsSubQueryForFixedColumn(key v3.AttributeKey, op v3.FilterOperator) (string, error) {
|
||||
func ExistsSubQueryForFixedColumn(key v3.AttributeKey, op v3.FilterOperator) (string, error) {
|
||||
if key.DataType == v3.AttributeKeyDataTypeString {
|
||||
if op == v3.FilterOperatorExists {
|
||||
return fmt.Sprintf("%s %s ''", key.Key, tracesOperatorMappingV3[v3.FilterOperatorNotEqual]), nil
|
||||
@ -244,7 +244,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan
|
||||
|
||||
selectLabels := getSelectLabels(mq.AggregateOperator, mq.GroupBy)
|
||||
|
||||
having := having(mq.Having)
|
||||
having := Having(mq.Having)
|
||||
if having != "" {
|
||||
having = " having " + having
|
||||
}
|
||||
@ -272,7 +272,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan
|
||||
|
||||
// we don't need value for first query
|
||||
if options.GraphLimitQtype == constants.FirstQueryGraphLimit {
|
||||
queryTmpl = "SELECT " + getSelectKeys(mq.AggregateOperator, mq.GroupBy) + " from (" + queryTmpl + ")"
|
||||
queryTmpl = "SELECT " + GetSelectKeys(mq.AggregateOperator, mq.GroupBy) + " from (" + queryTmpl + ")"
|
||||
}
|
||||
|
||||
emptyValuesInGroupByFilter, err := handleEmptyValuesInGroupBy(mq.GroupBy)
|
||||
@ -281,7 +281,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan
|
||||
}
|
||||
filterSubQuery += emptyValuesInGroupByFilter
|
||||
|
||||
groupBy := groupByAttributeKeyTags(panelType, options.GraphLimitQtype, mq.GroupBy...)
|
||||
groupBy := GroupByAttributeKeyTags(panelType, options.GraphLimitQtype, mq.GroupBy...)
|
||||
if groupBy != "" {
|
||||
groupBy = " group by " + groupBy
|
||||
}
|
||||
@ -291,7 +291,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan
|
||||
}
|
||||
|
||||
if options.GraphLimitQtype == constants.SecondQueryGraphLimit {
|
||||
filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", getSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "%s)"
|
||||
filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", GetSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "%s)"
|
||||
}
|
||||
|
||||
aggregationKey := ""
|
||||
@ -311,7 +311,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan
|
||||
rate = rate / 60.0
|
||||
}
|
||||
|
||||
op := fmt.Sprintf("%s(%s)/%f", aggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey, rate)
|
||||
op := fmt.Sprintf("%s(%s)/%f", AggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey, rate)
|
||||
query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy)
|
||||
return query, nil
|
||||
case
|
||||
@ -324,17 +324,17 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan
|
||||
v3.AggregateOperatorP90,
|
||||
v3.AggregateOperatorP95,
|
||||
v3.AggregateOperatorP99:
|
||||
op := fmt.Sprintf("quantile(%v)(%s)", aggregateOperatorToPercentile[mq.AggregateOperator], aggregationKey)
|
||||
op := fmt.Sprintf("quantile(%v)(%s)", AggregateOperatorToPercentile[mq.AggregateOperator], aggregationKey)
|
||||
query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy)
|
||||
return query, nil
|
||||
case v3.AggregateOperatorAvg, v3.AggregateOperatorSum, v3.AggregateOperatorMin, v3.AggregateOperatorMax:
|
||||
op := fmt.Sprintf("%s(%s)", aggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey)
|
||||
op := fmt.Sprintf("%s(%s)", AggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey)
|
||||
query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy)
|
||||
return query, nil
|
||||
case v3.AggregateOperatorCount:
|
||||
if mq.AggregateAttribute.Key != "" {
|
||||
if mq.AggregateAttribute.IsColumn {
|
||||
subQuery, err := existsSubQueryForFixedColumn(mq.AggregateAttribute, v3.FilterOperatorExists)
|
||||
subQuery, err := ExistsSubQueryForFixedColumn(mq.AggregateAttribute, v3.FilterOperatorExists)
|
||||
if err == nil {
|
||||
filterSubQuery = fmt.Sprintf("%s AND %s", filterSubQuery, subQuery)
|
||||
}
|
||||
@ -354,9 +354,9 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan
|
||||
var query string
|
||||
if panelType == v3.PanelTypeTrace {
|
||||
withSubQuery := fmt.Sprintf(constants.TracesExplorerViewSQLSelectWithSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_LOCAL_TABLENAME, spanIndexTableTimeFilter, filterSubQuery)
|
||||
withSubQuery = addLimitToQuery(withSubQuery, mq.Limit)
|
||||
withSubQuery = AddLimitToQuery(withSubQuery, mq.Limit)
|
||||
if mq.Offset != 0 {
|
||||
withSubQuery = addOffsetToQuery(withSubQuery, mq.Offset)
|
||||
withSubQuery = AddOffsetToQuery(withSubQuery, mq.Offset)
|
||||
}
|
||||
// query = withSubQuery + ") " + fmt.Sprintf(constants.TracesExplorerViewSQLSelectQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME)
|
||||
query = fmt.Sprintf(constants.TracesExplorerViewSQLSelectBeforeSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME) + withSubQuery + ") " + fmt.Sprintf(constants.TracesExplorerViewSQLSelectAfterSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME, spanIndexTableTimeFilter)
|
||||
@ -403,7 +403,7 @@ func groupBy(panelType v3.PanelType, graphLimitQtype string, tags ...string) str
|
||||
return strings.Join(tags, ",")
|
||||
}
|
||||
|
||||
func groupByAttributeKeyTags(panelType v3.PanelType, graphLimitQtype string, tags ...v3.AttributeKey) string {
|
||||
func GroupByAttributeKeyTags(panelType v3.PanelType, graphLimitQtype string, tags ...v3.AttributeKey) string {
|
||||
groupTags := []string{}
|
||||
for _, tag := range tags {
|
||||
groupTags = append(groupTags, fmt.Sprintf("`%s`", tag.Key))
|
||||
@ -456,7 +456,7 @@ func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags []
|
||||
return str
|
||||
}
|
||||
|
||||
func having(items []v3.Having) string {
|
||||
func Having(items []v3.Having) string {
|
||||
// aggregate something and filter on that aggregate
|
||||
var having []string
|
||||
for _, item := range items {
|
||||
@ -465,7 +465,7 @@ func having(items []v3.Having) string {
|
||||
return strings.Join(having, " AND ")
|
||||
}
|
||||
|
||||
func reduceToQuery(query string, reduceTo v3.ReduceToOperator, _ v3.AggregateOperator) (string, error) {
|
||||
func ReduceToQuery(query string, reduceTo v3.ReduceToOperator, _ v3.AggregateOperator) (string, error) {
|
||||
|
||||
var groupBy string
|
||||
switch reduceTo {
|
||||
@ -485,14 +485,14 @@ func reduceToQuery(query string, reduceTo v3.ReduceToOperator, _ v3.AggregateOpe
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func addLimitToQuery(query string, limit uint64) string {
|
||||
func AddLimitToQuery(query string, limit uint64) string {
|
||||
if limit == 0 {
|
||||
limit = 100
|
||||
}
|
||||
return fmt.Sprintf("%s LIMIT %d", query, limit)
|
||||
}
|
||||
|
||||
func addOffsetToQuery(query string, offset uint64) string {
|
||||
func AddOffsetToQuery(query string, offset uint64) string {
|
||||
return fmt.Sprintf("%s OFFSET %d", query, offset)
|
||||
}
|
||||
|
||||
@ -513,7 +513,7 @@ func PrepareTracesQuery(start, end int64, panelType v3.PanelType, mq *v3.Builder
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query = addLimitToQuery(query, mq.Limit)
|
||||
query = AddLimitToQuery(query, mq.Limit)
|
||||
|
||||
return query, nil
|
||||
} else if options.GraphLimitQtype == constants.SecondQueryGraphLimit {
|
||||
@ -529,13 +529,13 @@ func PrepareTracesQuery(start, end int64, panelType v3.PanelType, mq *v3.Builder
|
||||
return "", err
|
||||
}
|
||||
if panelType == v3.PanelTypeValue {
|
||||
query, err = reduceToQuery(query, mq.ReduceTo, mq.AggregateOperator)
|
||||
query, err = ReduceToQuery(query, mq.ReduceTo, mq.AggregateOperator)
|
||||
}
|
||||
if panelType == v3.PanelTypeList || panelType == v3.PanelTypeTable {
|
||||
query = addLimitToQuery(query, mq.Limit)
|
||||
query = AddLimitToQuery(query, mq.Limit)
|
||||
|
||||
if mq.Offset != 0 {
|
||||
query = addOffsetToQuery(query, mq.Offset)
|
||||
query = AddOffsetToQuery(query, mq.Offset)
|
||||
}
|
||||
}
|
||||
return query, err
|
||||
|
414
pkg/query-service/app/traces/v4/query_builder.go
Normal file
414
pkg/query-service/app/traces/v4/query_builder.go
Normal file
@ -0,0 +1,414 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.signoz.io/signoz/pkg/query-service/app/resource"
|
||||
tracesV3 "go.signoz.io/signoz/pkg/query-service/app/traces/v3"
|
||||
"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"
|
||||
)
|
||||
|
||||
const NANOSECOND = 1000000000
|
||||
|
||||
var tracesOperatorMappingV3 = map[v3.FilterOperator]string{
|
||||
v3.FilterOperatorIn: "IN",
|
||||
v3.FilterOperatorNotIn: "NOT IN",
|
||||
v3.FilterOperatorEqual: "=",
|
||||
v3.FilterOperatorNotEqual: "!=",
|
||||
v3.FilterOperatorLessThan: "<",
|
||||
v3.FilterOperatorLessThanOrEq: "<=",
|
||||
v3.FilterOperatorGreaterThan: ">",
|
||||
v3.FilterOperatorGreaterThanOrEq: ">=",
|
||||
v3.FilterOperatorLike: "ILIKE",
|
||||
v3.FilterOperatorNotLike: "NOT ILIKE",
|
||||
v3.FilterOperatorRegex: "match(%s, %s)",
|
||||
v3.FilterOperatorNotRegex: "NOT match(%s, %s)",
|
||||
v3.FilterOperatorContains: "ILIKE",
|
||||
v3.FilterOperatorNotContains: "NOT ILIKE",
|
||||
v3.FilterOperatorExists: "mapContains(%s, '%s')",
|
||||
v3.FilterOperatorNotExists: "NOT mapContains(%s, '%s')",
|
||||
}
|
||||
|
||||
func getClickHouseTracesColumnType(columnType v3.AttributeKeyType) string {
|
||||
if columnType == v3.AttributeKeyTypeResource {
|
||||
return "resources"
|
||||
}
|
||||
return "attributes"
|
||||
}
|
||||
|
||||
func getClickHouseTracesColumnDataType(columnDataType v3.AttributeKeyDataType) string {
|
||||
if columnDataType == v3.AttributeKeyDataTypeFloat64 || columnDataType == v3.AttributeKeyDataTypeInt64 {
|
||||
return "number"
|
||||
}
|
||||
if columnDataType == v3.AttributeKeyDataTypeBool {
|
||||
return "bool"
|
||||
}
|
||||
return "string"
|
||||
}
|
||||
|
||||
func getColumnName(key v3.AttributeKey) string {
|
||||
// if key present in static return as it is
|
||||
if _, ok := constants.StaticFieldsTraces[key.Key]; ok {
|
||||
return key.Key
|
||||
}
|
||||
|
||||
if !key.IsColumn {
|
||||
keyType := getClickHouseTracesColumnType(key.Type)
|
||||
keyDType := getClickHouseTracesColumnDataType(key.DataType)
|
||||
return fmt.Sprintf("%s_%s['%s']", keyType, keyDType, key.Key)
|
||||
}
|
||||
|
||||
return "`" + utils.GetClickhouseColumnNameV2(string(key.Type), string(key.DataType), key.Key) + "`"
|
||||
}
|
||||
|
||||
// getSelectLabels returns the select labels for the query based on groupBy and aggregateOperator
|
||||
func getSelectLabels(groupBy []v3.AttributeKey) string {
|
||||
var labels []string
|
||||
for _, tag := range groupBy {
|
||||
name := getColumnName(tag)
|
||||
labels = append(labels, fmt.Sprintf(" %s as `%s`", name, tag.Key))
|
||||
}
|
||||
return strings.Join(labels, ",")
|
||||
}
|
||||
|
||||
func buildTracesFilterQuery(fs *v3.FilterSet) (string, error) {
|
||||
var conditions []string
|
||||
|
||||
if fs != nil && len(fs.Items) != 0 {
|
||||
for _, item := range fs.Items {
|
||||
|
||||
// skip if it's a resource attribute
|
||||
if item.Key.Type == v3.AttributeKeyTypeResource {
|
||||
continue
|
||||
}
|
||||
|
||||
val := item.Value
|
||||
// generate the key
|
||||
columnName := getColumnName(item.Key)
|
||||
var fmtVal string
|
||||
item.Operator = v3.FilterOperator(strings.ToLower(strings.TrimSpace(string(item.Operator))))
|
||||
if item.Operator != v3.FilterOperatorExists && item.Operator != v3.FilterOperatorNotExists {
|
||||
var err error
|
||||
val, err = utils.ValidateAndCastValue(val, item.Key.DataType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid value for key %s: %v", item.Key.Key, err)
|
||||
}
|
||||
}
|
||||
if val != nil {
|
||||
fmtVal = utils.ClickHouseFormattedValue(val)
|
||||
}
|
||||
if operator, ok := tracesOperatorMappingV3[item.Operator]; ok {
|
||||
switch item.Operator {
|
||||
case v3.FilterOperatorContains, v3.FilterOperatorNotContains:
|
||||
// we also want to treat %, _ as literals for contains
|
||||
val := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", item.Value), false)
|
||||
conditions = append(conditions, fmt.Sprintf("%s %s '%%%s%%'", columnName, operator, val))
|
||||
case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex:
|
||||
conditions = append(conditions, fmt.Sprintf(operator, columnName, fmtVal))
|
||||
case v3.FilterOperatorExists, v3.FilterOperatorNotExists:
|
||||
if item.Key.IsColumn {
|
||||
subQuery, err := tracesV3.ExistsSubQueryForFixedColumn(item.Key, item.Operator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
conditions = append(conditions, subQuery)
|
||||
} else {
|
||||
cType := getClickHouseTracesColumnType(item.Key.Type)
|
||||
cDataType := getClickHouseTracesColumnDataType(item.Key.DataType)
|
||||
col := fmt.Sprintf("%s_%s", cType, cDataType)
|
||||
conditions = append(conditions, fmt.Sprintf(operator, col, item.Key.Key))
|
||||
}
|
||||
|
||||
default:
|
||||
conditions = append(conditions, fmt.Sprintf("%s %s %s", columnName, operator, fmtVal))
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("unsupported operator %s", item.Operator)
|
||||
}
|
||||
}
|
||||
}
|
||||
queryString := strings.Join(conditions, " AND ")
|
||||
|
||||
return queryString, nil
|
||||
}
|
||||
|
||||
func handleEmptyValuesInGroupBy(groupBy []v3.AttributeKey) (string, error) {
|
||||
// TODO(nitya): in future when we support user based mat column handle them
|
||||
// skipping now as we don't support creating them
|
||||
filterItems := []v3.FilterItem{}
|
||||
if len(groupBy) != 0 {
|
||||
for _, item := range groupBy {
|
||||
if !item.IsColumn {
|
||||
filterItems = append(filterItems, v3.FilterItem{
|
||||
Key: item,
|
||||
Operator: v3.FilterOperatorExists,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(filterItems) != 0 {
|
||||
filterSet := v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: filterItems,
|
||||
}
|
||||
return buildTracesFilterQuery(&filterSet)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// orderBy returns a string of comma separated tags for order by clause
|
||||
// if there are remaining items which are not present in tags they are also added
|
||||
// if the order is not specified, it defaults to ASC
|
||||
func orderBy(panelType v3.PanelType, items []v3.OrderBy, tagLookup map[string]struct{}) []string {
|
||||
var orderBy []string
|
||||
|
||||
for _, item := range items {
|
||||
if item.ColumnName == constants.SigNozOrderByValue {
|
||||
orderBy = append(orderBy, fmt.Sprintf("value %s", item.Order))
|
||||
} else if _, ok := tagLookup[item.ColumnName]; ok {
|
||||
orderBy = append(orderBy, fmt.Sprintf("`%s` %s", item.ColumnName, item.Order))
|
||||
} else if panelType == v3.PanelTypeList {
|
||||
attr := v3.AttributeKey{Key: item.ColumnName, DataType: item.DataType, Type: item.Type, IsColumn: item.IsColumn}
|
||||
name := getColumnName(attr)
|
||||
orderBy = append(orderBy, fmt.Sprintf("%s %s", name, item.Order))
|
||||
}
|
||||
}
|
||||
|
||||
return orderBy
|
||||
}
|
||||
|
||||
func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags []v3.AttributeKey) string {
|
||||
tagLookup := map[string]struct{}{}
|
||||
for _, v := range tags {
|
||||
tagLookup[v.Key] = struct{}{}
|
||||
}
|
||||
|
||||
orderByArray := orderBy(panelType, items, tagLookup)
|
||||
|
||||
if len(orderByArray) == 0 {
|
||||
if panelType == v3.PanelTypeList {
|
||||
orderByArray = append(orderByArray, constants.TIMESTAMP+" DESC")
|
||||
} else {
|
||||
orderByArray = append(orderByArray, "value DESC")
|
||||
}
|
||||
}
|
||||
|
||||
str := strings.Join(orderByArray, ",")
|
||||
return str
|
||||
}
|
||||
|
||||
func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3.PanelType, options v3.QBOptions) (string, error) {
|
||||
tracesStart := utils.GetEpochNanoSecs(start)
|
||||
tracesEnd := utils.GetEpochNanoSecs(end)
|
||||
|
||||
// -1800 this is added so that the bucket start considers all the fingerprints.
|
||||
bucketStart := tracesStart/NANOSECOND - 1800
|
||||
bucketEnd := tracesEnd / NANOSECOND
|
||||
|
||||
timeFilter := fmt.Sprintf("(timestamp >= '%d' AND timestamp <= '%d') AND (ts_bucket_start >= %d AND ts_bucket_start <= %d)", tracesStart, tracesEnd, bucketStart, bucketEnd)
|
||||
|
||||
filterSubQuery, err := buildTracesFilterQuery(mq.Filters)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if filterSubQuery != "" {
|
||||
filterSubQuery = " AND " + filterSubQuery
|
||||
}
|
||||
|
||||
emptyValuesInGroupByFilter, err := handleEmptyValuesInGroupBy(mq.GroupBy)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if emptyValuesInGroupByFilter != "" {
|
||||
filterSubQuery = filterSubQuery + " AND " + emptyValuesInGroupByFilter
|
||||
}
|
||||
|
||||
resourceSubQuery, err := resource.BuildResourceSubQuery("signoz_traces", "distributed_traces_v3_resource", bucketStart, bucketEnd, mq.Filters, mq.GroupBy, mq.AggregateAttribute, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// join both the filter clauses
|
||||
if resourceSubQuery != "" {
|
||||
filterSubQuery = filterSubQuery + " AND (resource_fingerprint GLOBAL IN " + resourceSubQuery + ")"
|
||||
}
|
||||
|
||||
// timerange will be sent in epoch millisecond
|
||||
selectLabels := getSelectLabels(mq.GroupBy)
|
||||
if selectLabels != "" {
|
||||
selectLabels = selectLabels + ","
|
||||
}
|
||||
|
||||
orderBy := orderByAttributeKeyTags(panelType, mq.OrderBy, mq.GroupBy)
|
||||
if orderBy != "" {
|
||||
orderBy = " order by " + orderBy
|
||||
}
|
||||
|
||||
if mq.AggregateOperator == v3.AggregateOperatorNoOp {
|
||||
var query string
|
||||
if panelType == v3.PanelTypeTrace {
|
||||
withSubQuery := fmt.Sprintf(constants.TracesExplorerViewSQLSelectWithSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3_LOCAL_TABLENAME, timeFilter, filterSubQuery)
|
||||
withSubQuery = tracesV3.AddLimitToQuery(withSubQuery, mq.Limit)
|
||||
if mq.Offset != 0 {
|
||||
withSubQuery = tracesV3.AddOffsetToQuery(withSubQuery, mq.Offset)
|
||||
}
|
||||
query = fmt.Sprintf(constants.TracesExplorerViewSQLSelectBeforeSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3) + withSubQuery + ") " + fmt.Sprintf(constants.TracesExplorerViewSQLSelectAfterSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_V3, timeFilter)
|
||||
} else if panelType == v3.PanelTypeList {
|
||||
if len(mq.SelectColumns) == 0 {
|
||||
return "", fmt.Errorf("select columns cannot be empty for panelType %s", panelType)
|
||||
}
|
||||
// add it to the select labels
|
||||
selectLabels = getSelectLabels(mq.SelectColumns)
|
||||
queryNoOpTmpl := fmt.Sprintf("SELECT timestamp as timestamp_datetime, spanID, traceID,%s ", selectLabels) + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_V3 + " where %s %s" + "%s"
|
||||
query = fmt.Sprintf(queryNoOpTmpl, timeFilter, filterSubQuery, orderBy)
|
||||
} else {
|
||||
return "", fmt.Errorf("unsupported aggregate operator %s for panelType %s", mq.AggregateOperator, panelType)
|
||||
}
|
||||
return query, nil
|
||||
// ---- NOOP ends here ----
|
||||
}
|
||||
|
||||
having := tracesV3.Having(mq.Having)
|
||||
if having != "" {
|
||||
having = " having " + having
|
||||
}
|
||||
|
||||
groupBy := tracesV3.GroupByAttributeKeyTags(panelType, options.GraphLimitQtype, mq.GroupBy...)
|
||||
if groupBy != "" {
|
||||
groupBy = " group by " + groupBy
|
||||
}
|
||||
|
||||
aggregationKey := ""
|
||||
if mq.AggregateAttribute.Key != "" {
|
||||
aggregationKey = getColumnName(mq.AggregateAttribute)
|
||||
}
|
||||
|
||||
var queryTmpl string
|
||||
if options.GraphLimitQtype == constants.FirstQueryGraphLimit {
|
||||
queryTmpl = "SELECT"
|
||||
} else if panelType == v3.PanelTypeTable {
|
||||
queryTmpl =
|
||||
"SELECT "
|
||||
} else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue {
|
||||
// Select the aggregate value for interval
|
||||
queryTmpl =
|
||||
fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d SECOND) AS ts,", step)
|
||||
}
|
||||
|
||||
queryTmpl = queryTmpl + selectLabels +
|
||||
" %s as value " +
|
||||
"from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_V3 +
|
||||
" where " + timeFilter + "%s" +
|
||||
"%s%s" +
|
||||
"%s"
|
||||
|
||||
// we don't need value for first query
|
||||
if options.GraphLimitQtype == constants.FirstQueryGraphLimit {
|
||||
queryTmpl = "SELECT " + tracesV3.GetSelectKeys(mq.AggregateOperator, mq.GroupBy) + " from (" + queryTmpl + ")"
|
||||
}
|
||||
|
||||
if options.GraphLimitQtype == constants.SecondQueryGraphLimit {
|
||||
filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", tracesV3.GetSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "%s)"
|
||||
}
|
||||
|
||||
switch mq.AggregateOperator {
|
||||
case v3.AggregateOperatorRateSum,
|
||||
v3.AggregateOperatorRateMax,
|
||||
v3.AggregateOperatorRateAvg,
|
||||
v3.AggregateOperatorRateMin,
|
||||
v3.AggregateOperatorRate:
|
||||
|
||||
rate := float64(step)
|
||||
if options.PreferRPM {
|
||||
rate = rate / 60.0
|
||||
}
|
||||
|
||||
op := fmt.Sprintf("%s(%s)/%f", tracesV3.AggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey, rate)
|
||||
query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy)
|
||||
return query, nil
|
||||
case
|
||||
v3.AggregateOperatorP05,
|
||||
v3.AggregateOperatorP10,
|
||||
v3.AggregateOperatorP20,
|
||||
v3.AggregateOperatorP25,
|
||||
v3.AggregateOperatorP50,
|
||||
v3.AggregateOperatorP75,
|
||||
v3.AggregateOperatorP90,
|
||||
v3.AggregateOperatorP95,
|
||||
v3.AggregateOperatorP99:
|
||||
op := fmt.Sprintf("quantile(%v)(%s)", tracesV3.AggregateOperatorToPercentile[mq.AggregateOperator], aggregationKey)
|
||||
query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy)
|
||||
return query, nil
|
||||
case v3.AggregateOperatorAvg, v3.AggregateOperatorSum, v3.AggregateOperatorMin, v3.AggregateOperatorMax:
|
||||
op := fmt.Sprintf("%s(%s)", tracesV3.AggregateOperatorToSQLFunc[mq.AggregateOperator], aggregationKey)
|
||||
query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy)
|
||||
return query, nil
|
||||
case v3.AggregateOperatorCount:
|
||||
if mq.AggregateAttribute.Key != "" {
|
||||
if mq.AggregateAttribute.IsColumn {
|
||||
subQuery, err := tracesV3.ExistsSubQueryForFixedColumn(mq.AggregateAttribute, v3.FilterOperatorExists)
|
||||
if err == nil {
|
||||
filterSubQuery = fmt.Sprintf("%s AND %s", filterSubQuery, subQuery)
|
||||
}
|
||||
} else {
|
||||
column := getColumnName(mq.AggregateAttribute)
|
||||
filterSubQuery = fmt.Sprintf("%s AND has(%s, '%s')", filterSubQuery, column, mq.AggregateAttribute.Key)
|
||||
}
|
||||
}
|
||||
op := "toFloat64(count())"
|
||||
query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy)
|
||||
return query, nil
|
||||
case v3.AggregateOperatorCountDistinct:
|
||||
op := fmt.Sprintf("toFloat64(count(distinct(%s)))", aggregationKey)
|
||||
query := fmt.Sprintf(queryTmpl, op, filterSubQuery, groupBy, having, orderBy)
|
||||
return query, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported aggregate operator %s", mq.AggregateOperator)
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareTracesQuery returns the query string for traces
|
||||
// start and end are in epoch millisecond
|
||||
// step is in seconds
|
||||
func PrepareTracesQuery(start, end int64, panelType v3.PanelType, mq *v3.BuilderQuery, options v3.QBOptions) (string, error) {
|
||||
// adjust the start and end time to the step interval
|
||||
if panelType == v3.PanelTypeGraph {
|
||||
// adjust the start and end time to the step interval for graph panel types
|
||||
start = start - (start % (mq.StepInterval * 1000))
|
||||
end = end - (end % (mq.StepInterval * 1000))
|
||||
}
|
||||
if options.GraphLimitQtype == constants.FirstQueryGraphLimit {
|
||||
// give me just the group by names
|
||||
query, err := buildTracesQuery(start, end, mq.StepInterval, mq, panelType, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
query = tracesV3.AddLimitToQuery(query, mq.Limit)
|
||||
|
||||
return query, nil
|
||||
} else if options.GraphLimitQtype == constants.SecondQueryGraphLimit {
|
||||
query, err := buildTracesQuery(start, end, mq.StepInterval, mq, panelType, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return query, nil
|
||||
}
|
||||
|
||||
query, err := buildTracesQuery(start, end, mq.StepInterval, mq, panelType, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if panelType == v3.PanelTypeValue {
|
||||
query, err = tracesV3.ReduceToQuery(query, mq.ReduceTo, mq.AggregateOperator)
|
||||
}
|
||||
if panelType == v3.PanelTypeList || panelType == v3.PanelTypeTable {
|
||||
query = tracesV3.AddLimitToQuery(query, mq.Limit)
|
||||
|
||||
if mq.Offset != 0 {
|
||||
query = tracesV3.AddOffsetToQuery(query, mq.Offset)
|
||||
}
|
||||
}
|
||||
return query, err
|
||||
}
|
708
pkg/query-service/app/traces/v4/query_builder_test.go
Normal file
708
pkg/query-service/app/traces/v4/query_builder_test.go
Normal file
@ -0,0 +1,708 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||
)
|
||||
|
||||
func Test_getClickHouseTracesColumnType(t *testing.T) {
|
||||
type args struct {
|
||||
columnType v3.AttributeKeyType
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "tag",
|
||||
args: args{
|
||||
columnType: v3.AttributeKeyTypeTag,
|
||||
},
|
||||
want: "attributes",
|
||||
},
|
||||
{
|
||||
name: "resource",
|
||||
args: args{
|
||||
columnType: v3.AttributeKeyTypeResource,
|
||||
},
|
||||
want: "resources",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getClickHouseTracesColumnType(tt.args.columnType); got != tt.want {
|
||||
t.Errorf("GetClickhouseTracesColumnType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getClickHouseTracesColumnDataType(t *testing.T) {
|
||||
type args struct {
|
||||
columnDataType v3.AttributeKeyDataType
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
args: args{
|
||||
columnDataType: v3.AttributeKeyDataTypeString,
|
||||
},
|
||||
want: "string",
|
||||
},
|
||||
{
|
||||
name: "float64",
|
||||
args: args{
|
||||
columnDataType: v3.AttributeKeyDataTypeFloat64,
|
||||
},
|
||||
want: "number",
|
||||
},
|
||||
{
|
||||
name: "int64",
|
||||
args: args{
|
||||
columnDataType: v3.AttributeKeyDataTypeInt64,
|
||||
},
|
||||
want: "number",
|
||||
},
|
||||
{
|
||||
name: "bool",
|
||||
args: args{
|
||||
columnDataType: v3.AttributeKeyDataTypeBool,
|
||||
},
|
||||
want: "bool",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getClickHouseTracesColumnDataType(tt.args.columnDataType); got != tt.want {
|
||||
t.Errorf("getClickhouseTracesColumnDataType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getColumnName(t *testing.T) {
|
||||
type args struct {
|
||||
key v3.AttributeKey
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "tag",
|
||||
args: args{
|
||||
key: v3.AttributeKey{Key: "data", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
|
||||
},
|
||||
want: "attributes_string['data']",
|
||||
},
|
||||
{
|
||||
name: "column",
|
||||
args: args{
|
||||
key: v3.AttributeKey{Key: "data", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
|
||||
},
|
||||
want: "`attribute_string_data`",
|
||||
},
|
||||
{
|
||||
name: "static column",
|
||||
args: args{
|
||||
key: v3.AttributeKey{Key: "spanKind", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
|
||||
},
|
||||
want: "spanKind",
|
||||
},
|
||||
{
|
||||
name: "missing meta",
|
||||
args: args{
|
||||
key: v3.AttributeKey{Key: "xyz"},
|
||||
},
|
||||
want: "attributes_string['xyz']",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getColumnName(tt.args.key); got != tt.want {
|
||||
t.Errorf("getColumnName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getSelectLabels(t *testing.T) {
|
||||
type args struct {
|
||||
groupBy []v3.AttributeKey
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "count",
|
||||
args: args{
|
||||
groupBy: []v3.AttributeKey{{Key: "user_name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
|
||||
},
|
||||
want: " attributes_string['user_name'] as `user_name`",
|
||||
},
|
||||
{
|
||||
name: "multiple group by",
|
||||
args: args{
|
||||
groupBy: []v3.AttributeKey{
|
||||
{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, // static col
|
||||
{Key: "service_name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, IsColumn: true},
|
||||
},
|
||||
},
|
||||
want: " name as `name`, `resource_string_service_name` as `service_name`",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getSelectLabels(tt.args.groupBy); got != tt.want {
|
||||
t.Errorf("getSelectLabels() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildTracesFilterQuery(t *testing.T) {
|
||||
type args struct {
|
||||
fs *v3.FilterSet
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test ignore resource",
|
||||
args: args{
|
||||
fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "service.name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, Value: []interface{}{"service"}, Operator: v3.FilterOperatorIn},
|
||||
},
|
||||
}},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "Test buildTracesFilterQuery in, nin",
|
||||
args: args{
|
||||
fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{"GET", "POST"}, Operator: v3.FilterOperatorIn},
|
||||
{Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{"PUT"}, Operator: v3.FilterOperatorNotIn},
|
||||
{Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, Value: []interface{}{"server"}, Operator: v3.FilterOperatorNotIn},
|
||||
{Key: v3.AttributeKey{Key: "status.code", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{200}, Operator: v3.FilterOperatorNotIn},
|
||||
{Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeFloat64, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{100.0}, Operator: v3.FilterOperatorIn},
|
||||
{Key: v3.AttributeKey{Key: "isDone", DataType: v3.AttributeKeyDataTypeBool, Type: v3.AttributeKeyTypeTag}, Value: []interface{}{true}, Operator: v3.FilterOperatorIn},
|
||||
}},
|
||||
},
|
||||
want: "attributes_string['method'] IN ['GET','POST'] AND attributes_string['method'] NOT IN ['PUT'] AND attributes_number['status.code'] NOT IN [200] AND attributes_number['duration'] IN [100] AND attributes_bool['isDone'] IN [true]",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test buildTracesFilterQuery not eq, neq, gt, lt, gte, lte",
|
||||
args: args{
|
||||
fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 102, Operator: v3.FilterOperatorEqual},
|
||||
{Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 100, Operator: v3.FilterOperatorNotEqual},
|
||||
{Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 10, Operator: v3.FilterOperatorGreaterThan},
|
||||
{Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Value: 200, Operator: v3.FilterOperatorLessThan},
|
||||
{Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeFloat64, Type: v3.AttributeKeyTypeTag}, Value: 10.0, Operator: v3.FilterOperatorGreaterThanOrEq},
|
||||
{Key: v3.AttributeKey{Key: "duration_str", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "200", Operator: v3.FilterOperatorLessThanOrEq},
|
||||
}},
|
||||
},
|
||||
want: "attributes_number['duration'] = 102 AND attributes_number['duration'] != 100 AND attributes_number['duration'] > 10 AND attributes_number['duration'] < 200" +
|
||||
" AND attributes_number['duration'] >= 10.000000 AND attributes_string['duration_str'] <= '200'",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test contains, ncontains, like, nlike, regex, nregex",
|
||||
args: args{
|
||||
fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102.%", Operator: v3.FilterOperatorContains},
|
||||
{Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "103_", Operator: v3.FilterOperatorNotContains},
|
||||
{Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102.", Operator: v3.FilterOperatorLike},
|
||||
{Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "102", Operator: v3.FilterOperatorNotLike},
|
||||
{Key: v3.AttributeKey{Key: "path", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Value: "/mypath", Operator: v3.FilterOperatorRegex},
|
||||
{Key: v3.AttributeKey{Key: "path", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Value: "/health.*", Operator: v3.FilterOperatorNotRegex},
|
||||
}},
|
||||
},
|
||||
want: "attributes_string['host'] ILIKE '%102.\\%%' AND attributes_string['host'] NOT ILIKE '%103\\_%' AND attributes_string['host'] ILIKE '102.' AND attributes_string['host'] NOT ILIKE '102' AND " +
|
||||
"match(`attribute_string_path`, '/mypath') AND NOT match(`attribute_string_path`, '/health.*')",
|
||||
},
|
||||
{
|
||||
name: "Test exists, nexists",
|
||||
args: args{
|
||||
fs: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "host", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Operator: v3.FilterOperatorExists},
|
||||
{Key: v3.AttributeKey{Key: "duration", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag}, Operator: v3.FilterOperatorExists},
|
||||
{Key: v3.AttributeKey{Key: "isDone", DataType: v3.AttributeKeyDataTypeBool, Type: v3.AttributeKeyTypeTag}, Operator: v3.FilterOperatorNotExists},
|
||||
{Key: v3.AttributeKey{Key: "host1", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Operator: v3.FilterOperatorNotExists},
|
||||
{Key: v3.AttributeKey{Key: "path", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, Operator: v3.FilterOperatorNotExists},
|
||||
}},
|
||||
},
|
||||
want: "mapContains(attributes_string, 'host') AND mapContains(attributes_number, 'duration') AND NOT mapContains(attributes_bool, 'isDone') AND NOT mapContains(attributes_string, 'host1') AND path = ''",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := buildTracesFilterQuery(tt.args.fs)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("buildTracesFilterQuery() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("buildTracesFilterQuery() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_handleEmptyValuesInGroupBy(t *testing.T) {
|
||||
type args struct {
|
||||
groupBy []v3.AttributeKey
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test handleEmptyValuesInGroupBy",
|
||||
args: args{
|
||||
groupBy: []v3.AttributeKey{{Key: "bytes", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
|
||||
},
|
||||
want: "mapContains(attributes_string, 'bytes')",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Test handleEmptyValuesInGroupBy",
|
||||
args: args{
|
||||
groupBy: []v3.AttributeKey{{Key: "bytes", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}},
|
||||
},
|
||||
want: "",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := handleEmptyValuesInGroupBy(tt.args.groupBy)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("handleEmptyValuesInGroupBy() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("handleEmptyValuesInGroupBy() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_orderByAttributeKeyTags(t *testing.T) {
|
||||
type args struct {
|
||||
panelType v3.PanelType
|
||||
items []v3.OrderBy
|
||||
tags []v3.AttributeKey
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "test",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeGraph,
|
||||
items: []v3.OrderBy{{ColumnName: "name", Order: "ASC"}},
|
||||
tags: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
|
||||
},
|
||||
want: "`name` ASC",
|
||||
},
|
||||
{
|
||||
name: "order by value",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeGraph,
|
||||
items: []v3.OrderBy{{ColumnName: "name", Order: "ASC"}, {ColumnName: constants.SigNozOrderByValue, Order: "DESC"}},
|
||||
tags: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
|
||||
},
|
||||
want: "`name` ASC,value DESC",
|
||||
},
|
||||
{
|
||||
name: "test",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeList,
|
||||
items: []v3.OrderBy{{ColumnName: "status", Order: "DESC", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
|
||||
{ColumnName: "route", Order: "DESC", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}},
|
||||
},
|
||||
want: "attributes_string['status'] DESC,`attribute_string_route` DESC",
|
||||
},
|
||||
{
|
||||
name: "ignore order by in table panel",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeTable,
|
||||
items: []v3.OrderBy{{ColumnName: "timestamp", Order: "DESC"}},
|
||||
tags: []v3.AttributeKey{},
|
||||
},
|
||||
want: "value DESC",
|
||||
},
|
||||
{
|
||||
name: "add default order by ts for list panel",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeList,
|
||||
items: []v3.OrderBy{},
|
||||
tags: []v3.AttributeKey{},
|
||||
},
|
||||
want: "timestamp DESC",
|
||||
},
|
||||
{
|
||||
name: "add default order by value for graph panel",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeGraph,
|
||||
items: []v3.OrderBy{},
|
||||
tags: []v3.AttributeKey{},
|
||||
},
|
||||
want: "value DESC",
|
||||
},
|
||||
{
|
||||
name: "don't add default order by for table panel",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeTable,
|
||||
items: []v3.OrderBy{},
|
||||
tags: []v3.AttributeKey{},
|
||||
},
|
||||
want: "value DESC",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := orderByAttributeKeyTags(tt.args.panelType, tt.args.items, tt.args.tags); got != tt.want {
|
||||
t.Errorf("orderByAttributeKeyTags() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildTracesQuery(t *testing.T) {
|
||||
type args struct {
|
||||
start int64
|
||||
end int64
|
||||
step int64
|
||||
mq *v3.BuilderQuery
|
||||
panelType v3.PanelType
|
||||
options v3.QBOptions
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test buildTracesQuery",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeTable,
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
step: 1000,
|
||||
mq: &v3.BuilderQuery{
|
||||
AggregateOperator: v3.AggregateOperatorCount,
|
||||
Filters: &v3.FilterSet{
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{Key: "http.method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
|
||||
Value: 100,
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{{Key: "http.method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
|
||||
OrderBy: []v3.OrderBy{
|
||||
{ColumnName: "http.method", Order: "ASC"}},
|
||||
},
|
||||
},
|
||||
want: "SELECT attributes_string['http.method'] as `http.method`, toFloat64(count()) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
|
||||
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_string['http.method'] = '100' AND mapContains(attributes_string, 'http.method') " +
|
||||
"group by `http.method` order by `http.method` ASC",
|
||||
},
|
||||
{
|
||||
name: "Test buildTracesQuery",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeTable,
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
step: 1000,
|
||||
mq: &v3.BuilderQuery{
|
||||
AggregateOperator: v3.AggregateOperatorCount,
|
||||
Filters: &v3.FilterSet{
|
||||
Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "bytes", Type: v3.AttributeKeyTypeTag, DataType: v3.AttributeKeyDataTypeInt64}, Value: 100, Operator: ">"},
|
||||
{Key: v3.AttributeKey{Key: "service.name", Type: v3.AttributeKeyTypeResource, DataType: v3.AttributeKeyDataTypeString}, Value: "myService", Operator: "="},
|
||||
},
|
||||
},
|
||||
GroupBy: []v3.AttributeKey{{Key: "host", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeResource}},
|
||||
OrderBy: []v3.OrderBy{
|
||||
{ColumnName: "host", Order: "ASC"}},
|
||||
},
|
||||
},
|
||||
want: "SELECT resources_number['host'] as `host`, toFloat64(count()) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
|
||||
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_number['bytes'] > 100 AND " +
|
||||
"(resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND " +
|
||||
"(seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'myService' AND labels like '%service.name%myService%' AND " +
|
||||
"( (simpleJSONHas(labels, 'host') AND labels like '%host%') ))) " +
|
||||
"group by `host` order by `host` ASC",
|
||||
},
|
||||
{
|
||||
name: "test noop list view",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeList,
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
mq: &v3.BuilderQuery{
|
||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
||||
Filters: &v3.FilterSet{},
|
||||
SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}},
|
||||
OrderBy: []v3.OrderBy{{ColumnName: "timestamp", Order: "ASC"}},
|
||||
},
|
||||
},
|
||||
want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
|
||||
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) order by timestamp ASC",
|
||||
},
|
||||
{
|
||||
name: "test noop list view-without ts",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeList,
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
mq: &v3.BuilderQuery{
|
||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
||||
Filters: &v3.FilterSet{},
|
||||
SelectColumns: []v3.AttributeKey{{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}},
|
||||
},
|
||||
},
|
||||
want: "SELECT timestamp as timestamp_datetime, spanID, traceID, name as `name` from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
|
||||
"AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) order by timestamp DESC",
|
||||
},
|
||||
{
|
||||
name: "test noop trace view",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeTrace,
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
mq: &v3.BuilderQuery{
|
||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
||||
Filters: &v3.FilterSet{
|
||||
Items: []v3.FilterItem{
|
||||
{Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="},
|
||||
{Key: v3.AttributeKey{Key: "service.name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource}, Value: "myService", Operator: "="},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "SELECT subQuery.serviceName, subQuery.name, count() AS span_count, subQuery.durationNano, subQuery.traceID AS traceID FROM signoz_traces.distributed_signoz_index_v3 INNER JOIN " +
|
||||
"( SELECT * FROM (SELECT traceID, durationNano, serviceName, name FROM signoz_traces.signoz_index_v3 WHERE parentSpanID = '' AND (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND " +
|
||||
"(ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_string['method'] = 'GET' AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource " +
|
||||
"WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'service.name') = 'myService' AND labels like '%service.name%myService%')) " +
|
||||
"ORDER BY durationNano DESC LIMIT 1 BY traceID LIMIT 100) AS inner_subquery ) AS subQuery ON signoz_traces.distributed_signoz_index_v3.traceID = subQuery.traceID WHERE (timestamp >= '1680066360726210000' AND " +
|
||||
"timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) GROUP BY subQuery.traceID, subQuery.durationNano, subQuery.name, subQuery.serviceName ORDER BY " +
|
||||
"subQuery.durationNano desc LIMIT 1 BY subQuery.traceID;",
|
||||
},
|
||||
{
|
||||
name: "Test order by value with having",
|
||||
args: args{
|
||||
panelType: v3.PanelTypeTable,
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
mq: &v3.BuilderQuery{
|
||||
AggregateOperator: v3.AggregateOperatorCountDistinct,
|
||||
Filters: &v3.FilterSet{},
|
||||
AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
|
||||
OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "ASC"}},
|
||||
Having: []v3.Having{
|
||||
{
|
||||
ColumnName: "name",
|
||||
Operator: ">",
|
||||
Value: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "SELECT toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND " +
|
||||
"(ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) having value > 10 order by value ASC",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := buildTracesQuery(tt.args.start, tt.args.end, tt.args.step, tt.args.mq, tt.args.panelType, tt.args.options)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("buildTracesQuery() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("buildTracesQuery() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareTracesQuery(t *testing.T) {
|
||||
type args struct {
|
||||
start int64
|
||||
end int64
|
||||
panelType v3.PanelType
|
||||
mq *v3.BuilderQuery
|
||||
options v3.QBOptions
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "test with limit - first",
|
||||
args: args{
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
panelType: v3.PanelTypeTable,
|
||||
mq: &v3.BuilderQuery{
|
||||
StepInterval: 60,
|
||||
AggregateOperator: v3.AggregateOperatorCountDistinct,
|
||||
Filters: &v3.FilterSet{},
|
||||
AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
|
||||
GroupBy: []v3.AttributeKey{{Key: "function", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
|
||||
Limit: 10,
|
||||
OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "DESC"}},
|
||||
},
|
||||
options: v3.QBOptions{
|
||||
GraphLimitQtype: constants.FirstQueryGraphLimit,
|
||||
},
|
||||
},
|
||||
want: "SELECT `function` from (SELECT attributes_string['function'] as `function`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 " +
|
||||
"where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND mapContains(attributes_string, 'function') group by `function` order by value DESC) LIMIT 10",
|
||||
},
|
||||
{
|
||||
name: "test with limit - second",
|
||||
args: args{
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
panelType: v3.PanelTypeTable,
|
||||
mq: &v3.BuilderQuery{
|
||||
StepInterval: 60,
|
||||
AggregateOperator: v3.AggregateOperatorCountDistinct,
|
||||
Filters: &v3.FilterSet{},
|
||||
AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
|
||||
GroupBy: []v3.AttributeKey{{Key: "function", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
|
||||
OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "DESC"}},
|
||||
Limit: 10,
|
||||
},
|
||||
options: v3.QBOptions{
|
||||
GraphLimitQtype: constants.SecondQueryGraphLimit,
|
||||
},
|
||||
},
|
||||
want: "SELECT attributes_string['function'] as `function`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 where " +
|
||||
"(timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND mapContains(attributes_string, 'function') AND (`function`) GLOBAL IN (%s) group by `function` order by value DESC",
|
||||
},
|
||||
{
|
||||
name: "test with limit with resources- first",
|
||||
args: args{
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
panelType: v3.PanelTypeTable,
|
||||
mq: &v3.BuilderQuery{
|
||||
StepInterval: 60,
|
||||
AggregateOperator: v3.AggregateOperatorCountDistinct,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{Key: "line", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag},
|
||||
Value: 100,
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
},
|
||||
{
|
||||
Key: v3.AttributeKey{Key: "hostname", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource},
|
||||
Value: "server1",
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
},
|
||||
},
|
||||
},
|
||||
AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{Key: "function", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
|
||||
{Key: "service.name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource},
|
||||
},
|
||||
Limit: 10,
|
||||
OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "DESC"}},
|
||||
},
|
||||
options: v3.QBOptions{
|
||||
GraphLimitQtype: constants.FirstQueryGraphLimit,
|
||||
},
|
||||
},
|
||||
want: "SELECT `function`,`service.name` from (SELECT `attribute_string_function` as `function`, `resource_string_service$$name` as `service.name`, toFloat64(count(distinct(name))) as value " +
|
||||
"from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) " +
|
||||
"AND attributes_number['line'] = 100 AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE " +
|
||||
"(seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) AND simpleJSONExtractString(labels, 'hostname') = 'server1' AND labels like '%hostname%server1%' AND " +
|
||||
"( (simpleJSONHas(labels, 'service.name') AND labels like '%service.name%') ))) group by `function`,`service.name` order by value DESC) LIMIT 10",
|
||||
},
|
||||
{
|
||||
name: "test with limit with resources - second",
|
||||
args: args{
|
||||
start: 1680066360726210000,
|
||||
end: 1680066458000000000,
|
||||
panelType: v3.PanelTypeTable,
|
||||
mq: &v3.BuilderQuery{
|
||||
StepInterval: 60,
|
||||
AggregateOperator: v3.AggregateOperatorCountDistinct,
|
||||
Filters: &v3.FilterSet{
|
||||
Operator: "AND",
|
||||
Items: []v3.FilterItem{
|
||||
{
|
||||
Key: v3.AttributeKey{Key: "line", DataType: v3.AttributeKeyDataTypeInt64, Type: v3.AttributeKeyTypeTag},
|
||||
Value: 100,
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
},
|
||||
{
|
||||
Key: v3.AttributeKey{Key: "hostname", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource},
|
||||
Value: "server1",
|
||||
Operator: v3.FilterOperatorEqual,
|
||||
},
|
||||
},
|
||||
},
|
||||
AggregateAttribute: v3.AttributeKey{Key: "name", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
|
||||
GroupBy: []v3.AttributeKey{
|
||||
{Key: "function", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
|
||||
{Key: "serviceName", IsColumn: true, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
|
||||
},
|
||||
OrderBy: []v3.OrderBy{{ColumnName: "#SIGNOZ_VALUE", Order: "DESC"}},
|
||||
Limit: 10,
|
||||
},
|
||||
options: v3.QBOptions{
|
||||
GraphLimitQtype: constants.SecondQueryGraphLimit,
|
||||
},
|
||||
},
|
||||
want: "SELECT `attribute_string_function` as `function`, serviceName as `serviceName`, toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 " +
|
||||
"where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) AND attributes_number['line'] = 100 " +
|
||||
"AND (resource_fingerprint GLOBAL IN (SELECT fingerprint FROM signoz_traces.distributed_traces_v3_resource WHERE (seen_at_ts_bucket_start >= 1680064560) AND (seen_at_ts_bucket_start <= 1680066458) " +
|
||||
"AND simpleJSONExtractString(labels, 'hostname') = 'server1' AND labels like '%hostname%server1%')) AND (`function`,`serviceName`) GLOBAL IN (%s) group by `function`,`serviceName` order by value DESC",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := PrepareTracesQuery(tt.args.start, tt.args.end, tt.args.panelType, tt.args.mq, tt.args.options)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("PrepareTracesQuery() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("PrepareTracesQuery() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -239,6 +239,8 @@ const (
|
||||
SIGNOZ_TRACE_DBNAME = "signoz_traces"
|
||||
SIGNOZ_SPAN_INDEX_TABLENAME = "distributed_signoz_index_v2"
|
||||
SIGNOZ_SPAN_INDEX_LOCAL_TABLENAME = "signoz_index_v2"
|
||||
SIGNOZ_SPAN_INDEX_V3 = "distributed_signoz_index_v3"
|
||||
SIGNOZ_SPAN_INDEX_V3_LOCAL_TABLENAME = "signoz_index_v3"
|
||||
SIGNOZ_TIMESERIES_v4_LOCAL_TABLENAME = "time_series_v4"
|
||||
SIGNOZ_TIMESERIES_v4_6HRS_LOCAL_TABLENAME = "time_series_v4_6hrs"
|
||||
SIGNOZ_TIMESERIES_v4_1DAY_LOCAL_TABLENAME = "time_series_v4_1day"
|
||||
@ -444,3 +446,147 @@ const MaxFilterSuggestionsExamplesLimit = 10
|
||||
|
||||
var SpanRenderLimitStr = GetOrDefaultEnv("SPAN_RENDER_LIMIT", "2500")
|
||||
var MaxSpansInTraceStr = GetOrDefaultEnv("MAX_SPANS_IN_TRACE", "250000")
|
||||
|
||||
var StaticFieldsTraces = map[string]v3.AttributeKey{
|
||||
"timestamp": {},
|
||||
"traceID": {
|
||||
Key: "traceID",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"spanID": {
|
||||
Key: "spanID",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"parentSpanID": {
|
||||
Key: "parentSpanID",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"name": {
|
||||
Key: "name",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"serviceName": {
|
||||
Key: "serviceName",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"kind": {
|
||||
Key: "kind",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"spanKind": {
|
||||
Key: "spanKind",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"durationNano": {
|
||||
Key: "durationNano",
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
IsColumn: true,
|
||||
},
|
||||
"statusCode": {
|
||||
Key: "statusCode",
|
||||
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||
IsColumn: true,
|
||||
},
|
||||
"hasError": {
|
||||
Key: "hasError",
|
||||
DataType: v3.AttributeKeyDataTypeBool,
|
||||
IsColumn: true,
|
||||
},
|
||||
"statusMessage": {
|
||||
Key: "statusMessage",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"statusCodeString": {
|
||||
Key: "statusCodeString",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"externalHttpMethod": {
|
||||
Key: "externalHttpMethod",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"externalHttpUrl": {
|
||||
Key: "externalHttpUrl",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"dbSystem": {
|
||||
Key: "dbSystem",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"dbName": {
|
||||
Key: "dbName",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"dbOperation": {
|
||||
Key: "dbOperation",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"peerService": {
|
||||
Key: "peerService",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"httpMethod": {
|
||||
Key: "httpMethod",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"httpUrl": {
|
||||
Key: "httpUrl",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"httpRoute": {
|
||||
Key: "httpRoute",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"httpHost": {
|
||||
Key: "httpHost",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"msgSystem": {
|
||||
Key: "msgSystem",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"msgOperation": {
|
||||
Key: "msgOperation",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"rpcSystem": {
|
||||
Key: "rpcSystem",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"rpcService": {
|
||||
Key: "rpcService",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"rpcMethod": {
|
||||
Key: "rpcMethod",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
"responseStatusCode": {
|
||||
Key: "responseStatusCode",
|
||||
DataType: v3.AttributeKeyDataTypeString,
|
||||
IsColumn: true,
|
||||
},
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user