1#[cfg(feature = "config")]
26pub mod env_interpolate;
27
28#[cfg(feature = "config")]
29pub mod parse;
30
31#[cfg(feature = "config")]
32pub mod normalize;
33
34#[cfg(feature = "config")]
35pub mod expand;
36
37pub mod timing;
38
39#[cfg(feature = "config")]
40pub mod compile_after;
41
42#[cfg(feature = "config")]
43pub mod prepare;
44
45use std::collections::BTreeMap;
46
47use crate::config::{
48 BurstConfig, CardinalitySpikeConfig, DistributionConfig, DynamicLabelConfig, GapConfig,
49 OnSinkError,
50};
51use crate::encoder::EncoderConfig;
52use crate::generator::{GeneratorConfig, LogGeneratorConfig};
53use crate::packs::MetricOverride;
54use crate::sink::SinkConfig;
55
56#[derive(Debug, Clone)]
76#[cfg_attr(
77 feature = "config",
78 derive(serde::Serialize, serde::Deserialize),
79 serde(deny_unknown_fields)
80)]
81pub struct ScenarioFile {
82 pub version: u32,
84 #[cfg_attr(feature = "config", serde(default))]
88 pub scenario_name: Option<String>,
89 #[cfg_attr(feature = "config", serde(default))]
95 pub category: Option<String>,
96 #[cfg_attr(feature = "config", serde(default))]
100 pub description: Option<String>,
101 #[cfg_attr(feature = "config", serde(default))]
103 pub defaults: Option<Defaults>,
104 pub scenarios: Vec<Entry>,
106}
107
108#[derive(Debug, Clone)]
114#[cfg_attr(
115 feature = "config",
116 derive(serde::Serialize, serde::Deserialize),
117 serde(deny_unknown_fields)
118)]
119pub struct Defaults {
120 #[cfg_attr(feature = "config", serde(default))]
122 pub rate: Option<f64>,
123 #[cfg_attr(feature = "config", serde(default))]
127 pub duration: Option<String>,
128 #[cfg_attr(feature = "config", serde(default))]
130 pub encoder: Option<EncoderConfig>,
131 #[cfg_attr(feature = "config", serde(default))]
133 pub sink: Option<SinkConfig>,
134 #[cfg_attr(feature = "config", serde(default))]
136 pub labels: Option<BTreeMap<String, String>>,
137 #[cfg_attr(feature = "config", serde(default))]
139 pub on_sink_error: Option<OnSinkError>,
140 #[cfg_attr(
142 feature = "config",
143 serde(default, rename = "while", skip_serializing_if = "Option::is_none")
144 )]
145 pub while_clause: Option<WhileClause>,
146 #[cfg_attr(
148 feature = "config",
149 serde(default, rename = "delay", skip_serializing_if = "Option::is_none")
150 )]
151 pub delay_clause: Option<DelayClause>,
152}
153
154#[derive(Debug, Clone)]
164#[cfg_attr(
165 feature = "config",
166 derive(serde::Serialize, serde::Deserialize),
167 serde(deny_unknown_fields)
168)]
169pub struct Entry {
170 #[cfg_attr(feature = "config", serde(default))]
172 pub id: Option<String>,
173 pub signal_type: String,
175 #[cfg_attr(feature = "config", serde(default))]
177 pub name: Option<String>,
178 #[cfg_attr(feature = "config", serde(default))]
180 pub rate: Option<f64>,
181 #[cfg_attr(feature = "config", serde(default))]
183 pub duration: Option<String>,
184 #[cfg_attr(feature = "config", serde(default))]
186 pub generator: Option<GeneratorConfig>,
187 #[cfg_attr(feature = "config", serde(default))]
192 pub log_generator: Option<LogGeneratorConfig>,
193 #[cfg_attr(feature = "config", serde(default))]
195 pub labels: Option<BTreeMap<String, String>>,
196 #[cfg_attr(feature = "config", serde(default))]
198 pub dynamic_labels: Option<Vec<DynamicLabelConfig>>,
199 #[cfg_attr(feature = "config", serde(default))]
201 pub encoder: Option<EncoderConfig>,
202 #[cfg_attr(feature = "config", serde(default))]
204 pub sink: Option<SinkConfig>,
205 #[cfg_attr(feature = "config", serde(default))]
207 pub jitter: Option<f64>,
208 #[cfg_attr(feature = "config", serde(default))]
210 pub jitter_seed: Option<u64>,
211 #[cfg_attr(feature = "config", serde(default))]
213 pub gaps: Option<GapConfig>,
214 #[cfg_attr(feature = "config", serde(default))]
216 pub bursts: Option<BurstConfig>,
217 #[cfg_attr(feature = "config", serde(default))]
219 pub cardinality_spikes: Option<Vec<CardinalitySpikeConfig>>,
220 #[cfg_attr(feature = "config", serde(default))]
222 pub phase_offset: Option<String>,
223 #[cfg_attr(feature = "config", serde(default))]
225 pub clock_group: Option<String>,
226 #[cfg_attr(feature = "config", serde(default))]
228 pub after: Option<AfterClause>,
229 #[cfg_attr(
231 feature = "config",
232 serde(default, rename = "while", skip_serializing_if = "Option::is_none")
233 )]
234 pub while_clause: Option<WhileClause>,
235 #[cfg_attr(
237 feature = "config",
238 serde(default, rename = "delay", skip_serializing_if = "Option::is_none")
239 )]
240 pub delay_clause: Option<DelayClause>,
241
242 #[cfg_attr(feature = "config", serde(default))]
245 pub pack: Option<String>,
246 #[cfg_attr(feature = "config", serde(default))]
248 pub overrides: Option<BTreeMap<String, MetricOverride>>,
249
250 #[cfg_attr(feature = "config", serde(default))]
253 pub distribution: Option<DistributionConfig>,
254 #[cfg_attr(feature = "config", serde(default))]
256 pub buckets: Option<Vec<f64>>,
257 #[cfg_attr(feature = "config", serde(default))]
259 pub quantiles: Option<Vec<f64>>,
260 #[cfg_attr(feature = "config", serde(default))]
262 pub observations_per_tick: Option<u32>,
263 #[cfg_attr(feature = "config", serde(default))]
265 pub mean_shift_per_sec: Option<f64>,
266 #[cfg_attr(feature = "config", serde(default))]
268 pub seed: Option<u64>,
269 #[cfg_attr(feature = "config", serde(default))]
271 pub on_sink_error: Option<OnSinkError>,
272}
273
274#[derive(Debug, Clone, PartialEq, Eq)]
280#[cfg_attr(feature = "config", derive(serde::Serialize, serde::Deserialize))]
281pub enum AfterOp {
282 #[cfg_attr(feature = "config", serde(rename = "<"))]
284 LessThan,
285 #[cfg_attr(feature = "config", serde(rename = ">"))]
287 GreaterThan,
288}
289
290#[derive(Debug, Clone)]
306#[cfg_attr(
307 feature = "config",
308 derive(serde::Serialize, serde::Deserialize),
309 serde(deny_unknown_fields)
310)]
311pub struct AfterClause {
312 #[cfg_attr(feature = "config", serde(rename = "ref"))]
316 pub ref_id: String,
317 pub op: AfterOp,
319 pub value: f64,
321 #[cfg_attr(feature = "config", serde(default))]
323 pub delay: Option<String>,
324}
325
326#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333#[cfg_attr(feature = "config", derive(serde::Serialize))]
334pub enum WhileOp {
335 #[cfg_attr(feature = "config", serde(rename = "<"))]
336 LessThan,
337 #[cfg_attr(feature = "config", serde(rename = ">"))]
338 GreaterThan,
339}
340
341#[cfg(feature = "config")]
342impl<'de> serde::Deserialize<'de> for WhileOp {
343 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
344 where
345 D: serde::Deserializer<'de>,
346 {
347 let raw = String::deserialize(deserializer)?;
348 match raw.as_str() {
349 "<" => Ok(WhileOp::LessThan),
350 ">" => Ok(WhileOp::GreaterThan),
351 other => Err(serde::de::Error::custom(format!(
352 "unsupported operator '{other}' on while: — only strict \
353 comparisons '<' and '>' are accepted"
354 ))),
355 }
356 }
357}
358
359#[derive(Debug, Clone)]
368#[cfg_attr(
369 feature = "config",
370 derive(serde::Serialize, serde::Deserialize),
371 serde(deny_unknown_fields)
372)]
373pub struct WhileClause {
374 #[cfg_attr(feature = "config", serde(rename = "ref"))]
375 pub ref_id: String,
376 pub op: WhileOp,
377 pub value: f64,
378}
379
380#[derive(Debug, Clone, PartialEq)]
396#[cfg_attr(feature = "config", derive(serde::Serialize))]
397pub struct DelayClause {
398 #[cfg_attr(
399 feature = "config",
400 serde(
401 default,
402 skip_serializing_if = "Option::is_none",
403 with = "delay_duration_opt"
404 )
405 )]
406 pub open: Option<std::time::Duration>,
407 #[cfg_attr(
408 feature = "config",
409 serde(
410 default,
411 skip_serializing_if = "Option::is_none",
412 with = "delay_duration_opt"
413 )
414 )]
415 pub close: Option<std::time::Duration>,
416 #[cfg_attr(
417 feature = "config",
418 serde(default, skip_serializing_if = "Option::is_none")
419 )]
420 pub close_stale_marker: Option<bool>,
421 #[cfg_attr(
422 feature = "config",
423 serde(default, skip_serializing_if = "Option::is_none")
424 )]
425 pub close_snap_to: Option<f64>,
426}
427
428#[cfg(feature = "config")]
429impl<'de> serde::Deserialize<'de> for DelayClause {
430 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
431 where
432 D: serde::Deserializer<'de>,
433 {
434 #[derive(serde::Deserialize)]
435 #[serde(deny_unknown_fields)]
436 struct CloseStruct {
437 #[serde(default)]
438 duration: Option<String>,
439 #[serde(default)]
440 snap_to: Option<f64>,
441 #[serde(default)]
442 stale_marker: Option<bool>,
443 }
444
445 #[derive(serde::Deserialize)]
446 #[serde(untagged)]
447 enum CloseShape {
448 Duration(String),
449 Extended(CloseStruct),
450 }
451
452 #[derive(serde::Deserialize)]
453 #[serde(deny_unknown_fields)]
454 struct Raw {
455 #[serde(default)]
456 open: Option<String>,
457 #[serde(default)]
458 close: Option<CloseShape>,
459 }
460
461 let raw = Raw::deserialize(deserializer)?;
462
463 let open = match raw.open {
464 Some(s) => Some(
465 crate::config::validate::parse_delay_duration(&s)
466 .map_err(serde::de::Error::custom)?,
467 ),
468 None => None,
469 };
470
471 let (close, close_snap_to, close_stale_marker) = match raw.close {
472 None => (None, None, None),
473 Some(CloseShape::Duration(s)) => {
474 let dur = crate::config::validate::parse_delay_duration(&s)
475 .map_err(serde::de::Error::custom)?;
476 (Some(dur), None, None)
477 }
478 Some(CloseShape::Extended(ext)) => {
479 let dur = match ext.duration {
480 Some(s) => Some(
481 crate::config::validate::parse_delay_duration(&s)
482 .map_err(serde::de::Error::custom)?,
483 ),
484 None => None,
485 };
486 (dur, ext.snap_to, ext.stale_marker)
487 }
488 };
489
490 Ok(DelayClause {
491 open,
492 close,
493 close_stale_marker,
494 close_snap_to,
495 })
496 }
497}
498
499#[cfg(feature = "config")]
500mod delay_duration_opt {
501 use std::time::Duration;
502
503 use serde::Serializer;
504
505 pub fn serialize<S>(value: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
506 where
507 S: Serializer,
508 {
509 match value {
510 Some(d) => serializer.serialize_str(&format_duration(*d)),
511 None => serializer.serialize_none(),
512 }
513 }
514
515 fn format_duration(d: Duration) -> String {
516 let total_ms = d.as_millis();
517 if total_ms == 0 {
518 return "0ms".to_string();
519 }
520 if total_ms.is_multiple_of(3_600_000) {
521 return format!("{}h", total_ms / 3_600_000);
522 }
523 if total_ms.is_multiple_of(60_000) {
524 return format!("{}m", total_ms / 60_000);
525 }
526 if total_ms.is_multiple_of(1_000) {
527 return format!("{}s", total_ms / 1_000);
528 }
529 format!("{total_ms}ms")
530 }
531}
532
533#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
540#[cfg_attr(feature = "config", derive(serde::Serialize))]
541#[cfg_attr(feature = "config", serde(rename_all = "lowercase"))]
542#[non_exhaustive]
543pub enum ClauseKind {
544 After,
545 While,
546}
547
548impl std::fmt::Display for ClauseKind {
549 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
550 f.write_str(match self {
551 ClauseKind::After => "after",
552 ClauseKind::While => "while",
553 })
554 }
555}