Skip to main content

statsig_rust/event_logging/event_queue/
queued_layer_param_expo.rs

1use crate::{
2    evaluation::evaluation_types::ExtraExposureInfo,
3    event_logging::{
4        event_logger::ExposureTrigger,
5        exposure_sampling::{EvtSamplingDecision, ExposureSamplingKey},
6        exposure_utils::{get_metadata_with_details, get_statsig_metadata_with_sampling_decision},
7        statsig_event::StatsigEvent,
8        statsig_event_internal::{StatsigEventInternal, LAYER_EXPOSURE_EVENT_NAME},
9    },
10    hashing::ahash_str,
11    interned_string::InternedString,
12    statsig_types::Layer,
13    user::StatsigUserLoggable,
14    EvaluationDetails, SecondaryExposure,
15};
16
17use super::queued_event::{EnqueueOperation, QueuedEvent, QueuedExposure};
18use crate::event_logging::statsig_event::string_metadata_to_value_metadata;
19
20pub enum EnqueueLayerParamExpoOp<'a> {
21    LayerRef(u64, &'a Layer, &'a str, ExposureTrigger),
22    LayerOwned(u64, Box<Layer>, String, ExposureTrigger),
23}
24
25impl<'a> EnqueueLayerParamExpoOp<'a> {
26    fn get_layer_ref(&'a self) -> &'a Layer {
27        match self {
28            EnqueueLayerParamExpoOp::LayerRef(_, layer, _, _) => layer,
29            EnqueueLayerParamExpoOp::LayerOwned(_, layer, _, _) => layer,
30        }
31    }
32
33    fn get_parameter_name_ref(&'a self) -> &'a str {
34        match self {
35            EnqueueLayerParamExpoOp::LayerRef(_, _, parameter_name, _) => parameter_name,
36            EnqueueLayerParamExpoOp::LayerOwned(_, _, parameter_name, _) => parameter_name.as_str(),
37        }
38    }
39}
40
41impl EnqueueOperation for EnqueueLayerParamExpoOp<'_> {
42    fn as_exposure(&self) -> Option<&impl QueuedExposure<'_>> {
43        Some(self)
44    }
45
46    fn into_queued_event(self, sampling_decision: EvtSamplingDecision) -> QueuedEvent {
47        let event = match self {
48            EnqueueLayerParamExpoOp::LayerRef(exposure_time, layer, parameter_name, trigger) => {
49                extract_from_layer_ref(
50                    exposure_time,
51                    layer,
52                    parameter_name,
53                    trigger,
54                    sampling_decision,
55                )
56            }
57            EnqueueLayerParamExpoOp::LayerOwned(exposure_time, layer, parameter_name, trigger) => {
58                extract_from_layer_owned(
59                    exposure_time,
60                    layer,
61                    parameter_name,
62                    trigger,
63                    sampling_decision,
64                )
65            }
66        };
67
68        QueuedEvent::LayerParamExposure(event)
69    }
70}
71
72impl<'a> QueuedExposure<'a> for EnqueueLayerParamExpoOp<'a> {
73    fn create_exposure_sampling_key(&self) -> ExposureSamplingKey {
74        let layer = self.get_layer_ref();
75
76        let user_data = &layer.__user.data;
77        let evaluation = layer.__evaluation.as_ref().map(|e| &e.base);
78        let unit_id_type = layer
79            .__evaluation
80            .as_ref()
81            .and_then(|e| e.id_type.as_ref())
82            .map(|id| id.as_str());
83        // todo: use Cow and pre-hash the parameter name
84        let pname = self.get_parameter_name_ref();
85        let pname_hash = ahash_str(pname);
86        ExposureSamplingKey::new(evaluation, user_data, pname_hash, unit_id_type)
87    }
88
89    fn get_rule_id_ref(&'a self) -> &'a str {
90        &self.get_layer_ref().rule_id
91    }
92
93    fn get_extra_exposure_info_ref(&'a self) -> Option<&'a ExtraExposureInfo> {
94        get_layer_exposure_info(self.get_layer_ref())
95    }
96}
97
98pub struct QueuedLayerParamExposureEvent {
99    pub user: StatsigUserLoggable,
100    pub layer_name: String,
101    pub rule_id: String,
102    pub parameter_name: String,
103    pub secondary_exposures: Option<Vec<SecondaryExposure>>,
104    pub evaluation_details: EvaluationDetails,
105    pub version: Option<u32>,
106    pub exposure_trigger: ExposureTrigger,
107    pub sampling_decision: EvtSamplingDecision,
108    pub override_config_name: Option<InternedString>,
109    pub is_explicit: bool,
110    pub allocated_experiment: Option<InternedString>,
111    pub exposure_time: u64,
112}
113
114impl QueuedLayerParamExposureEvent {
115    pub fn into_statsig_event_internal(self) -> StatsigEventInternal {
116        let mut metadata = get_metadata_with_details(self.evaluation_details);
117        metadata.insert("config".into(), self.layer_name);
118        metadata.insert("ruleID".into(), self.rule_id);
119        metadata.insert(
120            "allocatedExperiment".into(),
121            self.allocated_experiment
122                .unwrap_or_default()
123                .unperformant_to_string(),
124        );
125        metadata.insert("parameterName".into(), self.parameter_name);
126        metadata.insert("isExplicitParameter".into(), self.is_explicit.to_string());
127
128        if let Some(version) = self.version {
129            metadata.insert("configVersion".into(), version.to_string());
130        }
131
132        if self.exposure_trigger == ExposureTrigger::Manual {
133            metadata.insert("isManualExposure".into(), "true".into());
134        }
135
136        if let Some(override_config_name) = self.override_config_name {
137            metadata.insert(
138                "overrideConfigName".into(),
139                override_config_name.unperformant_to_string(),
140            );
141        }
142
143        let statsig_metadata = get_statsig_metadata_with_sampling_decision(self.sampling_decision);
144
145        let event = StatsigEvent {
146            event_name: LAYER_EXPOSURE_EVENT_NAME.into(),
147            value: None,
148            metadata: Some(string_metadata_to_value_metadata(metadata)),
149            statsig_metadata: Some(statsig_metadata),
150        };
151
152        StatsigEventInternal::new(
153            self.exposure_time,
154            self.user,
155            event,
156            Some(self.secondary_exposures.unwrap_or_default()),
157        )
158    }
159}
160
161type ExtractFromEvaluationResult = (
162    bool,
163    Option<InternedString>,
164    Option<Vec<SecondaryExposure>>,
165    Option<u32>,
166    Option<InternedString>,
167);
168
169fn extract_exposure_info(layer: &Layer, parameter_name: &str) -> ExtractFromEvaluationResult {
170    let evaluation = match layer.__evaluation.as_ref() {
171        Some(eval) => eval,
172        None => return (false, None, None, None, None),
173    };
174
175    let is_explicit = evaluation.explicit_parameters.contains(parameter_name);
176    let secondary_exposures;
177    let mut allocated_experiment = None;
178
179    if is_explicit {
180        allocated_experiment = evaluation.allocated_experiment_name.clone();
181        secondary_exposures = Some(evaluation.base.secondary_exposures.clone());
182    } else {
183        secondary_exposures = evaluation.undelegated_secondary_exposures.clone();
184    }
185
186    // version might be on the top level or the exposure info
187    let mut version = layer.__version;
188    let mut override_config_name = None;
189
190    if let Some(exposure_info) = get_layer_exposure_info(layer) {
191        version = exposure_info.version;
192        override_config_name = exposure_info.override_config_name.clone();
193    }
194
195    (
196        is_explicit,
197        allocated_experiment,
198        secondary_exposures,
199        version,
200        override_config_name,
201    )
202}
203
204fn get_layer_exposure_info(layer: &Layer) -> Option<&ExtraExposureInfo> {
205    layer
206        .__evaluation
207        .as_ref()
208        .and_then(|eval| eval.base.exposure_info.as_ref())
209        .or(layer.__exposure_info.as_ref())
210}
211
212fn extract_from_layer_ref(
213    exposure_time: u64,
214    layer: &Layer,
215    param_name: &str,
216    trigger: ExposureTrigger,
217    sampling_decision: EvtSamplingDecision,
218) -> QueuedLayerParamExposureEvent {
219    let parameter_name = param_name.to_string();
220    let (is_explicit, allocated_experiment, secondary_exposures, version, override_config_name) =
221        extract_exposure_info(layer, &parameter_name);
222
223    let rule_id = match layer.__parameter_rule_ids {
224        Some(ref rule_ids) => rule_ids
225            .get(&InternedString::from_str_ref(param_name))
226            .map(|s| s.unperformant_to_string())
227            .unwrap_or_else(|| layer.rule_id.clone()),
228        None => layer.rule_id.clone(),
229    };
230
231    QueuedLayerParamExposureEvent {
232        exposure_time,
233        user: layer.__user.clone(),
234        layer_name: layer.name.clone(),
235        rule_id,
236        parameter_name,
237        exposure_trigger: trigger,
238        evaluation_details: layer.details.clone(),
239        version,
240        sampling_decision,
241        override_config_name,
242        secondary_exposures,
243        is_explicit,
244        allocated_experiment,
245    }
246}
247
248fn extract_from_layer_owned(
249    exposure_time: u64,
250    layer: Box<Layer>,
251    parameter_name: String,
252    trigger: ExposureTrigger,
253    sampling_decision: EvtSamplingDecision,
254) -> QueuedLayerParamExposureEvent {
255    let (is_explicit, allocated_experiment, secondary_exposures, version, override_config_name) =
256        extract_exposure_info(&layer, &parameter_name);
257
258    let rule_id = match layer.__parameter_rule_ids {
259        Some(ref rule_ids) => rule_ids
260            .get(&InternedString::from_str_ref(parameter_name.as_str()))
261            .map(|s| s.unperformant_to_string())
262            .unwrap_or_else(|| layer.rule_id.clone()),
263        None => layer.rule_id.clone(),
264    };
265
266    QueuedLayerParamExposureEvent {
267        exposure_time,
268        user: layer.__user,
269        layer_name: layer.name,
270        rule_id,
271        parameter_name,
272        exposure_trigger: trigger,
273        evaluation_details: layer.details,
274        version,
275        sampling_decision,
276        override_config_name,
277        secondary_exposures,
278        is_explicit,
279        allocated_experiment,
280    }
281}