Skip to content
87 changes: 74 additions & 13 deletions crates/analyzer/src/timeline/binned/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use std::hash::Hash;

use quent_time::{SpanNanoSec, TimeNanoSec, bin::BinnedSpan};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use rustc_hash::FxHashMap as HashMap;
use uuid::Uuid;

use crate::{
Expand All @@ -25,24 +25,34 @@ fn convert_capacity(
Ok(capacity_type.reinterpret_capacity_value(capacity_value.value.unwrap_or_default(), span))
}

/// An entity that exceeded the long-entities threshold on this resource, with
/// its longest usage span there.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LongUsageEntity {
pub entity_id: Uuid,
pub longest_usage: TimeNanoSec,
}

#[derive(Clone, Debug)]
pub struct ResourceTimeline<'a> {
pub config: BinnedSpan,
pub data: HashMap<&'a str, Vec<f64>>,
pub long_entities: Vec<Uuid>,
/// Long entities ordered longest-usage-first, or `None` when not requested.
pub long_usage_entities: Option<Vec<LongUsageEntity>>,
}

#[derive(Clone, Debug)]
pub struct ResourceTimelineByKey<'a, K> {
pub config: BinnedSpan,
pub data: HashMap<(K, &'a str), Vec<f64>>,
pub long_entities: Vec<Uuid>,
/// Long entities ordered longest-usage-first, or `None` when not requested.
pub long_usage_entities: Option<Vec<LongUsageEntity>>,
}

pub struct ResourceTimelineBuilder<'a> {
resource_type: &'a ResourceTypeDecl,
aggregator: KeyedAggregator<&'a str>,
long_entities: HashSet<Uuid>,
long_entities: HashMap<Uuid, TimeNanoSec>,
long_entities_threshold: Option<TimeNanoSec>,
}

Expand All @@ -57,7 +67,7 @@ impl<'a> ResourceTimelineBuilder<'a> {
Ok(Self {
resource_type,
aggregator,
long_entities: HashSet::default(),
long_entities: HashMap::default(),
long_entities_threshold,
})
}
Expand All @@ -76,7 +86,11 @@ impl<'a> ResourceTimelineBuilder<'a> {
&& usage.span().duration() > threshold
&& usage.span().intersects(&self.aggregator.config.span)
{
self.long_entities.insert(usage.entity_id());
let duration = usage.span().duration();
self.long_entities
.entry(usage.entity_id())
.and_modify(|d| *d = (*d).max(duration))
.or_insert(duration);
}
Ok(())
}
Expand All @@ -95,15 +109,30 @@ impl<'a> ResourceTimelineBuilder<'a> {
ResourceTimeline {
config: self.aggregator.config,
data: self.aggregator.finish(),
long_entities: self.long_entities.into_iter().collect(),
long_usage_entities: self.long_entities_threshold.is_some().then(|| {
let mut entities: Vec<LongUsageEntity> = self
.long_entities
.into_iter()
.map(|(entity_id, longest_usage)| LongUsageEntity {
entity_id,
longest_usage,
})
.collect();
entities.sort_by(|a, b| {
b.longest_usage
.cmp(&a.longest_usage)
.then_with(|| a.entity_id.cmp(&b.entity_id))
});
entities
}),
}
}
}

pub struct ResourceTimelineByKeyBuilder<'a, K> {
resource_type: &'a ResourceTypeDecl,
aggregator: KeyedAggregator<(K, &'a str)>,
long_entities: HashSet<Uuid>,
long_entities: HashMap<Uuid, TimeNanoSec>,
long_entities_threshold: Option<TimeNanoSec>,
}

