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 queries: Vec<String>,
66 pub fingerprints: Vec<String>,
67 pub max_tracked_statement_fingerprints: usize,
68 pub max_tracked_transaction_fingerprints: usize,
69 pub max_prometheus_series: usize,
70 pub max_statements_per_transaction_trace: usize,
71 pub max_get_pages_requests_per_trace: usize,
72 pub max_transaction_name_bytes: usize,
73 pub slow_operation_threshold_ms: u64,
74 pub baseline_sample_rate: f64,
75 pub max_diagnostic_events_per_minute: usize,
76 pub diagnostic_event_queue_capacity: usize,
77}
78
79impl Default for SqliteProfilingConfig {
80 fn default() -> Self {
81 Self {
82 queries: Vec::new(),
83 fingerprints: Vec::new(),
84 max_tracked_statement_fingerprints: 128,
85 max_tracked_transaction_fingerprints: 8,
86 max_prometheus_series: 25_000,
87 max_statements_per_transaction_trace: 32,
88 max_get_pages_requests_per_trace: 16,
89 max_transaction_name_bytes: 128,
90 slow_operation_threshold_ms: 10,
91 baseline_sample_rate: 0.001,
92 max_diagnostic_events_per_minute: 120,
93 diagnostic_event_queue_capacity: 256,
94 }
95 }
96}
97
98#[derive(Clone, Debug, Default)]
104pub struct SqliteProfilingConfigInput {
105 pub queries: Option<Vec<String>>,
106 pub fingerprints: Option<Vec<String>>,
107 pub max_tracked_statement_fingerprints: Option<u32>,
108 pub max_tracked_transaction_fingerprints: Option<u32>,
109 pub max_prometheus_series: Option<u32>,
110 pub max_statements_per_transaction_trace: Option<u32>,
111 pub max_get_pages_requests_per_trace: Option<u32>,
112 pub max_transaction_name_bytes: Option<u32>,
113 pub slow_operation_threshold_ms: Option<u32>,
114 pub baseline_sample_rate: Option<f64>,
115 pub max_diagnostic_events_per_minute: Option<u32>,
116 pub diagnostic_event_queue_capacity: Option<u32>,
117}
118
119#[derive(Default, serde::Deserialize)]
121#[serde(default, deny_unknown_fields)]
122struct SqliteProfilingQueries {
123 queries: Vec<String>,
124 fingerprints: Vec<String>,
125}
126
127impl SqliteProfilingConfig {
128 pub(crate) fn with_process_override(self) -> Self {
130 self.with_query_override(std::env::var("RIVETKIT_SQLITE_PROFILING").ok().as_deref())
131 }
132
133 fn with_query_override(mut self, process_override: Option<&str>) -> Self {
134 if let Some(value) = process_override {
135 let selection = match serde_json::from_str::<SqliteProfilingQueries>(value) {
136 Ok(selection) => selection,
137 Err(error) => {
138 tracing::warn!(%error, "invalid RIVETKIT_SQLITE_PROFILING; expected a JSON object with queries or fingerprints arrays; using profiling without fingerprint labels");
139 SqliteProfilingQueries::default()
140 }
141 };
142 self.queries = selection.queries;
143 self.fingerprints = selection.fingerprints;
144 }
145 self
146 }
147
148 fn from_input(input: SqliteProfilingConfigInput) -> Self {
149 let mut config = Self::default();
150 macro_rules! set_usize {
151 ($field:ident) => {
152 if let Some(value) = input.$field {
153 config.$field = value as usize;
154 }
155 };
156 }
157 config.queries = input.queries.unwrap_or_default();
158 config.fingerprints = input.fingerprints.unwrap_or_default();
159 set_usize!(max_tracked_statement_fingerprints);
160 set_usize!(max_tracked_transaction_fingerprints);
161 set_usize!(max_prometheus_series);
162 set_usize!(max_statements_per_transaction_trace);
163 set_usize!(max_get_pages_requests_per_trace);
164 set_usize!(max_transaction_name_bytes);
165 if let Some(value) = input.slow_operation_threshold_ms {
166 config.slow_operation_threshold_ms = u64::from(value);
167 }
168 if let Some(value) = input.baseline_sample_rate {
169 config.baseline_sample_rate = value;
170 }
171 set_usize!(max_diagnostic_events_per_minute);
172 set_usize!(diagnostic_event_queue_capacity);
173 config
174 }
175}
176
177#[derive(Clone, Debug, Default)]
180pub struct ActorTracingConfig {
181 pub sampler: Option<f64>,
183 pub actions: HashMap<String, f64>,
185}
186
187impl ActorTracingConfig {
188 pub(crate) fn sampler_for_action(&self, action_name: &str) -> Option<f64> {
189 self.actions.get(action_name).copied().or(self.sampler)
190 }
191
192 fn validate(&self, actions: &[ActionDefinition]) -> anyhow::Result<()> {
193 for rate in self.sampler.iter().chain(self.actions.values()) {
194 anyhow::ensure!(
195 rate.is_finite() && (0.0..=1.0).contains(rate),
196 "tracing sampler must be between 0 and 1, got {rate}"
197 );
198 }
199 if actions.is_empty() {
201 return Ok(());
202 }
203 for action_name in self.actions.keys() {
204 anyhow::ensure!(
205 actions.iter().any(|action| &action.name == action_name),
206 "tracing sampler names unknown action `{action_name}`"
207 );
208 }
209 Ok(())
210 }
211}
212
213#[derive(Clone, Debug)]
214pub struct ActorConfig {
215 pub name: Option<String>,
216 pub icon: Option<String>,
217 pub has_database: bool,
220 pub remote_sqlite: bool,
221 pub sqlite_profiling: SqliteProfilingConfig,
222 pub tracing: Arc<ActorTracingConfig>,
223 pub enable_actor_runtime_socket: bool,
225 pub has_state: bool,
228 pub can_hibernate_websocket: CanHibernateWebSocket,
229 pub state_save_interval: Duration,
230 pub create_vars_timeout: Duration,
231 pub create_conn_state_timeout: Duration,
232 pub on_before_connect_timeout: Duration,
233 pub on_connect_timeout: Duration,
234 pub on_migrate_timeout: Duration,
235 pub action_timeout: Duration,
236 pub sleep_timeout: Duration,
237 pub no_sleep: bool,
238 pub sleep_grace_period: Duration,
239 pub sleep_grace_period_overridden: bool,
240 pub connection_liveness_timeout: Duration,
241 pub connection_liveness_interval: Duration,
242 pub max_queue_size: u32,
243 pub max_schedules: u32,
244 pub max_queue_message_size: u32,
245 pub max_incoming_message_size: u32,
246 pub max_outgoing_message_size: u32,
247 pub overrides: Option<ActorConfigOverrides>,
248 pub actions: Vec<ActionDefinition>,
249 pub inspector_tabs: Vec<InspectorTabEntry>,
252}
253
254#[derive(Clone, Debug, Default)]
256pub struct ActorConfigInput {
257 pub name: Option<String>,
258 pub icon: Option<String>,
259 pub has_database: Option<bool>,
260 pub remote_sqlite: Option<bool>,
261 pub sqlite_profiling: Option<SqliteProfilingConfigInput>,
262 pub tracing: Option<ActorTracingConfig>,
263 pub enable_actor_runtime_socket: Option<bool>,
264 pub has_state: Option<bool>,
265 pub can_hibernate_websocket: Option<bool>,
266 pub state_save_interval_ms: Option<u32>,
267 pub create_vars_timeout_ms: Option<u32>,
268 pub create_conn_state_timeout_ms: Option<u32>,
269 pub on_before_connect_timeout_ms: Option<u32>,
270 pub on_connect_timeout_ms: Option<u32>,
271 pub on_migrate_timeout_ms: Option<u32>,
272 pub action_timeout_ms: Option<u32>,
273 pub sleep_timeout_ms: Option<u32>,
274 pub no_sleep: Option<bool>,
275 pub sleep_grace_period_ms: Option<u32>,
276 pub connection_liveness_timeout_ms: Option<u32>,
277 pub connection_liveness_interval_ms: Option<u32>,
278 pub max_queue_size: Option<u32>,
279 pub max_schedules: Option<u32>,
280 pub max_queue_message_size: Option<u32>,
281 pub max_incoming_message_size: Option<u32>,
282 pub max_outgoing_message_size: Option<u32>,
283 pub actions: Option<Vec<ActionDefinition>>,
284 pub inspector_tabs: Option<Vec<InspectorTabEntry>>,
285}
286
287impl ActorConfig {
288 pub fn from_input(config: ActorConfigInput) -> Self {
289 let mut actor_config = Self {
290 name: config.name,
291 icon: config.icon,
292 has_database: config.has_database.unwrap_or(false),
293 remote_sqlite: config.remote_sqlite.unwrap_or(false),
294 sqlite_profiling: config
295 .sqlite_profiling
296 .map(SqliteProfilingConfig::from_input)
297 .unwrap_or_default(),
298 tracing: Arc::new(config.tracing.unwrap_or_default()),
299 enable_actor_runtime_socket: config.enable_actor_runtime_socket.unwrap_or(false),
300 has_state: config.has_state.unwrap_or(false),
301 ..Self::default()
302 };
303 if let Some(can_hibernate_websocket) = config.can_hibernate_websocket {
304 actor_config.can_hibernate_websocket =
305 CanHibernateWebSocket::Bool(can_hibernate_websocket);
306 }
307 if let Some(value) = config.state_save_interval_ms {
308 actor_config.state_save_interval = duration_ms(value);
309 }
310 if let Some(value) = config.create_vars_timeout_ms {
311 actor_config.create_vars_timeout = duration_ms(value);
312 }
313 if let Some(value) = config.create_conn_state_timeout_ms {
314 actor_config.create_conn_state_timeout = duration_ms(value);
315 }
316 if let Some(value) = config.on_before_connect_timeout_ms {
317 actor_config.on_before_connect_timeout = duration_ms(value);
318 }
319 if let Some(value) = config.on_connect_timeout_ms {
320 actor_config.on_connect_timeout = duration_ms(value);
321 }
322 if let Some(value) = config.on_migrate_timeout_ms {
323 actor_config.on_migrate_timeout = duration_ms(value);
324 }
325 if let Some(value) = config.action_timeout_ms {
326 actor_config.action_timeout = duration_ms(value);
327 }
328 if let Some(value) = config.sleep_timeout_ms {
329 actor_config.sleep_timeout = duration_ms(value);
330 }
331 if let Some(value) = config.no_sleep {
332 actor_config.no_sleep = value;
333 }
334 if let Some(value) = config.sleep_grace_period_ms {
335 actor_config.sleep_grace_period = duration_ms(value);
336 actor_config.sleep_grace_period_overridden = true;
337 }
338 if let Some(value) = config.connection_liveness_timeout_ms {
339 actor_config.connection_liveness_timeout = duration_ms(value);
340 }
341 if let Some(value) = config.connection_liveness_interval_ms {
342 actor_config.connection_liveness_interval = duration_ms(value);
343 }
344 if let Some(value) = config.max_queue_size {
345 actor_config.max_queue_size = value;
346 }
347 if let Some(value) = config.max_schedules {
348 actor_config.max_schedules = value;
349 }
350 if let Some(value) = config.max_queue_message_size {
351 actor_config.max_queue_message_size = value;
352 }
353 if let Some(value) = config.max_incoming_message_size {
354 actor_config.max_incoming_message_size = value;
355 }
356 if let Some(value) = config.max_outgoing_message_size {
357 actor_config.max_outgoing_message_size = value;
358 }
359 if let Some(actions) = config.actions {
360 actor_config.actions = actions;
361 }
362 if let Some(tabs) = config.inspector_tabs {
363 actor_config.inspector_tabs = tabs;
364 }
365
366 actor_config
367 }
368
369 pub fn effective_sleep_grace_period(&self) -> Duration {
370 cap_duration(
371 self.sleep_grace_period,
372 self.overrides
373 .as_ref()
374 .and_then(|overrides| overrides.sleep_grace_period),
375 )
376 }
377
378 pub fn validate(&self) -> anyhow::Result<()> {
383 crate::inspector::validate_inspector_tabs(&self.inspector_tabs)?;
384 self.tracing.validate(&self.actions)?;
385 anyhow::ensure!(
386 self.sqlite_profiling.baseline_sample_rate.is_finite()
387 && (0.0..=1.0).contains(&self.sqlite_profiling.baseline_sample_rate),
388 "SQLite profiling baselineSampleRate must be between 0 and 1"
389 );
390 anyhow::ensure!(
391 self.sqlite_profiling.max_get_pages_requests_per_trace <= 16,
392 "SQLite profiling maxGetPagesRequestsPerTrace must be at most 16"
393 );
394 anyhow::ensure!(
395 self.sqlite_profiling.max_statements_per_transaction_trace
396 <= MAX_SQLITE_TRANSACTION_TRACE_STATEMENTS,
397 "SQLite profiling maxStatementsPerTransactionTrace must be at most 32"
398 );
399 anyhow::ensure!(
400 self.sqlite_profiling.max_transaction_name_bytes > 0,
401 "SQLite profiling maxTransactionNameBytes must be greater than zero"
402 );
403 Ok(())
404 }
405}
406
407impl Default for ActorConfig {
408 fn default() -> Self {
409 Self {
410 name: None,
411 icon: None,
412 has_database: false,
413 remote_sqlite: false,
414 sqlite_profiling: SqliteProfilingConfig::default(),
415 tracing: Arc::default(),
416 enable_actor_runtime_socket: false,
417 has_state: false,
418 can_hibernate_websocket: CanHibernateWebSocket::default(),
419 state_save_interval: DEFAULT_STATE_SAVE_INTERVAL,
420 create_vars_timeout: DEFAULT_CREATE_VARS_TIMEOUT,
421 create_conn_state_timeout: DEFAULT_CREATE_CONN_STATE_TIMEOUT,
422 on_before_connect_timeout: DEFAULT_ON_BEFORE_CONNECT_TIMEOUT,
423 on_connect_timeout: DEFAULT_ON_CONNECT_TIMEOUT,
424 on_migrate_timeout: DEFAULT_ON_MIGRATE_TIMEOUT,
425 action_timeout: DEFAULT_ACTION_TIMEOUT,
426 sleep_timeout: DEFAULT_SLEEP_TIMEOUT,
427 no_sleep: false,
428 sleep_grace_period: DEFAULT_SLEEP_GRACE_PERIOD,
429 sleep_grace_period_overridden: false,
430 connection_liveness_timeout: DEFAULT_CONNECTION_LIVENESS_TIMEOUT,
431 connection_liveness_interval: DEFAULT_CONNECTION_LIVENESS_INTERVAL,
432 max_queue_size: DEFAULT_MAX_QUEUE_SIZE,
433 max_schedules: DEFAULT_MAX_SCHEDULES,
434 max_queue_message_size: DEFAULT_MAX_QUEUE_MESSAGE_SIZE,
435 max_incoming_message_size: DEFAULT_MAX_INCOMING_MESSAGE_SIZE,
436 max_outgoing_message_size: DEFAULT_MAX_OUTGOING_MESSAGE_SIZE,
437 overrides: None,
438 actions: Vec::new(),
439 inspector_tabs: Vec::new(),
440 }
441 }
442}
443
444fn cap_duration(duration: Duration, override_duration: Option<Duration>) -> Duration {
445 if let Some(override_duration) = override_duration {
446 duration.min(override_duration)
447 } else {
448 duration
449 }
450}
451
452fn duration_ms(value: u32) -> Duration {
453 Duration::from_millis(u64::from(value))
454}
455
456