Skip to content

Commit

Permalink
chore: add total count and state filter (SigNoz#5745)
Browse files Browse the repository at this point in the history
  • Loading branch information
srikanthccv authored Aug 23, 2024
1 parent d7fd1d0 commit b798518
Show file tree
Hide file tree
Showing 9 changed files with 329 additions and 56 deletions.
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 @@ -5223,14 +5223,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 @@ -5289,7 +5293,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 @@ -716,6 +716,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 @@ -788,6 +795,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 @@ -805,6 +843,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

0 comments on commit b798518

Please sign in to comment.