mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-06-04 11:25:52 +08:00
130 lines
4.6 KiB
Go
130 lines
4.6 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"go.signoz.io/signoz/ee/query-service/anomaly"
|
|
baseapp "go.signoz.io/signoz/pkg/query-service/app"
|
|
"go.signoz.io/signoz/pkg/query-service/app/queryBuilder"
|
|
"go.signoz.io/signoz/pkg/query-service/model"
|
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
|
|
|
|
bodyBytes, _ := io.ReadAll(r.Body)
|
|
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
|
|
|
queryRangeParams, apiErrorObj := baseapp.ParseQueryRangeParams(r)
|
|
|
|
if apiErrorObj != nil {
|
|
zap.L().Error("error parsing metric query range params", zap.Error(apiErrorObj.Err))
|
|
RespondError(w, apiErrorObj, nil)
|
|
return
|
|
}
|
|
queryRangeParams.Version = "v4"
|
|
|
|
// add temporality for each metric
|
|
temporalityErr := aH.PopulateTemporality(r.Context(), queryRangeParams)
|
|
if temporalityErr != nil {
|
|
zap.L().Error("Error while adding temporality for metrics", zap.Error(temporalityErr))
|
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: temporalityErr}, nil)
|
|
return
|
|
}
|
|
|
|
anomalyQueryExists := false
|
|
anomalyQuery := &v3.BuilderQuery{}
|
|
if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeBuilder {
|
|
for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
|
|
for _, fn := range query.Functions {
|
|
if fn.Name == v3.FunctionNameAnomaly {
|
|
anomalyQueryExists = true
|
|
anomalyQuery = query
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if anomalyQueryExists {
|
|
// ensure all queries have metric data source, and there should be only one anomaly query
|
|
for _, query := range queryRangeParams.CompositeQuery.BuilderQueries {
|
|
// What is query.QueryName == query.Expression doing here?
|
|
// In the current implementation, the way to recognize if a query is a formula is by
|
|
// checking if the expression is the same as the query name. if the expression is different
|
|
// then it is a formula. otherwise, it is simple builder query.
|
|
if query.DataSource != v3.DataSourceMetrics && query.QueryName == query.Expression {
|
|
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("all queries must have metric data source")}, nil)
|
|
return
|
|
}
|
|
}
|
|
|
|
// get the threshold, and seasonality from the anomaly query
|
|
var seasonality anomaly.Seasonality
|
|
for _, fn := range anomalyQuery.Functions {
|
|
if fn.Name == v3.FunctionNameAnomaly {
|
|
seasonalityStr, ok := fn.NamedArgs["seasonality"].(string)
|
|
if !ok {
|
|
seasonalityStr = "daily"
|
|
}
|
|
if seasonalityStr == "weekly" {
|
|
seasonality = anomaly.SeasonalityWeekly
|
|
} else if seasonalityStr == "daily" {
|
|
seasonality = anomaly.SeasonalityDaily
|
|
} else {
|
|
seasonality = anomaly.SeasonalityHourly
|
|
}
|
|
break
|
|
}
|
|
}
|
|
var provider anomaly.Provider
|
|
switch seasonality {
|
|
case anomaly.SeasonalityWeekly:
|
|
provider = anomaly.NewWeeklyProvider(
|
|
anomaly.WithCache[*anomaly.WeeklyProvider](aH.opts.Cache),
|
|
anomaly.WithKeyGenerator[*anomaly.WeeklyProvider](queryBuilder.NewKeyGenerator()),
|
|
anomaly.WithReader[*anomaly.WeeklyProvider](aH.opts.DataConnector),
|
|
anomaly.WithFeatureLookup[*anomaly.WeeklyProvider](aH.opts.FeatureFlags),
|
|
)
|
|
case anomaly.SeasonalityDaily:
|
|
provider = anomaly.NewDailyProvider(
|
|
anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache),
|
|
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
|
|
anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector),
|
|
anomaly.WithFeatureLookup[*anomaly.DailyProvider](aH.opts.FeatureFlags),
|
|
)
|
|
case anomaly.SeasonalityHourly:
|
|
provider = anomaly.NewHourlyProvider(
|
|
anomaly.WithCache[*anomaly.HourlyProvider](aH.opts.Cache),
|
|
anomaly.WithKeyGenerator[*anomaly.HourlyProvider](queryBuilder.NewKeyGenerator()),
|
|
anomaly.WithReader[*anomaly.HourlyProvider](aH.opts.DataConnector),
|
|
anomaly.WithFeatureLookup[*anomaly.HourlyProvider](aH.opts.FeatureFlags),
|
|
)
|
|
default:
|
|
provider = anomaly.NewDailyProvider(
|
|
anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache),
|
|
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
|
|
anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector),
|
|
anomaly.WithFeatureLookup[*anomaly.DailyProvider](aH.opts.FeatureFlags),
|
|
)
|
|
}
|
|
anomalies, err := provider.GetAnomalies(r.Context(), &anomaly.GetAnomaliesRequest{Params: queryRangeParams})
|
|
if err != nil {
|
|
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
|
return
|
|
}
|
|
resp := v3.QueryRangeResponse{
|
|
Result: anomalies.Results,
|
|
ResultType: "anomaly",
|
|
}
|
|
aH.Respond(w, resp)
|
|
} else {
|
|
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
|
aH.QueryRangeV4(w, r)
|
|
}
|
|
}
|