aft/search_b2/
embed_counter.rs1use std::collections::HashMap;
4use std::marker::PhantomData;
5use std::rc::Rc;
6use std::sync::{Mutex, OnceLock};
7use std::thread::ThreadId;
8
9use serde::{Deserialize, Serialize};
10
11pub const FIXTURE_PROVIDER_MODEL: &str = "aft-search-fixture-v1";
13
14#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
15pub struct EmbedCounts {
16 pub requested: u64,
17 pub cache_hits: u64,
18 pub live_calls: u64,
19}
20
21impl EmbedCounts {
22 fn add_assign(&mut self, observation: Self) {
23 self.requested = self.requested.saturating_add(observation.requested);
24 self.cache_hits = self.cache_hits.saturating_add(observation.cache_hits);
25 self.live_calls = self.live_calls.saturating_add(observation.live_calls);
26 }
27}
28
29#[derive(Clone, Debug)]
30struct EmbedAttribution {
31 request_id: String,
32}
33
34#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
35enum ExecutionId {
36 Task(tokio::task::Id),
37 Thread(ThreadId),
38}
39
40fn current_execution() -> ExecutionId {
41 tokio::task::try_id()
42 .map(ExecutionId::Task)
43 .unwrap_or_else(|| ExecutionId::Thread(std::thread::current().id()))
44}
45
46fn active_attributions() -> &'static Mutex<HashMap<ExecutionId, Vec<EmbedAttribution>>> {
47 static ACTIVE: OnceLock<Mutex<HashMap<ExecutionId, Vec<EmbedAttribution>>>> = OnceLock::new();
48 ACTIVE.get_or_init(|| Mutex::new(HashMap::new()))
49}
50
51fn observations() -> &'static Mutex<HashMap<String, EmbedCounts>> {
52 static OBSERVATIONS: OnceLock<Mutex<HashMap<String, EmbedCounts>>> = OnceLock::new();
53 OBSERVATIONS.get_or_init(|| Mutex::new(HashMap::new()))
54}
55
56#[must_use]
61pub struct Guard {
62 execution: ExecutionId,
63 request_id: String,
64 _not_send: PhantomData<Rc<()>>,
65}
66
67pub fn install(request_id: impl Into<String>) -> Guard {
69 let request_id = request_id.into();
70 let execution = current_execution();
71 observations()
72 .lock()
73 .unwrap_or_else(std::sync::PoisonError::into_inner)
74 .insert(request_id.clone(), EmbedCounts::default());
75 active_attributions()
76 .lock()
77 .unwrap_or_else(std::sync::PoisonError::into_inner)
78 .entry(execution)
79 .or_default()
80 .push(EmbedAttribution {
81 request_id: request_id.clone(),
82 });
83 Guard {
84 execution,
85 request_id,
86 _not_send: PhantomData,
87 }
88}
89
90pub fn record(counts: EmbedCounts) {
92 let execution = current_execution();
93 let request_id = active_attributions()
94 .lock()
95 .unwrap_or_else(std::sync::PoisonError::into_inner)
96 .get(&execution)
97 .and_then(|attributions| attributions.last())
98 .map(|current| current.request_id.clone());
99 let Some(request_id) = request_id else {
100 return;
101 };
102
103 observations()
104 .lock()
105 .unwrap_or_else(std::sync::PoisonError::into_inner)
106 .entry(request_id)
107 .or_default()
108 .add_assign(counts);
109}
110
111pub fn read(request_id: &str) -> EmbedCounts {
113 observations()
114 .lock()
115 .unwrap_or_else(std::sync::PoisonError::into_inner)
116 .get(request_id)
117 .copied()
118 .unwrap_or_default()
119}
120
121impl Drop for Guard {
122 fn drop(&mut self) {
123 let mut active = active_attributions()
124 .lock()
125 .unwrap_or_else(std::sync::PoisonError::into_inner);
126 let mut remove_execution = false;
127 if let Some(attributions) = active.get_mut(&self.execution) {
128 let popped = attributions.pop();
129 debug_assert_eq!(
130 popped.as_ref().map(|current| current.request_id.as_str()),
131 Some(self.request_id.as_str())
132 );
133 remove_execution = attributions.is_empty();
134 }
135 if remove_execution {
136 active.remove(&self.execution);
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn sums_observations_and_ignores_unattributed_work() {
147 record(EmbedCounts {
148 requested: 99,
149 cache_hits: 99,
150 live_calls: 99,
151 });
152
153 let request_id = "counter-unit-sum";
154 let _guard = install(request_id);
155 record(EmbedCounts {
156 requested: 2,
157 cache_hits: 0,
158 live_calls: 1,
159 });
160 record(EmbedCounts {
161 requested: 0,
162 cache_hits: 1,
163 live_calls: 0,
164 });
165
166 assert_eq!(
167 read(request_id),
168 EmbedCounts {
169 requested: 2,
170 cache_hits: 1,
171 live_calls: 1,
172 }
173 );
174 }
175
176 #[test]
177 fn concurrent_requests_remain_isolated() {
178 let workers = (0..8)
179 .map(|index| {
180 std::thread::spawn(move || {
181 let request_id = format!("counter-unit-concurrent-{index}");
182 let _guard = install(&request_id);
183 record(EmbedCounts {
184 requested: index,
185 cache_hits: 1,
186 live_calls: index % 2,
187 });
188 (request_id.clone(), read(&request_id))
189 })
190 })
191 .collect::<Vec<_>>();
192
193 for (index, worker) in workers.into_iter().enumerate() {
194 let (request_id, counts) = worker.join().expect("counter worker");
195 assert_eq!(request_id, format!("counter-unit-concurrent-{index}"));
196 assert_eq!(counts.requested, index as u64);
197 assert_eq!(counts.cache_hits, 1);
198 assert_eq!(counts.live_calls, (index % 2) as u64);
199 }
200 }
201}