@@ -29,6 +29,7 @@ import (
2929 "github.com/grafana/loki/v3/pkg/util/constants"
3030 "github.com/grafana/loki/v3/pkg/util/httpreq"
3131 util_log "github.com/grafana/loki/v3/pkg/util/log"
32+ "github.com/grafana/loki/v3/pkg/util/querylimits"
3233)
3334
3435func TestLimits (t * testing.T ) {
@@ -1051,6 +1052,145 @@ func Test_MaxQuerySize(t *testing.T) {
10511052 }
10521053}
10531054
1055+ func Test_MaxQuerySize_WithQueryLimitsContext (t * testing.T ) {
1056+ // a sentinal query value to control when our mock stats handler returns context stats
1057+ ctxSentinal := `{context="true"}`
1058+ schemas := []config.PeriodConfig {
1059+ {
1060+ From : config.DayTime {Time : model .TimeFromUnix (testTime .Add (- 48 * time .Hour ).Unix ())},
1061+ IndexType : types .TSDBType ,
1062+ },
1063+ }
1064+
1065+ for _ , tc := range []struct {
1066+ desc string
1067+ query string
1068+ queryStart time.Time
1069+ queryEnd time.Time
1070+ queryBytes uint64
1071+ contextStart time.Time
1072+ contextEnd time.Time
1073+ contextBytes uint64
1074+ limit int
1075+ shouldErr bool
1076+ expectedStatsHits int
1077+ }{
1078+ {
1079+ desc : "No context, query under limit" ,
1080+ query : `{app="foo"} |= "foo"` ,
1081+ queryStart : testTime .Add (- 1 * time .Hour ),
1082+ queryEnd : testTime ,
1083+ queryBytes : 500 ,
1084+ limit : 1000 ,
1085+ shouldErr : false ,
1086+ expectedStatsHits : 1 ,
1087+ },
1088+ {
1089+ desc : "Context range larger, both under limit" ,
1090+ query : `{app="foo"} |= "foo"` ,
1091+ queryStart : testTime .Add (- 1 * time .Hour ),
1092+ queryEnd : testTime ,
1093+ queryBytes : 200 ,
1094+ contextStart : testTime .Add (- 24 * time .Hour ),
1095+ contextEnd : testTime ,
1096+ contextBytes : 800 ,
1097+ limit : 1000 ,
1098+ shouldErr : false ,
1099+ expectedStatsHits : 2 ,
1100+ },
1101+ {
1102+ desc : "Context range larger, context exceeds limit" ,
1103+ query : `{app="foo"} |= "foo"` ,
1104+ queryStart : testTime .Add (- 1 * time .Hour ),
1105+ queryEnd : testTime ,
1106+ queryBytes : 200 ,
1107+ contextStart : testTime .Add (- 24 * time .Hour ),
1108+ contextEnd : testTime ,
1109+ contextBytes : 1200 ,
1110+ limit : 1000 ,
1111+ shouldErr : true ,
1112+ expectedStatsHits : 2 ,
1113+ },
1114+ {
1115+ desc : "Query range larger, query exceeds limit" ,
1116+ query : `{app="foo"} |= "foo"` ,
1117+ queryStart : testTime .Add (- 24 * time .Hour ),
1118+ queryEnd : testTime ,
1119+ queryBytes : 1200 ,
1120+ contextStart : testTime .Add (- 1 * time .Hour ),
1121+ contextEnd : testTime ,
1122+ contextBytes : 200 ,
1123+ limit : 1000 ,
1124+ shouldErr : true ,
1125+ expectedStatsHits : 2 ,
1126+ },
1127+ } {
1128+ t .Run (tc .desc , func (t * testing.T ) {
1129+ statsHits := atomic .NewInt32 (0 )
1130+
1131+ statsHandler := queryrangebase .HandlerFunc (func (_ context.Context , req queryrangebase.Request ) (queryrangebase.Response , error ) {
1132+ statsHits .Inc ()
1133+
1134+ bytes := tc .queryBytes
1135+ if req .GetQuery () == ctxSentinal {
1136+ bytes = tc .contextBytes
1137+ }
1138+
1139+ return & IndexStatsResponse {
1140+ Response : & logproto.IndexStatsResponse {
1141+ Bytes : bytes ,
1142+ },
1143+ }, nil
1144+ })
1145+
1146+ promHandler := queryrangebase .HandlerFunc (func (_ context.Context , _ queryrangebase.Request ) (queryrangebase.Response , error ) {
1147+ return & LokiPromResponse {
1148+ Response : & queryrangebase.PrometheusResponse {
1149+ Status : "success" ,
1150+ },
1151+ }, nil
1152+ })
1153+
1154+ lokiReq := & LokiRequest {
1155+ Query : tc .query ,
1156+ StartTs : tc .queryStart ,
1157+ EndTs : tc .queryEnd ,
1158+ Direction : logproto .FORWARD ,
1159+ Path : "/query_range" ,
1160+ Plan : & plan.QueryPlan {
1161+ AST : syntax .MustParseExpr (tc .query ),
1162+ },
1163+ }
1164+
1165+ ctx := user .InjectOrgID (context .Background (), "foo" )
1166+
1167+ if ! tc .contextStart .IsZero () && ! tc .contextEnd .IsZero () {
1168+ ctx = querylimits .InjectQueryLimitsContextIntoContext (ctx , querylimits.Context {
1169+ Expr : ctxSentinal , // a hack to make mocking the stats handler easier, irl this should be the same query as in the request
1170+ From : tc .contextStart ,
1171+ To : tc .contextEnd ,
1172+ })
1173+ }
1174+
1175+ middlewares := []queryrangebase.Middleware {
1176+ NewQuerySizeLimiterMiddleware (schemas , testEngineOpts , util_log .Logger , fakeLimits {
1177+ maxQueryBytesRead : tc .limit ,
1178+ }, statsHandler ),
1179+ }
1180+
1181+ _ , err := queryrangebase .MergeMiddlewares (middlewares ... ).Wrap (promHandler ).Do (ctx , lokiReq )
1182+
1183+ if tc .shouldErr {
1184+ require .Error (t , err )
1185+ } else {
1186+ require .NoError (t , err )
1187+ }
1188+
1189+ require .Equal (t , tc .expectedStatsHits , int (statsHits .Load ()))
1190+ })
1191+ }
1192+ }
1193+
10541194func Test_MaxQuerySize_MaxLookBackPeriod (t * testing.T ) {
10551195 engineOpts := testEngineOpts
10561196 engineOpts .MaxLookBackPeriod = 1 * time .Hour
0 commit comments