1use std::collections::HashMap;
2use std::fmt;
3use std::sync::Arc;
4use std::time::Duration;
5
6use rivet_envoy_client::config::HttpRequest;
7
8use crate::inspector::InspectorTabEntry;
9
10const DEFAULT_STATE_SAVE_INTERVAL: Duration = Duration::from_secs(1);
11const DEFAULT_CREATE_VARS_TIMEOUT: Duration = Duration::from_secs(5);
12const DEFAULT_CREATE_CONN_STATE_TIMEOUT: Duration = Duration::from_secs(5);
13const DEFAULT_ON_BEFORE_CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
14const DEFAULT_ON_CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
15const DEFAULT_ON_MIGRATE_TIMEOUT: Duration = Duration::from_secs(30);
16const DEFAULT_ACTION_TIMEOUT: Duration = Duration::from_secs(60);
17const DEFAULT_SLEEP_TIMEOUT: Duration = Duration::from_secs(30);
18const DEFAULT_SLEEP_GRACE_PERIOD: Duration = Duration::from_secs(15);
19const DEFAULT_CONNECTION_LIVENESS_TIMEOUT: Duration = Duration::from_millis(2500);
20const DEFAULT_CONNECTION_LIVENESS_INTERVAL: Duration = Duration::from_secs(5);
21const DEFAULT_MAX_QUEUE_SIZE: u32 = 1000;
22pub const DEFAULT_MAX_SCHEDULES: u32 = 1_000;
23const DEFAULT_MAX_QUEUE_MESSAGE_SIZE: u32 = 65_536;
24const DEFAULT_MAX_INCOMING_MESSAGE_SIZE: u32 = 65_536;
25const DEFAULT_MAX_OUTGOING_MESSAGE_SIZE: u32 = 1_048_576;
26pub(crate) const MAX_SQLITE_TRANSACTION_TRACE_STATEMENTS: usize = 32;
27
28#[derive(Clone)]
29pub enum CanHibernateWebSocket {
30 Bool(bool),
31 Callback(Arc<dyn Fn(&HttpRequest) -> bool + Send + Sync>),
32}
33
34impl fmt::Debug for CanHibernateWebSocket {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 Self::Bool(value) => f.debug_tuple("Bool").field(value).finish(),
38 Self::Callback(_) => f.write_str("Callback(..)"),
39 }
40 }
41}
42
43impl Default for CanHibernateWebSocket {
44 fn default() -> Self {
45 Self::Bool(false)
46 }
47}
48
49#[derive(Clone, Debug, Default)]
50pub struct ActorConfigOverrides {
51 pub sleep_grace_period: Option<Duration>,
52}
53
54#[derive(Clone, Debug)]
55pub struct ActionDefinition {
56 pub name: String,
57}
58
59#[derive(Clone, Debug)]
64pub struct SqliteProfilingConfig {
65 pub enabled: bool,
66 pub max_tracked_statement_fingerprints: usize,
67 pub max_tracked_transaction_fingerprints: usize,
68 pub max_prometheus_series: usize,
69 pub max_statements_per_transaction_trace: usize,
70 pub max_get_pages_requests_per_trace: usize,
71 pub max_transaction_name_bytes: usize,
72 pub slow_operation_threshold_ms: u64,
73 pub baseline_sample_rate: f64,
74 pub max_diagnostic_events_per_minute: usize,
75 pub diagnostic_event_queue_capacity: usize,
76}
77
78impl Default for SqliteProfilingConfig {
79 fn default() -> Self {
80 Self {
81 enabled: true,
82 max_tracked_statement_fingerprints: 128,
83 max_tracked_transaction_fingerprints: 8,
84 max_prometheus_series: 25_000,
85 max_statements_per_transaction_trace: 32,
86 max_get_pages_requests_per_trace: 16,
87 max_transaction_name_bytes: 128,
88 slow_operation_threshold_ms: 10,
89 baseline_sample_rate: 0.001,
90 max_diagnostic_events_per_minute: 120,
91 diagnostic_event_queue_capacity: 256,
92 }
93 }
94}
95
96#[derive(Clone, Debug, Default)]
102pub struct SqliteProfilingConfigInput {
103 pub enabled: Option<bool>,
104 pub max_tracked_statement_fingerprints: Option<u32>,
105 pub max_tracked_transaction_fingerprints: Option<u32>,
106 pub max_prometheus_series: Option<u32>,
107 pub max_statements_per_transaction_trace: Option<u32>,
108 pub max_get_pages_requests_per_trace: Option<u32>,
109 pub max_transaction_name_bytes: Option<u32>,
110 pub slow_operation_threshold_ms: Option<u32>,
111 pub baseline_sample_rate: Option<f64>,
112 pub max_diagnostic_events_per_minute: Option<u32>,
113 pub diagnostic_event_queue_capacity: Option<u32>,
114}
115
116impl SqliteProfilingConfig {
117 fn from_input(input: SqliteProfilingConfigInput) -> Self {
118 let mut config = Self::default();
119 macro_rules! set_usize {
120 ($field:ident) => {
121 if let Some(value) = input.$field {
122 config.$field = value as usize;
123 }
124 };
125 }
126 if let Some(value) = input.enabled {
127 config.enabled = value;
128 }
129 set_usize!(max_tracked_statement_fingerprints);
130 set_usize!(max_tracked_transaction_fingerprints);
131 set_usize!(max_prometheus_series);
132 set_usize!(max_statements_per_transaction_trace);
133 set_usize!(max_get_pages_requests_per_trace);
134 set_usize!(max_transaction_name_bytes);
135 if let Some(value) = input.slow_operation_threshold_ms {
136 config.slow_operation_threshold_ms = u64::from(value);
137 }
138 if let Some(value) = input.baseline_sample_rate {
139 config.baseline_sample_rate = value;
140 }
141 set_usize!(max_diagnostic_events_per_minute);
142 set_usize!(diagnostic_event_queue_capacity);
143 config
144 }
145}
146
147#[derive(Clone, Debug, Default)]
150pub struct ActorTracingConfig {
151 pub sampler: Option<f64>,
153 pub actions: HashMap<String, f64>,
155}
156
157impl ActorTracingConfig {
158 pub(crate) fn sampler_for_action(&self, action_name: &str) -> Option<f64> {
159 self.actions.get(action_name).copied().or(self.sampler)
160 }
161
162 fn validate(&self, actions: &[ActionDefinition]) -> anyhow::Result<()> {
163 for rate in self.sampler.iter().chain(self.actions.values()) {
164 anyhow::ensure!(
165 rate.is_finite() && (0.0..=1.0).contains(rate),
166 "tracing sampler must be between 0 and 1, got {rate}"
167 );
168 }
169 if actions.is_empty() {
171 return Ok(());
172 }
173 for action_name in self.actions.keys() {
174 anyhow::ensure!(
175 actions.iter().any(|action| &action.name == action_name),
176 "tracing sampler names unknown action `{action_name}`"
177 );
178 }
179 Ok(())
180 }
181}
182
183#[derive(Clone, Debug)]
184pub struct ActorConfig {
185 pub name: Option<String>,
186 pub icon: Option<String>,
187 pub has_database: bool,
190 pub remote_sqlite: bool,
191 pub sqlite_profiling: SqliteProfilingConfig,
192 pub tracing: Arc<ActorTracingConfig>,
193 pub enable_actor_runtime_socket: bool,
195 pub has_state: bool,
198 pub can_hibernate_websocket: CanHibernateWebSocket,
199 pub state_save_interval: Duration,
200 pub create_vars_timeout: Duration,
201 pub create_conn_state_timeout: Duration,
202 pub on_before_connect_timeout: Duration,
203 pub on_connect_timeout: Duration,
204 pub on_migrate_timeout: Duration,
205 pub action_timeout: Duration,
206 pub sleep_timeout: Duration,
207 pub no_sleep: bool,
208 pub sleep_grace_period: Duration,
209 pub sleep_grace_period_overridden: bool,
210 pub connection_liveness_timeout: Duration,
211 pub connection_liveness_interval: Duration,
212 pub max_queue_size: u32,
213 pub max_schedules: u32,
214 pub max_queue_message_size: u32,
215 pub max_incoming_message_size: u32,
216 pub max_outgoing_message_size: u32,
217 pub overrides: Option<ActorConfigOverrides>,
218 pub actions: Vec<ActionDefinition>,
219 pub inspector_tabs: Vec<InspectorTabEntry>,
222}
223
224#[derive(Clone, Debug, Default)]
226pub struct ActorConfigInput {
227 pub name: Option<String>,
228 pub icon: Option<String>,
229 pub has_database: Option<bool>,
230 pub remote_sqlite: Option<bool>,
231 pub sqlite_profiling: Option<SqliteProfilingConfigInput>,
232 pub tracing: Option<ActorTracingConfig>,
233 pub enable_actor_runtime_socket: Option<bool>,
234 pub has_state: Option<bool>,
235 pub can_hibernate_websocket: Option<bool>,
236 pub state_save_interval_ms: Option<u32>,
237 pub create_vars_timeout_ms: Option<u32>,
238 pub create_conn_state_timeout_ms: Option<u32>,
239 pub on_before_connect_timeout_ms: Option<u32>,
240 pub on_connect_timeout_ms: Option<u32>,
241 pub on_migrate_timeout_ms: Option<u32>,
242 pub action_timeout_ms: Option<u32>,
243 pub sleep_timeout_ms: Option<u32>,
244 pub no_sleep: Option<bool>,
245 pub sleep_grace_period_ms: Option<u32>,
246 pub connection_liveness_timeout_ms: Option<u32>,
247 pub connection_liveness_interval_ms: Option<u32>,
248 pub max_queue_size: Option<u32>,
249 pub max_schedules: Option<u32>,
250 pub max_queue_message_size: Option<u32>,
251 pub max_incoming_message_size: Option<u32>,
252 pub max_outgoing_message_size: Option<u32>,
253 pub actions: Option<Vec<ActionDefinition>>,
254 pub inspector_tabs: Option<Vec<InspectorTabEntry>>,
255}
256
257impl ActorConfig {
258 pub fn from_input(config: ActorConfigInput) -> Self {
259 let mut actor_config = Self {
260 name: config.name,
261 icon: config.icon,
262 has_database: config.has_database.unwrap_or(false),
263 remote_sqlite: config.remote_sqlite.unwrap_or(false),
264 sqlite_profiling: config
265 .sqlite_profiling
266 .map(SqliteProfilingConfig::from_input)
267 .unwrap_or_default(),
268 tracing: Arc::new(config.tracing.unwrap_or_default()),
269 enable_actor_runtime_socket: config.enable_actor_runtime_socket.unwrap_or(false),
270 has_state: config.has_state.unwrap_or(false),
271 ..Self::default()
272 };
273 if let Some(can_hibernate_websocket) = config.can_hibernate_websocket {
274 actor_config.can_hibernate_websocket =
275 CanHibernateWebSocket::Bool(can_hibernate_websocket);
276 }
277 if let Some(value) = config.state_save_interval_ms {
278 actor_config.state_save_interval = duration_ms(value);
279 }
280 if let Some(value) = config.create_vars_timeout_ms {
281 actor_config.create_vars_timeout = duration_ms(value);
282 }
283 if let Some(value) = config.create_conn_state_timeout_ms {
284 actor_config.create_conn_state_timeout = duration_ms(value);
285 }
286 if let Some(value) = config.on_before_connect_timeout_ms {
287 actor_config.on_before_connect_timeout = duration_ms(value);
288 }
289 if let Some(value) = config.on_connect_timeout_ms {
290 actor_config.on_connect_timeout = duration_ms(value);
291 }
292 if let Some(value) = config.on_migrate_timeout_ms {
293 actor_config.on_migrate_timeout = duration_ms(value);
294 }
295 if let Some(value) = config.action_timeout_ms {
296 actor_config.action_timeout = duration_ms(value);
297 }
298 if let Some(value) = config.sleep_timeout_ms {
299 actor_config.sleep_timeout = duration_ms(value);
300 }
301 if let Some(value) = config.no_sleep {
302 actor_config.no_sleep = value;
303 }
304 if let Some(value) = config.sleep_grace_period_ms {
305 actor_config.sleep_grace_period = duration_ms(value);
306 actor_config.sleep_grace_period_overridden = true;
307 }
308 if let Some(value) = config.connection_liveness_timeout_ms {
309 actor_config.connection_liveness_timeout = duration_ms(value);
310 }
311 if let Some(value) = config.connection_liveness_interval_ms {
312 actor_config.connection_liveness_interval = duration_ms(value);
313 }
314 if let Some(value) = config.max_queue_size {
315 actor_config.max_queue_size = value;
316 }
317 if let Some(value) = config.max_schedules {
318 actor_config.max_schedules = value;
319 }
320 if let Some(value) = config.max_queue_message_size {
321 actor_config.max_queue_message_size = value;
322 }
323 if let Some(value) = config.max_incoming_message_size {
324 actor_config.max_incoming_message_size = value;
325 }
326 if let Some(value) = config.max_outgoing_message_size {
327 actor_config.max_outgoing_message_size = value;
328 }
329 if let Some(actions) = config.actions {
330 actor_config.actions = actions;
331 }
332 if let Some(tabs) = config.inspector_tabs {
333 actor_config.inspector_tabs = tabs;
334 }
335
336 actor_config
337 }
338
339 pub fn effective_sleep_grace_period(&self) -> Duration {
340 cap_duration(
341 self.sleep_grace_period,
342 self.overrides
343 .as_ref()
344 .and_then(|overrides| overrides.sleep_grace_period),
345 )
346 }
347
348 pub fn validate(&self) -> anyhow::Result<()> {
353 crate::inspector::validate_inspector_tabs(&self.inspector_tabs)?;
354 self.tracing.validate(&self.actions)?;
355 anyhow::ensure!(
356 self.sqlite_profiling.baseline_sample_rate.is_finite()
357 && (0.0..=1.0).contains(&self.sqlite_profiling.baseline_sample_rate),
358 "SQLite profiling baselineSampleRate must be between 0 and 1"
359 );
360 anyhow::ensure!(
361 self.sqlite_profiling.max_get_pages_requests_per_trace <= 16,
362 "SQLite profiling maxGetPagesRequestsPerTrace must be at most 16"
363 );
364 anyhow::ensure!(
365 self.sqlite_profiling.max_statements_per_transaction_trace
366 <= MAX_SQLITE_TRANSACTION_TRACE_STATEMENTS,
367 "SQLite profiling maxStatementsPerTransactionTrace must be at most 32"
368 );
369 anyhow::ensure!(
370 self.sqlite_profiling.max_transaction_name_bytes > 0,
371 "SQLite profiling maxTransactionNameBytes must be greater than zero"
372 );
373 Ok(())
374 }
375}
376
377impl Default for ActorConfig {
378 fn default() -> Self {
379 Self {
380 name: None,
381 icon: None,
382 has_database: false,
383 remote_sqlite: false,
384 sqlite_profiling: SqliteProfilingConfig::default(),
385 tracing: Arc::default(),
386 enable_actor_runtime_socket: false,
387 has_state: false,
388 can_hibernate_websocket: CanHibernateWebSocket::default(),
389 state_save_interval: DEFAULT_STATE_SAVE_INTERVAL,
390 create_vars_timeout: DEFAULT_CREATE_VARS_TIMEOUT,
391 create_conn_state_timeout: DEFAULT_CREATE_CONN_STATE_TIMEOUT,
392 on_before_connect_timeout: DEFAULT_ON_BEFORE_CONNECT_TIMEOUT,
393 on_connect_timeout: DEFAULT_ON_CONNECT_TIMEOUT,
394 on_migrate_timeout: DEFAULT_ON_MIGRATE_TIMEOUT,
395 action_timeout: DEFAULT_ACTION_TIMEOUT,
396 sleep_timeout: DEFAULT_SLEEP_TIMEOUT,
397 no_sleep: false,
398 sleep_grace_period: DEFAULT_SLEEP_GRACE_PERIOD,
399 sleep_grace_period_overridden: false,
400 connection_liveness_timeout: DEFAULT_CONNECTION_LIVENESS_TIMEOUT,
401 connection_liveness_interval: DEFAULT_CONNECTION_LIVENESS_INTERVAL,
402 max_queue_size: DEFAULT_MAX_QUEUE_SIZE,
403 max_schedules: DEFAULT_MAX_SCHEDULES,
404 max_queue_message_size: DEFAULT_MAX_QUEUE_MESSAGE_SIZE,
405 max_incoming_message_size: DEFAULT_MAX_INCOMING_MESSAGE_SIZE,
406 max_outgoing_message_size: DEFAULT_MAX_OUTGOING_MESSAGE_SIZE,
407 overrides: None,
408 actions: Vec::new(),
409 inspector_tabs: Vec::new(),
410 }
411 }
412}
413
414fn cap_duration(duration: Duration, override_duration: Option<Duration>) -> Duration {
415 if let Some(override_duration) = override_duration {
416 duration.min(override_duration)
417 } else {
418 duration
419 }
420}
421
422fn duration_ms(value: u32) -> Duration {
423 Duration::from_millis(u64::from(value))
424}
425
426#[cfg(test)]
428#[path = "../../tests/config.rs"]
429mod tests;