chore: tf testing

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
This commit is contained in:
Shivanshu Raj Shrivastava 2025-05-29 16:17:55 +05:30
parent 71b098776b
commit 89de68f987
No known key found for this signature in database
GPG Key ID: D34D26C62AC3E9AE
3 changed files with 1818 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,420 @@
package tracefunnel
import (
"fmt"
tracev4 "github.com/SigNoz/signoz/pkg/query-service/app/traces/v4"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
"strings"
)
func ValidateTraces(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange) (*v3.ClickHouseQuery, error) {
var query string
var err error
funnelSteps := funnel.Steps
containsErrorT1 := 0
containsErrorT2 := 0
containsErrorT3 := 0
if funnelSteps[0].HasErrors {
containsErrorT1 = 1
}
if funnelSteps[1].HasErrors {
containsErrorT2 = 1
}
if len(funnel.Steps) > 2 && funnelSteps[2].HasErrors {
containsErrorT3 = 1
}
// Build filter clauses for each step
clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[0].Filters)
if err != nil {
return nil, err
}
clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[1].Filters)
if err != nil {
return nil, err
}
clauseStep3 := ""
if len(funnel.Steps) > 2 {
clauseStep3, err = tracev4.BuildTracesFilterQuery(funnelSteps[2].Filters)
if err != nil {
return nil, err
}
}
if len(funnel.Steps) > 2 {
query = BuildThreeStepFunnelValidationQuery(
containsErrorT1,
containsErrorT2,
containsErrorT3,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[0].ServiceName,
funnelSteps[0].SpanName,
funnelSteps[1].ServiceName,
funnelSteps[1].SpanName,
funnelSteps[2].ServiceName,
funnelSteps[2].SpanName,
clauseStep1,
clauseStep2,
clauseStep3,
)
} else {
query = BuildTwoStepFunnelValidationQuery(
containsErrorT1,
containsErrorT2,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[0].ServiceName,
funnelSteps[0].SpanName,
funnelSteps[1].ServiceName,
funnelSteps[1].SpanName,
clauseStep1,
clauseStep2,
)
}
return &v3.ClickHouseQuery{
Query: query,
}, nil
}
func GetFunnelAnalytics(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange) (*v3.ClickHouseQuery, error) {
var query string
var err error
funnelSteps := funnel.Steps
containsErrorT1 := 0
containsErrorT2 := 0
containsErrorT3 := 0
latencyPointerT1 := funnelSteps[0].LatencyPointer
latencyPointerT2 := funnelSteps[1].LatencyPointer
latencyPointerT3 := "start"
if len(funnel.Steps) > 2 {
latencyPointerT3 = funnelSteps[2].LatencyPointer
}
if funnelSteps[0].HasErrors {
containsErrorT1 = 1
}
if funnelSteps[1].HasErrors {
containsErrorT2 = 1
}
if len(funnel.Steps) > 2 && funnelSteps[2].HasErrors {
containsErrorT3 = 1
}
// Build filter clauses for each step
clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[0].Filters)
if err != nil {
return nil, err
}
clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[1].Filters)
if err != nil {
return nil, err
}
clauseStep3 := ""
if len(funnel.Steps) > 2 {
clauseStep3, err = tracev4.BuildTracesFilterQuery(funnelSteps[2].Filters)
if err != nil {
return nil, err
}
}
if len(funnel.Steps) > 2 {
query = BuildThreeStepFunnelOverviewQuery(
containsErrorT1,
containsErrorT2,
containsErrorT3,
latencyPointerT1,
latencyPointerT2,
latencyPointerT3,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[0].ServiceName,
funnelSteps[0].SpanName,
funnelSteps[1].ServiceName,
funnelSteps[1].SpanName,
funnelSteps[2].ServiceName,
funnelSteps[2].SpanName,
clauseStep1,
clauseStep2,
clauseStep3,
)
} else {
query = BuildTwoStepFunnelOverviewQuery(
containsErrorT1,
containsErrorT2,
latencyPointerT1,
latencyPointerT2,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[0].ServiceName,
funnelSteps[0].SpanName,
funnelSteps[1].ServiceName,
funnelSteps[1].SpanName,
clauseStep1,
clauseStep2,
)
}
return &v3.ClickHouseQuery{Query: query}, nil
}
func GetFunnelStepAnalytics(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange, stepStart, stepEnd int64) (*v3.ClickHouseQuery, error) {
var query string
var err error
funnelSteps := funnel.Steps
containsErrorT1 := 0
containsErrorT2 := 0
containsErrorT3 := 0
latencyPointerT1 := funnelSteps[0].LatencyPointer
latencyPointerT2 := funnelSteps[1].LatencyPointer
latencyPointerT3 := "start"
if len(funnel.Steps) > 2 {
latencyPointerT3 = funnelSteps[2].LatencyPointer
}
latencyTypeT2 := "p99"
latencyTypeT3 := "p99"
if stepStart == stepEnd {
return nil, fmt.Errorf("step start and end cannot be the same for /step/overview")
}
if funnelSteps[0].HasErrors {
containsErrorT1 = 1
}
if funnelSteps[1].HasErrors {
containsErrorT2 = 1
}
if len(funnel.Steps) > 2 && funnelSteps[2].HasErrors {
containsErrorT3 = 1
}
if funnelSteps[1].LatencyType != "" {
latencyTypeT2 = strings.ToLower(funnelSteps[1].LatencyType)
}
if len(funnel.Steps) > 2 && funnelSteps[2].LatencyType != "" {
latencyTypeT3 = strings.ToLower(funnelSteps[2].LatencyType)
}
// Build filter clauses for each step
clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[0].Filters)
if err != nil {
return nil, err
}
clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[1].Filters)
if err != nil {
return nil, err
}
clauseStep3 := ""
if len(funnel.Steps) > 2 {
clauseStep3, err = tracev4.BuildTracesFilterQuery(funnelSteps[2].Filters)
if err != nil {
return nil, err
}
}
if len(funnel.Steps) > 2 {
query = BuildThreeStepFunnelStepOverviewQuery(
containsErrorT1,
containsErrorT2,
containsErrorT3,
latencyPointerT1,
latencyPointerT2,
latencyPointerT3,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[0].ServiceName,
funnelSteps[0].SpanName,
funnelSteps[1].ServiceName,
funnelSteps[1].SpanName,
funnelSteps[2].ServiceName,
funnelSteps[2].SpanName,
clauseStep1,
clauseStep2,
clauseStep3,
stepStart,
stepEnd,
latencyTypeT2,
latencyTypeT3,
)
} else {
query = BuildTwoStepFunnelStepOverviewQuery(
containsErrorT1,
containsErrorT2,
latencyPointerT1,
latencyPointerT2,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[0].ServiceName,
funnelSteps[0].SpanName,
funnelSteps[1].ServiceName,
funnelSteps[1].SpanName,
clauseStep1,
clauseStep2,
latencyTypeT2,
)
}
return &v3.ClickHouseQuery{Query: query}, nil
}
func GetStepAnalytics(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange) (*v3.ClickHouseQuery, error) {
var query string
funnelSteps := funnel.Steps
containsErrorT1 := 0
containsErrorT2 := 0
containsErrorT3 := 0
if funnelSteps[0].HasErrors {
containsErrorT1 = 1
}
if funnelSteps[1].HasErrors {
containsErrorT2 = 1
}
if len(funnel.Steps) > 2 && funnelSteps[2].HasErrors {
containsErrorT3 = 1
}
// Build filter clauses for each step
clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[0].Filters)
if err != nil {
return nil, err
}
clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[1].Filters)
if err != nil {
return nil, err
}
clauseStep3 := ""
if len(funnel.Steps) > 2 {
clauseStep3, err = tracev4.BuildTracesFilterQuery(funnelSteps[2].Filters)
if err != nil {
return nil, err
}
}
if len(funnel.Steps) > 2 {
query = BuildThreeStepFunnelCountQuery(
containsErrorT1,
containsErrorT2,
containsErrorT3,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[0].ServiceName,
funnelSteps[0].SpanName,
funnelSteps[1].ServiceName,
funnelSteps[1].SpanName,
funnelSteps[2].ServiceName,
funnelSteps[2].SpanName,
clauseStep1,
clauseStep2,
clauseStep3,
)
} else {
query = BuildTwoStepFunnelCountQuery(
containsErrorT1,
containsErrorT2,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[0].ServiceName,
funnelSteps[0].SpanName,
funnelSteps[1].ServiceName,
funnelSteps[1].SpanName,
clauseStep1,
clauseStep2,
)
}
return &v3.ClickHouseQuery{
Query: query,
}, nil
}
func GetSlowestTraces(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange, stepStart, stepEnd int64) (*v3.ClickHouseQuery, error) {
funnelSteps := funnel.Steps
containsErrorT1 := 0
containsErrorT2 := 0
stepStartOrder := 0
stepEndOrder := 1
if stepStart != stepEnd {
stepStartOrder = int(stepStart) - 1
stepEndOrder = int(stepEnd) - 1
if funnelSteps[stepStartOrder].HasErrors {
containsErrorT1 = 1
}
if funnelSteps[stepEndOrder].HasErrors {
containsErrorT2 = 1
}
}
// Build filter clauses for the steps
clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepStartOrder].Filters)
if err != nil {
return nil, err
}
clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepEndOrder].Filters)
if err != nil {
return nil, err
}
query := BuildTwoStepFunnelTopSlowTracesQuery(
containsErrorT1,
containsErrorT2,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[stepStartOrder].ServiceName,
funnelSteps[stepStartOrder].SpanName,
funnelSteps[stepEndOrder].ServiceName,
funnelSteps[stepEndOrder].SpanName,
clauseStep1,
clauseStep2,
)
return &v3.ClickHouseQuery{Query: query}, nil
}
func GetErroredTraces(funnel *tracefunneltypes.StorableFunnel, timeRange tracefunneltypes.TimeRange, stepStart, stepEnd int64) (*v3.ClickHouseQuery, error) {
funnelSteps := funnel.Steps
containsErrorT1 := 0
containsErrorT2 := 0
stepStartOrder := 0
stepEndOrder := 1
if stepStart != stepEnd {
stepStartOrder = int(stepStart) - 1
stepEndOrder = int(stepEnd) - 1
if funnelSteps[stepStartOrder].HasErrors {
containsErrorT1 = 1
}
if funnelSteps[stepEndOrder].HasErrors {
containsErrorT2 = 1
}
}
// Build filter clauses for the steps
clauseStep1, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepStartOrder].Filters)
if err != nil {
return nil, err
}
clauseStep2, err := tracev4.BuildTracesFilterQuery(funnelSteps[stepEndOrder].Filters)
if err != nil {
return nil, err
}
query := BuildTwoStepFunnelTopSlowErrorTracesQuery(
containsErrorT1,
containsErrorT2,
timeRange.StartTime,
timeRange.EndTime,
funnelSteps[stepStartOrder].ServiceName,
funnelSteps[stepStartOrder].SpanName,
funnelSteps[stepEndOrder].ServiceName,
funnelSteps[stepEndOrder].SpanName,
clauseStep1,
clauseStep2,
)
return &v3.ClickHouseQuery{Query: query}, nil
}

