#[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)]
#[cfg_attr(
feature = "config",
derive(serde::Serialize, serde::Deserialize),
serde(deny_unknown_fields)
)]
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(feature = "config")]
mod delay_duration_opt {
use std::time::Duration;
use serde::{Deserialize, Deserializer, Serializer};
use crate::config::validate::parse_delay_duration;
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(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
where
D: Deserializer<'de>,
{
let raw: Option<String> = Option::deserialize(deserializer)?;
match raw {
Some(s) => parse_delay_duration(&s)
.map(Some)
.map_err(serde::de::Error::custom),
None => Ok(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",
})
}
}