@@ -28,6 +28,9 @@ defmodule CodexPooler.Alerts.Evaluator do
2828 }
2929
3030 @ type evaluation_opts :: keyword ( ) | map ( )
31+ @ type projection_cache :: % {
32+ optional ( { Ecto.UUID . t ( ) | nil , String . t ( ) | nil } ) => [ map ( ) ]
33+ }
3134
3235 @ spec evaluate_rule ( AlertRule . t ( ) , evaluation_opts ( ) ) :: [ candidate ( ) ]
3336 def evaluate_rule ( rule , opts \\ [ ] )
@@ -95,15 +98,136 @@ defmodule CodexPooler.Alerts.Evaluator do
9598
9699 @ spec evaluate_active_rules ( evaluation_opts ( ) ) :: [ candidate ( ) ]
97100 def evaluate_active_rules ( opts \\ [ ] ) do
98- AlertRule
99- |> where ( [ rule ] , rule . state == "active" )
100- |> order_by ( [ rule ] , asc: rule . created_at , asc: rule . id )
101- |> Repo . all ( )
102- |> Enum . flat_map ( & evaluate_rule ( & 1 , opts ) )
101+ timestamp = evaluation_timestamp ( opts )
102+
103+ { candidate_groups , _projection_cache } =
104+ AlertRule
105+ |> where ( [ rule ] , rule . state == "active" )
106+ |> order_by ( [ rule ] , asc: rule . created_at , asc: rule . id )
107+ |> Repo . all ( )
108+ |> Enum . map_reduce ( % { } , fn rule , projection_cache ->
109+ evaluate_rule_with_projection_cache ( rule , timestamp , projection_cache )
110+ end )
111+
112+ List . flatten ( candidate_groups )
113+ end
114+
115+ defp evaluate_rule_with_projection_cache (
116+ % AlertRule { state: "disabled" } = rule ,
117+ timestamp ,
118+ projection_cache
119+ ) do
120+ { [ clear_candidate ( rule , dedupe_key_for_rule ( rule , nil ) , timestamp ) ] , projection_cache }
121+ end
122+
123+ defp evaluate_rule_with_projection_cache (
124+ % AlertRule { rule_kind: "pool_no_usable_assignments" } = rule ,
125+ timestamp ,
126+ projection_cache
127+ ) do
128+ { projection , projection_cache } =
129+ pool_projection_from_cache ( rule . pool_id , rule . model , timestamp , projection_cache )
130+
131+ dedupe_key = dedupe_key_for_rule ( rule , nil )
132+
133+ candidates =
134+ if projection . usable_assignment_count == 0 do
135+ [ pool_match_candidate ( rule , dedupe_key , projection , "no_usable_assignments" , timestamp ) ]
136+ else
137+ [ clear_candidate ( rule , dedupe_key , timestamp ) ]
138+ end
139+
140+ { candidates , projection_cache }
141+ end
142+
143+ defp evaluate_rule_with_projection_cache (
144+ % AlertRule { rule_kind: "pool_low_usable_assignments" } = rule ,
145+ timestamp ,
146+ projection_cache
147+ ) do
148+ min_usable = rule . min_usable_assignments || 1
149+
150+ { projection , projection_cache } =
151+ pool_projection_from_cache ( rule . pool_id , rule . model , timestamp , projection_cache )
152+
153+ dedupe_key = dedupe_key_for_rule ( rule , nil )
154+
155+ candidates =
156+ if projection . usable_assignment_count > 0 and
157+ projection . usable_assignment_count < min_usable do
158+ [ pool_match_candidate ( rule , dedupe_key , projection , "low_usable_assignments" , timestamp ) ]
159+ else
160+ [ clear_candidate ( rule , dedupe_key , timestamp ) ]
161+ end
162+
163+ { candidates , projection_cache }
164+ end
165+
166+ defp evaluate_rule_with_projection_cache (
167+ % AlertRule { rule_kind: "pool_all_assignments_in_state" } = rule ,
168+ timestamp ,
169+ projection_cache
170+ ) do
171+ { projection , projection_cache } =
172+ pool_projection_from_cache ( rule . pool_id , rule . model , timestamp , projection_cache )
173+
174+ dedupe_key = dedupe_key_for_rule ( rule , nil )
175+ target_state = rule . target_state
176+
177+ candidates =
178+ if ( target_state && projection . enabled_assignment_count > 0 ) and
179+ all_in_state? ( projection , target_state ) do
180+ [ pool_match_candidate ( rule , dedupe_key , projection , target_state , timestamp ) ]
181+ else
182+ [ clear_candidate ( rule , dedupe_key , timestamp ) ]
183+ end
184+
185+ { candidates , projection_cache }
186+ end
187+
188+ defp evaluate_rule_with_projection_cache (
189+ % AlertRule { rule_kind: rule_kind } = rule ,
190+ timestamp ,
191+ projection_cache
192+ )
193+ when rule_kind in [ "upstream_quota_threshold" , "upstream_auth_state" ] do
194+ { assignments , projection_cache } =
195+ assigned_identity_projections_from_cache (
196+ rule . pool_id ,
197+ rule . model ,
198+ timestamp ,
199+ projection_cache
200+ )
201+
202+ candidates =
203+ assignments
204+ |> Enum . reject ( & ( & 1 . assignment_status in @ disabled_assignment_states ) )
205+ |> Enum . map ( fn assignment ->
206+ case rule . rule_kind do
207+ "upstream_quota_threshold" -> threshold_candidate ( rule , assignment , timestamp )
208+ "upstream_auth_state" -> auth_state_candidate ( rule , assignment , timestamp )
209+ end
210+ end )
211+
212+ { candidates , projection_cache }
103213 end
104214
105215 defp pool_projection ( pool_id , model , timestamp ) do
106- assignments = assigned_identity_projections ( pool_id , model , timestamp )
216+ pool_projection_from_assignments (
217+ pool_id ,
218+ model ,
219+ assigned_identity_projections ( pool_id , model , timestamp )
220+ )
221+ end
222+
223+ defp pool_projection_from_cache ( pool_id , model , timestamp , projection_cache ) do
224+ { assignments , projection_cache } =
225+ assigned_identity_projections_from_cache ( pool_id , model , timestamp , projection_cache )
226+
227+ { pool_projection_from_assignments ( pool_id , model , assignments ) , projection_cache }
228+ end
229+
230+ defp pool_projection_from_assignments ( pool_id , model , assignments ) do
107231 enabled = Enum . reject ( assignments , & ( & 1 . assignment_status in @ disabled_assignment_states ) )
108232 usable = Enum . filter ( enabled , & & 1 . usable_assignment? )
109233
@@ -118,6 +242,25 @@ defmodule CodexPooler.Alerts.Evaluator do
118242 }
119243 end
120244
245+ @ spec assigned_identity_projections_from_cache (
246+ Ecto.UUID . t ( ) | nil ,
247+ String . t ( ) | nil ,
248+ DateTime . t ( ) ,
249+ projection_cache ( )
250+ ) :: { [ map ( ) ] , projection_cache ( ) }
251+ defp assigned_identity_projections_from_cache ( pool_id , model , timestamp , projection_cache ) do
252+ cache_key = { pool_id , model }
253+
254+ case Map . fetch ( projection_cache , cache_key ) do
255+ { :ok , assignments } ->
256+ { assignments , projection_cache }
257+
258+ :error ->
259+ assignments = assigned_identity_projections ( pool_id , model , timestamp )
260+ { assignments , Map . put ( projection_cache , cache_key , assignments ) }
261+ end
262+ end
263+
121264 defp assigned_identity_projections ( pool_id , model , timestamp ) do
122265 assignments = assignment_rows ( pool_id )
123266
0 commit comments