diff --git a/domains/query_engine/analyzer/src/diff.rs b/domains/query_engine/analyzer/src/diff.rs new file mode 100644 index 00000000..7f4e6569 --- /dev/null +++ b/domains/query_engine/analyzer/src/diff.rs @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::{HashMap, HashSet}; + +use quent_attributes::Value; +use quent_query_engine_ui::{ + self as ui, + diff::{ + Compatibility, DiffDelta, DiffOperatorDelta, DiffOperatorRef, DiffQuerySummary, QueryDiff, + QueryStatDiffs, + }, +}; +use quent_time::TimeSec; +use uuid::Uuid; + +/// Per-query operator data needed to compute a diff. +/// +/// Returned by [`UiAnalyzer::query_operator_stats`]. +pub struct QueryOperatorStats { + pub engine_id: Uuid, + pub instance_name: Option, + pub query_group_id: Option, + pub query_group_name: Option, + pub duration_s: TimeSec, + /// All operators that worked on this query, keyed by operator ID. + pub operators: HashMap, +} + +fn to_f64(v: &Value) -> Option { + match v { + Value::U8(n) => Some(*n as f64), + Value::U16(n) => Some(*n as f64), + Value::U32(n) => Some(*n as f64), + Value::U64(n) => Some(*n as f64), + Value::I8(n) => Some(*n as f64), + Value::I16(n) => Some(*n as f64), + Value::I32(n) => Some(*n as f64), + Value::I64(n) => Some(*n as f64), + Value::F32(n) => Some(*n as f64), + Value::F64(n) => Some(*n), + _ => None, + } +} + +/// Groups operators by type name and sums all numeric stats within each group. +/// +/// Returns a map of `operator_type_name → (count, summed_stats)`. +fn aggregate_by_type( + operators: &HashMap, +) -> HashMap)> { + let mut by_type: HashMap)> = HashMap::new(); + for op in operators.values() { + if let Some(type_name) = op.operator_type_name.as_deref() { + let entry = by_type.entry(type_name.to_string()).or_default(); + entry.0 += 1; + if let Some(stats) = &op.statistics { + for (key, val) in &stats.custom_statistics { + if let Some(v) = val.as_ref().and_then(to_f64) { + *entry.1.entry(key.clone()).or_insert(0.0) += v; + } + } + } + } + } + by_type +} + +pub fn compute_diff( + comparison_query_id: Uuid, + baseline: &QueryOperatorStats, + comparison: &QueryOperatorStats, +) -> QueryDiff { + let summary = DiffQuerySummary { + id: comparison_query_id, + engine_id: comparison.engine_id, + instance_name: comparison.instance_name.clone(), + query_group_id: comparison.query_group_id, + query_group_name: comparison.query_group_name.clone(), + }; + + let duration_delta = comparison.duration_s - baseline.duration_s; + let duration_pct = if baseline.duration_s != 0.0 { + Some(duration_delta / baseline.duration_s * 100.0) + } else { + None + }; + let stat_diff = QueryStatDiffs { + duration: DiffDelta { + stats: ( + Some(Value::F64(baseline.duration_s)), + Some(Value::F64(comparison.duration_s)), + ), + delta: Some(duration_delta), + percent_delta: duration_pct, + }, + }; + + let baseline_by_type = aggregate_by_type(&baseline.operators); + let comparison_by_type = aggregate_by_type(&comparison.operators); + + let all_type_names: HashSet<&str> = baseline_by_type + .keys() + .map(|k| k.as_str()) + .chain(comparison_by_type.keys().map(|k| k.as_str())) + .collect(); + + let empty: HashMap = HashMap::new(); + let mut warnings: Vec = Vec::new(); + let mut operator_diffs: Vec = Vec::new(); + + for type_name in all_type_names { + let b_entry = baseline_by_type.get(type_name); + let c_entry = comparison_by_type.get(type_name); + + if b_entry.is_none() { + warnings.push(format!("operator type '{type_name}' not found in baseline")); + } + if c_entry.is_none() { + warnings.push(format!("operator type '{type_name}' not found in comparison")); + } + + let (b_count, b_stats) = b_entry.map(|(c, s)| (*c, s)).unwrap_or((0, &empty)); + let (c_count, c_stats) = c_entry.map(|(c, s)| (*c, s)).unwrap_or((0, &empty)); + + let all_stat_keys: HashSet<&str> = b_stats + .keys() + .map(|k| k.as_str()) + .chain(c_stats.keys().map(|k| k.as_str())) + .collect(); + + let stats: HashMap = all_stat_keys + .into_iter() + .map(|key| { + let b_val = b_stats.get(key).copied(); + let c_val = c_stats.get(key).copied(); + + let (delta, percent_delta) = match (b_val, c_val) { + (Some(b), Some(c)) => { + let d = c - b; + let pct = if b != 0.0 { Some(d / b * 100.0) } else { None }; + (Some(d), pct) + } + _ => (None, None), + }; + + ( + key.to_string(), + DiffDelta { + stats: (b_val.map(Value::F64), c_val.map(Value::F64)), + delta, + percent_delta, + }, + ) + }) + .collect(); + + operator_diffs.push(DiffOperatorDelta { + operators: ( + DiffOperatorRef { + label: type_name.to_string(), + operator_type_name: Some(type_name.to_string()), + count: b_count, + }, + DiffOperatorRef { + label: type_name.to_string(), + operator_type_name: Some(type_name.to_string()), + count: c_count, + }, + ), + stats, + }); + } + + QueryDiff { + compatibility: Compatibility::Compatible, + query: Some(summary), + operator_diffs: Some(operator_diffs), + stat_diffs: Some(stat_diff), + warnings: if warnings.is_empty() { None } else { Some(warnings) }, + } +} diff --git a/domains/query_engine/analyzer/src/lib.rs b/domains/query_engine/analyzer/src/lib.rs index b39ca2d1..2b9a40a8 100644 --- a/domains/query_engine/analyzer/src/lib.rs +++ b/domains/query_engine/analyzer/src/lib.rs @@ -46,6 +46,7 @@ pub mod model; pub mod view; // UI related mods +pub mod diff; pub mod ui; pub trait QueryEngineModel: Model { diff --git a/domains/query_engine/analyzer/src/ui.rs b/domains/query_engine/analyzer/src/ui.rs index 2d233820..09d03c9d 100644 --- a/domains/query_engine/analyzer/src/ui.rs +++ b/domains/query_engine/analyzer/src/ui.rs @@ -6,6 +6,8 @@ use std::collections::HashMap; use quent_analyzer::AnalyzerResult; use quent_events::Event; use quent_query_engine_ui as ui; + +use crate::diff::QueryOperatorStats; use quent_ui::timeline::{ request::{BulkChunkedTimelineRequest, BulkTimelineRequest, SingleTimelineRequest}, response::{ @@ -52,6 +54,11 @@ pub trait UiAnalyzer { /// non-volumous information related to this query. fn query_bundle(&self, query_id: Uuid) -> AnalyzerResult>; + /// Return the per-operator stats for a single query. + /// + /// Used as input to the workload diff computation. + fn query_operator_stats(&self, query_id: Uuid) -> AnalyzerResult; + /// Access the underlying query engine model of this analyzer. fn query_engine_model(&self) -> &impl QueryEngineModel; diff --git a/domains/query_engine/server/src/lib.rs b/domains/query_engine/server/src/lib.rs index b23e96cb..178ae4ff 100644 --- a/domains/query_engine/server/src/lib.rs +++ b/domains/query_engine/server/src/lib.rs @@ -72,7 +72,9 @@ where timelines: TimelineCache::new(), }; - let mut http_routes = axum::Router::new().nest("/api/engines", ui::routes(state)); + let mut http_routes = axum::Router::new() + .nest("/api/engines", ui::routes(state.clone())) + .nest("/api", ui::diff_routes(state)); #[cfg(feature = "swagger")] { diff --git a/domains/query_engine/server/src/timeline_cache.rs b/domains/query_engine/server/src/timeline_cache.rs index 259953b2..f40ec285 100644 --- a/domains/query_engine/server/src/timeline_cache.rs +++ b/domains/query_engine/server/src/timeline_cache.rs @@ -823,7 +823,8 @@ mod tests { use quent_analyzer::AnalyzerResult; use quent_events::Event; use quent_query_engine_analyzer::{ - QueryEngineModel, engine::Engine, model::InMemoryQueryEngineModel, ui::UiAnalyzer, + QueryEngineModel, diff::QueryOperatorStats, engine::Engine, + model::InMemoryQueryEngineModel, ui::UiAnalyzer, }; use quent_query_engine_model::engine::{EngineEvent, Exit, Init}; use quent_ui::{ @@ -961,6 +962,13 @@ mod tests { unimplemented!("not needed by timeline cache tests") } + fn query_operator_stats( + &self, + _query_id: Uuid, + ) -> AnalyzerResult { + unimplemented!("not needed by timeline cache tests") + } + fn query_engine_model(&self) -> &impl QueryEngineModel { &self.model } diff --git a/domains/query_engine/server/src/ui.rs b/domains/query_engine/server/src/ui.rs index 4efd8027..3bb6f169 100644 --- a/domains/query_engine/server/src/ui.rs +++ b/domains/query_engine/server/src/ui.rs @@ -10,6 +10,7 @@ use axum::{ }; use quent_analyzer::AnalyzerResult; +use quent_query_engine_analyzer::diff::compute_diff; use quent_query_engine_analyzer::{QueryEngineModel, query_group::QueryGroup, ui::UiAnalyzer}; use quent_query_engine_ui as ui; use quent_ui::timeline::{ @@ -32,7 +33,6 @@ pub(crate) mod embedded { #[derive(Embed)] #[folder = "../../../ui/dist/"] struct UiAssets; - pub async fn serve(uri: axum::http::Uri) -> impl IntoResponse { let path = uri.path().trim_start_matches('/'); let file = UiAssets::get(path).or_else(|| UiAssets::get("index.html")); @@ -278,6 +278,47 @@ where )) } +#[cfg_attr(feature = "swagger", utoipa::path( + post, + path = "/api/workload-diff", + tag = "diff", + request_body = Object, + responses( + (status = 200, description = "Workload diff statistics", body = Object) + ) +))] +#[tracing::instrument(skip_all, err)] +async fn workload_diff( + State(state): State>, + Json(request): Json, +) -> ServerResult> +where + A: UiAnalyzer + Send + Sync + 'static, +{ + let baseline_analyzer = state + .analyzers + .get(request.baseline_query.engine_id) + .await?; + let baseline_stats = + baseline_analyzer.query_operator_stats(request.baseline_query.query_id)?; + + let mut diffs = Vec::new(); + for comparison_ref in &request.comparison_queries { + let comparison_analyzer = state.analyzers.get(comparison_ref.engine_id).await?; + let comparison_stats = + comparison_analyzer.query_operator_stats(comparison_ref.query_id)?; + diffs.push(compute_diff( + comparison_ref.query_id, + &baseline_stats, + &comparison_stats, + )); + } + + Ok(Json(ui::diff::DiffResponse { + comparison_queries: diffs, + })) +} + #[cfg(feature = "swagger")] #[derive(utoipa::OpenApi)] #[openapi( @@ -289,14 +330,31 @@ where query, single_timeline, bulk_timelines, + workload_diff, ), tags( (name = "engines", description = "Engine, query group, and query management"), (name = "timelines", description = "Resource timeline data"), + (name = "diff", description = "Cross-engine query comparison"), ) )] pub(crate) struct ApiDoc; +pub fn diff_routes(state: ServiceState) -> Router<()> +where + A: UiAnalyzer + Send + Sync + 'static, + ::EntityRef: serde::Serialize, + ::TimelineGlobalParams: + Send + Sync + Clone + serde::Serialize + Hash + Eq + 'static, + ::TimelineParams: Send + Sync + Clone + serde::Serialize + Hash + Eq + 'static, + for<'de> ::TimelineGlobalParams: serde::Deserialize<'de>, + for<'de> ::TimelineParams: serde::Deserialize<'de>, +{ + Router::new() + .route("/workload-diff", post(workload_diff)) + .with_state(state) +} + pub fn routes(state: ServiceState) -> Router<()> where A: UiAnalyzer + Send + Sync + 'static, diff --git a/domains/query_engine/ui/src/diff.rs b/domains/query_engine/ui/src/diff.rs new file mode 100644 index 00000000..6ea7ad0f --- /dev/null +++ b/domains/query_engine/ui/src/diff.rs @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +use quent_attributes::Value; +use serde::{Deserialize, Serialize}; +use ts_rs::TS; +use uuid::Uuid; + +/// Identifies a single query within a specific engine. +#[derive(TS, Debug, Deserialize)] +pub struct DiffQueryRef { + pub engine_id: Uuid, + pub query_id: Uuid, +} + +/// Request body for a workload diff. +#[derive(TS, Debug, Deserialize)] +pub struct DiffRequest { + pub baseline_query: DiffQueryRef, + pub comparison_queries: Vec, +} + +/// Whether two queries are structurally comparable (i.e. same plan shape). +#[derive(TS, Debug, Serialize)] +#[serde(rename_all = "lowercase")] +#[ts(rename_all = "lowercase")] +pub enum Compatibility { + Compatible, + Incompatible, +} + +/// Summary metadata for a query included in a diff result. +#[derive(TS, Debug, Serialize)] +pub struct DiffQuerySummary { + pub id: Uuid, + pub engine_id: Uuid, + pub instance_name: Option, + pub query_group_id: Option, + pub query_group_name: Option, +} + +/// A reference to an operator type in a diff, aggregated across all operators of that type. +#[derive(TS, Debug, Serialize)] +pub struct DiffOperatorRef { + pub label: String, + pub operator_type_name: Option, + /// Number of operators of this type that were aggregated. + pub count: usize, +} + +/// The raw values and computed delta for a single stat across two queries. +/// +/// `stats.0` is the baseline value, `stats.1` is the comparison value. +#[derive(TS, Debug, Serialize)] +pub struct DiffDelta { + pub stats: (Option, Option), + pub delta: Option, + pub percent_delta: Option, +} + +/// Stat deltas for a matched pair of operators (one from each query). +#[derive(TS, Debug, Serialize)] +pub struct DiffOperatorDelta { + /// `operators.0` is from the baseline, `operators.1` is from the comparison query. + pub operators: (DiffOperatorRef, DiffOperatorRef), + /// Keyed by stat name. + pub stats: HashMap, +} + +/// Query-level stat deltas (derived from query timestamps). +#[derive(TS, Debug, Serialize)] +pub struct QueryStatDiffs { + pub duration: DiffDelta, +} + +/// The diff result for a single comparison query against the baseline. +#[derive(TS, Debug, Serialize)] +pub struct QueryDiff { + pub compatibility: Compatibility, + #[serde(skip_serializing_if = "Option::is_none")] + pub query: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub operator_diffs: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub stat_diffs: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub warnings: Option>, +} + +/// Response body for a workload diff. +/// +/// One [`QueryDiff`] per entry in `DiffRequest.comparison_queries`, in the same order. +#[derive(TS, Debug, Serialize)] +pub struct DiffResponse { + pub comparison_queries: Vec, +} diff --git a/domains/query_engine/ui/src/lib.rs b/domains/query_engine/ui/src/lib.rs index ffd35b84..65fd5490 100644 --- a/domains/query_engine/ui/src/lib.rs +++ b/domains/query_engine/ui/src/lib.rs @@ -3,6 +3,8 @@ //! Types shared with the UI. +pub mod diff; + use quent_analyzer::fsm::FsmTypeDecl; use quent_attributes::{Attribute, Value}; use quent_query_engine_model as qe; diff --git a/examples/simulator/analyzer/src/lib.rs b/examples/simulator/analyzer/src/lib.rs index 35b8d39c..84af87f4 100644 --- a/examples/simulator/analyzer/src/lib.rs +++ b/examples/simulator/analyzer/src/lib.rs @@ -3,8 +3,9 @@ use quent_events::Event; pub use quent_query_engine_analyzer::QueryEngineModel; +use quent_query_engine_analyzer::diff::QueryOperatorStats; use quent_query_engine_analyzer::ui::UiAnalyzer; -use quent_query_engine_ui::{QueryBundle, QueryEntities}; +use quent_query_engine_ui::{self as ui, QueryBundle, QueryEntities}; use quent_ui::{ FiniteStateMachine, ResourceGroupNode, ResourceTree, convert_resource_tree, quantity::QuantitySpec, @@ -239,6 +240,37 @@ impl UiAnalyzer for SimulatorUiAnalyzer { }) } + fn query_operator_stats(&self, query_id: Uuid) -> AnalyzerResult { + let view = self.model.query_view(query_id)?; + + let engine = view.engine()?.to_ui()?; + let epoch = view.query_epoch(query_id)?; + let query = self.model.query(query_id)?; + // println!("{:#?}", query.to_ui()?); + let duration_s = to_secs(query.span()?.duration()); + + // get query group metadata + let query_group_id = query.query_group_id().ok_or_else(|| { + quent_analyzer::AnalyzerError::IncompleteEntity(format!( + "query {} has no query_group_id", + query_id + )) + })?; + let query_group = view.query_group(query_group_id)?.to_ui(); + // println!("{:#?}", query_group.to_ui()); + let operators: StdHashMap = + view.operators().map(|o| (o.id(), o.to_ui(epoch))).collect(); + + Ok(QueryOperatorStats { + engine_id: engine.id, + instance_name: engine.instance_name, + query_group_id: Some(query_group_id), + query_group_name: query_group.instance_name, + duration_s, + operators, + }) + } + fn query_engine_model(&self) -> &impl QueryEngineModel { &self.model } diff --git a/examples/simulator/server/build.rs b/examples/simulator/server/build.rs index 5b3ca3d0..bf87e404 100644 --- a/examples/simulator/server/build.rs +++ b/examples/simulator/server/build.rs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +use quent_query_engine_ui::diff::{DiffRequest, DiffResponse}; use quent_query_engine_ui::QueryBundle; use quent_simulator_ui::{EntityRef, QueryFilter, TaskFilter}; use quent_ui::timeline::{ @@ -20,5 +21,8 @@ fn main() -> Result<(), Box> { as TS>::export_all_to(TS_OUT_DIR)?; ::export_all_to(TS_OUT_DIR)?; + ::export_all_to(TS_OUT_DIR)?; + ::export_all_to(TS_OUT_DIR)?; + Ok(()) } diff --git a/examples/simulator/server/ts-bindings/Compatibility.ts b/examples/simulator/server/ts-bindings/Compatibility.ts new file mode 100644 index 00000000..f02ef045 --- /dev/null +++ b/examples/simulator/server/ts-bindings/Compatibility.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Whether two queries are structurally comparable (i.e. same plan shape). + */ +export type Compatibility = "compatible" | "incompatible"; diff --git a/examples/simulator/server/ts-bindings/DiffDelta.ts b/examples/simulator/server/ts-bindings/DiffDelta.ts new file mode 100644 index 00000000..0c003e86 --- /dev/null +++ b/examples/simulator/server/ts-bindings/DiffDelta.ts @@ -0,0 +1,9 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Value } from "./Value"; + +/** + * The raw values and computed delta for a single stat across two queries. + * + * `stats.0` is the baseline value, `stats.1` is the comparison value. + */ +export type DiffDelta = { stats: [Value | null, Value | null], delta: number | null, percent_delta: number | null, }; diff --git a/examples/simulator/server/ts-bindings/DiffOperatorDelta.ts b/examples/simulator/server/ts-bindings/DiffOperatorDelta.ts new file mode 100644 index 00000000..12ce5063 --- /dev/null +++ b/examples/simulator/server/ts-bindings/DiffOperatorDelta.ts @@ -0,0 +1,16 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DiffDelta } from "./DiffDelta"; +import type { DiffOperatorRef } from "./DiffOperatorRef"; + +/** + * Stat deltas for a matched pair of operators (one from each query). + */ +export type DiffOperatorDelta = { +/** + * `operators.0` is from the baseline, `operators.1` is from the comparison query. + */ +operators: [DiffOperatorRef, DiffOperatorRef], +/** + * Keyed by stat name. + */ +stats: { [key in string]?: DiffDelta }, }; diff --git a/examples/simulator/server/ts-bindings/DiffOperatorRef.ts b/examples/simulator/server/ts-bindings/DiffOperatorRef.ts new file mode 100644 index 00000000..868f43ad --- /dev/null +++ b/examples/simulator/server/ts-bindings/DiffOperatorRef.ts @@ -0,0 +1,10 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * A reference to an operator type in a diff, aggregated across all operators of that type. + */ +export type DiffOperatorRef = { label: string, operator_type_name: string | null, +/** + * Number of operators of this type that were aggregated. + */ +count: number, }; diff --git a/examples/simulator/server/ts-bindings/DiffQueryRef.ts b/examples/simulator/server/ts-bindings/DiffQueryRef.ts new file mode 100644 index 00000000..567a90e2 --- /dev/null +++ b/examples/simulator/server/ts-bindings/DiffQueryRef.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Identifies a single query within a specific engine. + */ +export type DiffQueryRef = { engine_id: string, query_id: string, }; diff --git a/examples/simulator/server/ts-bindings/DiffQuerySummary.ts b/examples/simulator/server/ts-bindings/DiffQuerySummary.ts new file mode 100644 index 00000000..d2482337 --- /dev/null +++ b/examples/simulator/server/ts-bindings/DiffQuerySummary.ts @@ -0,0 +1,6 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +/** + * Summary metadata for a query included in a diff result. + */ +export type DiffQuerySummary = { id: string, engine_id: string, instance_name: string | null, query_group_id: string | null, query_group_name: string | null, }; diff --git a/examples/simulator/server/ts-bindings/DiffRequest.ts b/examples/simulator/server/ts-bindings/DiffRequest.ts new file mode 100644 index 00000000..1608d44c --- /dev/null +++ b/examples/simulator/server/ts-bindings/DiffRequest.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DiffQueryRef } from "./DiffQueryRef"; + +/** + * Request body for a workload diff. + */ +export type DiffRequest = { baseline_query: DiffQueryRef, comparison_queries: Array, }; diff --git a/examples/simulator/server/ts-bindings/DiffResponse.ts b/examples/simulator/server/ts-bindings/DiffResponse.ts new file mode 100644 index 00000000..9340e88a --- /dev/null +++ b/examples/simulator/server/ts-bindings/DiffResponse.ts @@ -0,0 +1,9 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { QueryDiff } from "./QueryDiff"; + +/** + * Response body for a workload diff. + * + * One [`QueryDiff`] per entry in `DiffRequest.comparison_queries`, in the same order. + */ +export type DiffResponse = { comparison_queries: Array, }; diff --git a/examples/simulator/server/ts-bindings/QueryDiff.ts b/examples/simulator/server/ts-bindings/QueryDiff.ts new file mode 100644 index 00000000..c85929ee --- /dev/null +++ b/examples/simulator/server/ts-bindings/QueryDiff.ts @@ -0,0 +1,10 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Compatibility } from "./Compatibility"; +import type { DiffOperatorDelta } from "./DiffOperatorDelta"; +import type { DiffQuerySummary } from "./DiffQuerySummary"; +import type { QueryStatDiffs } from "./QueryStatDiffs"; + +/** + * The diff result for a single comparison query against the baseline. + */ +export type QueryDiff = { compatibility: Compatibility, query: DiffQuerySummary | null, operator_diffs: Array | null, stat_diffs: QueryStatDiffs | null, warnings: Array | null, }; diff --git a/examples/simulator/server/ts-bindings/QueryStatDiffs.ts b/examples/simulator/server/ts-bindings/QueryStatDiffs.ts new file mode 100644 index 00000000..c57d9354 --- /dev/null +++ b/examples/simulator/server/ts-bindings/QueryStatDiffs.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { DiffDelta } from "./DiffDelta"; + +/** + * Query-level stat deltas (derived from query timestamps). + */ +export type QueryStatDiffs = { duration: DiffDelta, };