Expand All @@ -121,7 +150,7 @@ where
Ok(Self {
resource_type,
aggregator,
long_entities: HashSet::default(),
long_entities: HashMap::default(),
long_entities_threshold,
})
}
Expand All @@ -139,7 +168,11 @@ where
&& usage.span().duration() > threshold
&& usage.span().intersects(&self.aggregator.config.span)
{
self.long_entities.insert(usage.entity_id());
let duration = usage.span().duration();
self.long_entities
.entry(usage.entity_id())
.and_modify(|d| *d = (*d).max(duration))
.or_insert(duration);
}
Ok(())
}
Expand All @@ -158,7 +191,23 @@ where
ResourceTimelineByKey {
config: self.aggregator.config,
data: self.aggregator.finish(),
long_entities: self.long_entities.into_iter().collect(),
long_usage_entities: self.long_entities_threshold.is_some().then(|| {
let mut entities: Vec<LongUsageEntity> = self
.long_entities
.into_iter()
.map(|(entity_id, longest_usage)| LongUsageEntity {
entity_id,
longest_usage,
})
.collect();
// Longest usage first; id tie-break for determinism.
entities.sort_by(|a, b| {
b.longest_usage
.cmp(&a.longest_usage)
.then_with(|| a.entity_id.cmp(&b.entity_id))
});
entities
}),
}
}
}
Expand All @@ -167,6 +216,8 @@ where
mod tests {
use std::num::NonZero;

use rustc_hash::FxHashSet as HashSet;

use crate::{
fsm::{
FsmUsages,
Expand Down Expand Up @@ -778,7 +829,12 @@ mod tests {
let mut outside_builder =
ResourceTimelineBuilder::try_new(resource_type, config, Some(threshold)).unwrap();
outside_builder.try_extend(outside_fsms.usages()).unwrap();
assert!(!outside_builder.build().long_entities.contains(&resource_id));
assert!(
!outside_builder
.build()
.long_usage_entities
.is_some_and(|e| e.iter().any(|lu| lu.entity_id == resource_id))
);

let mut inside_fsms = InMemoryFsms::<RtFsm, RtFsmTransition>::new();
inside_fsms.insert(make_fsm(500, 1500));
Expand All @@ -787,6 +843,11 @@ mod tests {
let mut inside_builder =
ResourceTimelineBuilder::try_new(resource_type, config, Some(threshold)).unwrap();
inside_builder.try_extend(inside_fsms.usages()).unwrap();
assert!(inside_builder.build().long_entities.contains(&resource_id));
assert!(
inside_builder
.build()
.long_usage_entities
.is_some_and(|e| e.iter().any(|lu| lu.entity_id == resource_id))
);
}
}
32 changes: 27 additions & 5 deletions crates/ui/src/timeline/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,34 @@ pub struct EntityFilter {
// TODO(johanpel): instance name
}

/// Parameters of paginated APIs.
#[derive(TS, Debug, Clone, Serialize, Deserialize)]
pub struct PageParams {
/// Maximum number of items per page.
pub max: u32,
/// The zero-based page index requested.
pub page: u32,
}

/// Parameters for requesting long entities in a resource timeline
#[derive(TS, Debug, Clone, Serialize, Deserialize)]
pub struct LongEntitiesParams {
/// Fully include entities that have usages exceeding this amount of time.
pub threshold_s: TimeSec,
/// Parameters of the page of long entities. If this is not set, return
/// all long entities.
pub page: Option<PageParams>,
}

/// Parameters for requesting a resource timeline.
#[derive(TS, Debug, Clone, Serialize, Deserialize)]
pub struct ResourceTimelineRequest<TimelineParams> {
/// The ID of the resource
pub resource_id: Uuid,
/// If set, fully include entities that have usages exceeding this amount of time.
pub long_entities_threshold_s: Option<TimeSec>,
/// Parameters for dealing with long entities.
///
/// If this is not set, then no long entities are returned.
pub long_entities: Option<LongEntitiesParams>,
/// Entity filters.
pub entity_filter: EntityFilter,
/// Application-specific request parameters, e.g. for filtering.
Expand All @@ -71,9 +92,10 @@ pub struct ResourceGroupTimelineRequest<TimelineParams> {
/// The type name of the leaf resources for which to produce the timeline
/// for this group.
pub resource_type_name: String,
/// If set, fully include entities that have usages exceeding this amount of
/// time in seconds.
pub long_entities_threshold_s: Option<TimeSec>,
/// Parameters for dealing with long entities.
///
/// If this is not set, then no long entities are returned.
pub long_entities: Option<LongEntitiesParams>,
/// Entity filters.
pub entity_filter: EntityFilter,
/// Application-specific request parameters, e.g. for filtering.
Expand Down
20 changes: 16 additions & 4 deletions crates/ui/src/timeline/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@ use ts_rs::TS;

use crate::FiniteStateMachine;

/// Long entities in resource timelines.
#[derive(TS, Debug, Clone, Serialize)]
pub struct LongEntities {
/// FSMs that have usage spans exceeding the long entities threshold.
///
/// This may be empty if no long entities exist or if they were not requested.
/// If pagination is requested, this will only hold one page.
pub long_fsms: Vec<FiniteStateMachine>,
/// Total number of long FSMs, before pagination.
pub long_fsms_total: u32,
}

#[derive(TS, Debug, Clone, Serialize)]
pub struct ResourceTimelineBinned {
/// The configuration of the binned timeline.
pub config: BinnedSpanSec,
/// Maps a resource capacity name to a vector where each element holds an
/// aggregated value of a time bin.
pub capacities_values: HashMap<String, Vec<f64>>,
/// FSMs that have usage spans exceeding the long_entities_threshold.
pub long_fsms: Vec<FiniteStateMachine>,
/// Entities with long usage durations of this resource, if requested.
pub long_entities: Option<LongEntities>,
}

#[derive(TS, Debug, Clone, Serialize)]
Expand All @@ -27,8 +39,8 @@ pub struct ResourceTimelineBinnedByState {
/// Maps a resource capacity name to a map of a state name to a vector where
/// each element holds an aggregated value of a time bin.
pub capacities_states_values: HashMap<String, HashMap<String, Vec<f64>>>,
/// FSMs that have usage spans exceeding the long_entities_threshold.
pub long_fsms: Vec<FiniteStateMachine>,
/// Entities with long usage durations of this resource, if requested.
pub long_entities: Option<LongEntities>,
}

#[derive(TS, Debug, Clone, Serialize)]
Expand Down
Loading
Loading