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