#[cfg(feature = "config")]
pub mod env_interpolate;
#[cfg(feature = "config")]
pub mod parse;
#[cfg(feature = "config")]
pub mod normalize;
#[cfg(feature = "config")]
pub mod expand;
pub mod timing;
#[cfg(feature = "config")]
pub mod compile_after;
#[cfg(feature = "config")]
pub mod prepare;
use std::collections::BTreeMap;
use crate::config::{
BurstConfig, CardinalitySpikeConfig, DistributionConfig, DynamicLabelConfig, GapConfig,
OnSinkError,
};
use crate::encoder::EncoderConfig;
use crate::generator::{GeneratorConfig, LogGeneratorConfig};
use crate::packs::MetricOverride;
use crate::sink::SinkConfig;
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "config",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields)
)]
pub struct ScenarioFile {
pub version: u32,
#[cfg_attr(feature = "config", serde(default))]
pub scenario_name: Option<String>,
#[cfg_attr(feature = "config", serde(default))]
pub category: Option<String>,
#[cfg_attr(feature = "config", serde(default))]
pub description: Option<String>,
#[cfg_attr(feature = "config", serde(default))]
pub defaults: Option<Defaults>,
pub scenarios: Vec<Entry>,
}
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "config",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields)
)]
pub struct Defaults {
#[cfg_attr(feature = "config", serde(default))]
pub rate: Option<f64>,
#[cfg_attr(feature = "config", serde(default))]
pub duration: Option<String>,
#[cfg_attr(feature = "config", serde(default))]
pub encoder: Option<EncoderConfig>,
#[cfg_attr(feature = "config", serde(default))]
pub sink: Option<SinkConfig>,
#[cfg_attr(feature = "config", serde(default))]
pub labels: Option<BTreeMap<String, String>>,
#[cfg_attr(feature = "config", serde(default))]
pub on_sink_error: Option<OnSinkError>,
#[cfg_attr(
feature = "config",
serde(default, rename = "while", skip_serializing_if = "Option::is_none")
)]
pub while_clause: Option<WhileClause>,
#[cfg_attr(
feature = "config",
serde(default, rename = "delay", skip_serializing_if = "Option::is_none")
)]
pub delay_clause: Option<DelayClause>,
}
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "config",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields)
)]
pub struct Entry {
#[cfg_attr(feature = "config", serde(default))]
pub id: Option<String>,
pub signal_type: String,
#[cfg_attr(feature = "config", serde(default))]
pub name: Option<String>,
#[cfg_attr(feature = "config", serde(default))]
pub rate: Option<f64>,
#[cfg_attr(feature = "config", serde(default))]
pub duration: Option<String>,
#[cfg_attr(feature = "config", serde(default))]
pub generator: Option<GeneratorConfig>,
#[cfg_attr(feature = "config", serde(default))]
pub log_generator: Option<LogGeneratorConfig>,
#[cfg_attr(feature = "config", serde(default))]
pub labels: Option<BTreeMap<String, String>>,
#[cfg_attr(feature = "config", serde(default))]
pub dynamic_labels: Option<Vec<DynamicLabelConfig>>,
#[cfg_attr(feature = "config", serde(default))]
pub encoder: Option<EncoderConfig>,
#[cfg_attr(feature = "config", serde(default))]
pub sink: Option<SinkConfig>,
#[cfg_attr(feature = "config", serde(default))]
pub jitter: Option<f64>,
#[cfg_attr(feature = "config", serde(default))]
pub jitter_seed: Option<u64>,
#[cfg_attr(feature = "config", serde(default))]
pub gaps: Option<GapConfig>,
#[cfg_attr(feature = "config", serde(default))]
pub bursts: Option<BurstConfig>,
#[cfg_attr(feature = "config", serde(default))]
pub cardinality_spikes: Option<Vec<CardinalitySpikeConfig>>,
#[cfg_attr(feature = "config", serde(default))]
pub phase_offset: Option<String>,
#[cfg_attr(feature = "config", serde(default))]
pub clock_group: Option<String>,
#[cfg_attr(feature = "config", serde(default))]
pub after: Option<AfterClause>,
#[cfg_attr(
feature = "config",
serde(default, rename = "while", skip_serializing_if = "Option::is_none")
)]
pub while_clause: Option<WhileClause>,
#[cfg_attr(
feature = "config",
serde(default, rename = "delay", skip_serializing_if = "Option::is_none")
)]
pub delay_clause: Option<DelayClause>,
#[cfg_attr(feature = "config", serde(default))]
pub pack: Option<String>,
#[cfg_attr(feature = "config", serde(default))]
pub overrides: Option<BTreeMap<String, MetricOverride>>,
#[cfg_attr(feature = "config", serde(default))]
pub distribution: Option<DistributionConfig>,
#[cfg_attr(feature = "config", serde(default))]
pub buckets: Option<Vec<f64>>,
#[cfg_attr(feature = "config", serde(default))]
pub quantiles: Option<Vec<f64>>,
#[cfg_attr(feature = "config", serde(default))]
pub observations_per_tick: Option<u32>,
#[cfg_attr(feature = "config", serde(default))]
pub mean_shift_per_sec: Option<f64>,
#[cfg_attr(feature = "config", serde(default))]
pub seed: Option<u64>,
#[cfg_attr(feature = "config", serde(default))]
pub on_sink_error: Option<OnSinkError>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "config", derive(serde::Serialize, serde::Deserialize))]
pub enum AfterOp {
#[cfg_attr(feature = "config", serde(rename = "<"))]
LessThan,
#[cfg_attr(feature = "config", serde(rename = ">"))]
GreaterThan,
}
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "config",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields)
)]
pub struct AfterClause {
#[cfg_attr(feature = "config", serde(rename = "ref"))]
pub ref_id: String,
pub op: AfterOp,
pub value: f64,
#[cfg_attr(feature = "config", serde(default))]
pub delay: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "config", derive(serde::Serialize))]
pub enum WhileOp {
#[cfg_attr(feature = "config", serde(rename = "<"))]
LessThan,
#[cfg_attr(feature = "config", serde(rename = ">"))]
GreaterThan,
}
#[cfg(feature = "config")]
impl<'de> serde::Deserialize<'de> for WhileOp {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
match raw.as_str() {
"<" => Ok(WhileOp::LessThan),
">" => Ok(WhileOp::GreaterThan),
other => Err(serde::de::Error::custom(format!(
"unsupported operator '{other}' on while: — only strict \
comparisons '<' and '>' are accepted"
))),
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(
feature = "config",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields)
)]
pub struct WhileClause {
#[cfg_attr(feature = "config", serde(rename = "ref"))]
pub ref_id: String,
pub op: WhileOp,
pub value: f64,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "config", derive(serde::Serialize))]
pub struct DelayClause {
#[cfg_attr(
feature = "config",
serde(
default,
skip_serializing_if = "Option::is_none",
with = "delay_duration_opt"
)
)]
pub open: Option<std::time::Duration>,
#[cfg_attr(
feature = "config",
serde(
default,
skip_serializing_if = "Option::is_none",
with = "delay_duration_opt"
)
)]
pub close: Option<std::time::Duration>,
#[cfg_attr(
feature = "config",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub close_stale_marker: Option<bool>,
#[cfg_attr(
feature = "config",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub close_snap_to: Option<f64>,
}
#[cfg(feature = "config")]
impl<'de> serde::Deserialize<'de> for DelayClause {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct CloseStruct {
#[serde(default)]
duration: Option<String>,
#[serde(default)]
snap_to: Option<f64>,
#[serde(default)]
stale_marker: Option<bool>,
}
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum CloseShape {
Duration(String),
Extended(CloseStruct),
}
#[derive(serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct Raw {
#[serde(default)]
open: Option<String>,
#[serde(default)]
close: Option<CloseShape>,
}
let raw = Raw::deserialize(deserializer)?;
let open = match raw.open {
Some(s) => Some(
crate::config::validate::parse_delay_duration(&s)
.map_err(serde::de::Error::custom)?,
),
None => None,
};
let (close, close_snap_to, close_stale_marker) = match raw.close {
None => (None, None, None),
Some(CloseShape::Duration(s)) => {
let dur = crate::config::validate::parse_delay_duration(&s)
.map_err(serde::de::Error::custom)?;
(Some(dur), None, None)
}
Some(CloseShape::Extended(ext)) => {
let dur = match ext.duration {
Some(s) => Some(
crate::config::validate::parse_delay_duration(&s)
.map_err(serde::de::Error::custom)?,
),
None => None,
};
(dur, ext.snap_to, ext.stale_marker)
}
};
Ok(DelayClause {
open,
close,
close_stale_marker,
close_snap_to,
})
}
}
#[cfg(feature = "config")]
mod delay_duration_opt {
use std::time::Duration;
use serde::Serializer;
pub fn serialize<S>(value: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(d) => serializer.serialize_str(&format_duration(*d)),
None => serializer.serialize_none(),
}
}
fn format_duration(d: Duration) -> String {
let total_ms = d.as_millis();
if total_ms == 0 {
return "0ms".to_string();
}
if total_ms.is_multiple_of(3_600_000) {
return format!("{}h", total_ms / 3_600_000);
}
if total_ms.is_multiple_of(60_000) {
return format!("{}m", total_ms / 60_000);
}
if total_ms.is_multiple_of(1_000) {
return format!("{}s", total_ms / 1_000);
}
format!("{total_ms}ms")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "config", derive(serde::Serialize))]
#[cfg_attr(feature = "config", serde(rename_all = "lowercase"))]
#[non_exhaustive]
pub enum ClauseKind {
After,
While,
}
impl std::fmt::Display for ClauseKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
ClauseKind::After => "after",
ClauseKind::While => "while",
})
}
}