Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions nexus/src/external_api/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6571,7 +6571,11 @@ impl NexusExternalApi for NexusExternalApiImpl {
nexus
.timeseries_query(&opctx, &query)
.await
.map(|tables| HttpResponseOk(views::OxqlQueryResult { tables }))
.map(|tables| {
HttpResponseOk(views::OxqlQueryResult {
tables: tables.into_iter().map(Into::into).collect(),
})
})
.map_err(HttpError::from)
};
apictx
Expand All @@ -6598,7 +6602,11 @@ impl NexusExternalApi for NexusExternalApiImpl {
nexus
.timeseries_query_project(&opctx, &project_lookup, &query)
.await
.map(|tables| HttpResponseOk(views::OxqlQueryResult { tables }))
.map(|tables| {
HttpResponseOk(views::OxqlQueryResult {
tables: tables.into_iter().map(Into::into).collect(),
})
})
.map_err(HttpError::from)
};
apictx
Expand Down
2 changes: 1 addition & 1 deletion nexus/tests/integration_tests/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ async fn test_instance_watcher_metrics(

#[track_caller]
fn count_state(
table: &oxql_types::Table,
table: &oxql_types::TableOutput,
instance_id: InstanceUuid,
state: &'static str,
) -> Result<i64, MetricsNotYet> {
Expand Down
10 changes: 5 additions & 5 deletions nexus/tests/integration_tests/metrics_querier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl<'a, N> MetricsQuerier<'a, N> {
cond: F,
) -> T
where
F: Fn(Vec<oxql_types::Table>) -> Result<T, MetricsNotYet>,
F: Fn(Vec<oxql_types::TableOutput>) -> Result<T, MetricsNotYet>,
{
self.timeseries_query_until("/v1/system/timeseries/query", query, cond)
.await
Expand All @@ -108,7 +108,7 @@ impl<'a, N> MetricsQuerier<'a, N> {
cond: F,
) -> T
where
F: Fn(Vec<oxql_types::Table>) -> Result<T, MetricsNotYet>,
F: Fn(Vec<oxql_types::TableOutput>) -> Result<T, MetricsNotYet>,
{
self.timeseries_query_until(
&format!("/v1/timeseries/query?project={project}"),
Expand All @@ -128,7 +128,7 @@ impl<'a, N> MetricsQuerier<'a, N> {
&self,
project: &str,
query: &str,
) -> Vec<oxql_types::Table> {
) -> Vec<oxql_types::TableOutput> {
self.project_timeseries_query_until(project, query, |tables| Ok(tables))
.await
}
Expand Down Expand Up @@ -270,7 +270,7 @@ impl<'a, N> MetricsQuerier<'a, N> {
cond: F,
) -> T
where
F: Fn(Vec<oxql_types::Table>) -> Result<T, MetricsNotYet>,
F: Fn(Vec<oxql_types::TableOutput>) -> Result<T, MetricsNotYet>,
{
let result = wait_for_condition(
|| async {
Expand Down Expand Up @@ -389,5 +389,5 @@ impl<'a, N> MetricsQuerier<'a, N> {

enum TimeseriesQueryResult {
TimeseriesNotFound,
Ok(Vec<oxql_types::Table>),
Ok(Vec<oxql_types::TableOutput>),
}
2 changes: 1 addition & 1 deletion nexus/types/src/external_api/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1117,7 +1117,7 @@ pub struct AllowList {
#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)]
pub struct OxqlQueryResult {
/// Tables resulting from the query, each containing timeseries.
pub tables: Vec<oxql_types::Table>,
pub tables: Vec<oxql_types::TableOutput>,
}

// ALERTS
Expand Down
8 changes: 4 additions & 4 deletions openapi/nexus.json
Original file line number Diff line number Diff line change
Expand Up @@ -22275,7 +22275,7 @@
"description": "Tables resulting from the query, each containing timeseries.",
"type": "array",
"items": {
"$ref": "#/components/schemas/Table"
"$ref": "#/components/schemas/TableOutput"
}
}
},
Expand Down Expand Up @@ -25523,16 +25523,16 @@
"vlan_id"
]
},
"Table": {
"TableOutput": {
"description": "A table represents one or more timeseries with the same schema.\n\nA table is the result of an OxQL query. It contains a name, usually the name of the timeseries schema from which the data is derived, and any number of timeseries, which contain the actual data.",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"timeseries": {
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Timeseries"
}
}
Expand Down
1 change: 1 addition & 0 deletions oximeter/oxql-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub mod point;
pub mod table;

pub use self::table::Table;
pub use self::table::TableOutput;
pub use self::table::Timeseries;

/// Describes the time alignment for an OxQL query.
Expand Down
58 changes: 58 additions & 0 deletions oximeter/oxql-types/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,61 @@ impl Table {
}
}
}

/// A table represents one or more timeseries with the same schema.
///
/// A table is the result of an OxQL query. It contains a name, usually the name
/// of the timeseries schema from which the data is derived, and any number of
/// timeseries, which contain the actual data.
//
// # Motivation
//
// This struct is derived from [`Table`] but presents timeseries data as a `Vec`
// rather than a map keyed by [`TimeseriesKey`]. This provides a cleaner JSON
// representation for external consumers, as these numeric keys are ephemeral
// identifiers that have no meaning to API consumers. Key ordering is retained
// as this is contructed from the already sorted values present in [`Table`].
//
// When serializing a [`Table`] to JSON, the `BTreeMap<TimeseriesKey, Timeseries>`
// structure produces output with numeric keys like:
// ```json
// {
// "timeseries": {
// "2352746367989923131": { ... },
// "3940108470521992408": { ... }
// }
// }
// ```
//
// `TableOutput` instead serializes timeseries as an array:
// ```json
// {
// "timeseries": [ { ... }, { ... } ]
// }
// ```
#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
pub struct TableOutput {
// The name of the table.
pub name: String,
// The set of timeseries in the table, ordered by key.
timeseries: Vec<Timeseries>,
}

impl From<Table> for TableOutput {
fn from(table: Table) -> Self {
let timeseries: Vec<_> = table.timeseries.into_values().collect();
TableOutput { name: table.name, timeseries }
}
}

impl TableOutput {
/// Return the name of the table.
pub fn name(&self) -> &str {
self.name.as_str()
}

/// Return the list of timeseries in this table, ordered by key.
pub fn timeseries(&self) -> impl ExactSizeIterator<Item = &Timeseries> {
self.timeseries.iter()
}
}
Loading