Hand-rolls a minimal PromQL parser (src/promql.rs) for metric-name + label-matcher selection, optional [range] selectors, and top-level sum()/avg()/rate() wrapping, rather than pulling in a full PromQL parser crate — the required subset is small and a hand-rolled parser stays fully engine-independent (mirrors filter_parser.rs) with no unused-AST overhead. Adds EngineQuery::Promql/EngineResponse::Promql (engine parses + evaluates in place against the index, no copying) and GET /api/query, returning Vec<PromqlSeriesDto> (labels + (timestamp_ms, f64) points) for charting, with malformed queries surfaced as 400 + ErrorDto. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
172 lines
5.5 KiB
Rust
172 lines
5.5 KiB
Rust
use crate::data_value::{MetricPrimitiveKind, RegexToMetricPrimitiveMatcher};
|
|
use crate::metric_primitive::MetricPrimitive;
|
|
use crate::settings::CssEntry;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::BTreeMap;
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct LogEntryDto {
|
|
pub sequence: u64,
|
|
pub timestamp_ms: i64,
|
|
pub text: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct ParamDeclarationDto {
|
|
pub name: String,
|
|
pub message_match_regex: String,
|
|
pub capture_group: String,
|
|
pub kind: MetricPrimitiveKind,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct MetricDeclarationDto {
|
|
pub name: String,
|
|
pub message_match_regex: String,
|
|
pub params: Vec<RegexToMetricPrimitiveMatcher>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct TimelineBucketDto {
|
|
pub start_ms: i64,
|
|
pub count: u64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct StatsDto {
|
|
pub log_count: u64,
|
|
pub first_sequence: Option<u64>,
|
|
pub last_sequence: Option<u64>,
|
|
pub first_timestamp_ms: Option<i64>,
|
|
pub last_timestamp_ms: Option<i64>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct LogsParams {
|
|
pub since_sequence: Option<u64>,
|
|
pub start_ms: Option<i64>,
|
|
pub end_ms: Option<i64>,
|
|
pub limit: Option<usize>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct TimelineParams {
|
|
pub start_ms: Option<i64>,
|
|
pub end_ms: Option<i64>,
|
|
pub bucket_ms: Option<i64>,
|
|
}
|
|
|
|
/// One entry from a metric's derived bucket timeseries: the label combination
|
|
/// that bucket represents, plus one `(sequence, timestamp, value)` occurrence
|
|
/// within it.
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct BucketValueDto {
|
|
pub labels: BTreeMap<String, MetricPrimitive>,
|
|
pub sequence: u64,
|
|
pub timestamp_ms: i64,
|
|
pub value: MetricPrimitive,
|
|
}
|
|
|
|
/// Query params for `GET /api/metrics/:name`. `labels` is a single
|
|
/// comma-separated `key=value` list (e.g. `?labels=path=/a,duration=5`)
|
|
/// rather than repeated `label.foo=bar` params, since axum's `Query`
|
|
/// extractor needs a fixed/known set of field names — an arbitrary label set
|
|
/// doesn't fit that without a custom deserializer. Every listed label must
|
|
/// match (compared against each label value's `Display` form, see
|
|
/// `MetricPrimitive`); unlisted labels on a bucket are ignored.
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct BucketValuesParams {
|
|
pub labels: Option<String>,
|
|
pub since_sequence: Option<u64>,
|
|
pub start_ms: Option<i64>,
|
|
pub end_ms: Option<i64>,
|
|
pub limit: Option<usize>,
|
|
}
|
|
|
|
/// One chart-ready time series returned by `GET /api/query` (ticket 013's PromQL
|
|
/// endpoint): a label combination plus its `(timestamp_ms, value)` points, sorted by
|
|
/// timestamp. `labels` uses plain `String` values (not `MetricPrimitive`) since every
|
|
/// value has already been through `MetricPrimitive`'s `Display` form — chart code
|
|
/// doesn't need the typed value, just something to show in a legend/tooltip.
|
|
///
|
|
/// `value` is always `f64` (see `crate::engine::coerce_to_f64` for how a bucket's
|
|
/// `MetricPrimitive` is coerced into one), since a chart needs a plottable number
|
|
/// regardless of the underlying metric primitive's kind.
|
|
#[derive(Debug, Clone, Serialize, PartialEq)]
|
|
pub struct PromqlSeriesDto {
|
|
pub labels: BTreeMap<String, String>,
|
|
pub points: Vec<(i64, f64)>,
|
|
}
|
|
|
|
/// Query params for `GET /api/query`. `promql` is the query string (see
|
|
/// `crate::promql`); `start_ms`/`end_ms` bound which points are included, mirroring
|
|
/// `/api/logs`/`/api/metrics/:name`.
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct PromqlParams {
|
|
pub promql: String,
|
|
pub start_ms: Option<i64>,
|
|
pub end_ms: Option<i64>,
|
|
}
|
|
|
|
/// JSON error body returned by API handlers on a 4xx/5xx response, e.g.
|
|
/// `{ "error": "invalid regex: ..." }`.
|
|
#[derive(Debug, Serialize)]
|
|
pub struct ErrorDto {
|
|
pub error: String,
|
|
}
|
|
|
|
/// Request body for `POST /api/settings/css`: raw CSS content to import and
|
|
/// store alongside the settings file.
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct ImportCssRequest {
|
|
pub filename: String,
|
|
pub content: String,
|
|
}
|
|
|
|
/// Request body for `POST /api/settings/css/link`: a reference to an
|
|
/// external CSS path/URL that ottyl doesn't copy or own.
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct LinkCssRequest {
|
|
pub path_or_url: String,
|
|
}
|
|
|
|
/// Response shape for a single CSS entry, used both by the list endpoint and
|
|
/// by mutation endpoints that return the affected entry. `id` is the entry's
|
|
/// real id for `Imported` entries, or a stable id derived from the
|
|
/// linked path/URL for `Linked` entries (see `settings::css_entry_id`) —
|
|
/// this lets the frontend key/reference both kinds of entries uniformly,
|
|
/// even though only `Imported` entries are deletable/content-fetchable.
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct CssEntryDto {
|
|
pub id: String,
|
|
pub kind: CssEntryKind,
|
|
pub filename: Option<String>,
|
|
pub path_or_url: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Serialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum CssEntryKind {
|
|
Imported,
|
|
Linked,
|
|
}
|
|
|
|
impl From<&CssEntry> for CssEntryDto {
|
|
fn from(entry: &CssEntry) -> Self {
|
|
match entry {
|
|
CssEntry::Imported { id, filename } => CssEntryDto {
|
|
id: id.clone(),
|
|
kind: CssEntryKind::Imported,
|
|
filename: Some(filename.clone()),
|
|
path_or_url: None,
|
|
},
|
|
CssEntry::Linked { path_or_url } => CssEntryDto {
|
|
id: crate::settings::css_entry_id(entry),
|
|
kind: CssEntryKind::Linked,
|
|
filename: None,
|
|
path_or_url: Some(path_or_url.clone()),
|
|
},
|
|
}
|
|
}
|
|
}
|