use serde::{Deserialize, Serialize};
use std::path::Path;
use std::sync::Arc;
use crate::{LocalJsonSink, RunSink};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CaptureMode {
Light,
Investigation,
}
impl CaptureMode {
#[must_use]
pub const fn core_defaults(self) -> CaptureLimits {
match self {
Self::Light => CaptureLimits {
max_requests: 100_000,
max_stages: 200_000,
max_queues: 200_000,
max_inflight_snapshots: 200_000,
max_runtime_snapshots: 100_000,
},
Self::Investigation => CaptureLimits {
max_requests: 300_000,
max_stages: 600_000,
max_queues: 600_000,
max_inflight_snapshots: 600_000,
max_runtime_snapshots: 300_000,
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct CaptureLimits {
pub max_requests: usize,
pub max_stages: usize,
pub max_queues: usize,
pub max_inflight_snapshots: usize,
pub max_runtime_snapshots: usize,
}
impl Default for CaptureLimits {
fn default() -> Self {
CaptureMode::Light.core_defaults()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct CaptureLimitsOverride {
pub max_requests: Option<usize>,
pub max_stages: Option<usize>,
pub max_queues: Option<usize>,
pub max_inflight_snapshots: Option<usize>,
pub max_runtime_snapshots: Option<usize>,
}
impl CaptureLimitsOverride {
#[must_use]
pub const fn apply(self, base: CaptureLimits) -> CaptureLimits {
CaptureLimits {
max_requests: match self.max_requests {
Some(value) => value,
None => base.max_requests,
},
max_stages: match self.max_stages {
Some(value) => value,
None => base.max_stages,
},
max_queues: match self.max_queues {
Some(value) => value,
None => base.max_queues,
},
max_inflight_snapshots: match self.max_inflight_snapshots {
Some(value) => value,
None => base.max_inflight_snapshots,
},
max_runtime_snapshots: match self.max_runtime_snapshots {
Some(value) => value,
None => base.max_runtime_snapshots,
},
}
}
const fn merge(self, newer: Self) -> Self {
Self {
max_requests: match newer.max_requests {
Some(value) => Some(value),
None => self.max_requests,
},
max_stages: match newer.max_stages {
Some(value) => Some(value),
None => self.max_stages,
},
max_queues: match newer.max_queues {
Some(value) => Some(value),
None => self.max_queues,
},
max_inflight_snapshots: match newer.max_inflight_snapshots {
Some(value) => Some(value),
None => self.max_inflight_snapshots,
},
max_runtime_snapshots: match newer.max_runtime_snapshots {
Some(value) => Some(value),
None => self.max_runtime_snapshots,
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct EffectiveCoreConfig {
pub mode: CaptureMode,
pub capture_limits: CaptureLimits,
pub strict_lifecycle: bool,
}
#[derive(Clone)]
pub(crate) struct Config {
pub service_name: String,
pub service_version: Option<String>,
pub run_id: Option<String>,
pub mode: CaptureMode,
pub sink: Arc<dyn RunSink + Send + Sync>,
pub effective_core: EffectiveCoreConfig,
pub strict_lifecycle: bool,
}
impl Config {
pub(crate) fn from_builder(builder: &TailtriageBuilder) -> Self {
let mode_defaults = builder.mode.core_defaults();
let effective_limits = match builder.capture_limits {
Some(full_override) => full_override,
None => builder.capture_limits_override.apply(mode_defaults),
};
let effective_core = EffectiveCoreConfig {
mode: builder.mode,
capture_limits: effective_limits,
strict_lifecycle: builder.strict_lifecycle,
};
Self {
service_name: builder.service_name.clone(),
service_version: builder.service_version.clone(),
run_id: builder.run_id.clone(),
mode: builder.mode,
sink: Arc::clone(&builder.sink),
effective_core,
strict_lifecycle: builder.strict_lifecycle,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BuildError {
EmptyServiceName,
}
impl std::fmt::Display for BuildError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::EmptyServiceName => write!(f, "service_name cannot be empty"),
}
}
}
impl std::error::Error for BuildError {}
#[derive(Clone)]
pub struct TailtriageBuilder {
pub(crate) service_name: String,
pub(crate) service_version: Option<String>,
pub(crate) run_id: Option<String>,
pub(crate) mode: CaptureMode,
pub(crate) sink: Arc<dyn RunSink + Send + Sync>,
pub(crate) capture_limits: Option<CaptureLimits>,
pub(crate) capture_limits_override: CaptureLimitsOverride,
pub(crate) strict_lifecycle: bool,
}
impl TailtriageBuilder {
pub(crate) fn new(service_name: impl Into<String>) -> Self {
Self {
service_name: service_name.into(),
service_version: None,
run_id: None,
mode: CaptureMode::Light,
sink: Arc::new(LocalJsonSink::new("tailtriage-run.json")),
capture_limits: None,
capture_limits_override: CaptureLimitsOverride::default(),
strict_lifecycle: false,
}
}
#[must_use]
pub fn light(mut self) -> Self {
self.mode = CaptureMode::Light;
self
}
#[must_use]
pub fn investigation(mut self) -> Self {
self.mode = CaptureMode::Investigation;
self
}
#[must_use]
pub fn output(mut self, output_path: impl AsRef<Path>) -> Self {
self.sink = Arc::new(LocalJsonSink::new(output_path));
self
}
#[must_use]
pub fn sink<S>(mut self, sink: S) -> Self
where
S: RunSink + Send + Sync + 'static,
{
self.sink = Arc::new(sink);
self
}
#[must_use]
pub fn service_version(mut self, service_version: impl Into<String>) -> Self {
self.service_version = Some(service_version.into());
self
}
#[must_use]
pub fn run_id(mut self, run_id: impl Into<String>) -> Self {
self.run_id = Some(run_id.into());
self
}
#[must_use]
pub fn capture_limits(mut self, limits: CaptureLimits) -> Self {
self.capture_limits = Some(limits);
self
}
#[must_use]
pub fn capture_limits_override(mut self, overrides: CaptureLimitsOverride) -> Self {
self.capture_limits_override = self.capture_limits_override.merge(overrides);
self
}
#[must_use]
pub fn strict_lifecycle(mut self, strict_lifecycle: bool) -> Self {
self.strict_lifecycle = strict_lifecycle;
self
}
pub fn build(self) -> Result<crate::Tailtriage, BuildError> {
crate::Tailtriage::from_config(Config::from_builder(&self))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RequestOptions {
pub request_id: Option<String>,
pub kind: Option<String>,
}
impl RequestOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn request_id(mut self, request_id: impl Into<String>) -> Self {
self.request_id = Some(request_id.into());
self
}
#[must_use]
pub fn kind(mut self, kind: impl Into<String>) -> Self {
self.kind = Some(kind.into());
self
}
}