Consolidate EngineSnapshot's impl blocks into snapshot/mod.rs
They were scattered across filter/metrics/params/query/sort.rs by an earlier file-splitting pass. Files can be split as much as needed, but a single type's impl should never be split across files — so all 15 impl EngineSnapshot blocks now live in mod.rs, while each submodule keeps its own types, free helper functions, and tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TeyhRfMYjMditek6geDRoQ
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
7df6026a21
commit
1b2f690278
+10
-184
@@ -2,15 +2,15 @@
|
||||
//! stats, declaration listing, bucket-value listing, and PromQL evaluation.
|
||||
|
||||
use crate::api::dto::{
|
||||
BucketValueDto, LogEntryDto, MetricDeclarationDto, ParamDeclarationDto, PromqlSeriesDto,
|
||||
BucketValueDto, LogEntryDto, MetricDeclarationDto, ParamDeclarationDto,
|
||||
StatsDto, TimelineBucketDto,
|
||||
};
|
||||
use crate::consts::{DEFAULT_TIMELINE_BUCKET_MS, MERGE_WINDOW_MS};
|
||||
use crate::engine::data_value::{DataValue, MetricPrimitiveKind};
|
||||
use crate::engine::data_value::DataValue;
|
||||
use crate::engine::index_key::IndexKey;
|
||||
use crate::engine::message::{EngineQuery, EngineResponse};
|
||||
use crate::engine::metric_primitive::{MetricPrimitive, Number};
|
||||
use crate::engine::promql::{AggOp, LabelMatcher, PromqlQuery};
|
||||
use crate::engine::promql::{LabelMatcher, PromqlQuery};
|
||||
use crate::engine::rate;
|
||||
#[cfg(test)]
|
||||
use crate::engine::snapshot::sort::SortHelperIndexEntry;
|
||||
@@ -263,7 +263,7 @@ pub(super) fn handle_query_inner(
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_to_f64(value: &MetricPrimitive) -> f64 {
|
||||
pub(crate) fn coerce_to_f64(value: &MetricPrimitive) -> f64 {
|
||||
match value {
|
||||
MetricPrimitive::Number(n) => match n {
|
||||
Number::Int(v) => *v as f64,
|
||||
@@ -282,7 +282,7 @@ fn coerce_to_f64(value: &MetricPrimitive) -> f64 {
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_label_matchers(
|
||||
pub(crate) fn matches_label_matchers(
|
||||
labels: &BTreeMap<String, MetricPrimitive>,
|
||||
matchers: &[LabelMatcher],
|
||||
) -> bool {
|
||||
@@ -291,7 +291,7 @@ fn matches_label_matchers(
|
||||
})
|
||||
}
|
||||
|
||||
fn project_labels(
|
||||
pub(crate) fn project_labels(
|
||||
labels: &BTreeMap<String, String>,
|
||||
by_labels: &[String],
|
||||
) -> BTreeMap<String, String> {
|
||||
@@ -301,7 +301,7 @@ fn project_labels(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn windowed_rate_series(
|
||||
pub(crate) fn windowed_rate_series(
|
||||
points: &[(i64, f64)],
|
||||
range_ms: i64,
|
||||
start_ms: Option<i64>,
|
||||
@@ -329,7 +329,7 @@ fn windowed_rate_series(
|
||||
series
|
||||
}
|
||||
|
||||
fn merge_series_windowed(sub_series: &[Vec<(i64, f64)>], is_avg: bool) -> Vec<(i64, f64)> {
|
||||
pub(crate) fn merge_series_windowed(sub_series: &[Vec<(i64, f64)>], is_avg: bool) -> Vec<(i64, f64)> {
|
||||
if sub_series.len() <= 1 {
|
||||
return sub_series.first().cloned().unwrap_or_default();
|
||||
}
|
||||
@@ -350,7 +350,7 @@ fn merge_series_windowed(sub_series: &[Vec<(i64, f64)>], is_avg: bool) -> Vec<(i
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn merge_series_locf(sub_series: &[Vec<(i64, f64)>], is_avg: bool) -> Vec<(i64, f64)> {
|
||||
pub(crate) fn merge_series_locf(sub_series: &[Vec<(i64, f64)>], is_avg: bool) -> Vec<(i64, f64)> {
|
||||
let mut all_timestamps: std::collections::BTreeSet<i64> = std::collections::BTreeSet::new();
|
||||
for points in sub_series {
|
||||
all_timestamps.extend(points.iter().map(|(ts, _)| *ts));
|
||||
@@ -373,184 +373,10 @@ fn merge_series_locf(sub_series: &[Vec<(i64, f64)>], is_avg: bool) -> Vec<(i64,
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn ottyl_reserved_numeric_value_fn(metric_name: &str) -> Option<fn(u64, i64) -> f64> {
|
||||
pub(crate) fn ottyl_reserved_numeric_value_fn(metric_name: &str) -> Option<fn(u64, i64) -> f64> {
|
||||
match metric_name {
|
||||
"ottyl_seq" => Some(|sequence, _timestamp_ms| sequence as f64),
|
||||
"ottyl_ts" => Some(|_sequence, timestamp_ms| timestamp_ms as f64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl EngineSnapshot {
|
||||
pub(crate) fn evaluate_promql(
|
||||
&self,
|
||||
parsed: &PromqlQuery,
|
||||
start_ms: Option<i64>,
|
||||
end_ms: Option<i64>,
|
||||
) -> Result<Vec<PromqlSeriesDto>, String> {
|
||||
if parsed.metric_name == "ottyl_fmt" {
|
||||
return Err(
|
||||
"'ottyl_fmt' is a categorical value (json/logfmt/none), not a number, so it can't be \
|
||||
used in a dashboard PromQL query; filter on it in the search bar instead, e.g. \
|
||||
ottyl_fmt==json"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
if ottyl_reserved_numeric_value_fn(&parsed.metric_name).is_none()
|
||||
&& !self.index.contains_key(&IndexKey::MetricDeclaration { name: parsed.metric_name.clone() })
|
||||
&& self.bucket_values(&parsed.metric_name, None).next().is_none()
|
||||
{
|
||||
return Err(format!(
|
||||
"unknown metric \"{}\": no declaration or recorded data for it (was it deleted?)",
|
||||
parsed.metric_name
|
||||
));
|
||||
}
|
||||
if matches!(parsed.agg, Some(AggOp::Sum) | Some(AggOp::Avg))
|
||||
&& !parsed.inner_rate
|
||||
&& self.declared_value_kind(&parsed.metric_name) == Some(MetricPrimitiveKind::String)
|
||||
{
|
||||
return Err(format!(
|
||||
"cannot sum/avg a string-typed metric \"{}\"; use count() or a numeric-typed metric",
|
||||
parsed.metric_name
|
||||
));
|
||||
}
|
||||
|
||||
let mut series_points: BTreeMap<BTreeMap<String, String>, Vec<(i64, f64)>> = BTreeMap::new();
|
||||
if let Some(reserved_value) = ottyl_reserved_numeric_value_fn(&parsed.metric_name) {
|
||||
let no_labels: BTreeMap<String, MetricPrimitive> = BTreeMap::new();
|
||||
if matches_label_matchers(&no_labels, &parsed.matchers) {
|
||||
for (sequence, timestamp, _text) in self.logs_range() {
|
||||
let timestamp_ms = timestamp.as_millis();
|
||||
if start_ms.is_none_or(|start| timestamp_ms >= start)
|
||||
&& end_ms.is_none_or(|end| timestamp_ms <= end)
|
||||
{
|
||||
series_points
|
||||
.entry(BTreeMap::new())
|
||||
.or_default()
|
||||
.push((timestamp_ms, reserved_value(sequence, timestamp_ms)));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (labels, (_, timestamp, value)) in self.bucket_values(&parsed.metric_name, None)
|
||||
.filter(|(labels, _)| matches_label_matchers(labels, &parsed.matchers))
|
||||
.filter(|(_, (_, timestamp, _))| {
|
||||
start_ms.is_none_or(|start| timestamp.as_millis() >= start)
|
||||
&& end_ms.is_none_or(|end| timestamp.as_millis() <= end)
|
||||
})
|
||||
{
|
||||
let display_labels: BTreeMap<String, String> =
|
||||
labels.iter().map(|(name, value)| (name.clone(), value.to_string())).collect();
|
||||
series_points
|
||||
.entry(display_labels)
|
||||
.or_default()
|
||||
.push((timestamp.as_millis(), coerce_to_f64(value)));
|
||||
}
|
||||
}
|
||||
for points in series_points.values_mut() {
|
||||
points.sort_by_key(|(timestamp_ms, _)| *timestamp_ms);
|
||||
}
|
||||
|
||||
Ok(match parsed.agg {
|
||||
None => series_points
|
||||
.into_iter()
|
||||
.map(|(labels, points)| PromqlSeriesDto { labels, points })
|
||||
.collect(),
|
||||
Some(AggOp::Count) => {
|
||||
let mut groups: BTreeMap<BTreeMap<String, String>, Vec<Vec<(i64, f64)>>> = BTreeMap::new();
|
||||
for (labels, points) in series_points {
|
||||
let count_points: Vec<(i64, f64)> =
|
||||
points.iter().map(|(timestamp_ms, _)| (*timestamp_ms, 1.0)).collect();
|
||||
let group_key = project_labels(&labels, &parsed.by_labels);
|
||||
groups.entry(group_key).or_default().push(count_points);
|
||||
}
|
||||
groups
|
||||
.into_iter()
|
||||
.map(|(group_labels, sub_series)| {
|
||||
let points = merge_series_windowed(&sub_series, false);
|
||||
PromqlSeriesDto { labels: group_labels, points }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
Some(AggOp::Sum) | Some(AggOp::Avg) => {
|
||||
let is_avg = parsed.agg == Some(AggOp::Avg);
|
||||
let rate_range_ms = parsed
|
||||
.inner_rate
|
||||
.then(|| parsed.range.expect("inner rate() always carries a range").as_millis() as i64);
|
||||
|
||||
let mut groups: BTreeMap<BTreeMap<String, String>, Vec<Vec<(i64, f64)>>> = BTreeMap::new();
|
||||
for (labels, points) in series_points {
|
||||
let points = match rate_range_ms {
|
||||
Some(range_ms) => windowed_rate_series(&points, range_ms, start_ms, end_ms),
|
||||
None => points,
|
||||
};
|
||||
let group_key = project_labels(&labels, &parsed.by_labels);
|
||||
groups.entry(group_key).or_default().push(points);
|
||||
}
|
||||
|
||||
groups
|
||||
.into_iter()
|
||||
.map(|(group_labels, sub_series)| {
|
||||
let points = if rate_range_ms.is_some() {
|
||||
merge_series_locf(&sub_series, is_avg)
|
||||
} else {
|
||||
merge_series_windowed(&sub_series, is_avg)
|
||||
};
|
||||
PromqlSeriesDto { labels: group_labels, points }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
Some(AggOp::Rate) => {
|
||||
let range_ms = parsed.range.expect("rate() always carries a range").as_millis() as i64;
|
||||
series_points
|
||||
.into_iter()
|
||||
.map(|(labels, points)| PromqlSeriesDto {
|
||||
labels,
|
||||
points: windowed_rate_series(&points, range_ms, start_ms, end_ms),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
Some(AggOp::Gauge) => {
|
||||
series_points
|
||||
.into_iter()
|
||||
.filter_map(|(labels, points)| {
|
||||
let last = *points.last()?;
|
||||
Some(PromqlSeriesDto { labels, points: vec![last] })
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
Some(AggOp::Histogram) => {
|
||||
let bucket_width =
|
||||
parsed.bucket_width.expect("histogram() always carries a bucket width");
|
||||
let last_ts = series_points
|
||||
.values()
|
||||
.flat_map(|points| points.iter().map(|(ts, _)| *ts))
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let mut groups: BTreeMap<BTreeMap<String, String>, Vec<&(i64, f64)>> = BTreeMap::new();
|
||||
for (labels, points) in &series_points {
|
||||
let group_key = project_labels(labels, &parsed.by_labels);
|
||||
groups.entry(group_key).or_default().extend(points.iter());
|
||||
}
|
||||
|
||||
groups
|
||||
.into_iter()
|
||||
.flat_map(|(group_labels, points)| {
|
||||
let mut counts: BTreeMap<i64, u64> = BTreeMap::new();
|
||||
for (_, value) in points {
|
||||
let bucket_index = (value / bucket_width).floor() as i64;
|
||||
*counts.entry(bucket_index).or_insert(0) += 1;
|
||||
}
|
||||
counts.into_iter().map(move |(bucket_index, count)| {
|
||||
let upper_bound = (bucket_index + 1) as f64 * bucket_width;
|
||||
let mut labels = group_labels.clone();
|
||||
labels.insert("le".to_string(), upper_bound.to_string());
|
||||
PromqlSeriesDto { labels, points: vec![(last_ts, count as f64)] }
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user