package anomaly import ( "math" "time" "github.com/SigNoz/signoz/pkg/query-service/common" v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3" ) type Seasonality string const ( SeasonalityHourly Seasonality = "hourly" SeasonalityDaily Seasonality = "daily" SeasonalityWeekly Seasonality = "weekly" ) func (s Seasonality) String() string { return string(s) } var ( oneWeekOffset = 24 * 7 * time.Hour.Milliseconds() oneDayOffset = 24 * time.Hour.Milliseconds() oneHourOffset = time.Hour.Milliseconds() fiveMinOffset = 5 * time.Minute.Milliseconds() ) func (s Seasonality) IsValid() bool { switch s { case SeasonalityHourly, SeasonalityDaily, SeasonalityWeekly: return true default: return false } } type GetAnomaliesRequest struct { Params *v3.QueryRangeParamsV3 Seasonality Seasonality } type GetAnomaliesResponse struct { Results []*v3.Result } // anomalyParams is the params for anomaly detection // prediction = avg(past_period_query) + avg(current_season_query) - mean(past_season_query, past2_season_query, past3_season_query) // // ^ ^ // | | // (rounded value for past peiod) + (seasonal growth) // // score = abs(value - prediction) / stddev (current_season_query) type anomalyQueryParams struct { // CurrentPeriodQuery is the query range params for period user is looking at or eval window // Example: (now-5m, now), (now-30m, now), (now-1h, now) // The results obtained from this query are used to compare with predicted values // and to detect anomalies CurrentPeriodQuery *v3.QueryRangeParamsV3 // PastPeriodQuery is the query range params for past period of seasonality // Example: For weekly seasonality, (now-1w-5m, now-1w) // : For daily seasonality, (now-1d-5m, now-1d) // : For hourly seasonality, (now-1h-5m, now-1h) PastPeriodQuery *v3.QueryRangeParamsV3 // CurrentSeasonQuery is the query range params for current period (seasonal) // Example: For weekly seasonality, this is the query range params for the (now-1w-5m, now) // : For daily seasonality, this is the query range params for the (now-1d-5m, now) // : For hourly seasonality, this is the query range params for the (now-1h-5m, now) CurrentSeasonQuery *v3.QueryRangeParamsV3 // PastSeasonQuery is the query range params for past seasonal period to the current season // Example: For weekly seasonality, this is the query range params for the (now-2w-5m, now-1w) // : For daily seasonality, this is the query range params for the (now-2d-5m, now-1d) // : For hourly seasonality, this is the query range params for the (now-2h-5m, now-1h) PastSeasonQuery *v3.QueryRangeParamsV3 // Past2SeasonQuery is the query range params for past 2 seasonal period to the current season // Example: For weekly seasonality, this is the query range params for the (now-3w-5m, now-2w) // : For daily seasonality, this is the query range params for the (now-3d-5m, now-2d) // : For hourly seasonality, this is the query range params for the (now-3h-5m, now-2h) Past2SeasonQuery *v3.QueryRangeParamsV3 // Past3SeasonQuery is the query range params for past 3 seasonal period to the current season // Example: For weekly seasonality, this is the query range params for the (now-4w-5m, now-3w) // : For daily seasonality, this is the query range params for the (now-4d-5m, now-3d) // : For hourly seasonality, this is the query range params for the (now-4h-5m, now-3h) Past3SeasonQuery *v3.QueryRangeParamsV3 } func updateStepInterval(req *v3.QueryRangeParamsV3) { start := req.Start end := req.End req.Step = int64(math.Max(float64(common.MinAllowedStepInterval(start, end)), 60)) for _, q := range req.CompositeQuery.BuilderQueries { // If the step interval is less than the minimum allowed step interval, set it to the minimum allowed step interval if minStep := common.MinAllowedStepInterval(start, end); q.StepInterval < minStep { q.StepInterval = minStep } } } func prepareAnomalyQueryParams(req *v3.QueryRangeParamsV3, seasonality Seasonality) *anomalyQueryParams { start := req.Start end := req.End currentPeriodQuery := &v3.QueryRangeParamsV3{ Start: start, End: end, CompositeQuery: req.CompositeQuery.Clone(), Variables: make(map[string]interface{}, 0), NoCache: false, } updateStepInterval(currentPeriodQuery) var pastPeriodStart, pastPeriodEnd int64 switch seasonality { // for one week period, we fetch the data from the past week with 5 min offset case SeasonalityWeekly: pastPeriodStart = start - oneWeekOffset - fiveMinOffset pastPeriodEnd = end - oneWeekOffset // for one day period, we fetch the data from the past day with 5 min offset case SeasonalityDaily: pastPeriodStart = start - oneDayOffset - fiveMinOffset pastPeriodEnd = end - oneDayOffset // for one hour period, we fetch the data from the past hour with 5 min offset case SeasonalityHourly: pastPeriodStart = start - oneHourOffset - fiveMinOffset pastPeriodEnd = end - oneHourOffset } pastPeriodQuery := &v3.QueryRangeParamsV3{ Start: pastPeriodStart, End: pastPeriodEnd, CompositeQuery: req.CompositeQuery.Clone(), Variables: make(map[string]interface{}, 0), NoCache: false, } updateStepInterval(pastPeriodQuery) // seasonality growth trend var currentGrowthPeriodStart, currentGrowthPeriodEnd int64 switch seasonality { case SeasonalityWeekly: currentGrowthPeriodStart = start - oneWeekOffset currentGrowthPeriodEnd = start case SeasonalityDaily: currentGrowthPeriodStart = start - oneDayOffset currentGrowthPeriodEnd = start case SeasonalityHourly: currentGrowthPeriodStart = start - oneHourOffset currentGrowthPeriodEnd = start } currentGrowthQuery := &v3.QueryRangeParamsV3{ Start: currentGrowthPeriodStart, End: currentGrowthPeriodEnd, CompositeQuery: req.CompositeQuery.Clone(), Variables: make(map[string]interface{}, 0), NoCache: false, } updateStepInterval(currentGrowthQuery) var pastGrowthPeriodStart, pastGrowthPeriodEnd int64 switch seasonality { case SeasonalityWeekly: pastGrowthPeriodStart = start - 2*oneWeekOffset pastGrowthPeriodEnd = start - 1*oneWeekOffset case SeasonalityDaily: pastGrowthPeriodStart = start - 2*oneDayOffset pastGrowthPeriodEnd = start - 1*oneDayOffset case SeasonalityHourly: pastGrowthPeriodStart = start - 2*oneHourOffset pastGrowthPeriodEnd = start - 1*oneHourOffset } pastGrowthQuery := &v3.QueryRangeParamsV3{ Start: pastGrowthPeriodStart, End: pastGrowthPeriodEnd, CompositeQuery: req.CompositeQuery.Clone(), Variables: make(map[string]interface{}, 0), NoCache: false, } updateStepInterval(pastGrowthQuery) var past2GrowthPeriodStart, past2GrowthPeriodEnd int64 switch seasonality { case SeasonalityWeekly: past2GrowthPeriodStart = start - 3*oneWeekOffset past2GrowthPeriodEnd = start - 2*oneWeekOffset case SeasonalityDaily: past2GrowthPeriodStart = start - 3*oneDayOffset past2GrowthPeriodEnd = start - 2*oneDayOffset case SeasonalityHourly: past2GrowthPeriodStart = start - 3*oneHourOffset past2GrowthPeriodEnd = start - 2*oneHourOffset } past2GrowthQuery := &v3.QueryRangeParamsV3{ Start: past2GrowthPeriodStart, End: past2GrowthPeriodEnd, CompositeQuery: req.CompositeQuery.Clone(), Variables: make(map[string]interface{}, 0), NoCache: false, } updateStepInterval(past2GrowthQuery) var past3GrowthPeriodStart, past3GrowthPeriodEnd int64 switch seasonality { case SeasonalityWeekly: past3GrowthPeriodStart = start - 4*oneWeekOffset past3GrowthPeriodEnd = start - 3*oneWeekOffset case SeasonalityDaily: past3GrowthPeriodStart = start - 4*oneDayOffset past3GrowthPeriodEnd = start - 3*oneDayOffset case SeasonalityHourly: past3GrowthPeriodStart = start - 4*oneHourOffset past3GrowthPeriodEnd = start - 3*oneHourOffset } past3GrowthQuery := &v3.QueryRangeParamsV3{ Start: past3GrowthPeriodStart, End: past3GrowthPeriodEnd, CompositeQuery: req.CompositeQuery.Clone(), Variables: make(map[string]interface{}, 0), NoCache: false, } updateStepInterval(past3GrowthQuery) return &anomalyQueryParams{ CurrentPeriodQuery: currentPeriodQuery, PastPeriodQuery: pastPeriodQuery, CurrentSeasonQuery: currentGrowthQuery, PastSeasonQuery: pastGrowthQuery, Past2SeasonQuery: past2GrowthQuery, Past3SeasonQuery: past3GrowthQuery, } } type anomalyQueryResults struct { CurrentPeriodResults []*v3.Result PastPeriodResults []*v3.Result CurrentSeasonResults []*v3.Result PastSeasonResults []*v3.Result Past2SeasonResults []*v3.Result Past3SeasonResults []*v3.Result }