diff options
author | Leon Yang (Containers) <lnyng@meta.com> | 2024-04-11 13:18:46 -0700 |
---|---|---|
committer | Facebook GitHub Bot <facebook-github-bot@users.noreply.github.com> | 2024-04-11 13:18:46 -0700 |
commit | b4bb5b576633a0d24151f5ae2494ce52ef45c9a1 (patch) | |
tree | 34b6fae58c325744dead4745da5dcf20e5c6acaf | |
parent | f3d3d29d863a2a6e1478dfb6405df0e4eb7feab7 (diff) |
QueriableContainer
Summary:
Some rust type gymnastics to unify the FieldId type for Vec, BTreeMap, and CgroupModel. Reduce some code.
We can then add feature to this trait to get X field of an item from a container whose Y field equals Z, is min/max among other items, etc.
Reviewed By: brianc118
Differential Revision: D55968833
fbshipit-source-id: bab97d65bb72234fd67b723f23043327bac87234
-rw-r--r-- | below/dump/src/cgroup.rs | 8 | ||||
-rw-r--r-- | below/dump/src/system.rs | 5 | ||||
-rw-r--r-- | below/dump/src/test.rs | 12 | ||||
-rw-r--r-- | below/model/src/cgroup.rs | 76 | ||||
-rw-r--r-- | below/model/src/lib.rs | 236 | ||||
-rw-r--r-- | below/render/src/default_configs.rs | 27 | ||||
-rw-r--r-- | below/view/src/cgroup_tabs.rs | 8 |
7 files changed, 139 insertions, 233 deletions
diff --git a/below/dump/src/cgroup.rs b/below/dump/src/cgroup.rs index dd8604f5..04dafaeb 100644 --- a/below/dump/src/cgroup.rs +++ b/below/dump/src/cgroup.rs @@ -127,10 +127,10 @@ impl Dumper for Cgroup { //sort if let Some(field_id) = &handle.select { // field_id that queries its own data - let field_id = CgroupModelFieldId { - path: Some(vec![]), - subquery_id: field_id.to_owned(), - }; + let field_id = CgroupModelFieldId::new( + Some(model::CgroupPath { path: vec![] }), + field_id.to_owned(), + ); if handle.opts.sort { model::sort_queriables(&mut children, &field_id, false); } diff --git a/below/dump/src/system.rs b/below/dump/src/system.rs index 2c0b26d9..6141599d 100644 --- a/below/dump/src/system.rs +++ b/below/dump/src/system.rs @@ -48,10 +48,7 @@ impl Dumper for System { { let value = subquery_id.clone(); fields.push(DumpField::FieldId(model::SystemModelFieldId::Cpus( - model::BTreeMapFieldId { - key: Some(*key), - subquery_id: value, - }, + model::BTreeMapFieldId::new(Some(*key), value), ))); } } diff --git a/below/dump/src/test.rs b/below/dump/src/test.rs index 21126d82..6fb44574 100644 --- a/below/dump/src/test.rs +++ b/below/dump/src/test.rs @@ -41,10 +41,7 @@ fn test_dump_sys_content() { let mut fields = command::expand_fields(command::DEFAULT_SYSTEM_FIELDS, true); for subquery_id in enum_iterator::all::<model::SingleCpuModelFieldId>() { fields.push(DumpField::FieldId(model::SystemModelFieldId::Cpus( - model::BTreeMapFieldId { - key: Some(31), - subquery_id, - }, + model::BTreeMapFieldId::new(Some(31), subquery_id), ))); } opts.output_format = Some(OutputFormat::Json); @@ -97,10 +94,9 @@ fn test_dump_sys_titles() { .into_iter() .chain( enum_iterator::all::<model::SingleCpuModelFieldId>().map(|subquery_id| { - DumpField::FieldId(model::SystemModelFieldId::Cpus(model::BTreeMapFieldId { - key: Some(31), - subquery_id, - })) + DumpField::FieldId(model::SystemModelFieldId::Cpus( + model::BTreeMapFieldId::new(Some(31), subquery_id), + )) }), ) .filter_map(|dump_field| match dump_field { diff --git a/below/model/src/cgroup.rs b/below/model/src/cgroup.rs index 1c42e537..b94f3626 100644 --- a/below/model/src/cgroup.rs +++ b/below/model/src/cgroup.rs @@ -70,75 +70,49 @@ pub struct CgroupModel { /// The path is used to drill into the Cgroup Model tree. If Vec empty, the /// current CgroupModel is selected and queried with the subquery_id. /// The path is optional in parsing and converting to String. -#[derive(Clone, Debug, PartialEq)] -pub struct CgroupModelFieldId { - /// To drill into children recursively. If Vec empty, queries self. - /// None is only for listing variants and otherwise invalid. - pub path: Option<Vec<String>>, - pub subquery_id: SingleCgroupModelFieldId, -} +pub type CgroupModelFieldId = QueriableContainerFieldId<CgroupModel>; -impl FieldId for CgroupModelFieldId { - type Queriable = CgroupModel; -} - -impl DelegatedSequence for CgroupModelFieldId { - type Delegate = SingleCgroupModelFieldId; - fn get_delegate(&self) -> &Self::Delegate { - &self.subquery_id - } - fn from_delegate(delegate: Self::Delegate) -> Self { - Self { - path: None, - subquery_id: delegate, - } - } -} - -impl Sequence for CgroupModelFieldId { - impl_sequence_for_delegated_sequence!(); +#[derive(Clone, Debug, PartialEq)] +pub struct CgroupPath { + pub path: Vec<String>, } -impl std::string::ToString for CgroupModelFieldId { +impl ToString for CgroupPath { fn to_string(&self) -> String { - match &self.path { - Some(path) if path.is_empty() => self.subquery_id.to_string(), - Some(path) => format!("path:/{}/.{}", path.join("/"), self.subquery_id.to_string()), - None => format!("[path:/<cgroup_path>/.]{}", self.subquery_id.to_string()), - } + format!("path:/{}/", self.path.join("/")) } } -impl std::str::FromStr for CgroupModelFieldId { +impl FromStr for CgroupPath { type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { - let (path_str, subquery_id_str) = if s.starts_with("path:") { - s["path:".len()..] - .rsplit_once("/.") - .ok_or_else(|| anyhow!("Path is not terminated by `/.`: {}", s))? - } else { - ("", s) - }; - let path = Some( - path_str + if !s.starts_with("path:/") { + return Err(anyhow!("Path is not prefixed with `path:/`: {}", s)); + } + Ok(Self { + path: s["path:/".len()..] .split('/') .filter(|part| !part.is_empty()) .map(|part| part.to_owned()) .collect(), - ); - let subquery_id = SingleCgroupModelFieldId::from_str(subquery_id_str)?; - Ok(Self { path, subquery_id }) + }) } } -impl Queriable for CgroupModel { - type FieldId = CgroupModelFieldId; - fn query(&self, field_id: &Self::FieldId) -> Option<Field> { +impl QueriableContainer for CgroupModel { + type Idx = CgroupPath; + type SubqueryId = SingleCgroupModelFieldId; + const IDX_PLACEHOLDER: &'static str = "[path:/<cgroup_path>/.]"; + fn split(s: &str) -> Option<(&str, &str)> { + let idx_end = s.rfind("/.")?; + Some((&s[..idx_end + 1], &s[idx_end + 2..])) + } + fn get_item(&self, idx: &Self::Idx) -> Option<&SingleCgroupModel> { let mut model = self; - for part in field_id.path.as_ref()?.iter() { + for part in idx.path.iter() { model = model.children.get(part.as_str())?; } - model.data.query(&field_id.subquery_id) + Some(&model.data) } } @@ -926,8 +900,6 @@ mod tests { let model: CgroupModel = serde_json::from_str(model_json).expect("Failed to deserialize cgroup model JSON"); for (field_id, expected) in &[ - // "path:" omitted falls back to querying self (root) - ("full_path", Some("")), // Ignore consecutive slashes ("path:///////.name", Some("<root>")), ("path:/system.slice/.full_path", Some("/system.slice")), diff --git a/below/model/src/lib.rs b/below/model/src/lib.rs index 36174868..c69380f5 100644 --- a/below/model/src/lib.rs +++ b/below/model/src/lib.rs @@ -15,6 +15,7 @@ use std::collections::BTreeMap; use std::collections::BTreeSet; use std::fmt; +use std::marker::PhantomData; use std::str::FromStr; use std::time::Duration; use std::time::Instant; @@ -341,160 +342,96 @@ pub trait Nameable { fn name() -> &'static str; } -/// Sequence that wraps a delegate sequence and owns no variants. -trait DelegatedSequence { - type Delegate: Sequence; - fn get_delegate(&self) -> &Self::Delegate; - // Not using From trait as conversion should only be used for Sequence - fn from_delegate(delegate: Self::Delegate) -> Self; +/// Type that contains sub-queriables of the same type, individually retrieveable +/// by some index. It is itself a Queriable. +pub trait QueriableContainer { + type Idx; + type SubqueryId: FieldId; + const IDX_PLACEHOLDER: &'static str = "<idx>."; + fn split(s: &str) -> Option<(&str, &str)> { + s.split_once('.') + } + fn get_item(&self, idx: &Self::Idx) -> Option<&<Self::SubqueryId as FieldId>::Queriable>; } -/// Implements Sequence for DelegatedSequence. Must be a macro due to orphan -/// rule. See https://github.com/rust-lang/rfcs/issues/1124 -macro_rules! impl_sequence_for_delegated_sequence { - () => { - const CARDINALITY: usize = <Self as DelegatedSequence>::Delegate::CARDINALITY; - fn next(&self) -> Option<Self> { - self.get_delegate().next().map(Self::from_delegate) - } - fn previous(&self) -> Option<Self> { - self.get_delegate().previous().map(Self::from_delegate) - } - fn first() -> Option<Self> { - <Self as DelegatedSequence>::Delegate::first().map(Self::from_delegate) - } - fn last() -> Option<Self> { - <Self as DelegatedSequence>::Delegate::last().map(Self::from_delegate) - } - }; +impl<C: QueriableContainer> Queriable for C { + type FieldId = QueriableContainerFieldId<C>; + fn query(&self, field_id: &<C as Queriable>::FieldId) -> Option<Field> { + self.get_item(field_id.idx.as_ref()?) + .and_then(|sub| sub.query(&field_id.subquery_id.0)) + } } -pub(crate) use impl_sequence_for_delegated_sequence; -/// Type that makes Vec Queriable if Vec's inner type is Queriable. Uses `idx` -/// to query into a Vec. Uses `subquery_id` to query into the selected item. #[derive(Clone, Debug, PartialEq)] -pub struct VecFieldId<F: FieldId> { +pub struct QueriableContainerFieldId<C: QueriableContainer> { /// None is only for listing variants and otherwise invalid. - pub idx: Option<usize>, - pub subquery_id: F, + /// If None, shows up as C::IDX_PLACEHOLDER + pub idx: Option<C::Idx>, + // Wraps inside a tuple so we can #[derive] traits without adding type constraints + pub subquery_id: (C::SubqueryId,), + phantom: PhantomData<C>, } -impl<F: FieldId> FieldId for VecFieldId<F> -where - <F as FieldId>::Queriable: Sized, -{ - type Queriable = Vec<F::Queriable>; +impl<C: QueriableContainer> FieldId for QueriableContainerFieldId<C> { + type Queriable = C; } -impl<F: FieldId + Sequence> DelegatedSequence for VecFieldId<F> { - type Delegate = F; - fn get_delegate(&self) -> &Self::Delegate { - &self.subquery_id - } - fn from_delegate(delegate: Self::Delegate) -> Self { +impl<C: QueriableContainer> QueriableContainerFieldId<C> { + pub fn new(idx: Option<C::Idx>, subquery_id: C::SubqueryId) -> Self { Self { - idx: None, - subquery_id: delegate, + idx, + subquery_id: (subquery_id,), + phantom: PhantomData, } } } -impl<F: FieldId + Sequence> Sequence for VecFieldId<F> { - impl_sequence_for_delegated_sequence!(); -} - -impl<F: FieldId + ToString> ToString for VecFieldId<F> { - fn to_string(&self) -> String { - match self.idx { - Some(idx) => format!("{}.{}", idx, self.subquery_id.to_string()), - None => format!("<idx>.{}", self.subquery_id.to_string()), - } - } -} - -impl<F: FieldId + FromStr> FromStr for VecFieldId<F> +impl<C: QueriableContainer> Sequence for QueriableContainerFieldId<C> where - <F as FromStr>::Err: Into<anyhow::Error>, + C::SubqueryId: Sequence, { - type Err = anyhow::Error; - fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { - if let Some(dot_idx) = s.find('.') { - Ok(Self { - idx: Some(s[..dot_idx].parse()?), - subquery_id: F::from_str(&s[dot_idx + 1..]).map_err(Into::into)?, - }) - } else { - Err(anyhow!( - "Unable to find a variant of the given enum matching string `{}`.", - s, - )) - } + const CARDINALITY: usize = C::SubqueryId::CARDINALITY; + fn next(&self) -> Option<Self> { + self.subquery_id.0.next().map(|s| Self::new(None, s)) } -} - -impl<Q: Queriable> Queriable for Vec<Q> { - type FieldId = VecFieldId<Q::FieldId>; - fn query(&self, field_id: &Self::FieldId) -> Option<Field> { - self.get(field_id.idx?) - .and_then(|f| f.query(&field_id.subquery_id)) + fn previous(&self) -> Option<Self> { + self.subquery_id.0.previous().map(|s| Self::new(None, s)) } -} - -/// Type that makes BTreeMap Queriable if its value is Queriable. Uses `key` -/// to query into a map. Uses `subquery_id` to query into the selected value. -#[derive(Clone, Debug, PartialEq)] -pub struct BTreeMapFieldId<K, F: FieldId> { - /// None is only for listing variants and otherwise invalid. - pub key: Option<K>, - pub subquery_id: F, -} - -impl<K: Ord, F: FieldId> FieldId for BTreeMapFieldId<K, F> -where - <F as FieldId>::Queriable: Sized, -{ - type Queriable = BTreeMap<K, F::Queriable>; -} - -impl<K, F: FieldId + Sequence> DelegatedSequence for BTreeMapFieldId<K, F> { - type Delegate = F; - fn get_delegate(&self) -> &Self::Delegate { - &self.subquery_id + fn first() -> Option<Self> { + C::SubqueryId::first().map(|s| Self::new(None, s)) } - fn from_delegate(delegate: Self::Delegate) -> Self { - Self { - key: None, - subquery_id: delegate, - } + fn last() -> Option<Self> { + C::SubqueryId::last().map(|s| Self::new(None, s)) } } -impl<K, F: FieldId + Sequence> Sequence for BTreeMapFieldId<K, F> { - impl_sequence_for_delegated_sequence!(); -} - -impl<K: ToString, F: FieldId + ToString> ToString for BTreeMapFieldId<K, F> { +impl<C: QueriableContainer> ToString for QueriableContainerFieldId<C> +where + C::Idx: ToString, + C::SubqueryId: ToString, +{ fn to_string(&self) -> String { - match &self.key { - Some(key) => format!("{}.{}", key.to_string(), self.subquery_id.to_string()), - None => format!("<key>.{}", self.subquery_id.to_string()), + match self.idx.as_ref() { + Some(idx) => format!("{}.{}", idx.to_string(), self.subquery_id.0.to_string()), + None => format!("{}{}", C::IDX_PLACEHOLDER, self.subquery_id.0.to_string()), } } } -impl<K: FromStr, F: FieldId + FromStr> FromStr for BTreeMapFieldId<K, F> +impl<C: QueriableContainer> FromStr for QueriableContainerFieldId<C> where - <K as FromStr>::Err: Into<anyhow::Error>, - <F as FromStr>::Err: Into<anyhow::Error>, + C::Idx: FromStr, + C::SubqueryId: FromStr, + <C::Idx as FromStr>::Err: Into<anyhow::Error>, + <C::SubqueryId as FromStr>::Err: Into<anyhow::Error>, { type Err = anyhow::Error; fn from_str(s: &str) -> std::result::Result<Self, Self::Err> { - // Only works with keys that don't contain dot - if let Some(dot_idx) = s.find('.') { - Ok(Self { - key: Some(K::from_str(&s[..dot_idx]).map_err(Into::into)?), - subquery_id: F::from_str(&s[dot_idx + 1..]).map_err(Into::into)?, - }) + if let Some((idx_str, subquery_id_str)) = C::split(s) { + Ok(Self::new( + Some(C::Idx::from_str(idx_str).map_err(Into::into)?), + C::SubqueryId::from_str(subquery_id_str).map_err(Into::into)?, + )) } else { Err(anyhow!( "Unable to find a variant of the given enum matching string `{}`.", @@ -504,14 +441,27 @@ where } } -impl<K: Ord, Q: Queriable> Queriable for BTreeMap<K, Q> { - type FieldId = BTreeMapFieldId<K, Q::FieldId>; - fn query(&self, field_id: &Self::FieldId) -> Option<Field> { - self.get(field_id.key.as_ref()?) - .and_then(|f| f.query(&field_id.subquery_id)) +impl<Q: Queriable> QueriableContainer for Vec<Q> { + type Idx = usize; + type SubqueryId = Q::FieldId; + fn get_item(&self, idx: &usize) -> Option<&Q> { + self.get(*idx) } } +pub type VecFieldId<Q> = QueriableContainerFieldId<Vec<Q>>; + +impl<K: Ord, Q: Queriable> QueriableContainer for BTreeMap<K, Q> { + type Idx = K; + type SubqueryId = Q::FieldId; + const IDX_PLACEHOLDER: &'static str = "<key>."; + fn get_item(&self, idx: &K) -> Option<&Q> { + self.get(idx) + } +} + +pub type BTreeMapFieldId<K, Q> = QueriableContainerFieldId<BTreeMap<K, Q>>; + pub struct NetworkStats<'a> { net: &'a procfs::NetStat, ethtool: &'a Option<ethtool::EthtoolStats>, @@ -645,14 +595,8 @@ mod tests { #[test] fn test_vec_field_id() { let query_str = "1.msg"; - let query = <VecFieldId<TestModelFieldId>>::from_str(query_str).expect("bad query str"); - assert_eq!( - query, - VecFieldId { - idx: Some(1), - subquery_id: TestModelFieldId::Msg, - } - ); + let query = <VecFieldId<TestModel>>::from_str(query_str).expect("bad query str"); + assert_eq!(query, VecFieldId::new(Some(1), TestModelFieldId::Msg),); assert_eq!(query.to_string(), query_str); } @@ -667,10 +611,7 @@ mod tests { }, ]; assert_eq!( - data.query(&VecFieldId { - idx: Some(1), - subquery_id: TestModelFieldId::Msg, - }), + data.query(&VecFieldId::new(Some(1), TestModelFieldId::Msg,)), Some(Field::Str("world".to_owned())) ); } @@ -678,14 +619,11 @@ mod tests { #[test] fn test_btreemap_field_id() { let query_str = "hello.msg"; - let query = <BTreeMapFieldId<String, TestModelFieldId>>::from_str(query_str) - .expect("bad query str"); + let query = + <BTreeMapFieldId<String, TestModel>>::from_str(query_str).expect("bad query str"); assert_eq!( query, - BTreeMapFieldId { - key: Some("hello".to_owned()), - subquery_id: TestModelFieldId::Msg, - } + BTreeMapFieldId::new(Some("hello".to_owned()), TestModelFieldId::Msg) ); assert_eq!(query.to_string(), query_str); } @@ -706,10 +644,10 @@ mod tests { }, ); assert_eq!( - data.query(&BTreeMapFieldId { - key: Some("hello".to_owned()), - subquery_id: TestModelFieldId::Msg, - }), + data.query(&BTreeMapFieldId::new( + Some("hello".to_owned()), + TestModelFieldId::Msg, + )), Some(Field::Str("world".to_owned())) ); } diff --git a/below/render/src/default_configs.rs b/below/render/src/default_configs.rs index 1942e0d1..2f955863 100644 --- a/below/render/src/default_configs.rs +++ b/below/render/src/default_configs.rs @@ -35,13 +35,13 @@ impl HasRenderConfig for model::SingleCgroupModel { Cpu(field_id) => model::CgroupCpuModel::get_render_config_builder(field_id), Io(field_id) => model::CgroupIoModel::get_render_config_builder(field_id), IoDetails(field_id) => { - model::CgroupIoModel::get_render_config_builder(&field_id.subquery_id) + model::CgroupIoModel::get_render_config_builder(&field_id.subquery_id.0) } Mem(field_id) => model::CgroupMemoryModel::get_render_config_builder(field_id), Pressure(field_id) => model::CgroupPressureModel::get_render_config_builder(field_id), CgroupStat(field_id) => model::CgroupStatModel::get_render_config_builder(field_id), MemNuma(field_id) => { - model::CgroupMemoryNumaModel::get_render_config_builder(&field_id.subquery_id) + model::CgroupMemoryNumaModel::get_render_config_builder(&field_id.subquery_id.0) } Props(field_id) => model::CgroupProperties::get_render_config_builder(field_id), Pids(field_id) => model::CgroupPidsModel::get_render_config_builder(field_id), @@ -394,7 +394,7 @@ impl HasRenderConfig for model::NetworkModel { use model::NetworkModelFieldId::*; match field_id { Interfaces(field_id) => { - model::SingleNetModel::get_render_config_builder(&field_id.subquery_id) + model::SingleNetModel::get_render_config_builder(&field_id.subquery_id.0) } Tcp(field_id) => model::TcpModel::get_render_config_builder(field_id), Ip(field_id) => model::IpModel::get_render_config_builder(field_id), @@ -812,7 +812,7 @@ impl HasRenderConfigForDump for model::SingleNetModel { impl HasRenderConfig for Vec<model::SingleQueueModel> { fn get_render_config_builder(field_id: &Self::FieldId) -> RenderConfigBuilder { let mut rc = - model::SingleQueueModel::get_render_config_builder(&field_id.subquery_id).get(); + model::SingleQueueModel::get_render_config_builder(&field_id.subquery_id.0).get(); rc.title = rc.title.map(|title| title.to_string()); rc.into() } @@ -827,7 +827,7 @@ impl HasRenderConfigForDump for Vec<model::SingleQueueModel> { .idx .expect("VecFieldId without index should not have render config"); self.get(idx) - .map(|queue| queue.get_openmetrics_config_for_dump(&field_id.subquery_id))? + .map(|queue| queue.get_openmetrics_config_for_dump(&field_id.subquery_id.0))? } } @@ -1042,12 +1042,14 @@ impl HasRenderConfig for model::SystemModel { Mem(field_id) => model::MemoryModel::get_render_config_builder(field_id), Vm(field_id) => model::VmModel::get_render_config_builder(field_id), Slab(field_id) => { - model::SingleSlabModel::get_render_config_builder(&field_id.subquery_id) + model::SingleSlabModel::get_render_config_builder(&field_id.subquery_id.0) } Disks(field_id) => { - model::SingleDiskModel::get_render_config_builder(&field_id.subquery_id) + model::SingleDiskModel::get_render_config_builder(&field_id.subquery_id.0) + } + Btrfs(field_id) => { + model::BtrfsModel::get_render_config_builder(&field_id.subquery_id.0) } - Btrfs(field_id) => model::BtrfsModel::get_render_config_builder(&field_id.subquery_id), } } } @@ -1159,12 +1161,13 @@ impl HasRenderConfigForDump for model::SingleCpuModel { impl HasRenderConfig for BTreeMap<u32, model::SingleCpuModel> { fn get_render_config_builder(field_id: &Self::FieldId) -> RenderConfigBuilder { - let mut rc = model::SingleCpuModel::get_render_config_builder(&field_id.subquery_id).get(); + let mut rc = + model::SingleCpuModel::get_render_config_builder(&field_id.subquery_id.0).get(); rc.title = rc.title.map(|title| { format!( "CPU {} {}", field_id - .key + .idx .expect("BTreeMapFieldId without key should not have render config"), title ) @@ -1179,10 +1182,10 @@ impl HasRenderConfigForDump for BTreeMap<u32, model::SingleCpuModel> { field_id: &Self::FieldId, ) -> Option<RenderOpenMetricsConfigBuilder> { let key = field_id - .key + .idx .expect("BTreeMapFieldId without key should not have render config"); self.get(&key) - .map(|cpu| cpu.get_openmetrics_config_for_dump(&field_id.subquery_id))? + .map(|cpu| cpu.get_openmetrics_config_for_dump(&field_id.subquery_id.0))? } } diff --git a/below/view/src/cgroup_tabs.rs b/below/view/src/cgroup_tabs.rs index b771bc76..2e79f1c4 100644 --- a/below/view/src/cgroup_tabs.rs +++ b/below/view/src/cgroup_tabs.rs @@ -134,10 +134,10 @@ impl CgroupTab { let mut children = Vec::from_iter(&cgroup.children); if let Some(sort_order) = state.sort_order.as_ref() { // field_id that query its own data - let field_id = CgroupModelFieldId { - path: Some(vec![]), - subquery_id: sort_order.clone(), - }; + let field_id = CgroupModelFieldId::new( + Some(model::CgroupPath { path: vec![] }), + sort_order.clone(), + ); sort_queriables(&mut children, &field_id, state.reverse); } |