Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add total count and state filter #5745

Merged
merged 2 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions pkg/query-service/app/clickhouseReader/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -5222,14 +5222,18 @@ func (r *ClickHouseReader) AddRuleStateHistory(ctx context.Context, ruleStateHis
}

func (r *ClickHouseReader) ReadRuleStateHistoryByRuleID(
ctx context.Context, ruleID string, params *v3.QueryRuleStateHistory) ([]v3.RuleStateHistory, error) {
ctx context.Context, ruleID string, params *v3.QueryRuleStateHistory) (*v3.RuleStateTimeline, error) {

var conditions []string

conditions = append(conditions, fmt.Sprintf("rule_id = '%s'", ruleID))

conditions = append(conditions, fmt.Sprintf("unix_milli >= %d AND unix_milli < %d", params.Start, params.End))

if params.State != "" {
conditions = append(conditions, fmt.Sprintf("state = '%s'", params.State))
}

if params.Filters != nil && len(params.Filters.Items) != 0 {
for _, item := range params.Filters.Items {
toFormat := item.Value
Expand Down Expand Up @@ -5288,7 +5292,19 @@ func (r *ClickHouseReader) ReadRuleStateHistoryByRuleID(
return nil, err
}

return history, nil
var total uint64
err = r.db.QueryRow(ctx, fmt.Sprintf("SELECT count(*) FROM %s.%s WHERE %s",
signozHistoryDBName, ruleStateHistoryTableName, whereClause)).Scan(&total)
if err != nil {
return nil, err
}

timeline := &v3.RuleStateTimeline{
Items: history,
Total: total,
}

return timeline, nil
}

func (r *ClickHouseReader) ReadRuleStateHistoryTopContributorsByRuleID(
Expand Down
57 changes: 57 additions & 0 deletions pkg/query-service/app/http_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,13 @@ func (aH *APIHandler) getRuleStats(w http.ResponseWriter, r *http.Request) {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
return
}
if math.IsNaN(currentAvgResolutionTime) || math.IsInf(currentAvgResolutionTime, 0) {
currentAvgResolutionTime = 0
}
if math.IsNaN(pastAvgResolutionTime) || math.IsInf(pastAvgResolutionTime, 0) {
pastAvgResolutionTime = 0
}

stats := v3.Stats{
TotalCurrentTriggers: totalCurrentTriggers,
TotalPastTriggers: totalPastTriggers,
Expand Down Expand Up @@ -799,6 +806,37 @@ func (aH *APIHandler) getRuleStateHistory(w http.ResponseWriter, r *http.Request
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
return
}

rule, err := aH.ruleManager.GetRule(r.Context(), ruleID)
if err == nil {
for idx := range res.Items {
lbls := make(map[string]string)
err := json.Unmarshal([]byte(res.Items[idx].Labels), &lbls)
if err != nil {
continue
}
filterItems := []v3.FilterItem{}
if rule.AlertType == "LOGS_BASED_ALERT" || rule.AlertType == "TRACES_BASED_ALERT" {
if rule.RuleCondition.CompositeQuery != nil {
if rule.RuleCondition.QueryType() == v3.QueryTypeBuilder {
for _, query := range rule.RuleCondition.CompositeQuery.BuilderQueries {
if query.Filters != nil && len(query.Filters.Items) > 0 {
filterItems = append(filterItems, query.Filters.Items...)
}
}
}
}
}
newFilters := common.PrepareFilters(lbls, filterItems)
ts := time.Unix(res.Items[idx].UnixMilli/1000, 0)
if rule.AlertType == "LOGS_BASED_ALERT" {
res.Items[idx].RelatedLogsLink = common.PrepareLinksToLogs(ts, newFilters)
} else if rule.AlertType == "TRACES_BASED_ALERT" {
res.Items[idx].RelatedTracesLink = common.PrepareLinksToTraces(ts, newFilters)
}
}
}

aH.Respond(w, res)
}

Expand All @@ -816,6 +854,25 @@ func (aH *APIHandler) getRuleStateHistoryTopContributors(w http.ResponseWriter,
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
return
}

rule, err := aH.ruleManager.GetRule(r.Context(), ruleID)
if err == nil {
for idx := range res {
lbls := make(map[string]string)
err := json.Unmarshal([]byte(res[idx].Labels), &lbls)
if err != nil {
continue
}
ts := time.Unix(params.End/1000, 0)
filters := common.PrepareFilters(lbls, nil)
if rule.AlertType == "LOGS_BASED_ALERT" {
res[idx].RelatedLogsLink = common.PrepareLinksToLogs(ts, filters)
} else if rule.AlertType == "TRACES_BASED_ALERT" {
res[idx].RelatedTracesLink = common.PrepareLinksToTraces(ts, filters)
}
}
}

aH.Respond(w, res)
}

Expand Down
183 changes: 183 additions & 0 deletions pkg/query-service/common/query_range.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package common

import (
"encoding/json"
"fmt"
"math"
"net/url"
"time"

"go.signoz.io/signoz/pkg/query-service/constants"
Expand Down Expand Up @@ -70,3 +73,183 @@ func LCMList(nums []int64) int64 {
}
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(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(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
}
2 changes: 1 addition & 1 deletion pkg/query-service/interfaces/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ type Reader interface {

AddRuleStateHistory(ctx context.Context, ruleStateHistory []v3.RuleStateHistory) error
GetOverallStateTransitions(ctx context.Context, ruleID string, params *v3.QueryRuleStateHistory) ([]v3.RuleStateTransition, error)
ReadRuleStateHistoryByRuleID(ctx context.Context, ruleID string, params *v3.QueryRuleStateHistory) ([]v3.RuleStateHistory, error)
ReadRuleStateHistoryByRuleID(ctx context.Context, ruleID string, params *v3.QueryRuleStateHistory) (*v3.RuleStateTimeline, error)
GetTotalTriggers(ctx context.Context, ruleID string, params *v3.QueryRuleStateHistory) (uint64, error)
GetTriggersByInterval(ctx context.Context, ruleID string, params *v3.QueryRuleStateHistory) (*v3.Series, error)
GetAvgResolutionTime(ctx context.Context, ruleID string, params *v3.QueryRuleStateHistory) (float64, error)
Expand Down
39 changes: 36 additions & 3 deletions pkg/query-service/model/v3/v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,11 @@ func (l LabelsString) String() string {
return string(l)
}

type RuleStateTimeline struct {
Items []RuleStateHistory `json:"items"`
Total uint64 `json:"total"`
}

type RuleStateHistory struct {
RuleID string `json:"ruleID" ch:"rule_id"`
RuleName string `json:"ruleName" ch:"rule_name"`
Expand All @@ -1194,11 +1199,15 @@ type RuleStateHistory struct {
Labels LabelsString `json:"labels" ch:"labels"`
Fingerprint uint64 `json:"fingerprint" ch:"fingerprint"`
Value float64 `json:"value" ch:"value"`

RelatedTracesLink string `json:"relatedTracesLink"`
RelatedLogsLink string `json:"relatedLogsLink"`
}

type QueryRuleStateHistory struct {
Start int64 `json:"start"`
End int64 `json:"end"`
State string `json:"state"`
Filters *FilterSet `json:"filters"`
Offset int64 `json:"offset"`
Limit int64 `json:"limit"`
Expand All @@ -1219,9 +1228,11 @@ func (r *QueryRuleStateHistory) Validate() error {
}

type RuleStateHistoryContributor struct {
Fingerprint uint64 `json:"fingerprint" ch:"fingerprint"`
Labels LabelsString `json:"labels" ch:"labels"`
Count uint64 `json:"count" ch:"count"`
Fingerprint uint64 `json:"fingerprint" ch:"fingerprint"`
Labels LabelsString `json:"labels" ch:"labels"`
Count uint64 `json:"count" ch:"count"`
RelatedTracesLink string `json:"relatedTracesLink"`
RelatedLogsLink string `json:"relatedLogsLink"`
}

type RuleStateTransition struct {
Expand Down Expand Up @@ -1255,3 +1266,25 @@ type QueryProgress struct {

ElapsedMs uint64 `json:"elapsed_ms"`
}

type URLShareableTimeRange struct {
Start int64 `json:"start"`
End int64 `json:"end"`
PageSize int64 `json:"pageSize"`
}

type URLShareableBuilderQuery struct {
QueryData []BuilderQuery `json:"queryData"`
QueryFormulas []string `json:"queryFormulas"`
}

type URLShareableCompositeQuery struct {
QueryType string `json:"queryType"`
Builder URLShareableBuilderQuery `json:"builder"`
}

type URLShareableOptions struct {
MaxLines int `json:"maxLines"`
Format string `json:"format"`
SelectColumns []AttributeKey `json:"selectColumns"`
}
Loading
Loading