1use std::{borrow::Cow, cell::Cell, sync::Arc};
2
3use ahash::{HashMap, HashMapExt};
4use rkyv::{primitive::ArchivedU64, vec::ArchivedVec};
5use serde::{
6 ser::{SerializeSeq, SerializeStruct},
7 Deserialize, Deserializer, Serialize, Serializer,
8};
9use serde_json::value::RawValue;
10
11use crate::{
12 evaluation::evaluation_data::SpecView,
13 interned_string::InternedString,
14 interned_values::{
15 mmap_data_v2::{
16 ArchivedMmapDynamicString, ArchivedMmapReturnable, ArchivedMmapRule, ArchivedMmapSpec,
17 },
18 InternedStore,
19 },
20 log_e,
21 specs_response::spec_types::Spec,
22};
23
24const TAG: &str = "SpecsHashMap";
25
26#[derive(PartialEq, Debug, Default)] pub struct SpecsHashMap(pub HashMap<InternedString, SpecPointer>);
28
29#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
30pub(crate) struct SpecDecodeStats {
31 pub(crate) total: usize,
32 pub(crate) mmap: usize,
33}
34
35thread_local! {
36 static ACTIVE_SPEC_DECODE_STATS: Cell<Option<SpecDecodeStats>> = const { Cell::new(None) };
40}
41
42struct SpecDecodeStatsGuard {
43 previous: Option<SpecDecodeStats>,
44}
45
46impl Drop for SpecDecodeStatsGuard {
47 fn drop(&mut self) {
48 ACTIVE_SPEC_DECODE_STATS.with(|stats| stats.set(self.previous));
49 }
50}
51
52pub(crate) fn track_spec_decodes<T>(callback: impl FnOnce() -> T) -> (T, SpecDecodeStats) {
53 let previous =
54 ACTIVE_SPEC_DECODE_STATS.with(|stats| stats.replace(Some(SpecDecodeStats::default())));
55 let guard = SpecDecodeStatsGuard { previous };
56 let result = callback();
57 let stats = ACTIVE_SPEC_DECODE_STATS.with(Cell::get).unwrap_or_default();
58 drop(guard);
59 (result, stats)
60}
61
62pub(crate) fn seed_spec_decode_stats(stats: SpecDecodeStats) {
63 ACTIVE_SPEC_DECODE_STATS.with(|active| {
64 if active.get().is_some() {
65 active.set(Some(stats));
66 }
67 });
68}
69
70fn record_specs_cleared(values: &HashMap<InternedString, SpecPointer>) {
71 ACTIVE_SPEC_DECODE_STATS.with(|active| {
72 let Some(mut stats) = active.get() else {
73 return;
74 };
75
76 stats.total -= values.len();
77 stats.mmap -= values.values().filter(|value| value.is_mmap()).count();
78 active.set(Some(stats));
79 });
80}
81
82fn record_spec_change(previous_is_mmap: Option<bool>, next_is_mmap: Option<bool>) {
83 ACTIVE_SPEC_DECODE_STATS.with(|active| {
84 let Some(mut stats) = active.get() else {
85 return;
86 };
87
88 if let Some(previous_is_mmap) = previous_is_mmap {
89 stats.total -= 1;
90 stats.mmap -= usize::from(previous_is_mmap);
91 }
92 if let Some(next_is_mmap) = next_is_mmap {
93 stats.total += 1;
94 stats.mmap += usize::from(next_is_mmap);
95 }
96 active.set(Some(stats));
97 });
98}
99
100#[cfg(test)]
101mod decode_stats_tests {
102 use super::{record_spec_change, seed_spec_decode_stats, track_spec_decodes, SpecDecodeStats};
103
104 #[test]
105 fn seeded_stats_track_replacements_and_deletions() {
106 let (_, stats) = track_spec_decodes(|| {
107 seed_spec_decode_stats(SpecDecodeStats { total: 3, mmap: 2 });
108 record_spec_change(Some(true), Some(false));
109 record_spec_change(Some(false), None);
110 });
111
112 assert_eq!(stats, SpecDecodeStats { total: 2, mmap: 1 });
113 }
114}
115
116impl<'de> Deserialize<'de> for SpecsHashMap {
117 fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
118 where
119 D: Deserializer<'de>,
120 {
121 let raw_values: HashMap<InternedString, Box<RawValue>> =
122 Deserialize::deserialize(_deserializer)?;
123
124 let mut result = SpecsHashMap(HashMap::with_capacity(raw_values.len()));
125 for (key, raw_value) in raw_values.into_iter() {
126 let json_string = raw_value.get();
127
128 let mut preloaded = None;
129 if InternedStore::has_preloaded_mmap_v2() {
130 if let Ok(identity) = serde_json::from_str::<SpecIdentity<'_>>(json_string) {
131 preloaded =
132 InternedStore::try_get_preloaded_spec(&key, identity.entity.as_ref());
133 if let Some(spec) = &preloaded {
134 let existing_checksum = spec.view().checksum().map(|value| value.as_str());
135 match (identity.checksum.as_deref(), existing_checksum) {
136 (Some(checksum), Some(existing)) if existing == checksum => {
137 result.insert(key, preloaded.expect("preloaded spec must exist"));
138 continue;
139 }
140 (None, None) => {}
141 _ => preloaded = None,
142 }
143 }
144 }
145 }
146
147 let spec: Spec = match serde_json::from_str(json_string) {
148 Ok(spec) => spec,
149 Err(e) => {
150 log_e!(TAG, "Failed to deserialize spec: {}", e);
151 continue;
152 }
153 };
154
155 if preloaded
156 .as_ref()
157 .is_some_and(|preloaded| preloaded.matches_owned_spec(&spec))
158 {
159 result.insert(key, preloaded.take().expect("preloaded spec must exist"));
160 } else {
161 result.insert(key, SpecPointer::Pointer(Arc::new(spec)));
162 }
163 }
164
165 Ok(result)
166 }
167}
168
169#[derive(Deserialize)]
170struct SpecIdentity<'a> {
171 #[serde(borrow)]
172 checksum: Option<Cow<'a, str>>,
173 #[serde(borrow)]
174 entity: Cow<'a, str>,
175}
176
177impl Serialize for SpecsHashMap {
178 fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
179 where
180 S: Serializer,
181 {
182 self.0.serialize(_serializer)
183 }
184}
185
186impl SpecsHashMap {
188 pub fn get(&self, key: &InternedString) -> Option<&SpecPointer> {
189 self.0.get(key)
190 }
191
192 pub fn keys(&self) -> impl Iterator<Item = &InternedString> {
193 self.0.keys()
194 }
195
196 pub fn iter(&self) -> impl Iterator<Item = (&InternedString, &SpecPointer)> {
197 self.0.iter()
198 }
199
200 pub fn insert(&mut self, key: InternedString, value: SpecPointer) {
201 let next_is_mmap = value.is_mmap();
202 let previous = self.0.insert(key, value);
203 record_spec_change(
204 previous.as_ref().map(SpecPointer::is_mmap),
205 Some(next_is_mmap),
206 );
207 }
208
209 pub fn len(&self) -> usize {
210 self.0.len()
211 }
212
213 pub fn is_empty(&self) -> bool {
214 self.0.is_empty()
215 }
216
217 pub fn clear(&mut self) {
218 record_specs_cleared(&self.0);
219 self.0.clear();
220 }
221
222 pub fn remove(&mut self, key: &InternedString) -> Option<SpecPointer> {
223 let previous = self.0.remove(key);
224 record_spec_change(previous.as_ref().map(SpecPointer::is_mmap), None);
225 previous
226 }
227}
228
229#[derive(Clone, Copy)]
230struct MmapSpecHandle {
231 spec: &'static ArchivedMmapSpec,
232}
233
234impl MmapSpecHandle {
235 pub(crate) fn new(spec: &'static ArchivedMmapSpec) -> Self {
236 Self { spec }
237 }
238
239 pub(crate) fn archived(self) -> &'static ArchivedMmapSpec {
240 self.spec
241 }
242}
243
244#[derive(Clone )]
245pub struct SpecPointer {
246 inner: SpecPointerInner,
247}
248
249#[derive(Clone)]
250enum SpecPointerInner {
251 Pointer(Arc<Spec>),
252 Static(&'static Spec),
253 Mmap(MmapSpecHandle),
254}
255
256impl SpecPointer {
257 #[allow(non_snake_case)]
258 pub fn Pointer(spec: Arc<Spec>) -> Self {
259 Self {
260 inner: SpecPointerInner::Pointer(spec),
261 }
262 }
263
264 #[allow(non_snake_case)]
265 pub fn Static(spec: &'static Spec) -> Self {
266 Self {
267 inner: SpecPointerInner::Static(spec),
268 }
269 }
270
271 pub fn as_spec_ref(&self) -> &Spec {
272 match &self.inner {
273 SpecPointerInner::Pointer(spec) => spec,
274 SpecPointerInner::Static(spec) => spec,
275 SpecPointerInner::Mmap(handle) => {
276 InternedStore::materialize_mmap_spec(handle.archived())
277 }
278 }
279 }
280
281 pub(crate) fn view(&self) -> SpecView<'_> {
282 match &self.inner {
283 SpecPointerInner::Pointer(spec) => SpecView::Owned(spec),
284 SpecPointerInner::Static(spec) => SpecView::Owned(spec),
285 SpecPointerInner::Mmap(handle) => SpecView::Archived(handle.archived()),
286 }
287 }
288
289 pub(crate) fn from_mmap(spec: &'static ArchivedMmapSpec) -> Self {
290 Self {
291 inner: SpecPointerInner::Mmap(MmapSpecHandle::new(spec)),
292 }
293 }
294
295 pub(crate) fn matches_owned_spec(&self, spec: &Spec) -> bool {
296 match &self.inner {
297 SpecPointerInner::Mmap(handle) => {
298 handle.archived().content_hash.to_native()
299 == crate::interned_values::mmap_data_v2::spec_content_hash(spec)
300 }
301 SpecPointerInner::Pointer(existing) => existing.as_ref() == spec,
302 SpecPointerInner::Static(existing) => *existing == spec,
303 }
304 }
305
306 pub(crate) fn into_pointer(self) -> Option<Arc<Spec>> {
307 match self.inner {
308 SpecPointerInner::Pointer(spec) => Some(spec),
309 SpecPointerInner::Static(_) | SpecPointerInner::Mmap(_) => None,
310 }
311 }
312
313 pub(crate) fn is_mmap(&self) -> bool {
314 matches!(self.inner, SpecPointerInner::Mmap(_))
315 }
316}
317
318impl Serialize for SpecPointer {
319 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
320 where
321 S: Serializer,
322 {
323 match &self.inner {
324 SpecPointerInner::Pointer(spec) => spec.serialize(serializer),
325 SpecPointerInner::Static(spec) => spec.serialize(serializer),
326 SpecPointerInner::Mmap(handle) => handle.serialize(serializer),
327 }
328 }
329}
330
331impl Serialize for MmapSpecHandle {
332 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
333 where
334 S: Serializer,
335 {
336 let spec = self.spec;
337 let field_count = 7
338 + usize::from(spec.checksum.is_some())
339 + usize::from(spec.explicit_parameters.is_some())
340 + usize::from(spec.has_shared_params.is_some())
341 + usize::from(spec.is_active.is_some())
342 + usize::from(spec.version.is_some())
343 + usize::from(spec.target_app_ids.is_some())
344 + usize::from(spec.forward_all_exposures.is_some())
345 + usize::from(spec.fields_used.is_some())
346 + usize::from(spec.use_new_layer_eval.is_some());
347 let mut state = serializer.serialize_struct("Spec", field_count)?;
348 if let Some(checksum) = spec.checksum.as_ref() {
349 state.serialize_field("checksum", &MmapString(checksum))?;
350 }
351 state.serialize_field("type", &MmapString(&spec.spec_type))?;
352 state.serialize_field("salt", &MmapString(&spec.salt))?;
353 state.serialize_field("defaultValue", &MmapReturnable(&spec.default_value))?;
354 state.serialize_field("enabled", &spec.enabled)?;
355 state.serialize_field("rules", &MmapRules(&spec.rules))?;
356 state.serialize_field("idType", &MmapString(&spec.id_type))?;
357 if let Some(parameters) = spec.explicit_parameters.as_ref() {
358 state.serialize_field("explicitParameters", &MmapStrings(parameters))?;
359 }
360 state.serialize_field("entity", &MmapString(&spec.entity))?;
361 if let Some(value) = spec.has_shared_params.as_ref() {
362 state.serialize_field("hasSharedParams", value)?;
363 }
364 if let Some(value) = spec.is_active.as_ref() {
365 state.serialize_field("isActive", value)?;
366 }
367 if let Some(value) = spec.version.as_ref() {
368 state.serialize_field("version", &value.to_native())?;
369 }
370 if let Some(values) = spec.target_app_ids.as_ref() {
371 state.serialize_field("targetAppIDs", &MmapStrings(values))?;
372 }
373 if let Some(value) = spec.forward_all_exposures.as_ref() {
374 state.serialize_field("forwardAllExposures", value)?;
375 }
376 if let Some(values) = spec.fields_used.as_ref() {
377 state.serialize_field("fieldsUsed", &MmapStrings(values))?;
378 }
379 if let Some(value) = spec.use_new_layer_eval.as_ref() {
380 state.serialize_field("useNewLayerEval", value)?;
381 }
382 state.end()
383 }
384}
385
386struct MmapString<'a>(&'a ArchivedU64);
387
388impl Serialize for MmapString<'_> {
389 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
390 where
391 S: Serializer,
392 {
393 serializer.serialize_str(mmap_string(self.0))
394 }
395}
396
397struct MmapStrings<'a>(&'a ArchivedVec<ArchivedU64>);
398
399impl Serialize for MmapStrings<'_> {
400 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
401 where
402 S: Serializer,
403 {
404 let mut sequence = serializer.serialize_seq(Some(self.0.len()))?;
405 for value in self.0.iter() {
406 sequence.serialize_element(&MmapString(value))?;
407 }
408 sequence.end()
409 }
410}
411
412struct MmapRules<'a>(&'a ArchivedVec<ArchivedMmapRule>);
413
414impl Serialize for MmapRules<'_> {
415 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
416 where
417 S: Serializer,
418 {
419 let mut sequence = serializer.serialize_seq(Some(self.0.len()))?;
420 for rule in self.0.iter() {
421 sequence.serialize_element(&MmapRule(rule))?;
422 }
423 sequence.end()
424 }
425}
426
427struct MmapRule<'a>(&'a ArchivedMmapRule);
428
429impl Serialize for MmapRule<'_> {
430 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
431 where
432 S: Serializer,
433 {
434 let rule = self.0;
435 let field_count = 6
436 + usize::from(rule.salt.is_some())
437 + usize::from(rule.group_name.is_some())
438 + usize::from(rule.config_delegate.is_some())
439 + usize::from(rule.is_experiment_group.is_some())
440 + usize::from(rule.sampling_rate.is_some());
441 let mut state = serializer.serialize_struct("Rule", field_count)?;
442 state.serialize_field("name", &MmapString(&rule.name))?;
443 state.serialize_field("passPercentage", &rule.pass_percentage.to_native())?;
444 state.serialize_field("returnValue", &MmapReturnable(&rule.return_value))?;
445 state.serialize_field("id", &MmapString(&rule.id))?;
446 if let Some(value) = rule.salt.as_ref() {
447 state.serialize_field("salt", &MmapString(value))?;
448 }
449 state.serialize_field("conditions", &MmapStrings(&rule.conditions))?;
450 state.serialize_field("idType", &MmapDynamicString(&rule.id_type))?;
451 if let Some(value) = rule.group_name.as_ref() {
452 state.serialize_field("groupName", &MmapString(value))?;
453 }
454 if let Some(value) = rule.config_delegate.as_ref() {
455 state.serialize_field("configDelegate", &MmapString(value))?;
456 }
457 if let Some(value) = rule.is_experiment_group.as_ref() {
458 state.serialize_field("isExperimentGroup", value)?;
459 }
460 if let Some(value) = rule.sampling_rate.as_ref() {
461 state.serialize_field("samplingRate", &value.to_native())?;
462 }
463 state.end()
464 }
465}
466
467struct MmapDynamicString<'a>(&'a ArchivedMmapDynamicString);
468
469impl Serialize for MmapDynamicString<'_> {
470 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
471 where
472 S: Serializer,
473 {
474 let value = mmap_string(&self.0.value);
475 match value.parse::<bool>() {
476 Ok(value) => serializer.serialize_bool(value),
477 Err(_) => serializer.serialize_str(value),
478 }
479 }
480}
481
482struct MmapReturnable<'a>(&'a ArchivedMmapReturnable);
483
484impl Serialize for MmapReturnable<'_> {
485 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
486 where
487 S: Serializer,
488 {
489 InternedStore::get_mmap_returnable(self.0).serialize(serializer)
490 }
491}
492
493fn mmap_string(hash: &ArchivedU64) -> &'static str {
494 InternedStore::get_mmap_string(hash.to_native())
495 .expect("validated mmap string reference must exist")
496}
497
498impl SpecPointer {
499 pub fn from_spec(spec: Spec) -> Self {
500 Self::Pointer(Arc::new(spec))
501 }
502}
503
504impl PartialEq for SpecPointer {
505 fn eq(&self, other: &Self) -> bool {
506 match (&self.inner, &other.inner) {
507 (SpecPointerInner::Mmap(left), SpecPointerInner::Mmap(right))
508 if std::ptr::eq(left.archived(), right.archived()) =>
509 {
510 true
511 }
512 _ => self.as_spec_ref() == other.as_spec_ref(),
513 }
514 }
515}
516
517impl std::fmt::Debug for SpecPointer {
518 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
519 match &self.inner {
520 SpecPointerInner::Pointer(spec) => {
521 formatter.debug_tuple("Pointer").field(spec).finish()
522 }
523 SpecPointerInner::Static(spec) => formatter.debug_tuple("Static").field(spec).finish(),
524 SpecPointerInner::Mmap(_) => formatter.write_str("Mmap"),
525 }
526 }
527}