poll.bend
cswap-bend-cli@0.1.0.0 · 387 lines · 79 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/clock.bend as Clock
4import cswap-bend-cli@0.1.0.0/policy/usage.bend as PUsage
5import cswap-bend-cli@0.1.0.0/policy/types.bend as PT
6
7
8
9
10
11
12
13def serve_ttl() -> U32:
14 180
15
16def decision_trust() -> U32:
17 300
18
19def trust_ceiling() -> U32:
20 3600
21
22def rate_limit_trust_ceiling() -> U32:
23 7200
24
25def claim_ttl() -> U32:
26 90
27
28def min_interval() -> U32:
29 180
30
31def urgent_interval() -> U32:
32 60
33
34def active_ceiling() -> U32:
35 300
36
37def candidate_default() -> U32:
38 300
39
40def candidate_ceiling() -> U32:
41 600
42
43def exhausted_interval() -> U32:
44 600
45
46def reset_slack() -> U32:
47 60
48
49def recent_429_window() -> U32:
50 3600
51
52def post_429_min() -> U32:
53 360
54
55def post_429_cap() -> U32:
56 1800
57
58def edge_backoff() -> U32:
59 300
60
61def backoff_base() -> U32:
62 30
63
64def backoff_cap() -> U32:
65 600
66
67def retry_margin() -> U32:
68 900
69
70def retry_floor_cap() -> U32:
71 4500
72
73def escalation_margin() -> U32:
74 150
75
76def min_u(+a: U32, +b: U32) -> U32:
77 Clock.sel(U32.is_lt(a, b), a, b)
78
79def max_u(+a: U32, +b: U32) -> U32:
80 Clock.sel(U32.is_gt(a, b), a, b)
81
82
83def sub0(+a: U32, +b: U32) -> U32:
84 Clock.sel(U32.is_gt(a, b), (a - b : U32), 0)
85
86def absdiff(+a: U32, +b: U32) -> U32:
87 Clock.sel(U32.is_gt(a, b), (a - b : U32), (b - a : U32))
88
89def tenths.mk(+i: String, +f: String) -> Maybe<&2, U32>:
90 do Maybe<&2, U32>:
91 ip : U32 <- U32.read(i)
92 f1 : U32 <- Clock.num(f ++ "00", 0n, 1n)
93 f2 : U32 <- Clock.num(f ++ "00", 1n, 1n)
94 return (ip * 10 + f1 + Clock.sel(U32.is_ge(f2, 5), 1, 0) : U32)
95
96def tenths.pick(ok: Bool, +i: String, +f: String) -> Maybe<&2, U32>:
97 match ok:
98 case True{}:
99 tenths.mk(i, f)
100 case False{}:
101 None{}
102
103def tenths.parts(ps: List<&2, String>) -> Maybe<&2, U32>:
104 match ps:
105 case Con{+i, Nil{}}:
106 tenths.pick(Clock.all_digits(i) && Bool.not(String.is_empty(i)) && Nat.is_le(String.length(i), 7n), i, "")
107 case Con{+i, Con{+f, Nil{}}}:
108 tenths.pick(Clock.all_digits(i) && Clock.all_digits(f) && Bool.not(String.is_empty(i)) && Nat.is_le(String.length(i), 7n), i, f)
109 case _:
110 None{}
111
112
113def tenths(s: String) -> Maybe<&2, U32>:
114 tenths.parts(String.split(s, '.'))
115
116def tenths.json(j: Json.Json) -> Maybe<&2, U32>:
117 match j:
118 case Json.JNum{t}:
119 tenths(t)
120 case _:
121 None{}
122
123
124type Win is Data:
125 Win{pct: U32, reset: Maybe<&2, U32>}
126
127def win.parse(m: Maybe<&2, String>) -> Maybe<&2, U32>:
128 match m:
129 case None{}:
130 None{}
131 case Some{s}:
132 Clock.parse_iso8601(s)
133
134def win.reset(+w: Json.Json) -> Maybe<&2, U32>:
135 win.parse(Json.as_string(Maybe.default(&2, Json.Json, Json.get("resets_at", w), Json.JNull{})))
136def min_acc(acc: Maybe<&2, U32>, +p: U32) -> Maybe<&2, U32>:
137 match acc:
138 case None{}:
139 Some{p}
140 case Some{a}:
141 Some{min_u(a, p)}
142
143def any.step(r: Maybe<&2, U32>, acc: Maybe<&2, U32>) -> Maybe<&2, U32>:
144 match r:
145 case Some{x}:
146 min_acc(acc, x)
147 case None{}:
148 acc
149
150
151def earliest_reset(ws: List<&2, Win>, acc: Maybe<&2, U32>) -> Maybe<&2, U32>:
152 match ws:
153 case Nil{}:
154 acc
155 case Con{Win{p, r}, t}:
156 earliest_reset(t, any.step(r, acc))
157
158
159type Sched is Data:
160 Sched{next: U32, interval: U32}
161
162def interval.moving(+base: U32) -> U32:
163 max_u(min_interval(), (base / 2 : U32))
164
165def interval.still(+base: U32, +ceil: U32) -> U32:
166 min_u(ceil, max_u(min_interval(), (base * 3 / 2 : U32)))
167
168
169def interval.base(prev: Maybe<&2, U32>, +dflt: U32) -> U32:
170 match prev:
171 case Some{+b}:
172 Clock.sel(U32.is_eq(b, 0), dflt, b)
173 case None{}:
174 dflt
175
176def interval.pick(known: Bool, moving: Bool, +dflt: U32, +base: U32, +ceil: U32) -> U32:
177 match known moving:
178 case False{} _:
179 dflt
180 case True{} True{}:
181 interval.moving(base)
182 case True{} False{}:
183 interval.still(base, ceil)
184
185def is_moving(prev: Maybe<&2, U32>, new: Maybe<&2, U32>) -> Bool:
186 match prev new:
187 case Some{a} Some{b}:
188 U32.is_ge(absdiff(a, b), 10)
189 case _ _:
190 False{}
191
192def is_known(prev: Maybe<&2, U32>, new: Maybe<&2, U32>) -> Bool:
193 Maybe.is_some(&2, U32, prev) && Maybe.is_some(&2, U32, new)
194
195def new_ge(new: Maybe<&2, U32>, +floor: U32) -> Bool:
196 match new:
197 case Some{n}:
198 U32.is_ge(n, floor)
199 case None{}:
200 False{}
201
202def exhausted(new: Maybe<&2, U32>) -> Bool:
203 match new:
204 case Some{n}:
205 U32.is_ge(n, 1000)
206 case None{}:
207 False{}
208
209def plan.r429(recent: Bool, +base: U32, +i: U32) -> U32:
210 Clock.sel(recent, min_u(post_429_cap(), max_u(i, max_u((base * 3 / 2 : U32), post_429_min()))), i)
211
212def plan.exh(ex: Bool, +i: U32) -> U32:
213 Clock.sel(ex, max_u(i, exhausted_interval()), i)
214
215def plan.interval(+i0: U32, +urgent: Bool, +recent: Bool, +base: U32, +ex: Bool) -> U32:
216 plan.exh(ex, plan.r429(recent, base, Clock.sel(urgent, urgent_interval(), i0)))
217
218def plan.cap(+next: U32, r: Maybe<&2, U32>) -> U32:
219 match r:
220 case Some{x}:
221 min_u(next, (x + reset_slack() : U32))
222 case None{}:
223 next
224
225def keep.m(ok: Bool, m: Maybe<&2, U32>) -> Maybe<&2, U32>:
226 match ok:
227 case True{}:
228 m
229 case False{}:
230 None{}
231
232def plan.reset.pick(ex: Bool, lim: Maybe<&2, U32>, fut: Maybe<&2, U32>, +now: U32) -> Maybe<&2, U32>:
233 match ex lim:
234 case True{} Some{+x}:
235 keep.m(U32.is_gt(x, now), Some{x})
236 case True{} None{}:
237 None{}
238 case False{} _:
239 fut
240
241def plan.fin(+now: U32, +interval: U32, r: Maybe<&2, U32>) -> Sched:
242 Sched{plan.cap((now + interval : U32), r), interval}
243
244def nat_maybe(m: Maybe<&2, U32>) -> Maybe<&2, Nat>:
245 match m:
246 case Some{x}:
247 Some{U32.to_nat(x)}
248 case None{}:
249 None{}
250
251def reset_nat(+w: Json.Json) -> Maybe<&2, Nat>:
252 nat_maybe(win.reset(w))
253
254def centi(m: Maybe<&2, U32>) -> Maybe<&2, Nat>:
255 match m:
256 case Some{t}:
257 Some{U32.to_nat((t * 10 : U32))}
258 case None{}:
259 None{}
260
261def pct_centi(+w: Json.Json) -> Maybe<&2, Nat>:
262 centi(tenths.json(Maybe.default(&2, Json.Json, Json.get("pct", w), Json.JNull{})))
263
264def scoped.mk(n: Maybe<&2, String>, p: Maybe<&2, Nat>, r: Maybe<&2, Nat>) -> Maybe<&2, PT.Scoped>:
265 match n p:
266 case Some{name} Some{x}:
267 Some{PT.Scoped{name, x, r}}
268 case _ _:
269 None{}
270
271def scoped.item(+w: Json.Json) -> Maybe<&2, PT.Scoped>:
272 scoped.mk(Json.as_string(Maybe.default(&2, Json.Json, Json.get("name", w), Json.JNull{})), pct_centi(w), reset_nat(w))
273
274def scoped.cons(m: Maybe<&2, PT.Scoped>, rest: List<&2, PT.Scoped>) -> List<&2, PT.Scoped>:
275 match m:
276 case Some{x}:
277 x <> rest
278 case None{}:
279 rest
280
281def scoped.all(ws: List<&2, Json.Json>) -> List<&2, PT.Scoped>:
282 match ws:
283 case Nil{}:
284 []
285 case Con{+w, t}:
286 scoped.cons(scoped.item(w), scoped.all(t))
287
288def window_of(+u: Json.Json, +k: String) -> Json.Json:
289 Maybe.default(&2, Json.Json, Json.get(k, u), Json.JNull{})
290
291
292def policy_usage(+u: Json.Json) -> PT.Usage:
293 PT.Usage{0n, pct_centi(window_of(u, "five_hour")), reset_nat(window_of(u, "five_hour")), pct_centi(window_of(u, "seven_day")), reset_nat(window_of(u, "seven_day")), scoped.all(Json.to_list(window_of(u, "scoped"))), None{}, 0n, PT.ErrNone{}, 0n, None{}, 0n, 0n, 0n, 0n, PT.NoSentinel{}}
294
295def maybe_u32(m: Maybe<&2, Nat>) -> Maybe<&2, U32>:
296 match m:
297 case Some{x}:
298 Some{U32.from_nat(x)}
299 case None{}:
300 None{}
301
302def win.back(ws: List<&2, PT.Win>) -> List<&2, Win>:
303 match ws:
304 case Nil{}:
305 []
306 case Con{PT.Win{p, r}, t}:
307 Win{U32.from_nat(Nat.div(p, 10n)), maybe_u32(r)} <> win.back(t)
308
309
310
311def relevant(+usage: Json.Json, +models: List<&2, String>) -> List<&2, Win>:
312 win.back(PUsage.windows(policy_usage(usage), models))
313
314def future.pick(ok: Bool, +x: U32, acc: Maybe<&2, U32>) -> Maybe<&2, U32>:
315 match ok:
316 case True{}:
317 min_acc(acc, x)
318 case False{}:
319 acc
320
321def future.step(r: Maybe<&2, U32>, +now: U32, acc: Maybe<&2, U32>) -> Maybe<&2, U32>:
322 match r:
323 case Some{+x}:
324 future.pick(U32.is_gt(x, now), x, acc)
325 case None{}:
326 acc
327
328def earliest_future(ws: List<&2, Win>, +now: U32, acc: Maybe<&2, U32>) -> Maybe<&2, U32>:
329 match ws:
330 case Nil{}:
331 acc
332 case Con{Win{p, r}, t}:
333 earliest_future(t, now, future.step(r, now, acc))
334
335def max_acc(acc: Maybe<&2, U32>, +p: U32) -> Maybe<&2, U32>:
336 match acc:
337 case None{}:
338 Some{p}
339 case Some{a}:
340 Some{max_u(a, p)}
341
342def limiting.step(full: Bool, r: Maybe<&2, U32>, acc: Maybe<&2, U32>) -> Maybe<&2, U32>:
343 match full r:
344 case True{} Some{x}:
345 max_acc(acc, x)
346 case _ _:
347 acc
348
349def limiting_reset(ws: List<&2, Win>, acc: Maybe<&2, U32>) -> Maybe<&2, U32>:
350 match ws:
351 case Nil{}:
352 acc
353 case Con{Win{p, r}, t}:
354 limiting_reset(t, limiting.step(U32.is_ge(p, 1000), r, acc))
355
356def plan.go(+usage: Json.Json, +models: List<&2, String>, +now: U32, +interval: U32, +ex: Bool) -> Sched:
357 plan.fin(now, interval, plan.reset.pick(ex, limiting_reset(relevant(usage, models), None{}), earliest_future(relevant(usage, models), now, None{}), now))
358
359def plan.with(+base: U32, +known: Bool, +moving: Bool, +ex: Bool, +in_band: Bool, +usage: Json.Json, +active: Bool, +models: List<&2, String>, +recent: Bool, +now: U32, +ceil: U32, +dflt: U32) -> Sched:
360 plan.go(usage, models, now,
361 plan.interval(interval.pick(known, moving, dflt, base, ceil), active && moving && Bool.not(recent) && in_band, recent, base, ex),
362 ex)
363
364def dflt_of(active: Bool) -> U32:
365 Clock.sel(active, min_interval(), candidate_default())
366
367def ceil_of(active: Bool) -> U32:
368 Clock.sel(active, active_ceiling(), candidate_ceiling())
369
370def plan.start(prev_interval: Maybe<&2, U32>, +prev: Maybe<&2, U32>, +new: Maybe<&2, U32>, +usage: Json.Json, +active: Bool, +threshold: U32, +models: List<&2, String>, recent: Bool, +now: U32) -> Sched:
371 plan.with(interval.base(prev_interval, dflt_of(active)), is_known(prev, new), is_moving(prev, new), exhausted(new), new_ge(new, sub0(threshold, escalation_margin())),
372 usage, active, models, recent, now, ceil_of(active), dflt_of(active))
373
374def max_pct(ws: List<&2, Win>, acc: Maybe<&2, U32>) -> Maybe<&2, U32>:
375 match ws:
376 case Nil{}:
377 acc
378 case Con{Win{p, r}, t}:
379 max_pct(t, max_acc(acc, p))
380
381def binding(usage: Json.Json, models: List<&2, String>) -> Maybe<&2, U32>:
382 max_pct(relevant(usage, models), None{})
383
384
385
386def plan_after_fetch(prev_interval: Maybe<&2, U32>, prev_usage: Json.Json, +new_usage: Json.Json, is_active: Bool, +threshold: U32, +models: List<&2, String>, recent_429: Bool, +now: U32) -> Sched:
387 plan.start(prev_interval, binding(prev_usage, models), binding(new_usage, models), new_usage, is_active, threshold, models, recent_429, now)