From 6085d861229fe83a55c2bced62422daa527fa4d9 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Mon, 23 May 2022 12:55:16 -0700 Subject: [PATCH 01/30] feat(console): dynamically size task details field list ## Motivation Currently, the task details view always gives the list of fields 50% of the vertical space. This isn't a particularly effective use of space when a task has only a small number of fields --- we end up wasting a lot of the vertical real estate that could be used for something nicer. ## Solution This branch changes the layout of the task details view to give the list of fields a `Min` layout constraint with the length of the list of fields plus the border width. This way, when the list of fields is short, we don't waste a bunch of vertical space on blank lines. --- tokio-console/src/view/task.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tokio-console/src/view/task.rs b/tokio-console/src/view/task.rs index 0ee97e8aa..b546e63ea 100644 --- a/tokio-console/src/view/task.rs +++ b/tokio-console/src/view/task.rs @@ -66,6 +66,7 @@ impl TaskView { }) .collect(); + let fields_len = (task.formatted_fields().len() + 2) as u16; let (controls_area, stats_area, poll_dur_area, fields_area, warnings_area) = if warnings.is_empty() { let chunks = Layout::default() @@ -77,9 +78,9 @@ impl TaskView { // task stats layout::Constraint::Length(8), // poll duration - layout::Constraint::Length(9), + layout::Constraint::Min(9), // fields - layout::Constraint::Percentage(60), + layout::Constraint::Min(fields_len), ] .as_ref(), ) @@ -97,9 +98,9 @@ impl TaskView { // task stats layout::Constraint::Length(8), // poll duration - layout::Constraint::Length(9), + layout::Constraint::Min(9), // fields - layout::Constraint::Percentage(60), + layout::Constraint::Min(fields_len), ] .as_ref(), ) From 40e2f6fdda339b2b6c66ad308a9acca740b68dad Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 5 Aug 2022 12:36:35 -0700 Subject: [PATCH 02/30] chore: update Tonic and Prost dependencies (#364) This branch updates the console crates' dependency on `tonic` to v0.8.0 and `prost` to 0.11.0. In addition, I've added a [`cargo xtask`][xtask] command for manually regenerating the generated protobuf bindings. This is necessary as the current approach, regenerating the bindings in an integration test, does not work when the protos fail to compile (which they do after the Tonic update). Since running the crate's tests requires compiling the crate, if the proto bindings don't compile, we can't re-run the test. [xtask]: https://github.com/matklad/cargo-xtask --- .cargo/config | 5 +- .github/workflows/ci.yaml | 5 ++ Cargo.lock | 47 ++++++----- Cargo.toml | 3 +- console-api/Cargo.toml | 12 +-- console-api/src/generated/google.protobuf.rs | 0 .../src/generated/rs.tokio.console.common.rs | 27 +++++++ .../generated/rs.tokio.console.instrument.rs | 40 +++++++--- .../generated/rs.tokio.console.resources.rs | 11 +++ .../src/generated/rs.tokio.console.tasks.rs | 16 +++- .../src/generated/rs.tokio.console.trace.rs | 38 ++++++--- console-api/tests/bootstrap.rs | 64 +++++++++------ console-subscriber/Cargo.toml | 6 +- console-subscriber/src/stats.rs | 8 +- tokio-console/Cargo.toml | 6 +- xtask/Cargo.toml | 14 ++++ xtask/src/main.rs | 80 +++++++++++++++++++ 17 files changed, 298 insertions(+), 84 deletions(-) create mode 100644 console-api/src/generated/google.protobuf.rs create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/main.rs diff --git a/.cargo/config b/.cargo/config index 16f1e73ac..f2ea191eb 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,2 +1,5 @@ [build] -rustflags = ["--cfg", "tokio_unstable"] \ No newline at end of file +rustflags = ["--cfg", "tokio_unstable"] + +[alias] +xtask = "run --manifest-path ./xtask/Cargo.toml --" \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bd41a4a4d..7f321f945 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -75,6 +75,11 @@ jobs: override: true - uses: Swatinem/rust-cache@v1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Run cargo test (API) uses: actions-rs/cargo@v1 with: diff --git a/Cargo.lock b/Cargo.lock index cc901b32e..b1f35352d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,15 +238,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "cmake" -version = "0.1.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" -dependencies = [ - "cc", -] - [[package]] name = "color-eyre" version = "0.5.11" @@ -277,9 +268,10 @@ dependencies = [ [[package]] name = "console-api" -version = "0.3.0" +version = "0.4.0" dependencies = [ "prost", + "prost-build", "prost-types", "tonic", "tonic-build", @@ -1069,9 +1061,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd5316aa8f5c82add416dfbc25116b84b748a21153f512917e8143640a71bbd" +checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" dependencies = [ "bytes", "prost-derive", @@ -1079,13 +1071,11 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328f9f29b82409216decb172d81e936415d21245befa79cd34c3f29d87d1c50b" +checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" dependencies = [ "bytes", - "cfg-if", - "cmake", "heck", "itertools", "lazy_static", @@ -1101,9 +1091,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df35198f0777b75e9ff669737c6da5136b59dba33cf5a010a6d1cc4d56defc6f" +checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" dependencies = [ "anyhow", "itertools", @@ -1114,9 +1104,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926681c118ae6e512a3ccefd4abbe5521a14f4cc1e207356d4d00c0b7f2006fd" +checksum = "d30bc806a29b347314be074ff0608ef8e547286e8ea68b061a2fe55689edc01f" dependencies = [ "bytes", "prost", @@ -1528,9 +1518,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1a361140b1af3f548e0a5105126b3fc737542f6cd4947b66419c80be07db22" +checksum = "498f271adc46acce75d66f639e4d35b31b2394c295c82496727dafa16d465dd2" dependencies = [ "async-stream", "async-trait", @@ -1560,9 +1550,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d17087af5c80e5d5fc8ba9878e60258065a0a757e35efe7a05b7904bece1943" +checksum = "2fbcd2800e34e743b9ae795867d5f77b535d3a3be69fd731e39145719752df8c" dependencies = [ "prettyplease", "proc-macro2", @@ -1910,3 +1900,12 @@ name = "windows_x86_64_msvc" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "clap", + "color-eyre", + "tonic-build", +] diff --git a/Cargo.toml b/Cargo.toml index 0f289d821..153d99d70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "tokio-console", "console-subscriber", - "console-api" + "console-api", + "xtask" ] resolver = "2" diff --git a/console-api/Cargo.toml b/console-api/Cargo.toml index 009ed725d..f01d08a08 100644 --- a/console-api/Cargo.toml +++ b/console-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "console-api" -version = "0.3.0" +version = "0.4.0" license = "MIT" edition = "2021" rust-version = "1.58.0" @@ -29,19 +29,21 @@ keywords = [ transport = ["tonic-build/transport", "tonic/transport"] [dependencies] -tonic = { version = "0.7", default-features = false, features = [ +tonic = { version = "0.8", default-features = false, features = [ "prost", "codegen", "transport", ] } -prost = "0.10" -prost-types = "0.10" +prost = "0.11" +prost-types = "0.11" tracing-core = "0.1.17" [dev-dependencies] -tonic-build = { version = "0.7", default-features = false, features = [ +tonic-build = { version = "0.8", default-features = false, features = [ "prost", "transport" ] } +# explicit dep so we can get the version with fixed whitespace. +prost-build = "0.11.1" [package.metadata.docs.rs] all-features = true diff --git a/console-api/src/generated/google.protobuf.rs b/console-api/src/generated/google.protobuf.rs new file mode 100644 index 000000000..e69de29bb diff --git a/console-api/src/generated/rs.tokio.console.common.rs b/console-api/src/generated/rs.tokio.console.common.rs index d9a77c8d3..d651148e3 100644 --- a/console-api/src/generated/rs.tokio.console.common.rs +++ b/console-api/src/generated/rs.tokio.console.common.rs @@ -165,6 +165,18 @@ pub mod metadata { /// Indicates metadata is associated with an event. Event = 1, } + impl Kind { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Kind::Span => "SPAN", + Kind::Event => "EVENT", + } + } + } /// Describes the level of verbosity of a span or event. /// /// Corresponds to `Level` in the `tracing` crate. @@ -191,6 +203,21 @@ pub mod metadata { /// Designates very low priority, often extremely verbose, information. Trace = 4, } + impl Level { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Level::Error => "ERROR", + Level::Warn => "WARN", + Level::Info => "INFO", + Level::Debug => "DEBUG", + Level::Trace => "TRACE", + } + } + } } /// Contains stats about objects that can be polled. Currently these can be: /// - tasks that have been spawned diff --git a/console-api/src/generated/rs.tokio.console.instrument.rs b/console-api/src/generated/rs.tokio.console.instrument.rs index bf1f570ab..e39c9331c 100644 --- a/console-api/src/generated/rs.tokio.console.instrument.rs +++ b/console-api/src/generated/rs.tokio.console.instrument.rs @@ -30,7 +30,7 @@ pub struct ResumeRequest { /// - we can use one single timestamp for all the data /// - we can have all the new_metadata in one place /// - things such as async ops and resource ops do not make sense -/// on their own as they have relations to tasks and resources +/// on their own as they have relations to tasks and resources #[derive(Clone, PartialEq, ::prost::Message)] pub struct Update { /// The system time when this update was recorded. @@ -64,6 +64,7 @@ pub struct ResumeResponse { pub mod instrument_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] use tonic::codegen::*; + use tonic::codegen::http::Uri; /// `InstrumentServer` implements `Instrument` as a service. #[derive(Debug, Clone)] pub struct InstrumentClient { @@ -84,19 +85,24 @@ pub mod instrument_client { where T: tonic::client::GrpcService, T::Error: Into, - T::ResponseBody: Default + Body + Send + 'static, + T::ResponseBody: Body + Send + 'static, ::Error: Into + Send, { pub fn new(inner: T) -> Self { let inner = tonic::client::Grpc::new(inner); Self { inner } } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } pub fn with_interceptor( inner: T, interceptor: F, ) -> InstrumentClient> where F: tonic::service::Interceptor, + T::ResponseBody: Default, T: tonic::codegen::Service< http::Request, Response = http::Response< @@ -109,19 +115,19 @@ pub mod instrument_client { { InstrumentClient::new(InterceptedService::new(inner, interceptor)) } - /// Compress requests with `gzip`. + /// Compress requests with the given encoding. /// /// This requires the server to support it otherwise it might respond with an /// error. #[must_use] - pub fn send_gzip(mut self) -> Self { - self.inner = self.inner.send_gzip(); + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); self } - /// Enable decompressing responses with `gzip`. + /// Enable decompressing responses. #[must_use] - pub fn accept_gzip(mut self) -> Self { - self.inner = self.inner.accept_gzip(); + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); self } /// Produces a stream of updates representing the behavior of the instrumented async runtime. @@ -258,8 +264,8 @@ pub mod instrument_server { #[derive(Debug)] pub struct InstrumentServer { inner: _Inner, - accept_compression_encodings: (), - send_compression_encodings: (), + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, } struct _Inner(Arc); impl InstrumentServer { @@ -283,6 +289,18 @@ pub mod instrument_server { { InterceptedService::new(Self::new(inner), interceptor) } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } } impl tonic::codegen::Service> for InstrumentServer where @@ -491,7 +509,7 @@ pub mod instrument_server { write!(f, "{:?}", self.0) } } - impl tonic::transport::NamedService for InstrumentServer { + impl tonic::server::NamedService for InstrumentServer { const NAME: &'static str = "rs.tokio.console.instrument.Instrument"; } } diff --git a/console-api/src/generated/rs.tokio.console.resources.rs b/console-api/src/generated/rs.tokio.console.resources.rs index 10c014758..714391461 100644 --- a/console-api/src/generated/rs.tokio.console.resources.rs +++ b/console-api/src/generated/rs.tokio.console.resources.rs @@ -80,6 +80,17 @@ pub mod resource { /// `TIMER` signals that this is a timer resource, e.g. waiting for a sleep to finish. Timer = 0, } + impl Known { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Known::Timer => "TIMER", + } + } + } /// Every resource is either a known kind or an other (unknown) kind. #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Kind { diff --git a/console-api/src/generated/rs.tokio.console.tasks.rs b/console-api/src/generated/rs.tokio.console.tasks.rs index 10915a6c5..6ff543bc9 100644 --- a/console-api/src/generated/rs.tokio.console.tasks.rs +++ b/console-api/src/generated/rs.tokio.console.tasks.rs @@ -47,7 +47,7 @@ pub struct TaskDetails { /// /// This is either: /// - the raw binary representation of a HdrHistogram.rs `Histogram` - /// serialized to binary in the V2 format (legacy) + /// serialized to binary in the V2 format (legacy) /// - a binary histogram plus details on outliers (current) #[prost(oneof="task_details::PollTimesHistogram", tags="3, 4")] pub poll_times_histogram: ::core::option::Option, @@ -58,7 +58,7 @@ pub mod task_details { /// /// This is either: /// - the raw binary representation of a HdrHistogram.rs `Histogram` - /// serialized to binary in the V2 format (legacy) + /// serialized to binary in the V2 format (legacy) /// - a binary histogram plus details on outliers (current) #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum PollTimesHistogram { @@ -125,6 +125,18 @@ pub mod task { /// (such as `tokio::task::spawn_blocking`). Blocking = 1, } + impl Kind { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Kind::Spawn => "SPAWN", + Kind::Blocking => "BLOCKING", + } + } + } } /// Task performance statistics. #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/console-api/src/generated/rs.tokio.console.trace.rs b/console-api/src/generated/rs.tokio.console.trace.rs index ed3e98991..13c2843a5 100644 --- a/console-api/src/generated/rs.tokio.console.trace.rs +++ b/console-api/src/generated/rs.tokio.console.trace.rs @@ -84,6 +84,7 @@ pub mod trace_event { pub mod trace_client { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] use tonic::codegen::*; + use tonic::codegen::http::Uri; /// Allows observers to stream trace events for a given `WatchRequest` filter. #[derive(Debug, Clone)] pub struct TraceClient { @@ -104,19 +105,24 @@ pub mod trace_client { where T: tonic::client::GrpcService, T::Error: Into, - T::ResponseBody: Default + Body + Send + 'static, + T::ResponseBody: Body + Send + 'static, ::Error: Into + Send, { pub fn new(inner: T) -> Self { let inner = tonic::client::Grpc::new(inner); Self { inner } } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } pub fn with_interceptor( inner: T, interceptor: F, ) -> TraceClient> where F: tonic::service::Interceptor, + T::ResponseBody: Default, T: tonic::codegen::Service< http::Request, Response = http::Response< @@ -129,19 +135,19 @@ pub mod trace_client { { TraceClient::new(InterceptedService::new(inner, interceptor)) } - /// Compress requests with `gzip`. + /// Compress requests with the given encoding. /// /// This requires the server to support it otherwise it might respond with an /// error. #[must_use] - pub fn send_gzip(mut self) -> Self { - self.inner = self.inner.send_gzip(); + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); self } - /// Enable decompressing responses with `gzip`. + /// Enable decompressing responses. #[must_use] - pub fn accept_gzip(mut self) -> Self { - self.inner = self.inner.accept_gzip(); + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); self } /// Produces a stream of trace events for the given filter. @@ -192,8 +198,8 @@ pub mod trace_server { #[derive(Debug)] pub struct TraceServer { inner: _Inner, - accept_compression_encodings: (), - send_compression_encodings: (), + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, } struct _Inner(Arc); impl TraceServer { @@ -217,6 +223,18 @@ pub mod trace_server { { InterceptedService::new(Self::new(inner), interceptor) } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } } impl tonic::codegen::Service> for TraceServer where @@ -310,7 +328,7 @@ pub mod trace_server { write!(f, "{:?}", self.0) } } - impl tonic::transport::NamedService for TraceServer { + impl tonic::server::NamedService for TraceServer { const NAME: &'static str = "rs.tokio.console.trace.Trace"; } } diff --git a/console-api/tests/bootstrap.rs b/console-api/tests/bootstrap.rs index ed48c6a13..27c629cf4 100644 --- a/console-api/tests/bootstrap.rs +++ b/console-api/tests/bootstrap.rs @@ -1,38 +1,56 @@ -use std::{path::PathBuf, process::Command}; +use std::{fs, path::PathBuf, process::Command}; #[test] fn bootstrap() { - let iface_files = &[ - "proto/trace.proto", - "proto/common.proto", - "proto/tasks.proto", - "proto/instrument.proto", - "proto/resources.proto", - "proto/async_ops.proto", - ]; - let dirs = &["proto"]; + let root_dir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR")); + let proto_dir = root_dir.join("proto"); + let proto_ext = std::ffi::OsStr::new("proto"); + let proto_files = fs::read_dir(&proto_dir).and_then(|dir| { + dir.filter_map(|entry| { + (|| { + let entry = entry?; + if entry.file_type()?.is_dir() { + return Ok(None); + } - let out_dir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR")) - .join("src") - .join("generated"); + let path = entry.path(); + if path.extension() != Some(proto_ext) { + return Ok(None); + } - tonic_build::configure() + Ok(Some(path)) + })() + .transpose() + }) + .collect::, _>>() + }); + let proto_files = match proto_files { + Ok(files) => files, + Err(error) => panic!("failed to list proto files: {}", error), + }; + + let out_dir = root_dir.join("src").join("generated"); + + if let Err(error) = tonic_build::configure() .build_client(true) .build_server(true) + .emit_rerun_if_changed(false) .protoc_arg("--experimental_allow_proto3_optional") - .out_dir(format!("{}", out_dir.display())) - .compile(iface_files, dirs) - .unwrap(); + .out_dir(&out_dir) + .compile(&proto_files[..], &[proto_dir]) + { + panic!("failed to compile `console-api` protobuf: {}", error); + } let status = Command::new("git") .arg("diff") .arg("--exit-code") .arg("--") - .arg(format!("{}", out_dir.display())) - .status() - .unwrap(); - - if !status.success() { - panic!("You should commit the protobuf files"); + .arg(out_dir) + .status(); + match status { + Ok(status) if !status.success() => panic!("You should commit the protobuf files"), + Err(error) => panic!("failed to run `git diff`: {}", error), + Ok(_) => {} } } diff --git a/console-subscriber/Cargo.toml b/console-subscriber/Cargo.toml index aec867f43..82a68a5a6 100644 --- a/console-subscriber/Cargo.toml +++ b/console-subscriber/Cargo.toml @@ -35,8 +35,8 @@ crossbeam-utils = "0.8.7" tokio = { version = "^1.15", features = ["sync", "time", "macros", "tracing"] } tokio-stream = "0.1" thread_local = "1.1.3" -console-api = { version = "0.3.0", path = "../console-api", features = ["transport"] } -tonic = { version = "0.7", features = ["transport"] } +console-api = { version = "0.4.0", path = "../console-api", features = ["transport"] } +tonic = { version = "0.8", features = ["transport"] } tracing-core = "0.1.24" tracing = "0.1.26" tracing-subscriber = { version = "0.3.11", default-features = false, features = ["fmt", "registry"] } @@ -46,7 +46,7 @@ hdrhistogram = { version = "7.3.0", default-features = false, features = ["seria # feature to also enable `tracing-subscriber`'s parking_lot feature flag. parking_lot_crate = { package = "parking_lot", version = "0.11", optional = true } humantime = "2.1.0" -prost-types = "0.10.0" +prost-types = "0.11.0" # Required for recording: serde = { version = "1", features = ["derive"] } diff --git a/console-subscriber/src/stats.rs b/console-subscriber/src/stats.rs index 32fb364f1..24848307d 100644 --- a/console-subscriber/src/stats.rs +++ b/console-subscriber/src/stats.rs @@ -532,7 +532,13 @@ impl ToProto for PollStats { last_poll_ended: timestamps .last_poll_ended .map(|at| base_time.to_timestamp(at)), - busy_time: Some(timestamps.busy_time.into()), + busy_time: Some(timestamps.busy_time.try_into().unwrap_or_else(|error| { + eprintln!( + "failed to convert busy time to protobuf duration: {}", + error + ); + Default::default() + })), } } } diff --git a/tokio-console/Cargo.toml b/tokio-console/Cargo.toml index b5be2873e..ed332a7ea 100644 --- a/tokio-console/Cargo.toml +++ b/tokio-console/Cargo.toml @@ -27,16 +27,16 @@ keywords = [ [dependencies] atty = "0.2" -console-api = { version = "0.3.0", path = "../console-api", features = ["transport"] } +console-api = { version = "0.4.0", path = "../console-api", features = ["transport"] } clap = { version = "3", features = ["cargo", "derive", "env"] } tokio = { version = "1", features = ["full", "rt-multi-thread"] } -tonic = { version = "0.7", features = ["transport"] } +tonic = { version = "0.8", features = ["transport"] } futures = "0.3" tui = { version = "0.16.0", default-features = false, features = ["crossterm"] } tracing = "0.1" tracing-subscriber = { version = "0.3.0", features = ["env-filter"] } tracing-journald = { version = "0.2", optional = true } -prost-types = "0.10" +prost-types = "0.11" crossterm = { version = "0.20", features = ["event-stream"] } color-eyre = { version = "0.5", features = ["issue-url"] } hdrhistogram = { version = "7.3.0", default-features = false, features = ["serialization"] } diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 000000000..09f60941a --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "xtask" +version = "0.1.0" +license = "MIT" +edition = "2021" +rust-version = "1.56.0" +publish = false + +[dependencies] +tonic-build = { version = "0.8", default-features = false, features = [ + "prost", "transport" +] } +clap = { version = "3", features = ["derive"] } +color-eyre = "0.5" \ No newline at end of file diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 000000000..807dd2f1f --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,80 @@ +use clap::Parser; +use color_eyre::{ + eyre::{ensure, WrapErr}, + Result, +}; +use std::{fs, path::PathBuf}; + +/// tokio-console dev tasks +#[derive(Debug, clap::Parser)] +struct Args { + #[clap(subcommand)] + cmd: Command, +} + +#[derive(Debug, clap::Subcommand)] +enum Command { + /// Generate `console-api` protobuf bindings. + GenProto, +} + +fn main() -> Result<()> { + color_eyre::install()?; + Args::parse().cmd.run() +} + +impl Command { + fn run(&self) -> Result<()> { + match self { + Self::GenProto => gen_proto(), + } + } +} + +fn gen_proto() -> Result<()> { + eprintln!("generating `console-api` protos..."); + + let api_dir = { + let mut mydir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR")); + ensure!(mydir.pop(), "manifest path should not be relative!"); + mydir.join("console-api") + }; + + let proto_dir = api_dir.join("proto"); + let proto_ext = std::ffi::OsStr::new("proto"); + let proto_files = fs::read_dir(&proto_dir) + .with_context(|| { + format!( + "failed to read protobuf directory `{}`", + proto_dir.display() + ) + })? + .filter_map(|entry| { + (|| { + let entry = entry?; + if entry.file_type()?.is_dir() { + return Ok(None); + } + + let path = entry.path(); + if path.extension() != Some(proto_ext) { + return Ok(None); + } + + Ok(Some(path)) + })() + .transpose() + }) + .collect::>>()?; + + let out_dir = api_dir.join("src").join("generated"); + + tonic_build::configure() + .build_client(true) + .build_server(true) + .emit_rerun_if_changed(false) + .protoc_arg("--experimental_allow_proto3_optional") + .out_dir(&out_dir) + .compile(&proto_files[..], &[proto_dir]) + .context("failed to compile protobuf files") +} From 23cb6bf7cdaafd3fe691e4a6f7f91cc17e169795 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 5 Aug 2022 12:45:51 -0700 Subject: [PATCH 03/30] feat(console): only suggest opening issues for panics (#365) This changes the console CLI's error handling so that GitHub issues are only suggested for panics, not for recoverable errors (such as "no config file found", "couldn't connect to remote host", etc). --- tokio-console/src/view/styles.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tokio-console/src/view/styles.rs b/tokio-console/src/view/styles.rs index 515308441..dbcd6f06a 100644 --- a/tokio-console/src/view/styles.rs +++ b/tokio-console/src/view/styles.rs @@ -48,11 +48,23 @@ impl Styles { } pub fn error_init(&self) -> color_eyre::Result<()> { - use color_eyre::config::{HookBuilder, Theme}; + use color_eyre::{ + config::{HookBuilder, Theme}, + ErrorKind, + }; let mut builder = HookBuilder::new() .issue_url(concat!(env!("CARGO_PKG_REPOSITORY"), "/issues/new")) - .add_issue_metadata("version", env!("CARGO_PKG_VERSION")); + .add_issue_metadata("version", env!("CARGO_PKG_VERSION")) + .issue_filter(|kind| match kind { + // Only suggest reporting GitHub issues for panics, not for + // errors, so people don't open GitHub issues for stuff like not + // being able to find a config file or connections being + // terminated by remote hosts. + ErrorKind::NonRecoverable(_) => true, + ErrorKind::Recoverable(_) => false, + }); + if self.palette == Palette::NoColors { // disable colors in error reports builder = builder.theme(Theme::new()); From 66465689dceec509d9e1e37a55646a89285005e3 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 5 Aug 2022 12:51:52 -0700 Subject: [PATCH 04/30] feat(console): init error handling before subcmds (#365) This way, if running a subcommand panics/errors, we still get nice `color-eyre` reports. --- tokio-console/src/main.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tokio-console/src/main.rs b/tokio-console/src/main.rs index 68bb5f402..50f6eb720 100644 --- a/tokio-console/src/main.rs +++ b/tokio-console/src/main.rs @@ -26,6 +26,13 @@ mod warnings; #[tokio::main] async fn main() -> color_eyre::Result<()> { let mut args = config::Config::parse()?; + // initialize error handling first, in case panics occur while setting up + // other stuff. + let styles = view::Styles::from_config(args.view_options.clone()); + styles.error_init()?; + + args.trace_init()?; + tracing::debug!(?args.target_addr, ?args.view_options); match args.subcmd { Some(config::OptionalCmd::GenConfig) => { @@ -40,16 +47,10 @@ async fn main() -> color_eyre::Result<()> { None => {} } - let retain_for = args.retain_for(); - args.trace_init()?; - tracing::debug!(?args.target_addr, ?args.view_options); - let target = args.target_addr(); tracing::info!(?target, "using target addr"); - let styles = view::Styles::from_config(args.view_options); - styles.error_init()?; - + let retain_for = args.retain_for(); let (mut terminal, _cleanup) = term::init_crossterm()?; terminal.clear()?; let mut conn = conn::Connection::new(target); From 523a44a30cf047fe0a56f746624df0cc3239a160 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 5 Aug 2022 12:56:25 -0700 Subject: [PATCH 05/30] feat(console): filter out boring frames in backtraces (#365) This commit adds `color_eyre`'s default backtrace frame filters, so we skip stuff that's not relevant. --- tokio-console/src/view/styles.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tokio-console/src/view/styles.rs b/tokio-console/src/view/styles.rs index dbcd6f06a..c95174e67 100644 --- a/tokio-console/src/view/styles.rs +++ b/tokio-console/src/view/styles.rs @@ -63,7 +63,13 @@ impl Styles { // terminated by remote hosts. ErrorKind::NonRecoverable(_) => true, ErrorKind::Recoverable(_) => false, - }); + }) + // filter out `color-eyre`'s default set of frames to skip from + // backtraces. + // + // this includes `std::rt`, `color_eyre`'s own frames, and + // `tokio::runtime` & friends. + .add_default_filters(); if self.palette == Palette::NoColors { // disable colors in error reports From fcb54dffda2a9f4c85cc82a24bff26e0777ceacc Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 5 Aug 2022 13:11:15 -0700 Subject: [PATCH 06/30] feat(console): include config options in autogenerated issues (#365) This adds a dump of all the console's config options to the issue metadata for GitHub issues generated using `color_eyre`'s GitHub issue generation on panics.
Example issue Markdown: ## Error ``` lol ``` ## Metadata |key|value| |--|--| |**version**|0.1.6| |**config.subcmd**|`None`| |**config.target_addr**|`Some(http://127.0.0.1:6669/)`| |**config.env_filter**|`None`| |**config.log_directory**|`Some("/tmp/tokio-console/logs")`| |**config.retain_for**|`None`| |**config.view_options.no_colors**|`false`| |**config.view_options.lang**|`Some("en_US.UTF-8")`| |**config.view_options.ascii_only**|`Some(false)`| |**config.view_options.truecolor**|`Some(true)`| |**config.view_options.palette**|`Some(All)`| |**config.view_options.toggles.color_durations**|`Some(false)`| |**config.view_options.toggles.color_terminated**|`Some(false)`| |**location**|tokio-console/src/main.rs:36:5| --- tokio-console/src/config.rs | 36 ++++++++++++++++++++++++++++++++ tokio-console/src/main.rs | 2 +- tokio-console/src/view/styles.rs | 8 ++++--- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/tokio-console/src/config.rs b/tokio-console/src/config.rs index 9e0153e5d..04f14b917 100644 --- a/tokio-console/src/config.rs +++ b/tokio-console/src/config.rs @@ -330,6 +330,42 @@ impl Config { .clone() } + pub(crate) fn add_issue_metadata( + &self, + mut builder: color_eyre::config::HookBuilder, + ) -> color_eyre::config::HookBuilder { + macro_rules! add_issue_metadata { + ($self:ident, $builder:ident => + $( + $($name:ident).+ + ),+ + $(,)? + ) => { + $( + $builder = $builder.add_issue_metadata(concat!("config", $(".", stringify!($name)),+), format!("`{:?}`", $self$(.$name)+)); + )* + } + } + + add_issue_metadata! { + self, builder => + subcmd, + target_addr, + env_filter, + log_directory, + retain_for, + view_options.no_colors, + view_options.lang, + view_options.ascii_only, + view_options.truecolor, + view_options.palette, + view_options.toggles.color_durations, + view_options.toggles.color_terminated, + } + + builder + } + fn from_path(config_path: ConfigPath) -> color_eyre::Result> { ConfigFile::from_path(config_path)? .map(|config| config.try_into()) diff --git a/tokio-console/src/main.rs b/tokio-console/src/main.rs index 50f6eb720..c10b062a6 100644 --- a/tokio-console/src/main.rs +++ b/tokio-console/src/main.rs @@ -29,7 +29,7 @@ async fn main() -> color_eyre::Result<()> { // initialize error handling first, in case panics occur while setting up // other stuff. let styles = view::Styles::from_config(args.view_options.clone()); - styles.error_init()?; + styles.error_init(&args)?; args.trace_init()?; tracing::debug!(?args.target_addr, ?args.view_options); diff --git a/tokio-console/src/view/styles.rs b/tokio-console/src/view/styles.rs index c95174e67..b71587c1d 100644 --- a/tokio-console/src/view/styles.rs +++ b/tokio-console/src/view/styles.rs @@ -47,7 +47,7 @@ impl Styles { } } - pub fn error_init(&self) -> color_eyre::Result<()> { + pub fn error_init(&self, cfg: &crate::config::Config) -> color_eyre::Result<()> { use color_eyre::{ config::{HookBuilder, Theme}, ErrorKind, @@ -55,7 +55,6 @@ impl Styles { let mut builder = HookBuilder::new() .issue_url(concat!(env!("CARGO_PKG_REPOSITORY"), "/issues/new")) - .add_issue_metadata("version", env!("CARGO_PKG_VERSION")) .issue_filter(|kind| match kind { // Only suggest reporting GitHub issues for panics, not for // errors, so people don't open GitHub issues for stuff like not @@ -69,7 +68,10 @@ impl Styles { // // this includes `std::rt`, `color_eyre`'s own frames, and // `tokio::runtime` & friends. - .add_default_filters(); + .add_default_filters() + .add_issue_metadata("version", env!("CARGO_PKG_VERSION")); + // Add all the config values to the GitHub issue metadata + builder = cfg.add_issue_metadata(builder); if self.palette == Palette::NoColors { // disable colors in error reports From 2cb6ee5b813837324f5f9934a929ac928cfbb03f Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 10 Aug 2022 13:37:11 -0700 Subject: [PATCH 07/30] chore(api): prepare to release v0.4.0 (#366) ## 0.4.0 (2022-08-10) #### Breaking Changes * Update `tonic` to `0.8` (#364) ([40e2f6fd](40e2f6fd)) #### Features * Update `tonic` to `0.8` (#364) ([40e2f6fd](40e2f6fd)) --- console-api/CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/console-api/CHANGELOG.md b/console-api/CHANGELOG.md index aa964ba43..0793b625a 100644 --- a/console-api/CHANGELOG.md +++ b/console-api/CHANGELOG.md @@ -1,3 +1,16 @@ + +## 0.4.0 (2022-08-10) + + +#### Breaking Changes + +* Update `tonic` to `0.8` (#364) ([40e2f6fd](40e2f6fd)) + +#### Features + +* Update `tonic` to `0.8` (#364) ([40e2f6fd](40e2f6fd)) + + ## 0.3.0 (2022-05-23) From 337a972ac35a01df185d00565f69cb03d377bd95 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 10 Aug 2022 15:50:30 -0700 Subject: [PATCH 08/30] chore: prepare to release console-subscriber v0.1.7 (#367) ## 0.1.7 (2022-08-10) #### Features * Update `tonic` to `0.8` (#364) ([40e2f6fd](40e2f6fd)) * Update `console-api` to `0.4` (#364) ([40e2f6fd](40e2f6fd)) --- Cargo.lock | 2 +- console-subscriber/CHANGELOG.md | 10 ++++++++++ console-subscriber/Cargo.toml | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1f35352d..ddbf7b86e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ dependencies = [ [[package]] name = "console-subscriber" -version = "0.1.6" +version = "0.1.7" dependencies = [ "console-api", "crossbeam-channel", diff --git a/console-subscriber/CHANGELOG.md b/console-subscriber/CHANGELOG.md index 977b9fafd..bfea92bbb 100644 --- a/console-subscriber/CHANGELOG.md +++ b/console-subscriber/CHANGELOG.md @@ -1,3 +1,13 @@ + +## 0.1.7 (2022-08-10) + + +#### Features + +* Update `tonic` to `0.8` (#364) ([40e2f6fd](40e2f6fd)) +* Update `console-api` to `0.4` (#364) ([40e2f6fd](40e2f6fd)) + + ## 0.1.6 (2022-05-23) diff --git a/console-subscriber/Cargo.toml b/console-subscriber/Cargo.toml index 82a68a5a6..b0da7f467 100644 --- a/console-subscriber/Cargo.toml +++ b/console-subscriber/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "console-subscriber" -version = "0.1.6" +version = "0.1.7" license = "MIT" edition = "2021" rust-version = "1.58.0" From 3bf60bce7b478c189a3145311e06f14cb2fc1e11 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 10 Aug 2022 16:44:00 -0700 Subject: [PATCH 09/30] chore: prepare to release tokio-console v0.1.7 (#368) ## 0.1.7 (2022-08-10) #### Features * include config options in autogenerated issues (#365) ([fcb54dff](fcb54dff)) * filter out boring frames in backtraces (#365) ([523a44a3](523a44a3)) * init error handling before subcmds (#365) ([66465689](66465689)) * only suggest opening issues for panics (#365) ([23cb6bf7](23cb6bf7)) * update `tonic` to `0.8` (#364) ([40e2f6fd](40e2f6fd)) * update `console-api` to `0.4` (#364) ([40e2f6fd](40e2f6fd)) --- Cargo.lock | 2 +- tokio-console/CHANGELOG.md | 13 +++++++++++++ tokio-console/Cargo.toml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ddbf7b86e..4b8588d88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1435,7 +1435,7 @@ dependencies = [ [[package]] name = "tokio-console" -version = "0.1.6" +version = "0.1.7" dependencies = [ "atty", "clap", diff --git a/tokio-console/CHANGELOG.md b/tokio-console/CHANGELOG.md index f48e07a97..d28c0d5c5 100644 --- a/tokio-console/CHANGELOG.md +++ b/tokio-console/CHANGELOG.md @@ -1,3 +1,16 @@ + +## 0.1.7 (2022-08-10) + + +#### Features + +* include config options in autogenerated issues (#365) ([fcb54dff](fcb54dff)) +* filter out boring frames in backtraces (#365) ([523a44a3](523a44a3)) +* init error handling before subcmds (#365) ([66465689](66465689)) +* only suggest opening issues for panics (#365) ([23cb6bf7](23cb6bf7)) +* update `tonic` to `0.8` (#364) ([40e2f6fd](40e2f6fd)) +* update `console-api` to `0.4` (#364) ([40e2f6fd](40e2f6fd)) + ## 0.1.6 (2022-05-24) diff --git a/tokio-console/Cargo.toml b/tokio-console/Cargo.toml index ed332a7ea..2d8d5158d 100644 --- a/tokio-console/Cargo.toml +++ b/tokio-console/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tokio-console" -version = "0.1.6" +version = "0.1.7" license = "MIT" repository = "https://github.com/tokio-rs/console" edition = "2021" From faaf8084bc1c4437bbc27975dc3b8f65b7c90f18 Mon Sep 17 00:00:00 2001 From: Noah Kennedy Date: Sun, 4 Sep 2022 15:56:46 -0500 Subject: [PATCH 10/30] chore(api): fix clippy errors (#375) Needed for #374. This configures clippy to ignore most of the generated code in `console-api`. --- console-api/src/async_ops.rs | 2 ++ console-api/src/common.rs | 7 ++++++- console-api/src/instrument.rs | 2 ++ console-api/src/resources.rs | 2 ++ console-api/src/tasks.rs | 2 ++ console-api/src/trace.rs | 2 ++ console-subscriber/examples/barrier.rs | 3 +-- 7 files changed, 17 insertions(+), 3 deletions(-) diff --git a/console-api/src/async_ops.rs b/console-api/src/async_ops.rs index a91cf8d0c..289801e08 100644 --- a/console-api/src/async_ops.rs +++ b/console-api/src/async_ops.rs @@ -1 +1,3 @@ +#![allow(warnings)] + include!("generated/rs.tokio.console.async_ops.rs"); diff --git a/console-api/src/common.rs b/console-api/src/common.rs index 211d70ba4..cb6edf73c 100644 --- a/console-api/src/common.rs +++ b/console-api/src/common.rs @@ -1,7 +1,12 @@ use std::fmt; use std::hash::{Hash, Hasher}; -include!("generated/rs.tokio.console.common.rs"); +pub use generated::*; + +mod generated { + #![allow(warnings)] + include!("generated/rs.tokio.console.common.rs"); +} impl From for metadata::Level { fn from(level: tracing_core::Level) -> Self { diff --git a/console-api/src/instrument.rs b/console-api/src/instrument.rs index 3a9c6b5d6..b7189bab4 100644 --- a/console-api/src/instrument.rs +++ b/console-api/src/instrument.rs @@ -1 +1,3 @@ +#![allow(warnings)] + include!("generated/rs.tokio.console.instrument.rs"); diff --git a/console-api/src/resources.rs b/console-api/src/resources.rs index 82956509a..78c22b3bf 100644 --- a/console-api/src/resources.rs +++ b/console-api/src/resources.rs @@ -1 +1,3 @@ +#![allow(warnings)] + include!("generated/rs.tokio.console.resources.rs"); diff --git a/console-api/src/tasks.rs b/console-api/src/tasks.rs index 2d4eea2bb..9576d9571 100644 --- a/console-api/src/tasks.rs +++ b/console-api/src/tasks.rs @@ -1 +1,3 @@ +#![allow(warnings)] + include!("generated/rs.tokio.console.tasks.rs"); diff --git a/console-api/src/trace.rs b/console-api/src/trace.rs index 0cdaa8452..4bc972e9c 100644 --- a/console-api/src/trace.rs +++ b/console-api/src/trace.rs @@ -1 +1,3 @@ +#![allow(warnings)] + include!("generated/rs.tokio.console.trace.rs"); diff --git a/console-subscriber/examples/barrier.rs b/console-subscriber/examples/barrier.rs index c9f54c273..c794acb8e 100644 --- a/console-subscriber/examples/barrier.rs +++ b/console-subscriber/examples/barrier.rs @@ -16,8 +16,7 @@ async fn main() -> Result<(), Box> { let task_name = format!("task-{}", i); handles.push(task::Builder::default().name(&task_name).spawn(async move { tokio::time::sleep(Duration::from_secs(i)).await; - let wait_result = c.wait().await; - wait_result + c.wait().await })); } From 0106407cc712b65793801d70324896138d4a4d59 Mon Sep 17 00:00:00 2001 From: Noah Kennedy Date: Sun, 4 Sep 2022 16:02:37 -0500 Subject: [PATCH 11/30] fix(subscriber): fix build on tokio 1.21.0 (#374) Due to a change in the unstable task builder APIs, this no longer compiles with the latest version of Tokio. Fortunately, it's a simple fix. --- Cargo.lock | 70 +++++++++++++++++++----- console-subscriber/Cargo.toml | 2 +- console-subscriber/examples/app.rs | 22 ++++++-- console-subscriber/examples/barrier.rs | 14 +++-- console-subscriber/examples/mutex.rs | 4 +- console-subscriber/examples/rwlock.rs | 4 +- console-subscriber/examples/semaphore.rs | 19 ++++--- console-subscriber/src/lib.rs | 2 +- 8 files changed, 102 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b8588d88..31f14e8be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -822,16 +822,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "miow", - "ntapi", "wasi 0.11.0+wasi-snapshot-preview1", - "winapi", + "windows-sys 0.36.1", ] [[package]] @@ -959,7 +957,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.34.0", ] [[package]] @@ -1414,14 +1412,15 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42" dependencies = [ + "autocfg", "bytes", "libc", "memchr", - "mio 0.8.2", + "mio 0.8.4", "num_cpus", "once_cell", "parking_lot 0.12.0", @@ -1864,11 +1863,24 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] [[package]] @@ -1877,30 +1889,60 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_i686_gnu" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_msvc" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_x86_64_gnu" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_msvc" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "xtask" version = "0.1.0" diff --git a/console-subscriber/Cargo.toml b/console-subscriber/Cargo.toml index b0da7f467..aa6f611d0 100644 --- a/console-subscriber/Cargo.toml +++ b/console-subscriber/Cargo.toml @@ -54,7 +54,7 @@ serde_json = "1" crossbeam-channel = "0.5" [dev-dependencies] -tokio = { version = "^1.7", features = ["full", "rt-multi-thread"] } +tokio = { version = "^1.21", features = ["full", "rt-multi-thread"] } futures = "0.3" [package.metadata.docs.rs] diff --git a/console-subscriber/examples/app.rs b/console-subscriber/examples/app.rs index 065778f62..f1247ed62 100644 --- a/console-subscriber/examples/app.rs +++ b/console-subscriber/examples/app.rs @@ -23,15 +23,20 @@ async fn main() -> Result<(), Box> { "blocks" => { tokio::task::Builder::new() .name("blocks") - .spawn(double_sleepy(1, 10)); + .spawn(double_sleepy(1, 10)) + .unwrap(); } "coma" => { tokio::task::Builder::new() .name("coma") - .spawn(std::future::pending::<()>()); + .spawn(std::future::pending::<()>()) + .unwrap(); } "burn" => { - tokio::task::Builder::new().name("burn").spawn(burn(1, 10)); + tokio::task::Builder::new() + .name("burn") + .spawn(burn(1, 10)) + .unwrap(); } "help" | "-h" => { eprintln!("{}", HELP); @@ -47,10 +52,12 @@ async fn main() -> Result<(), Box> { let task1 = tokio::task::Builder::new() .name("task1") - .spawn(spawn_tasks(1, 10)); + .spawn(spawn_tasks(1, 10)) + .unwrap(); let task2 = tokio::task::Builder::new() .name("task2") - .spawn(spawn_tasks(10, 30)); + .spawn(spawn_tasks(10, 30)) + .unwrap(); let result = tokio::try_join! { task1, @@ -66,7 +73,10 @@ async fn spawn_tasks(min: u64, max: u64) { loop { for i in min..max { tracing::trace!(i, "spawning wait task"); - tokio::task::Builder::new().name("wait").spawn(wait(i)); + tokio::task::Builder::new() + .name("wait") + .spawn(wait(i)) + .unwrap(); let sleep = Duration::from_secs(max) - Duration::from_secs(i); tracing::trace!(?sleep, "sleeping..."); diff --git a/console-subscriber/examples/barrier.rs b/console-subscriber/examples/barrier.rs index c794acb8e..f2c62a81f 100644 --- a/console-subscriber/examples/barrier.rs +++ b/console-subscriber/examples/barrier.rs @@ -14,10 +14,15 @@ async fn main() -> Result<(), Box> { for i in 0..30 { let c = barrier.clone(); let task_name = format!("task-{}", i); - handles.push(task::Builder::default().name(&task_name).spawn(async move { - tokio::time::sleep(Duration::from_secs(i)).await; - c.wait().await - })); + handles.push( + task::Builder::default() + .name(&task_name) + .spawn(async move { + tokio::time::sleep(Duration::from_secs(i)).await; + c.wait().await + }) + .unwrap(), + ); } // Will not resolve until all "after wait" messages have been printed @@ -33,6 +38,7 @@ async fn main() -> Result<(), Box> { // Exactly one barrier will resolve as the "leader" assert_eq!(num_leaders, 1); }) + .unwrap() .await?; Ok(()) diff --git a/console-subscriber/examples/mutex.rs b/console-subscriber/examples/mutex.rs index 483ff8ebc..f50c952cc 100644 --- a/console-subscriber/examples/mutex.rs +++ b/console-subscriber/examples/mutex.rs @@ -20,11 +20,13 @@ async fn main() -> Result<(), Box> { *lock += 1; tokio::time::sleep(Duration::from_secs(1)).await; } - }); + }) + .unwrap(); } while *count.lock().await < 50 {} }) + .unwrap() .await?; Ok(()) diff --git a/console-subscriber/examples/rwlock.rs b/console-subscriber/examples/rwlock.rs index 34e06dcc7..437b411b4 100644 --- a/console-subscriber/examples/rwlock.rs +++ b/console-subscriber/examples/rwlock.rs @@ -20,7 +20,8 @@ async fn main() -> Result<(), Box> { *lock += 1; tokio::time::sleep(Duration::from_secs(1)).await; } - }); + }) + .unwrap(); } loop { @@ -31,6 +32,7 @@ async fn main() -> Result<(), Box> { } } }) + .unwrap() .await?; Ok(()) diff --git a/console-subscriber/examples/semaphore.rs b/console-subscriber/examples/semaphore.rs index 223d73400..b9779eb1e 100644 --- a/console-subscriber/examples/semaphore.rs +++ b/console-subscriber/examples/semaphore.rs @@ -21,19 +21,24 @@ async fn main() -> Result<(), Box> { .spawn(async move { let _permit = acquire_sem.acquire_many(i).await.unwrap(); tokio::time::sleep(Duration::from_secs(i as u64 * 2)).await; - }), + }) + .unwrap(), + ); + tasks.push( + tokio::task::Builder::default() + .name(&add_task_name) + .spawn(async move { + tokio::time::sleep(Duration::from_secs(i as u64 * 5)).await; + add_sem.add_permits(i as usize); + }) + .unwrap(), ); - tasks.push(tokio::task::Builder::default().name(&add_task_name).spawn( - async move { - tokio::time::sleep(Duration::from_secs(i as u64 * 5)).await; - add_sem.add_permits(i as usize); - }, - )); } let all_tasks = futures::future::try_join_all(tasks); all_tasks.await.unwrap(); }) + .unwrap() .await?; Ok(()) diff --git a/console-subscriber/src/lib.rs b/console-subscriber/src/lib.rs index 748b045d3..bde80347c 100644 --- a/console-subscriber/src/lib.rs +++ b/console-subscriber/src/lib.rs @@ -1063,7 +1063,7 @@ where T: Send + 'static, { #[cfg(tokio_unstable)] - return tokio::task::Builder::new().name(_name).spawn(task); + return tokio::task::Builder::new().name(_name).spawn(task).unwrap(); #[cfg(not(tokio_unstable))] tokio::spawn(task) From 8fb1732dfd78ec3a8e4945c453d1c127f63ecdc4 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Sun, 4 Sep 2022 14:57:21 -0700 Subject: [PATCH 12/30] chore(subscriber): prepare to release v0.1.8 (#376) ## 0.1.8 (2022-09-04) #### Bug Fixes * fix build on tokio 1.21.0 (#374) ([0106407c](0106407c)) --- console-subscriber/CHANGELOG.md | 9 +++++++++ console-subscriber/Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/console-subscriber/CHANGELOG.md b/console-subscriber/CHANGELOG.md index bfea92bbb..4c5864cb6 100644 --- a/console-subscriber/CHANGELOG.md +++ b/console-subscriber/CHANGELOG.md @@ -1,3 +1,12 @@ + +## 0.1.8 (2022-09-04) + + +#### Bug Fixes + +* fix build on tokio 1.21.0 (#374) ([0106407c](0106407c)) + + ## 0.1.7 (2022-08-10) diff --git a/console-subscriber/Cargo.toml b/console-subscriber/Cargo.toml index aa6f611d0..f541a5aeb 100644 --- a/console-subscriber/Cargo.toml +++ b/console-subscriber/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "console-subscriber" -version = "0.1.7" +version = "0.1.8" license = "MIT" edition = "2021" rust-version = "1.58.0" From da0e9724fa132595e2085cfb08ac7bfbf10542ba Mon Sep 17 00:00:00 2001 From: Micheal <38526063+michealkeines@users.noreply.github.com> Date: Thu, 8 Sep 2022 03:47:25 +0530 Subject: [PATCH 13/30] fix(console): fix ascii-only flipped input (#377) Fixes: #372 `--ascii-only true` shows ascii `--ascii-only false` shows emojis if `--ascii-only` is not passed, default value will be true, shows emojis --- tokio-console/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio-console/src/config.rs b/tokio-console/src/config.rs index 04f14b917..52f0b4448 100644 --- a/tokio-console/src/config.rs +++ b/tokio-console/src/config.rs @@ -411,7 +411,7 @@ fn default_log_directory() -> PathBuf { impl ViewOptions { pub fn is_utf8(&self) -> bool { - if !self.ascii_only.unwrap_or(true) { + if self.ascii_only.unwrap_or(false) { return false; } self.lang.as_deref().unwrap_or_default().ends_with("UTF-8") From 40f7971d30451f7321b73a03222b71731dabc52a Mon Sep 17 00:00:00 2001 From: Daniel Caballero Date: Wed, 21 Sep 2022 20:07:52 +0200 Subject: [PATCH 14/30] fix(console): declare `tokio-console` bin as `default-run` (#379) Currently, the documented `cargo-run` in README no longer works, as now there are two binaries: ```console $ cargo run error: `cargo run` could not determine which binary to run. Use the `--bin` option t o specify a binary, or the `default-run` manifest key. available binaries: tokio-console, xtask ``` This branch declares the `tokio-console` binary as the [`default-run`] value in the `Cargo.toml`, so it is now run by default by `cargo run`. Alternatively, we can just update the README line with `cargo run --bin tokio-console` [`default-run`]: (https://doc.rust-lang.org/cargo/reference/manifest.html#the-default-run-field) --- tokio-console/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/tokio-console/Cargo.toml b/tokio-console/Cargo.toml index 2d8d5158d..30496f672 100644 --- a/tokio-console/Cargo.toml +++ b/tokio-console/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" rust-version = "1.58.0" authors = ["Eliza Weisman ", "Tokio Contributors ",] readme = "README.md" +default-run = "tokio-console" homepage = "https://github.com/tokio-rs/console/tree/main/tokio-console" description = """ The Tokio console: a debugger for async Rust. From 3248caa8f8551e22c9d361e23cabd3c98aa143b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20B=C3=BCsch?= Date: Sat, 8 Oct 2022 07:46:08 +1100 Subject: [PATCH 15/30] fix(console): make `retain_for` default to 6s if not specfied (#383) Fixes #382 --- tokio-console/src/config.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tokio-console/src/config.rs b/tokio-console/src/config.rs index 52f0b4448..675953b9d 100644 --- a/tokio-console/src/config.rs +++ b/tokio-console/src/config.rs @@ -121,6 +121,12 @@ pub enum OptionalCmd { #[derive(Debug, Clone, Copy, Deserialize)] struct RetainFor(Option); +impl Default for RetainFor { + fn default() -> Self { + Self(Some(Duration::from_secs(6))) + } +} + impl fmt::Display for RetainFor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { @@ -320,7 +326,7 @@ impl Config { } pub(crate) fn retain_for(&self) -> Option { - self.retain_for.as_ref().and_then(|value| value.0) + self.retain_for.unwrap_or_default().0 } pub(crate) fn target_addr(&self) -> Uri { @@ -390,7 +396,7 @@ impl Default for Config { target_addr: Some(default_target_addr()), env_filter: Some(tracing_subscriber::EnvFilter::new("off")), log_directory: Some(default_log_directory()), - retain_for: Some(RetainFor(Some(Duration::from_secs(6)))), + retain_for: Some(RetainFor::default()), view_options: ViewOptions::default(), subcmd: None, } From 06531a91af5b3922e615a0ce3578ebf4ae5c9059 Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Sat, 8 Oct 2022 04:54:59 +0800 Subject: [PATCH 16/30] docs: fix typos and markdown lints (#369) Found via these commands: codespell . markdownlint *.md --disable MD013 --- CODE_OF_CONDUCT.md | 7 ++++-- CONTRIBUTING.md | 63 +++++++++++++++++++++++----------------------- README.md | 32 +++++++++++++---------- SECURITY.md | 2 ++ 4 files changed, 57 insertions(+), 47 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 3a11057ae..6c5770ef4 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,7 +1,10 @@ # Code of Conduct -The Tokio project adheres to the [Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). This describes the minimum behavior expected from all contributors. +The Tokio project adheres to the +[Rust Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct). +This describes the minimum behavior expected from all contributors. ## Enforcement -Instances of violations of the Code of Conduct can be reported by contacting the project team at [moderation@tokio.rs](mailto:moderation@tokio.rs). +Instances of violations of the Code of Conduct can be reported by contacting +the project team at [moderation@tokio.rs](mailto:moderation@tokio.rs). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 674fe4191..8e36c61fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,9 +3,9 @@ :balloon: Thanks for your help improving the project! We are so happy to have you! -There are opportunities to contribute to Tokio at any level. It doesn't matter if -you are just getting started with Rust or are the most weathered expert, we can -use your help. +There are opportunities to contribute to Tokio at any level. It doesn't matter +if you are just getting started with Rust or are the most weathered expert, we +can use your help. **No contribution is too small and all contributions are valued.** @@ -20,8 +20,8 @@ not covered in this guide, please joinus! ## Conduct The Tokio project adheres to the [Rust Code of Conduct][coc]. This describes -the _minimum_ behavior expected from all contributors. Instances of violations of the -Code of Conduct can be reported by contacting the project team at +the _minimum_ behavior expected from all contributors. Instances of violations +of the Code of Conduct can be reported by contacting the project team at [moderation@tokio.rs](mailto:moderation@tokio.rs). [coc]: https://github.com/rust-lang/rust/blob/master/CODE_OF_CONDUCT.md @@ -156,7 +156,7 @@ The provided [example application] can be used for testing UI changes: When opening pull requests that make UI changes, please include one or more screenshots demonstrating your change! For bug fixes, it is often also useful to -include a screenshot showing the console *prior* to the change, in order to +include a screenshot showing the console _prior_ to the change, in order to demonstrate the bug that's being fixed. #### Integration tests @@ -178,7 +178,7 @@ for a reader to understand and actually testing the API. The type level example for `tokio_timer::Timeout` provides a good example of a documentation test: -``` +```rust /// // import the `timeout` function, usually this is done /// // with `use tokio::prelude::*` /// use tokio::prelude::FutureExt; @@ -204,7 +204,7 @@ documentation test: /// # } ``` -Given that this is a *type* level documentation test and the primary way users +Given that this is a _type_ level documentation test and the primary way users of `tokio` will create an instance of `Timeout` is by using `FutureExt::timeout`, this is how the documentation test is structured. @@ -215,7 +215,7 @@ easiest way to execute a future from a test. If this were a documentation test for the `Timeout::new` function, then the example would explicitly use `Timeout::new`. For example: -``` +```rust /// use tokio::timer::Timeout; /// use futures::Future; /// use futures::sync::oneshot; @@ -254,7 +254,7 @@ history**. But also, we use the git commit messages to **generate the change log**. Since commits are merged by [squashing](#commit-squashing), these rules are not -required for individual commits to a development branch. However, they *are* +required for individual commits to a development branch. However, they _are_ required for the final squash commit to the `main` branch. Generally, the PR description and title are used as the commit message for the squash commit. Therefore, please try to follow these rules when writing the description and @@ -266,7 +266,7 @@ Each commit message consists of a **header**, a **body** and a **footer**. The header has a special format that includes a **type**, an (optional) **scope** and a **subject**: -``` +```sh (): @@ -284,6 +284,7 @@ which we use to generate changelogs. [clog]: https://github.com/clog-tool/clog-cli #### Type + Must be one of the following: * **feat**: A new feature @@ -335,7 +336,6 @@ is also the place to reference GitHub issues that this commit The last line of commits introducing breaking changes should be in the form `BREAKING CHANGE: ` - ### Opening the Pull Request Open a new pull request using the GitHub web UI. Please try to follow the @@ -388,7 +388,7 @@ does not land, the submitters should come away from the experience feeling like their effort was not wasted or unappreciated**. Every Pull Request from a new contributor is an opportunity to grow the community. -### Review a bit at a time. +### Review a bit at a time Do not overwhelm new contributors. @@ -407,7 +407,7 @@ Note that only **incremental** improvement is needed to land a PR. This means that the PR does not need to be perfect, only better than the status quo. Follow up PRs may be opened to continue iterating. -When changes are necessary, *request* them, do not *demand* them, and **do not +When changes are necessary, _request_ them, do not _demand_ them, and **do not assume that the submitter already knows how to add a test or run a benchmark**. Specific performance optimization techniques, coding styles and conventions @@ -427,7 +427,7 @@ with the appropriate reason to keep the conversation flow concise and relevant. ### Be aware of the person behind the code -Be aware that *how* you communicate requests and reviews in your feedback can +Be aware that _how_ you communicate requests and reviews in your feedback can have a significant impact on the success of the Pull Request. Yes, we may land a particular change that makes Tokio better, but the individual might just not want to have anything to do with Tokio ever again. The goal is not just having @@ -440,7 +440,7 @@ check with the contributor to see if they intend to continue the work before checking if they would mind if you took it over (especially if it just has nits left). When doing so, it is courteous to give the original contributor credit for the work they started (either by preserving their name and email address in -the commit log, or by using an `Author: ` meta-data tag in the commit. +the commit log, or by using an `Author:` meta-data tag in the commit. _Adapted from the [Node.js contributing guide][node]_. @@ -458,36 +458,35 @@ targeted at maintainers. Most contributors aren't able to set these labels. The area label describes cross-cutting areas of work on the console project. -- **A-instrumentation**: Related to application instrumentation (such as adding +* **A-instrumentation**: Related to application instrumentation (such as adding new instrumentation to an async runtime or other library). -- **A-warnings**: Related to warnings displayed in the console CLI. This +* **A-warnings**: Related to warnings displayed in the console CLI. This includes changes that add new warnings, improve existing warnings, or improvements to the console's warning system as a whole. -- **A-recording**: Related to recording and playing back console data. +* **A-recording**: Related to recording and playing back console data. ### Crate The crate label describes what crates in the repository are involved in an issue or PR. -- **C-api**: Related to the `console-api` crate and/or protobuf definitions. -- **C-console**: Related to the `console` command-line application. -- **C-subscriber**: Related to the `console-subscriber` crate. +* **C-api**: Related to the `console-api` crate and/or protobuf definitions. +* **C-console**: Related to the `console` command-line application. +* **C-subscriber**: Related to the `console-subscriber` crate. ### Effort and calls for participation The effort label represents a _best guess_ for the approximate amount of effort that an issue will likely require. These are not always accurate! :) - -- **E-easy**: This is relatively easy. These issues are often good for newcomers +* **E-easy**: This is relatively easy. These issues are often good for newcomers to the project and/or Rust beginners. -- **E-medium**: Medium effort. This issue is expected to be relatively +* **E-medium**: Medium effort. This issue is expected to be relatively straightforward, but may require a larger amount of work than `E-easy` issues, or require some design work. -- **E-hard** This either involves very tricky code, is something we don't know +* **E-hard** This either involves very tricky code, is something we don't know how to solve, or is difficult for some other reason. -- **E-needs-mvce**: This bug is missing a minimal complete and verifiable +* **E-needs-mvce**: This bug is missing a minimal complete and verifiable example. The "E-" prefix is the same as used in the Rust compiler repository. Some @@ -499,14 +498,14 @@ server if you want to know how difficult an issue likely is. The severity label categorizes what type of issue is described by an issue, or what is implemented by a pull request. -- **S-bug**: This is a bug in the console. If this label is added to an issue, +* **S-bug**: This is a bug in the console. If this label is added to an issue, then that issue describes a bug. If this label is added to a pull request, - then this pull request *fixes* a bug. -- **S-feature**: This is adding a new feature. -- **S-performance**: Related to improving performance, either in the + then this pull request _fixes_ a bug. +* **S-feature**: This is adding a new feature. +* **S-performance**: Related to improving performance, either in the instrumented application or in the `console` CLI. This may be added to performance regressions that don't result in a crash or incorrect data, as well as to pull requests that implement optimizations. -- **S-refactor**: This is a refactor. This label describes proposed or +* **S-refactor**: This is a refactor. This label describes proposed or implemented changes that are related to improve code quality or set up for future changes, but shouldn't effect behavior, fix bugs, or add new APIs. diff --git a/README.md b/README.md index 2d2ab4a8d..a8b7b8d39 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,10 @@ toolkit consists of multiple components: [`tracing`]. * tools for **displaying and exploring diagnostic data**, implemented as gRPC - clients using the console wire protocol. the [`tokio-console`] crate implements an - **an interactive command-line tool** that consumes this data, but **other - implementations**, such as graphical or web-based tools, are also possible. + clients using the console wire protocol. the [`tokio-console`] crate + implements an **an interactive command-line tool** that consumes this data, + but **other implementations**, such as graphical or web-based tools, are + also possible. [gRPC]: https://grpc.io/ [protocol buffers]: https://developers.google.com/protocol-buffers @@ -58,7 +59,7 @@ viewing details for a single task: ![task details view](https://user-images.githubusercontent.com/2796466/129774524-288c967b-6066-4f98-973d-099b3e6a2c55.png) -## on the shoulders of giants... +## on the shoulders of giants the console is **part of a much larger effort** to improve debugging tooling for async Rust. **a [2019 Google Summer of Code project][gsoc] by Matthias Prechtl** @@ -86,7 +87,9 @@ others. [Instruments]: https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/MeasuringPerformance.html [**@matprec**]: https://github.com/matprec [**@pnkfelix**]: https://github.com/pnkfelix + ## using it + ### instrumenting your program to **instrument an application using Tokio**, add a dependency on the @@ -101,10 +104,13 @@ notes: * in order to collect task data from Tokio, **the `tokio_unstable` cfg must be enabled**. for example, you could build your project with + ```shell - $ RUSTFLAGS="--cfg tokio_unstable" cargo build + RUSTFLAGS="--cfg tokio_unstable" cargo build ``` + or add the following to your `.cargo/config.toml` file: + ```toml [build] rustflags = ["--cfg", "tokio_unstable"] @@ -116,15 +122,15 @@ notes: * the `tokio` and `runtime` [`tracing` targets] must be enabled at the [`TRACE` level]. - + if you're using the [`console_subscriber::init()`][init] or + * if you're using the [`console_subscriber::init()`][init] or [`console_subscriber::Builder`][builder] APIs, these targets are enabled automatically. - + if you are manually configuring the `tracing` subscriber using the + * if you are manually configuring the `tracing` subscriber using the [`EnvFilter`] or [`Targets`] filters from [`tracing-subscriber`], add `"tokio=trace,runtime=trace"` to your filter configuration. - + also, ensure you have not enabled any of the [compile time filter + * also, ensure you have not enabled any of the [compile time filter features][compile_time_filters] in your `Cargo.toml`. ### running the console @@ -132,13 +138,13 @@ notes: to **run the console command-line tool**, install `tokio-console` from [crates.io](https://crates.io/crates/tokio-console) ```shell -$ cargo install --locked tokio-console +cargo install --locked tokio-console ``` and run locally ```shell -$ tokio-console +tokio-console ``` > **alternative method:** run the tool from a local checkout of this repository @@ -154,14 +160,14 @@ as an argument to the console (either as an `:` or `:`). for example: ```shell -$ cargo run -- http://my.great.console.app.local:5555 +cargo run -- http://my.great.console.app.local:5555 ``` the console command-line tool supports a number of additional flags to configure its behavior. the `-h` or `--help` flag will print a list of supported command-line flags and arguments: -``` +```text USAGE: tokio-console [FLAGS] [OPTIONS] [TARGET_ADDR] @@ -240,7 +246,7 @@ OPTIONS: * `years`, `year`, `y` -- defined as 365.25 days [default: 6s] ``` -## for development: +## for development the `console-subscriber/examples` directory contains **some potentially useful tools**: diff --git a/SECURITY.md b/SECURITY.md index 6bb8f5314..55c3247f8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,3 +1,5 @@ +# Security + ## Report a security issue The Tokio project team welcomes security reports and is committed to providing prompt attention to security issues. Security issues should be reported privately via [security@tokio.rs](mailto:security@tokio.rs). Security issues should not be reported via the public Github Issue tracker. From d98f15956075a2d64f5cb96b1011eff7b3110e51 Mon Sep 17 00:00:00 2001 From: Arif Driessen Date: Thu, 17 Nov 2022 19:41:46 +0100 Subject: [PATCH 17/30] fix(console): enable view-switching keystrokes on details views (#387) Currently, the help text for the `r` and `t` keystrokes to switch to the Resources and Tasks views is shown on the Task Details and Resource Details views, as well as on the Task List and Resource List views. See this screenshot for an example: ![image](https://user-images.githubusercontent.com/2345750/202058962-4a8f062c-78fd-47eb-a3b1-5de470976aa8.png) However, the keystrokes are not actually handled while the console is showing a details view. This branch changes the console to handle the `r` and `t` keystrokes on all views. This also simplifies the keyboard input code somewhat. --- Cargo.lock | 2 +- tokio-console/src/view/mod.rs | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31f14e8be..615baa841 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ dependencies = [ [[package]] name = "console-subscriber" -version = "0.1.7" +version = "0.1.8" dependencies = [ "console-api", "crossbeam-channel", diff --git a/tokio-console/src/view/mod.rs b/tokio-console/src/view/mod.rs index e22d239af..8f1d5429d 100644 --- a/tokio-console/src/view/mod.rs +++ b/tokio-console/src/view/mod.rs @@ -96,6 +96,17 @@ impl View { pub(crate) fn update_input(&mut self, event: input::Event, state: &State) -> UpdateKind { use ViewState::*; let mut update_kind = UpdateKind::Other; + + if matches!(event, key!(Char('t'))) { + self.state = TasksList; + return update_kind; + } + + if matches!(event, key!(Char('r'))) { + self.state = ResourcesList; + return update_kind; + } + match self.state { TasksList => { // The enter key changes views, so handle here since we can @@ -110,9 +121,6 @@ impl View { )); } } - key!(Char('r')) => { - self.state = ResourcesList; - } _ => { // otherwise pass on to view self.tasks_list.update_input(event); @@ -127,9 +135,6 @@ impl View { self.state = ResourceInstance(self::resource::ResourceView::new(res)); } } - key!(Char('t')) => { - self.state = TasksList; - } _ => { // otherwise pass on to view self.resources_list.update_input(event); From c7ce40fcdc46c9a8f74c2442b3d87452f5515e6c Mon Sep 17 00:00:00 2001 From: Maxime BORGES Date: Tue, 6 Dec 2022 18:11:28 +0100 Subject: [PATCH 18/30] chore(ci): build for aarch64 (#389) Build and add to the released binaries for `aarch64-unknown-linux-gnu`, `aarch64-apple-darwin` and `aarch64-pc-windows-msvc` --- .github/workflows/release.yaml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0d6bbfeac..b8e00e19a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -32,12 +32,26 @@ jobs: needs: create-release strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + include: + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + - target: x86_64-apple-darwin + os: macos-latest + - target: x86_64-pc-windows-msvc + os: windows-latest + - target: aarch64-unknown-linux-gnu + os: ubuntu-latest + - target: aarch64-apple-darwin + os: macos-latest + - target: aarch64-pc-windows-msvc + os: windows-latest + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 - uses: taiki-e/upload-rust-binary-action@v1 with: bin: tokio-console + target: ${{ matrix.target }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 70fc2c5d694b76499a68eca271ce4e337a313cfd Mon Sep 17 00:00:00 2001 From: Cynthia Coan Date: Tue, 6 Dec 2022 11:12:05 -0700 Subject: [PATCH 19/30] chore(console): upgrade parking_lot to 0.12 (#390) Hey I noticed this out of date in my `Cargo.lock`, and since I didn't see any PRs/issues open doing so already, figured I'd open one! --- Cargo.lock | 2 +- console-subscriber/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 615baa841..ce3617900 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,7 +288,7 @@ dependencies = [ "futures", "hdrhistogram", "humantime", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "prost-types", "serde", "serde_json", diff --git a/console-subscriber/Cargo.toml b/console-subscriber/Cargo.toml index f541a5aeb..cbead40f7 100644 --- a/console-subscriber/Cargo.toml +++ b/console-subscriber/Cargo.toml @@ -44,7 +44,7 @@ futures = { version = "0.3", default-features = false } hdrhistogram = { version = "7.3.0", default-features = false, features = ["serialization"] } # The parking_lot dependency is renamed, because we want our `parking_lot` # feature to also enable `tracing-subscriber`'s parking_lot feature flag. -parking_lot_crate = { package = "parking_lot", version = "0.11", optional = true } +parking_lot_crate = { package = "parking_lot", version = "0.12", optional = true } humantime = "2.1.0" prost-types = "0.11.0" From 300bd9309e0b1781984ef8f26d1b6785caeb4c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E6=89=8B=E6=8E=89=E5=8C=85=E5=B7=A5=E7=A8=8B?= =?UTF-8?q?=E5=B8=88?= Date: Thu, 19 Jan 2023 01:14:20 +0800 Subject: [PATCH 20/30] style: make clippy happy (#396) Signed-off-by: hi-rustin Signed-off-by: hi-rustin --- console-subscriber/src/stats.rs | 2 +- xtask/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/console-subscriber/src/stats.rs b/console-subscriber/src/stats.rs index 24848307d..2bce3c085 100644 --- a/console-subscriber/src/stats.rs +++ b/console-subscriber/src/stats.rs @@ -312,7 +312,7 @@ impl AsyncOpStats { pub(crate) fn task_id(&self) -> Option { let id = self.task_id.load(); if id > 0 { - Some(id as u64) + Some(id) } else { None } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 807dd2f1f..4e07a0509 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -74,7 +74,7 @@ fn gen_proto() -> Result<()> { .build_server(true) .emit_rerun_if_changed(false) .protoc_arg("--experimental_allow_proto3_optional") - .out_dir(&out_dir) + .out_dir(out_dir) .compile(&proto_files[..], &[proto_dir]) .context("failed to compile protobuf files") } From a7548d089812ac61602a31a699d14777d312ac6d Mon Sep 17 00:00:00 2001 From: Yuta Yamaguchi Date: Thu, 19 Jan 2023 02:25:49 +0900 Subject: [PATCH 21/30] fix(console): fix `ViewOptions` default lang' (#394) Fixes #393 --- tokio-console/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio-console/src/config.rs b/tokio-console/src/config.rs index 675953b9d..e2fcdf122 100644 --- a/tokio-console/src/config.rs +++ b/tokio-console/src/config.rs @@ -498,7 +498,7 @@ impl Default for ViewOptions { fn default() -> Self { Self { no_colors: false, - lang: Some("en_us.UTF8".to_string()), + lang: Some("en_us.UTF-8".to_string()), ascii_only: Some(false), truecolor: Some(true), palette: Some(Palette::All), From 3c668a3679b5536f8a047db7a35d432c645aacef Mon Sep 17 00:00:00 2001 From: Daniel Henry-Mantilla Date: Wed, 18 Jan 2023 18:36:27 +0100 Subject: [PATCH 22/30] fix(subscriber): fix off-by-one indexing for `callsites` (#391) Co-authored-by: Eliza Weisman --- console-subscriber/src/callsites.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console-subscriber/src/callsites.rs b/console-subscriber/src/callsites.rs index 819f6fc09..feb635638 100644 --- a/console-subscriber/src/callsites.rs +++ b/console-subscriber/src/callsites.rs @@ -23,7 +23,7 @@ impl Callsites { } let idx = self.len.fetch_add(1, Ordering::AcqRel); - if idx <= MAX_CALLSITES { + if idx < MAX_CALLSITES { // If there's still room in the callsites array, stick the address // in there. self.ptrs[idx] From f01c4571dab803edd22378137f95ee5f7a603ad8 Mon Sep 17 00:00:00 2001 From: Yuta Yamaguchi Date: Thu, 19 Jan 2023 18:21:01 +0900 Subject: [PATCH 23/30] chore(console): fix ConfigFile comment typo (#392) --- tokio-console/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio-console/src/config.rs b/tokio-console/src/config.rs index e2fcdf122..52b980c2d 100644 --- a/tokio-console/src/config.rs +++ b/tokio-console/src/config.rs @@ -200,7 +200,7 @@ pub struct ColorToggles { color_terminated: Option, } -/// A sturct used to parse the toml config file +/// A struct used to parse the toml config file #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(deny_unknown_fields)] struct ConfigFile { From 7286d6f75022f3504a0379ff3fa15585a614753e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E6=89=8B=E6=8E=89=E5=8C=85=E5=B7=A5=E7=A8=8B?= =?UTF-8?q?=E5=B8=88?= Date: Tue, 7 Feb 2023 02:44:53 +0800 Subject: [PATCH 24/30] fix(subscriber): bump minimum Tokio version (#397) Fixes #386 Signed-off-by: hi-rustin --- console-subscriber/Cargo.toml | 2 +- console-subscriber/README.md | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/console-subscriber/Cargo.toml b/console-subscriber/Cargo.toml index cbead40f7..3ed323c32 100644 --- a/console-subscriber/Cargo.toml +++ b/console-subscriber/Cargo.toml @@ -32,7 +32,7 @@ env-filter = ["tracing-subscriber/env-filter"] [dependencies] crossbeam-utils = "0.8.7" -tokio = { version = "^1.15", features = ["sync", "time", "macros", "tracing"] } +tokio = { version = "^1.21", features = ["sync", "time", "macros", "tracing"] } tokio-stream = "0.1" thread_local = "1.1.3" console-api = { version = "0.4.0", path = "../console-api", features = ["transport"] } diff --git a/console-subscriber/README.md b/console-subscriber/README.md index 9d9e5224c..01c5c05e0 100644 --- a/console-subscriber/README.md +++ b/console-subscriber/README.md @@ -78,7 +78,7 @@ runtime][Tokio] is considered *experimental*. In order to use ``` If you're using a workspace, you should put the `.cargo/config.toml` file in the root of your workspace. Otherwise, put the `.cargo/config.toml` file in the root directory of your crate. - + Putting `.cargo/config.toml` files below the workspace or crate root directory may lead to tools like Rust-Analyzer or VSCode not using your `.cargo/config.toml` since they invoke cargo from the workspace or crate root and cargo only looks for the `.cargo` directory in the current & parent directories. @@ -101,9 +101,9 @@ runtime][Tokio] is considered *experimental*. In order to use [`EnvFilter`] or [`Targets`] filters from [`tracing-subscriber`], add `"tokio=trace,runtime=trace"` to your filter configuration. - + Also, ensure you have not enabled any of the [compile time filter + + Also, ensure you have not enabled any of the [compile time filter features][compile_time_filters] in your `Cargo.toml`. - + #### Required Tokio Versions Because instrumentation for different aspects of the runtime is being added to @@ -111,7 +111,7 @@ Tokio over time, the latest Tokio release is generally *recommended* to access a the console's functionality. However, it should generally be compatible with earlier Tokio versions, although some information may not be available. A minimum version of [Tokio v1.0.0] or later is required to use the console's -task instrumentation. +task instrumentation. Other instrumentation is added in later Tokio releases: @@ -126,8 +126,10 @@ Other instrumentation is added in later Tokio releases: * [Tokio v1.15.0] or later is required to track [`tokio::sync`] resources, such as `Mutex`es, `RwLock`s, `Semaphore`s, `oneshot` channels, `mpsc` channels, et - cetera. - + cetera. + +* [Tokio v1.21.0] or later is required to use newest `task::Builder::spawn*` APIs. + [Tokio v1.0.0]: https://github.com/tokio-rs/tokio/releases/tag/tokio-1.0.0 [Tokio v1.7.0]: https://github.com/tokio-rs/tokio/releases/tag/tokio-1.7.0 [Tokio v1.12.0]:https://github.com/tokio-rs/tokio/releases/tag/tokio-1.12.0 @@ -144,6 +146,7 @@ Other instrumentation is added in later Tokio releases: [builder]: https://docs.rs/console-subscriber/latest/console_subscriber/struct.Builder.html [init]: https://docs.rs/console-subscriber/latest/console_subscriber/fn.init.html [compile_time_filters]: https://docs.rs/tracing/latest/tracing/level_filters/index.html#compile-time-filters +[Tokio v1.21.0]: https://github.com/tokio-rs/tokio/releases/tag/tokio-1.21.0 ### Adding the Console Subscriber From 57b866dd70ee36545ea0c02b02be872183cfa431 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Thu, 9 Mar 2023 00:01:50 +0100 Subject: [PATCH 25/30] feat(console): reduce decimal digits in UI (#402) The durations in the tokio-console UI are shown with a unit, so the digits after the decimal separator are generally not relevant. Since space is at a premium in a terminal UI, it makes sense to cut down where possible. This change removes all digits after the decimal separator in the tasks table view. In the task detail view, 2 decimal places are kept. Additionally, 4 new duration formats are added to represent minutes-with-secondsi, hours-with-minutes, days-with-hours and days. These are only applied once the leading unit is greater than zero. For example, 59 seconds will be shown as seconds: `99s` and then 60 seconds will be shown as minutes-with-seconds: `1m00s`. New colors have been chosen for each format. NOTE: I had to make a small unrelated change in `task::Details::make_percentiles_widget` to make clippy happy. Fixes: #224 --- tokio-console/src/view/async_ops.rs | 9 +- tokio-console/src/view/mod.rs | 5 +- tokio-console/src/view/resources.rs | 11 +-- tokio-console/src/view/styles.rs | 141 +++++++++++++++++++++++----- tokio-console/src/view/task.rs | 31 +++--- tokio-console/src/view/tasks.rs | 9 +- 6 files changed, 143 insertions(+), 63 deletions(-) diff --git a/tokio-console/src/view/async_ops.rs b/tokio-console/src/view/async_ops.rs index 7e804cfeb..0cdf02162 100644 --- a/tokio-console/src/view/async_ops.rs +++ b/tokio-console/src/view/async_ops.rs @@ -7,7 +7,7 @@ use crate::{ view::{ self, bold, table::{self, TableList, TableListState}, - DUR_LEN, DUR_PRECISION, + DUR_LEN, DUR_TABLE_PRECISION, }, }; @@ -106,12 +106,7 @@ impl TableList<9> for AsyncOpsTable { let mut polls_width = view::Width::new(Self::WIDTHS[7] as u16); let dur_cell = |dur: std::time::Duration| -> Cell<'static> { - Cell::from(styles.time_units(format!( - "{:>width$.prec$?}", - dur, - width = DUR_LEN, - prec = DUR_PRECISION, - ))) + Cell::from(styles.time_units(dur, DUR_TABLE_PRECISION, Some(DUR_LEN))) }; let rows = { diff --git a/tokio-console/src/view/mod.rs b/tokio-console/src/view/mod.rs index 8f1d5429d..9f50d5da7 100644 --- a/tokio-console/src/view/mod.rs +++ b/tokio-console/src/view/mod.rs @@ -18,11 +18,12 @@ mod tasks; pub(crate) use self::styles::{Palette, Styles}; pub(crate) use self::table::SortBy; -const DUR_LEN: usize = 10; +const DUR_LEN: usize = 6; // This data is only updated every second, so it doesn't make a ton of // sense to have a lot of precision in timestamps (and this makes sure // there's room for the unit!) -const DUR_PRECISION: usize = 4; +const DUR_LIST_PRECISION: usize = 2; +const DUR_TABLE_PRECISION: usize = 0; const TABLE_HIGHLIGHT_SYMBOL: &str = ">> "; pub struct View { diff --git a/tokio-console/src/view/resources.rs b/tokio-console/src/view/resources.rs index 4b65b00e8..afe9fc1f6 100644 --- a/tokio-console/src/view/resources.rs +++ b/tokio-console/src/view/resources.rs @@ -6,7 +6,7 @@ use crate::{ view::{ self, bold, table::{self, TableList, TableListState}, - DUR_LEN, DUR_PRECISION, + DUR_LEN, DUR_TABLE_PRECISION, }, }; @@ -104,12 +104,11 @@ impl TableList<9> for ResourcesTable { ))), Cell::from(parent_width.update_str(resource.parent_id()).to_owned()), Cell::from(kind_width.update_str(resource.kind()).to_owned()), - Cell::from(styles.time_units(format!( - "{:>width$.prec$?}", + Cell::from(styles.time_units( resource.total(now), - width = DUR_LEN, - prec = DUR_PRECISION, - ))), + DUR_TABLE_PRECISION, + Some(DUR_LEN), + )), Cell::from(target_width.update_str(resource.target()).to_owned()), Cell::from(type_width.update_str(resource.concrete_type()).to_owned()), Cell::from(resource.type_visibility().render(styles)), diff --git a/tokio-console/src/view/styles.rs b/tokio-console/src/view/styles.rs index b71587c1d..7834cca2b 100644 --- a/tokio-console/src/view/styles.rs +++ b/tokio-console/src/view/styles.rs @@ -1,6 +1,6 @@ use crate::config; use serde::{Deserialize, Serialize}; -use std::{borrow::Cow, str::FromStr}; +use std::{str::FromStr, time::Duration}; use tui::{ style::{Color, Modifier, Style}, text::Span, @@ -32,11 +32,41 @@ pub enum Palette { All, } +/// Represents formatted time spans. +/// +/// Distinguishing between different units allows appropriate colouring. +enum FormattedDuration { + /// Days (and no minor unit), e.g. `102d` + Days(String), + /// Days with hours, e.g. `12d03h` + DaysHours(String), + /// Hours with minutes, e.g. `14h32m` + HoursMinutes(String), + /// Minutes with seconds, e.g. `43m02s` + MinutesSeconds(String), + /// The `time::Duration` debug string which uses units ranging from + /// picoseconds (`ps`) to seconds (`s`). May contain decimal digits + /// (e.g. `628.76ms`) or not (e.g. `32ns`) + Debug(String), +} + +impl FormattedDuration { + fn into_inner(self) -> String { + match self { + Self::Days(inner) => inner, + Self::DaysHours(inner) => inner, + Self::HoursMinutes(inner) => inner, + Self::MinutesSeconds(inner) => inner, + Self::Debug(inner) => inner, + } + } +} + fn fg_style(color: Color) -> Style { Style::default().fg(color) } -// === impl Config === +// === impl Styles === impl Styles { pub fn from_config(config: config::ViewOptions) -> Self { @@ -126,39 +156,100 @@ impl Styles { } } - pub fn time_units<'a>(&self, text: impl Into>) -> Span<'a> { - let mut text = text.into(); - if !self.toggles.color_durations() { - return Span::raw(text); - } + /// Creates a span with a formatted duration inside. + /// + /// The formatted duration will be colored depending on the palette + /// defined for this `Styles` object. + /// + /// If the `width` parameter is `None` then no padding will be + /// added. Otherwise the text in the span will be left-padded to + /// the specified width (right aligned). Passing `Some(0)` is + /// equivalent to `None`. + pub fn time_units<'a>(&self, dur: Duration, prec: usize, width: Option) -> Span<'a> { + let formatted = self.duration_text(dur, width.unwrap_or(0), prec); - if !self.utf8 { - if let Some(mu_offset) = text.find("µs") { - text.to_mut().replace_range(mu_offset.., "us"); - } + if !self.toggles.color_durations() { + return Span::raw(formatted.into_inner()); } let style = match self.palette { - Palette::NoColors => return Span::raw(text), - Palette::Ansi8 | Palette::Ansi16 => match text.as_ref() { - s if s.ends_with("ps") => fg_style(Color::Blue), - s if s.ends_with("ns") => fg_style(Color::Green), - s if s.ends_with("µs") || s.ends_with("us") => fg_style(Color::Yellow), - s if s.ends_with("ms") => fg_style(Color::Red), - s if s.ends_with('s') => fg_style(Color::Magenta), + Palette::NoColors => return Span::raw(formatted.into_inner()), + Palette::Ansi8 | Palette::Ansi16 => match &formatted { + FormattedDuration::Days(_) => fg_style(Color::Blue), + FormattedDuration::DaysHours(_) => fg_style(Color::Blue), + FormattedDuration::HoursMinutes(_) => fg_style(Color::Cyan), + FormattedDuration::MinutesSeconds(_) => fg_style(Color::Green), + FormattedDuration::Debug(s) if s.ends_with("ps") => fg_style(Color::Gray), + FormattedDuration::Debug(s) if s.ends_with("ns") => fg_style(Color::Gray), + FormattedDuration::Debug(s) if s.ends_with("µs") || s.ends_with("us") => { + fg_style(Color::Magenta) + } + FormattedDuration::Debug(s) if s.ends_with("ms") => fg_style(Color::Red), + FormattedDuration::Debug(s) if s.ends_with('s') => fg_style(Color::Yellow), _ => Style::default(), }, - Palette::Ansi256 | Palette::All => match text.as_ref() { - s if s.ends_with("ps") => fg_style(Color::Indexed(40)), // green 3 - s if s.ends_with("ns") => fg_style(Color::Indexed(41)), // spring green 3 - s if s.ends_with("µs") || s.ends_with("us") => fg_style(Color::Indexed(42)), // spring green 2 - s if s.ends_with("ms") => fg_style(Color::Indexed(43)), // cyan 3 - s if s.ends_with('s') => fg_style(Color::Indexed(44)), // dark turquoise, + Palette::Ansi256 | Palette::All => match &formatted { + FormattedDuration::Days(_) => fg_style(Color::Indexed(33)), // dodger blue 1 + FormattedDuration::DaysHours(_) => fg_style(Color::Indexed(33)), // dodger blue 1 + FormattedDuration::HoursMinutes(_) => fg_style(Color::Indexed(39)), // deep sky blue 1 + FormattedDuration::MinutesSeconds(_) => fg_style(Color::Indexed(45)), // turquoise 2 + FormattedDuration::Debug(s) if s.ends_with("ps") => fg_style(Color::Indexed(40)), // green 3 + FormattedDuration::Debug(s) if s.ends_with("ns") => fg_style(Color::Indexed(41)), // spring green 3 + FormattedDuration::Debug(s) if s.ends_with("µs") || s.ends_with("us") => { + fg_style(Color::Indexed(42)) + } // spring green 2 + FormattedDuration::Debug(s) if s.ends_with("ms") => fg_style(Color::Indexed(43)), // cyan 3 + FormattedDuration::Debug(s) if s.ends_with('s') => fg_style(Color::Indexed(44)), // dark turquoise, _ => Style::default(), }, }; - Span::styled(text, style) + Span::styled(formatted.into_inner(), style) + } + + fn duration_text(&self, dur: Duration, width: usize, prec: usize) -> FormattedDuration { + let secs = dur.as_secs(); + + if secs >= 60 * 60 * 24 * 100 { + let days = secs / (60 * 60 * 24); + FormattedDuration::Days(format!("{days:>width$}d", days = days, width = width)) + } else if secs >= 60 * 60 * 24 { + let hours = secs / (60 * 60); + FormattedDuration::DaysHours(format!( + "{days:>leading_width$}d{hours:02.0}h", + days = hours / 24, + hours = hours % 24, + // Subtract the known 4 characters that trail the days value. + leading_width = width.saturating_sub(4), + )) + } else if secs >= 60 * 60 { + let mins = secs / 60; + FormattedDuration::HoursMinutes(format!( + "{hours:>leading_width$}h{minutes:02.0}m", + hours = mins / 60, + minutes = mins % 60, + // Subtract the known 4 characters that trail the hours value. + leading_width = width.saturating_sub(4), + )) + } else if secs >= 60 { + FormattedDuration::MinutesSeconds(format!( + "{minutes:>leading_width$}m{seconds:02.0}s", + minutes = secs / 60, + seconds = secs % 60, + // Subtract the known 4 characters that trail the minutes value. + leading_width = width.saturating_sub(4), + )) + } else { + let mut text = format!("{:>width$.prec$?}", dur, width = width, prec = prec); + + if !self.utf8 { + if let Some(mu_offset) = text.find("µs") { + text.replace_range(mu_offset.., "us"); + } + } + + FormattedDuration::Debug(text) + } } pub fn terminated(&self) -> Style { diff --git a/tokio-console/src/view/task.rs b/tokio-console/src/view/task.rs index 2708bfb92..b8febbcab 100644 --- a/tokio-console/src/view/task.rs +++ b/tokio-console/src/view/task.rs @@ -9,6 +9,7 @@ use crate::{ view::{ self, bold, mini_histogram::{HistogramMetadata, MiniHistogram}, + DUR_LIST_PRECISION, }, }; use std::{ @@ -329,29 +330,27 @@ impl Details { fn make_percentiles_widget(&self, styles: &view::Styles) -> Text<'static> { let mut text = Text::default(); let histogram = self.poll_times_histogram(); - let percentiles = - histogram - .iter() - .flat_map(|&DurationHistogram { ref histogram, .. }| { - let pairs = [10f64, 25f64, 50f64, 75f64, 90f64, 95f64, 99f64] - .iter() - .map(move |i| (*i, histogram.value_at_percentile(*i))); - pairs.map(|pair| { - Spans::from(vec![ - bold(format!("p{:>2}: ", pair.0)), - dur(styles, Duration::from_nanos(pair.1)), - ]) - }) - }); + let percentiles = histogram + .iter() + .flat_map(|&DurationHistogram { histogram, .. }| { + let pairs = [10f64, 25f64, 50f64, 75f64, 90f64, 95f64, 99f64] + .iter() + .map(move |i| (*i, histogram.value_at_percentile(*i))); + pairs.map(|pair| { + Spans::from(vec![ + bold(format!("p{:>2}: ", pair.0)), + dur(styles, Duration::from_nanos(pair.1)), + ]) + }) + }); text.extend(percentiles); text } } fn dur(styles: &view::Styles, dur: std::time::Duration) -> Span<'static> { - const DUR_PRECISION: usize = 4; // TODO(eliza): can we not have to use `format!` to make a string here? is // there a way to just give TUI a `fmt::Debug` implementation, or does it // have to be given a string in order to do layout stuff? - styles.time_units(format!("{:.prec$?}", dur, prec = DUR_PRECISION)) + styles.time_units(dur, DUR_LIST_PRECISION, None) } diff --git a/tokio-console/src/view/tasks.rs b/tokio-console/src/view/tasks.rs index eb6b2365a..780f4775c 100644 --- a/tokio-console/src/view/tasks.rs +++ b/tokio-console/src/view/tasks.rs @@ -6,7 +6,7 @@ use crate::{ view::{ self, bold, table::{self, TableList, TableListState}, - DUR_LEN, DUR_PRECISION, + DUR_LEN, DUR_TABLE_PRECISION, }, }; use tui::{ @@ -68,12 +68,7 @@ impl TableList<11> for TasksTable { .sort(now, &mut table_list_state.sorted_items); let dur_cell = |dur: std::time::Duration| -> Cell<'static> { - Cell::from(styles.time_units(format!( - "{:>width$.prec$?}", - dur, - width = DUR_LEN, - prec = DUR_PRECISION, - ))) + Cell::from(styles.time_units(dur, DUR_TABLE_PRECISION, Some(DUR_LEN))) }; // Start out wide enough to display the column headers... From 4ec13da730a21a04e43be45f3c13f3bc75427363 Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Tue, 28 Mar 2023 18:12:41 +0200 Subject: [PATCH 26/30] chore(console): remove `tracing-subscriber` 0.2 from dependencies (#404) The `color-eyre` crate was previously on version 0.5. This version depends on `tracing-subscriber` 0.2, meaning that we had 2 different veresions of `tracing-subscriber` in our dependency tree. This change updates `color-eyre` to 0.6, which depends on `tracing-subscriber` 0.3, the same as `console-subscriber`. --- Cargo.lock | 35 ++++++++++++----------------------- tokio-console/Cargo.toml | 2 +- xtask/Cargo.toml | 2 +- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce3617900..be4f27905 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,9 +240,9 @@ dependencies = [ [[package]] name = "color-eyre" -version = "0.5.11" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" dependencies = [ "backtrace", "color-spantrace", @@ -256,9 +256,9 @@ dependencies = [ [[package]] name = "color-spantrace" -version = "0.1.6" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" dependencies = [ "once_cell", "owo-colors", @@ -298,7 +298,7 @@ dependencies = [ "tonic", "tracing", "tracing-core", - "tracing-subscriber 0.3.11", + "tracing-subscriber", ] [[package]] @@ -908,9 +908,9 @@ checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" [[package]] name = "owo-colors" -version = "1.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking_lot" @@ -1456,7 +1456,7 @@ dependencies = [ "tonic", "tracing", "tracing-journald", - "tracing-subscriber 0.3.11", + "tracing-subscriber", "tui", ] @@ -1647,12 +1647,12 @@ dependencies = [ [[package]] name = "tracing-error" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" dependencies = [ "tracing", - "tracing-subscriber 0.2.25", + "tracing-subscriber", ] [[package]] @@ -1673,7 +1673,7 @@ checksum = "1ba49f4829f4e95702943ec6b2fad8936b369d20fa27036caf01329fb230e460" dependencies = [ "libc", "tracing-core", - "tracing-subscriber 0.3.11", + "tracing-subscriber", ] [[package]] @@ -1687,17 +1687,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-subscriber" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.3.11" diff --git a/tokio-console/Cargo.toml b/tokio-console/Cargo.toml index 30496f672..6b0909312 100644 --- a/tokio-console/Cargo.toml +++ b/tokio-console/Cargo.toml @@ -39,7 +39,7 @@ tracing-subscriber = { version = "0.3.0", features = ["env-filter"] } tracing-journald = { version = "0.2", optional = true } prost-types = "0.11" crossterm = { version = "0.20", features = ["event-stream"] } -color-eyre = { version = "0.5", features = ["issue-url"] } +color-eyre = { version = "0.6", features = ["issue-url"] } hdrhistogram = { version = "7.3.0", default-features = false, features = ["serialization"] } h2 = "0.3" regex = "1.5" diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 09f60941a..f113b8128 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -11,4 +11,4 @@ tonic-build = { version = "0.8", default-features = false, features = [ "prost", "transport" ] } clap = { version = "3", features = ["derive"] } -color-eyre = "0.5" \ No newline at end of file +color-eyre = "0.6" \ No newline at end of file From 001fc49f09ad78cc4ab50770cf4a677ae177103f Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Tue, 28 Mar 2023 18:27:53 +0200 Subject: [PATCH 27/30] feat(console): use tokio task ids in task views (#403) Tokio Console generates its own sequential Id for internal tracking and indexing of objects (tasks, resources, etc.). However, this Id will be recreated if Console is restarted. In order to provide more useful information to the user, the task Id generated by Tokio can be used in the task list and task details screens instead. If used in this way, the ID field in the task list and task detail views will be stable across restarts of Console (assuming the monitored application is not restarted). This also frees up horizontal space, as the `task.id` attribute doesn't need to be displayed separately. The disadvantage of using Tokio's task Id is that it is not guaranteed to be present by the type system. To avoid problems with missing task Ids, the Tokio task Id is store in addition to the `span::Id` (used to communicate with the `console-subscriber`) and the sequential console `Id` (used in the `store`). If a task is missing the `task.id` field for whatever reason it will still appear, but with an empty ID. If multiple runtimes are active, then duplicate ID values will appear. Fixes: #385 Co-authored-by: Eliza Weisman --- console-api/src/common.rs | 2 +- tokio-console/src/state/mod.rs | 1 + tokio-console/src/state/tasks.rs | 43 ++++++++++++++++++++++++++++---- tokio-console/src/view/tasks.rs | 2 +- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/console-api/src/common.rs b/console-api/src/common.rs index cb6edf73c..732479f49 100644 --- a/console-api/src/common.rs +++ b/console-api/src/common.rs @@ -210,7 +210,7 @@ impl From<&dyn std::fmt::Debug> for field::Value { // or vice versa. However, this is unavoidable here, because `prost` generates // a struct with `#[derive(PartialEq)]`, but we cannot add`#[derive(Hash)]` to the // generated code. -#[allow(clippy::derive_hash_xor_eq)] +#[allow(clippy::derived_hash_with_manual_eq)] impl Hash for field::Name { fn hash(&self, state: &mut H) { match self { diff --git a/tokio-console/src/state/mod.rs b/tokio-console/src/state/mod.rs index eb96606af..1458a1065 100644 --- a/tokio-console/src/state/mod.rs +++ b/tokio-console/src/state/mod.rs @@ -271,6 +271,7 @@ impl Metadata { impl Field { const SPAWN_LOCATION: &'static str = "spawn.location"; const NAME: &'static str = "task.name"; + const TASK_ID: &'static str = "task.id"; /// Converts a wire-format `Field` into an internal `Field` representation, /// using the provided `Metadata` for the task span that the field came diff --git a/tokio-console/src/state/tasks.rs b/tokio-console/src/state/tasks.rs index 8be120d8f..1c75453ca 100644 --- a/tokio-console/src/state/tasks.rs +++ b/tokio-console/src/state/tasks.rs @@ -5,7 +5,7 @@ use crate::{ histogram::DurationHistogram, pb_duration, store::{self, Id, SpanId, Store}, - Field, Metadata, Visibility, + Field, FieldValue, Metadata, Visibility, }, util::Percentage, view, @@ -58,6 +58,17 @@ pub(crate) enum TaskState { pub(crate) type TaskRef = store::Ref; +/// The Id for a Tokio task. +/// +/// This should be equivalent to [`tokio::task::Id`], which can't be +/// used because it's not possible to construct outside the `tokio` +/// crate. +/// +/// Within the context of `tokio-console`, we don't depend on it +/// being the same as Tokio's own type, as the task id is recorded +/// as a `u64` in tracing and then sent via the wire protocol as such. +pub(crate) type TaskId = u64; + #[derive(Debug)] pub(crate) struct Task { /// The task's pretty (console-generated, sequential) task ID. @@ -65,10 +76,14 @@ pub(crate) struct Task { /// This is NOT the `tracing::span::Id` for the task's tracing span on the /// remote. id: Id, + /// The `tokio::task::Id` in the remote tokio runtime. + task_id: Option, /// The `tracing::span::Id` on the remote process for this task's span. /// /// This is used when requesting a task details stream. span_id: SpanId, + /// A cached string representation of the Id for display purposes. + id_str: String, short_desc: InternedStr, formatted_fields: Vec>>, stats: TaskStats, @@ -147,6 +162,7 @@ impl TasksState { } }; let mut name = None; + let mut task_id = None; let mut fields = task .fields .drain(..) @@ -157,6 +173,13 @@ impl TasksState { name = Some(strings.string(field.value.to_string())); return None; } + if &*field.name == Field::TASK_ID { + task_id = match field.value { + FieldValue::U64(id) => Some(id as TaskId), + _ => None, + }; + return None; + } Some(field) }) .collect::>(); @@ -170,15 +193,19 @@ impl TasksState { // remap the server's ID to a pretty, sequential task ID let id = ids.id_for(span_id); - let short_desc = strings.string(match name.as_ref() { - Some(name) => format!("{} ({})", id, name), - None => format!("{}", id), + let short_desc = strings.string(match (task_id, name.as_ref()) { + (Some(task_id), Some(name)) => format!("{task_id} ({name})"), + (Some(task_id), None) => task_id.to_string(), + (None, Some(name)) => name.as_ref().to_owned(), + (None, None) => "".to_owned(), }); let mut task = Task { name, id, + task_id, span_id, + id_str: task_id.map(|id| id.to_string()).unwrap_or_default(), short_desc, formatted_fields, stats, @@ -245,6 +272,10 @@ impl Task { self.span_id } + pub(crate) fn id_str(&self) -> &str { + &self.id_str + } + pub(crate) fn target(&self) -> &str { &self.target } @@ -426,7 +457,9 @@ impl Default for SortBy { impl SortBy { pub fn sort(&self, now: SystemTime, tasks: &mut [Weak>]) { match self { - Self::Tid => tasks.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().id)), + Self::Tid => { + tasks.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().task_id)) + } Self::Name => { tasks.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().name.clone())) } diff --git a/tokio-console/src/view/tasks.rs b/tokio-console/src/view/tasks.rs index 780f4775c..d103779ab 100644 --- a/tokio-console/src/view/tasks.rs +++ b/tokio-console/src/view/tasks.rs @@ -122,7 +122,7 @@ impl TableList<11> for TasksTable { warnings, Cell::from(id_width.update_str(format!( "{:>width$}", - task.id(), + task.id_str(), width = id_width.chars() as usize ))), Cell::from(task.state().render(styles)), From bff8b8a4291b0584ab4f97c5f91246eb9a68f262 Mon Sep 17 00:00:00 2001 From: Nikhil Benesch Date: Tue, 28 Mar 2023 15:47:09 -0400 Subject: [PATCH 28/30] feat(console): add support for Unix domain sockets (#388) Add support for console connections that use Unix domain sockets rather than TCP. Closes #296. Co-authored-by: Eliza Weisman --- Cargo.lock | 1 + console-subscriber/Cargo.toml | 2 +- console-subscriber/examples/uds.rs | 40 ++++++++++ console-subscriber/src/builder.rs | 113 ++++++++++++++++++++++++++--- console-subscriber/src/lib.rs | 33 ++++++--- tokio-console/Cargo.toml | 1 + tokio-console/args.example | 3 + tokio-console/src/config.rs | 4 + tokio-console/src/conn.rs | 33 ++++++++- 9 files changed, 208 insertions(+), 22 deletions(-) create mode 100644 console-subscriber/examples/uds.rs diff --git a/Cargo.lock b/Cargo.lock index be4f27905..02156e518 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1454,6 +1454,7 @@ dependencies = [ "tokio", "toml", "tonic", + "tower", "tracing", "tracing-journald", "tracing-subscriber", diff --git a/console-subscriber/Cargo.toml b/console-subscriber/Cargo.toml index 3ed323c32..3c044c8d7 100644 --- a/console-subscriber/Cargo.toml +++ b/console-subscriber/Cargo.toml @@ -33,7 +33,7 @@ env-filter = ["tracing-subscriber/env-filter"] crossbeam-utils = "0.8.7" tokio = { version = "^1.21", features = ["sync", "time", "macros", "tracing"] } -tokio-stream = "0.1" +tokio-stream = { version = "0.1", features = ["net"] } thread_local = "1.1.3" console-api = { version = "0.4.0", path = "../console-api", features = ["transport"] } tonic = { version = "0.8", features = ["transport"] } diff --git a/console-subscriber/examples/uds.rs b/console-subscriber/examples/uds.rs new file mode 100644 index 000000000..03f6f2d4a --- /dev/null +++ b/console-subscriber/examples/uds.rs @@ -0,0 +1,40 @@ +//! Demonstrates serving the console API over a [Unix domain socket] (UDS) +//! connection, rather than over TCP. +//! +//! Note that this example only works on Unix operating systems that +//! support UDS, such as Linux, BSDs, and macOS. +//! +//! [Unix domain socket]: https://en.wikipedia.org/wiki/Unix_domain_socket + +#[cfg(unix)] +use { + std::time::Duration, + tokio::{fs, task, time}, + tracing::info, +}; + +#[cfg(unix)] +#[tokio::main] +async fn main() -> Result<(), Box> { + let cwd = fs::canonicalize(".").await?; + let addr = cwd.join("console-server"); + console_subscriber::ConsoleLayer::builder() + .server_addr(&*addr) + .init(); + info!( + "listening for console connections at file://localhost{}", + addr.display() + ); + task::Builder::default() + .name("sleepy") + .spawn(async move { time::sleep(Duration::from_secs(90)).await }) + .unwrap() + .await?; + + Ok(()) +} + +#[cfg(not(unix))] +fn main() { + panic!("only supported on Unix platforms") +} diff --git a/console-subscriber/src/builder.rs b/console-subscriber/src/builder.rs index 2c9fb0cc6..1e0819dde 100644 --- a/console-subscriber/src/builder.rs +++ b/console-subscriber/src/builder.rs @@ -1,6 +1,8 @@ use super::{ConsoleLayer, Server}; +#[cfg(unix)] +use std::path::Path; use std::{ - net::{SocketAddr, ToSocketAddrs}, + net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}, path::PathBuf, thread, time::Duration, @@ -32,7 +34,7 @@ pub struct Builder { pub(crate) retention: Duration, /// The address on which to serve the RPC server. - pub(super) server_addr: SocketAddr, + pub(super) server_addr: ServerAddr, /// If and where to save a recording of the events. pub(super) recording_path: Option, @@ -58,7 +60,7 @@ impl Default for Builder { publish_interval: ConsoleLayer::DEFAULT_PUBLISH_INTERVAL, retention: ConsoleLayer::DEFAULT_RETENTION, poll_duration_max: ConsoleLayer::DEFAULT_POLL_DURATION_MAX, - server_addr: SocketAddr::new(Server::DEFAULT_IP, Server::DEFAULT_PORT), + server_addr: ServerAddr::Tcp(SocketAddr::new(Server::DEFAULT_IP, Server::DEFAULT_PORT)), recording_path: None, filter_env_var: "RUST_LOG".to_string(), self_trace: false, @@ -137,8 +139,38 @@ impl Builder { /// before falling back on constructing a socket address from those /// defaults. /// + /// The socket address can be either a TCP socket address or a + /// [Unix domain socket] (UDS) address. Unix domain sockets are only + /// supported on Unix-compatible operating systems, such as Linux, BSDs, + /// and macOS. + /// + /// Each call to this method will overwrite the previously set value. + /// + /// # Examples + /// + /// Connect to the TCP address `localhost:1234`: + /// + /// ``` + /// # use console_subscriber::Builder; + /// use std::net::Ipv4Addr; + /// let builder = Builder::default().server_addr((Ipv4Addr::LOCALHOST, 1234)); + /// ``` + /// + /// Connect to the UDS address `/tmp/tokio-console`: + /// + /// ``` + /// # use console_subscriber::Builder; + /// # #[cfg(unix)] + /// use std::path::Path; + /// + /// // Unix domain sockets are only available on Unix-compatible operating systems. + /// #[cfg(unix)] + /// let builder = Builder::default().server_addr(Path::new("/tmp/tokio-console")); + /// ``` + /// /// [environment variable]: `Builder::with_default_env` - pub fn server_addr(self, server_addr: impl Into) -> Self { + /// [Unix domain socket]: https://en.wikipedia.org/wiki/Unix_domain_socket + pub fn server_addr(self, server_addr: impl Into) -> Self { Self { server_addr: server_addr.into(), ..self @@ -231,11 +263,14 @@ impl Builder { } if let Ok(bind) = std::env::var("TOKIO_CONSOLE_BIND") { - self.server_addr = bind - .to_socket_addrs() - .expect("TOKIO_CONSOLE_BIND must be formatted as HOST:PORT, such as localhost:4321") - .next() - .expect("tokio console could not resolve TOKIO_CONSOLE_BIND"); + self.server_addr = ServerAddr::Tcp( + bind.to_socket_addrs() + .expect( + "TOKIO_CONSOLE_BIND must be formatted as HOST:PORT, such as localhost:4321", + ) + .next() + .expect("tokio console could not resolve TOKIO_CONSOLE_BIND"), + ); } if let Some(interval) = duration_from_env("TOKIO_CONSOLE_PUBLISH_INTERVAL") { @@ -456,6 +491,66 @@ impl Builder { } } +/// Specifies the address on which a [`Server`] should listen. +/// +/// This type is passed as an argument to the [`Builder::server_addr`] +/// method, and may be either a TCP socket address, or a [Unix domain socket] +/// (UDS) address. Unix domain sockets are only supported on Unix-compatible +/// operating systems, such as Linux, BSDs, and macOS. +/// +/// [`Server`]: crate::Server +/// [Unix domain socket]: https://en.wikipedia.org/wiki/Unix_domain_socket +#[derive(Clone, Debug)] +#[non_exhaustive] +pub enum ServerAddr { + /// A TCP address. + Tcp(SocketAddr), + /// A Unix socket address. + #[cfg(unix)] + Unix(PathBuf), +} + +impl From for ServerAddr { + fn from(addr: SocketAddr) -> ServerAddr { + ServerAddr::Tcp(addr) + } +} + +impl From for ServerAddr { + fn from(addr: SocketAddrV4) -> ServerAddr { + ServerAddr::Tcp(addr.into()) + } +} + +impl From for ServerAddr { + fn from(addr: SocketAddrV6) -> ServerAddr { + ServerAddr::Tcp(addr.into()) + } +} + +impl From<(I, u16)> for ServerAddr +where + I: Into, +{ + fn from(pieces: (I, u16)) -> ServerAddr { + ServerAddr::Tcp(pieces.into()) + } +} + +#[cfg(unix)] +impl From for ServerAddr { + fn from(path: PathBuf) -> ServerAddr { + ServerAddr::Unix(path) + } +} + +#[cfg(unix)] +impl<'a> From<&'a Path> for ServerAddr { + fn from(path: &'a Path) -> ServerAddr { + ServerAddr::Unix(path.to_path_buf()) + } +} + /// Initializes the console [tracing `Subscriber`][sub] and starts the console /// subscriber [`Server`] on its own background thread. /// diff --git a/console-subscriber/src/lib.rs b/console-subscriber/src/lib.rs index bde80347c..d91ee392a 100644 --- a/console-subscriber/src/lib.rs +++ b/console-subscriber/src/lib.rs @@ -5,7 +5,7 @@ use serde::Serialize; use std::{ cell::RefCell, fmt, - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{IpAddr, Ipv4Addr}, sync::{ atomic::{AtomicUsize, Ordering}, Arc, @@ -13,7 +13,11 @@ use std::{ time::{Duration, Instant}, }; use thread_local::ThreadLocal; +#[cfg(unix)] +use tokio::net::UnixListener; use tokio::sync::{mpsc, oneshot}; +#[cfg(unix)] +use tokio_stream::wrappers::UnixListenerStream; use tracing_core::{ span::{self, Id}, subscriber::{self, Subscriber}, @@ -36,7 +40,7 @@ pub(crate) mod sync; mod visitors; use aggregator::Aggregator; -pub use builder::Builder; +pub use builder::{Builder, ServerAddr}; use callsites::Callsites; use record::Recorder; use stack::SpanStack; @@ -134,7 +138,7 @@ pub struct ConsoleLayer { /// [cli]: https://crates.io/crates/tokio-console pub struct Server { subscribe: mpsc::Sender, - addr: SocketAddr, + addr: ServerAddr, aggregator: Option, client_buffer: usize, } @@ -945,13 +949,22 @@ impl Server { .take() .expect("cannot start server multiple times"); let aggregate = spawn_named(aggregate.run(), "console::aggregate"); - let addr = self.addr; - let serve = builder - .add_service(proto::instrument::instrument_server::InstrumentServer::new( - self, - )) - .serve(addr); - let res = spawn_named(serve, "console::serve").await; + let addr = self.addr.clone(); + let router = builder.add_service( + proto::instrument::instrument_server::InstrumentServer::new(self), + ); + let res = match addr { + ServerAddr::Tcp(addr) => { + let serve = router.serve(addr); + spawn_named(serve, "console::serve").await + } + #[cfg(unix)] + ServerAddr::Unix(path) => { + let incoming = UnixListener::bind(path)?; + let serve = router.serve_with_incoming(UnixListenerStream::new(incoming)); + spawn_named(serve, "console::serve").await + } + }; aggregate.abort(); res?.map_err(Into::into) } diff --git a/tokio-console/Cargo.toml b/tokio-console/Cargo.toml index 6b0909312..a6f0de3f3 100644 --- a/tokio-console/Cargo.toml +++ b/tokio-console/Cargo.toml @@ -34,6 +34,7 @@ tokio = { version = "1", features = ["full", "rt-multi-thread"] } tonic = { version = "0.8", features = ["transport"] } futures = "0.3" tui = { version = "0.16.0", default-features = false, features = ["crossterm"] } +tower = "0.4.12" tracing = "0.1" tracing-subscriber = { version = "0.3.0", features = ["env-filter"] } tracing-journald = { version = "0.2", optional = true } diff --git a/tokio-console/args.example b/tokio-console/args.example index db39c8f85..a6b59aaf8 100644 --- a/tokio-console/args.example +++ b/tokio-console/args.example @@ -7,6 +7,9 @@ ARGS: This may be an IP address and port, or a DNS name. + On Unix platforms, this may also be a URI with the `file` scheme that specifies the path + to a Unix domain socket, as in `file://localhost/path/to/socket`. + [default: http://127.0.0.1:6669] OPTIONS: diff --git a/tokio-console/src/config.rs b/tokio-console/src/config.rs index 52b980c2d..9513d7609 100644 --- a/tokio-console/src/config.rs +++ b/tokio-console/src/config.rs @@ -26,6 +26,10 @@ pub struct Config { /// /// This may be an IP address and port, or a DNS name. /// + /// On Unix platforms, this may also be a URI with the `file` scheme that + /// specifies the path to a Unix domain socket, as in + /// `file://localhost/path/to/socket`. + /// /// [default: http://127.0.0.1:6669] #[clap(value_hint = ValueHint::Url)] pub(crate) target_addr: Option, diff --git a/tokio-console/src/conn.rs b/tokio-console/src/conn.rs index faf42e9e0..6330b329b 100644 --- a/tokio-console/src/conn.rs +++ b/tokio-console/src/conn.rs @@ -5,7 +5,12 @@ use console_api::instrument::{ use console_api::tasks::TaskDetails; use futures::stream::StreamExt; use std::{error::Error, pin::Pin, time::Duration}; -use tonic::{transport::Channel, transport::Uri, Streaming}; +#[cfg(unix)] +use tokio::net::UnixStream; +use tonic::{ + transport::{Channel, Endpoint, Uri}, + Streaming, +}; #[derive(Debug)] pub struct Connection { @@ -78,7 +83,31 @@ impl Connection { tokio::time::sleep(backoff).await; } let try_connect = async { - let mut client = InstrumentClient::connect(self.target.clone()).await?; + let channel = match self.target.scheme_str() { + #[cfg(unix)] + Some("file") => { + // Dummy endpoint is ignored by the connector. + let endpoint = Endpoint::from_static("http://localhost"); + if !matches!(self.target.host(), None | Some("localhost")) { + return Err("cannot connect to non-localhost unix domain socket".into()); + } + let path = self.target.path().to_owned(); + endpoint + .connect_with_connector(tower::service_fn(move |_| { + UnixStream::connect(path.clone()) + })) + .await? + } + #[cfg(not(unix))] + Some("file") => { + return Err("unix domain sockets are not supported on this platform".into()); + } + _ => { + let endpoint = Endpoint::try_from(self.target.clone())?; + endpoint.connect().await? + } + }; + let mut client = InstrumentClient::new(channel); let request = tonic::Request::new(InstrumentRequest {}); let stream = Box::new(client.watch_updates(request).await?.into_inner()); Ok::>(State::Connected { client, stream }) From 6fa2185134c8791446a1f1b5dc2ee11d254966ad Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Thu, 30 Mar 2023 22:06:38 +0200 Subject: [PATCH 29/30] fix(console): fix calculation of busy time during poll (#405) The Console API specifies sending task busy duration only for completed polls, it doesn't include the time spent in the current poll if the task is active. Tokio Console then calculates the busy time including the time spent in the current poll - based on the last poll start and poll end times sent by the Console Subscriber. However, there was an error in the logic which determined when a task is being polled for the purpose of calculating the busy time. The logic only considered the first poll, when there was no recorded end poll time at all. This change adapts the logic so that it also considers the case where the last recorded poll start is later than the last recorded poll end. This indicates that the task is being polled. Co-authored-by: Eliza Weisman --- tokio-console/src/state/tasks.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tokio-console/src/state/tasks.rs b/tokio-console/src/state/tasks.rs index 1c75453ca..ad362f518 100644 --- a/tokio-console/src/state/tasks.rs +++ b/tokio-console/src/state/tasks.rs @@ -321,12 +321,12 @@ impl Task { } pub(crate) fn busy(&self, since: SystemTime) -> Duration { - if let (Some(last_poll_started), None) = - (self.stats.last_poll_started, self.stats.last_poll_ended) - { - // in this case the task is being polled at the moment - let current_time_in_poll = since.duration_since(last_poll_started).unwrap_or_default(); - return self.stats.busy + current_time_in_poll; + if let Some(started) = self.stats.last_poll_started { + if self.stats.last_poll_started > self.stats.last_poll_ended { + // in this case the task is being polled at the moment + let current_time_in_poll = since.duration_since(started).unwrap_or_default(); + return self.stats.busy + current_time_in_poll; + } } self.stats.busy } From af6693b4cfb90f09616e9393e2507b5f71a5d42f Mon Sep 17 00:00:00 2001 From: Hayden Stainsby Date: Thu, 13 Apr 2023 20:05:25 +0200 Subject: [PATCH 30/30] refac(console): factor out `Durations` widget from task view (#408) There are 2 widgets which display the poll times for a task in the detail view. The poll times percentiles are always displayed and if UTF-8 is available, then a sparkline histogram is also shown to the right. The logic for displaying these two widgets is quite long and is currently interspersed within the `render` function for the task detail view plus helper functions. Additionally, it is not easy to add a second set of widgets showing the time between waking and being polled for a task which is planned for #409. This change factors out that logic into separate widgets. There was already a separate widget `MiniHistogram`. Some of the logic that was previously in the task detail view has been moved here. A new widget `Percentiles` has been added to encapsulate the logic for preparing and displaying the percentiles. A top level `Durations` widget occupies the entire width of the task detail view and control the horizontal layout of the `Percentiles` and `MiniHistogram` widgets. The new widget will also supress the histogram if there isn't at least enough room to display the legend at the bottom --- tokio-console/src/view/durations.rs | 108 +++++++++++ tokio-console/src/view/mini_histogram.rs | 220 ++++++++++++++--------- tokio-console/src/view/mod.rs | 7 +- tokio-console/src/view/percentiles.rs | 83 +++++++++ tokio-console/src/view/task.rs | 159 ++-------------- 5 files changed, 347 insertions(+), 230 deletions(-) create mode 100644 tokio-console/src/view/durations.rs create mode 100644 tokio-console/src/view/percentiles.rs diff --git a/tokio-console/src/view/durations.rs b/tokio-console/src/view/durations.rs new file mode 100644 index 000000000..29303ed36 --- /dev/null +++ b/tokio-console/src/view/durations.rs @@ -0,0 +1,108 @@ +use std::cmp; + +use tui::{ + layout::{self}, + widgets::Widget, +}; + +use crate::{ + state::histogram::DurationHistogram, + view::{self, mini_histogram::MiniHistogram, percentiles::Percentiles}, +}; + +// This is calculated so that a legend like the below generally fits: +// │0647.17µs 909.31µs │ +// This also gives at characters for the sparkline itself. +const MIN_HISTOGRAM_BLOCK_WIDTH: u16 = 22; + +/// This is a tui-rs widget to visualize durations as a list of percentiles +/// and if possible, a mini-histogram too. +/// +/// This widget wraps the [`Percentiles`] and [`MiniHistogram`] widgets which +/// are displayed side by side. The mini-histogram will only be displayed if +/// a) UTF-8 support is enabled via [`Styles`] +/// b) There is at least a minimum width (22 characters to display the full +/// bottom legend) left after drawing the percentiles +/// +/// This +/// +/// [`Styles`]: crate::view::Styles +pub(crate) struct Durations<'a> { + /// Widget style + styles: &'a view::Styles, + /// The histogram data to render + histogram: Option<&'a DurationHistogram>, + /// Title for percentiles block + percentiles_title: &'a str, + /// Title for histogram sparkline block + histogram_title: &'a str, +} + +impl<'a> Widget for Durations<'a> { + fn render(self, area: tui::layout::Rect, buf: &mut tui::buffer::Buffer) { + // Only split the durations area in half if we're also drawing a + // sparkline. We require UTF-8 to draw the sparkline and also enough width. + let (percentiles_area, histogram_area) = if self.styles.utf8 { + let percentiles_width = cmp::max(self.percentiles_title.len() as u16, 13_u16) + 2; + + // If there isn't enough width left after drawing the percentiles + // then we won't draw the sparkline at all. + if area.width < percentiles_width + MIN_HISTOGRAM_BLOCK_WIDTH { + (area, None) + } else { + let areas = layout::Layout::default() + .direction(layout::Direction::Horizontal) + .constraints( + [ + layout::Constraint::Length(percentiles_width), + layout::Constraint::Min(MIN_HISTOGRAM_BLOCK_WIDTH), + ] + .as_ref(), + ) + .split(area); + (areas[0], Some(areas[1])) + } + } else { + (area, None) + }; + + let percentiles_widget = Percentiles::new(self.styles) + .title(self.percentiles_title) + .histogram(self.histogram); + percentiles_widget.render(percentiles_area, buf); + + if let Some(histogram_area) = histogram_area { + let histogram_widget = MiniHistogram::default() + .block(self.styles.border_block().title(self.histogram_title)) + .histogram(self.histogram) + .duration_precision(2); + histogram_widget.render(histogram_area, buf); + } + } +} + +impl<'a> Durations<'a> { + pub(crate) fn new(styles: &'a view::Styles) -> Self { + Self { + styles, + histogram: None, + percentiles_title: "Percentiles", + histogram_title: "Histogram", + } + } + + pub(crate) fn histogram(mut self, histogram: Option<&'a DurationHistogram>) -> Self { + self.histogram = histogram; + self + } + + pub(crate) fn percentiles_title(mut self, title: &'a str) -> Self { + self.percentiles_title = title; + self + } + + pub(crate) fn histogram_title(mut self, title: &'a str) -> Self { + self.histogram_title = title; + self + } +} diff --git a/tokio-console/src/view/mini_histogram.rs b/tokio-console/src/view/mini_histogram.rs index 72ff983ef..1bdfcbd9e 100644 --- a/tokio-console/src/view/mini_histogram.rs +++ b/tokio-console/src/view/mini_histogram.rs @@ -7,6 +7,8 @@ use tui::{ widgets::{Block, Widget}, }; +use crate::state::histogram::DurationHistogram; + /// This is a tui-rs widget to visualize a latency histogram in a small area. /// It is based on the [`Sparkline`] widget, so it draws a mini bar chart with /// some labels for clarity. Unlike Sparkline, it does not omit very small @@ -18,10 +20,8 @@ pub(crate) struct MiniHistogram<'a> { block: Option>, /// Widget style style: Style, - /// Values for the buckets of the histogram - data: &'a [u64], - /// Metadata about the histogram - metadata: HistogramMetadata, + /// The histogram data to render + histogram: Option<&'a DurationHistogram>, /// The maximum value to take to compute the maximum bar height (if nothing is specified, the /// widget uses the max of the dataset) max: Option, @@ -51,8 +51,7 @@ impl<'a> Default for MiniHistogram<'a> { MiniHistogram { block: None, style: Default::default(), - data: &[], - metadata: Default::default(), + histogram: None, max: None, bar_set: symbols::bar::NINE_LEVELS, duration_precision: 4, @@ -75,34 +74,43 @@ impl<'a> Widget for MiniHistogram<'a> { return; } - let max_qty_label = self.metadata.max_bucket.to_string(); - let min_qty_label = self.metadata.min_bucket.to_string(); + let (data, metadata) = match self.histogram { + // Bit of a deadlock: We cannot know the highest bucket value without determining the number of buckets, + // and we cannot determine the number of buckets without knowing the width of the chart area which depends on + // the number of digits in the highest bucket value. + // So just assume here the number of digits in the highest bucket value is 3. + // If we overshoot, there will be empty columns/buckets at the right end of the chart. + // If we undershoot, the rightmost 1-2 columns/buckets will be hidden. + // We could get the max bucket value from the previous render though... + Some(h) => chart_data(h, inner_area.width - 3), + None => return, + }; + + let max_qty_label = metadata.max_bucket.to_string(); + let min_qty_label = metadata.min_bucket.to_string(); let max_record_label = format!( "{:.prec$?}", - Duration::from_nanos(self.metadata.max_value), + Duration::from_nanos(metadata.max_value), prec = self.duration_precision, ); let min_record_label = format!( "{:.prec$?}", - Duration::from_nanos(self.metadata.min_value), + Duration::from_nanos(metadata.min_value), prec = self.duration_precision, ); let y_axis_label_width = max_qty_label.len() as u16; - self.render_legend( + render_legend( inner_area, buf, + &metadata, max_record_label, min_record_label, max_qty_label, min_qty_label, ); - let legend_height = if self.metadata.high_outliers > 0 { - 2 - } else { - 1 - }; + let legend_height = if metadata.high_outliers > 0 { 2 } else { 1 }; // Shrink the bars area by 1 row from the bottom // and `y_axis_label_width` columns from the left. @@ -112,74 +120,23 @@ impl<'a> Widget for MiniHistogram<'a> { width: inner_area.width - y_axis_label_width, height: inner_area.height - legend_height, }; - self.render_bars(bars_area, buf); + self.render_bars(bars_area, buf, data); } } impl<'a> MiniHistogram<'a> { - fn render_legend( + fn render_bars( &mut self, area: tui::layout::Rect, buf: &mut tui::buffer::Buffer, - max_record_label: String, - min_record_label: String, - max_qty_label: String, - min_qty_label: String, + data: Vec, ) { - // If there are outliers, display a note - let labels_pos = if self.metadata.high_outliers > 0 { - let outliers = format!( - "{} outliers (highest: {:?})", - self.metadata.high_outliers, - self.metadata - .highest_outlier - .expect("if there are outliers, the highest should be set") - ); - buf.set_string( - area.right() - outliers.len() as u16, - area.bottom() - 1, - &outliers, - Style::default(), - ); - 2 - } else { - 1 - }; - - // top left: max quantity - buf.set_string(area.left(), area.top(), &max_qty_label, Style::default()); - // bottom left: 0 aligned to right - let zero_label = format!("{:>width$}", &min_qty_label, width = max_qty_label.len()); - buf.set_string( - area.left(), - area.bottom() - labels_pos, - &zero_label, - Style::default(), - ); - // bottom left below the chart: min time - buf.set_string( - area.left() + max_qty_label.len() as u16, - area.bottom() - labels_pos, - &min_record_label, - Style::default(), - ); - // bottom right: max time - buf.set_string( - area.right() - max_record_label.len() as u16, - area.bottom() - labels_pos, - &max_record_label, - Style::default(), - ); - } - - fn render_bars(&mut self, area: tui::layout::Rect, buf: &mut tui::buffer::Buffer) { let max = match self.max { Some(v) => v, - None => *self.data.iter().max().unwrap_or(&1u64), + None => *data.iter().max().unwrap_or(&1u64), }; - let max_index = std::cmp::min(area.width as usize, self.data.len()); - let mut data = self - .data + let max_index = std::cmp::min(area.width as usize, data.len()); + let mut data = data .iter() .take(max_index) .map(|e| { @@ -244,15 +201,11 @@ impl<'a> MiniHistogram<'a> { self } - #[allow(dead_code)] - pub fn data(mut self, data: &'a [u64]) -> MiniHistogram<'a> { - self.data = data; - self - } - - #[allow(dead_code)] - pub fn metadata(mut self, metadata: HistogramMetadata) -> MiniHistogram<'a> { - self.metadata = metadata; + pub(crate) fn histogram( + mut self, + histogram: Option<&'a DurationHistogram>, + ) -> MiniHistogram<'a> { + self.histogram = histogram; self } @@ -268,3 +221,106 @@ impl<'a> MiniHistogram<'a> { self } } + +fn render_legend( + area: tui::layout::Rect, + buf: &mut tui::buffer::Buffer, + metadata: &HistogramMetadata, + max_record_label: String, + min_record_label: String, + max_qty_label: String, + min_qty_label: String, +) { + // If there are outliers, display a note + let labels_pos = if metadata.high_outliers > 0 { + let outliers = format!( + "{} outliers (highest: {:?})", + metadata.high_outliers, + metadata + .highest_outlier + .expect("if there are outliers, the highest should be set") + ); + buf.set_string( + area.right() - outliers.len() as u16, + area.bottom() - 1, + &outliers, + Style::default(), + ); + 2 + } else { + 1 + }; + + // top left: max quantity + buf.set_string(area.left(), area.top(), &max_qty_label, Style::default()); + // bottom left: 0 aligned to right + let zero_label = format!("{:>width$}", &min_qty_label, width = max_qty_label.len()); + buf.set_string( + area.left(), + area.bottom() - labels_pos, + &zero_label, + Style::default(), + ); + // bottom left below the chart: min time + buf.set_string( + area.left() + max_qty_label.len() as u16, + area.bottom() - labels_pos, + &min_record_label, + Style::default(), + ); + // bottom right: max time + buf.set_string( + area.right() - max_record_label.len() as u16, + area.bottom() - labels_pos, + &max_record_label, + Style::default(), + ); +} + +/// From the histogram, build a visual representation by trying to make as +/// many buckets as the width of the render area. +fn chart_data(histogram: &DurationHistogram, width: u16) -> (Vec, HistogramMetadata) { + let &DurationHistogram { + ref histogram, + high_outliers, + highest_outlier, + .. + } = histogram; + + let step_size = ((histogram.max() - histogram.min()) as f64 / width as f64).ceil() as u64 + 1; + // `iter_linear` panics if step_size is 0 + let data = if step_size > 0 { + let mut found_first_nonzero = false; + let data: Vec = histogram + .iter_linear(step_size) + .filter_map(|value| { + let count = value.count_since_last_iteration(); + // Remove the 0s from the leading side of the buckets. + // Because HdrHistogram can return empty buckets depending + // on its internal state, as it approximates values. + if count == 0 && !found_first_nonzero { + None + } else { + found_first_nonzero = true; + Some(count) + } + }) + .collect(); + data + } else { + Vec::new() + }; + let max_bucket = data.iter().max().copied().unwrap_or_default(); + let min_bucket = data.iter().min().copied().unwrap_or_default(); + ( + data, + HistogramMetadata { + max_value: histogram.max(), + min_value: histogram.min(), + max_bucket, + min_bucket, + high_outliers, + highest_outlier, + }, + ) +} diff --git a/tokio-console/src/view/mod.rs b/tokio-console/src/view/mod.rs index 9f50d5da7..3d35350b8 100644 --- a/tokio-console/src/view/mod.rs +++ b/tokio-console/src/view/mod.rs @@ -8,7 +8,9 @@ use tui::{ }; mod async_ops; +mod durations; mod mini_histogram; +mod percentiles; mod resource; mod resources; mod styles; @@ -18,11 +20,14 @@ mod tasks; pub(crate) use self::styles::{Palette, Styles}; pub(crate) use self::table::SortBy; -const DUR_LEN: usize = 6; // This data is only updated every second, so it doesn't make a ton of // sense to have a lot of precision in timestamps (and this makes sure // there's room for the unit!) +const DUR_LEN: usize = 6; +// Precision (after decimal point) for durations displayed in a list +// (detail view) const DUR_LIST_PRECISION: usize = 2; +// Precision (after decimal point) for durations displayed in a table const DUR_TABLE_PRECISION: usize = 0; const TABLE_HIGHLIGHT_SYMBOL: &str = ">> "; diff --git a/tokio-console/src/view/percentiles.rs b/tokio-console/src/view/percentiles.rs new file mode 100644 index 000000000..6f8ba4666 --- /dev/null +++ b/tokio-console/src/view/percentiles.rs @@ -0,0 +1,83 @@ +use std::time::Duration; + +use tui::{ + text::{Spans, Text}, + widgets::{Paragraph, Widget}, +}; + +use crate::{ + state::histogram::DurationHistogram, + view::{self, bold}, +}; + +/// This is a tui-rs widget to display duration percentiles in a list form. +/// It wraps the [`Paragraph`] widget. +pub(crate) struct Percentiles<'a> { + /// Widget style + styles: &'a view::Styles, + /// The histogram data to render + histogram: Option<&'a DurationHistogram>, + /// The title of the paragraph + title: &'a str, +} + +impl<'a> Widget for Percentiles<'a> { + fn render(self, area: tui::layout::Rect, buf: &mut tui::buffer::Buffer) { + let inner = Paragraph::new(self.make_percentiles_inner()) + .block(self.styles.border_block().title(self.title)); + + inner.render(area, buf) + } +} + +impl<'a> Percentiles<'a> { + pub(crate) fn new(styles: &'a view::Styles) -> Self { + Self { + styles, + histogram: None, + title: "Percentiles", + } + } + + pub(crate) fn make_percentiles_inner(&self) -> Text<'static> { + let mut text = Text::default(); + let histogram = match self.histogram { + Some(DurationHistogram { histogram, .. }) => histogram, + _ => return text, + }; + + // Get the important percentile values from the histogram + let pairs = [10f64, 25f64, 50f64, 75f64, 90f64, 95f64, 99f64] + .iter() + .map(move |i| (*i, histogram.value_at_percentile(*i))); + let percentiles = pairs.map(|pair| { + Spans::from(vec![ + bold(format!("p{:>2}: ", pair.0)), + self.styles.time_units( + Duration::from_nanos(pair.1), + view::DUR_LIST_PRECISION, + None, + ), + ]) + }); + + text.extend(percentiles); + text + } + + #[allow(dead_code)] + pub(crate) fn styles(mut self, styles: &'a view::Styles) -> Percentiles<'a> { + self.styles = styles; + self + } + + pub(crate) fn histogram(mut self, histogram: Option<&'a DurationHistogram>) -> Percentiles<'a> { + self.histogram = histogram; + self + } + + pub(crate) fn title(mut self, title: &'a str) -> Percentiles<'a> { + self.title = title; + self + } +} diff --git a/tokio-console/src/view/task.rs b/tokio-console/src/view/task.rs index b8febbcab..93edd13b7 100644 --- a/tokio-console/src/view/task.rs +++ b/tokio-console/src/view/task.rs @@ -1,16 +1,8 @@ use crate::{ input, - state::{ - histogram::DurationHistogram, - tasks::{Details, Task}, - DetailsRef, - }, + state::{tasks::Task, DetailsRef}, util::Percentage, - view::{ - self, bold, - mini_histogram::{HistogramMetadata, MiniHistogram}, - DUR_LIST_PRECISION, - }, + view::{self, bold, durations::Durations}, }; use std::{ cell::RefCell, @@ -121,26 +113,6 @@ impl TaskView { ) .split(stats_area); - // Only split the histogram area in half if we're also drawing a - // sparkline (which requires UTF-8 characters). - let poll_dur_area = if styles.utf8 { - Layout::default() - .direction(layout::Direction::Horizontal) - .constraints( - [ - // 24 chars is long enough for the title "Poll Times Percentiles" - layout::Constraint::Length(24), - layout::Constraint::Min(50), - ] - .as_ref(), - ) - .split(poll_dur_area) - } else { - vec![poll_dur_area] - }; - - let percentiles_area = poll_dur_area[0]; - let controls = Spans::from(vec![ Span::raw("controls: "), bold(styles.if_utf8("\u{238B} esc", "esc")), @@ -177,12 +149,15 @@ impl TaskView { let percent = amt.as_secs_f64().percent_of(total.as_secs_f64()); Spans::from(vec![ bold(name), - dur(styles, amt), + styles.time_units(amt, view::DUR_LIST_PRECISION, None), Span::from(format!(" ({:.2}%)", percent)), ]) }; - overview.push(Spans::from(vec![bold("Total Time: "), dur(styles, total)])); + overview.push(Spans::from(vec![ + bold("Total Time: "), + styles.time_units(total, view::DUR_LIST_PRECISION, None), + ])); overview.push(dur_percent("Busy: ", task.busy(now))); overview.push(dur_percent("Idle: ", task.idle(now))); @@ -224,30 +199,6 @@ impl TaskView { let mut fields = Text::default(); fields.extend(task.formatted_fields().iter().cloned().map(Spans::from)); - // If UTF-8 is disabled we can't draw the histogram sparklne. - if styles.utf8 { - let sparkline_area = poll_dur_area[1]; - - // Bit of a deadlock: We cannot know the highest bucket value without determining the number of buckets, - // and we cannot determine the number of buckets without knowing the width of the chart area which depends on - // the number of digits in the highest bucket value. - // So just assume here the number of digits in the highest bucket value is 3. - // If we overshoot, there will be empty columns/buckets at the right end of the chart. - // If we undershoot, the rightmost 1-2 columns/buckets will be hidden. - // We could get the max bucket value from the previous render though... - let (chart_data, metadata) = details - .map(|d| d.make_chart_data(sparkline_area.width - 3)) - .unwrap_or_default(); - - let histogram_sparkline = MiniHistogram::default() - .block(styles.border_block().title("Poll Times Histogram")) - .data(&chart_data) - .metadata(metadata) - .duration_precision(2); - - frame.render_widget(histogram_sparkline, sparkline_area); - } - if let Some(warnings_area) = warnings_area { let warnings = List::new(warnings).block(styles.border_block().title("Warnings")); frame.render_widget(warnings, warnings_area); @@ -255,102 +206,16 @@ impl TaskView { let task_widget = Paragraph::new(overview).block(styles.border_block().title("Task")); let wakers_widget = Paragraph::new(waker_stats).block(styles.border_block().title("Waker")); + let poll_durations_widget = Durations::new(styles) + .histogram(details.and_then(|d| d.poll_times_histogram())) + .percentiles_title("Poll Times Percentiles") + .histogram_title("Poll Times Histogram"); let fields_widget = Paragraph::new(fields).block(styles.border_block().title("Fields")); - let percentiles_widget = Paragraph::new( - details - .map(|details| details.make_percentiles_widget(styles)) - .unwrap_or_default(), - ) - .block(styles.border_block().title("Poll Times Percentiles")); frame.render_widget(Block::default().title(controls), controls_area); frame.render_widget(task_widget, stats_area[0]); frame.render_widget(wakers_widget, stats_area[1]); + frame.render_widget(poll_durations_widget, poll_dur_area); frame.render_widget(fields_widget, fields_area); - frame.render_widget(percentiles_widget, percentiles_area); } } - -impl Details { - /// From the histogram, build a visual representation by trying to make as - // many buckets as the width of the render area. - fn make_chart_data(&self, width: u16) -> (Vec, HistogramMetadata) { - self.poll_times_histogram() - .map( - |&DurationHistogram { - ref histogram, - high_outliers, - highest_outlier, - .. - }| { - let step_size = ((histogram.max() - histogram.min()) as f64 / width as f64) - .ceil() as u64 - + 1; - // `iter_linear` panics if step_size is 0 - let data = if step_size > 0 { - let mut found_first_nonzero = false; - let data: Vec = histogram - .iter_linear(step_size) - .filter_map(|value| { - let count = value.count_since_last_iteration(); - // Remove the 0s from the leading side of the buckets. - // Because HdrHistogram can return empty buckets depending - // on its internal state, as it approximates values. - if count == 0 && !found_first_nonzero { - None - } else { - found_first_nonzero = true; - Some(count) - } - }) - .collect(); - data - } else { - Vec::new() - }; - let max_bucket = data.iter().max().copied().unwrap_or_default(); - let min_bucket = data.iter().min().copied().unwrap_or_default(); - ( - data, - HistogramMetadata { - max_value: histogram.max(), - min_value: histogram.min(), - max_bucket, - min_bucket, - high_outliers, - highest_outlier, - }, - ) - }, - ) - .unwrap_or_default() - } - - /// Get the important percentile values from the histogram - fn make_percentiles_widget(&self, styles: &view::Styles) -> Text<'static> { - let mut text = Text::default(); - let histogram = self.poll_times_histogram(); - let percentiles = histogram - .iter() - .flat_map(|&DurationHistogram { histogram, .. }| { - let pairs = [10f64, 25f64, 50f64, 75f64, 90f64, 95f64, 99f64] - .iter() - .map(move |i| (*i, histogram.value_at_percentile(*i))); - pairs.map(|pair| { - Spans::from(vec![ - bold(format!("p{:>2}: ", pair.0)), - dur(styles, Duration::from_nanos(pair.1)), - ]) - }) - }); - text.extend(percentiles); - text - } -} - -fn dur(styles: &view::Styles, dur: std::time::Duration) -> Span<'static> { - // TODO(eliza): can we not have to use `format!` to make a string here? is - // there a way to just give TUI a `fmt::Debug` implementation, or does it - // have to be given a string in order to do layout stuff? - styles.time_units(dur, DUR_LIST_PRECISION, None) -}