mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-13 05:19:04 +08:00
chore: add enrichment in threshold rule (#5925)
This commit is contained in:
parent
83d01e7a0d
commit
49dd5f2ef7
@ -728,7 +728,7 @@ func (r *ClickHouseReader) GetServiceOverview(ctx context.Context, queryParams *
|
|||||||
return &serviceOverviewItems, nil
|
return &serviceOverviewItems, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildFilterArrayQuery(ctx context.Context, excludeMap map[string]struct{}, params []string, filter string, query *string, args []interface{}) []interface{} {
|
func buildFilterArrayQuery(_ context.Context, excludeMap map[string]struct{}, params []string, filter string, query *string, args []interface{}) []interface{} {
|
||||||
for i, e := range params {
|
for i, e := range params {
|
||||||
filterKey := filter + String(5)
|
filterKey := filter + String(5)
|
||||||
if i == 0 && i == len(params)-1 {
|
if i == 0 && i == len(params)-1 {
|
||||||
@ -1237,7 +1237,7 @@ func String(length int) string {
|
|||||||
return StringWithCharset(length, charset)
|
return StringWithCharset(length, charset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildQueryWithTagParams(ctx context.Context, tags []model.TagQuery) (string, []interface{}, *model.ApiError) {
|
func buildQueryWithTagParams(_ context.Context, tags []model.TagQuery) (string, []interface{}, *model.ApiError) {
|
||||||
query := ""
|
query := ""
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
for _, item := range tags {
|
for _, item := range tags {
|
||||||
@ -1447,7 +1447,7 @@ func (r *ClickHouseReader) GetTagFilters(ctx context.Context, queryParams *model
|
|||||||
return &tagFiltersResult, nil
|
return &tagFiltersResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func excludeTags(ctx context.Context, tags []string) []string {
|
func excludeTags(_ context.Context, tags []string) []string {
|
||||||
excludedTagsMap := map[string]bool{
|
excludedTagsMap := map[string]bool{
|
||||||
"http.code": true,
|
"http.code": true,
|
||||||
"http.route": true,
|
"http.route": true,
|
||||||
@ -2201,7 +2201,7 @@ func (r *ClickHouseReader) SetTTL(ctx context.Context,
|
|||||||
return &model.SetTTLResponseItem{Message: "move ttl has been successfully set up"}, nil
|
return &model.SetTTLResponseItem{Message: "move ttl has been successfully set up"}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClickHouseReader) deleteTtlTransactions(ctx context.Context, numberOfTransactionsStore int) {
|
func (r *ClickHouseReader) deleteTtlTransactions(_ context.Context, numberOfTransactionsStore int) {
|
||||||
_, err := r.localDB.Exec("DELETE FROM ttl_status WHERE transaction_id NOT IN (SELECT distinct transaction_id FROM ttl_status ORDER BY created_at DESC LIMIT ?)", numberOfTransactionsStore)
|
_, err := r.localDB.Exec("DELETE FROM ttl_status WHERE transaction_id NOT IN (SELECT distinct transaction_id FROM ttl_status ORDER BY created_at DESC LIMIT ?)", numberOfTransactionsStore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("Error in processing ttl_status delete sql query", zap.Error(err))
|
zap.L().Error("Error in processing ttl_status delete sql query", zap.Error(err))
|
||||||
@ -2209,7 +2209,7 @@ func (r *ClickHouseReader) deleteTtlTransactions(ctx context.Context, numberOfTr
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkTTLStatusItem checks if ttl_status table has an entry for the given table name
|
// checkTTLStatusItem checks if ttl_status table has an entry for the given table name
|
||||||
func (r *ClickHouseReader) checkTTLStatusItem(ctx context.Context, tableName string) (model.TTLStatusItem, *model.ApiError) {
|
func (r *ClickHouseReader) checkTTLStatusItem(_ context.Context, tableName string) (model.TTLStatusItem, *model.ApiError) {
|
||||||
statusItem := []model.TTLStatusItem{}
|
statusItem := []model.TTLStatusItem{}
|
||||||
|
|
||||||
query := `SELECT id, status, ttl, cold_storage_ttl FROM ttl_status WHERE table_name = ? ORDER BY created_at DESC`
|
query := `SELECT id, status, ttl, cold_storage_ttl FROM ttl_status WHERE table_name = ? ORDER BY created_at DESC`
|
||||||
|
@ -41,6 +41,7 @@ import (
|
|||||||
"go.signoz.io/signoz/pkg/query-service/cache"
|
"go.signoz.io/signoz/pkg/query-service/cache"
|
||||||
"go.signoz.io/signoz/pkg/query-service/common"
|
"go.signoz.io/signoz/pkg/query-service/common"
|
||||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/contextlinks"
|
||||||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
"go.signoz.io/signoz/pkg/query-service/postprocess"
|
"go.signoz.io/signoz/pkg/query-service/postprocess"
|
||||||
|
|
||||||
@ -767,6 +768,48 @@ func (aH *APIHandler) getOverallStateTransitions(w http.ResponseWriter, r *http.
|
|||||||
aH.Respond(w, stateItems)
|
aH.Respond(w, stateItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (aH *APIHandler) metaForLinks(ctx context.Context, rule *rules.GettableRule) ([]v3.FilterItem, []v3.AttributeKey, map[string]v3.AttributeKey) {
|
||||||
|
filterItems := []v3.FilterItem{}
|
||||||
|
groupBy := []v3.AttributeKey{}
|
||||||
|
keys := make(map[string]v3.AttributeKey)
|
||||||
|
|
||||||
|
if rule.AlertType == rules.AlertTypeLogs {
|
||||||
|
logFields, err := aH.reader.GetLogFields(ctx)
|
||||||
|
if err == nil {
|
||||||
|
params := &v3.QueryRangeParamsV3{
|
||||||
|
CompositeQuery: rule.RuleCondition.CompositeQuery,
|
||||||
|
}
|
||||||
|
keys = model.GetLogFieldsV3(ctx, params, logFields)
|
||||||
|
} else {
|
||||||
|
zap.L().Error("failed to get log fields using empty keys; the link might not work as expected", zap.Error(err))
|
||||||
|
}
|
||||||
|
} else if rule.AlertType == rules.AlertTypeTraces {
|
||||||
|
traceFields, err := aH.reader.GetSpanAttributeKeys(ctx)
|
||||||
|
if err == nil {
|
||||||
|
keys = traceFields
|
||||||
|
} else {
|
||||||
|
zap.L().Error("failed to get span attributes using empty keys; the link might not work as expected", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule.AlertType == rules.AlertTypeLogs || rule.AlertType == rules.AlertTypeTraces {
|
||||||
|
if rule.RuleCondition.CompositeQuery != nil {
|
||||||
|
if rule.RuleCondition.QueryType() == v3.QueryTypeBuilder {
|
||||||
|
selectedQuery := rule.RuleCondition.GetSelectedQueryName()
|
||||||
|
if rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery] != nil &&
|
||||||
|
rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery].Filters != nil {
|
||||||
|
filterItems = rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery].Filters.Items
|
||||||
|
}
|
||||||
|
if rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery] != nil &&
|
||||||
|
rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery].GroupBy != nil {
|
||||||
|
groupBy = rule.RuleCondition.CompositeQuery.BuilderQueries[selectedQuery].GroupBy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filterItems, groupBy, keys
|
||||||
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) getRuleStateHistory(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) getRuleStateHistory(w http.ResponseWriter, r *http.Request) {
|
||||||
ruleID := mux.Vars(r)["id"]
|
ruleID := mux.Vars(r)["id"]
|
||||||
params := model.QueryRuleStateHistory{}
|
params := model.QueryRuleStateHistory{}
|
||||||
@ -794,24 +837,18 @@ func (aH *APIHandler) getRuleStateHistory(w http.ResponseWriter, r *http.Request
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filterItems := []v3.FilterItem{}
|
filterItems, groupBy, keys := aH.metaForLinks(r.Context(), rule)
|
||||||
if rule.AlertType == rules.AlertTypeLogs || rule.AlertType == rules.AlertTypeTraces {
|
newFilters := contextlinks.PrepareFilters(lbls, filterItems, groupBy, keys)
|
||||||
if rule.RuleCondition.CompositeQuery != nil {
|
end := time.Unix(res.Items[idx].UnixMilli/1000, 0)
|
||||||
if rule.RuleCondition.QueryType() == v3.QueryTypeBuilder {
|
// why are we subtracting 3 minutes?
|
||||||
for _, query := range rule.RuleCondition.CompositeQuery.BuilderQueries {
|
// the query range is calculated based on the rule's evalWindow and evalDelay
|
||||||
if query.Filters != nil && len(query.Filters.Items) > 0 {
|
// alerts have 2 minutes delay built in, so we need to subtract that from the start time
|
||||||
filterItems = append(filterItems, query.Filters.Items...)
|
// to get the correct query range
|
||||||
}
|
start := end.Add(-time.Duration(rule.EvalWindow)).Add(-3 * time.Minute)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newFilters := common.PrepareFilters(lbls, filterItems)
|
|
||||||
ts := time.Unix(res.Items[idx].UnixMilli/1000, 0)
|
|
||||||
if rule.AlertType == rules.AlertTypeLogs {
|
if rule.AlertType == rules.AlertTypeLogs {
|
||||||
res.Items[idx].RelatedLogsLink = common.PrepareLinksToLogs(ts, newFilters)
|
res.Items[idx].RelatedLogsLink = contextlinks.PrepareLinksToLogs(start, end, newFilters)
|
||||||
} else if rule.AlertType == rules.AlertTypeTraces {
|
} else if rule.AlertType == rules.AlertTypeTraces {
|
||||||
res.Items[idx].RelatedTracesLink = common.PrepareLinksToTraces(ts, newFilters)
|
res.Items[idx].RelatedTracesLink = contextlinks.PrepareLinksToTraces(start, end, newFilters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -842,12 +879,14 @@ func (aH *APIHandler) getRuleStateHistoryTopContributors(w http.ResponseWriter,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ts := time.Unix(params.End/1000, 0)
|
filterItems, groupBy, keys := aH.metaForLinks(r.Context(), rule)
|
||||||
filters := common.PrepareFilters(lbls, nil)
|
newFilters := contextlinks.PrepareFilters(lbls, filterItems, groupBy, keys)
|
||||||
|
end := time.Unix(params.End/1000, 0)
|
||||||
|
start := time.Unix(params.Start/1000, 0)
|
||||||
if rule.AlertType == rules.AlertTypeLogs {
|
if rule.AlertType == rules.AlertTypeLogs {
|
||||||
res[idx].RelatedLogsLink = common.PrepareLinksToLogs(ts, filters)
|
res[idx].RelatedLogsLink = contextlinks.PrepareLinksToLogs(start, end, newFilters)
|
||||||
} else if rule.AlertType == rules.AlertTypeTraces {
|
} else if rule.AlertType == rules.AlertTypeTraces {
|
||||||
res[idx].RelatedTracesLink = common.PrepareLinksToTraces(ts, filters)
|
res[idx].RelatedTracesLink = contextlinks.PrepareLinksToTraces(start, end, newFilters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1051,23 +1090,6 @@ func (aH *APIHandler) getDashboard(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) saveAndReturn(w http.ResponseWriter, r *http.Request, signozDashboard model.DashboardData) {
|
|
||||||
toSave := make(map[string]interface{})
|
|
||||||
toSave["title"] = signozDashboard.Title
|
|
||||||
toSave["description"] = signozDashboard.Description
|
|
||||||
toSave["tags"] = signozDashboard.Tags
|
|
||||||
toSave["layout"] = signozDashboard.Layout
|
|
||||||
toSave["widgets"] = signozDashboard.Widgets
|
|
||||||
toSave["variables"] = signozDashboard.Variables
|
|
||||||
|
|
||||||
dashboard, apiError := dashboards.CreateDashboard(r.Context(), toSave, aH.featureFlags)
|
|
||||||
if apiError != nil {
|
|
||||||
RespondError(w, apiError, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aH.Respond(w, dashboard)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) createDashboards(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) createDashboards(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
var postData map[string]interface{}
|
var postData map[string]interface{}
|
||||||
@ -3527,55 +3549,6 @@ func (aH *APIHandler) autoCompleteAttributeValues(w http.ResponseWriter, r *http
|
|||||||
aH.Respond(w, response)
|
aH.Respond(w, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) getLogFieldsV3(ctx context.Context, queryRangeParams *v3.QueryRangeParamsV3) (map[string]v3.AttributeKey, error) {
|
|
||||||
data := map[string]v3.AttributeKey{}
|
|
||||||
for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
|
|
||||||
if query.DataSource == v3.DataSourceLogs {
|
|
||||||
fields, apiError := aH.reader.GetLogFields(ctx)
|
|
||||||
if apiError != nil {
|
|
||||||
return nil, apiError.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// top level fields meta will always be present in the frontend. (can be support for that as enchancement)
|
|
||||||
getType := func(t string) (v3.AttributeKeyType, bool) {
|
|
||||||
if t == "attributes" {
|
|
||||||
return v3.AttributeKeyTypeTag, false
|
|
||||||
} else if t == "resources" {
|
|
||||||
return v3.AttributeKeyTypeResource, false
|
|
||||||
}
|
|
||||||
return "", true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, selectedField := range fields.Selected {
|
|
||||||
fieldType, pass := getType(selectedField.Type)
|
|
||||||
if pass {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
data[selectedField.Name] = v3.AttributeKey{
|
|
||||||
Key: selectedField.Name,
|
|
||||||
Type: fieldType,
|
|
||||||
DataType: v3.AttributeKeyDataType(strings.ToLower(selectedField.DataType)),
|
|
||||||
IsColumn: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, interestingField := range fields.Interesting {
|
|
||||||
fieldType, pass := getType(interestingField.Type)
|
|
||||||
if pass {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
data[interestingField.Name] = v3.AttributeKey{
|
|
||||||
Key: interestingField.Name,
|
|
||||||
Type: fieldType,
|
|
||||||
DataType: v3.AttributeKeyDataType(strings.ToLower(interestingField.DataType)),
|
|
||||||
IsColumn: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) getSpanKeysV3(ctx context.Context, queryRangeParams *v3.QueryRangeParamsV3) (map[string]v3.AttributeKey, error) {
|
func (aH *APIHandler) getSpanKeysV3(ctx context.Context, queryRangeParams *v3.QueryRangeParamsV3) (map[string]v3.AttributeKey, error) {
|
||||||
data := map[string]v3.AttributeKey{}
|
data := map[string]v3.AttributeKey{}
|
||||||
for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
|
for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
|
||||||
@ -3617,14 +3590,14 @@ func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.Que
|
|||||||
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
||||||
// check if any enrichment is required for logs if yes then enrich them
|
// check if any enrichment is required for logs if yes then enrich them
|
||||||
if logsv3.EnrichmentRequired(queryRangeParams) {
|
if logsv3.EnrichmentRequired(queryRangeParams) {
|
||||||
// get the fields if any logs query is present
|
logsFields, err := aH.reader.GetLogFields(ctx)
|
||||||
var fields map[string]v3.AttributeKey
|
|
||||||
fields, err = aH.getLogFieldsV3(ctx, queryRangeParams)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
RespondError(w, apiErrObj, errQuriesByName)
|
RespondError(w, apiErrObj, errQuriesByName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// get the fields if any logs query is present
|
||||||
|
fields := model.GetLogFieldsV3(ctx, queryRangeParams, logsFields)
|
||||||
logsv3.Enrich(queryRangeParams, fields)
|
logsv3.Enrich(queryRangeParams, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3666,6 +3639,7 @@ func (aH *APIHandler) queryRangeV3(ctx context.Context, queryRangeParams *v3.Que
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Adding queryId to the context signals clickhouse queries to report progress
|
// Adding queryId to the context signals clickhouse queries to report progress
|
||||||
|
//lint:ignore SA1029 ignore for now
|
||||||
ctx = context.WithValue(ctx, "queryId", queryIdHeader)
|
ctx = context.WithValue(ctx, "queryId", queryIdHeader)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -3918,13 +3892,13 @@ func (aH *APIHandler) liveTailLogsV2(w http.ResponseWriter, r *http.Request) {
|
|||||||
// check if any enrichment is required for logs if yes then enrich them
|
// check if any enrichment is required for logs if yes then enrich them
|
||||||
if logsv3.EnrichmentRequired(queryRangeParams) {
|
if logsv3.EnrichmentRequired(queryRangeParams) {
|
||||||
// get the fields if any logs query is present
|
// get the fields if any logs query is present
|
||||||
var fields map[string]v3.AttributeKey
|
logsFields, err := aH.reader.GetLogFields(r.Context())
|
||||||
fields, err = aH.getLogFieldsV3(r.Context(), queryRangeParams)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
RespondError(w, apiErrObj, nil)
|
RespondError(w, apiErrObj, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
fields := model.GetLogFieldsV3(r.Context(), queryRangeParams, logsFields)
|
||||||
logsv3.Enrich(queryRangeParams, fields)
|
logsv3.Enrich(queryRangeParams, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4003,14 +3977,14 @@ func (aH *APIHandler) liveTailLogs(w http.ResponseWriter, r *http.Request) {
|
|||||||
case v3.QueryTypeBuilder:
|
case v3.QueryTypeBuilder:
|
||||||
// check if any enrichment is required for logs if yes then enrich them
|
// check if any enrichment is required for logs if yes then enrich them
|
||||||
if logsv3.EnrichmentRequired(queryRangeParams) {
|
if logsv3.EnrichmentRequired(queryRangeParams) {
|
||||||
// get the fields if any logs query is present
|
logsFields, err := aH.reader.GetLogFields(r.Context())
|
||||||
var fields map[string]v3.AttributeKey
|
|
||||||
fields, err = aH.getLogFieldsV3(r.Context(), queryRangeParams)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
RespondError(w, apiErrObj, nil)
|
RespondError(w, apiErrObj, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// get the fields if any logs query is present
|
||||||
|
fields := model.GetLogFieldsV3(r.Context(), queryRangeParams, logsFields)
|
||||||
logsv3.Enrich(queryRangeParams, fields)
|
logsv3.Enrich(queryRangeParams, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4087,13 +4061,13 @@ func (aH *APIHandler) queryRangeV4(ctx context.Context, queryRangeParams *v3.Que
|
|||||||
// check if any enrichment is required for logs if yes then enrich them
|
// check if any enrichment is required for logs if yes then enrich them
|
||||||
if logsv3.EnrichmentRequired(queryRangeParams) {
|
if logsv3.EnrichmentRequired(queryRangeParams) {
|
||||||
// get the fields if any logs query is present
|
// get the fields if any logs query is present
|
||||||
var fields map[string]v3.AttributeKey
|
logsFields, err := aH.reader.GetLogFields(r.Context())
|
||||||
fields, err = aH.getLogFieldsV3(ctx, queryRangeParams)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
RespondError(w, apiErrObj, errQuriesByName)
|
RespondError(w, apiErrObj, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
fields := model.GetLogFieldsV3(r.Context(), queryRangeParams, logsFields)
|
||||||
logsv3.Enrich(queryRangeParams, fields)
|
logsv3.Enrich(queryRangeParams, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,11 +94,11 @@ func Enrich(params *v3.QueryRangeParamsV3, fields map[string]v3.AttributeKey) {
|
|||||||
if query.Expression != queryName && query.DataSource != v3.DataSourceLogs {
|
if query.Expression != queryName && query.DataSource != v3.DataSourceLogs {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
enrichLogsQuery(query, fields)
|
EnrichLogsQuery(query, fields)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func enrichLogsQuery(query *v3.BuilderQuery, fields map[string]v3.AttributeKey) error {
|
func EnrichLogsQuery(query *v3.BuilderQuery, fields map[string]v3.AttributeKey) error {
|
||||||
// enrich aggregation attribute
|
// enrich aggregation attribute
|
||||||
if query.AggregateAttribute.Key != "" {
|
if query.AggregateAttribute.Key != "" {
|
||||||
query.AggregateAttribute = enrichFieldWithMetadata(query.AggregateAttribute, fields)
|
query.AggregateAttribute = enrichFieldWithMetadata(query.AggregateAttribute, fields)
|
||||||
|
@ -545,6 +545,13 @@ func Enrich(params *v3.QueryRangeParamsV3, keys map[string]v3.AttributeKey) {
|
|||||||
if params.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
if params.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
||||||
for _, query := range params.CompositeQuery.BuilderQueries {
|
for _, query := range params.CompositeQuery.BuilderQueries {
|
||||||
if query.DataSource == v3.DataSourceTraces {
|
if query.DataSource == v3.DataSourceTraces {
|
||||||
|
EnrichTracesQuery(query, keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnrichTracesQuery(query *v3.BuilderQuery, keys map[string]v3.AttributeKey) {
|
||||||
// enrich aggregate attribute
|
// enrich aggregate attribute
|
||||||
query.AggregateAttribute = enrichKeyWithMetadata(query.AggregateAttribute, keys)
|
query.AggregateAttribute = enrichKeyWithMetadata(query.AggregateAttribute, keys)
|
||||||
// enrich filter items
|
// enrich filter items
|
||||||
@ -563,7 +570,4 @@ func Enrich(params *v3.QueryRangeParamsV3, keys map[string]v3.AttributeKey) {
|
|||||||
for idx, selectColumn := range query.SelectColumns {
|
for idx, selectColumn := range query.SelectColumns {
|
||||||
query.SelectColumns[idx] = enrichKeyWithMetadata(selectColumn, keys)
|
query.SelectColumns[idx] = enrichKeyWithMetadata(selectColumn, keys)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,39 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var TracesListViewDefaultSelectedColumns = []v3.AttributeKey{
|
||||||
|
{
|
||||||
|
Key: "serviceName",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
IsColumn: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "name",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
IsColumn: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "durationNano",
|
||||||
|
DataType: v3.AttributeKeyDataTypeArrayFloat64,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
IsColumn: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "httpMethod",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
IsColumn: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "responseStatusCode",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
IsColumn: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// check if traceId filter is used in traces query and return the list of traceIds
|
// check if traceId filter is used in traces query and return the list of traceIds
|
||||||
func TraceIdFilterUsedWithEqual(params *v3.QueryRangeParamsV3) (bool, []string) {
|
func TraceIdFilterUsedWithEqual(params *v3.QueryRangeParamsV3) (bool, []string) {
|
||||||
compositeQuery := params.CompositeQuery
|
compositeQuery := params.CompositeQuery
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||||
@ -73,183 +70,3 @@ func LCMList(nums []int64) int64 {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(srikanthccv): move the custom function in threshold_rule.go to here
|
|
||||||
func PrepareLinksToTraces(ts time.Time, filterItems []v3.FilterItem) string {
|
|
||||||
|
|
||||||
start := ts.Add(-time.Minute * 15)
|
|
||||||
end := ts.Add(time.Minute * 15)
|
|
||||||
|
|
||||||
// Traces list view expects time in nanoseconds
|
|
||||||
tr := v3.URLShareableTimeRange{
|
|
||||||
Start: start.UnixNano(),
|
|
||||||
End: end.UnixNano(),
|
|
||||||
PageSize: 100,
|
|
||||||
}
|
|
||||||
|
|
||||||
options := v3.URLShareableOptions{
|
|
||||||
MaxLines: 2,
|
|
||||||
Format: "list",
|
|
||||||
SelectColumns: constants.TracesListViewDefaultSelectedColumns,
|
|
||||||
}
|
|
||||||
|
|
||||||
period, _ := json.Marshal(tr)
|
|
||||||
urlEncodedTimeRange := url.QueryEscape(string(period))
|
|
||||||
|
|
||||||
urlData := v3.URLShareableCompositeQuery{
|
|
||||||
QueryType: string(v3.QueryTypeBuilder),
|
|
||||||
Builder: v3.URLShareableBuilderQuery{
|
|
||||||
QueryData: []v3.BuilderQuery{
|
|
||||||
{
|
|
||||||
DataSource: v3.DataSourceTraces,
|
|
||||||
QueryName: "A",
|
|
||||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
|
||||||
AggregateAttribute: v3.AttributeKey{},
|
|
||||||
Filters: &v3.FilterSet{
|
|
||||||
Items: filterItems,
|
|
||||||
Operator: "AND",
|
|
||||||
},
|
|
||||||
Expression: "A",
|
|
||||||
Disabled: false,
|
|
||||||
Having: []v3.Having{},
|
|
||||||
StepInterval: 60,
|
|
||||||
OrderBy: []v3.OrderBy{
|
|
||||||
{
|
|
||||||
ColumnName: "timestamp",
|
|
||||||
Order: "desc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
QueryFormulas: make([]string, 0),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
data, _ := json.Marshal(urlData)
|
|
||||||
compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
|
|
||||||
|
|
||||||
optionsData, _ := json.Marshal(options)
|
|
||||||
urlEncodedOptions := url.QueryEscape(string(optionsData))
|
|
||||||
|
|
||||||
return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PrepareLinksToLogs(ts time.Time, filterItems []v3.FilterItem) string {
|
|
||||||
start := ts.Add(-time.Minute * 15)
|
|
||||||
end := ts.Add(time.Minute * 15)
|
|
||||||
|
|
||||||
// Logs list view expects time in milliseconds
|
|
||||||
// Logs list view expects time in milliseconds
|
|
||||||
tr := v3.URLShareableTimeRange{
|
|
||||||
Start: start.UnixMilli(),
|
|
||||||
End: end.UnixMilli(),
|
|
||||||
PageSize: 100,
|
|
||||||
}
|
|
||||||
|
|
||||||
options := v3.URLShareableOptions{
|
|
||||||
MaxLines: 2,
|
|
||||||
Format: "list",
|
|
||||||
SelectColumns: []v3.AttributeKey{},
|
|
||||||
}
|
|
||||||
|
|
||||||
period, _ := json.Marshal(tr)
|
|
||||||
urlEncodedTimeRange := url.QueryEscape(string(period))
|
|
||||||
|
|
||||||
urlData := v3.URLShareableCompositeQuery{
|
|
||||||
QueryType: string(v3.QueryTypeBuilder),
|
|
||||||
Builder: v3.URLShareableBuilderQuery{
|
|
||||||
QueryData: []v3.BuilderQuery{
|
|
||||||
{
|
|
||||||
DataSource: v3.DataSourceLogs,
|
|
||||||
QueryName: "A",
|
|
||||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
|
||||||
AggregateAttribute: v3.AttributeKey{},
|
|
||||||
Filters: &v3.FilterSet{
|
|
||||||
Items: filterItems,
|
|
||||||
Operator: "AND",
|
|
||||||
},
|
|
||||||
Expression: "A",
|
|
||||||
Disabled: false,
|
|
||||||
Having: []v3.Having{},
|
|
||||||
StepInterval: 60,
|
|
||||||
OrderBy: []v3.OrderBy{
|
|
||||||
{
|
|
||||||
ColumnName: "timestamp",
|
|
||||||
Order: "desc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
QueryFormulas: make([]string, 0),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
data, _ := json.Marshal(urlData)
|
|
||||||
compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
|
|
||||||
|
|
||||||
optionsData, _ := json.Marshal(options)
|
|
||||||
urlEncodedOptions := url.QueryEscape(string(optionsData))
|
|
||||||
|
|
||||||
return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following function is used to prepare the where clause for the query
|
|
||||||
// `lbls` contains the key value pairs of the labels from the result of the query
|
|
||||||
// We iterate over the where clause and replace the labels with the actual values
|
|
||||||
// There are two cases:
|
|
||||||
// 1. The label is present in the where clause
|
|
||||||
// 2. The label is not present in the where clause
|
|
||||||
//
|
|
||||||
// Example for case 2:
|
|
||||||
// Latency by serviceName without any filter
|
|
||||||
// In this case, for each service with latency > threshold we send a notification
|
|
||||||
// The expectation will be that clicking on the related traces for service A, will
|
|
||||||
// take us to the traces page with the filter serviceName=A
|
|
||||||
// So for all the missing labels in the where clause, we add them as key = value
|
|
||||||
//
|
|
||||||
// Example for case 1:
|
|
||||||
// Severity text IN (WARN, ERROR)
|
|
||||||
// In this case, the Severity text will appear in the `lbls` if it were part of the group
|
|
||||||
// by clause, in which case we replace it with the actual value for the notification
|
|
||||||
// i.e Severity text = WARN
|
|
||||||
// If the Severity text is not part of the group by clause, then we add it as it is
|
|
||||||
func PrepareFilters(labels map[string]string, filters []v3.FilterItem) []v3.FilterItem {
|
|
||||||
var filterItems []v3.FilterItem
|
|
||||||
|
|
||||||
added := make(map[string]struct{})
|
|
||||||
|
|
||||||
for _, item := range filters {
|
|
||||||
exists := false
|
|
||||||
for key, value := range labels {
|
|
||||||
if item.Key.Key == key {
|
|
||||||
// if the label is present in the where clause, replace it with key = value
|
|
||||||
filterItems = append(filterItems, v3.FilterItem{
|
|
||||||
Key: item.Key,
|
|
||||||
Operator: v3.FilterOperatorEqual,
|
|
||||||
Value: value,
|
|
||||||
})
|
|
||||||
exists = true
|
|
||||||
added[key] = struct{}{}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
// if the label is not present in the where clause, add it as it is
|
|
||||||
filterItems = append(filterItems, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the labels which are not present in the where clause
|
|
||||||
for key, value := range labels {
|
|
||||||
if _, ok := added[key]; !ok {
|
|
||||||
filterItems = append(filterItems, v3.FilterItem{
|
|
||||||
Key: v3.AttributeKey{Key: key},
|
|
||||||
Operator: v3.FilterOperatorEqual,
|
|
||||||
Value: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filterItems
|
|
||||||
}
|
|
||||||
|
@ -401,39 +401,6 @@ const TIMESTAMP = "timestamp"
|
|||||||
const FirstQueryGraphLimit = "first_query_graph_limit"
|
const FirstQueryGraphLimit = "first_query_graph_limit"
|
||||||
const SecondQueryGraphLimit = "second_query_graph_limit"
|
const SecondQueryGraphLimit = "second_query_graph_limit"
|
||||||
|
|
||||||
var TracesListViewDefaultSelectedColumns = []v3.AttributeKey{
|
|
||||||
{
|
|
||||||
Key: "serviceName",
|
|
||||||
DataType: v3.AttributeKeyDataTypeString,
|
|
||||||
Type: v3.AttributeKeyTypeTag,
|
|
||||||
IsColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "name",
|
|
||||||
DataType: v3.AttributeKeyDataTypeString,
|
|
||||||
Type: v3.AttributeKeyTypeTag,
|
|
||||||
IsColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "durationNano",
|
|
||||||
DataType: v3.AttributeKeyDataTypeArrayFloat64,
|
|
||||||
Type: v3.AttributeKeyTypeTag,
|
|
||||||
IsColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "httpMethod",
|
|
||||||
DataType: v3.AttributeKeyDataTypeString,
|
|
||||||
Type: v3.AttributeKeyTypeTag,
|
|
||||||
IsColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "responseStatusCode",
|
|
||||||
DataType: v3.AttributeKeyDataTypeString,
|
|
||||||
Type: v3.AttributeKeyTypeTag,
|
|
||||||
IsColumn: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const DefaultFilterSuggestionsAttributesLimit = 50
|
const DefaultFilterSuggestionsAttributesLimit = 50
|
||||||
const MaxFilterSuggestionsAttributesLimit = 100
|
const MaxFilterSuggestionsAttributesLimit = 100
|
||||||
const DefaultFilterSuggestionsExamplesLimit = 2
|
const DefaultFilterSuggestionsExamplesLimit = 2
|
||||||
|
203
pkg/query-service/contextlinks/links.go
Normal file
203
pkg/query-service/contextlinks/links.go
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
package contextlinks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
tracesV3 "go.signoz.io/signoz/pkg/query-service/app/traces/v3"
|
||||||
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PrepareLinksToTraces(start, end time.Time, filterItems []v3.FilterItem) string {
|
||||||
|
|
||||||
|
// Traces list view expects time in nanoseconds
|
||||||
|
tr := v3.URLShareableTimeRange{
|
||||||
|
Start: start.UnixNano(),
|
||||||
|
End: end.UnixNano(),
|
||||||
|
PageSize: 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
options := v3.URLShareableOptions{
|
||||||
|
MaxLines: 2,
|
||||||
|
Format: "list",
|
||||||
|
SelectColumns: tracesV3.TracesListViewDefaultSelectedColumns,
|
||||||
|
}
|
||||||
|
|
||||||
|
period, _ := json.Marshal(tr)
|
||||||
|
urlEncodedTimeRange := url.QueryEscape(string(period))
|
||||||
|
|
||||||
|
builderQuery := v3.BuilderQuery{
|
||||||
|
DataSource: v3.DataSourceTraces,
|
||||||
|
QueryName: "A",
|
||||||
|
AggregateOperator: v3.AggregateOperatorNoOp,
|
||||||
|
AggregateAttribute: v3.AttributeKey{},
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Items: filterItems,
|
||||||
|
Operator: "AND",
|
||||||
|
},
|
||||||
|
Expression: "A",
|
||||||
|
Disabled: false,
|
||||||
|
Having: []v3.Having{},
|
||||||
|
StepInterval: 60,
|
||||||
|
OrderBy: []v3.OrderBy{
|
||||||
|
{
|
||||||
|
ColumnName: "timestamp",
|
||||||
|
Order: "desc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
urlData := v3.URLShareableCompositeQuery{
|
||||||
|
QueryType: string(v3.QueryTypeBuilder),
|
||||||
|
Builder: v3.URLShareableBuilderQuery{
|
||||||
|
QueryData: []v3.BuilderQuery{
|
||||||
|
builderQuery,
|
||||||
|
},
|
||||||
|
QueryFormulas: make([]string, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _ := json.Marshal(urlData)
|
||||||
|
compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
|
||||||
|
|
||||||
|
optionsData, _ := json.Marshal(options)
|
||||||
|
urlEncodedOptions := url.QueryEscape(string(optionsData))
|
||||||
|
|
||||||
|
return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrepareLinksToLogs(start, end time.Time, filterItems []v3.FilterItem) string {
|
||||||
|
|
||||||
|
// Logs list view expects time in milliseconds
|
||||||
|
tr := v3.URLShareableTimeRange{
|
||||||
|
Start: start.UnixMilli(),
|
||||||
|
End: end.UnixMilli(),
|
||||||
|
PageSize: 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
options := v3.URLShareableOptions{
|
||||||
|
MaxLines: 2,
|
||||||
|
Format: "list",
|
||||||
|
SelectColumns: []v3.AttributeKey{},
|
||||||
|
}
|
||||||
|
|
||||||
|
period, _ := json.Marshal(tr)
|
||||||
|
urlEncodedTimeRange := url.QueryEscape(string(period))
|
||||||
|
|
||||||
|
builderQuery := v3.BuilderQuery{
|
||||||
|
DataSource: v3.DataSourceLogs,
|
||||||
|
QueryName: "A",
|
||||||
|
AggregateOperator: v3.AggregateOperatorNoOp,
|
||||||
|
AggregateAttribute: v3.AttributeKey{},
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Items: filterItems,
|
||||||
|
Operator: "AND",
|
||||||
|
},
|
||||||
|
Expression: "A",
|
||||||
|
Disabled: false,
|
||||||
|
Having: []v3.Having{},
|
||||||
|
StepInterval: 60,
|
||||||
|
OrderBy: []v3.OrderBy{
|
||||||
|
{
|
||||||
|
ColumnName: "timestamp",
|
||||||
|
Order: "desc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
urlData := v3.URLShareableCompositeQuery{
|
||||||
|
QueryType: string(v3.QueryTypeBuilder),
|
||||||
|
Builder: v3.URLShareableBuilderQuery{
|
||||||
|
QueryData: []v3.BuilderQuery{
|
||||||
|
builderQuery,
|
||||||
|
},
|
||||||
|
QueryFormulas: make([]string, 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _ := json.Marshal(urlData)
|
||||||
|
compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
|
||||||
|
|
||||||
|
optionsData, _ := json.Marshal(options)
|
||||||
|
urlEncodedOptions := url.QueryEscape(string(optionsData))
|
||||||
|
|
||||||
|
return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following function is used to prepare the where clause for the query
|
||||||
|
// `lbls` contains the key value pairs of the labels from the result of the query
|
||||||
|
// We iterate over the where clause and replace the labels with the actual values
|
||||||
|
// There are two cases:
|
||||||
|
// 1. The label is present in the where clause
|
||||||
|
// 2. The label is not present in the where clause
|
||||||
|
//
|
||||||
|
// Example for case 2:
|
||||||
|
// Latency by serviceName without any filter
|
||||||
|
// In this case, for each service with latency > threshold we send a notification
|
||||||
|
// The expectation will be that clicking on the related traces for service A, will
|
||||||
|
// take us to the traces page with the filter serviceName=A
|
||||||
|
// So for all the missing labels in the where clause, we add them as key = value
|
||||||
|
//
|
||||||
|
// Example for case 1:
|
||||||
|
// Severity text IN (WARN, ERROR)
|
||||||
|
// In this case, the Severity text will appear in the `lbls` if it were part of the group
|
||||||
|
// by clause, in which case we replace it with the actual value for the notification
|
||||||
|
// i.e Severity text = WARN
|
||||||
|
// If the Severity text is not part of the group by clause, then we add it as it is
|
||||||
|
func PrepareFilters(labels map[string]string, whereClauseItems []v3.FilterItem, groupByItems []v3.AttributeKey, keys map[string]v3.AttributeKey) []v3.FilterItem {
|
||||||
|
var filterItems []v3.FilterItem
|
||||||
|
|
||||||
|
added := make(map[string]struct{})
|
||||||
|
|
||||||
|
for _, item := range whereClauseItems {
|
||||||
|
exists := false
|
||||||
|
for key, value := range labels {
|
||||||
|
if item.Key.Key == key {
|
||||||
|
// if the label is present in the where clause, replace it with key = value
|
||||||
|
filterItems = append(filterItems, v3.FilterItem{
|
||||||
|
Key: item.Key,
|
||||||
|
Operator: v3.FilterOperatorEqual,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
exists = true
|
||||||
|
added[key] = struct{}{}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
// if there is no label for the filter item, add it as it is
|
||||||
|
filterItems = append(filterItems, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are labels which are not part of the where clause, but
|
||||||
|
// exist in the result, then they could be part of the group by clause
|
||||||
|
for key, value := range labels {
|
||||||
|
if _, ok := added[key]; !ok {
|
||||||
|
// start by taking the attribute key from the keys map, if not present, create a new one
|
||||||
|
attributeKey, ok := keys[key]
|
||||||
|
if !ok {
|
||||||
|
attributeKey = v3.AttributeKey{Key: key}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there is a group by item with the same key, use that instead
|
||||||
|
for _, groupByItem := range groupByItems {
|
||||||
|
if groupByItem.Key == key {
|
||||||
|
attributeKey = groupByItem
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterItems = append(filterItems, v3.FilterItem{
|
||||||
|
Key: attributeKey,
|
||||||
|
Operator: v3.FilterOperatorEqual,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterItems
|
||||||
|
}
|
@ -1,5 +1,12 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
)
|
||||||
|
|
||||||
type LogsLiveTailClientV2 struct {
|
type LogsLiveTailClientV2 struct {
|
||||||
Name string
|
Name string
|
||||||
Logs chan *SignozLogV2
|
Logs chan *SignozLogV2
|
||||||
@ -21,3 +28,48 @@ type QueryProgress struct {
|
|||||||
|
|
||||||
ElapsedMs uint64 `json:"elapsed_ms"`
|
ElapsedMs uint64 `json:"elapsed_ms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetLogFieldsV3(ctx context.Context, queryRangeParams *v3.QueryRangeParamsV3, fields *GetFieldsResponse) map[string]v3.AttributeKey {
|
||||||
|
data := map[string]v3.AttributeKey{}
|
||||||
|
for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
|
||||||
|
if query.DataSource == v3.DataSourceLogs {
|
||||||
|
|
||||||
|
// top level fields meta will always be present in the frontend. (can be support for that as enchancement)
|
||||||
|
getType := func(t string) (v3.AttributeKeyType, bool) {
|
||||||
|
if t == "attributes" {
|
||||||
|
return v3.AttributeKeyTypeTag, false
|
||||||
|
} else if t == "resources" {
|
||||||
|
return v3.AttributeKeyTypeResource, false
|
||||||
|
}
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, selectedField := range fields.Selected {
|
||||||
|
fieldType, pass := getType(selectedField.Type)
|
||||||
|
if pass {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data[selectedField.Name] = v3.AttributeKey{
|
||||||
|
Key: selectedField.Name,
|
||||||
|
Type: fieldType,
|
||||||
|
DataType: v3.AttributeKeyDataType(strings.ToLower(selectedField.DataType)),
|
||||||
|
IsColumn: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, interestingField := range fields.Interesting {
|
||||||
|
fieldType, pass := getType(interestingField.Type)
|
||||||
|
if pass {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data[interestingField.Name] = v3.AttributeKey{
|
||||||
|
Key: interestingField.Name,
|
||||||
|
Type: fieldType,
|
||||||
|
DataType: v3.AttributeKeyDataType(strings.ToLower(interestingField.DataType)),
|
||||||
|
IsColumn: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -124,6 +125,47 @@ type RuleCondition struct {
|
|||||||
SelectedQuery string `json:"selectedQueryName,omitempty"`
|
SelectedQuery string `json:"selectedQueryName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rc *RuleCondition) GetSelectedQueryName() string {
|
||||||
|
if rc != nil {
|
||||||
|
if rc.SelectedQuery != "" {
|
||||||
|
return rc.SelectedQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
queryNames := map[string]struct{}{}
|
||||||
|
|
||||||
|
if rc.CompositeQuery != nil {
|
||||||
|
if rc.QueryType() == v3.QueryTypeBuilder {
|
||||||
|
for name := range rc.CompositeQuery.BuilderQueries {
|
||||||
|
queryNames[name] = struct{}{}
|
||||||
|
}
|
||||||
|
} else if rc.QueryType() == v3.QueryTypeClickHouseSQL {
|
||||||
|
for name := range rc.CompositeQuery.ClickHouseQueries {
|
||||||
|
queryNames[name] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following logic exists for backward compatibility
|
||||||
|
// If there is no selected query, then
|
||||||
|
// - check if F1 is present, if yes, return F1
|
||||||
|
// - else return the query with max ascii value
|
||||||
|
// this logic is not really correct. we should be considering
|
||||||
|
// whether the query is enabled or not. but this is a temporary
|
||||||
|
// fix to support backward compatibility
|
||||||
|
if _, ok := queryNames["F1"]; ok {
|
||||||
|
return "F1"
|
||||||
|
}
|
||||||
|
keys := make([]string, 0, len(queryNames))
|
||||||
|
for k := range queryNames {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys[len(keys)-1]
|
||||||
|
}
|
||||||
|
// This should never happen
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (rc *RuleCondition) IsValid() bool {
|
func (rc *RuleCondition) IsValid() bool {
|
||||||
|
|
||||||
if rc.CompositeQuery == nil {
|
if rc.CompositeQuery == nil {
|
||||||
|
@ -202,6 +202,21 @@ func (r *BaseRule) Unit() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *BaseRule) Timestamps(ts time.Time) (time.Time, time.Time) {
|
||||||
|
start := ts.Add(-time.Duration(r.evalWindow)).UnixMilli()
|
||||||
|
end := ts.UnixMilli()
|
||||||
|
|
||||||
|
if r.evalDelay > 0 {
|
||||||
|
start = start - int64(r.evalDelay.Milliseconds())
|
||||||
|
end = end - int64(r.evalDelay.Milliseconds())
|
||||||
|
}
|
||||||
|
// round to minute otherwise we could potentially miss data
|
||||||
|
start = start - (start % (60 * 1000))
|
||||||
|
end = end - (end % (60 * 1000))
|
||||||
|
|
||||||
|
return time.UnixMilli(start), time.UnixMilli(end)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *BaseRule) SetLastError(err error) {
|
func (r *BaseRule) SetLastError(err error) {
|
||||||
r.mtx.Lock()
|
r.mtx.Lock()
|
||||||
defer r.mtx.Unlock()
|
defer r.mtx.Unlock()
|
||||||
|
@ -6,9 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
@ -16,6 +14,7 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"go.signoz.io/signoz/pkg/query-service/common"
|
"go.signoz.io/signoz/pkg/query-service/common"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/contextlinks"
|
||||||
"go.signoz.io/signoz/pkg/query-service/model"
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
"go.signoz.io/signoz/pkg/query-service/postprocess"
|
"go.signoz.io/signoz/pkg/query-service/postprocess"
|
||||||
|
|
||||||
@ -31,6 +30,7 @@ import (
|
|||||||
"go.signoz.io/signoz/pkg/query-service/utils/timestamp"
|
"go.signoz.io/signoz/pkg/query-service/utils/timestamp"
|
||||||
|
|
||||||
logsv3 "go.signoz.io/signoz/pkg/query-service/app/logs/v3"
|
logsv3 "go.signoz.io/signoz/pkg/query-service/app/logs/v3"
|
||||||
|
tracesV3 "go.signoz.io/signoz/pkg/query-service/app/traces/v3"
|
||||||
"go.signoz.io/signoz/pkg/query-service/formatter"
|
"go.signoz.io/signoz/pkg/query-service/formatter"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
@ -53,6 +53,10 @@ type ThresholdRule struct {
|
|||||||
querier interfaces.Querier
|
querier interfaces.Querier
|
||||||
// querierV2 is used for alerts created after the introduction of new metrics query builder
|
// querierV2 is used for alerts created after the introduction of new metrics query builder
|
||||||
querierV2 interfaces.Querier
|
querierV2 interfaces.Querier
|
||||||
|
|
||||||
|
// used for attribute metadata enrichment for logs and traces
|
||||||
|
logsKeys map[string]v3.AttributeKey
|
||||||
|
spansKeys map[string]v3.AttributeKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewThresholdRule(
|
func NewThresholdRule(
|
||||||
@ -164,16 +168,8 @@ func (r *ThresholdRule) prepareQueryRange(ts time.Time) (*v3.QueryRangeParamsV3,
|
|||||||
|
|
||||||
zap.L().Info("prepareQueryRange", zap.Int64("ts", ts.UnixMilli()), zap.Int64("evalWindow", r.evalWindow.Milliseconds()), zap.Int64("evalDelay", r.evalDelay.Milliseconds()))
|
zap.L().Info("prepareQueryRange", zap.Int64("ts", ts.UnixMilli()), zap.Int64("evalWindow", r.evalWindow.Milliseconds()), zap.Int64("evalDelay", r.evalDelay.Milliseconds()))
|
||||||
|
|
||||||
start := ts.Add(-time.Duration(r.evalWindow)).UnixMilli()
|
startTs, endTs := r.Timestamps(ts)
|
||||||
end := ts.UnixMilli()
|
start, end := startTs.UnixMilli(), endTs.UnixMilli()
|
||||||
|
|
||||||
if r.evalDelay > 0 {
|
|
||||||
start = start - int64(r.evalDelay.Milliseconds())
|
|
||||||
end = end - int64(r.evalDelay.Milliseconds())
|
|
||||||
}
|
|
||||||
// round to minute otherwise we could potentially miss data
|
|
||||||
start = start - (start % (60 * 1000))
|
|
||||||
end = end - (end % (60 * 1000))
|
|
||||||
|
|
||||||
if r.ruleCondition.QueryType() == v3.QueryTypeClickHouseSQL {
|
if r.ruleCondition.QueryType() == v3.QueryTypeClickHouseSQL {
|
||||||
params := &v3.QueryRangeParamsV3{
|
params := &v3.QueryRangeParamsV3{
|
||||||
@ -239,245 +235,76 @@ func (r *ThresholdRule) prepareQueryRange(ts time.Time) (*v3.QueryRangeParamsV3,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following function is used to prepare the where clause for the query
|
|
||||||
// `lbls` contains the key value pairs of the labels from the result of the query
|
|
||||||
// We iterate over the where clause and replace the labels with the actual values
|
|
||||||
// There are two cases:
|
|
||||||
// 1. The label is present in the where clause
|
|
||||||
// 2. The label is not present in the where clause
|
|
||||||
//
|
|
||||||
// Example for case 2:
|
|
||||||
// Latency by serviceName without any filter
|
|
||||||
// In this case, for each service with latency > threshold we send a notification
|
|
||||||
// The expectation will be that clicking on the related traces for service A, will
|
|
||||||
// take us to the traces page with the filter serviceName=A
|
|
||||||
// So for all the missing labels in the where clause, we add them as key = value
|
|
||||||
//
|
|
||||||
// Example for case 1:
|
|
||||||
// Severity text IN (WARN, ERROR)
|
|
||||||
// In this case, the Severity text will appear in the `lbls` if it were part of the group
|
|
||||||
// by clause, in which case we replace it with the actual value for the notification
|
|
||||||
// i.e Severity text = WARN
|
|
||||||
// If the Severity text is not part of the group by clause, then we add it as it is
|
|
||||||
func (r *ThresholdRule) fetchFilters(selectedQuery string, lbls labels.Labels) []v3.FilterItem {
|
|
||||||
var filterItems []v3.FilterItem
|
|
||||||
|
|
||||||
added := make(map[string]struct{})
|
|
||||||
|
|
||||||
if r.ruleCondition.CompositeQuery.QueryType == v3.QueryTypeBuilder &&
|
|
||||||
r.ruleCondition.CompositeQuery.BuilderQueries[selectedQuery] != nil &&
|
|
||||||
r.ruleCondition.CompositeQuery.BuilderQueries[selectedQuery].Filters != nil {
|
|
||||||
|
|
||||||
for _, item := range r.ruleCondition.CompositeQuery.BuilderQueries[selectedQuery].Filters.Items {
|
|
||||||
exists := false
|
|
||||||
for _, label := range lbls {
|
|
||||||
if item.Key.Key == label.Name {
|
|
||||||
// if the label is present in the where clause, replace it with key = value
|
|
||||||
filterItems = append(filterItems, v3.FilterItem{
|
|
||||||
Key: item.Key,
|
|
||||||
Operator: v3.FilterOperatorEqual,
|
|
||||||
Value: label.Value,
|
|
||||||
})
|
|
||||||
exists = true
|
|
||||||
added[label.Name] = struct{}{}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
// if the label is not present in the where clause, add it as it is
|
|
||||||
filterItems = append(filterItems, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the labels which are not present in the where clause
|
|
||||||
for _, label := range lbls {
|
|
||||||
if _, ok := added[label.Name]; !ok {
|
|
||||||
filterItems = append(filterItems, v3.FilterItem{
|
|
||||||
Key: v3.AttributeKey{Key: label.Name},
|
|
||||||
Operator: v3.FilterOperatorEqual,
|
|
||||||
Value: label.Value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filterItems
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ThresholdRule) prepareLinksToLogs(ts time.Time, lbls labels.Labels) string {
|
func (r *ThresholdRule) prepareLinksToLogs(ts time.Time, lbls labels.Labels) string {
|
||||||
selectedQuery := r.GetSelectedQuery()
|
selectedQuery := r.GetSelectedQuery()
|
||||||
|
|
||||||
|
qr, err := r.prepareQueryRange(ts)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
start := time.UnixMilli(qr.Start)
|
||||||
|
end := time.UnixMilli(qr.End)
|
||||||
|
|
||||||
// TODO(srikanthccv): handle formula queries
|
// TODO(srikanthccv): handle formula queries
|
||||||
if selectedQuery < "A" || selectedQuery > "Z" {
|
if selectedQuery < "A" || selectedQuery > "Z" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
q, err := r.prepareQueryRange(ts)
|
q := r.ruleCondition.CompositeQuery.BuilderQueries[selectedQuery]
|
||||||
if err != nil {
|
if q == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
// Logs list view expects time in milliseconds
|
|
||||||
tr := v3.URLShareableTimeRange{
|
if q.DataSource != v3.DataSourceLogs {
|
||||||
Start: q.Start,
|
return ""
|
||||||
End: q.End,
|
|
||||||
PageSize: 100,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
options := v3.URLShareableOptions{
|
queryFilter := []v3.FilterItem{}
|
||||||
MaxLines: 2,
|
if q.Filters != nil {
|
||||||
Format: "list",
|
queryFilter = q.Filters.Items
|
||||||
SelectColumns: []v3.AttributeKey{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
period, _ := json.Marshal(tr)
|
filterItems := contextlinks.PrepareFilters(lbls.Map(), queryFilter, q.GroupBy, r.logsKeys)
|
||||||
urlEncodedTimeRange := url.QueryEscape(string(period))
|
|
||||||
|
|
||||||
filterItems := r.fetchFilters(selectedQuery, lbls)
|
return contextlinks.PrepareLinksToLogs(start, end, filterItems)
|
||||||
urlData := v3.URLShareableCompositeQuery{
|
|
||||||
QueryType: string(v3.QueryTypeBuilder),
|
|
||||||
Builder: v3.URLShareableBuilderQuery{
|
|
||||||
QueryData: []v3.BuilderQuery{
|
|
||||||
{
|
|
||||||
DataSource: v3.DataSourceLogs,
|
|
||||||
QueryName: "A",
|
|
||||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
|
||||||
AggregateAttribute: v3.AttributeKey{},
|
|
||||||
Filters: &v3.FilterSet{
|
|
||||||
Items: filterItems,
|
|
||||||
Operator: "AND",
|
|
||||||
},
|
|
||||||
Expression: "A",
|
|
||||||
Disabled: false,
|
|
||||||
Having: []v3.Having{},
|
|
||||||
StepInterval: 60,
|
|
||||||
OrderBy: []v3.OrderBy{
|
|
||||||
{
|
|
||||||
ColumnName: "timestamp",
|
|
||||||
Order: "desc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
QueryFormulas: make([]string, 0),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
data, _ := json.Marshal(urlData)
|
|
||||||
compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
|
|
||||||
|
|
||||||
optionsData, _ := json.Marshal(options)
|
|
||||||
urlEncodedOptions := url.QueryEscape(string(optionsData))
|
|
||||||
|
|
||||||
return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ThresholdRule) prepareLinksToTraces(ts time.Time, lbls labels.Labels) string {
|
func (r *ThresholdRule) prepareLinksToTraces(ts time.Time, lbls labels.Labels) string {
|
||||||
selectedQuery := r.GetSelectedQuery()
|
selectedQuery := r.GetSelectedQuery()
|
||||||
|
|
||||||
|
qr, err := r.prepareQueryRange(ts)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
start := time.UnixMilli(qr.Start)
|
||||||
|
end := time.UnixMilli(qr.End)
|
||||||
|
|
||||||
// TODO(srikanthccv): handle formula queries
|
// TODO(srikanthccv): handle formula queries
|
||||||
if selectedQuery < "A" || selectedQuery > "Z" {
|
if selectedQuery < "A" || selectedQuery > "Z" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
q, err := r.prepareQueryRange(ts)
|
q := r.ruleCondition.CompositeQuery.BuilderQueries[selectedQuery]
|
||||||
if err != nil {
|
if q == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
// Traces list view expects time in nanoseconds
|
|
||||||
tr := v3.URLShareableTimeRange{
|
if q.DataSource != v3.DataSourceTraces {
|
||||||
Start: q.Start * time.Second.Microseconds(),
|
return ""
|
||||||
End: q.End * time.Second.Microseconds(),
|
|
||||||
PageSize: 100,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
options := v3.URLShareableOptions{
|
queryFilter := []v3.FilterItem{}
|
||||||
MaxLines: 2,
|
if q.Filters != nil {
|
||||||
Format: "list",
|
queryFilter = q.Filters.Items
|
||||||
SelectColumns: constants.TracesListViewDefaultSelectedColumns,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
period, _ := json.Marshal(tr)
|
filterItems := contextlinks.PrepareFilters(lbls.Map(), queryFilter, q.GroupBy, r.spansKeys)
|
||||||
urlEncodedTimeRange := url.QueryEscape(string(period))
|
|
||||||
|
|
||||||
filterItems := r.fetchFilters(selectedQuery, lbls)
|
return contextlinks.PrepareLinksToTraces(start, end, filterItems)
|
||||||
urlData := v3.URLShareableCompositeQuery{
|
|
||||||
QueryType: string(v3.QueryTypeBuilder),
|
|
||||||
Builder: v3.URLShareableBuilderQuery{
|
|
||||||
QueryData: []v3.BuilderQuery{
|
|
||||||
{
|
|
||||||
DataSource: v3.DataSourceTraces,
|
|
||||||
QueryName: "A",
|
|
||||||
AggregateOperator: v3.AggregateOperatorNoOp,
|
|
||||||
AggregateAttribute: v3.AttributeKey{},
|
|
||||||
Filters: &v3.FilterSet{
|
|
||||||
Items: filterItems,
|
|
||||||
Operator: "AND",
|
|
||||||
},
|
|
||||||
Expression: "A",
|
|
||||||
Disabled: false,
|
|
||||||
Having: []v3.Having{},
|
|
||||||
StepInterval: 60,
|
|
||||||
OrderBy: []v3.OrderBy{
|
|
||||||
{
|
|
||||||
ColumnName: "timestamp",
|
|
||||||
Order: "desc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
QueryFormulas: make([]string, 0),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
data, _ := json.Marshal(urlData)
|
|
||||||
compositeQuery := url.QueryEscape(url.QueryEscape(string(data)))
|
|
||||||
|
|
||||||
optionsData, _ := json.Marshal(options)
|
|
||||||
urlEncodedOptions := url.QueryEscape(string(optionsData))
|
|
||||||
|
|
||||||
return fmt.Sprintf("compositeQuery=%s&timeRange=%s&startTime=%d&endTime=%d&options=%s", compositeQuery, urlEncodedTimeRange, tr.Start, tr.End, urlEncodedOptions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ThresholdRule) GetSelectedQuery() string {
|
func (r *ThresholdRule) GetSelectedQuery() string {
|
||||||
if r.ruleCondition != nil {
|
return r.ruleCondition.GetSelectedQueryName()
|
||||||
if r.ruleCondition.SelectedQuery != "" {
|
|
||||||
return r.ruleCondition.SelectedQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
queryNames := map[string]struct{}{}
|
|
||||||
|
|
||||||
if r.ruleCondition.CompositeQuery != nil {
|
|
||||||
if r.ruleCondition.QueryType() == v3.QueryTypeBuilder {
|
|
||||||
for name := range r.ruleCondition.CompositeQuery.BuilderQueries {
|
|
||||||
queryNames[name] = struct{}{}
|
|
||||||
}
|
|
||||||
} else if r.ruleCondition.QueryType() == v3.QueryTypeClickHouseSQL {
|
|
||||||
for name := range r.ruleCondition.CompositeQuery.ClickHouseQueries {
|
|
||||||
queryNames[name] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following logic exists for backward compatibility
|
|
||||||
// If there is no selected query, then
|
|
||||||
// - check if F1 is present, if yes, return F1
|
|
||||||
// - else return the query with max ascii value
|
|
||||||
// this logic is not really correct. we should be considering
|
|
||||||
// whether the query is enabled or not. but this is a temporary
|
|
||||||
// fix to support backward compatibility
|
|
||||||
if _, ok := queryNames["F1"]; ok {
|
|
||||||
return "F1"
|
|
||||||
}
|
|
||||||
keys := make([]string, 0, len(queryNames))
|
|
||||||
for k := range queryNames {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
return keys[len(keys)-1]
|
|
||||||
}
|
|
||||||
// This should never happen
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ThresholdRule) buildAndRunQuery(ctx context.Context, ts time.Time) (Vector, error) {
|
func (r *ThresholdRule) buildAndRunQuery(ctx context.Context, ts time.Time) (Vector, error) {
|
||||||
@ -492,11 +319,37 @@ func (r *ThresholdRule) buildAndRunQuery(ctx context.Context, ts time.Time) (Vec
|
|||||||
}
|
}
|
||||||
|
|
||||||
if params.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
if params.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
||||||
|
hasLogsQuery := false
|
||||||
|
hasTracesQuery := false
|
||||||
|
for _, query := range params.CompositeQuery.BuilderQueries {
|
||||||
|
if query.DataSource == v3.DataSourceLogs {
|
||||||
|
hasLogsQuery = true
|
||||||
|
}
|
||||||
|
if query.DataSource == v3.DataSourceTraces {
|
||||||
|
hasTracesQuery = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasLogsQuery {
|
||||||
// check if any enrichment is required for logs if yes then enrich them
|
// check if any enrichment is required for logs if yes then enrich them
|
||||||
if logsv3.EnrichmentRequired(params) {
|
if logsv3.EnrichmentRequired(params) {
|
||||||
// Note: Sending empty fields key because enrichment is only needed for json
|
logsFields, err := r.reader.GetLogFields(ctx)
|
||||||
// TODO: Add support for attribute enrichment later
|
if err != nil {
|
||||||
logsv3.Enrich(params, map[string]v3.AttributeKey{})
|
return nil, err
|
||||||
|
}
|
||||||
|
logsKeys := model.GetLogFieldsV3(ctx, params, logsFields)
|
||||||
|
r.logsKeys = logsKeys
|
||||||
|
logsv3.Enrich(params, logsKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasTracesQuery {
|
||||||
|
spanKeys, err := r.reader.GetSpanAttributeKeys(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.spansKeys = spanKeys
|
||||||
|
tracesV3.Enrich(params, spanKeys)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,11 +507,13 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time) (interface{}, er
|
|||||||
if r.typ == AlertTypeTraces {
|
if r.typ == AlertTypeTraces {
|
||||||
link := r.prepareLinksToTraces(ts, smpl.MetricOrig)
|
link := r.prepareLinksToTraces(ts, smpl.MetricOrig)
|
||||||
if link != "" && r.hostFromSource() != "" {
|
if link != "" && r.hostFromSource() != "" {
|
||||||
|
zap.L().Info("adding traces link to annotations", zap.String("link", fmt.Sprintf("%s/traces-explorer?%s", r.hostFromSource(), link)))
|
||||||
annotations = append(annotations, labels.Label{Name: "related_traces", Value: fmt.Sprintf("%s/traces-explorer?%s", r.hostFromSource(), link)})
|
annotations = append(annotations, labels.Label{Name: "related_traces", Value: fmt.Sprintf("%s/traces-explorer?%s", r.hostFromSource(), link)})
|
||||||
}
|
}
|
||||||
} else if r.typ == AlertTypeLogs {
|
} else if r.typ == AlertTypeLogs {
|
||||||
link := r.prepareLinksToLogs(ts, smpl.MetricOrig)
|
link := r.prepareLinksToLogs(ts, smpl.MetricOrig)
|
||||||
if link != "" && r.hostFromSource() != "" {
|
if link != "" && r.hostFromSource() != "" {
|
||||||
|
zap.L().Info("adding logs link to annotations", zap.String("link", fmt.Sprintf("%s/logs/logs-explorer?%s", r.hostFromSource(), link)))
|
||||||
annotations = append(annotations, labels.Label{Name: "related_logs", Value: fmt.Sprintf("%s/logs/logs-explorer?%s", r.hostFromSource(), link)})
|
annotations = append(annotations, labels.Label{Name: "related_logs", Value: fmt.Sprintf("%s/logs/logs-explorer?%s", r.hostFromSource(), link)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1303,12 +1303,23 @@ func TestThresholdRuleTracesLink(t *testing.T) {
|
|||||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metaCols := make([]cmock.ColumnType, 0)
|
||||||
|
metaCols = append(metaCols, cmock.ColumnType{Name: "DISTINCT(tagKey)", Type: "String"})
|
||||||
|
metaCols = append(metaCols, cmock.ColumnType{Name: "tagType", Type: "String"})
|
||||||
|
metaCols = append(metaCols, cmock.ColumnType{Name: "dataType", Type: "String"})
|
||||||
|
metaCols = append(metaCols, cmock.ColumnType{Name: "isColumn", Type: "Bool"})
|
||||||
|
|
||||||
cols := make([]cmock.ColumnType, 0)
|
cols := make([]cmock.ColumnType, 0)
|
||||||
cols = append(cols, cmock.ColumnType{Name: "value", Type: "Float64"})
|
cols = append(cols, cmock.ColumnType{Name: "value", Type: "Float64"})
|
||||||
cols = append(cols, cmock.ColumnType{Name: "attr", Type: "String"})
|
cols = append(cols, cmock.ColumnType{Name: "attr", Type: "String"})
|
||||||
cols = append(cols, cmock.ColumnType{Name: "timestamp", Type: "String"})
|
cols = append(cols, cmock.ColumnType{Name: "timestamp", Type: "String"})
|
||||||
|
|
||||||
for idx, c := range testCases {
|
for idx, c := range testCases {
|
||||||
|
metaRows := cmock.NewRows(metaCols, c.metaValues)
|
||||||
|
mock.
|
||||||
|
ExpectQuery("SELECT DISTINCT(tagKey), tagType, dataType, isColumn FROM archiveNamespace.span_attributes_keys").
|
||||||
|
WillReturnRows(metaRows)
|
||||||
|
|
||||||
rows := cmock.NewRows(cols, c.values)
|
rows := cmock.NewRows(cols, c.values)
|
||||||
|
|
||||||
// We are testing the eval logic after the query is run
|
// We are testing the eval logic after the query is run
|
||||||
@ -1402,12 +1413,38 @@ func TestThresholdRuleLogsLink(t *testing.T) {
|
|||||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attrMetaCols := make([]cmock.ColumnType, 0)
|
||||||
|
attrMetaCols = append(attrMetaCols, cmock.ColumnType{Name: "name", Type: "String"})
|
||||||
|
attrMetaCols = append(attrMetaCols, cmock.ColumnType{Name: "dataType", Type: "String"})
|
||||||
|
|
||||||
|
resourceMetaCols := make([]cmock.ColumnType, 0)
|
||||||
|
resourceMetaCols = append(resourceMetaCols, cmock.ColumnType{Name: "name", Type: "String"})
|
||||||
|
resourceMetaCols = append(resourceMetaCols, cmock.ColumnType{Name: "dataType", Type: "String"})
|
||||||
|
|
||||||
|
createTableCols := make([]cmock.ColumnType, 0)
|
||||||
|
createTableCols = append(createTableCols, cmock.ColumnType{Name: "statement", Type: "String"})
|
||||||
|
|
||||||
cols := make([]cmock.ColumnType, 0)
|
cols := make([]cmock.ColumnType, 0)
|
||||||
cols = append(cols, cmock.ColumnType{Name: "value", Type: "Float64"})
|
cols = append(cols, cmock.ColumnType{Name: "value", Type: "Float64"})
|
||||||
cols = append(cols, cmock.ColumnType{Name: "attr", Type: "String"})
|
cols = append(cols, cmock.ColumnType{Name: "attr", Type: "String"})
|
||||||
cols = append(cols, cmock.ColumnType{Name: "timestamp", Type: "String"})
|
cols = append(cols, cmock.ColumnType{Name: "timestamp", Type: "String"})
|
||||||
|
|
||||||
for idx, c := range testCases {
|
for idx, c := range testCases {
|
||||||
|
attrMetaRows := cmock.NewRows(attrMetaCols, c.attrMetaValues)
|
||||||
|
mock.
|
||||||
|
ExpectSelect("SELECT DISTINCT name, datatype from signoz_logs.distributed_logs_attribute_keys group by name, datatype").
|
||||||
|
WillReturnRows(attrMetaRows)
|
||||||
|
|
||||||
|
resourceMetaRows := cmock.NewRows(resourceMetaCols, c.resourceMetaValues)
|
||||||
|
mock.
|
||||||
|
ExpectSelect("SELECT DISTINCT name, datatype from signoz_logs.distributed_logs_resource_keys group by name, datatype").
|
||||||
|
WillReturnRows(resourceMetaRows)
|
||||||
|
|
||||||
|
createTableRows := cmock.NewRows(createTableCols, c.createTableValues)
|
||||||
|
mock.
|
||||||
|
ExpectSelect("SHOW CREATE TABLE signoz_logs.logs").
|
||||||
|
WillReturnRows(createTableRows)
|
||||||
|
|
||||||
rows := cmock.NewRows(cols, c.values)
|
rows := cmock.NewRows(cols, c.values)
|
||||||
|
|
||||||
// We are testing the eval logic after the query is run
|
// We are testing the eval logic after the query is run
|
||||||
|
@ -7,6 +7,10 @@ var (
|
|||||||
targetUnit string
|
targetUnit string
|
||||||
yAxisUnit string
|
yAxisUnit string
|
||||||
values [][]interface{}
|
values [][]interface{}
|
||||||
|
metaValues [][]interface{}
|
||||||
|
attrMetaValues [][]interface{}
|
||||||
|
resourceMetaValues [][]interface{}
|
||||||
|
createTableValues [][]interface{}
|
||||||
expectAlerts int
|
expectAlerts int
|
||||||
compareOp string
|
compareOp string
|
||||||
matchType string
|
matchType string
|
||||||
@ -23,6 +27,12 @@ var (
|
|||||||
{float64(299316000), "attr", time.Now().Add(3 * time.Second)}, // 0.3 seconds
|
{float64(299316000), "attr", time.Now().Add(3 * time.Second)}, // 0.3 seconds
|
||||||
{float64(66640400.00000001), "attr", time.Now().Add(4 * time.Second)}, // 0.06 seconds
|
{float64(66640400.00000001), "attr", time.Now().Add(4 * time.Second)}, // 0.06 seconds
|
||||||
},
|
},
|
||||||
|
metaValues: [][]interface{}{},
|
||||||
|
createTableValues: [][]interface{}{
|
||||||
|
{"statement"},
|
||||||
|
},
|
||||||
|
attrMetaValues: [][]interface{}{},
|
||||||
|
resourceMetaValues: [][]interface{}{},
|
||||||
expectAlerts: 0,
|
expectAlerts: 0,
|
||||||
compareOp: "1", // Above
|
compareOp: "1", // Above
|
||||||
matchType: "1", // Once
|
matchType: "1", // Once
|
||||||
@ -38,6 +48,12 @@ var (
|
|||||||
{float64(299316000), "attr", time.Now().Add(3 * time.Second)}, // 299.31 ms
|
{float64(299316000), "attr", time.Now().Add(3 * time.Second)}, // 299.31 ms
|
||||||
{float64(66640400.00000001), "attr", time.Now().Add(4 * time.Second)}, // 66.64 ms
|
{float64(66640400.00000001), "attr", time.Now().Add(4 * time.Second)}, // 66.64 ms
|
||||||
},
|
},
|
||||||
|
metaValues: [][]interface{}{},
|
||||||
|
createTableValues: [][]interface{}{
|
||||||
|
{"statement"},
|
||||||
|
},
|
||||||
|
attrMetaValues: [][]interface{}{},
|
||||||
|
resourceMetaValues: [][]interface{}{},
|
||||||
expectAlerts: 4,
|
expectAlerts: 4,
|
||||||
compareOp: "1", // Above
|
compareOp: "1", // Above
|
||||||
matchType: "1", // Once
|
matchType: "1", // Once
|
||||||
@ -59,6 +75,12 @@ var (
|
|||||||
{float64(299316000), "attr", time.Now().Add(3 * time.Second)}, // 0.3 GB
|
{float64(299316000), "attr", time.Now().Add(3 * time.Second)}, // 0.3 GB
|
||||||
{float64(66640400.00000001), "attr", time.Now().Add(4 * time.Second)}, // 66.64 MB
|
{float64(66640400.00000001), "attr", time.Now().Add(4 * time.Second)}, // 66.64 MB
|
||||||
},
|
},
|
||||||
|
metaValues: [][]interface{}{},
|
||||||
|
createTableValues: [][]interface{}{
|
||||||
|
{"statement"},
|
||||||
|
},
|
||||||
|
attrMetaValues: [][]interface{}{},
|
||||||
|
resourceMetaValues: [][]interface{}{},
|
||||||
expectAlerts: 0,
|
expectAlerts: 0,
|
||||||
compareOp: "1", // Above
|
compareOp: "1", // Above
|
||||||
matchType: "1", // Once
|
matchType: "1", // Once
|
||||||
|
Loading…
x
Reference in New Issue
Block a user