relay_cardinality/redis/
state.rs1use std::collections::BTreeMap;
2
3use relay_statsd::metric;
4
5use crate::{
6 CardinalityLimit,
7 limiter::{Entry, EntryId, Scoping},
8 redis::quota::{PartialQuotaScoping, QuotaScoping},
9 statsd::CardinalityLimiterCounters,
10};
11
12#[derive(Debug)]
17pub struct LimitState<'a> {
18 pub limit: u32,
20
21 partial_scope: PartialQuotaScoping,
23 scopes: BTreeMap<QuotaScoping, Vec<RedisEntry>>,
27
28 cardinality_limit: &'a CardinalityLimit,
30 cache_hits: u64,
32 cache_misses: u64,
34 accepts: u64,
36 rejections: u64,
38}
39
40impl<'a> LimitState<'a> {
41 pub fn new(scoping: Scoping, cardinality_limit: &'a CardinalityLimit) -> Option<Self> {
45 Some(Self {
46 partial_scope: PartialQuotaScoping::new(scoping, cardinality_limit)?,
47 scopes: BTreeMap::new(),
48 limit: cardinality_limit.limit,
49 cardinality_limit,
50 cache_hits: 0,
51 cache_misses: 0,
52 accepts: 0,
53 rejections: 0,
54 })
55 }
56
57 pub fn from_limits(scoping: Scoping, limits: &'a [CardinalityLimit]) -> Vec<Self> {
61 limits
62 .iter()
63 .filter_map(|limit| LimitState::new(scoping, limit))
64 .collect::<Vec<_>>()
65 }
66
67 pub fn matching_scope(&self, entry: Entry) -> Option<QuotaScoping> {
69 if self.partial_scope.matches(&entry) {
70 Some(self.partial_scope.complete(entry))
71 } else {
72 None
73 }
74 }
75
76 pub fn add(&mut self, scope: QuotaScoping, entry: RedisEntry) {
80 self.scopes.entry(scope).or_default().push(entry)
81 }
82
83 pub fn is_empty(&self) -> bool {
89 self.scopes.is_empty()
90 }
91
92 pub fn id(&self) -> &'a str {
94 &self.cardinality_limit.id
95 }
96
97 pub fn cardinality_limit(&self) -> &'a CardinalityLimit {
99 self.cardinality_limit
100 }
101
102 pub fn scopes(&self) -> &BTreeMap<QuotaScoping, Vec<RedisEntry>> {
104 &self.scopes
105 }
106
107 pub fn take_scopes(&mut self) -> BTreeMap<QuotaScoping, Vec<RedisEntry>> {
109 std::mem::take(&mut self.scopes)
110 }
111
112 pub fn cache_hit(&mut self) {
114 self.cache_hits += 1;
115 }
116
117 pub fn cache_miss(&mut self) {
119 self.cache_misses += 1;
120 }
121
122 pub fn accepted(&mut self) {
124 self.accepts += 1;
125 }
126
127 pub fn rejected(&mut self) {
129 self.rejections += 1;
130 }
131}
132
133impl Drop for LimitState<'_> {
134 fn drop(&mut self) {
135 let passive = if self.cardinality_limit.passive {
136 "true"
137 } else {
138 "false"
139 };
140
141 metric!(
142 counter(CardinalityLimiterCounters::RedisCacheHit) += self.cache_hits,
143 id = &self.cardinality_limit.id,
144 passive = passive,
145 );
146 metric!(
147 counter(CardinalityLimiterCounters::RedisCacheMiss) += self.cache_misses,
148 id = &self.cardinality_limit.id,
149 passive = passive,
150 );
151 metric!(
152 counter(CardinalityLimiterCounters::Accepted) += self.accepts,
153 id = &self.cardinality_limit.id,
154 passive = passive,
155 );
156 metric!(
157 counter(CardinalityLimiterCounters::Rejected) += self.rejections,
158 id = &self.cardinality_limit.id,
159 passive = passive,
160 );
161 }
162}
163
164#[derive(Clone, Copy, Debug)]
166pub struct RedisEntry {
167 pub id: EntryId,
169 pub hash: u32,
171}
172
173impl RedisEntry {
174 pub fn new(id: EntryId, hash: u32) -> Self {
176 Self { id, hash }
177 }
178}