Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
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
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions crates/ui/src/entities/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Requests and responses for entity-list queries.
pub mod request;
pub mod response;
137 changes: 137 additions & 0 deletions crates/ui/src/entities/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashSet;

use quent_analyzer::{AnalyzerResult, Model, resource::tree::ResourceTreeNode};
use quent_time::{TimeError, TimeSec, TimeUnixNanoSec, span::SpanUnixNanoSec, to_nanosecs};
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use uuid::Uuid;

use crate::paginate::PageParams;

/// Restricts returned entities to appear in a certain scope.
#[derive(TS, Debug, Clone, Serialize, Deserialize)]
pub enum EntityScope {
/// Only return entities that use this resource.
Resource { resource_id: Uuid },
/// Only return entities that use any resource within this group.
ResourceGroup {
resource_group_id: Uuid,
resource_type_name: String,
},
}

impl EntityScope {
/// Resolve the scope to the resource IDs it covers: the single resource, or
/// the leaf resources of the group that have the requested type.
pub fn resolve(&self, model: &impl Model) -> AnalyzerResult<HashSet<Uuid>> {
match self {
EntityScope::Resource { resource_id } => {
model.resource(*resource_id)?;
Ok([*resource_id].into_iter().collect())
}
EntityScope::ResourceGroup {
resource_group_id,
resource_type_name,
} => {
let tree = ResourceTreeNode::try_new(model, *resource_group_id)?;
Ok(tree
.iter_leaf_ids()

@dhruv9vats dhruv9vats Jul 13, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean that only leaf resources should have valid usages ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this limitation is removed as a part of #191.

The resource concept will be unrelated to hierarchical trees of entities, also see:

https://github.com/rapidsai/quent/blob/main/crates/ref-tree/src/lib.rs
https://github.com/rapidsai/quent/blob/main/crates/resource/src/lib.rs

.filter(|&id| {
model
.resource(id)
.is_ok_and(|r| r.type_name() == resource_type_name)
})
.collect())
}
}
}
}

/// A time window, resolved against an epoch supplied at conversion.
#[derive(TS, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct TimeWindow {
/// The start time of the window in seconds.
pub start: TimeSec,
/// The end time of the window in seconds.
pub end: TimeSec,
}

impl TimeWindow {
/// Resolve to an absolute span by offsetting from `epoch`.
pub fn try_into_span(self, epoch: TimeUnixNanoSec) -> Result<SpanUnixNanoSec, TimeError> {
SpanUnixNanoSec::try_new(
epoch + to_nanosecs(self.start),
epoch + to_nanosecs(self.end),
)
}
}

/// Entity filters.
///
/// Every field that is set must match, `None` fields do not filter.
#[derive(TS, Debug, Clone, Default, Serialize, Deserialize)]
pub struct EntityListFilter {
/// Restrict resulting entities to be in this scope.
pub scope: Option<EntityScope>,
/// Restrict resulting entities to be of this type.
pub entity_type_name: Option<String>,
/// Keep only entities with resource usages longer than this threshold.
///
/// N.B. only Fsm-type entities can have usages.
pub min_usage_s: Option<TimeSec>,
}

/// The key entities are sorted by.
#[derive(TS, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum EntitySortKey {
/// The longest single resource-usage span within the window.
UsageDuration,
}

/// The direction to sort in.
#[derive(TS, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum SortDir {
/// Sort in ascending order.
Asc,
/// Sort in descending order.
Desc,
}

/// Sorting parameters.
#[derive(TS, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Sort {
/// The key to sort on.
pub key: EntitySortKey,
/// The direction to sort in.
pub dir: SortDir,
}

/// A single entity-list query.
#[derive(TS, Debug, Clone, Serialize, Deserialize)]
pub struct EntityListEntry<EntryParams> {
/// The window of time entities must fall in.
pub window: TimeWindow,
/// Filter parameters.
pub filter: EntityListFilter,
/// Sort parameters.
pub sort: Sort,
/// Pagination parameters.
///
/// When this is not set, return the full list. Depending on the dataset
/// size and other parameters, this may result in a large volume of data and
/// should be used with care.
pub page: Option<PageParams>,
/// Per-query application-specific parameters.
pub application: EntryParams,
}

/// Parameters for listing entities.
#[derive(TS, Debug, Clone, Serialize, Deserialize)]
pub struct EntityListRequest<GlobalParams, EntryParams> {
pub entry: EntityListEntry<EntryParams>,
/// Global application parameters shared by the query.
pub app_params: GlobalParams,
}
17 changes: 17 additions & 0 deletions crates/ui/src/entities/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use serde::Serialize;
use ts_rs::TS;

use crate::FiniteStateMachine;

/// A ranked, paged list of entities.
#[derive(TS, Debug, Clone, Serialize)]
pub struct EntityListResponse {
// TODO(johanpel): generalize to other entity types, but only FSMs are
// represented today.
pub items: Vec<FiniteStateMachine>,
/// The count of entities matching the filter before paging.
pub total: u32,
}
54 changes: 54 additions & 0 deletions crates/ui/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashMap;

use quent_analyzer::{self as a, AnalyzerResult, Entity, Model, resource::tree::ResourceTreeNode};
use quent_time::{TimeSec, TimeUnixNanoSec, try_to_secs_relative};
use serde::Serialize;
use ts_rs::TS;
use uuid::Uuid;

pub mod entities;
pub mod paginate;
pub mod quantity;
pub mod timeline;

Expand Down Expand Up @@ -249,4 +253,54 @@ impl FiniteStateMachine {
.collect::<Result<Vec<_>, _>>()?,
})
}

/// Build from any application FSM via the [`FsmUsages`](a::fsm::FsmUsages)
/// interface.
///
/// Usages are grouped onto their state's transition by state name, which is
/// unique within an FSM.
pub fn try_from_fsm<'a, F>(
fsm: &'a F,
epoch: TimeUnixNanoSec,
) -> Result<Self, quent_time::TimeError>
where
F: a::fsm::FsmUsages<'a>,
{
use a::fsm::Transition;
use a::resource::Usage;
use quent_time::Timestamp;

let mut usages_by_state: HashMap<String, Vec<FsmUsage>> = HashMap::new();
for (state_name, usage) in fsm.usages_with_state_names() {
usages_by_state
.entry(state_name.to_owned())
.or_default()
.push(FsmUsage {
resource: usage.resource_id(),
capacities: usage
.capacities()
.map(|c| (c.name.to_string(), c.value))
.collect(),
});
}

// 0..=len covers every transition including the exit transition.
let transitions = (0..=fsm.len())
.filter_map(|i| fsm.transition(i))
.map(|t| {
Ok(FsmTransition {
name: t.name().to_owned(),
usages: usages_by_state.remove(t.name()).unwrap_or_default(),
timestamp: try_to_secs_relative(t.timestamp(), epoch)?,
})
})
.collect::<Result<Vec<_>, quent_time::TimeError>>()?;

Ok(Self {
id: fsm.id(),
type_name: fsm.type_name().to_owned(),
instance_name: fsm.instance_name().to_owned(),
transitions,
})
}
}
16 changes: 16 additions & 0 deletions crates/ui/src/paginate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Pagination parameters shared across list endpoints.

use serde::{Deserialize, Serialize};
use ts_rs::TS;

/// Parameters for paginated lists.
#[derive(TS, Debug, Clone, Copy, Serialize, Deserialize)]
pub struct PageParams {
/// The maximum size of a page.
pub max: u32,
/// The zero-based index of the requested page.
pub page: u32,
}
Loading
Loading