View File

@ -40,6 +40,7 @@ import (
_ "github.com/mattn/go-sqlite3"
"github.com/SigNoz/signoz/pkg/cache"
traceFunnelsModule "github.com/SigNoz/signoz/pkg/modules/tracefunnel"
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
"github.com/SigNoz/signoz/pkg/query-service/app/inframetrics"
@ -63,6 +64,7 @@ import (
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
"go.uber.org/zap"
@ -5255,4 +5257,240 @@ func (aH *APIHandler) RegisterTraceFunnelsRoutes(router *mux.Router, am *middlew
traceFunnelsRouter.HandleFunc("/{funnel_id}",
am.ViewAccess(aH.Signoz.Handlers.TraceFunnel.UpdateFunnel)).
Methods(http.MethodPut)
// Analytics endpoints
traceFunnelsRouter.HandleFunc("/{funnel_id}/analytics/validate", aH.handleValidateTraces).Methods("POST")
traceFunnelsRouter.HandleFunc("/{funnel_id}/analytics/overview", aH.handleFunnelAnalytics).Methods("POST")
traceFunnelsRouter.HandleFunc("/{funnel_id}/analytics/steps", aH.handleStepAnalytics).Methods("POST")
traceFunnelsRouter.HandleFunc("/{funnel_id}/analytics/steps/overview", aH.handleFunnelStepAnalytics).Methods("POST")
traceFunnelsRouter.HandleFunc("/{funnel_id}/analytics/slow-traces", aH.handleFunnelSlowTraces).Methods("POST")
traceFunnelsRouter.HandleFunc("/{funnel_id}/analytics/error-traces", aH.handleFunnelErrorTraces).Methods("POST")
}
func (aH *APIHandler) handleValidateTraces(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
funnelID := vars["funnel_id"]
claims, err := authtypes.ClaimsFromContext(r.Context())
if err != nil {
render.Error(w, err)
return
}
funnel, err := aH.Signoz.Modules.TraceFunnel.Get(r.Context(), valuer.MustNewUUID(funnelID), valuer.MustNewUUID(claims.OrgID))
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("funnel not found: %v", err)}, nil)
return
}
var timeRange traceFunnels.TimeRange
if err := json.NewDecoder(r.Body).Decode(&timeRange); err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("error decoding time range: %v", err)}, nil)
return
}
if len(funnel.Steps) < 2 {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("funnel must have at least 2 steps")}, nil)
return
}
chq, err := traceFunnelsModule.ValidateTraces(funnel, timeRange)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error building clickhouse query: %v", err)}, nil)
return
}
results, err := aH.reader.GetListResultV3(r.Context(), chq.Query)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error converting clickhouse results to list: %v", err)}, nil)
return
}
aH.Respond(w, results)
}
func (aH *APIHandler) handleFunnelAnalytics(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
funnelID := vars["funnel_id"]
claims, err := authtypes.ClaimsFromContext(r.Context())
if err != nil {
render.Error(w, err)
return
}
funnel, err := aH.Signoz.Modules.TraceFunnel.Get(r.Context(), valuer.MustNewUUID(funnelID), valuer.MustNewUUID(claims.OrgID))
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("funnel not found: %v", err)}, nil)
return
}
var stepTransition traceFunnels.StepTransitionRequest
if err := json.NewDecoder(r.Body).Decode(&stepTransition); err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("error decoding time range: %v", err)}, nil)
return
}
chq, err := traceFunnelsModule.GetFunnelAnalytics(funnel, stepTransition.TimeRange)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error building clickhouse query: %v", err)}, nil)
return
}
results, err := aH.reader.GetListResultV3(r.Context(), chq.Query)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error converting clickhouse results to list: %v", err)}, nil)
return
}
aH.Respond(w, results)
}
func (aH *APIHandler) handleFunnelStepAnalytics(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
funnelID := vars["funnel_id"]
claims, err := authtypes.ClaimsFromContext(r.Context())
if err != nil {
render.Error(w, err)
return
}
funnel, err := aH.Signoz.Modules.TraceFunnel.Get(r.Context(), valuer.MustNewUUID(funnelID), valuer.MustNewUUID(claims.OrgID))
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("funnel not found: %v", err)}, nil)
return
}
var stepTransition traceFunnels.StepTransitionRequest
if err := json.NewDecoder(r.Body).Decode(&stepTransition); err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("error decoding time range: %v", err)}, nil)
return
}
chq, err := traceFunnelsModule.GetFunnelStepAnalytics(funnel, stepTransition.TimeRange, stepTransition.StepStart, stepTransition.StepEnd)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error building clickhouse query: %v", err)}, nil)
return
}
results, err := aH.reader.GetListResultV3(r.Context(), chq.Query)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error converting clickhouse results to list: %v", err)}, nil)
return
}
aH.Respond(w, results)
}
func (aH *APIHandler) handleStepAnalytics(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
funnelID := vars["funnel_id"]
claims, err := authtypes.ClaimsFromContext(r.Context())
if err != nil {
render.Error(w, err)
return
}
funnel, err := aH.Signoz.Modules.TraceFunnel.Get(r.Context(), valuer.MustNewUUID(funnelID), valuer.MustNewUUID(claims.OrgID))
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("funnel not found: %v", err)}, nil)
return
}
var timeRange traceFunnels.TimeRange
if err := json.NewDecoder(r.Body).Decode(&timeRange); err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("error decoding time range: %v", err)}, nil)
return
}
chq, err := traceFunnelsModule.GetStepAnalytics(funnel, timeRange)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error building clickhouse query: %v", err)}, nil)
return
}
results, err := aH.reader.GetListResultV3(r.Context(), chq.Query)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error converting clickhouse results to list: %v", err)}, nil)
return
}
aH.Respond(w, results)
}
func (aH *APIHandler) handleFunnelSlowTraces(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
funnelID := vars["funnel_id"]
claims, err := authtypes.ClaimsFromContext(r.Context())
if err != nil {
render.Error(w, err)
return
}
funnel, err := aH.Signoz.Modules.TraceFunnel.Get(r.Context(), valuer.MustNewUUID(funnelID), valuer.MustNewUUID(claims.OrgID))
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("funnel not found: %v", err)}, nil)
return
}
var req traceFunnels.StepTransitionRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("invalid request body: %v", err)}, nil)
return
}
chq, err := traceFunnelsModule.GetSlowestTraces(funnel, req.TimeRange, req.StepStart, req.StepEnd)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error building clickhouse query: %v", err)}, nil)
return
}
results, err := aH.reader.GetListResultV3(r.Context(), chq.Query)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error converting clickhouse results to list: %v", err)}, nil)
return
}
aH.Respond(w, results)
}
func (aH *APIHandler) handleFunnelErrorTraces(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
funnelID := vars["funnel_id"]
claims, err := authtypes.ClaimsFromContext(r.Context())
if err != nil {
render.Error(w, err)
return
}
funnel, err := aH.Signoz.Modules.TraceFunnel.Get(r.Context(), valuer.MustNewUUID(funnelID), valuer.MustNewUUID(claims.OrgID))
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("funnel not found: %v", err)}, nil)
return
}
var req traceFunnels.StepTransitionRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("invalid request body: %v", err)}, nil)
return
}
chq, err := traceFunnelsModule.GetErroredTraces(funnel, req.TimeRange, req.StepStart, req.StepEnd)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error building clickhouse query: %v", err)}, nil)
return
}
results, err := aH.reader.GetListResultV3(r.Context(), chq.Query)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: fmt.Errorf("error converting clickhouse results to list: %v", err)}, nil)
return
}
aH.Respond(w, results)
}