-
-
Notifications
You must be signed in to change notification settings - Fork 142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: add resource detail view #188
Conversation
Signed-off-by: Zahari Dichev <[email protected]>
This looks amazing. I'm going to try to give this branch a detailed review today, but the screenshots look awesome. Amazing work! |
Signed-off-by: Zahari Dichev <[email protected]>
@hawkw Thanks a bunch for taking the time. Let me know after a quick glance whether you prefer me to break up the PR somehow or whether I can do anything to make it easier to review. |
Signed-off-by: Zahari Dichev <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall, this seems pretty good --- and the UI screenshots you posted are awesome! i had a bunch of minor suggestions.
also, i wonder if it would be worth adding a way to hide/show all the internal resources---and maybe hide them by default? i don't know if we really want to expose tokio internals like the batch semaphore by default, unless the user asks for them, it seems like it could potentially just be a bunch of information that's confusing to a user who isn't familiar with tokio's internals. but, we could address that in a separate PR.
@@ -37,6 +37,10 @@ message AsyncOp { | |||
// The source of this async operation. Most commonly this should be the name | |||
// of the method where the instantiation of this op has happened. | |||
string source = 3; | |||
// The ID of the parent async op. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it might be nice if this comment explained a bit more what the "parent async op" of an async op is --- my guess is that this is for situations such as a Sleep
inside of an Interval
, or a Semaphore
op inside a channel? but, it would be nice if the comment explained that.
also, if a given async op doesn't have a parent, this will not be set, right? it might be worth documenting that as well.
console-api/proto/common.proto
Outdated
// State attributes of an entity. These are dependent on the type of the entity. | ||
// For example, a timer resource will have a duration while a semaphore resource may |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
// State attributes of an entity. These are dependent on the type of the entity. | |
// For example, a timer resource will have a duration while a semaphore resource may | |
// State attributes of an entity. These are dependent on the type of the entity. | |
// | |
// For example, a timer resource will have a duration, while a semaphore resource may |
console-api/proto/common.proto
Outdated
|
||
// State attributes of an entity. These are dependent on the type of the entity. | ||
// For example, a timer resource will have a duration while a semaphore resource may | ||
// have permits as an attribute. Likewise the async ops of a sempahore may have attributes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit/typo:
// have permits as an attribute. Likewise the async ops of a sempahore may have attributes | |
// have permits as an attribute. Likewise, the async ops of a semaphore may have attributes |
@@ -0,0 +1,40 @@ | |||
use std::sync::Arc; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for adding new examples, these are great!
take it or leave it, but it might also be nice to have some examples that combine multiple resource types. that way, we could simulate what a real application that uses a large number of resources might look like. we could maybe stick similar code to these examples into examples/app.rs
(maybe with a CLI option) so that the example can also spawn a number of tasks that use barriers, mutexen, etc.
it would be totally fine to make that change in a follow-up branch, though.
console-subscriber/examples/mutex.rs
Outdated
loop { | ||
if *count.lock().await >= 50 { | ||
break; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, take it or leave it: this seems like it could be a while
loop:
loop { | |
if *count.lock().await >= 50 { | |
break; | |
} | |
} | |
while *count.lock().await < 50 {} |
console/src/view/resource.rs
Outdated
let mut overview = Vec::with_capacity(6); | ||
overview.push(Spans::from(vec![ | ||
bold("ID: "), | ||
Span::raw(format!("{} ", resource.id())), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be nice not to format this on every frame...
console/src/view/resource.rs
Outdated
|
||
overview.push(Spans::from(vec![ | ||
bold("Kind: "), | ||
Span::raw(format!("{} ", resource.kind())), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we store the kind string as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In these situations do you mean storing the string as an InternetStr
on the Resource/AsyncOp
objects?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depending on the string, we may or may not want to intern them. In the case of resource.kind()
, I believe they already are InternedStr
s:
console/console/src/state/resources.rs
Lines 33 to 37 in 042c1f7
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] | |
pub(crate) enum Kind { | |
Timer, | |
Other(InternedStr), | |
} |
I think we could probably add a method to resources::Kind
that returns an &'static str
, so that we don't need to format!
it here. I can open a quick PR for that change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...actually, i just double-checked, and resource.kind()
already returns an &str
. so i think this can just be
Span::raw(format!("{} ", resource.kind())), | |
Span::raw(resource.kind()), |
Edit: oh, hmm, that doesn't actually work due to lifetime issues. i think we may need to make some additional changes to InternedStr
to make this work nicely...
console/src/view/resource.rs
Outdated
|
||
overview.push(Spans::from(vec![ | ||
bold("Type: "), | ||
Span::raw(format!("{} ", resource.concrete_type())), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...and the concrete type string?
console/src/view/resources.rs
Outdated
"Kind", | ||
"Total", | ||
"Target", | ||
"Type", | ||
"Viz", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i have a slight preference for spelling this "Vis", as it's short for "Visibility"...or maybe it should just be "pub"? but, i could be convinced otherwise...
console/src/view/resources.rs
Outdated
Some(id) => { | ||
format!("{}", id) | ||
} | ||
None => "n/a".to_string(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another string that shouldn't have to be format!
ed every time we render a frame...
Signed-off-by: Zahari Dichev <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall, this looks pretty good to me and i think we should merge this and address the bigger potential improvements in follow-up branches. i commented on a few minor suggestions we may want to address first.
console-api/proto/async_ops.proto
Outdated
// The ID of the parent async op. This field is set when the this async op | ||
// is created in the context of another one. An example of that is for example | ||
// a `Mutex::lock` which internally calls `Semaphore::acquire`. This field can | ||
// be empty. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some minor nits here:
// The ID of the parent async op. This field is set when the this async op | |
// is created in the context of another one. An example of that is for example | |
// a `Mutex::lock` which internally calls `Semaphore::acquire`. This field can | |
// be empty. | |
// The ID of the parent async op. | |
// | |
// This field is only set if this async op was created while inside of another | |
// async op. For example, `tokio::sync`'s `Mutex::lock` internally calls | |
// `Semaphore::acquire`. | |
// | |
// This field can be empty; if it is empty, this async op is not a child of another | |
// async op. |
console-api/proto/common.proto
Outdated
// State attributes of an entity. These are dependent on the type of the entity. | ||
// | ||
// For example, a timer resource will have a duration, while a semaphore resource may | ||
// have permits as an attribute. Likewise the async ops of a semaphore may have attributes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// have permits as an attribute. Likewise the async ops of a semaphore may have attributes | |
// have a permit count. Likewise, the async ops of a semaphore may have attributes |
match update_type { | ||
UpdateType::Resource => { | ||
if let Some(parent) = self | ||
.resources | ||
.get(&update_id) | ||
.and_then(|r| self.resources.get(r.parent_id.as_ref()?)) | ||
.filter(|parent| parent.inherit_child_attrs) | ||
{ | ||
to_update.push((parent.id, UpdateType::Resource)); | ||
} | ||
} | ||
UpdateType::AsyncOp => { | ||
if let Some(parent) = self | ||
.async_ops | ||
.get(&update_id) | ||
.and_then(|r| self.async_ops.get(r.parent_id.as_ref()?)) | ||
.filter(|parent| parent.inherit_child_attrs) | ||
{ | ||
to_update.push((parent.id, UpdateType::AsyncOp)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think in theory there might be a way to not repeat this code quite as much, but i don't know if it's worth the effort of figuring that out. we could maybe have a function that takes a hash map and performs the lookup part? but, not a blocker.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, you could have a method that takes an UpdateType
and gives you back the hashmap, and then leave the currently duplicated code inline as a single non-duplicated if
statement. (This works in part because you can just pass along the UpdateType
into the to_update.push
invocation, rather than writing out the specific variant the way you are doing here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, that's more or less what i was imagining. i think the filter
might have to be kept separate, as well, because the field access is being performed on a different type. we could introduce a trait for that, but, hmm...
@@ -34,7 +34,7 @@ pub(crate) enum SortBy { | |||
#[derive(Debug)] | |||
pub(crate) struct AsyncOp { | |||
id: u64, | |||
parent_id: Option<u64>, | |||
parent_id: InternedStr, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think the parent ID could probably just be a normal String
rather than an InternedStr
, unless we expect a single async op to have a lot of children. but, we'll clean up unused interned strings anyway so this is fine in either case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hawkw I actually wonder whether we can take a different approach with all the parent info. Namely, what if whenever we have a parent we simply have an Option<AsyncOpRef>
or Option<ResourceRef>
and then get all the needed strings, etc by upgrading the parent ref? That will make it much easier when we have a toggle that determines whether we should display any info about internal resources. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could definitely do something like that; it might be worth doing in a follow-up branch so that we can get this PR merged sooner?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
console/src/state/resources.rs
Outdated
let parent_id = strings.string(match parent_id { | ||
Some(id) => { | ||
format!("{}", id) | ||
} | ||
None => "n/a".to_string(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, take it or leave it: this could maybe be simplified to
let parent_id = strings.string(match parent_id { | |
Some(id) => { | |
format!("{}", id) | |
} | |
None => "n/a".to_string(), | |
let parent_id = strings.string(parent_id | |
.map(u64::to_string) | |
.unwrap_or_else(|| "n/a".to_string()); | |
); |
console/src/state/resources.rs
Outdated
let r = r.borrow(); | ||
format!("{} ({}::{})", r.id(), r.target(), r.concrete_type()) | ||
}) | ||
.unwrap_or(format!("{}", id)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this should probably be
.unwrap_or(format!("{}", id)), | |
.unwrap_or_else(|| format!("{}", id)), |
so that we don't format the string if we did find a parent (see the documentation for https://doc.rust-lang.org/stable/core/option/enum.Option.html#method.unwrap_or)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, it might be slightly more efficient not to go through the format!
macro when the format arguments are just "{}"
; we can use the fmt::Display
implementation for u64
directly:
.unwrap_or(format!("{}", id)), | |
.unwrap_or_else(|| id.to_string()), |
but, that's probably not a huge performance impact, regardless.
console/src/state/async_ops.rs
Outdated
let task_id = pb.task_id.map(|id| id.id); | ||
let task_id_str = strings.string( | ||
task_id | ||
.map(|tid| format!("{}", tid)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could be
.map(|tid| format!("{}", tid)) | |
.map(u64::to_string) |
which might be very slightly more efficient (but not a big deal)
console/src/view/async_ops.rs
Outdated
let task_str = match task { | ||
Some(task_str) => task_str, | ||
None => async_op.task_id_str().to_owned(), | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit/tioli: could just be
let task_str = match task { | |
Some(task_str) => task_str, | |
None => async_op.task_id_str().to_owned(), | |
}; | |
let task_str = task.unwrap_or_else(|| async_op.task_id_str().to_owned()); |
Signed-off-by: Zahari Dichev <[email protected]>
Signed-off-by: Zahari Dichev <[email protected]>
Signed-off-by: Zahari Dichev <[email protected]>
@zaharidichev is this ready to merge or are you planning on making an additional changes in this branch? |
I can address more of the feedback in follow-ups. Its already getting quite big. I think the corresponding tokio branch needs to be sanity checked before we merge this though: tokio-rs/tokio#4302 |
…sole into zd/resource-details
Signed-off-by: Zahari Dichev <[email protected]>
@hawkw @zaharidichev to confirm: this PR is ready to merge pending the Tokio PR? |
@zaharidichev Tokio 1.15 is out. |
Signed-off-by: Zahari Dichev <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added docs
This PR introduces the resoruce detail view. It also brings a couple of additional changes:
Some screenshots:
Signed-off-by: Zahari Dichev [email protected]