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..d0c01f7f1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -35,16 +35,12 @@ jobs: uses: actions/checkout@v3 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: stable - override: true - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - name: Run cargo check - uses: actions-rs/cargo@v1 - with: - command: check + run: cargo check test_os: name: Tests on ${{ matrix.os }} with Rust ${{ matrix.rust }} @@ -55,9 +51,8 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] rust: [stable] include: - # Make 1.58 MSRV due to <=1.57 failing to build console-api due to a bug - # with cargo version resolution. - - rust: 1.58.0 + # Make 1.60 MSRV, as it's Tonic 0.9's MSRV. + - rust: 1.60.0 os: ubuntu-latest # Try to build on the latest nightly. This job is allowed to fail, but # it's useful to help catch bugs in upcoming Rust versions. @@ -68,30 +63,24 @@ jobs: uses: actions/checkout@v3 - name: Install ${{ matrix.rust }} toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: ${{ matrix.rust }} - override: true - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - - name: Run cargo test (API) - uses: actions-rs/cargo@v1 + - name: Install Protoc + uses: arduino/setup-protoc@v1 with: - command: test - args: -p console-api + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Run cargo test (API) + run: cargo test -p console-api - name: Run cargo test (subscriber) - uses: actions-rs/cargo@v1 - with: - command: test - args: -p console-subscriber + run: cargo test -p console-subscriber - name: Run cargo test (console) - uses: actions-rs/cargo@v1 - with: - command: test - args: -p tokio-console --locked + run: cargo test -p tokio-console --locked lints: name: Lints @@ -101,22 +90,14 @@ jobs: uses: actions/checkout@v3 - name: Install stable toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@master with: - profile: minimal toolchain: stable - override: true components: rustfmt, clippy - - uses: Swatinem/rust-cache@v1 + - uses: Swatinem/rust-cache@v2 - name: Run cargo fmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + run: cargo fmt --all -- --check - name: Run cargo clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: -- -D warnings + run: cargo clippy -- -D warnings diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0d6bbfeac..458785021 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -20,7 +20,6 @@ jobs: prefix: "(console-subscriber)|(console-api)|(tokio-console)" changelog: "$prefix/CHANGELOG.md" title: "$prefix $version" - branch: main draft: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -32,12 +31,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 }} 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/Cargo.lock b/Cargo.lock index cc901b32e..27d4c3088 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,36 +41,15 @@ version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" -[[package]] -name = "async-stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-trait" -version = "0.1.53" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] @@ -92,9 +71,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.5.0" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5611d4977882c5af1c0f7a34d51b5d87f784f86912bb543986b014ea4995ef93" +checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" dependencies = [ "async-trait", "axum-core", @@ -110,20 +89,19 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", + "rustversion", "serde", "sync_wrapper", - "tokio", "tower", - "tower-http", "tower-layer", "tower-service", ] [[package]] name = "axum-core" -version = "0.2.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95cd109b3e93c9541dcce5b0219dcf89169dcc58c1bebed65082808324258afb" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", "bytes", @@ -131,6 +109,9 @@ dependencies = [ "http", "http-body", "mime", + "rustversion", + "tower-layer", + "tower-service", ] [[package]] @@ -154,6 +135,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "bitflags" version = "1.3.2" @@ -192,16 +179,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "3.1.18" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", "bitflags", "clap_derive", "clap_lex", "indexmap", - "lazy_static", + "once_cell", "strsim", "termcolor", "textwrap", @@ -218,40 +205,31 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.1.18" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.90", ] [[package]] name = "clap_lex" -version = "0.2.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 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" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f1885697ee8a177096d42f158922251a41973117f6d8a234cee94b9509157b7" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" dependencies = [ "backtrace", "color-spantrace", @@ -265,9 +243,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", @@ -277,9 +255,10 @@ dependencies = [ [[package]] name = "console-api" -version = "0.3.0" +version = "0.5.0" dependencies = [ "prost", + "prost-build", "prost-types", "tonic", "tonic-build", @@ -288,7 +267,7 @@ dependencies = [ [[package]] name = "console-subscriber" -version = "0.1.6" +version = "0.1.10" dependencies = [ "console-api", "crossbeam-channel", @@ -296,7 +275,7 @@ dependencies = [ "futures", "hdrhistogram", "humantime", - "parking_lot 0.11.2", + "parking_lot", "prost-types", "serde", "serde_json", @@ -306,7 +285,7 @@ dependencies = [ "tonic", "tracing", "tracing-core", - "tracing-subscriber 0.3.11", + "tracing-subscriber", ] [[package]] @@ -340,16 +319,16 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.20.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" dependencies = [ "bitflags", "crossterm_winapi", "futures-core", "libc", - "mio 0.7.14", - "parking_lot 0.11.2", + "mio", + "parking_lot", "signal-hook", "signal-hook-mio", "winapi", @@ -357,9 +336,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" +checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" dependencies = [ "winapi", ] @@ -499,7 +478,7 @@ checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.90", ] [[package]] @@ -551,9 +530,9 @@ checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[package]] name = "h2" -version = "0.3.13" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" dependencies = [ "bytes", "fnv", @@ -580,7 +559,7 @@ version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" dependencies = [ - "base64", + "base64 0.13.0", "byteorder", "flate2", "nom", @@ -604,9 +583,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -615,26 +594,20 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", "pin-project-lite", ] -[[package]] -name = "http-range-header" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" - [[package]] name = "httparse" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -650,9 +623,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.18" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -731,9 +704,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "lazy_static" @@ -743,9 +716,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "lock_api" @@ -783,9 +756,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "matchit" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" [[package]] name = "memchr" @@ -817,38 +790,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" -dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", -] - -[[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", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "windows-sys 0.36.1", ] [[package]] @@ -867,15 +816,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", -] - [[package]] name = "num-traits" version = "0.2.14" @@ -906,9 +846,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.10.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "os_str_bytes" @@ -918,20 +858,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" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.5", -] +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking_lot" @@ -940,21 +869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ "lock_api", - "parking_lot_core 0.9.2", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -967,7 +882,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.34.0", ] [[package]] @@ -988,22 +903,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.90", ] [[package]] @@ -1031,7 +946,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b83ec2d0af5c5c556257ff52c9f98934e243b9fd39604bfb2a9b75ec2e97f18" dependencies = [ "proc-macro2", - "syn", + "syn 1.0.90", ] [[package]] @@ -1043,7 +958,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.90", "version_check", ] @@ -1060,18 +975,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "prost" -version = "0.10.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd5316aa8f5c82add416dfbc25116b84b748a21153f512917e8143640a71bbd" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", "prost-derive", @@ -1079,54 +994,53 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.10.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328f9f29b82409216decb172d81e936415d21245befa79cd34c3f29d87d1c50b" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", - "cfg-if", - "cmake", "heck", "itertools", "lazy_static", "log", "multimap", "petgraph", + "prettyplease", "prost", "prost-types", "regex", + "syn 1.0.90", "tempfile", "which", ] [[package]] name = "prost-derive" -version = "0.10.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df35198f0777b75e9ff669737c6da5136b59dba33cf5a010a6d1cc4d56defc6f" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.90", ] [[package]] name = "prost-types" -version = "0.10.0" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926681c118ae6e512a3ccefd4abbe5521a14f4cc1e207356d4d00c0b7f2006fd" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "bytes", "prost", ] [[package]] name = "quote" -version = "1.0.17" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -1161,6 +1075,19 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ratatui" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc0d032bccba900ee32151ec0265667535c230169f5a011154cdcd984e16829" +dependencies = [ + "bitflags", + "cassowary", + "crossterm", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -1222,6 +1149,12 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + [[package]] name = "ryu" version = "1.0.9" @@ -1251,7 +1184,7 @@ checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.90", ] [[package]] @@ -1291,7 +1224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" dependencies = [ "libc", - "mio 0.7.14", + "mio", "signal-hook", ] @@ -1318,9 +1251,9 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -1343,6 +1276,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "0.1.1" @@ -1374,9 +1318,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" @@ -1395,7 +1339,7 @@ checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.90", ] [[package]] @@ -1424,17 +1368,18 @@ 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", "num_cpus", "once_cell", - "parking_lot 0.12.0", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -1445,7 +1390,7 @@ dependencies = [ [[package]] name = "tokio-console" -version = "0.1.6" +version = "0.1.9" dependencies = [ "atty", "clap", @@ -1460,15 +1405,16 @@ dependencies = [ "humantime", "once_cell", "prost-types", + "ratatui", "regex", "serde", "tokio", "toml", "tonic", + "tower", "tracing", "tracing-journald", - "tracing-subscriber 0.3.11", - "tui", + "tracing-subscriber", ] [[package]] @@ -1489,7 +1435,7 @@ checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.90", ] [[package]] @@ -1528,14 +1474,13 @@ dependencies = [ [[package]] name = "tonic" -version = "0.7.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1a361140b1af3f548e0a5105126b3fc737542f6cd4947b66419c80be07db22" +checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ - "async-stream", "async-trait", "axum", - "base64", + "base64 0.21.0", "bytes", "futures-core", "futures-util", @@ -1547,35 +1492,32 @@ dependencies = [ "percent-encoding", "pin-project", "prost", - "prost-derive", "tokio", "tokio-stream", - "tokio-util", "tower", "tower-layer", "tower-service", "tracing", - "tracing-futures", ] [[package]] name = "tonic-build" -version = "0.7.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d17087af5c80e5d5fc8ba9878e60258065a0a757e35efe7a05b7904bece1943" +checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" dependencies = [ "prettyplease", "proc-macro2", "prost-build", "quote", - "syn", + "syn 1.0.90", ] [[package]] name = "tower" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", @@ -1591,30 +1533,11 @@ dependencies = [ "tracing", ] -[[package]] -name = "tower-http" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aba3f3efabf7fb41fae8534fc20a817013dd1c12cb45441efb6c82e6556b4cd8" -dependencies = [ - "bitflags", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-range-header", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - [[package]] name = "tower-layer" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" @@ -1643,7 +1566,7 @@ checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.90", ] [[package]] @@ -1658,22 +1581,12 @@ dependencies = [ [[package]] name = "tracing-error" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" -dependencies = [ - "tracing", - "tracing-subscriber 0.2.25", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" dependencies = [ - "pin-project", "tracing", + "tracing-subscriber", ] [[package]] @@ -1684,7 +1597,7 @@ checksum = "1ba49f4829f4e95702943ec6b2fad8936b369d20fa27036caf01329fb230e460" dependencies = [ "libc", "tracing-core", - "tracing-subscriber 0.3.11", + "tracing-subscriber", ] [[package]] @@ -1698,17 +1611,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" @@ -1718,7 +1620,7 @@ dependencies = [ "ansi_term", "lazy_static", "matchers", - "parking_lot 0.12.0", + "parking_lot", "regex", "sharded-slab", "smallvec", @@ -1734,25 +1636,18 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" -[[package]] -name = "tui" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23" -dependencies = [ - "bitflags", - "cassowary", - "crossterm", - "unicode-segmentation", - "unicode-width", -] - [[package]] name = "unicode-bidi" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + [[package]] name = "unicode-normalization" version = "0.1.19" @@ -1764,9 +1659,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" @@ -1874,11 +1769,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]] @@ -1887,26 +1795,65 @@ 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" +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/README.md b/README.md index 2d2ab4a8d..058487104 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 @@ -52,13 +53,13 @@ toolkit consists of multiple components: wow! whoa! it's like `top(1)` for tasks! -![task list view](https://user-images.githubusercontent.com/2796466/129774465-7bd2ad2f-f1a3-4830-a8fa-f72667028fa1.png) +![task list view](assets/readme/top-for-tasks.png) viewing details for a single task: -![task details view](https://user-images.githubusercontent.com/2796466/129774524-288c967b-6066-4f98-973d-099b3e6a2c55.png) +![task details view](assets/readme/task-details.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,61 +160,78 @@ 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: +The console command-line tool supports a number of additional flags to configure +its behavior. The `help` command will print a list of supported command-line +flags and arguments: -``` +```text USAGE: - tokio-console [FLAGS] [OPTIONS] [TARGET_ADDR] + tokio-console [OPTIONS] [TARGET_ADDR] [SUBCOMMAND] ARGS: The address of a console-enabled process to connect to. - This may be an IP address and port, or a DNS name. [default: http://127.0.0.1:6669] - -FLAGS: - --ascii-only - Explicitly use only ASCII characters - - -h, --help - Print help information - - --no-colors - Disable ANSI colors entirely + This may be an IP address and port, or a DNS name. - --no-duration-colors - Disable color-coding for duration units + 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`. - --no-terminated-colors - Disable color-coding for terminated tasks - - -V, --version - Print version information + [default: http://127.0.0.1:6669] OPTIONS: + --ascii-only + Explicitly use only ASCII characters + --colorterm Overrides the value of the `COLORTERM` environment variable. If this is set to `24bit` or `truecolor`, 24-bit RGB color support will be enabled. - [env: COLORTERM=truecolor] [possible values: 24bit, truecolor] + + [env: COLORTERM=truecolor] + [possible values: 24bit, truecolor] + + -h, --help + Print help information --lang - Overrides the terminal's default language [env: LANG=en_US.UTF-8] [default: en_us.UTF-8] + Overrides the terminal's default language + + [env: LANG=] --log Log level filter for the console's internal diagnostics. - The console will log to stderr if a log level filter is provided. Since the console - application runs interactively, stderr should generally be redirected to a file to avoid - interfering with the console's text output. [env: RUST_LOG=] [default: off] + Logs are written to a new file at the path given by the `--log-dir` argument (or its + default value), or to the system journal if `systemd-journald` support is enabled. + + If this is set to 'off' or is not set, no logs will be written. + + [default: off] + + [env: RUST_LOG=] + + --log-dir + Path to a directory to write the console's internal logs to. + + [default: /tmp/tokio-console/logs] + + --no-colors + Disable ANSI colors entirely + + --no-duration-colors + Disable color-coding for duration units + + --no-terminated-colors + Disable color-coding for terminated tasks --palette - Explicitly set which color palette to use [possible values: 8, 16, 256, all, off] + Explicitly set which color palette to use + + [possible values: 8, 16, 256, all, off] --retain-for How long to continue displaying completed tasks and dropped resources after they have @@ -237,10 +260,24 @@ OPTIONS: * `months`, `month`, `M` -- defined as 30.44 days - * `years`, `year`, `y` -- defined as 365.25 days [default: 6s] + * `years`, `year`, `y` -- defined as 365.25 days + + [default: 6s] + + -V, --version + Print version information + +SUBCOMMANDS: + gen-completion + Generate shell completions + gen-config + Generate a `console.toml` config file with the default configuration values, overridden + by any provided command-line arguments + help + Print this message or the help of the given subcommand(s) ``` -## for development: +## for development the `console-subscriber/examples` directory contains **some potentially useful tools**: @@ -264,7 +301,7 @@ cargo run --example $name [`Layer`]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/trait.Layer.html [`tracing` targets]: https://docs.rs/tracing/latest/tracing/struct.Metadata.html [`TRACE` level]: https://docs.rs/tracing/latest/tracing/struct.Level.html#associatedconstant.TRACE -[builder]: https://docs.rs/console-subscriber/latest/console_subscriber/struct.builder.html +[builder]: https://docs.rs/console-subscriber/latest/console_subscriber/struct.Builder.html [init]: https://docs.rs/console-subscriber/latest/console_subscriber/fn.init.html [`EnvFilter`]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html [`Targets`]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/targets/struct.Targets.html 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. diff --git a/assets/readme/task-details.png b/assets/readme/task-details.png new file mode 100644 index 000000000..6a527b345 Binary files /dev/null and b/assets/readme/task-details.png differ diff --git a/assets/readme/top-for-tasks.png b/assets/readme/top-for-tasks.png new file mode 100644 index 000000000..4d6c56b8e Binary files /dev/null and b/assets/readme/top-for-tasks.png differ diff --git a/assets/tokio-console-0.1.8/resource_details_semaphore.png b/assets/tokio-console-0.1.8/resource_details_semaphore.png new file mode 100644 index 000000000..7337b7d46 Binary files /dev/null and b/assets/tokio-console-0.1.8/resource_details_semaphore.png differ diff --git a/assets/tokio-console-0.1.8/resource_details_sleep.png b/assets/tokio-console-0.1.8/resource_details_sleep.png new file mode 100644 index 000000000..3458120dc Binary files /dev/null and b/assets/tokio-console-0.1.8/resource_details_sleep.png differ diff --git a/assets/tokio-console-0.1.8/resources_list.png b/assets/tokio-console-0.1.8/resources_list.png new file mode 100644 index 000000000..02d4409b1 Binary files /dev/null and b/assets/tokio-console-0.1.8/resources_list.png differ diff --git a/assets/tokio-console-0.1.8/task_details.png b/assets/tokio-console-0.1.8/task_details.png new file mode 100644 index 000000000..a2c8c7bdc Binary files /dev/null and b/assets/tokio-console-0.1.8/task_details.png differ diff --git a/assets/tokio-console-0.1.8/tasks_list.png b/assets/tokio-console-0.1.8/tasks_list.png new file mode 100644 index 000000000..6c226e71e Binary files /dev/null and b/assets/tokio-console-0.1.8/tasks_list.png differ diff --git a/bin/README.md b/bin/README.md new file mode 100644 index 000000000..3a4d21ac6 --- /dev/null +++ b/bin/README.md @@ -0,0 +1,42 @@ +# tokio-console dev scripts + +This directory contains shell scripts useful for Tokio Console development. +Currently, all the scripts in this directory are related to publishing releases. + +- `release.sh`: Releases a new version of a Tokio Console crate. This includes + updating the crate's version in its `Cargo.toml`, updating the changelog, + running pre-release tests, creating a release tag, and publishing the crate to + crates.io. + + Invoked with the name of the crate to release, and the version of the new + release. For example: + + ```console + $ bin/release.sh tokio-console 0.1.9 + ``` + + The script will validate whether a new release can be published prior to + updating the changelog and crate version. Then, the script will display the + git diff for the generated release commit, and prompt the user to confirm + that it is correct prior to publishing the release. + + Releases should be published on the `main` branch. Note that this script + requires that the user is authenticated to publish releases of the crate in + question to crates.io. + +- `update-changelog.sh`: Updates the generated `CHANGELOG.md` for a given crate + and version, without committing the changelog update or publishing a tag. + + Invoked with the path to the crate to generate change notes for, and the name + that will be used for the new release's Git tag. For example: + + ```console + $ bin/update-changelog.sh tokio-console tokio-console-v0.1.9 + ``` + + The `release.sh` script will run this script automatically as part of the + release process. However, it can also be invoked separately to just update the + changelog. + +- `_util.sh`: Contains utilities used by other shell scripts. This script is not + run directly. diff --git a/bin/_util.sh b/bin/_util.sh new file mode 100644 index 000000000..a2d499bb9 --- /dev/null +++ b/bin/_util.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# utility functions used in other shell scripts. +# +# currently, this includes: +# - cargo-style stderr logging (`err`, `note`, and `status` functions) +# - confirmation prompts (`confirm` function) +set -euo pipefail + +# Log an error to stderr +# +# Args: +# $1: message to log +err() { + echo -e "\e[31m\e[1merror:\e[0m" "$@" 1>&2; +} + +# Log a note to stderr +# +# Args: +# $1: message to log +note() { + echo -e "\e[31m\e[1mnote:\e[0m" "$@" 1>&2; +} + +# Log a cargo-style status message to stderr +# +# Args: +# $1: a "tag" for the log message (should be 12 characters or less in +# length) +# $2: message to log +status() { + local width=12 + local tag="$1" + local msg="$2" + printf "\e[32m\e[1m%${width}s\e[0m %s\n" "$tag" "$msg" +} + +# Prompt the user to confirm an action +# +# Args: +# $1: message to display to the user along with the `[y/N]` prompt +# +# Returns: +# 0 if the user confirmed, 1 otherwise +confirm() { + while read -r -p "$1 [Y/n] " input + do + case "$input" in + [yY][eE][sS]|[yY]) + return 0 + ;; + [nN][oO]|[nN]) + return 1 + ;; + *) + err "invalid input $input" + ;; + esac + done +} + +# Returns the path to a Mycelium crate. +# +# Args: +# $1: crate name +# +# Returns: +# 0 if the crate exists, 0 if it does not exist. +crate_path() { + local crate="$1" + local mycoprefix='mycelium-'; + if [[ -d $crate ]]; then + echo "$crate" + elif [[ -d "${crate#"$mycoprefix"}" ]]; then + echo "${crate#"$mycoprefix"}" + else + err "unknown crate $crate" + return 1; + fi +} diff --git a/bin/release.sh b/bin/release.sh new file mode 100755 index 000000000..73a3721df --- /dev/null +++ b/bin/release.sh @@ -0,0 +1,266 @@ +#!/usr/bin/env bash +usage="Releases a tokio-console crate. + +USAGE: + $(basename "$0") [FLAGS] + +FLAGS: + -h, --help Show this help text and exit. + -v, --verbose Enable verbose output. + -d, --dry-run Do not change any files or commit a git tag." + +set -euo pipefail + +bindir=$( cd "${BASH_SOURCE[0]%/*}" && pwd ) +rootdir=$( cd "$bindir"/.. && pwd ) + +# shellcheck source=_util.sh +. "$bindir"/_util.sh + +cd "$rootdir" + +verify() { + status "Verifying" "if $crate v$version can be released" + + local branch + branch=$(git rev-parse --abbrev-ref HEAD) + if [[ "$branch" != "main" ]]; then + err "you are not on the 'main' branch" + if ! confirm " are you sure you want to release from the '$branch' branch?"; then + echo "okay, exiting" + exit 1 + fi + fi + + if ! cargo --list | grep -q "hack"; then + err "missing cargo-hack executable" + if confirm " install it?"; then + cargo install cargo-hack + else + echo "okay, exiting" + exit 1 + fi + fi + + status "Checking" "if $crate builds across feature combinations" + + local cargo_hack=(cargo hack -p "$crate" --feature-powerset --no-dev-deps) + + if [[ "$verbose" ]]; then + cargo_hack+=("$verbose" check) + else + cargo_hack+=(check --quiet) + fi + + "${cargo_hack[@]}" + local cargo_hack_status="$?" + + if [[ "$cargo_hack_status" != "0" ]] ; then + err "$crate did not build with all feature combinations (cargo hack exited with $cargo_hack_status)!" + exit 1 + fi + + + if git tag -l | grep -Fxq "$tag" ; then + err "git tag \`$tag\` already exists" + exit 1 + fi +} + +update_version() { + # check the current version of the crate + local curr_version + curr_version=$(cargo pkgid -p "$crate" | sed -n 's/.*#\(.*\)/\1/p') + if [[ "$curr_version" == "$version" ]]; then + err "crate $crate is already at version $version!" + if ! confirm " are you sure you want to release $version?"; then + echo "okay, exiting" + exit 0 + fi + else + status "Updating" "$crate from $curr_version to $version" + sed -i \ + "/\[package\]/,/\[.*dependencies\]/{s/^version = \"$curr_version\"/version = \"$version\"/}" \ + "$cargo_toml" + fi +} + +publish() { + status "Publishing" "$crate v$version" + cd "$path" + local cargo_package=(cargo package) + local cargo_publish=(cargo publish) + + if [[ "$verbose" ]]; then + cargo_package+=("$verbose") + cargo_publish+=("$verbose") + fi + + if [[ "$dry_run" ]]; then + cargo_publish+=("$dry_run") + fi + + "${cargo_package[@]}" + "${cargo_publish[@]}" + + status "Tagging" "$tag" + local git_tag=(git tag "$tag") + local git_push_tags=(git push --tags) + if [[ "$dry_run" ]]; then + echo "# " "${git_tag[@]}" + echo "# " "${git_push_tags[@]}" + else + "${git_tag[@]}" + "${git_push_tags[@]}" + fi +} + +update_changelog() { + # shellcheck source=update-changelog + . "$bindir"/update-changelog.sh + changelog_status="$?" + + if [[ $changelog_status -ne 0 ]]; then + err "failed to update changelog" + exit "$changelog_status" + fi +} + + +verbose='' +dry_run='' + +for arg in "$@" +do + case "$arg" in + -h|--help) + echo "$usage" + exit 0 + ;; + -v|--verbose) + verbose="--verbose" + ;; + -d|--dry-run) + dry_run="--dry-run" + ;; + -*) + err "unknown flag $arg" + echo "$usage" + exit 1 + ;; + *) # crate or version + if [[ -z "${crate+crate}" ]]; then + crate="$arg" + elif [[ -z "${version+version}" ]]; then + version="$arg" + else + err "unknown positional argument \"$arg\"" + echo "$usage" + exit 1 + fi + ;; + esac +done + +if [[ "$verbose" ]]; then + set -x +fi + +if [[ -z "${version+version}" ]]; then + err "no version specified!" + errexit=1 +fi + +if [[ "${crate+crate}" ]]; then + tag="$crate-v$version" +else + err "no crate specified!" + errexit=1 +fi + +if [[ "${errexit+errexit}" ]]; then + echo "$usage" + exit 1 +fi + +path=$(crate_path "$crate") + +cargo_toml="${path}/Cargo.toml" +changelog="${path}/CHANGELOG.md" + +files=("$cargo_toml" "$changelog") + +is_uncommitted='' +for file in "${files[@]}"; do + if ! git diff-index --quiet HEAD -- "$file"; then + err "would overwrite uncommitted changes to $file!" + is_uncommitted=1 + fi +done + +if [[ "$is_uncommitted" ]]; then + exit 1 +fi + +verify +update_version +update_changelog + +staged="$(git diff-index --cached --name-only HEAD --)" +if [[ "$staged" ]]; then + err "skipping commit, as it would include the following unrelated staged files:" + echo "$staged" + exit 1 +fi + +status "Ready" "to prepare release commit!" +echo "" + +git add "${files[@]}" +git diff --staged + +if [[ "$dry_run" ]]; then + git reset HEAD -- "${files[@]}" + git checkout HEAD -- "${files[@]}" +fi + +echo "" + +if confirm "commit and push?"; then + git_commit=(git commit -sS -m "chore($crate): prepare to release $crate $version") + + if [[ "$dry_run" ]]; then + + echo "" + echo "# " "${git_commit[@]}" + echo "# " "${git_push[@]}" + else + "${git_commit[@]}" + fi +else + echo "okay, exiting" + exit 1 +fi + +if confirm "publish the crate?"; then + + echo "" + publish +else + echo "okay, exiting" + exit 1 +fi + +cd "$rootdir" +git add "Cargo.lock" +git_push=(git push -u origin --force-with-lease) +git_amend=(git commit --amend --reuse-message HEAD) +if [[ "$dry_run" ]]; then + echo "" + echo "# git add Cargo.lock" + echo "# " "${git_amend[@]}" + echo "# " "${git_push[@]}" +else + "${git_amend[@]}" + "${git_push[@]}" +fi diff --git a/bin/update-changelog.sh b/bin/update-changelog.sh new file mode 100755 index 000000000..7cbef4699 --- /dev/null +++ b/bin/update-changelog.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +usage="Updates the changelog for a Tokio Console crate. + +USAGE: + $(basename "$0") [FLAGS] + +FLAGS: + -h, --help Show this help text and exit. + -v, --verbose Enable verbose output." + +set -euo pipefail + +bindir=$( cd "${BASH_SOURCE[0]%/*}" && pwd ) +rootdir=$( cd "$bindir"/.. && pwd ) + +# shellcheck source=_util.sh +. "$bindir"/_util.sh + +cd "$rootdir" + +verbose='' + +for arg in "$@" +do + case "$arg" in + -h|--help) + echo "$usage" + exit 0 + ;; + -v|--verbose) + verbose="--verbose" + ;; + -*) + err "unknown flag $arg" + echo "$usage" + exit 1 + ;; + *) # crate or version + if [[ -z "${path+path}" ]]; then + path="$arg" + elif [[ -z "${tag+tag}" ]]; then + tag="$arg" + else + err "unknown positional argument \"$arg\"" + echo "$usage" + exit 1 + fi + ;; + esac +done + +if [[ -z "${path+path}" ]]; then + err "no version specified!" + errexit=1 +fi + +if [[ -z "${tag+tag}" ]]; then + err "no tag specified!" + errexit=1 +fi + +if [[ "${errexit+errexit}" ]]; then + echo "$usage" + exit 1 +fi + +if ! [[ -x "$(command -v git-cliff)" ]]; then + err "missing git-cliff executable" + if confirm " install it?"; then + cargo install git-cliff + else + echo "okay, exiting" + exit 0 + fi +fi + +changelog_path="${path}/CHANGELOG.md" + +status "Updating" "$changelog_path for tag $tag" + +git_cliff=( + git-cliff + --include-path "${path}/**" + --output "$changelog_path" + --config cliff.toml + --tag "$tag" +) +if [[ "$verbose" ]]; then + git_cliff+=("$verbose") +fi + +export GIT_CLIFF__GIT__TAG_PATTERN="${path}-v[0-9]*" +"${git_cliff[@]}" \ No newline at end of file diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 000000000..57b7af176 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,113 @@ +# git-cliff ~ default configuration file +# https://git-cliff.org/docs/configuration +# +# Lines starting with "#" are comments. +# Configuration options are organized into tables and keys. +# See documentation for more information on available options. + +[changelog] +# changelog header +header = """ +# Changelog\n +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n +""" +# template for the changelog body +# https://tera.netlify.app/docs/#introduction +body = """ +{% if version %}\ + ## {{ version | trim_start_matches(pat="v") }} - ({{ timestamp | date(format="%Y-%m-%d") }}) +{% else %}\ + ## Unreleased +{% endif %}\ +{% if previous %}\ + {% if previous.commit_id %} + [{{ previous.commit_id | truncate(length=7, end="") }}](https://github.com/tokio-rs/console/commit/{{ previous.commit_id }})...\ + [{{ commit_id | truncate(length=7, end="") }}](https://github.com/tokio-rs/console/commit/{{ commit_id }}) + {% endif %}\ +{% endif %} +{% set has_breaking = false -%} +{% for commit in commits -%} + {% if commit.breaking -%} + {% set_global has_breaking = true -%} + {% endif -%} +{% endfor -%} +{% if has_breaking -%} + ### Breaking Changes + {% for commit in commits -%} + {% if commit.breaking -%} + - **{{ commit.message | upper_first }}** ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/tokio-rs/console/commit/{{ commit.id }}))
{{ commit.breaking_description }} + {% endif -%} + {% endfor -%} +{% endif -%} +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {% if commit.breaking %}[**breaking**](#{{ version }}-breaking) {% endif -%} {{ commit.message | upper_first }} (\ + [{{ commit.id | truncate(length=7, end="") }}](https://github.com/tokio-rs/console/commit/{{ commit.id }})\ + {% for link in commit.links -%} + , {{ link.text }}({{ link.href }})\ + {% endfor -%} + )\ + {% endfor %} +{% endfor %}\n +""" +# remove the leading and trailing whitespace from the template +trim = true +# changelog footer +footer = """ + +""" + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false +# regex for preprocessing the commit messages +commit_preprocessors = [ + { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/tokio-rs/console/issues/${2}))"}, # replace issue numbers +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Added" }, + { message = "^fix", group = "Fixed" }, + { message = "^doc", group = "Documented" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", skip = true }, + { message = "^refac", skip = true }, + { message = "^style", skip = true }, + { message = "^test", skip = true }, + { message = "^chore\\(release\\)", skip = true }, + { message = "[Pp]repare to release", skip = true }, + { message = "^chore", skip = true }, + { body = ".*security", group = "Security" }, + { body = ".*[dD]eprecate.*", group = "Deprecated" }, + # older non-conventional commits + { message = "^subscriber:", group = "Added" }, + { message = "^console:", group = "Added" }, + { message = "^api:", group = "Added" }, + { message = "^example:", group = "Documented" }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = false +# filter out the commits that are not matched by commit parsers +filter_commits = false +# regex for skipping tags +# skip_tags = "v0.1.0-beta.1" +# regex for ignoring tags +ignore_tags = "" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" +# limit the number of commits included in the changelog. +# limit_commits = 42 + +link_parsers = [ + { pattern = "[fF]ixes #(\\d+)", text = "fixes [#${1}]", href = "https://github.com/tokio-rs/console/issues/$1"}, + { pattern = "[cC]loses #(\\d+)", text = "closes [#${1}]", href = "https://github.com/tokio-rs/console/issues/$1"}, + { pattern = "[sS]ee #(\\d+)", text = "see [#${1}]", href = "https://github.com/tokio-rs/console/issues/$1"}, +] diff --git a/console-api/CHANGELOG.md b/console-api/CHANGELOG.md index aa964ba43..329d62788 100644 --- a/console-api/CHANGELOG.md +++ b/console-api/CHANGELOG.md @@ -1,45 +1,103 @@ - -## 0.3.0 (2022-05-23) +# Changelog +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -#### Features +## console-api-v0.5.0 - (2023-05-09) -* add optional histogram outlier details (#351) ([46115913](46115913)) +[2cb6ee5](https://github.com/tokio-rs/console/commit/2cb6ee5b813837324f5f9934a929ac928cfbb03f)...[e3c5656](https://github.com/tokio-rs/console/commit/e3c56561a062be123be460dd477f512a6a9ec3cd) -#### Breaking Changes +### Breaking Changes +- **Update `tonic` to v0.9 ([#420](https://github.com/tokio-rs/console/issues/420))** ([b70c1d8](https://github.com/tokio-rs/console/commit/b70c1d886d64fc43de6715f07ae49313f778e92b))
This is a breaking change for users of `console-api`, as it changes the +public `tonic` dependency to a semver-incompatible version. This breaks +compatibility with `tonic` 0.8. -* add optional histogram outlier details (#351) ([46115913](46115913)) +### Added +- Use tokio task ids in task views ([#403](https://github.com/tokio-rs/console/issues/403)) ([001fc49](https://github.com/tokio-rs/console/commit/001fc49f09ad78cc4ab50770cf4a677ae177103f)) +- Add scheduled time per task ([#406](https://github.com/tokio-rs/console/issues/406)) ([ac20daa](https://github.com/tokio-rs/console/commit/ac20daaf301f80e87002593813965d11d11371e4)) +- Add task scheduled times histogram ([#409](https://github.com/tokio-rs/console/issues/409)) ([3b37dda](https://github.com/tokio-rs/console/commit/3b37dda773f8cd237f6759d193fdc83a75ab7653)) +- [**breaking**](#console-api-v0.5.0-breaking) Update `tonic` to v0.9 ([#420](https://github.com/tokio-rs/console/issues/420)) ([b70c1d8](https://github.com/tokio-rs/console/commit/b70c1d886d64fc43de6715f07ae49313f778e92b)) +- Update MSRV to Rust 1.60.0 ([e3c5656](https://github.com/tokio-rs/console/commit/e3c56561a062be123be460dd477f512a6a9ec3cd)) - -## 0.2.0 (2022-04-08) +## console-api-v0.3.0 - (2022-05-23) +[0d6d7a9](https://github.com/tokio-rs/console/commit/0d6d7a9af3a8174ca624f4289c5877ad3ac4f227)...[5490d64](https://github.com/tokio-rs/console/commit/5490d64c098d6997f4327e7ec08d5136ece2a2e5) -#### Breaking Changes +### Breaking Changes +- **Add optional histogram outlier details ([#351](https://github.com/tokio-rs/console/issues/351))** ([4611591](https://github.com/tokio-rs/console/commit/46115913877051090abd36719161f306b68124c7))
This is a breaking change *to the Rust bindings* (the `console-api` +crate) due to changing a field from an `Option` to a protobuf `oneof` +(introducing a new enum type). This is **not** a breaking change to the +protobufs themselves --- the actual wire format change is +backwards-compatible, but the generated Rust code changes in a breaking +way. -* Update `tonic` to `0.7` (#318) ([83d8a870](83d8a870)) +### Added -#### Features +- [**breaking**](#console-api-v0.3.0-breaking) Add optional histogram outlier details ([#351](https://github.com/tokio-rs/console/issues/351)) ([4611591](https://github.com/tokio-rs/console/commit/46115913877051090abd36719161f306b68124c7)) -* Update `tonic` to `0.7` (#318) ([83d8a870](83d8a870)) +### Documented - -## 0.1.2 (2022-02-04) +- Update minimal Rust version ([#338](https://github.com/tokio-rs/console/issues/338)) ([ff3b6db](https://github.com/tokio-rs/console/commit/ff3b6db6fa06456a14992663e8ff7ba8c80c1cc1)) -#### Bug Fixes +## console-api-v0.2.0 - (2022-04-11) +[c7cab71](https://github.com/tokio-rs/console/commit/c7cab7112368682a8ccea8c4ec4a5ef99b88d567)...[0d6d7a9](https://github.com/tokio-rs/console/commit/0d6d7a9af3a8174ca624f4289c5877ad3ac4f227) -* fix accidental exhaustive matching on `metadata::Kind` (#271) - ([d9aafaa0](d9aafaa0), closes [#270](270)) +### Breaking Changes +- **Update `tonic` to `0.7` ([#318](https://github.com/tokio-rs/console/issues/318))** ([83d8a87](https://github.com/tokio-rs/console/commit/83d8a870bcc40be71bc23d0f45fc374899c636a8))
`console-api` is now no longer compatible with projects using `prost` +0.9 or `tonic` 0.7. These crates must be updated to use `console-api` +0.2. - -## 0.1.1 (2022-01-18) +### Added -#### Features +- [**breaking**](#console-api-v0.2.0-breaking) Update `tonic` to `0.7` ([#318](https://github.com/tokio-rs/console/issues/318)) ([83d8a87](https://github.com/tokio-rs/console/commit/83d8a870bcc40be71bc23d0f45fc374899c636a8)) -- add `From` for `Id` (#244) ([095b1ef](095b1ef)) +### Documented - -## 0.1.0 (2021-12-16) +- Reword comment on the tracing_core::metadata::Kind match ([#272](https://github.com/tokio-rs/console/issues/272)) ([1ac3b9f](https://github.com/tokio-rs/console/commit/1ac3b9f4558d8f4f1233aa40ffd87702c58cbfee)) -- Initial release! 🎉 +## console-api-v0.1.2 - (2022-02-04) + +[1fe0650](https://github.com/tokio-rs/console/commit/1fe06508604dcfff473455fe848e9ff2a5588f62)...[c7cab71](https://github.com/tokio-rs/console/commit/c7cab7112368682a8ccea8c4ec4a5ef99b88d567) + + +### Fixed + +- Fix accidental exhaustive matching on `metadata::Kind` ([#271](https://github.com/tokio-rs/console/issues/271)) ([d9aafaa](https://github.com/tokio-rs/console/commit/d9aafaa05549379cf02113faea90816de2235c16), fixes [#270](https://github.com/tokio-rs/console/issues/270)) + +## console-api-v0.1.1 - (2022-01-18) + +[5c041d7](https://github.com/tokio-rs/console/commit/5c041d7149684fbc2735058c386f85e02b5381fb)...[1fe0650](https://github.com/tokio-rs/console/commit/1fe06508604dcfff473455fe848e9ff2a5588f62) + + +### Documented + +- Post-release readme fixup ([#221](https://github.com/tokio-rs/console/issues/221)) ([28a4321](https://github.com/tokio-rs/console/commit/28a4321e0f555c3744194ec64dccc93e4fd194ce)) + +## console-api-v0.1.0 - (2021-12-16) + + +### Added + +- Add TUI app, simple top-style view ([#2](https://github.com/tokio-rs/console/issues/2)) ([c7f0b43](https://github.com/tokio-rs/console/commit/c7f0b43494e439331ea2ae0ba4fc4cea8ddff6e3)) +- Send structured fields on the wire ([#26](https://github.com/tokio-rs/console/issues/26)) ([38adbd9](https://github.com/tokio-rs/console/commit/38adbd97aefc53d06e509c7b33c98b4dcfa7a970), fixes [#6](https://github.com/tokio-rs/console/issues/6)) +- Populate `Metadata`'s `field names` ([#32](https://github.com/tokio-rs/console/issues/32)) ([e45fca0](https://github.com/tokio-rs/console/commit/e45fca08102cefec7494d28f80863990cfb24160)) +- Record and send poll times with HdrHistogram ([#47](https://github.com/tokio-rs/console/issues/47)) ([94e7834](https://github.com/tokio-rs/console/commit/94e7834db44c3b19c54ff16a22f1b0e6464be1a2), closes [#36](https://github.com/tokio-rs/console/issues/36)) +- Use sequential `u64` task IDs ([#75](https://github.com/tokio-rs/console/issues/75)) ([c2c486e](https://github.com/tokio-rs/console/commit/c2c486ee9c792453db81786490bff52a031be9e9)) +- Resource instrumentation ([#77](https://github.com/tokio-rs/console/issues/77)) ([f4a21ac](https://github.com/tokio-rs/console/commit/f4a21acb18935af8b256999e2380eb5fb7e17d72)) +- Use `Location` for tasks and resources ([#154](https://github.com/tokio-rs/console/issues/154)) ([08c5186](https://github.com/tokio-rs/console/commit/08c5186eb01f18f8e4018058d12817e4127dd7be)) +- Add resource detail view ([#188](https://github.com/tokio-rs/console/issues/188)) ([1aa9b59](https://github.com/tokio-rs/console/commit/1aa9b594f30e42098c6c6bbf41eb1d2b01dc0426)) +- Count dropped events due to buffer cap ([#211](https://github.com/tokio-rs/console/issues/211)) ([aa09600](https://github.com/tokio-rs/console/commit/aa09600b3bdc6591eafc9fe7b4507f7da2bca498)) + +### Documented + +- Console-api docs ([#197](https://github.com/tokio-rs/console/issues/197)) ([fdf8637](https://github.com/tokio-rs/console/commit/fdf8637f2671a95d84a4c9046a2ed411e08045ef)) +- Add a README and lib.rs docs ([#201](https://github.com/tokio-rs/console/issues/201)) ([5af6e07](https://github.com/tokio-rs/console/commit/5af6e07d6eb44b133dcd0d6deff6b99a806d9e79)) +- Add a README (and `lib.rs` docs) ([#202](https://github.com/tokio-rs/console/issues/202)) ([a79c505](https://github.com/tokio-rs/console/commit/a79c5056875a3593b4fd61d18e42c2aa6a08688c)) + +### Fixed + +- Make proto/ vendor-able ([#128](https://github.com/tokio-rs/console/issues/128)) ([81cd611](https://github.com/tokio-rs/console/commit/81cd61152755abfdfa2f00727d079e65006e8c55)) + + diff --git a/console-api/Cargo.toml b/console-api/Cargo.toml index 009ed725d..1623854fd 100644 --- a/console-api/Cargo.toml +++ b/console-api/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "console-api" -version = "0.3.0" +version = "0.5.0" license = "MIT" edition = "2021" -rust-version = "1.58.0" +rust-version = "1.60.0" authors = ["Eliza Weisman ", "Tokio Contributors ",] readme = "README.md" repository = "https://github.com/tokio-rs/console/" @@ -26,22 +26,25 @@ keywords = [ [features] # Generate code that is compatible with Tonic's `transport` module. -transport = ["tonic-build/transport", "tonic/transport"] +transport = ["tonic/transport"] [dependencies] -tonic = { version = "0.7", default-features = false, features = [ +tonic = { version = "0.9", 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" +futures-core = "0.3" [dev-dependencies] -tonic-build = { version = "0.7", default-features = false, features = [ +tonic-build = { version = "0.9", 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/README.md b/console-api/README.md index bfc48fb17..74b7de647 100644 --- a/console-api/README.md +++ b/console-api/README.md @@ -37,7 +37,7 @@ system consists of two primary components: * _instrumentation_, embedded in the application, which collects data from the async runtime and exposes it over the console's wire format * _consumers_, such as the [`tokio-console`] command-line application, which - connect to the instrumented application, recieve telemetry data, and display + connect to the instrumented application, receive telemetry data, and display it to the user The wire format [protobuf] bindings in this crate are used by both the diff --git a/console-api/proto/common.proto b/console-api/proto/common.proto index 5e4a8ec86..f36785c38 100644 --- a/console-api/proto/common.proto +++ b/console-api/proto/common.proto @@ -177,10 +177,13 @@ message PollStats { // its poll method has completed. optional google.protobuf.Timestamp last_poll_ended = 5; // The total duration this object was being *actively polled*, summed across - // all polls. Note that this includes only polls that have completed and is - // not reflecting any inprogress polls. Subtracting `busy_time` from the + // all polls. + // + // Note that this includes only polls that have completed, and does not + // reflect any in-progress polls. Subtracting `busy_time` from the // total lifetime of the polled object results in the amount of time it - // has spent *waiting* to be polled. + // has spent *waiting* to be polled (including the `scheduled_time` value + // from `TaskStats`, if this is a task). google.protobuf.Duration busy_time = 6; } diff --git a/console-api/proto/tasks.proto b/console-api/proto/tasks.proto index 8c00c3d1c..f676629de 100644 --- a/console-api/proto/tasks.proto +++ b/console-api/proto/tasks.proto @@ -59,6 +59,12 @@ message TaskDetails { // A histogram plus additional data. DurationHistogram histogram = 4; } + + // A histogram of task scheduled durations. + // + // The scheduled duration is the time a task spends between being + // woken and when it is next polled. + DurationHistogram scheduled_times_histogram = 5; } // Data recorded when a new task is spawned. @@ -130,6 +136,16 @@ message Stats { common.PollStats poll_stats = 7; // The total number of times this task has woken itself. uint64 self_wakes = 8; + // The total duration this task was scheduled prior to being polled, summed + // across all poll cycles. + // + // Note that this includes only polls that have started, and does not + // reflect any scheduled state where the task hasn't yet been polled. + // Subtracting both `busy_time` (from the task's `PollStats`) and + // `scheduled_time` from the total lifetime of the task results in the + // amount of time it spent unable to progress because it was waiting on + // some resource. + google.protobuf.Duration scheduled_time = 9; } 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..732479f49 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 { @@ -205,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/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.async_ops.rs b/console-api/src/generated/rs.tokio.console.async_ops.rs index fb2613540..bbdf909ca 100644 --- a/console-api/src/generated/rs.tokio.console.async_ops.rs +++ b/console-api/src/generated/rs.tokio.console.async_ops.rs @@ -2,16 +2,17 @@ /// /// This includes a list of any new async ops, and updates to the associated statistics /// for any async ops that have changed since the last update. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AsyncOpUpdate { /// A list of new async operations that were created since the last `AsyncOpUpdate` /// was sent. Note that the fact that an async operation has been created /// does not mean that is has been polled or is being polled. This information /// is reflected in the `Stats` of the operation. - #[prost(message, repeated, tag="1")] + #[prost(message, repeated, tag = "1")] pub new_async_ops: ::prost::alloc::vec::Vec, /// Any async op stats that have changed since the last update. - #[prost(map="uint64, message", tag="2")] + #[prost(map = "uint64, message", tag = "2")] pub stats_update: ::std::collections::HashMap, /// A count of how many async op events (e.g. polls, creation, etc) were not /// recorded because the application's event buffer was at capacity. @@ -23,7 +24,7 @@ pub struct AsyncOpUpdate { /// /// If the application's instrumentation ensures reliable delivery of events, /// this will always be 0. - #[prost(uint64, tag="3")] + #[prost(uint64, tag = "3")] pub dropped_events: u64, } /// An async operation. @@ -31,24 +32,25 @@ pub struct AsyncOpUpdate { /// An async operation is an operation that is associated with a resource /// This could, for example, be a a read or write on a TCP stream, or a receive operation on /// a channel. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AsyncOp { /// The async op's ID. /// /// This uniquely identifies this op across all *currently live* /// ones. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub id: ::core::option::Option, /// The numeric ID of the op's `Metadata`. /// /// This identifies the `Metadata` that describes the `tracing` span /// corresponding to this async op. The metadata for this ID will have been sent /// in a prior `RegisterMetadata` message. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub metadata: ::core::option::Option, /// The source of this async operation. Most commonly this should be the name /// of the method where the instantiation of this op has happened. - #[prost(string, tag="3")] + #[prost(string, tag = "3")] pub source: ::prost::alloc::string::String, /// The ID of the parent async op. /// @@ -58,28 +60,29 @@ pub struct AsyncOp { /// /// This field can be empty; if it is empty, this async op is not a child of another /// async op. - #[prost(message, optional, tag="4")] + #[prost(message, optional, tag = "4")] pub parent_async_op_id: ::core::option::Option, /// The resources's ID. - #[prost(message, optional, tag="5")] + #[prost(message, optional, tag = "5")] pub resource_id: ::core::option::Option, } /// Statistics associated with a given async operation. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Stats { /// Timestamp of when the async op has been created. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub created_at: ::core::option::Option<::prost_types::Timestamp>, /// Timestamp of when the async op was dropped. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub dropped_at: ::core::option::Option<::prost_types::Timestamp>, /// The Id of the task that is awaiting on this op. - #[prost(message, optional, tag="4")] + #[prost(message, optional, tag = "4")] pub task_id: ::core::option::Option, /// Contains the operation poll stats. - #[prost(message, optional, tag="5")] + #[prost(message, optional, tag = "5")] pub poll_stats: ::core::option::Option, /// State attributes of the async op. - #[prost(message, repeated, tag="6")] + #[prost(message, repeated, tag = "6")] pub attributes: ::prost::alloc::vec::Vec, } diff --git a/console-api/src/generated/rs.tokio.console.common.rs b/console-api/src/generated/rs.tokio.console.common.rs index d9a77c8d3..478de279e 100644 --- a/console-api/src/generated/rs.tokio.console.common.rs +++ b/console-api/src/generated/rs.tokio.console.common.rs @@ -1,54 +1,59 @@ /// Unique identifier for each task. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Id { /// The unique identifier's concrete value. - #[prost(uint64, tag="1")] + #[prost(uint64, tag = "1")] pub id: u64, } /// A Rust source code location. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Location { /// The file path - #[prost(string, optional, tag="1")] + #[prost(string, optional, tag = "1")] pub file: ::core::option::Option<::prost::alloc::string::String>, /// The Rust module path - #[prost(string, optional, tag="2")] + #[prost(string, optional, tag = "2")] pub module_path: ::core::option::Option<::prost::alloc::string::String>, /// The line number in the source code file. - #[prost(uint32, optional, tag="3")] + #[prost(uint32, optional, tag = "3")] pub line: ::core::option::Option, /// The character in `line`. - #[prost(uint32, optional, tag="4")] + #[prost(uint32, optional, tag = "4")] pub column: ::core::option::Option, } /// Unique identifier for metadata. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct MetaId { /// The unique identifier's concrete value. - #[prost(uint64, tag="1")] + #[prost(uint64, tag = "1")] pub id: u64, } /// Unique identifier for spans. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SpanId { /// The unique identifier's concrete value. - #[prost(uint64, tag="1")] + #[prost(uint64, tag = "1")] pub id: u64, } /// A message representing a key-value pair of data associated with a `Span` +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Field { /// Metadata for the task span that the field came from. - #[prost(message, optional, tag="8")] + #[prost(message, optional, tag = "8")] pub metadata_id: ::core::option::Option, /// The key of the key-value pair. /// /// This is either represented as a string, or as an index into a `Metadata`'s /// array of field name strings. - #[prost(oneof="field::Name", tags="1, 2")] + #[prost(oneof = "field::Name", tags = "1, 2")] pub name: ::core::option::Option, /// The value of the key-value pair. - #[prost(oneof="field::Value", tags="3, 4, 5, 6, 7")] + #[prost(oneof = "field::Value", tags = "3, 4, 5, 6, 7")] pub value: ::core::option::Option, } /// Nested message and enum types in `Field`. @@ -57,107 +62,123 @@ pub mod field { /// /// This is either represented as a string, or as an index into a `Metadata`'s /// array of field name strings. + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Name { /// The string representation of the name. - #[prost(string, tag="1")] + #[prost(string, tag = "1")] StrName(::prost::alloc::string::String), /// An index position into the `Metadata.field_names` of the metadata /// for the task span that the field came from. - #[prost(uint64, tag="2")] + #[prost(uint64, tag = "2")] NameIdx(u64), } /// The value of the key-value pair. + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Value { /// A value serialized to a string using `fmt::Debug`. - #[prost(string, tag="3")] + #[prost(string, tag = "3")] DebugVal(::prost::alloc::string::String), /// A string value. - #[prost(string, tag="4")] + #[prost(string, tag = "4")] StrVal(::prost::alloc::string::String), /// An unsigned integer value. - #[prost(uint64, tag="5")] + #[prost(uint64, tag = "5")] U64Val(u64), /// A signed integer value. - #[prost(sint64, tag="6")] + #[prost(sint64, tag = "6")] I64Val(i64), /// A boolean value. - #[prost(bool, tag="7")] + #[prost(bool, tag = "7")] BoolVal(bool), } } /// Represents a period of time in which a program was executing in a particular context. /// /// Corresponds to `Span` in the `tracing` crate. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Span { /// An Id that uniquely identifies it in relation to other spans. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub id: ::core::option::Option, /// Identifier for metadata describing static characteristics of all spans originating /// from that callsite, such as its name, source code location, verbosity level, and /// the names of its fields. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub metadata_id: ::core::option::Option, /// User-defined key-value pairs of arbitrary data that describe the context the span represents, - #[prost(message, repeated, tag="3")] + #[prost(message, repeated, tag = "3")] pub fields: ::prost::alloc::vec::Vec, /// Timestamp for the span. - #[prost(message, optional, tag="4")] + #[prost(message, optional, tag = "4")] pub at: ::core::option::Option<::prost_types::Timestamp>, } /// Any new metadata that was registered since the last update. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterMetadata { /// The new metadata that was registered since the last update. - #[prost(message, repeated, tag="1")] + #[prost(message, repeated, tag = "1")] pub metadata: ::prost::alloc::vec::Vec, } /// Nested message and enum types in `RegisterMetadata`. pub mod register_metadata { /// One metadata element registered since the last update. + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct NewMetadata { /// Unique identifier for `metadata`. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub id: ::core::option::Option, /// The metadata payload. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub metadata: ::core::option::Option, } } /// Metadata associated with a span or event. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Metadata { /// The name of the span or event. - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub name: ::prost::alloc::string::String, /// Describes the part of the system where the span or event that this /// metadata describes occurred. - #[prost(string, tag="2")] + #[prost(string, tag = "2")] pub target: ::prost::alloc::string::String, /// The path to the Rust module where the span occurred. - #[prost(string, tag="3")] + #[prost(string, tag = "3")] pub module_path: ::prost::alloc::string::String, /// The Rust source location associated with the span or event. - #[prost(message, optional, tag="4")] + #[prost(message, optional, tag = "4")] pub location: ::core::option::Option, /// Indicates whether metadata is associated with a span or with an event. - #[prost(enumeration="metadata::Kind", tag="5")] + #[prost(enumeration = "metadata::Kind", tag = "5")] pub kind: i32, /// Describes the level of verbosity of a span or event. - #[prost(enumeration="metadata::Level", tag="6")] + #[prost(enumeration = "metadata::Level", tag = "6")] pub level: i32, /// The names of the key-value fields attached to the /// span or event this metadata is associated with. - #[prost(string, repeated, tag="7")] + #[prost(string, repeated, tag = "7")] pub field_names: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, } /// Nested message and enum types in `Metadata`. pub mod metadata { /// Indicates whether metadata is associated with a span or with an event. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] #[repr(i32)] pub enum Kind { /// Indicates metadata is associated with a span. @@ -165,10 +186,40 @@ 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", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SPAN" => Some(Self::Span), + "EVENT" => Some(Self::Event), + _ => None, + } + } + } /// Describes the level of verbosity of a span or event. /// /// Corresponds to `Level` in the `tracing` crate. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] #[repr(i32)] pub enum Level { /// The "error" level. @@ -191,14 +242,41 @@ 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", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "ERROR" => Some(Self::Error), + "WARN" => Some(Self::Warn), + "INFO" => Some(Self::Info), + "DEBUG" => Some(Self::Debug), + "TRACE" => Some(Self::Trace), + _ => None, + } + } + } } /// Contains stats about objects that can be polled. Currently these can be: /// - tasks that have been spawned /// - async operations on resources that are performed within the context of a task +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PollStats { /// The total number of times this object has been polled. - #[prost(uint64, tag="1")] + #[prost(uint64, tag = "1")] pub polls: u64, /// The timestamp of the first time this object was polled. /// @@ -206,7 +284,7 @@ pub struct PollStats { /// /// Subtracting this timestamp from `created_at` can be used to calculate the /// time to first poll for this object, a measurement of executor latency. - #[prost(message, optional, tag="3")] + #[prost(message, optional, tag = "3")] pub first_poll: ::core::option::Option<::prost_types::Timestamp>, /// The timestamp of the most recent time this objects's poll method was invoked. /// @@ -215,7 +293,7 @@ pub struct PollStats { /// If the object has only been polled a single time, then this value may be /// equal to the `first_poll` timestamp. /// - #[prost(message, optional, tag="4")] + #[prost(message, optional, tag = "4")] pub last_poll_started: ::core::option::Option<::prost_types::Timestamp>, /// The timestamp of the most recent time this objects's poll method finished execution. /// @@ -223,14 +301,17 @@ pub struct PollStats { /// /// If the object does not exist anymore, then this is the time the final invocation of /// its poll method has completed. - #[prost(message, optional, tag="5")] + #[prost(message, optional, tag = "5")] pub last_poll_ended: ::core::option::Option<::prost_types::Timestamp>, /// The total duration this object was being *actively polled*, summed across - /// all polls. Note that this includes only polls that have completed and is - /// not reflecting any inprogress polls. Subtracting `busy_time` from the + /// all polls. + /// + /// Note that this includes only polls that have completed, and does not + /// reflect any in-progress polls. Subtracting `busy_time` from the /// total lifetime of the polled object results in the amount of time it - /// has spent *waiting* to be polled. - #[prost(message, optional, tag="6")] + /// has spent *waiting* to be polled (including the `scheduled_time` value + /// from `TaskStats`, if this is a task). + #[prost(message, optional, tag = "6")] pub busy_time: ::core::option::Option<::prost_types::Duration>, } /// State attributes of an entity. These are dependent on the type of the entity. @@ -240,13 +321,14 @@ pub struct PollStats { /// indicating how many permits they are trying to acquire vs how many are acquired. /// These values may change over time. Therefore, they live in the runtime stats rather /// than the static data describing the entity. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Attribute { /// The key-value pair for the attribute - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub field: ::core::option::Option, /// Some values carry a unit of measurement. For example, a duration /// carries an associated unit of time, such as "ms" for milliseconds. - #[prost(string, optional, tag="2")] + #[prost(string, optional, tag = "2")] pub unit: ::core::option::Option<::prost::alloc::string::String>, } diff --git a/console-api/src/generated/rs.tokio.console.instrument.rs b/console-api/src/generated/rs.tokio.console.instrument.rs index bf1f570ab..bfc9d0f05 100644 --- a/console-api/src/generated/rs.tokio.console.instrument.rs +++ b/console-api/src/generated/rs.tokio.console.instrument.rs @@ -4,25 +4,26 @@ /// TODO: In the future allow for the request to specify /// only the data that the caller cares about (i.e. only /// tasks but no resources) +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct InstrumentRequest { -} +pub struct InstrumentRequest {} /// TaskDetailsRequest requests the stream of updates about /// the specific task identified in the request. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TaskDetailsRequest { /// Identifies the task for which details were requested. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub id: ::core::option::Option, } /// PauseRequest requests the stream of updates to pause. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct PauseRequest { -} +pub struct PauseRequest {} /// ResumeRequest requests the stream of updates to resume after a pause. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct ResumeRequest { -} +pub struct ResumeRequest {} /// Update carries all information regarding tasks, resources, async operations /// and resource operations in one message. There are a couple of reasons to combine all /// of these into a single message: @@ -30,40 +31,42 @@ 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 +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Update { /// The system time when this update was recorded. /// /// This is the timestamp any durations in the included `Stats` were /// calculated relative to. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub now: ::core::option::Option<::prost_types::Timestamp>, /// Task state update. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub task_update: ::core::option::Option, /// Resource state update. - #[prost(message, optional, tag="3")] + #[prost(message, optional, tag = "3")] pub resource_update: ::core::option::Option, /// Async operations state update - #[prost(message, optional, tag="4")] + #[prost(message, optional, tag = "4")] pub async_op_update: ::core::option::Option, /// Any new span metadata that was registered since the last update. - #[prost(message, optional, tag="5")] + #[prost(message, optional, tag = "5")] pub new_metadata: ::core::option::Option, } /// `PauseResponse` is the value returned after a pause request. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct PauseResponse { -} +pub struct PauseResponse {} /// `ResumeResponse` is the value returned after a resume request. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct ResumeResponse { -} +pub struct ResumeResponse {} /// Generated client implementations. 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 { @@ -73,7 +76,7 @@ pub mod instrument_client { /// Attempt to create a new client by connecting to a given endpoint. pub async fn connect(dst: D) -> Result where - D: std::convert::TryInto, + D: TryInto, D::Error: Into, { let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; @@ -84,19 +87,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,26 +117,42 @@ 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. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); self } - /// Enable decompressing responses with `gzip`. + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` #[must_use] - pub fn accept_gzip(mut self) -> Self { - self.inner = self.inner.accept_gzip(); + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); self } /// Produces a stream of updates representing the behavior of the instrumented async runtime. pub async fn watch_updates( &mut self, request: impl tonic::IntoRequest, - ) -> Result< + ) -> std::result::Result< tonic::Response>, tonic::Status, > { @@ -145,13 +169,21 @@ pub mod instrument_client { let path = http::uri::PathAndQuery::from_static( "/rs.tokio.console.instrument.Instrument/WatchUpdates", ); - self.inner.server_streaming(request.into_request(), path, codec).await + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "rs.tokio.console.instrument.Instrument", + "WatchUpdates", + ), + ); + self.inner.server_streaming(req, path, codec).await } /// Produces a stream of updates describing the activity of a specific task. pub async fn watch_task_details( &mut self, request: impl tonic::IntoRequest, - ) -> Result< + ) -> std::result::Result< tonic::Response< tonic::codec::Streaming, >, @@ -170,13 +202,21 @@ pub mod instrument_client { let path = http::uri::PathAndQuery::from_static( "/rs.tokio.console.instrument.Instrument/WatchTaskDetails", ); - self.inner.server_streaming(request.into_request(), path, codec).await + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "rs.tokio.console.instrument.Instrument", + "WatchTaskDetails", + ), + ); + self.inner.server_streaming(req, path, codec).await } /// Registers that the console observer wants to pause the stream. pub async fn pause( &mut self, request: impl tonic::IntoRequest, - ) -> Result, tonic::Status> { + ) -> std::result::Result, tonic::Status> { self.inner .ready() .await @@ -190,13 +230,18 @@ pub mod instrument_client { let path = http::uri::PathAndQuery::from_static( "/rs.tokio.console.instrument.Instrument/Pause", ); - self.inner.unary(request.into_request(), path, codec).await + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("rs.tokio.console.instrument.Instrument", "Pause"), + ); + self.inner.unary(req, path, codec).await } /// Registers that the console observer wants to resume the stream. pub async fn resume( &mut self, request: impl tonic::IntoRequest, - ) -> Result, tonic::Status> { + ) -> std::result::Result, tonic::Status> { self.inner .ready() .await @@ -210,7 +255,12 @@ pub mod instrument_client { let path = http::uri::PathAndQuery::from_static( "/rs.tokio.console.instrument.Instrument/Resume", ); - self.inner.unary(request.into_request(), path, codec).await + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new("rs.tokio.console.instrument.Instrument", "Resume"), + ); + self.inner.unary(req, path, codec).await } } } @@ -218,12 +268,12 @@ pub mod instrument_client { pub mod instrument_server { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] use tonic::codegen::*; - ///Generated trait containing gRPC methods that should be implemented for use with InstrumentServer. + /// Generated trait containing gRPC methods that should be implemented for use with InstrumentServer. #[async_trait] pub trait Instrument: Send + Sync + 'static { - ///Server streaming response type for the WatchUpdates method. + /// Server streaming response type for the WatchUpdates method. type WatchUpdatesStream: futures_core::Stream< - Item = Result, + Item = std::result::Result, > + Send + 'static; @@ -231,10 +281,16 @@ pub mod instrument_server { async fn watch_updates( &self, request: tonic::Request, - ) -> Result, tonic::Status>; - ///Server streaming response type for the WatchTaskDetails method. + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the WatchTaskDetails method. type WatchTaskDetailsStream: futures_core::Stream< - Item = Result, + Item = std::result::Result< + super::super::tasks::TaskDetails, + tonic::Status, + >, > + Send + 'static; @@ -242,24 +298,29 @@ pub mod instrument_server { async fn watch_task_details( &self, request: tonic::Request, - ) -> Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Registers that the console observer wants to pause the stream. async fn pause( &self, request: tonic::Request, - ) -> Result, tonic::Status>; + ) -> std::result::Result, tonic::Status>; /// Registers that the console observer wants to resume the stream. async fn resume( &self, request: tonic::Request, - ) -> Result, tonic::Status>; + ) -> std::result::Result, tonic::Status>; } /// `InstrumentServer` implements `Instrument` as a service. #[derive(Debug)] pub struct InstrumentServer { inner: _Inner, - accept_compression_encodings: (), - send_compression_encodings: (), + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, } struct _Inner(Arc); impl InstrumentServer { @@ -272,6 +333,8 @@ pub mod instrument_server { inner, accept_compression_encodings: Default::default(), send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, } } pub fn with_interceptor( @@ -283,6 +346,34 @@ 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 + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } } impl tonic::codegen::Service> for InstrumentServer where @@ -296,7 +387,7 @@ pub mod instrument_server { fn poll_ready( &mut self, _cx: &mut Context<'_>, - ) -> Poll> { + ) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, req: http::Request) -> Self::Future { @@ -319,7 +410,7 @@ pub mod instrument_server { &mut self, request: tonic::Request, ) -> Self::Future { - let inner = self.0.clone(); + let inner = Arc::clone(&self.0); let fut = async move { (*inner).watch_updates(request).await }; @@ -328,6 +419,8 @@ pub mod instrument_server { } let accept_compression_encodings = self.accept_compression_encodings; let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; let inner = self.inner.clone(); let fut = async move { let inner = inner.0; @@ -337,6 +430,10 @@ pub mod instrument_server { .apply_compression_config( accept_compression_encodings, send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, ); let res = grpc.server_streaming(method, req).await; Ok(res) @@ -360,7 +457,7 @@ pub mod instrument_server { &mut self, request: tonic::Request, ) -> Self::Future { - let inner = self.0.clone(); + let inner = Arc::clone(&self.0); let fut = async move { (*inner).watch_task_details(request).await }; @@ -369,6 +466,8 @@ pub mod instrument_server { } let accept_compression_encodings = self.accept_compression_encodings; let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; let inner = self.inner.clone(); let fut = async move { let inner = inner.0; @@ -378,6 +477,10 @@ pub mod instrument_server { .apply_compression_config( accept_compression_encodings, send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, ); let res = grpc.server_streaming(method, req).await; Ok(res) @@ -398,13 +501,15 @@ pub mod instrument_server { &mut self, request: tonic::Request, ) -> Self::Future { - let inner = self.0.clone(); + let inner = Arc::clone(&self.0); let fut = async move { (*inner).pause(request).await }; Box::pin(fut) } } let accept_compression_encodings = self.accept_compression_encodings; let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; let inner = self.inner.clone(); let fut = async move { let inner = inner.0; @@ -414,6 +519,10 @@ pub mod instrument_server { .apply_compression_config( accept_compression_encodings, send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, ); let res = grpc.unary(method, req).await; Ok(res) @@ -434,13 +543,15 @@ pub mod instrument_server { &mut self, request: tonic::Request, ) -> Self::Future { - let inner = self.0.clone(); + let inner = Arc::clone(&self.0); let fut = async move { (*inner).resume(request).await }; Box::pin(fut) } } let accept_compression_encodings = self.accept_compression_encodings; let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; let inner = self.inner.clone(); let fut = async move { let inner = inner.0; @@ -450,6 +561,10 @@ pub mod instrument_server { .apply_compression_config( accept_compression_encodings, send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, ); let res = grpc.unary(method, req).await; Ok(res) @@ -478,12 +593,14 @@ pub mod instrument_server { inner, accept_compression_encodings: self.accept_compression_encodings, send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, } } } impl Clone for _Inner { fn clone(&self) -> Self { - Self(self.0.clone()) + Self(Arc::clone(&self.0)) } } impl std::fmt::Debug for _Inner { @@ -491,7 +608,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..2ee3ba497 100644 --- a/console-api/src/generated/rs.tokio.console.resources.rs +++ b/console-api/src/generated/rs.tokio.console.resources.rs @@ -5,17 +5,18 @@ /// - any new resources that were created since the last update /// - the current stats for any resource whose stats changed since the last update /// - any new poll ops that have been invoked on a resource +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct ResourceUpdate { /// A list of new resources that were created since the last `ResourceUpdate` was /// sent. - #[prost(message, repeated, tag="1")] + #[prost(message, repeated, tag = "1")] pub new_resources: ::prost::alloc::vec::Vec, /// Any resource stats that have changed since the last update. - #[prost(map="uint64, message", tag="2")] + #[prost(map = "uint64, message", tag = "2")] pub stats_update: ::std::collections::HashMap, /// A list of all new poll ops that have been invoked on resources since the last update. - #[prost(message, repeated, tag="3")] + #[prost(message, repeated, tag = "3")] pub new_poll_ops: ::prost::alloc::vec::Vec, /// A count of how many resource events (e.g. polls, creation, etc) were not /// recorded because the application's event buffer was at capacity. @@ -27,10 +28,11 @@ pub struct ResourceUpdate { /// /// If the application's instrumentation ensures reliable delivery of events, /// this will always be 0. - #[prost(uint64, tag="4")] + #[prost(uint64, tag = "4")] pub dropped_events: u64, } /// Static data recorded when a new resource is created. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Resource { /// The resources's ID. @@ -38,79 +40,111 @@ pub struct Resource { /// This uniquely identifies this resource across all *currently live* /// resources. This is also the primary way any operations on a resource /// are associated with it - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub id: ::core::option::Option, /// The numeric ID of the resources's `Metadata`. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub metadata: ::core::option::Option, /// The resources's concrete rust type. - #[prost(string, tag="3")] + #[prost(string, tag = "3")] pub concrete_type: ::prost::alloc::string::String, /// The kind of resource (e.g timer, mutex) - #[prost(message, optional, tag="4")] + #[prost(message, optional, tag = "4")] pub kind: ::core::option::Option, /// The location in code where the resource was created. - #[prost(message, optional, tag="5")] + #[prost(message, optional, tag = "5")] pub location: ::core::option::Option, /// The ID of the parent resource. - #[prost(message, optional, tag="6")] + #[prost(message, optional, tag = "6")] pub parent_resource_id: ::core::option::Option, /// Is the resource an internal component of another resource? /// /// For example, a `tokio::time::Interval` resource might contain a /// `tokio::time::Sleep` resource internally. - #[prost(bool, tag="7")] + #[prost(bool, tag = "7")] pub is_internal: bool, } /// Nested message and enum types in `Resource`. pub mod resource { /// The kind of resource (e.g. timer, mutex). + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Kind { /// Every resource is either a known kind or an other (unknown) kind. - #[prost(oneof="kind::Kind", tags="1, 2")] + #[prost(oneof = "kind::Kind", tags = "1, 2")] pub kind: ::core::option::Option, } /// Nested message and enum types in `Kind`. pub mod kind { /// `Known` collects the kinds of resources that are known in this version of the API. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] #[repr(i32)] pub enum Known { /// `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", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "TIMER" => Some(Self::Timer), + _ => None, + } + } + } /// Every resource is either a known kind or an other (unknown) kind. + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Kind { /// `known` signals that this kind of resource is known to the console API. - #[prost(enumeration="Known", tag="1")] + #[prost(enumeration = "Known", tag = "1")] Known(i32), /// `other` signals that this kind of resource is unknown to the console API. - #[prost(string, tag="2")] + #[prost(string, tag = "2")] Other(::prost::alloc::string::String), } } } /// Task runtime stats of a resource. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Stats { /// Timestamp of when the resource was created. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub created_at: ::core::option::Option<::prost_types::Timestamp>, /// Timestamp of when the resource was dropped. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub dropped_at: ::core::option::Option<::prost_types::Timestamp>, /// State attributes of the resource. These are dependent on the type of the resource. /// For example, a timer resource will have a duration while a semaphore resource may /// have permits as an attribute. These values may change over time as the state of /// the resource changes. Therefore, they live in the runtime stats rather than the /// static data describing the resource. - #[prost(message, repeated, tag="3")] + #[prost(message, repeated, tag = "3")] pub attributes: ::prost::alloc::vec::Vec, } /// A `PollOp` describes each poll operation that completes within the async /// application. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PollOp { /// The numeric ID of the op's `Metadata`. @@ -118,21 +152,21 @@ pub struct PollOp { /// This identifies the `Metadata` that describes the `tracing` span /// corresponding to this op. The metadata for this ID will have been sent /// in a prior `RegisterMetadata` message. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub metadata: ::core::option::Option, /// The resources's ID. - #[prost(message, optional, tag="3")] + #[prost(message, optional, tag = "3")] pub resource_id: ::core::option::Option, /// the name of this op (e.g. poll_elapsed, new_timeout, reset, etc.) - #[prost(string, tag="4")] + #[prost(string, tag = "4")] pub name: ::prost::alloc::string::String, /// Identifies the task context that this poll op has been called from. - #[prost(message, optional, tag="5")] + #[prost(message, optional, tag = "5")] pub task_id: ::core::option::Option, /// Identifies the async op ID that this poll op is part of. - #[prost(message, optional, tag="6")] + #[prost(message, optional, tag = "6")] pub async_op_id: ::core::option::Option, /// Whether this poll op has returned with ready or pending. - #[prost(bool, tag="7")] + #[prost(bool, tag = "7")] pub is_ready: bool, } diff --git a/console-api/src/generated/rs.tokio.console.tasks.rs b/console-api/src/generated/rs.tokio.console.tasks.rs index 10915a6c5..be5ac1b1a 100644 --- a/console-api/src/generated/rs.tokio.console.tasks.rs +++ b/console-api/src/generated/rs.tokio.console.tasks.rs @@ -4,13 +4,14 @@ /// update. This includes: /// - any new tasks that were spawned since the last update /// - the current stats for any task whose stats changed since the last update +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TaskUpdate { /// A list of new tasks that were spawned since the last `TaskUpdate` was /// sent. /// /// If this is empty, no new tasks were spawned. - #[prost(message, repeated, tag="1")] + #[prost(message, repeated, tag = "1")] pub new_tasks: ::prost::alloc::vec::Vec, /// Any task stats that have changed since the last update. /// @@ -19,7 +20,7 @@ pub struct TaskUpdate { /// since the last `TaskUpdate` in which they were present. If a task's ID /// *is* included in this map, the corresponding value represents a complete /// snapshot of that task's stats at in the current time window. - #[prost(map="uint64, message", tag="3")] + #[prost(map = "uint64, message", tag = "3")] pub stats_update: ::std::collections::HashMap, /// A count of how many task events (e.g. polls, spawns, etc) were not /// recorded because the application's event buffer was at capacity. @@ -31,25 +32,32 @@ pub struct TaskUpdate { /// /// If the application's instrumentation ensures reliable delivery of events, /// this will always be 0. - #[prost(uint64, tag="4")] + #[prost(uint64, tag = "4")] pub dropped_events: u64, } /// A task details update +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TaskDetails { /// The task's ID which the details belong to. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub task_id: ::core::option::Option, /// The timestamp for when the update to the task took place. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub now: ::core::option::Option<::prost_types::Timestamp>, + /// A histogram of task scheduled durations. + /// + /// The scheduled duration is the time a task spends between being + /// woken and when it is next polled. + #[prost(message, optional, tag = "5")] + pub scheduled_times_histogram: ::core::option::Option, /// A histogram of task poll durations. /// /// 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")] + #[prost(oneof = "task_details::PollTimesHistogram", tags = "3, 4")] pub poll_times_histogram: ::core::option::Option, } /// Nested message and enum types in `TaskDetails`. @@ -58,19 +66,21 @@ 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) + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum PollTimesHistogram { /// HdrHistogram.rs `Histogram` serialized to binary in the V2 format - #[prost(bytes, tag="3")] + #[prost(bytes, tag = "3")] LegacyHistogram(::prost::alloc::vec::Vec), /// A histogram plus additional data. - #[prost(message, tag="4")] + #[prost(message, tag = "4")] Histogram(super::DurationHistogram), } } /// Data recorded when a new task is spawned. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Task { /// The task's ID. @@ -80,20 +90,20 @@ pub struct Task { /// identified by this ID; if the client requires additional information /// included in the `Task` message, it should store that data and access it /// by ID. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub id: ::core::option::Option, /// The numeric ID of the task's `Metadata`. /// /// This identifies the `Metadata` that describes the `tracing` span /// corresponding to this task. The metadata for this ID will have been sent /// in a prior `RegisterMetadata` message. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub metadata: ::core::option::Option, /// The category of task this task belongs to. - #[prost(enumeration="task::Kind", tag="3")] + #[prost(enumeration = "task::Kind", tag = "3")] pub kind: i32, /// A list of `Field` objects attached to this task. - #[prost(message, repeated, tag="4")] + #[prost(message, repeated, tag = "4")] pub fields: ::prost::alloc::vec::Vec, /// An ordered list of span IDs corresponding to the `tracing` span context /// in which this task was spawned. @@ -106,16 +116,26 @@ pub struct Task { /// /// These IDs may correspond to `tracing` spans which are *not* tasks, if /// additional trace data is being collected. - #[prost(message, repeated, tag="5")] + #[prost(message, repeated, tag = "5")] pub parents: ::prost::alloc::vec::Vec, /// The location in code where the task was spawned. - #[prost(message, optional, tag="6")] + #[prost(message, optional, tag = "6")] pub location: ::core::option::Option, } /// Nested message and enum types in `Task`. pub mod task { /// The category of task this task belongs to. - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] #[repr(i32)] pub enum Kind { /// A task spawned using a runtime's standard asynchronous task spawning @@ -125,50 +145,83 @@ 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", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "SPAWN" => Some(Self::Spawn), + "BLOCKING" => Some(Self::Blocking), + _ => None, + } + } + } } /// Task performance statistics. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Stats { /// Timestamp of when the task was spawned. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub created_at: ::core::option::Option<::prost_types::Timestamp>, /// Timestamp of when the task was dropped. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub dropped_at: ::core::option::Option<::prost_types::Timestamp>, /// The total number of times this task has been woken over its lifetime. - #[prost(uint64, tag="3")] + #[prost(uint64, tag = "3")] pub wakes: u64, /// The total number of times this task's waker has been cloned. - #[prost(uint64, tag="4")] + #[prost(uint64, tag = "4")] pub waker_clones: u64, /// The total number of times this task's waker has been dropped. - #[prost(uint64, tag="5")] + #[prost(uint64, tag = "5")] pub waker_drops: u64, /// The timestamp of the most recent time this task has been woken. /// /// If this is `None`, the task has not yet been woken. - #[prost(message, optional, tag="6")] + #[prost(message, optional, tag = "6")] pub last_wake: ::core::option::Option<::prost_types::Timestamp>, /// Contains task poll statistics. - #[prost(message, optional, tag="7")] + #[prost(message, optional, tag = "7")] pub poll_stats: ::core::option::Option, /// The total number of times this task has woken itself. - #[prost(uint64, tag="8")] + #[prost(uint64, tag = "8")] pub self_wakes: u64, + /// The total duration this task was scheduled prior to being polled, summed + /// across all poll cycles. + /// + /// Note that this includes only polls that have started, and does not + /// reflect any scheduled state where the task hasn't yet been polled. + /// Subtracting both `busy_time` (from the task's `PollStats`) and + /// `scheduled_time` from the total lifetime of the task results in the + /// amount of time it spent unable to progress because it was waiting on + /// some resource. + #[prost(message, optional, tag = "9")] + pub scheduled_time: ::core::option::Option<::prost_types::Duration>, } +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DurationHistogram { /// HdrHistogram.rs `Histogram` serialized to binary in the V2 format - #[prost(bytes="vec", tag="1")] + #[prost(bytes = "vec", tag = "1")] pub raw_histogram: ::prost::alloc::vec::Vec, /// The histogram's maximum value. - #[prost(uint64, tag="2")] + #[prost(uint64, tag = "2")] pub max_value: u64, /// The number of outliers which have exceeded the histogram's maximum value. - #[prost(uint64, tag="3")] + #[prost(uint64, tag = "3")] pub high_outliers: u64, /// The highest recorded outlier. This is only present if `high_outliers` is /// greater than zero. - #[prost(uint64, optional, tag="4")] + #[prost(uint64, optional, tag = "4")] pub highest_outlier: ::core::option::Option, } diff --git a/console-api/src/generated/rs.tokio.console.trace.rs b/console-api/src/generated/rs.tokio.console.trace.rs index ed3e98991..b15967baf 100644 --- a/console-api/src/generated/rs.tokio.console.trace.rs +++ b/console-api/src/generated/rs.tokio.console.trace.rs @@ -1,82 +1,89 @@ /// Start watching trace events with the provided filter. +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct WatchRequest { /// Specifies which trace events should be streamed. - #[prost(string, tag="1")] + #[prost(string, tag = "1")] pub filter: ::prost::alloc::string::String, } /// A trace event +#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct TraceEvent { /// A trace event - #[prost(oneof="trace_event::Event", tags="1, 2, 3, 4, 5, 6")] + #[prost(oneof = "trace_event::Event", tags = "1, 2, 3, 4, 5, 6")] pub event: ::core::option::Option, } /// Nested message and enum types in `TraceEvent`. pub mod trace_event { /// `RegisterThreads` signals that a new thread was registered. + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct RegisterThreads { /// `names` maps the registered thread id's to their associated name. - #[prost(map="uint64, string", tag="1")] + #[prost(map = "uint64, string", tag = "1")] pub names: ::std::collections::HashMap, } /// `Enter` signals that a span was entered. + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Enter { /// `span_id` identifies the span that was entered. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub span_id: ::core::option::Option, /// `thread_id` identifies who entered the span. - #[prost(uint64, tag="2")] + #[prost(uint64, tag = "2")] pub thread_id: u64, /// `at` identifies when the span was entered. - #[prost(message, optional, tag="3")] + #[prost(message, optional, tag = "3")] pub at: ::core::option::Option<::prost_types::Timestamp>, } /// `Exit` signals that a span was exited. + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Exit { /// `span_id` identifies the span that was exited. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub span_id: ::core::option::Option, /// `thread_id` identifies who exited the span. - #[prost(uint64, tag="2")] + #[prost(uint64, tag = "2")] pub thread_id: u64, /// `at` identifies when the span was exited. - #[prost(message, optional, tag="3")] + #[prost(message, optional, tag = "3")] pub at: ::core::option::Option<::prost_types::Timestamp>, } /// `Close` signals that a span was closed. + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Close { /// `span_id` identifies the span that was closed. - #[prost(message, optional, tag="1")] + #[prost(message, optional, tag = "1")] pub span_id: ::core::option::Option, /// `at` identifies when the span was closed. - #[prost(message, optional, tag="2")] + #[prost(message, optional, tag = "2")] pub at: ::core::option::Option<::prost_types::Timestamp>, } /// A trace event + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Oneof)] pub enum Event { /// A new thread was registered. - #[prost(message, tag="1")] + #[prost(message, tag = "1")] RegisterThread(RegisterThreads), /// A new span metadata was registered. - #[prost(message, tag="2")] + #[prost(message, tag = "2")] RegisterMetadata(super::super::common::RegisterMetadata), /// A span was created. - #[prost(message, tag="3")] + #[prost(message, tag = "3")] NewSpan(super::super::common::Span), /// A span was entered. - #[prost(message, tag="4")] + #[prost(message, tag = "4")] EnterSpan(Enter), /// A span was exited. - #[prost(message, tag="5")] + #[prost(message, tag = "5")] ExitSpan(Exit), /// A span was closed. - #[prost(message, tag="6")] + #[prost(message, tag = "6")] CloseSpan(Close), } } @@ -84,6 +91,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 { @@ -93,7 +101,7 @@ pub mod trace_client { /// Attempt to create a new client by connecting to a given endpoint. pub async fn connect(dst: D) -> Result where - D: std::convert::TryInto, + D: TryInto, D::Error: Into, { let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; @@ -104,19 +112,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,26 +142,42 @@ 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. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); self } - /// Enable decompressing responses with `gzip`. + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` #[must_use] - pub fn accept_gzip(mut self) -> Self { - self.inner = self.inner.accept_gzip(); + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); self } /// Produces a stream of trace events for the given filter. pub async fn watch( &mut self, request: impl tonic::IntoRequest, - ) -> Result< + ) -> std::result::Result< tonic::Response>, tonic::Status, > { @@ -165,7 +194,10 @@ pub mod trace_client { let path = http::uri::PathAndQuery::from_static( "/rs.tokio.console.trace.Trace/Watch", ); - self.inner.server_streaming(request.into_request(), path, codec).await + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("rs.tokio.console.trace.Trace", "Watch")); + self.inner.server_streaming(req, path, codec).await } } } @@ -173,12 +205,12 @@ pub mod trace_client { pub mod trace_server { #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] use tonic::codegen::*; - ///Generated trait containing gRPC methods that should be implemented for use with TraceServer. + /// Generated trait containing gRPC methods that should be implemented for use with TraceServer. #[async_trait] pub trait Trace: Send + Sync + 'static { - ///Server streaming response type for the Watch method. + /// Server streaming response type for the Watch method. type WatchStream: futures_core::Stream< - Item = Result, + Item = std::result::Result, > + Send + 'static; @@ -186,14 +218,16 @@ pub mod trace_server { async fn watch( &self, request: tonic::Request, - ) -> Result, tonic::Status>; + ) -> std::result::Result, tonic::Status>; } /// Allows observers to stream trace events for a given `WatchRequest` filter. #[derive(Debug)] pub struct TraceServer { inner: _Inner, - accept_compression_encodings: (), - send_compression_encodings: (), + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, } struct _Inner(Arc); impl TraceServer { @@ -206,6 +240,8 @@ pub mod trace_server { inner, accept_compression_encodings: Default::default(), send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, } } pub fn with_interceptor( @@ -217,6 +253,34 @@ 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 + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } } impl tonic::codegen::Service> for TraceServer where @@ -230,7 +294,7 @@ pub mod trace_server { fn poll_ready( &mut self, _cx: &mut Context<'_>, - ) -> Poll> { + ) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, req: http::Request) -> Self::Future { @@ -253,13 +317,15 @@ pub mod trace_server { &mut self, request: tonic::Request, ) -> Self::Future { - let inner = self.0.clone(); + let inner = Arc::clone(&self.0); let fut = async move { (*inner).watch(request).await }; Box::pin(fut) } } let accept_compression_encodings = self.accept_compression_encodings; let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; let inner = self.inner.clone(); let fut = async move { let inner = inner.0; @@ -269,6 +335,10 @@ pub mod trace_server { .apply_compression_config( accept_compression_encodings, send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, ); let res = grpc.server_streaming(method, req).await; Ok(res) @@ -297,12 +367,14 @@ pub mod trace_server { inner, accept_compression_encodings: self.accept_compression_encodings, send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, } } } impl Clone for _Inner { fn clone(&self) -> Self { - Self(self.0.clone()) + Self(Arc::clone(&self.0)) } } impl std::fmt::Debug for _Inner { @@ -310,7 +382,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/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-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/CHANGELOG.md b/console-subscriber/CHANGELOG.md index 977b9fafd..413ff680b 100644 --- a/console-subscriber/CHANGELOG.md +++ b/console-subscriber/CHANGELOG.md @@ -1,80 +1,201 @@ - -## 0.1.6 (2022-05-23) +# Changelog +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -#### Features +## console-subscriber-v0.1.10 - (2023-07-03) -* add `Builder::poll_duration_histogram_max` (#351) ([a966feb3](a966feb3)) +[05cdab0](https://github.com/tokio-rs/console/commit/05cdab07a3da603697520a56f0b99b2e2042d8bd)...[91929d0](https://github.com/tokio-rs/console/commit/91929d030768287b5f95595a757eea5eeb151022) -#### Bug Fixes -* fix memory leak from resizing histograms (#351) ([32dd3376](32dd3376), closes [#350](350)) +### Fixed +- Fix self wakes count ([#430](https://github.com/tokio-rs/console/issues/430)) ([ee0b8e2](https://github.com/tokio-rs/console/commit/ee0b8e28c7761edd277beb865b2a1e0a3bfa1851)) +- Do not report excessive polling ([#378](https://github.com/tokio-rs/console/issues/378)) ([#440](https://github.com/tokio-rs/console/issues/440)) ([91929d0](https://github.com/tokio-rs/console/commit/91929d030768287b5f95595a757eea5eeb151022), closes [#378](https://github.com/tokio-rs/console/issues/378)) - -## 0.1.5 (2022-04-30) +### Console_subscriber +- Remove clock skew warning in start_poll ([#434](https://github.com/tokio-rs/console/issues/434)) ([fb45ca1](https://github.com/tokio-rs/console/commit/fb45ca16a77a9a63e88494a892076d41495e6bb2)) -#### Features +## console-subscriber-v0.1.9 - (2023-05-09) +[8fb1732](https://github.com/tokio-rs/console/commit/8fb1732dfd78ec3a8e4945c453d1c127f63ecdc4)...[05cdab0](https://github.com/tokio-rs/console/commit/05cdab07a3da603697520a56f0b99b2e2042d8bd) -* add support for `EnvFilter` in `Builder::init` (#337) ([1fe84b72](1fe84b72)) - -## 0.1.4 (2022-04-11) +### Added +- Add support for Unix domain sockets ([#388](https://github.com/tokio-rs/console/issues/388)) ([bff8b8a](https://github.com/tokio-rs/console/commit/bff8b8a4291b0584ab4f97c5f91246eb9a68f262), closes [#296](https://github.com/tokio-rs/console/issues/296)) +- Add scheduled time per task ([#406](https://github.com/tokio-rs/console/issues/406)) ([ac20daa](https://github.com/tokio-rs/console/commit/ac20daaf301f80e87002593813965d11d11371e4)) +- Add task scheduled times histogram ([#409](https://github.com/tokio-rs/console/issues/409)) ([3b37dda](https://github.com/tokio-rs/console/commit/3b37dda773f8cd237f6759d193fdc83a75ab7653)) +- Update `tonic` to 0.9 ([#420](https://github.com/tokio-rs/console/issues/420)) ([54f6be9](https://github.com/tokio-rs/console/commit/54f6be985a248d3dd5a98a7624a2447d0547bc60)) +- Update MSRV to Rust 1.60.0 ([e3c5656](https://github.com/tokio-rs/console/commit/e3c56561a062be123be460dd477f512a6a9ec3cd)) -#### Bug Fixes +### Fixed -* fix memory leak from historical `PollOp`s (#311) ([9178ecf0](9178ecf0), closes [#256](256)) +- Fix off-by-one indexing for `callsites` ([#391](https://github.com/tokio-rs/console/issues/391)) ([3c668a3](https://github.com/tokio-rs/console/commit/3c668a3679b5536f8a047db7a35d432c645aacef)) +- Bump minimum Tokio version ([#397](https://github.com/tokio-rs/console/issues/397)) ([7286d6f](https://github.com/tokio-rs/console/commit/7286d6f75022f3504a0379ff3fa15585a614753e), fixes [#386](https://github.com/tokio-rs/console/issues/386)) +## console-subscriber-v0.1.8 - (2022-09-04) -#### Features +[95a17b6](https://github.com/tokio-rs/console/commit/95a17b6f549ca6d9d22777043dc6f65432fdc69b)...[8fb1732](https://github.com/tokio-rs/console/commit/8fb1732dfd78ec3a8e4945c453d1c127f63ecdc4) -* **console-api:** Update `tonic` to `0.7` (#318) ([83d8a870](83d8a870)) -* don't trace tasks spawned through the console server (#314) ([0045e9bf](0045e9bf)) +### Fixed - -## 0.1.3 (2022-02-18) +- Fix build on tokio 1.21.0 ([#374](https://github.com/tokio-rs/console/issues/374)) ([0106407](https://github.com/tokio-rs/console/commit/0106407cc712b65793801d70324896138d4a4d59)) +## console-subscriber-v0.1.6 - (2022-05-23) -#### Features +[0b3f592](https://github.com/tokio-rs/console/commit/0b3f59280070b1b9f44ec7473ff36279c4ad54c4)...[95a17b6](https://github.com/tokio-rs/console/commit/95a17b6f549ca6d9d22777043dc6f65432fdc69b) -* add `Builder::filter_env_var` builder parameter (#276) ([dbdb1494](dbdb1494), closes [#206](206)) -#### Bug Fixes +### Added -* record timestamps for updates last (#289) ([703f1aa4](703f1aa4), closes [#266](266)) -* use monotonic `Instant`s for all timestamps (#288) ([abc08300](abc08300), closes [#286](286)) -* bail rather than panic when encountering clock skew (#287) ([24db8c60](24db8c60), closes [#286](286)) -* fix compilation on targets without 64-bit atomics (#282) ([5590fdbc](5590fdbc), closes [#279](279)) +- Add `Builder::poll_duration_histogram_max` ([#351](https://github.com/tokio-rs/console/issues/351)) ([a966feb](https://github.com/tokio-rs/console/commit/a966feb3d24e555b76c39830216f6fcff6c18f85)) - - -## 0.1.2 (2022-01-18) +### Fixed +- Fix memory leak from resizing histograms ([#351](https://github.com/tokio-rs/console/issues/351)) ([32dd337](https://github.com/tokio-rs/console/commit/32dd33760a633a409d7828783dd8c095c7b6b0ed), fixes [#350](https://github.com/tokio-rs/console/issues/350)) -#### Bug Fixes +## console-subscriber-v0.1.5 - (2022-04-30) -* update console-api dependencies to require 0.1.2 (#274) ([b95f683f](b95f683f)) +[43fb91f](https://github.com/tokio-rs/console/commit/43fb91f58b1ed6255d21fe591c68275995ea8894)...[0b3f592](https://github.com/tokio-rs/console/commit/0b3f59280070b1b9f44ec7473ff36279c4ad54c4) - -## 0.1.1 (2022-01-18) +### Added +- Add support for `EnvFilter` in `Builder::init` ([#337](https://github.com/tokio-rs/console/issues/337)) ([1fe84b7](https://github.com/tokio-rs/console/commit/1fe84b7270e9e6d41d0f1b97029ef4793aa6b58d)) -#### Bug Fixes +### Documented -* only send *new* tasks/resources/etc over the event channel (#238) ([fdc77e28](fdc77e28)) -* increased default event buffer capacity (#235) ([0cf0aee](0cf0aee)) -* use saturating arithmetic for attribute updates (#234) ([fe82e170](fe82e170)) +- Fix links to console-subscriber's API docs ([#326](https://github.com/tokio-rs/console/issues/326)) ([bebaa16](https://github.com/tokio-rs/console/commit/bebaa16b3b72ea08724bc0dc5d3aae60920485c7)) +- Fix broken `Server` rustdoc ([#332](https://github.com/tokio-rs/console/issues/332)) ([84605c4](https://github.com/tokio-rs/console/commit/84605c4adc809bd715670c61a8a6e1a33a790fdf)) +- Update minimal Rust version ([#338](https://github.com/tokio-rs/console/issues/338)) ([ff3b6db](https://github.com/tokio-rs/console/commit/ff3b6db6fa06456a14992663e8ff7ba8c80c1cc1)) -#### Changes +## console-subscriber-v0.1.4 - (2022-04-11) -* moved ID rewriting from `console-subscriber` to the client (#244) ([095b1ef](095b1ef)) +[0e67d17](https://github.com/tokio-rs/console/commit/0e67d17e1b92f549c787a5c700008064c10da00e)...[43fb91f](https://github.com/tokio-rs/console/commit/43fb91f58b1ed6255d21fe591c68275995ea8894) -## 0.1.0 (2021-12-16) +### Breaking Changes +- **Update `tonic` to `0.7` ([#318](https://github.com/tokio-rs/console/issues/318))** ([83d8a87](https://github.com/tokio-rs/console/commit/83d8a870bcc40be71bc23d0f45fc374899c636a8))
`console-api` is now no longer compatible with projects using `prost` +0.9 or `tonic` 0.7. These crates must be updated to use `console-api` +0.2. +### Added -- Initial release! 🎉 +- [**breaking**](#console-subscriber-v0.1.4-breaking) Update `tonic` to `0.7` ([#318](https://github.com/tokio-rs/console/issues/318)) ([83d8a87](https://github.com/tokio-rs/console/commit/83d8a870bcc40be71bc23d0f45fc374899c636a8)) +- Don't trace tasks spawned through the console server ([#314](https://github.com/tokio-rs/console/issues/314)) ([0045e9b](https://github.com/tokio-rs/console/commit/0045e9bf509b8fd180c20ea846ff1da065c86a7f)) + +### Documented + +- Warn against enabling compile time filters in the readme ([#317](https://github.com/tokio-rs/console/issues/317)) ([9a27cd2](https://github.com/tokio-rs/console/commit/9a27cd23dfe1004c5cc8e04c58dfac187ebf93fa), closes [#315](https://github.com/tokio-rs/console/issues/315)) + +### Fixed + +- Fix memory leak from historical `PollOp`s ([#311](https://github.com/tokio-rs/console/issues/311)) ([9178ecf](https://github.com/tokio-rs/console/commit/9178ecf02f094f8b23dc26f02faaba4ec09fd8f5), fixes [#256](https://github.com/tokio-rs/console/issues/256)) + +## console-subscriber-v0.1.3 - (2022-02-18) + +[e590df3](https://github.com/tokio-rs/console/commit/e590df39ca38cf795b1aec493403e1411e3b4766)...[0e67d17](https://github.com/tokio-rs/console/commit/0e67d17e1b92f549c787a5c700008064c10da00e) + + +### Added + +- Add `Builder::filter_env_var` builder parameter ([#276](https://github.com/tokio-rs/console/issues/276)) ([dbdb149](https://github.com/tokio-rs/console/commit/dbdb14949bd2ac7c58e5c38cecbeb3fb76f45619), closes [#206](https://github.com/tokio-rs/console/issues/206)) + +### Documented + +- Fix broken links in READMEs and subscriber doc comment ([#285](https://github.com/tokio-rs/console/issues/285)) ([a2202f7](https://github.com/tokio-rs/console/commit/a2202f76beb0cc7983355aec108697f8964fe837)) +- Add information on where to put .cargo/config.toml ([#284](https://github.com/tokio-rs/console/issues/284)) ([d07aa89](https://github.com/tokio-rs/console/commit/d07aa89b168a120c47fb4bc88d6691a157406631)) +- Document minimum Tokio versions ([#291](https://github.com/tokio-rs/console/issues/291)) ([3b1f14a](https://github.com/tokio-rs/console/commit/3b1f14a50c507e7b5b672491fada6dfb067fc671), closes [#281](https://github.com/tokio-rs/console/issues/281)) + +### Fixed + +- Fix compilation on targets without 64-bit atomics ([#282](https://github.com/tokio-rs/console/issues/282)) ([5590fdb](https://github.com/tokio-rs/console/commit/5590fdbc3e7f78c6a3800f0e07c148320447788e), fixes [#279](https://github.com/tokio-rs/console/issues/279)) +- Bail rather than panic when encountering clock skew ([#287](https://github.com/tokio-rs/console/issues/287)) ([24db8c6](https://github.com/tokio-rs/console/commit/24db8c603fc86199f54a074a08390c68d1aa04e1), fixes [#286](https://github.com/tokio-rs/console/issues/286)) +- Use monotonic `Instant`s for all timestamps ([#288](https://github.com/tokio-rs/console/issues/288)) ([abc0830](https://github.com/tokio-rs/console/commit/abc083000cb6de51e37d5037283e97ed0e27249e), fixes [#286](https://github.com/tokio-rs/console/issues/286)) +- Record timestamps for updates last ([#289](https://github.com/tokio-rs/console/issues/289)) ([703f1aa](https://github.com/tokio-rs/console/commit/703f1aa449c7579d15af8adfbfc172e75da99281), fixes [#266](https://github.com/tokio-rs/console/issues/266)) + +## console-subscriber-v0.1.2 - (2022-02-07) + +[12a4821](https://github.com/tokio-rs/console/commit/12a4821a0058dd6e1a0e4f6729a0f78fac291e0e)...[e590df3](https://github.com/tokio-rs/console/commit/e590df39ca38cf795b1aec493403e1411e3b4766) + + +### Fixed + +- Console-api dependencies to require 0.1.2 ([#274](https://github.com/tokio-rs/console/issues/274)) ([b95f683](https://github.com/tokio-rs/console/commit/b95f683f0514978429535a75c86f8974b05a69aa)) + +## console-subscriber-v0.1.1 - (2022-01-18) + +[d3a410e](https://github.com/tokio-rs/console/commit/d3a410e5aaeb96fd061f47ae61fdadcce5b195d7)...[12a4821](https://github.com/tokio-rs/console/commit/12a4821a0058dd6e1a0e4f6729a0f78fac291e0e) + + +### Fixed + +- Use saturating arithmetic for attribute updates ([#234](https://github.com/tokio-rs/console/issues/234)) ([fe82e17](https://github.com/tokio-rs/console/commit/fe82e1704686ccbcdabaa1715cf30c5ce15cc17a)) +- Increase default event buffer capacity a bit ([#235](https://github.com/tokio-rs/console/issues/235)) ([0cf0aee](https://github.com/tokio-rs/console/commit/0cf0aee31af1cf6992e98db8269fbfcec2d54961)) +- Only send *new* tasks/resources/etc over the event channel ([#238](https://github.com/tokio-rs/console/issues/238)) ([fdc77e2](https://github.com/tokio-rs/console/commit/fdc77e28d45da73595320fab8ce56f982c562bb6)) + +## console-subscriber-v0.1.0 - (2021-12-16) + + +### Added + +- Assert `tokio-unstable` is on ([776966e](https://github.com/tokio-rs/console/commit/776966ea1444525490b9f060e96555809d44cf26)) +- Send structured fields on the wire ([#26](https://github.com/tokio-rs/console/issues/26)) ([38adbd9](https://github.com/tokio-rs/console/commit/38adbd97aefc53d06e509c7b33c98b4dcfa7a970), fixes [#6](https://github.com/tokio-rs/console/issues/6)) +- Drop data for completed tasks ([#31](https://github.com/tokio-rs/console/issues/31)) ([94aad1c](https://github.com/tokio-rs/console/commit/94aad1c88e9f97e08ef513449e1399092187da21)) +- Emit waker stats ([#44](https://github.com/tokio-rs/console/issues/44)) ([2d2716b](https://github.com/tokio-rs/console/commit/2d2716badf35e3c887c8ab8dfd6ab64a721c6cf5), closes [#42](https://github.com/tokio-rs/console/issues/42)) +- Record and send poll times with HdrHistogram ([#47](https://github.com/tokio-rs/console/issues/47)) ([94e7834](https://github.com/tokio-rs/console/commit/94e7834db44c3b19c54ff16a22f1b0e6464be1a2), closes [#36](https://github.com/tokio-rs/console/issues/36)) +- Correctly reflect busy and idle times ([#60](https://github.com/tokio-rs/console/issues/60)) ([e48f005](https://github.com/tokio-rs/console/commit/e48f005cf6ed88cac94465b7ba56ad05477fd9b6), fixes [#59](https://github.com/tokio-rs/console/issues/59)) +- Support multiple task callsites ([#68](https://github.com/tokio-rs/console/issues/68)) ([6b835e7](https://github.com/tokio-rs/console/commit/6b835e765fb43e9cf0dafef97ff3edf9042b7da7)) +- Use sequential `u64` task IDs ([#75](https://github.com/tokio-rs/console/issues/75)) ([c2c486e](https://github.com/tokio-rs/console/commit/c2c486ee9c792453db81786490bff52a031be9e9)) +- Remove fmt layer from init ([#64](https://github.com/tokio-rs/console/issues/64)) ([778a8f1](https://github.com/tokio-rs/console/commit/778a8f1fd60c1b92628cef59b021abf3fb0449a4)) +- Add ability to record events to a file ([#86](https://github.com/tokio-rs/console/issues/86)) ([4fc72c0](https://github.com/tokio-rs/console/commit/4fc72c011ae5552ac4bd97cb69354f4205e1107f), closes [#84](https://github.com/tokio-rs/console/issues/84)) +- Implement more design ideas from #25 ([#91](https://github.com/tokio-rs/console/issues/91)) ([ef9eafa](https://github.com/tokio-rs/console/commit/ef9eafad1e54acd6221d644e26ae7c01ce2eaed9)) +- Periodically shrink growable collections ([#94](https://github.com/tokio-rs/console/issues/94)) ([9f7d499](https://github.com/tokio-rs/console/commit/9f7d4998106427170458fb1737dbd5e7ae16c1a4)) +- Remove trace event calls from the subscriber ([#95](https://github.com/tokio-rs/console/issues/95)) ([246fc45](https://github.com/tokio-rs/console/commit/246fc45a76d6afb2ee6537b2ee73004570ffcbc9), closes [#27](https://github.com/tokio-rs/console/issues/27)) +- Accept durations with units ([#93](https://github.com/tokio-rs/console/issues/93)) ([e590f83](https://github.com/tokio-rs/console/commit/e590f8318cc4ab6346d67f4f4c98a8b4d47c1d58)) +- Add pause and resume ([#78](https://github.com/tokio-rs/console/issues/78)) ([1738481](https://github.com/tokio-rs/console/commit/173848173207afffce06c04a2ebfaa834794c6b1), closes [#85](https://github.com/tokio-rs/console/issues/85)) +- Spill callsites into hash set ([#97](https://github.com/tokio-rs/console/issues/97)) ([5fe4437](https://github.com/tokio-rs/console/commit/5fe443768dc9a63de2f6e66cf711e6fc535e8678)) +- Resource instrumentation ([#77](https://github.com/tokio-rs/console/issues/77)) ([f4a21ac](https://github.com/tokio-rs/console/commit/f4a21acb18935af8b256999e2380eb5fb7e17d72)) +- Represent readiness as bool ([#103](https://github.com/tokio-rs/console/issues/103)) ([ba95a38](https://github.com/tokio-rs/console/commit/ba95a38251a92ac3988333ab04655fa59d404937)) +- Emit and show self-wake counts for tasks ([#112](https://github.com/tokio-rs/console/issues/112)) ([4023ad3](https://github.com/tokio-rs/console/commit/4023ad3be3db3a600f9351f3cdd3d25b45b3d6cb), closes [#55](https://github.com/tokio-rs/console/issues/55)) +- Look at event parents to determine resource id ([#114](https://github.com/tokio-rs/console/issues/114)) ([0685482](https://github.com/tokio-rs/console/commit/06854828198fe3ab996c39d7bd7eaa7e87cffcae)) +- Name tasks spawned by the console subscriber ([#117](https://github.com/tokio-rs/console/issues/117)) ([05b9f5b](https://github.com/tokio-rs/console/commit/05b9f5bf2accba58da97a4b08d4ab500892465b7)) +- Add `retain-for` cmd line arg ([#119](https://github.com/tokio-rs/console/issues/119)) ([7231a33](https://github.com/tokio-rs/console/commit/7231a33268d409e4188c49b91ae8fc77c2889df6)) +- Use per-layer filtering ([#140](https://github.com/tokio-rs/console/issues/140)) ([f2c30d5](https://github.com/tokio-rs/console/commit/f2c30d52c9f22de69bac38009a9183135808806c), closes [#76](https://github.com/tokio-rs/console/issues/76)) +- Use `Location` for tasks and resources ([#154](https://github.com/tokio-rs/console/issues/154)) ([08c5186](https://github.com/tokio-rs/console/commit/08c5186eb01f18f8e4018058d12817e4127dd7be)) +- Enable spans with names starting with `runtime` ([#156](https://github.com/tokio-rs/console/issues/156)) ([3c50514](https://github.com/tokio-rs/console/commit/3c50514060724e0655d44b58f16fd282d84ce43b)) +- Resources UI ([#145](https://github.com/tokio-rs/console/issues/145)) ([577fb55](https://github.com/tokio-rs/console/commit/577fb55e48de052b9cd186476f092c76317bc09f)) +- Do not print errors when we cannot determine task context ([#186](https://github.com/tokio-rs/console/issues/186)) ([bdcdcb1](https://github.com/tokio-rs/console/commit/bdcdcb1675b80758c2177dfb5e71426957f02cee)) +- Unify build, init, and the Layer system ([#195](https://github.com/tokio-rs/console/issues/195)) ([457f525](https://github.com/tokio-rs/console/commit/457f525fd59bc9683a6dda7fcdb2bc225ee2cf71), closes [#183](https://github.com/tokio-rs/console/issues/183), closes [#196](https://github.com/tokio-rs/console/issues/196)) +- Add resource detail view ([#188](https://github.com/tokio-rs/console/issues/188)) ([1aa9b59](https://github.com/tokio-rs/console/commit/1aa9b594f30e42098c6c6bbf41eb1d2b01dc0426)) +- Rename `TasksLayer` to `ConsoleLayer` ([#207](https://github.com/tokio-rs/console/issues/207)) ([fbadf2f](https://github.com/tokio-rs/console/commit/fbadf2fe795a822c0843789b3113d9c297883345)) +- Count dropped events due to buffer cap ([#211](https://github.com/tokio-rs/console/issues/211)) ([aa09600](https://github.com/tokio-rs/console/commit/aa09600b3bdc6591eafc9fe7b4507f7da2bca498)) + +### Documented + +- Add some misbehaving options to example app ([#54](https://github.com/tokio-rs/console/issues/54)) ([5568bf6](https://github.com/tokio-rs/console/commit/5568bf6cdfd22af57b5dd6d0ef283466ec77058c)) +- Add Netlify auto-publishing of `main` API docs ([#116](https://github.com/tokio-rs/console/issues/116)) ([b0c5a9d](https://github.com/tokio-rs/console/commit/b0c5a9d269b571459395d7ef08b7c09f53adc39b)) +- Add a README (and `lib.rs` docs) ([#202](https://github.com/tokio-rs/console/issues/202)) ([a79c505](https://github.com/tokio-rs/console/commit/a79c5056875a3593b4fd61d18e42c2aa6a08688c)) +- Use H1 headers in builder API docs ([#204](https://github.com/tokio-rs/console/issues/204)) ([6261e15](https://github.com/tokio-rs/console/commit/6261e15b6b7e2eab3a235a8d7693ca61855d03e7)) +- Console-subscriber API docs ([#198](https://github.com/tokio-rs/console/issues/198)) ([7d16ead](https://github.com/tokio-rs/console/commit/7d16eadc5c254f21b0f4fba31f2fdf758808a8f4)) + +### Fixed + +- Fix busy loop in aggregator task ([#17](https://github.com/tokio-rs/console/issues/17)) ([fff4698](https://github.com/tokio-rs/console/commit/fff46989221f0eea53303abaf08e6e2f29476500)) +- Use correct timestamps for Stats::to_proto ([#19](https://github.com/tokio-rs/console/issues/19)) ([90d38b1](https://github.com/tokio-rs/console/commit/90d38b169f61982f0158aa3ae4f23b039cd96102)) +- Require tokio >= 1.5 ([#22](https://github.com/tokio-rs/console/issues/22)) ([62dec4a](https://github.com/tokio-rs/console/commit/62dec4af406df453924be1133cef2963c6979999)) +- Update uncompleted tasks total time every update ([#28](https://github.com/tokio-rs/console/issues/28)) ([d7f1629](https://github.com/tokio-rs/console/commit/d7f16293d939886e1f16afb80fc92033473e6312)) +- Detect completed tasks even if console connects after ([#29](https://github.com/tokio-rs/console/issues/29)) ([53515a7](https://github.com/tokio-rs/console/commit/53515a7d9532e8b9780b56ab95d067309b46dc6f)) +- Consider by-value wakes to be waker drops ([#46](https://github.com/tokio-rs/console/issues/46)) ([aeaecf5](https://github.com/tokio-rs/console/commit/aeaecf5467c188acde1c14a18261eade864bcdb9)) +- Enable `runtime::` target for tracing events ([#99](https://github.com/tokio-rs/console/issues/99)) ([0da7243](https://github.com/tokio-rs/console/commit/0da72436ee42a11ab32003efa1353b1de52691fb)) +- Remove backticks from mangled PR review suggestion ([#105](https://github.com/tokio-rs/console/issues/105)) ([1ad57af](https://github.com/tokio-rs/console/commit/1ad57af03fd007a2357eb299e3c8f254dc10f302)) +- Include tracing events starting with tokio in filter ([#159](https://github.com/tokio-rs/console/issues/159)) ([6786d3e](https://github.com/tokio-rs/console/commit/6786d3e86966ff0524a3ed855caeff864be12b15), closes [#149](https://github.com/tokio-rs/console/issues/149)) +- Remove chrono from deps and sub-deps ([#175](https://github.com/tokio-rs/console/issues/175)) ([c4e3302](https://github.com/tokio-rs/console/commit/c4e3302a118e5da030686cdd8a68cb8c55629567)) +- Unset default dispatcher in span callbacks ([#170](https://github.com/tokio-rs/console/issues/170)) ([3170432](https://github.com/tokio-rs/console/commit/31704326f2e8665a7f062ceca84bf8d843007017)) +- Fix potential spurious flush notifications ([#178](https://github.com/tokio-rs/console/issues/178)) ([c5e9b37](https://github.com/tokio-rs/console/commit/c5e9b375540bdb9a08370fe5d305d77efe0a63a7)) +- Ignore spans that weren't initially recorded ([0cd7a2f](https://github.com/tokio-rs/console/commit/0cd7a2f76bcac4c609771d20f4c0fb21f10b62d4)) +- Ignore exiting spans that were never entered ([ad442e2](https://github.com/tokio-rs/console/commit/ad442e2852065b6c5d7a770d2ef68439945354c7)) + + diff --git a/console-subscriber/Cargo.toml b/console-subscriber/Cargo.toml index aec867f43..d3c414e3b 100644 --- a/console-subscriber/Cargo.toml +++ b/console-subscriber/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "console-subscriber" -version = "0.1.6" +version = "0.1.10" license = "MIT" edition = "2021" -rust-version = "1.58.0" +rust-version = "1.60.0" authors = ["Eliza Weisman ", "Tokio Contributors ",] readme = "README.md" repository = "https://github.com/tokio-rs/console/" @@ -32,11 +32,11 @@ env-filter = ["tracing-subscriber/env-filter"] [dependencies] crossbeam-utils = "0.8.7" -tokio = { version = "^1.15", features = ["sync", "time", "macros", "tracing"] } -tokio-stream = "0.1" +tokio = { version = "^1.21", features = ["sync", "time", "macros", "tracing"] } +tokio-stream = { version = "0.1", features = ["net"] } 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.5.0", path = "../console-api", features = ["transport"] } +tonic = { version = "0.9", features = ["transport"] } tracing-core = "0.1.24" tracing = "0.1.26" tracing-subscriber = { version = "0.3.11", default-features = false, features = ["fmt", "registry"] } @@ -44,9 +44,9 @@ 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.10.0" +prost-types = "0.11.0" # Required for recording: serde = { version = "1", features = ["derive"] } @@ -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/README.md b/console-subscriber/README.md index 9d9e5224c..bb518afdd 100644 --- a/console-subscriber/README.md +++ b/console-subscriber/README.md @@ -34,7 +34,7 @@ system consists of two primary components: * _instrumentation_, embedded in the application, which collects data from the async runtime and exposes it over the console's [wire format] * _consumers_, such as the [`tokio-console`] command-line application, which - connect to the instrumented application, recieve telemetry data, and display + connect to the instrumented application, receive telemetry data, and display it to the user This crate implements the instrumentation-side interface using data @@ -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 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 c9f54c273..f2c62a81f 100644 --- a/console-subscriber/examples/barrier.rs +++ b/console-subscriber/examples/barrier.rs @@ -14,11 +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; - let wait_result = c.wait().await; - wait_result - })); + 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 @@ -34,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/long_scheduled.rs b/console-subscriber/examples/long_scheduled.rs new file mode 100644 index 000000000..d8cf79144 --- /dev/null +++ b/console-subscriber/examples/long_scheduled.rs @@ -0,0 +1,78 @@ +//! Long scheduled time +//! +//! This example shows an application with a task that has an excessive +//! time between being woken and being polled. +//! +//! It consists of a channel where a sender task sends a message +//! through the channel and then immediately does a lot of work +//! (simulated in this case by a call to `std::thread::sleep`). +//! +//! As soon as the sender task calls `send()` the receiver task gets +//! woken, but because there's only a single worker thread, it doesn't +//! get polled until after the sender task has finished "working" and +//! yields (via `tokio::time::sleep`). +//! +//! In the console, this is visible in the `rx` task, which has very +//! high scheduled times - in the detail screen you will see that around +//! it is scheduled around 98% of the time. The `tx` task, on the other +//! hand, is busy most of the time. +use std::time::Duration; + +use console_subscriber::ConsoleLayer; +use tokio::{sync::mpsc, task}; +use tracing::info; + +#[tokio::main(flavor = "multi_thread", worker_threads = 1)] +async fn main() -> Result<(), Box> { + ConsoleLayer::builder() + .with_default_env() + .publish_interval(Duration::from_millis(100)) + .init(); + + let (tx, rx) = mpsc::channel::(1); + let count = 10000; + + let jh_rx = task::Builder::new() + .name("rx") + .spawn(receiver(rx, count)) + .unwrap(); + let jh_tx = task::Builder::new() + .name("tx") + .spawn(sender(tx, count)) + .unwrap(); + + let res_tx = jh_tx.await; + let res_rx = jh_rx.await; + info!( + "main: Joined sender: {:?} and receiver: {:?}", + res_tx, res_rx, + ); + + tokio::time::sleep(Duration::from_millis(200)).await; + + Ok(()) +} + +async fn sender(tx: mpsc::Sender, count: u32) { + info!("tx: started"); + + for idx in 0..count { + let msg: u32 = idx; + let res = tx.send(msg).await; + info!("tx: sent msg '{}' result: {:?}", msg, res); + + std::thread::sleep(Duration::from_millis(5000)); + info!("tx: work done"); + + tokio::time::sleep(Duration::from_millis(100)).await; + } +} + +async fn receiver(mut rx: mpsc::Receiver, count: u32) { + info!("rx: started"); + + for _ in 0..count { + let msg = rx.recv().await; + info!("rx: Received message: '{:?}'", msg); + } +} diff --git a/console-subscriber/examples/long_sleep.rs b/console-subscriber/examples/long_sleep.rs new file mode 100644 index 000000000..d2ee48583 --- /dev/null +++ b/console-subscriber/examples/long_sleep.rs @@ -0,0 +1,48 @@ +use std::time::Duration; + +use console_subscriber::ConsoleLayer; +use tokio::task::{self, yield_now}; +use tracing::info; + +#[tokio::main(flavor = "multi_thread", worker_threads = 2)] +async fn main() -> Result<(), Box> { + ConsoleLayer::builder() + .with_default_env() + .publish_interval(Duration::from_millis(100)) + .init(); + + let long_sleeps = task::Builder::new() + .name("long-sleeps") + .spawn(long_sleeps(5000)) + .unwrap(); + + let sleep_forever = task::Builder::new() + .name("sleep-forever") + .spawn(sleep_forever(5000)) + .unwrap(); + + match (long_sleeps.await, sleep_forever.await) { + (Ok(_), Ok(_)) => info!("Success"), + (_, _) => info!("Error awaiting tasks."), + } + + tokio::time::sleep(Duration::from_millis(200)).await; + + Ok(()) +} + +async fn long_sleeps(inc: u64) { + let millis = inc; + loop { + std::thread::sleep(Duration::from_millis(millis)); + + yield_now().await; + } +} + +async fn sleep_forever(inc: u64) { + let millis = inc; + loop { + std::thread::sleep(Duration::from_millis(millis)); + } +} 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/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/aggregator/id_data.rs b/console-subscriber/src/aggregator/id_data.rs index b9010b445..2ad2c74b0 100644 --- a/console-subscriber/src/aggregator/id_data.rs +++ b/console-subscriber/src/aggregator/id_data.rs @@ -104,18 +104,18 @@ impl IdData { if let Some(dropped_at) = stats.dropped_at() { let dropped_for = now.checked_duration_since(dropped_at).unwrap_or_default(); let dirty = stats.is_unsent(); - let should_drop = + let should_retain = // if there are any clients watching, retain all dirty tasks regardless of age (dirty && has_watchers) - || dropped_for > retention; + || dropped_for <= retention; tracing::trace!( stats.id = ?id, stats.dropped_at = ?dropped_at, stats.dropped_for = ?dropped_for, stats.dirty = dirty, - should_drop, + should_retain, ); - return !should_drop; + return should_retain; } true diff --git a/console-subscriber/src/aggregator/mod.rs b/console-subscriber/src/aggregator/mod.rs index cf126b1df..d423720f1 100644 --- a/console-subscriber/src/aggregator/mod.rs +++ b/console-subscriber/src/aggregator/mod.rs @@ -22,7 +22,13 @@ mod shrink; use self::id_data::{IdData, Include}; use self::shrink::{ShrinkMap, ShrinkVec}; -pub(crate) struct Aggregator { +/// Aggregates instrumentation traces and prepares state for the instrument +/// server. +/// +/// The `Aggregator` is responsible for receiving and organizing the +/// instrumentated events and preparing the data to be served to a instrument +/// client. +pub struct Aggregator { /// Channel of incoming events emitted by `TaskLayer`s. events: mpsc::Receiver, @@ -157,9 +163,22 @@ impl Aggregator { } } - pub(crate) async fn run(mut self) { + /// Runs the aggregator. + /// + /// This method will start the aggregator loop and should run as long as + /// the instrument server is running. If the instrument server stops, + /// this future can be aborted. + pub async fn run(mut self) { let mut publish = tokio::time::interval(self.publish_interval); loop { + // We want to apply pressure to clear out the event queue faster, + // as the channel becomes more full. + let fill_percentage = self.shared.fill_percentage.load(Relaxed) as u32; + // The fuller the channel is, the smaller the sleep interval is, + // leading to more frequent wake-ups. + let pressure_sleep = + tokio::time::sleep(self.publish_interval * (100 - fill_percentage) / 100); + let should_send = tokio::select! { // if the flush interval elapses, flush data to the client _ = publish.tick() => { @@ -169,6 +188,11 @@ impl Aggregator { } } + // if the pressure timer elapses, flush the queue + _ = pressure_sleep => { + false + } + // triggered when the event buffer is approaching capacity _ = self.shared.flush.should_flush.notified() => { tracing::debug!("approaching capacity; draining buffer"); @@ -327,6 +351,7 @@ impl Aggregator { task_id: Some(id.clone().into()), now, poll_times_histogram: Some(stats.poll_duration_histogram()), + scheduled_times_histogram: Some(stats.scheduled_duration_histogram()), }) { self.details_watchers @@ -374,6 +399,7 @@ impl Aggregator { task_id: Some(id.clone().into()), now: Some(self.base_time.to_timestamp(Instant::now())), poll_times_histogram: Some(task_stats.poll_duration_histogram()), + scheduled_times_histogram: Some(task_stats.scheduled_duration_histogram()), }; watchers.retain(|watch| watch.update(&details)); !watchers.is_empty() diff --git a/console-subscriber/src/builder.rs b/console-subscriber/src/builder.rs index 2c9fb0cc6..95123cd52 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, @@ -21,6 +23,11 @@ pub struct Builder { /// the aggregator task. pub(super) event_buffer_capacity: usize, + /// Percentage of the event buffer's fill level the aggregator task + /// attempts not to cross. As fill percentage gets closer to this value, + /// the aggregator will wake up more often. + pub(super) event_buffer_target_fill: f64, + /// The maximum number of updates to buffer per-client before the client is /// dropped. pub(super) client_buffer_capacity: usize, @@ -32,7 +39,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, @@ -48,17 +55,25 @@ pub struct Builder { /// Any polls exceeding this duration will be clamped to this value. Higher /// values will result in more memory usage. pub(super) poll_duration_max: Duration, + + /// The maximum value for the task scheduled duration histogram. + /// + /// Any scheduled times exceeding this duration will be clamped to this + /// value. Higher values will result in more memory usage. + pub(super) scheduled_duration_max: Duration, } impl Default for Builder { fn default() -> Self { Self { event_buffer_capacity: ConsoleLayer::DEFAULT_EVENT_BUFFER_CAPACITY, + event_buffer_target_fill: ConsoleLayer::DEFAULT_EVENT_BUFFER_TARGET_FILL, client_buffer_capacity: ConsoleLayer::DEFAULT_CLIENT_BUFFER_CAPACITY, 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), + scheduled_duration_max: ConsoleLayer::DEFAULT_SCHEDULED_DURATION_MAX, + 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, @@ -80,6 +95,29 @@ impl Builder { } } + /// Sets the target fraction for the desired fill level of channel of + /// events from subscriber layers to the aggregator task. + /// + /// Acceptable values are within `[0; 1]` range. + /// + /// When the channel's size approaches `event_buffer_target_fill`, the + /// aggregator task will be woken up more frequently to prevent the buffer + /// from overfilling. + /// + /// Having sufficient amount of spare capacity is a good idea to be able to + /// handle spikes of incoming events, because otherwise the buffer would + /// overflow and those events would get dropped. + /// + /// By default, this is + /// [`ConsoleLayer::DEFAULT_EVENT_BUFFER_TARGET_FILL`]. + pub fn event_buffer_target_fill(self, event_buffer_target_fill: f64) -> Self { + assert!((0.0..=1.0).contains(&event_buffer_target_fill)); + Self { + event_buffer_target_fill, + ..self + } + } + /// Sets the maximum capacity of updates to buffer for each subscribed /// client, if that client is not reading from the RPC stream. /// @@ -137,8 +175,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 @@ -203,6 +271,23 @@ impl Builder { } } + /// Sets the maximum value for task scheduled duration histograms. + /// + /// Any scheduled duration (the time from a task being woken until it is next + /// polled) exceeding this value will be clamped down to this duration + /// and recorded as an outlier. + /// + /// By default, this is [one second]. Higher values will increase per-task + /// memory usage. + /// + /// [one second]: ConsoleLayer::DEFAULT_SCHEDULED_DURATION_MAX + pub fn scheduled_duration_histogram_max(self, max: Duration) -> Self { + Self { + scheduled_duration_max: max, + ..self + } + } + /// Sets whether tasks, resources, and async ops from the console /// subscriber thread are recorded. /// @@ -231,11 +316,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 +544,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/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] diff --git a/console-subscriber/src/lib.rs b/console-subscriber/src/lib.rs index 748b045d3..006ab690b 100644 --- a/console-subscriber/src/lib.rs +++ b/console-subscriber/src/lib.rs @@ -1,11 +1,11 @@ #![doc = include_str!("../README.md")] use console_api as proto; -use proto::resources::resource; +use proto::{instrument::instrument_server::InstrumentServer, resources::resource}; use serde::Serialize; use std::{ cell::RefCell, fmt, - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{IpAddr, Ipv4Addr}, sync::{ atomic::{AtomicUsize, Ordering}, Arc, @@ -13,7 +13,14 @@ use std::{ time::{Duration, Instant}, }; use thread_local::ThreadLocal; -use tokio::sync::{mpsc, oneshot}; +#[cfg(unix)] +use tokio::net::UnixListener; +use tokio::{ + sync::{mpsc, oneshot}, + task::JoinHandle, +}; +#[cfg(unix)] +use tokio_stream::wrappers::UnixListenerStream; use tracing_core::{ span::{self, Id}, subscriber::{self, Subscriber}, @@ -21,7 +28,7 @@ use tracing_core::{ }; use tracing_subscriber::{ layer::Context, - registry::{Extensions, LookupSpan, SpanRef}, + registry::{Extensions, LookupSpan}, Layer, }; @@ -35,8 +42,8 @@ mod stats; pub(crate) mod sync; mod visitors; -use aggregator::Aggregator; -pub use builder::Builder; +pub use aggregator::Aggregator; +pub use builder::{Builder, ServerAddr}; use callsites::Callsites; use record::Recorder; use stack::SpanStack; @@ -60,9 +67,9 @@ pub struct ConsoleLayer { current_spans: ThreadLocal>, tx: mpsc::Sender, shared: Arc, - /// When the channel capacity goes under this number, a flush in the aggregator + /// When the channel length goes under this number, a flush in the aggregator /// will be triggered. - flush_under_capacity: usize, + flush_under_len: usize, /// Set of callsites for spans representing spawned tasks. /// @@ -119,6 +126,11 @@ pub struct ConsoleLayer { /// /// By default, this is one second. max_poll_duration_nanos: u64, + + /// Maximum value for the scheduled time histogram. + /// + /// By default, this is one second. + max_scheduled_duration_nanos: u64, } /// A gRPC [`Server`] that implements the [`tokio-console` wire format][wire]. @@ -134,7 +146,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, } @@ -151,6 +163,18 @@ struct Shared { /// flushed. flush: aggregator::Flush, + /// Used to represent percentage of the target fill level the channel is + /// currently at. + /// + /// This is not a total fill level, given complete buffer capacity - this + /// value is scaled by `event_buffer_target_fill` value. + /// + /// As fill percentage gets higher, the aggregator task gets woken more + /// often, to avoid filling up to the flush point. While `flush` notifies + /// the aggregator task to flush immediately, this value is used as a soft + /// pressure to avoid hard flushes. + fill_percentage: AtomicUsize, + /// A counter of how many task events were dropped because the event buffer /// was at capacity. dropped_tasks: AtomicUsize, @@ -269,6 +293,7 @@ impl ConsoleLayer { ?config.recording_path, ?config.filter_env_var, ?config.poll_duration_max, + ?config.scheduled_duration_max, ?base_time, "configured console subscriber" ); @@ -277,9 +302,12 @@ impl ConsoleLayer { let (subscribe, rpcs) = mpsc::channel(256); let shared = Arc::new(Shared::default()); let aggregator = Aggregator::new(events, rpcs, &config, shared.clone(), base_time.clone()); - // Conservatively, start to trigger a flush when half the channel is full. // This tries to reduce the chance of losing events to a full channel. - let flush_under_capacity = config.event_buffer_capacity / 2; + // Note that `event_buffer_target_fill` is guaranteed to be in `0..=1` + // range, therefore the output valeus should be sane. + let flush_under_len = (config.event_buffer_capacity as f64 + * config.event_buffer_target_fill) + .trunc() as usize; let recorder = config .recording_path .as_ref() @@ -294,7 +322,7 @@ impl ConsoleLayer { current_spans: ThreadLocal::new(), tx, shared, - flush_under_capacity, + flush_under_len, spawn_callsites: Callsites::default(), waker_callsites: Callsites::default(), resource_callsites: Callsites::default(), @@ -306,6 +334,7 @@ impl ConsoleLayer { recorder, base_time, max_poll_duration_nanos: config.poll_duration_max.as_nanos() as u64, + max_scheduled_duration_nanos: config.scheduled_duration_max.as_nanos() as u64, }; (layer, server) } @@ -320,7 +349,13 @@ impl ConsoleLayer { /// events being dropped more frequently. /// /// See also [`Builder::event_buffer_capacity`]. - pub const DEFAULT_EVENT_BUFFER_CAPACITY: usize = 1024 * 100; + pub const DEFAULT_EVENT_BUFFER_CAPACITY: usize = 1024 * 10; + /// Default fraction for the acceptable fill capacity of the events sent + /// from [`ConsoleLayer`] to a [`Server`]. + /// + /// When `fraction` of the buffer capacity is exhausted, the aggregator + /// task will be woken up more frequently to bring the capacity down. + pub const DEFAULT_EVENT_BUFFER_TARGET_FILL: f64 = 0.1; /// Default maximum capacity for th echannel of events sent from a /// [`Server`] to each subscribed client. /// @@ -361,6 +396,15 @@ impl ConsoleLayer { /// See also [`Builder::poll_duration_histogram_max`]. pub const DEFAULT_POLL_DURATION_MAX: Duration = Duration::from_secs(1); + /// The default maximum value for the task scheduled duration histogram. + /// + /// Any scheduled duration (the time from a task being woken until it is next + /// polled) exceeding this will be clamped to this value. By default, the + /// maximum scheduled duration is one second. + /// + /// See also [`Builder::scheduled_duration_histogram_max`]. + pub const DEFAULT_SCHEDULED_DURATION_MAX: Duration = Duration::from_secs(1); + fn is_spawn(&self, meta: &'static Metadata<'static>) -> bool { self.spawn_callsites.contains(meta) } @@ -446,11 +490,16 @@ impl ConsoleLayer { } }; - let capacity = self.tx.capacity(); - if capacity <= self.flush_under_capacity { + let length = self.tx.max_capacity() - self.tx.capacity(); + if length >= self.flush_under_len { self.shared.flush.trigger(); } + let fill_percentage = core::cmp::min(length * 100 / self.flush_under_len, 100); + self.shared + .fill_percentage + .store(fill_percentage, Ordering::Relaxed); + sent } @@ -563,7 +612,11 @@ where fields: record::SerializeFields(fields.clone()), }); if let Some(stats) = self.send_stats(&self.shared.dropped_tasks, move || { - let stats = Arc::new(stats::TaskStats::new(self.max_poll_duration_nanos, at)); + let stats = Arc::new(stats::TaskStats::new( + self.max_poll_duration_nanos, + self.max_scheduled_duration_nanos, + at, + )); let event = Event::Spawn { id: id.clone(), stats: stats.clone(), @@ -765,87 +818,59 @@ where } fn on_enter(&self, id: &span::Id, cx: Context<'_, S>) { - fn update LookupSpan<'a>>( - span: &SpanRef, - at: Option, - ) -> Option { + if let Some(span) = cx.span(id) { + let now = Instant::now(); let exts = span.extensions(); // if the span we are entering is a task or async op, record the // poll stats. if let Some(stats) = exts.get::>() { - let at = at.unwrap_or_else(Instant::now); - stats.start_poll(at); - Some(at) + stats.start_poll(now); } else if let Some(stats) = exts.get::>() { - let at = at.unwrap_or_else(Instant::now); - stats.start_poll(at); - Some(at) - // otherwise, is the span a resource? in that case, we also want - // to enter it, although we don't care about recording poll - // stats. + stats.start_poll(now); } else if exts.get::>().is_some() { - Some(at.unwrap_or_else(Instant::now)) + // otherwise, is the span a resource? in that case, we also want + // to enter it, although we don't care about recording poll + // stats. } else { - None - } - } + return; + }; - if let Some(span) = cx.span(id) { - if let Some(now) = update(&span, None) { - if let Some(parent) = span.parent() { - update(&parent, Some(now)); - } - self.current_spans - .get_or_default() - .borrow_mut() - .push(id.clone()); - - self.record(|| record::Event::Enter { - id: id.into_u64(), - at: self.base_time.to_system_time(now), - }); - } + self.current_spans + .get_or_default() + .borrow_mut() + .push(id.clone()); + + self.record(|| record::Event::Enter { + id: id.into_u64(), + at: self.base_time.to_system_time(now), + }); } } fn on_exit(&self, id: &span::Id, cx: Context<'_, S>) { - fn update LookupSpan<'a>>( - span: &SpanRef, - at: Option, - ) -> Option { + if let Some(span) = cx.span(id) { let exts = span.extensions(); + let now = Instant::now(); // if the span we are entering is a task or async op, record the // poll stats. if let Some(stats) = exts.get::>() { - let at = at.unwrap_or_else(Instant::now); - stats.end_poll(at); - Some(at) + stats.end_poll(now); } else if let Some(stats) = exts.get::>() { - let at = at.unwrap_or_else(Instant::now); - stats.end_poll(at); - Some(at) + stats.end_poll(now); + } else if exts.get::>().is_some() { // otherwise, is the span a resource? in that case, we also want // to enter it, although we don't care about recording poll // stats. - } else if exts.get::>().is_some() { - Some(at.unwrap_or_else(Instant::now)) } else { - None - } - } + return; + }; - if let Some(span) = cx.span(id) { - if let Some(now) = update(&span, None) { - if let Some(parent) = span.parent() { - update(&parent, Some(now)); - } - self.current_spans.get_or_default().borrow_mut().pop(id); + self.current_spans.get_or_default().borrow_mut().pop(id); - self.record(|| record::Event::Exit { - id: id.into_u64(), - at: self.base_time.to_system_time(now), - }); - } + self.record(|| record::Event::Exit { + id: id.into_u64(), + at: self.base_time.to_system_time(now), + }); } } @@ -937,23 +962,159 @@ impl Server { /// /// [`tonic`]: https://docs.rs/tonic/ pub async fn serve_with( - mut self, + self, mut builder: tonic::transport::Server, ) -> Result<(), Box> { - let aggregate = self + let addr = self.addr.clone(); + let ServerParts { + instrument_server, + aggregator, + } = self.into_parts(); + let aggregate = spawn_named(aggregator.run(), "console::aggregate"); + let router = builder.add_service(instrument_server); + 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) + } + + /// Returns the parts needed to spawn a gRPC server and the aggregator that + /// supplies it. + /// + /// Note that a server spawned in this way will disregard any value set by + /// [`Builder::server_addr`], as the user becomes responsible for defining + /// the address when calling [`Router::serve`]. + /// + /// Additionally, the user of this API must ensure that the [`Aggregator`] + /// is running for as long as the gRPC server is. If the server stops + /// running, the aggregator task can be aborted. + /// + /// # Examples + /// + /// The parts can be used to serve the instrument server together with + /// other endpoints from the same gRPC server. + /// + /// ``` + /// use console_subscriber::{ConsoleLayer, ServerParts}; + /// + /// # let runtime = tokio::runtime::Builder::new_current_thread() + /// # .enable_all() + /// # .build() + /// # .unwrap(); + /// # runtime.block_on(async { + /// let (console_layer, server) = ConsoleLayer::builder().build(); + /// let ServerParts { + /// instrument_server, + /// aggregator, + /// .. + /// } = server.into_parts(); + /// + /// let aggregator_handle = tokio::spawn(aggregator.run()); + /// let router = tonic::transport::Server::builder() + /// //.add_service(some_other_service) + /// .add_service(instrument_server); + /// let serve = router.serve(std::net::SocketAddr::new( + /// std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)), + /// 6669, + /// )); + /// + /// // Finally, spawn the server. + /// tokio::spawn(serve); + /// # // Avoid a warning that `console_layer` and `aggregator_handle` are unused. + /// # drop(console_layer); + /// # let mut aggregator_handle = aggregator_handle; + /// # aggregator_handle.abort(); + /// # }); + /// ``` + /// + /// [`Router::serve`]: fn@tonic::transport::server::Router::serve + pub fn into_parts(mut self) -> ServerParts { + let aggregator = self .aggregator .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; - aggregate.abort(); - res?.map_err(Into::into) + + let instrument_server = proto::instrument::instrument_server::InstrumentServer::new(self); + + ServerParts { + instrument_server, + aggregator, + } + } +} + +/// Server Parts +/// +/// This struct contains the parts returned by [`Server::into_parts`]. It may contain +/// further parts in the future, an as such is marked as `non_exhaustive`. +/// +/// The `InstrumentServer` can be used to construct a router which +/// can be added to a [`tonic`] gRPC server. +/// +/// The `aggregator` is a future which should be running as long as the server is. +/// Generally, this future should be spawned onto an appropriate runtime and then +/// aborted if the server gets shut down. +/// +/// See the [`Server::into_parts`] documentation for usage. +#[non_exhaustive] +pub struct ServerParts { + /// The instrument server. + /// + /// See the documentation for [`InstrumentServer`] for details. + pub instrument_server: InstrumentServer, + + /// The aggregator. + /// + /// Responsible for collecting and preparing traces for the instrument server + /// to send its clients. + /// + /// The aggregator should be [`run`] when the instrument server is started. + /// If the server stops running for any reason, the aggregator task can be + /// aborted. + /// + /// [`run`]: fn@crate::Aggregator::run + pub aggregator: Aggregator, +} + +/// Aggregator handle. +/// +/// This object is returned from [`Server::into_parts`]. It can be +/// used to abort the aggregator task. +/// +/// The aggregator collects the traces that implement the async runtime +/// being observed and prepares them to be served by the gRPC server. +/// +/// Normally, if the server, started with [`Server::serve`] or +/// [`Server::serve_with`] stops for any reason, the aggregator is aborted, +/// hoewver, if the server was started with the [`InstrumentServer`] returned +/// from [`Server::into_parts`], then it is the responsibility of the user +/// of the API to stop the aggregator task by calling [`abort`] on this +/// object. +/// +/// [`abort`]: fn@crate::AggregatorHandle::abort +pub struct AggregatorHandle { + join_handle: JoinHandle<()>, +} + +impl AggregatorHandle { + /// Aborts the task running this aggregator. + /// + /// To avoid having a disconnected aggregator running forever, this + /// method should be called when the [`tonic::transport::Server`] started + /// with the [`InstrumentServer`] also returned from [`Server::into_parts`] + /// stops running. + pub fn abort(&mut self) { + self.join_handle.abort(); } } @@ -1063,7 +1224,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) diff --git a/console-subscriber/src/record.rs b/console-subscriber/src/record.rs index f5a7989b3..d58c7b4d3 100644 --- a/console-subscriber/src/record.rs +++ b/console-subscriber/src/record.rs @@ -95,7 +95,7 @@ fn record_io(file: File, rx: Receiver) -> io::Result<()> { }, )?; - // wait to recieve an event... + // wait to receive an event... while let Ok(event) = rx.recv() { // TODO: what to do if file error? write(&mut file, &event)?; diff --git a/console-subscriber/src/stats.rs b/console-subscriber/src/stats.rs index 32fb364f1..1a281b031 100644 --- a/console-subscriber/src/stats.rs +++ b/console-subscriber/src/stats.rs @@ -56,7 +56,7 @@ pub(crate) struct TaskStats { is_dropped: AtomicBool, // task stats pub(crate) created_at: Instant, - timestamps: Mutex, + dropped_at: Mutex>, // waker stats wakes: AtomicUsize, @@ -100,12 +100,6 @@ pub(crate) struct ResourceStats { pub(crate) parent_id: Option, } -#[derive(Debug, Default)] -struct TaskTimestamps { - dropped_at: Option, - last_wake: Option, -} - #[derive(Debug, Default)] struct PollStats { /// The number of polls in progress @@ -118,10 +112,13 @@ struct PollStats { #[derive(Debug, Default)] struct PollTimestamps { first_poll: Option, + last_wake: Option, last_poll_started: Option, last_poll_ended: Option, busy_time: Duration, - histogram: H, + scheduled_time: Duration, + poll_histogram: H, + scheduled_histogram: H, } #[derive(Debug)] @@ -132,8 +129,8 @@ struct Histogram { max_outlier: Option, } -trait RecordPoll { - fn record_poll_duration(&mut self, duration: Duration); +trait RecordDuration { + fn record_duration(&mut self, duration: Duration); } impl TimeAnchor { @@ -157,19 +154,26 @@ impl TimeAnchor { } impl TaskStats { - pub(crate) fn new(poll_duration_max: u64, created_at: Instant) -> Self { + pub(crate) fn new( + poll_duration_max: u64, + scheduled_duration_max: u64, + created_at: Instant, + ) -> Self { Self { is_dirty: AtomicBool::new(true), is_dropped: AtomicBool::new(false), created_at, - timestamps: Mutex::new(TaskTimestamps::default()), + dropped_at: Mutex::new(None), poll_stats: PollStats { timestamps: Mutex::new(PollTimestamps { - histogram: Histogram::new(poll_duration_max), + poll_histogram: Histogram::new(poll_duration_max), + scheduled_histogram: Histogram::new(scheduled_duration_max), first_poll: None, + last_wake: None, last_poll_started: None, last_poll_ended: None, busy_time: Duration::new(0, 0), + scheduled_time: Duration::new(0, 0), }), current_polls: AtomicUsize::new(0), polls: AtomicUsize::new(0), @@ -209,13 +213,14 @@ impl TaskStats { } fn wake(&self, at: Instant, self_wake: bool) { - let mut timestamps = self.timestamps.lock(); - timestamps.last_wake = cmp::max(timestamps.last_wake, Some(at)); - self.wakes.fetch_add(1, Release); + self.poll_stats.wake(at); + self.wakes.fetch_add(1, Release); if self_wake { - self.wakes.fetch_add(1, Release); + self.self_wakes.fetch_add(1, Release); } + + self.make_dirty(); } pub(crate) fn start_poll(&self, at: Instant) { @@ -235,17 +240,24 @@ impl TaskStats { return; } - let mut timestamps = self.timestamps.lock(); - let _prev = timestamps.dropped_at.replace(dropped_at); + let _prev = self.dropped_at.lock().replace(dropped_at); debug_assert_eq!(_prev, None, "tried to drop a task twice; this is a bug!"); self.make_dirty(); } pub(crate) fn poll_duration_histogram(&self) -> proto::tasks::task_details::PollTimesHistogram { - let hist = self.poll_stats.timestamps.lock().histogram.to_proto(); + let hist = self.poll_stats.timestamps.lock().poll_histogram.to_proto(); proto::tasks::task_details::PollTimesHistogram::Histogram(hist) } + pub(crate) fn scheduled_duration_histogram(&self) -> proto::tasks::DurationHistogram { + self.poll_stats + .timestamps + .lock() + .scheduled_histogram + .to_proto() + } + #[inline] fn make_dirty(&self) { self.is_dirty.swap(true, AcqRel); @@ -257,16 +269,28 @@ impl ToProto for TaskStats { fn to_proto(&self, base_time: &TimeAnchor) -> Self::Output { let poll_stats = Some(self.poll_stats.to_proto(base_time)); - let timestamps = self.timestamps.lock(); + let timestamps = self.poll_stats.timestamps.lock(); proto::tasks::Stats { poll_stats, created_at: Some(base_time.to_timestamp(self.created_at)), - dropped_at: timestamps.dropped_at.map(|at| base_time.to_timestamp(at)), + dropped_at: self.dropped_at.lock().map(|at| base_time.to_timestamp(at)), wakes: self.wakes.load(Acquire) as u64, waker_clones: self.waker_clones.load(Acquire) as u64, self_wakes: self.self_wakes.load(Acquire) as u64, waker_drops: self.waker_drops.load(Acquire) as u64, last_wake: timestamps.last_wake.map(|at| base_time.to_timestamp(at)), + scheduled_time: Some( + timestamps + .scheduled_time + .try_into() + .unwrap_or_else(|error| { + eprintln!( + "failed to convert `scheduled_time` to protobuf duration: {}", + error + ); + Default::default() + }), + ), } } } @@ -287,7 +311,7 @@ impl DroppedAt for TaskStats { // avoid acquiring the lock if we know we haven't tried to drop this // thing yet if self.is_dropped.load(Acquire) { - return self.timestamps.lock().dropped_at; + return *self.dropped_at.lock(); } None @@ -312,7 +336,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 } @@ -465,19 +489,42 @@ impl ToProto for ResourceStats { // === impl PollStats === -impl PollStats { - fn start_poll(&self, at: Instant) { - if self.current_polls.fetch_add(1, AcqRel) == 0 { - // We are starting the first poll - let mut timestamps = self.timestamps.lock(); - if timestamps.first_poll.is_none() { - timestamps.first_poll = Some(at); - } +impl PollStats { + fn wake(&self, at: Instant) { + let mut timestamps = self.timestamps.lock(); + timestamps.last_wake = cmp::max(timestamps.last_wake, Some(at)); + } - timestamps.last_poll_started = Some(at); + fn start_poll(&self, at: Instant) { + if self.current_polls.fetch_add(1, AcqRel) > 0 { + return; + } - self.polls.fetch_add(1, Release); + // We are starting the first poll + let mut timestamps = self.timestamps.lock(); + if timestamps.first_poll.is_none() { + timestamps.first_poll = Some(at); } + + timestamps.last_poll_started = Some(at); + + self.polls.fetch_add(1, Release); + + // If the last poll ended after the last wake then it was likely + // a self-wake, so we measure from the end of the last poll instead. + // This also ensures that `busy_time` and `scheduled_time` don't overlap. + let scheduled = match std::cmp::max(timestamps.last_wake, timestamps.last_poll_ended) { + Some(scheduled) => scheduled, + None => return, // Async operations record polls, but not wakes + }; + + // `at < scheduled` is possible when a task switches threads between polls. + let elapsed = at.saturating_duration_since(scheduled); + + // if we have a scheduled time histogram, add the timestamp + timestamps.scheduled_histogram.record_duration(elapsed); + + timestamps.scheduled_time += elapsed; } fn end_poll(&self, at: Instant) { @@ -512,7 +559,7 @@ impl PollStats { }; // if we have a poll time histogram, add the timestamp - timestamps.histogram.record_poll_duration(elapsed); + timestamps.poll_histogram.record_duration(elapsed); timestamps.busy_time += elapsed; } @@ -532,7 +579,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() + })), } } } @@ -592,8 +645,8 @@ impl Histogram { } } -impl RecordPoll for Histogram { - fn record_poll_duration(&mut self, duration: Duration) { +impl RecordDuration for Histogram { + fn record_duration(&mut self, duration: Duration) { let mut duration_ns = duration.as_nanos() as u64; // clamp the duration to the histogram's max value @@ -609,8 +662,8 @@ impl RecordPoll for Histogram { } } -impl RecordPoll for () { - fn record_poll_duration(&mut self, _: Duration) { +impl RecordDuration for () { + fn record_duration(&mut self, _: Duration) { // do nothing } } diff --git a/flake.lock b/flake.lock index f5660b1ca..3c0f64a8f 100644 --- a/flake.lock +++ b/flake.lock @@ -1,12 +1,15 @@ { "nodes": { "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1638122382, - "narHash": "sha256-sQzZzAbvKEqN9s0bzWuYmRaA03v40gaJ4+iL1LXjaeI=", + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "owner": "numtide", "repo": "flake-utils", - "rev": "74f7e4319258e287b0f9cb95426c9853b282730b", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", "type": "github" }, "original": { @@ -17,18 +20,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1639752787, - "narHash": "sha256-07kSWzpKtAzVvYyVdZ8MYUJDsU/G8IBu9USq0pNEHek=", - "owner": "nixos", + "lastModified": 1683594133, + "narHash": "sha256-iUhLhEAgOCnexSGDsYT2ouydis09uDoNzM7UC685XGE=", + "owner": "NixOS", "repo": "nixpkgs", - "rev": "28abc4e43a24d28729509e2d83f5c4f3b3418189", + "rev": "8d447c5626cfefb9b129d5b30103344377fe09bc", "type": "github" }, "original": { - "owner": "nixos", - "ref": "release-21.11", - "repo": "nixpkgs", - "type": "github" + "id": "nixpkgs", + "type": "indirect" } }, "root": { @@ -36,6 +37,21 @@ "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 0d956e7a4..f01107b94 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "The Tokio console: a debugger for async Rust."; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/release-21.11"; + # nixpkgs.url = "github:nixos/nixpkgs/release-21.11"; flake-utils = { url = "github:numtide/flake-utils"; inputs.nixpkgs.follows = "nixpkgs"; diff --git a/tokio-console/CHANGELOG.md b/tokio-console/CHANGELOG.md index f48e07a97..98e81f9ec 100644 --- a/tokio-console/CHANGELOG.md +++ b/tokio-console/CHANGELOG.md @@ -1,91 +1,132 @@ - -## 0.1.6 (2022-05-24) +# Changelog +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -#### Bug Fixes +## tokio-console-v0.1.9 - (2023-07-03) -* default `--no_colors` to `false` (#344) ([e58352fe](e58352fe)) +[5900300](https://github.com/tokio-rs/console/commit/59003004a6f2f2857be267061f23d34e2257e0f0)...[daa3d51](https://github.com/tokio-rs/console/commit/daa3d51895b52c11e1fd216becbc37b083e9758f) -#### Features -* add subcommand to gen shell completions (#336) ([df4d4683](df4d4683)) -* display outliers in histogram view (#351) ([dec891ff](dec891ff)) +### Added +- Help view modal ([#432](https://github.com/tokio-rs/console/issues/432)) ([5156e8e](https://github.com/tokio-rs/console/commit/5156e8e951521d1858929d4f52addda5f1a43941)) - -## 0.1.5 (2022-04-30) +### Documented +- Add column descriptions for all tables ([#431](https://github.com/tokio-rs/console/issues/431)) ([2de5b68](https://github.com/tokio-rs/console/commit/2de5b68d1a00a77d03a4817f955f385e494368bd)) -#### Bug Fixes +### Fixed -* always log to a file instead of `stderr` (#340) ([ef39b9a6](ef39b9a6), closes [#339](339)) +- Remove histogram minimum count ([#424](https://github.com/tokio-rs/console/issues/424)) ([2617504](https://github.com/tokio-rs/console/commit/26175044cca81cb4a8289841a0c3b458f2d287f1)) +- Remove trailing space from task/resource location ([#443](https://github.com/tokio-rs/console/issues/443)) ([29a09ad](https://github.com/tokio-rs/console/commit/29a09adf07eeb56be6233a7333d4cdee4fa954a2)) +- Make long locations readable ([#441](https://github.com/tokio-rs/console/issues/441)) ([daa3d51](https://github.com/tokio-rs/console/commit/daa3d51895b52c11e1fd216becbc37b083e9758f), closes [#411](https://github.com/tokio-rs/console/issues/411)) -#### Features +## tokio-console-v0.1.8 - (2023-05-09) -* add missing configurations to config file (#334) ([472ff52e](472ff52e), closes [#331](331)) -* emit a parse error a config file contains unknown fields (#330) ([3a67d476](3a67d476)) +[3bf60bc](https://github.com/tokio-rs/console/commit/3bf60bce7b478c189a3145311e06f14cb2fc1e11)...[5900300](https://github.com/tokio-rs/console/commit/59003004a6f2f2857be267061f23d34e2257e0f0) - -## 0.1.4 (2022-04-13) +### Added +- Reduce decimal digits in UI ([#402](https://github.com/tokio-rs/console/issues/402)) ([57b866d](https://github.com/tokio-rs/console/commit/57b866dd70ee36545ea0c02b02be872183cfa431)) +- Use tokio task ids in task views ([#403](https://github.com/tokio-rs/console/issues/403)) ([001fc49](https://github.com/tokio-rs/console/commit/001fc49f09ad78cc4ab50770cf4a677ae177103f)) +- Add support for Unix domain sockets ([#388](https://github.com/tokio-rs/console/issues/388)) ([bff8b8a](https://github.com/tokio-rs/console/commit/bff8b8a4291b0584ab4f97c5f91246eb9a68f262), closes [#296](https://github.com/tokio-rs/console/issues/296)) +- Add scheduled time per task ([#406](https://github.com/tokio-rs/console/issues/406)) ([ac20daa](https://github.com/tokio-rs/console/commit/ac20daaf301f80e87002593813965d11d11371e4)) +- Add task scheduled times histogram ([#409](https://github.com/tokio-rs/console/issues/409)) ([3b37dda](https://github.com/tokio-rs/console/commit/3b37dda773f8cd237f6759d193fdc83a75ab7653)) +- Update `tonic` to 0.9 ([#420](https://github.com/tokio-rs/console/issues/420)) ([54f6be9](https://github.com/tokio-rs/console/commit/54f6be985a248d3dd5a98a7624a2447d0547bc60)) +- Update MSRV to Rust 1.60.0 ([e3c5656](https://github.com/tokio-rs/console/commit/e3c56561a062be123be460dd477f512a6a9ec3cd)) -#### Features +### Documented -* add autogenerated example config file to docs (#327) ([79da280f](79da280f)) -* add `gen-config` subcommand to generate a config file (#324) ([e034f8d0](e034f8d0)) -* surface dropped event count if there are any (#316) ([16df5d30](16df5d30)) -* read configuration options from a config file (#320) ([defe3460](defe3460), closes [#310](310)) +- Update screenshots in README ([#419](https://github.com/tokio-rs/console/issues/419)) ([4f71484](https://github.com/tokio-rs/console/commit/4f714845f3cda0978b0b1cc850072878ed4f8599)) +- Revert "update screenshots in README ([#419](https://github.com/tokio-rs/console/issues/419))" ([7b86f7f](https://github.com/tokio-rs/console/commit/7b86f7f7d22d71ad7b677cdb2634c142c9bf5206)) +- Update screenshots in README ([#421](https://github.com/tokio-rs/console/issues/421)) ([f4d3213](https://github.com/tokio-rs/console/commit/f4d321397355a6e458e170e174ac420c26e6353a)) +### Fixed - -## 0.1.3 (2022-03-09) +- Fix ascii-only flipped input ([#377](https://github.com/tokio-rs/console/issues/377)) ([da0e972](https://github.com/tokio-rs/console/commit/da0e9724fa132595e2085cfb08ac7bfbf10542ba)) +- Declare `tokio-console` bin as `default-run` ([#379](https://github.com/tokio-rs/console/issues/379)) ([40f7971](https://github.com/tokio-rs/console/commit/40f7971d30451f7321b73a03222b71731dabc52a)) +- Make `retain_for` default to 6s if not specfied ([#383](https://github.com/tokio-rs/console/issues/383)) ([3248caa](https://github.com/tokio-rs/console/commit/3248caa8f8551e22c9d361e23cabd3c98aa143b6), fixes [#382](https://github.com/tokio-rs/console/issues/382)) +- Enable view-switching keystrokes on details views ([#387](https://github.com/tokio-rs/console/issues/387)) ([d98f159](https://github.com/tokio-rs/console/commit/d98f15956075a2d64f5cb96b1011eff7b3110e51)) +- Fix `ViewOptions` default lang' ([#394](https://github.com/tokio-rs/console/issues/394)) ([a7548d0](https://github.com/tokio-rs/console/commit/a7548d089812ac61602a31a699d14777d312ac6d), fixes [#393](https://github.com/tokio-rs/console/issues/393)) +- Fix calculation of busy time during poll ([#405](https://github.com/tokio-rs/console/issues/405)) ([6fa2185](https://github.com/tokio-rs/console/commit/6fa2185134c8791446a1f1b5dc2ee11d254966ad)) +## tokio-console-v0.1.7 - (2022-08-10) -#### Bug Fixes +[ce901c8](https://github.com/tokio-rs/console/commit/ce901c8f359d0de99430b51abd0cde63513de66a)...[3bf60bc](https://github.com/tokio-rs/console/commit/3bf60bce7b478c189a3145311e06f14cb2fc1e11) -* exit crossterm before printing panic messages (#307) ([43606b9a](43606b9a)) -* prevent panics if subscriber reports out-of-order times (#295) ([80d7f425](80d7f425)) -#### Features +### Added -* add icon representing column sorting state (#301) ([b9e0a226](b9e0a226)) +- Emit a parse error a config file contains unknown fields ([#330](https://github.com/tokio-rs/console/issues/330)) ([3a67d47](https://github.com/tokio-rs/console/commit/3a67d476835e4a1d3557190b85f5a89c760490bb)) +- Add missing configurations to config file ([#334](https://github.com/tokio-rs/console/issues/334)) ([472ff52](https://github.com/tokio-rs/console/commit/472ff52e6445dd2c103b218d60a4e0cad9a1972e), closes [#331](https://github.com/tokio-rs/console/issues/331)) +- Display outliers in histogram view ([#351](https://github.com/tokio-rs/console/issues/351)) ([dec891f](https://github.com/tokio-rs/console/commit/dec891ff080b135dc10919b2b59665989e73daf3)) +- Add subcommand to gen shell completions ([#336](https://github.com/tokio-rs/console/issues/336)) ([df4d468](https://github.com/tokio-rs/console/commit/df4d468375138b4115fb489b67ca72fbbd8f9ba1)) +- Only suggest opening issues for panics ([#365](https://github.com/tokio-rs/console/issues/365)) ([23cb6bf](https://github.com/tokio-rs/console/commit/23cb6bf7cdaafd3fe691e4a6f7f91cc17e169795)) +- Init error handling before subcmds ([#365](https://github.com/tokio-rs/console/issues/365)) ([6646568](https://github.com/tokio-rs/console/commit/66465689dceec509d9e1e37a55646a89285005e3)) +- Filter out boring frames in backtraces ([#365](https://github.com/tokio-rs/console/issues/365)) ([523a44a](https://github.com/tokio-rs/console/commit/523a44a30cf047fe0a56f746624df0cc3239a160)) +- Include config options in autogenerated issues ([#365](https://github.com/tokio-rs/console/issues/365)) ([fcb54df](https://github.com/tokio-rs/console/commit/fcb54dffda2a9f4c85cc82a24bff26e0777ceacc)) +### Documented - -## 0.1.2 (2022-02-18) +- Update minimal Rust version ([#338](https://github.com/tokio-rs/console/issues/338)) ([ff3b6db](https://github.com/tokio-rs/console/commit/ff3b6db6fa06456a14992663e8ff7ba8c80c1cc1)) +### Fixed -#### Bug Fixes +- Always log to a file instead of `stderr` ([#340](https://github.com/tokio-rs/console/issues/340)) ([ef39b9a](https://github.com/tokio-rs/console/commit/ef39b9a6419227d10e7a4d299ca95673ea200944), fixes [#339](https://github.com/tokio-rs/console/issues/339)) +- Default `--no_colors` to `false` ([#344](https://github.com/tokio-rs/console/issues/344)) ([e58352f](https://github.com/tokio-rs/console/commit/e58352fe5e205620f1fe43acaceaff9cf7913394)) -* console-api dependencies to require 0.1.2 (#274) ([b95f683f](b95f683f), closes [#270](270)) -* missing histogram in task details (#269) ([884f4eca](884f4eca), closes [#262](262)) +## tokio-console-v0.1.4 - (2022-04-13) +[3c55912](https://github.com/tokio-rs/console/commit/3c559121e3c5ad175471718a3cf87ada0146a7cd)...[ce901c8](https://github.com/tokio-rs/console/commit/ce901c8f359d0de99430b51abd0cde63513de66a) - -## 0.1.1 (2022-01-18) +### Breaking Changes +- **Update `tonic` to `0.7` ([#318](https://github.com/tokio-rs/console/issues/318))** ([83d8a87](https://github.com/tokio-rs/console/commit/83d8a870bcc40be71bc23d0f45fc374899c636a8))
`console-api` is now no longer compatible with projects using `prost` +0.9 or `tonic` 0.7. These crates must be updated to use `console-api` +0.2. +### Added -#### Features +- [**breaking**](#tokio-console-v0.1.4-breaking) Update `tonic` to `0.7` ([#318](https://github.com/tokio-rs/console/issues/318)) ([83d8a87](https://github.com/tokio-rs/console/commit/83d8a870bcc40be71bc23d0f45fc374899c636a8)) +- Read configuration options from a config file ([#320](https://github.com/tokio-rs/console/issues/320)) ([defe346](https://github.com/tokio-rs/console/commit/defe34609508086cdc527fbf813cbca4732d49cd), closes [#310](https://github.com/tokio-rs/console/issues/310)) +- Surface dropped event count if there are any ([#316](https://github.com/tokio-rs/console/issues/316)) ([16df5d3](https://github.com/tokio-rs/console/commit/16df5d30a011fc627e993ed71889877e70192baf)) +- Add `gen-config` subcommand to generate a config file ([#324](https://github.com/tokio-rs/console/issues/324)) ([e034f8d](https://github.com/tokio-rs/console/commit/e034f8d0967fc589e4a48425075b6ae9abb47fc8)) +- Add autogenerated example config file to docs ([#327](https://github.com/tokio-rs/console/issues/327)) ([79da280](https://github.com/tokio-rs/console/commit/79da280f4df9d1e0e6e3940e40b3faf123435a74)) -* feature-flag `tracing-journald` dependency (#250) ([24f25dbd](24f25dbd)) -* add vi style keybinds for tables (#223) ([1845c998](1845c998)) +### Documented -#### Bug Fixes +- Add tokio-console installation section ([#313](https://github.com/tokio-rs/console/issues/313)) ([d793903](https://github.com/tokio-rs/console/commit/d79390303590a534a8224efbe96c6023c336a32f)) -* fix task lookup in async ops view (#257) ([9a50b630](9a50b630)) -* don't make details requests with rewritten IDs (#251) ([4ec26a8d](4ec26a8d)) -* fix build error with journald enabled ([a931b7ec](a931b7ec)) -* increase default event buffer capacity a bit (#235) ([0cf0aee3](0cf0aee3)) -* wrap controls line when the terminal is too narrow (#231) ([ef415072](ef415072)) -* don't enable crossterm mouse capture (#222) ([e020d66c](e020d66c), closes [#167](167)) +## tokio-console-v0.1.3 - (2022-03-09) +[900a5c2](https://github.com/tokio-rs/console/commit/900a5c2bd5b610e9b939a5f824af1ac1a11267d0)...[3c55912](https://github.com/tokio-rs/console/commit/3c559121e3c5ad175471718a3cf87ada0146a7cd) -#### Changes -* move ID rewriting from `console-subscriber` to the client (#244) ([095b1ef](095b1ef)) +### Added - -## 0.1.0 (2021-12-16) +- Add icon representing column sorting state ([#301](https://github.com/tokio-rs/console/issues/301)) ([b9e0a22](https://github.com/tokio-rs/console/commit/b9e0a2266c98cd11a5261323dc20e04f17514b97)) -- Initial release! 🎉 +### Fixed + +- Prevent panics if subscriber reports out-of-order times ([#295](https://github.com/tokio-rs/console/issues/295)) ([80d7f42](https://github.com/tokio-rs/console/commit/80d7f4250ee5add0965ff100668be21d20621114)) +- Exit crossterm before printing panic messages ([#307](https://github.com/tokio-rs/console/issues/307)) ([43606b9](https://github.com/tokio-rs/console/commit/43606b9a7ccff0157325effbc48e1d71a194e5de)) + +## tokio-console-v0.1.2 - (2022-02-18) + +[e7b228d](https://github.com/tokio-rs/console/commit/e7b228d13b5da3885532ff5d42d7f41c90dcbcb0)...[900a5c2](https://github.com/tokio-rs/console/commit/900a5c2bd5b610e9b939a5f824af1ac1a11267d0) + + +### Added + +- Fix missing histogram in task details ([#269](https://github.com/tokio-rs/console/issues/269)) ([884f4ec](https://github.com/tokio-rs/console/commit/884f4ecac8cba7eee7f895024da4c6e28de75289)) + +### Documented + +- Document minimum Tokio versions ([#291](https://github.com/tokio-rs/console/issues/291)) ([3b1f14a](https://github.com/tokio-rs/console/commit/3b1f14a50c507e7b5b672491fada6dfb067fc671), closes [#281](https://github.com/tokio-rs/console/issues/281)) + +### Fixed + +- Console-api dependencies to require 0.1.2 ([#274](https://github.com/tokio-rs/console/issues/274)) ([b95f683](https://github.com/tokio-rs/console/commit/b95f683f0514978429535a75c86f8974b05a69aa)) + + diff --git a/tokio-console/Cargo.toml b/tokio-console/Cargo.toml index b5be2873e..52ec6b11a 100644 --- a/tokio-console/Cargo.toml +++ b/tokio-console/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "tokio-console" -version = "0.1.6" +version = "0.1.9" license = "MIT" repository = "https://github.com/tokio-rs/console" edition = "2021" -rust-version = "1.58.0" +rust-version = "1.60.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. @@ -27,18 +28,19 @@ keywords = [ [dependencies] atty = "0.2" -console-api = { version = "0.3.0", path = "../console-api", features = ["transport"] } +console-api = { version = "0.5.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.9", features = ["transport"] } futures = "0.3" -tui = { version = "0.16.0", default-features = false, features = ["crossterm"] } +ratatui = { version = "0.20.1", 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 } -prost-types = "0.10" -crossterm = { version = "0.20", features = ["event-stream"] } -color-eyre = { version = "0.5", features = ["issue-url"] } +prost-types = "0.11" +crossterm = { version = "0.26.1", features = ["event-stream"] } +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/tokio-console/README.md b/tokio-console/README.md index 95276af9f..12a7d466a 100644 --- a/tokio-console/README.md +++ b/tokio-console/README.md @@ -35,7 +35,7 @@ system consists of two primary components: collects data from the async runtime and exposes it over the console's wire format * 🛰️ _consumers_, which connect to the instrumented application, - recieve telemetry data, and display it to the user + receive telemetry data, and display it to the user This crate is the primary consumer of `tokio-console` telemetry, a command-line application that provides an interactive debugging interface. @@ -97,38 +97,107 @@ tokio-console http://my.instrumented.application.local:6669 See [here][cli-ref] for a complete list of all command-line arguments. +Tokio Console has a numnber of different views: +* [Tasks List](#tasks-list) +* [Task Details](#task-details) +* [Resources List](#resources-list) +* [Resource Details](#resource-details) + +### Tasks List + When the console CLI is launched, it displays a list of all [asynchronous tasks] in the program: -![tasks list](https://raw.githubusercontent.com/tokio-rs/console/main/assets/tasks_list.png) +![tasks list](https://raw.githubusercontent.com/tokio-rs/console/main/assets/tokio-console-0.1.8/tasks_list.png) + +Tasks are displayed in a table. + +* `Warn` - The number of warnings active for the task. +* `ID` - The ID of the task. This is the same as the value returned by the unstable [`tokio::task::Id`](https://docs.rs/tokio/latest/tokio/task/struct.Id.html) API (see documentation for details). +* `State` - The state of the task. + * `RUNNING`/▶ - Task is currently being polled. + * `IDLE`/⏸ - Task is waiting on some resource. + * `SCHED`/⏫ - Task is scheduled (it has been woken but not yet polled). + * `DONE`/⏹ - Task has completed. +* `Name` - The name of the task, which can be set when spawning a task using the unstable [`tokio::task::Builder::name()`](https://docs.rs/tokio/latest/tokio/task/struct.Builder.html#method.name) API. +* `Total` - Duration the task has been alive (sum of Busy, Sched, and Idle). +* `Busy` - Total duration for which the task has been actively executing. +* `Sched` - Total duration for which the task has been scheduled to be polled by the runtime. +* `Idle` - Total duration for which the task has been idle (waiting to be woken). +* `Polls` - Number of times the task has been polled. +* `Target` - The target of the span used to record the task. + * `tokio::task` - Async task. + * `tokio::task::blocking` - A blocking task (created with [tokio::task::spawn_blocking](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html)). +* `Location` - The source code location where the task was spawned from. +* `Fields` - Additional fields on the task span. + * `kind` - may be `task` (for async tasks) or `blocking` (for blocking tasks). + * `fn` - function signature for blocking tasks. Async tasks don't record this field, as it is generally very large when using `async`/`await`. Using the and arrow keys, an individual task can be highlighted. Pressingenter while a task is highlighted displays details about that -task: +task. + +### Task Details + +This view shows details about a specific task: + +![task details](https://raw.githubusercontent.com/tokio-rs/console/main/assets/tokio-console-0.1.8/task_details.png) -![task details](https://raw.githubusercontent.com/tokio-rs/console/main/assets/details2.png) +The task details view includes percentiles and a visual histogram of the polling (busy) times +and scheduled times. Pressing the escape key returns to the task list. +### Resources List + The r key switches from the list of tasks to a list of [resources], such as synchronization primitives, I/O resources, et cetera: -![resource list](https://raw.githubusercontent.com/tokio-rs/console/main/assets/resources.png) +![resource list](https://raw.githubusercontent.com/tokio-rs/console/main/assets/tokio-console-0.1.8/resources_list.png) + +Resources are displayed in a table similar to the task list. +* `ID` - The ID of the resource. This is a display ID as there is no internal resource ID to reference. +* `Parent` - The ID of the parent resource if it exists. +* `Kind` - The resource kind, this is a high level grouping of resources. + * `Sync` - Synchronization resources from [`tokio::sync`](https://docs.rs/tokio/latest/tokio/sync/index.html) such as [`Mutex`](https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html). + * `Timer` - Timer resources from [`tokio::time`](https://docs.rs/tokio/latest/tokio/time/index.html) such as [`Sleep`](https://docs.rs/tokio/latest/tokio/time/struct.Sleep.html). +* `Total` - Total duration that this resource has been alive. +* `Target` - The module path of the resource type. +* `Type` - The specific type of the resource, possible values depend on the resources instrumented in Tokio, which may vary between versions. +* `Vis` - The visibility of the resource. + * `INT`/🔒 - Internal, this resource is only used by other resources. + * `PUB`/✅ - Public, available in the public Tokio API. +* `Location` - The source code location where the resource was created. +* `Attributes` - Additional resource-dependent attributes, for example a resource of type `Sleep` record the `duration` of the sleep. Pressing the t key switches the view back to the task list. Like the task list view, the resource list view can be navigated using the and arrow keys. Pressing enter -while a resource is highlighted displays details about that resource: +while a resource is highlighted displays details about that resource. -![resource details --- oneshot](https://raw.githubusercontent.com/tokio-rs/console/main/assets/resource_details1.png) +### Resource Details -The resource details view lists the tasks currently waiting on that resource. -This may be a single task, as in the [`tokio::sync::oneshot`] channel above, or -a large number of tasks, such as this [`tokio::sync::Semaphore`]: +![resource details --- sleep](https://raw.githubusercontent.com/tokio-rs/console/main/assets/tokio-console-0.1.8/resource_details_sleep.png) -![resource details --- semaphore](https://raw.githubusercontent.com/tokio-rs/console/main/assets/resource_details2.png) +The resource details view lists the tasks currently waiting on that resource. +This may be a single task, as in the [`tokio::time::Sleep`] above, or +a large number of tasks, such as this private `tokio::sync::batch_semaphore::Semaphore`: + +![resource details --- semaphore](https://raw.githubusercontent.com/tokio-rs/console/main/assets/tokio-console-0.1.8/resource_details_semaphore.png) + +The resource details view includes a table of async ops belonging to the resource. + +* `ID` - The ID of the async op. This is a display ID similar to those recorded for resources. +* `Parent` - The ID of the parent async op, if it exists. +* `Task` - The ID and name of the task which performed this async op. +* `Source` - The method where the async op is being called from. +* `Total` - Total duration for which the async op has been alive (sum of Busy and Idle, as an async op has no scheduled state). +* `Busy` - Total duration for which the async op has been busy (its future is actively being polled). +* `Idle` - Total duration for which the async op has been idle (the future exists but is not being polled). +* `Polls` - Number of times the async op has been polled. +* `Attributes` - Additional attributes from the async op. These will vary based on the type of the async op. Like the task details view, pressing the escape key while viewing a resource's details returns to the resource list. 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 9e0153e5d..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, @@ -121,6 +125,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 { @@ -194,7 +204,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 { @@ -320,7 +330,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 { @@ -330,6 +340,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()) @@ -354,7 +400,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, } @@ -375,7 +421,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") @@ -456,7 +502,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), diff --git a/tokio-console/src/conn.rs b/tokio-console/src/conn.rs index faf42e9e0..6b1350a6c 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 }) @@ -154,8 +183,8 @@ impl Connection { } } - pub fn render(&self, styles: &crate::view::Styles) -> tui::text::Spans { - use tui::{ + pub fn render(&self, styles: &crate::view::Styles) -> ratatui::text::Spans { + use ratatui::{ style::{Color, Modifier}, text::{Span, Spans}, }; diff --git a/tokio-console/src/input.rs b/tokio-console/src/input.rs index 8df557d1c..088d5b771 100644 --- a/tokio-console/src/input.rs +++ b/tokio-console/src/input.rs @@ -1,4 +1,4 @@ -// TODO(eliza): support TUI backends other than crossterm? +// TODO(eliza): support Ratatui backends other than crossterm? // This would probably involve using `spawn_blocking` to drive their blocking // input-handling mechanisms in the background... pub use crossterm::event::*; @@ -13,10 +13,12 @@ pub fn should_quit(input: &Event) -> bool { Key(KeyEvent { code: Char('c'), modifiers, + .. }) | Key(KeyEvent { code: Char('d'), modifiers, + .. }) if modifiers.contains(KeyModifiers::CONTROL) => true, _ => false, } @@ -31,3 +33,23 @@ pub(crate) fn is_space(input: &Event) -> bool { }) ) } + +pub(crate) fn is_help_toggle(event: &Event) -> bool { + matches!( + event, + Event::Key(KeyEvent { + code: KeyCode::Char('?'), + .. + }) + ) +} + +pub(crate) fn is_esc(event: &Event) -> bool { + matches!( + event, + Event::Key(KeyEvent { + code: KeyCode::Esc, + .. + }) + ) +} diff --git a/tokio-console/src/main.rs b/tokio-console/src/main.rs index 68bb5f402..4fc6630bb 100644 --- a/tokio-console/src/main.rs +++ b/tokio-console/src/main.rs @@ -3,13 +3,13 @@ use console_api::tasks::TaskDetails; use state::State; use futures::stream::StreamExt; -use tokio::sync::{mpsc, watch}; -use tui::{ +use ratatui::{ layout::{Constraint, Direction, Layout}, style::Color, text::{Span, Spans}, widgets::{Paragraph, Wrap}, }; +use tokio::sync::{mpsc, watch}; use crate::view::{bold, UpdateKind}; @@ -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)?; + + 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); diff --git a/tokio-console/src/state/async_ops.rs b/tokio-console/src/state/async_ops.rs index e01879d84..d10b6ecdf 100644 --- a/tokio-console/src/state/async_ops.rs +++ b/tokio-console/src/state/async_ops.rs @@ -10,6 +10,7 @@ use crate::{ view, }; use console_api as proto; +use ratatui::text::Span; use std::{ cell::RefCell, collections::HashMap, @@ -17,7 +18,6 @@ use std::{ rc::{Rc, Weak}, time::{Duration, SystemTime}, }; -use tui::text::Span; #[derive(Default, Debug)] pub(crate) struct AsyncOpsState { diff --git a/tokio-console/src/state/histogram.rs b/tokio-console/src/state/histogram.rs index 94b68ab10..03be93339 100644 --- a/tokio-console/src/state/histogram.rs +++ b/tokio-console/src/state/histogram.rs @@ -30,7 +30,7 @@ impl DurationHistogram { }) } - fn from_proto(proto: &proto::DurationHistogram) -> Option { + pub(crate) fn from_proto(proto: &proto::DurationHistogram) -> Option { let histogram = deserialize_histogram(&proto.raw_histogram[..])?; Some(Self { histogram, diff --git a/tokio-console/src/state/mod.rs b/tokio-console/src/state/mod.rs index eb96606af..982e816b9 100644 --- a/tokio-console/src/state/mod.rs +++ b/tokio-console/src/state/mod.rs @@ -5,6 +5,10 @@ use crate::{ warnings::Linter, }; use console_api as proto; +use ratatui::{ + style::{Color, Modifier}, + text::Span, +}; use std::{ cell::RefCell, cmp::Ordering, @@ -15,10 +19,6 @@ use std::{ time::{Duration, SystemTime}, }; use tasks::{Details, Task, TasksState}; -use tui::{ - style::{Color, Modifier}, - text::Span, -}; pub mod async_ops; pub mod histogram; @@ -221,6 +221,10 @@ impl State { .poll_times_histogram .as_ref() .and_then(histogram::DurationHistogram::from_poll_durations), + scheduled_times_histogram: update + .scheduled_times_histogram + .as_ref() + .and_then(histogram::DurationHistogram::from_proto), }; *self.current_task_details.borrow_mut() = Some(details); @@ -271,6 +275,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 @@ -517,7 +522,7 @@ fn format_location(loc: Option) -> String { let truncated = truncate_registry_path(file); l.file = Some(truncated); } - format!("{} ", l) + l.to_string() }) .unwrap_or_else(|| "".to_string()) } diff --git a/tokio-console/src/state/resources.rs b/tokio-console/src/state/resources.rs index 5e755c76c..1ba85dfb7 100644 --- a/tokio-console/src/state/resources.rs +++ b/tokio-console/src/state/resources.rs @@ -6,13 +6,13 @@ use crate::state::{ }; use crate::view; use console_api as proto; +use ratatui::{style::Color, text::Span}; use std::{ collections::HashMap, convert::{TryFrom, TryInto}, rc::Rc, time::{Duration, SystemTime}, }; -use tui::{style::Color, text::Span}; #[derive(Default, Debug)] pub(crate) struct ResourcesState { diff --git a/tokio-console/src/state/tasks.rs b/tokio-console/src/state/tasks.rs index 8be120d8f..bf9c8a2c0 100644 --- a/tokio-console/src/state/tasks.rs +++ b/tokio-console/src/state/tasks.rs @@ -5,13 +5,14 @@ use crate::{ histogram::DurationHistogram, pb_duration, store::{self, Id, SpanId, Store}, - Field, Metadata, Visibility, + Field, FieldValue, Metadata, Visibility, }, util::Percentage, view, warnings::Linter, }; use console_api as proto; +use ratatui::{style::Color, text::Span}; use std::{ cell::RefCell, collections::HashMap, @@ -19,7 +20,6 @@ use std::{ rc::{Rc, Weak}, time::{Duration, SystemTime}, }; -use tui::{style::Color, text::Span}; #[derive(Default, Debug)] pub(crate) struct TasksState { @@ -32,6 +32,7 @@ pub(crate) struct TasksState { pub(crate) struct Details { pub(crate) span_id: SpanId, pub(crate) poll_times_histogram: Option, + pub(crate) scheduled_times_histogram: Option, } #[derive(Debug, Copy, Clone)] @@ -43,10 +44,11 @@ pub(crate) enum SortBy { Name = 3, Total = 4, Busy = 5, - Idle = 6, - Polls = 7, - Target = 8, - Location = 9, + Scheduled = 6, + Idle = 7, + Polls = 8, + Target = 9, + Location = 10, } #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] @@ -54,10 +56,22 @@ pub(crate) enum TaskState { Completed, Idle, Running, + Scheduled, } 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 +79,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, @@ -85,6 +103,7 @@ struct TaskStats { created_at: SystemTime, dropped_at: Option, busy: Duration, + scheduled: Duration, last_poll_started: Option, last_poll_ended: Option, idle: Option, @@ -147,6 +166,7 @@ impl TasksState { } }; let mut name = None; + let mut task_id = None; let mut fields = task .fields .drain(..) @@ -157,6 +177,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 +197,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, @@ -234,6 +265,10 @@ impl Details { pub(crate) fn poll_times_histogram(&self) -> Option<&DurationHistogram> { self.poll_times_histogram.as_ref() } + + pub(crate) fn scheduled_times_histogram(&self) -> Option<&DurationHistogram> { + self.scheduled_times_histogram.as_ref() + } } impl Task { @@ -245,6 +280,10 @@ impl Task { self.span_id } + pub(crate) fn id_str(&self) -> &str { + &self.id_str + } + pub(crate) fn target(&self) -> &str { &self.target } @@ -266,6 +305,10 @@ impl Task { self.stats.last_poll_started > self.stats.last_poll_ended } + pub(crate) fn is_scheduled(&self) -> bool { + self.stats.last_wake > self.stats.last_poll_started + } + pub(crate) fn is_completed(&self) -> bool { self.stats.total.is_some() } @@ -279,6 +322,10 @@ impl Task { return TaskState::Running; } + if self.is_scheduled() { + return TaskState::Scheduled; + } + TaskState::Idle } @@ -290,20 +337,34 @@ 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 } + pub(crate) fn scheduled(&self, since: SystemTime) -> Duration { + if let Some(wake) = self.stats.last_wake { + if self.stats.last_wake > self.stats.last_poll_started { + // In this case the task is scheduled, but has not yet been polled + let current_time_since_wake = since.duration_since(wake).unwrap_or_default(); + return self.stats.scheduled + current_time_since_wake; + } + } + self.stats.scheduled + } + pub(crate) fn idle(&self, since: SystemTime) -> Duration { self.stats .idle - .or_else(|| self.total(since).checked_sub(self.busy(since))) + .or_else(|| { + self.total(since) + .checked_sub(self.busy(since) + self.scheduled(since)) + }) .unwrap_or_default() } @@ -398,11 +459,14 @@ impl From for TaskStats { let poll_stats = pb.poll_stats.expect("task should have poll stats"); let busy = poll_stats.busy_time.map(pb_duration).unwrap_or_default(); - let idle = total.map(|total| total.checked_sub(busy).unwrap_or_default()); + let scheduled = pb.scheduled_time.map(pb_duration).unwrap_or_default(); + let idle = total.map(|total| total.checked_sub(busy + scheduled).unwrap_or_default()); Self { total, idle, + scheduled, busy, + last_wake: pb.last_wake.map(|v| v.try_into().unwrap()), last_poll_started: poll_stats.last_poll_started.map(|v| v.try_into().unwrap()), last_poll_ended: poll_stats.last_poll_ended.map(|v| v.try_into().unwrap()), polls: poll_stats.polls, @@ -411,7 +475,6 @@ impl From for TaskStats { wakes: pb.wakes, waker_clones: pb.waker_clones, waker_drops: pb.waker_drops, - last_wake: pb.last_wake.map(|v| v.try_into().unwrap()), self_wakes: pb.self_wakes, } } @@ -426,7 +489,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())) } @@ -441,6 +506,9 @@ impl SortBy { Self::Idle => { tasks.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().idle(now))) } + Self::Scheduled => { + tasks.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().scheduled(now))) + } Self::Busy => { tasks.sort_unstable_by_key(|task| task.upgrade().map(|t| t.borrow().busy(now))) } @@ -472,6 +540,7 @@ impl TryFrom for SortBy { idx if idx == Self::Name as usize => Ok(Self::Name), idx if idx == Self::Total as usize => Ok(Self::Total), idx if idx == Self::Busy as usize => Ok(Self::Busy), + idx if idx == Self::Scheduled as usize => Ok(Self::Scheduled), idx if idx == Self::Idle as usize => Ok(Self::Idle), idx if idx == Self::Polls as usize => Ok(Self::Polls), idx if idx == Self::Target as usize => Ok(Self::Target), @@ -484,6 +553,7 @@ impl TryFrom for SortBy { impl TaskState { pub(crate) fn render(self, styles: &crate::view::Styles) -> Span<'static> { const RUNNING_UTF8: &str = "\u{25B6}"; + const SCHEDULED_UTF8: &str = "\u{23EB}"; const IDLE_UTF8: &str = "\u{23F8}"; const COMPLETED_UTF8: &str = "\u{23F9}"; match self { @@ -491,6 +561,7 @@ impl TaskState { styles.if_utf8(RUNNING_UTF8, "BUSY"), styles.fg(Color::Green), ), + Self::Scheduled => Span::raw(styles.if_utf8(SCHEDULED_UTF8, "SCHED")), Self::Idle => Span::raw(styles.if_utf8(IDLE_UTF8, "IDLE")), Self::Completed => Span::raw(styles.if_utf8(COMPLETED_UTF8, "DONE")), } diff --git a/tokio-console/src/term.rs b/tokio-console/src/term.rs index 605119315..9fec79a58 100644 --- a/tokio-console/src/term.rs +++ b/tokio-console/src/term.rs @@ -1,6 +1,6 @@ pub use color_eyre::eyre::WrapErr; +pub use ratatui::{backend::CrosstermBackend, Terminal}; use std::io; -pub use tui::{backend::CrosstermBackend, Terminal}; pub fn init_crossterm() -> color_eyre::Result<(Terminal>, OnShutdown)> { diff --git a/tokio-console/src/view/async_ops.rs b/tokio-console/src/view/async_ops.rs index 7e804cfeb..380799b8d 100644 --- a/tokio-console/src/view/async_ops.rs +++ b/tokio-console/src/view/async_ops.rs @@ -1,3 +1,4 @@ +pub(crate) use crate::view::table::view_controls; use crate::{ state::{ async_ops::{AsyncOp, SortBy}, @@ -6,12 +7,13 @@ use crate::{ }, view::{ self, bold, - table::{self, TableList, TableListState}, - DUR_LEN, DUR_PRECISION, + controls::Controls, + table::{TableList, TableListState}, + DUR_LEN, DUR_TABLE_PRECISION, }, }; -use tui::{ +use ratatui::{ layout, style::{self, Color, Style}, text::Spans, @@ -55,10 +57,10 @@ impl TableList<9> for AsyncOpsTable { Self::HEADER[8].len() + 1, ]; - fn render( + fn render( table_list_state: &mut TableListState, styles: &view::Styles, - frame: &mut tui::terminal::Frame, + frame: &mut ratatui::terminal::Frame, area: layout::Rect, state: &mut State, ctx: Self::Context, @@ -106,12 +108,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 = { @@ -202,11 +199,11 @@ impl TableList<9> for AsyncOpsTable { .direction(layout::Direction::Vertical) .margin(0); - let controls = table::Controls::for_area(&area, styles); + let controls = Controls::new(view_controls(), &area, styles); let chunks = layout .constraints( [ - layout::Constraint::Length(controls.height), + layout::Constraint::Length(controls.height()), layout::Constraint::Max(area.height), ] .as_ref(), @@ -237,7 +234,7 @@ impl TableList<9> for AsyncOpsTable { .highlight_style(Style::default().add_modifier(style::Modifier::BOLD)); frame.render_stateful_widget(table, async_ops_area, &mut table_list_state.table_state); - frame.render_widget(controls.paragraph, controls_area); + frame.render_widget(controls.into_widget(), controls_area); table_list_state .sorted_items diff --git a/tokio-console/src/view/controls.rs b/tokio-console/src/view/controls.rs new file mode 100644 index 000000000..bf9972532 --- /dev/null +++ b/tokio-console/src/view/controls.rs @@ -0,0 +1,158 @@ +use crate::view::{self, bold}; + +use ratatui::{ + layout, + text::{Span, Spans, Text}, + widgets::{Paragraph, Widget}, +}; + +/// A list of controls which are available in all views. +const UNIVERSAL_CONTROLS: &[ControlDisplay] = &[ + ControlDisplay { + action: "toggle pause", + keys: &[KeyDisplay { + base: "space", + utf8: None, + }], + }, + ControlDisplay { + action: "quit", + keys: &[KeyDisplay { + base: "q", + utf8: None, + }], + }, +]; + +/// Construct a widget to display the controls available to the user in the +/// current view. +pub(crate) struct Controls { + paragraph: Paragraph<'static>, + height: u16, +} + +impl Controls { + pub(in crate::view) fn new( + view_controls: &'static [ControlDisplay], + area: &layout::Rect, + styles: &view::Styles, + ) -> Self { + let mut spans_controls = Vec::with_capacity(view_controls.len() + UNIVERSAL_CONTROLS.len()); + spans_controls.extend(view_controls.iter().map(|c| c.to_spans(styles, 0))); + spans_controls.extend(UNIVERSAL_CONTROLS.iter().map(|c| c.to_spans(styles, 0))); + + let mut lines = vec![Spans::from(vec![Span::from("controls: ")])]; + let mut current_line = lines.last_mut().expect("This vector is never empty"); + let separator = Span::from(", "); + + let controls_count: usize = spans_controls.len(); + for (idx, spans) in spans_controls.into_iter().enumerate() { + // If this is the first item on this line - or first item on the + // first line, then always include it - even if it goes beyond the + // line width, not much we can do anyway. + if idx == 0 || current_line.width() == 0 { + current_line.0.extend(spans.0); + continue; + } + + // Include the width of our separator in the current item if we + // aren't placing the last item. This is the separator after the + // new element. + let needed_trailing_separator_width = if idx == controls_count + 1 { + separator.width() + } else { + 0 + }; + + let total_width = current_line.width() + + separator.width() + + spans.width() + + needed_trailing_separator_width; + + // If the current item fits on this line, append it. + // Otherwise, append only the separator - we accounted for its + // width in the previous loop iteration - and then create a new + // line for the current item. + if total_width <= area.width as usize { + current_line.0.push(separator.clone()); + current_line.0.extend(spans.0); + } else { + current_line.0.push(separator.clone()); + lines.push(spans); + current_line = lines.last_mut().expect("This vector is never empty"); + } + } + + let height = lines.len() as u16; + let text = Text::from(lines); + + Self { + paragraph: Paragraph::new(text), + height, + } + } + + pub(crate) fn height(&self) -> u16 { + self.height + } + + pub(crate) fn into_widget(self) -> impl Widget { + self.paragraph + } +} + +pub(crate) fn controls_paragraph<'a>( + view_controls: &[ControlDisplay], + styles: &view::Styles, +) -> Paragraph<'a> { + let mut spans = Vec::with_capacity(1 + view_controls.len() + UNIVERSAL_CONTROLS.len()); + spans.push(Spans::from(vec![Span::raw("controls:")])); + spans.extend(view_controls.iter().map(|c| c.to_spans(styles, 2))); + spans.extend(UNIVERSAL_CONTROLS.iter().map(|c| c.to_spans(styles, 2))); + + Paragraph::new(spans) +} + +/// Construct span to display a control. +/// +/// A control is made up of an action and one or more keys that will trigger +/// that action. +#[derive(Clone)] +pub(crate) struct ControlDisplay { + pub(crate) action: &'static str, + pub(crate) keys: &'static [KeyDisplay], +} + +/// A key or keys which will be displayed to the user as part of spans +/// constructed by `ControlDisplay`. +/// +/// The `base` description of the key should be ASCII only, more advanced +/// descriptions can be supplied for that key in the `utf8` field. This +/// allows the application to pick the best one to display at runtime +/// based on the termainal being used. +#[derive(Clone)] +pub(crate) struct KeyDisplay { + pub(crate) base: &'static str, + pub(crate) utf8: Option<&'static str>, +} + +impl ControlDisplay { + pub(crate) fn to_spans(&self, styles: &view::Styles, indent: usize) -> Spans<'static> { + let mut spans = Vec::new(); + + spans.push(Span::from(" ".repeat(indent))); + spans.push(Span::from(self.action)); + spans.push(Span::from(" = ")); + for (idx, key_display) in self.keys.iter().enumerate() { + if idx > 0 { + spans.push(Span::from(" or ")) + } + spans.push(bold(match key_display.utf8 { + Some(utf8) => styles.if_utf8(utf8, key_display.base), + None => key_display.base, + })); + } + + Spans::from(spans) + } +} diff --git a/tokio-console/src/view/durations.rs b/tokio-console/src/view/durations.rs new file mode 100644 index 000000000..aefe5f7be --- /dev/null +++ b/tokio-console/src/view/durations.rs @@ -0,0 +1,122 @@ +use std::cmp; + +use ratatui::{ + 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 Ratatui 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, + /// Fixed width for percentiles block + percentiles_width: u16, +} + +impl<'a> Widget for Durations<'a> { + fn render(self, area: ratatui::layout::Rect, buf: &mut ratatui::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 = match self.percentiles_width { + // Fixed width + width if width > 0 => width, + // Long enough for the title or for a single line + // like "p99: 544.77µs" (13) (and borders on the sides). + _ => 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", + percentiles_width: 0, + } + } + + 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 + } + + pub(crate) fn percentiles_width(mut self, width: u16) -> Self { + self.percentiles_width = width; + self + } +} diff --git a/tokio-console/src/view/help.rs b/tokio-console/src/view/help.rs new file mode 100644 index 000000000..de249db61 --- /dev/null +++ b/tokio-console/src/view/help.rs @@ -0,0 +1,67 @@ +use ratatui::{ + layout::{self, Constraint, Direction, Layout}, + widgets::{Clear, Paragraph}, +}; + +use crate::{state::State, view}; + +pub(crate) trait HelpText { + fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static>; +} + +/// Simple view for help popup +pub(crate) struct HelpView<'a> { + help_text: Option>, +} + +impl<'a> HelpView<'a> { + pub(super) fn new(help_text: Paragraph<'a>) -> Self { + HelpView { + help_text: Some(help_text), + } + } + + pub(crate) fn render( + &mut self, + styles: &view::Styles, + frame: &mut ratatui::terminal::Frame, + _area: layout::Rect, + _state: &mut State, + ) { + let r = frame.size(); + let content = self + .help_text + .take() + .expect("help_text should be initialized"); + + let popup_layout = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Percentage(20), + Constraint::Min(15), + Constraint::Percentage(20), + ] + .as_ref(), + ) + .split(r); + + let popup_area = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage(20), + Constraint::Percentage(60), + Constraint::Percentage(20), + ] + .as_ref(), + ) + .split(popup_layout[1])[1]; + + let display_text = content.block(styles.border_block().title("Help")); + + // Clear the help block area and render the popup + frame.render_widget(Clear, popup_area); + frame.render_widget(display_text, popup_area); + } +} diff --git a/tokio-console/src/view/mini_histogram.rs b/tokio-console/src/view/mini_histogram.rs index 72ff983ef..56f4ece75 100644 --- a/tokio-console/src/view/mini_histogram.rs +++ b/tokio-console/src/view/mini_histogram.rs @@ -1,27 +1,27 @@ use std::time::Duration; -use tui::{ +use ratatui::{ layout::Rect, style::Style, symbols, widgets::{Block, Widget}, }; -/// This is a tui-rs widget to visualize a latency histogram in a small area. +use crate::state::histogram::DurationHistogram; + +/// This is a Ratatui 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 /// values. /// -/// [`Sparkline`]: tui::widgets::Sparkline +/// [`Sparkline`]: ratatui::widgets::Sparkline pub(crate) struct MiniHistogram<'a> { /// A block to wrap the widget in 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, @@ -39,8 +39,6 @@ pub(crate) struct HistogramMetadata { pub(crate) min_value: u64, /// The value of the bucket with the greatest quantity pub(crate) max_bucket: u64, - /// The value of the bucket with the smallest quantity - pub(crate) min_bucket: u64, /// Number of high outliers, if any pub(crate) high_outliers: u64, pub(crate) highest_outlier: Option, @@ -51,8 +49,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, @@ -61,7 +58,7 @@ impl<'a> Default for MiniHistogram<'a> { } impl<'a> Widget for MiniHistogram<'a> { - fn render(mut self, area: tui::layout::Rect, buf: &mut tui::buffer::Buffer) { + fn render(mut self, area: ratatui::layout::Rect, buf: &mut ratatui::buffer::Buffer) { let inner_area = match self.block.take() { Some(b) => { let inner_area = b.inner(area); @@ -75,34 +72,41 @@ 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 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 +116,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, + area: ratatui::layout::Rect, + buf: &mut ratatui::buffer::Buffer, + 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 +197,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 +217,95 @@ impl<'a> MiniHistogram<'a> { self } } + +fn render_legend( + area: ratatui::layout::Rect, + buf: &mut ratatui::buffer::Buffer, + metadata: &HistogramMetadata, + max_record_label: String, + min_record_label: String, + max_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 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(); + ( + data, + HistogramMetadata { + max_value: histogram.max(), + min_value: histogram.min(), + max_bucket, + high_outliers, + highest_outlier, + }, + ) +} diff --git a/tokio-console/src/view/mod.rs b/tokio-console/src/view/mod.rs index e22d239af..1a9703000 100644 --- a/tokio-console/src/view/mod.rs +++ b/tokio-console/src/view/mod.rs @@ -1,14 +1,21 @@ -use crate::view::{resources::ResourcesTable, table::TableListState, tasks::TasksTable}; +use crate::view::help::HelpView; +use crate::view::{ + help::HelpText, resources::ResourcesTable, table::TableListState, tasks::TasksTable, +}; use crate::{input, state::State}; -use std::{borrow::Cow, cmp}; -use tui::{ +use ratatui::{ layout, style::{self, Style}, text::Span, }; +use std::{borrow::Cow, cmp}; mod async_ops; +mod controls; +mod durations; +mod help; mod mini_histogram; +mod percentiles; mod resource; mod resources; mod styles; @@ -18,11 +25,15 @@ mod tasks; pub(crate) use self::styles::{Palette, Styles}; pub(crate) use self::table::SortBy; -const DUR_LEN: usize = 10; // 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_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 = ">> "; pub struct View { @@ -33,9 +44,10 @@ pub struct View { /// details view), we want to leave the task list's state the way we left it /// --- e.g., if the user previously selected a particular sorting, we want /// it to remain sorted that way when we return to it. - tasks_list: TableListState, + tasks_list: TableListState, resources_list: TableListState, state: ViewState, + show_help_modal: bool, pub(crate) styles: Styles, } @@ -87,8 +99,9 @@ impl View { pub fn new(styles: Styles) -> Self { Self { state: ViewState::TasksList, - tasks_list: TableListState::::default(), + tasks_list: TableListState::::default(), resources_list: TableListState::::default(), + show_help_modal: false, styles, } } @@ -96,6 +109,22 @@ impl View { pub(crate) fn update_input(&mut self, event: input::Event, state: &State) -> UpdateKind { use ViewState::*; let mut update_kind = UpdateKind::Other; + + if self.should_toggle_help_modal(&event) { + self.show_help_modal = !self.show_help_modal; + return update_kind; + } + + 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 +139,6 @@ impl View { )); } } - key!(Char('r')) => { - self.state = ResourcesList; - } _ => { // otherwise pass on to view self.tasks_list.update_input(event); @@ -127,9 +153,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); @@ -144,6 +167,26 @@ impl View { self.state = ResourcesList; update_kind = UpdateKind::Other; } + key!(Enter) => { + if let Some(op) = view.async_ops_table.selected_item().upgrade() { + if let Some(task_id) = op.borrow().task_id() { + let task = self + .tasks_list + .sorted_items + .iter() + .filter_map(|i| i.upgrade()) + .find(|t| task_id == t.borrow().id()); + + if let Some(task) = task { + update_kind = UpdateKind::SelectTask(task.borrow().span_id()); + self.state = TaskInstance(self::task::TaskView::new( + task, + state.task_details_ref(), + )); + } + } + } + } _ => { // otherwise pass on to view view.update_input(event); @@ -168,32 +211,46 @@ impl View { update_kind } - pub(crate) fn render( + /// The help modal should toggle on the `?` key and should exit on `Esc` + fn should_toggle_help_modal(&mut self, event: &crossterm::event::Event) -> bool { + input::is_help_toggle(event) || (self.show_help_modal && input::is_esc(event)) + } + + pub(crate) fn render( &mut self, - frame: &mut tui::terminal::Frame, + frame: &mut ratatui::terminal::Frame, area: layout::Rect, state: &mut State, ) { - match self.state { + let help_text: &dyn HelpText = match self.state { ViewState::TasksList => { self.tasks_list.render(&self.styles, frame, area, state, ()); + &self.tasks_list } ViewState::ResourcesList => { self.resources_list .render(&self.styles, frame, area, state, ()); + &self.resources_list } ViewState::TaskInstance(ref mut view) => { let now = state .last_updated_at() .expect("task view implies we've received an update"); view.render(&self.styles, frame, area, now); + view } ViewState::ResourceInstance(ref mut view) => { view.render(&self.styles, frame, area, state); + view } - } + }; state.retain_active(); + + if self.show_help_modal { + let mut help_view = HelpView::new(help_text.render_help_content(&self.styles)); + help_view.render(&self.styles, frame, area, state); + } } pub(crate) fn current_view(&self) -> &ViewState { diff --git a/tokio-console/src/view/percentiles.rs b/tokio-console/src/view/percentiles.rs new file mode 100644 index 000000000..207cc3ad7 --- /dev/null +++ b/tokio-console/src/view/percentiles.rs @@ -0,0 +1,83 @@ +use std::time::Duration; + +use ratatui::{ + text::{Spans, Text}, + widgets::{Paragraph, Widget}, +}; + +use crate::{ + state::histogram::DurationHistogram, + view::{self, bold}, +}; + +/// This is a Ratatui 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: ratatui::layout::Rect, buf: &mut ratatui::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/resource.rs b/tokio-console/src/view/resource.rs index 87129121d..8c85203ac 100644 --- a/tokio-console/src/view/resource.rs +++ b/tokio-console/src/view/resource.rs @@ -4,20 +4,24 @@ use crate::{ state::State, view::{ self, - async_ops::{AsyncOpsTable, AsyncOpsTableCtx}, - bold, TableListState, + async_ops::{self, AsyncOpsTable, AsyncOpsTableCtx}, + bold, + controls::{controls_paragraph, ControlDisplay, Controls, KeyDisplay}, + help::HelpText, + TableListState, }, }; -use std::{cell::RefCell, rc::Rc}; -use tui::{ +use once_cell::sync::OnceCell; +use ratatui::{ layout::{self, Layout}, text::{Span, Spans, Text}, - widgets::{Block, Paragraph}, + widgets::Paragraph, }; +use std::{cell::RefCell, rc::Rc}; pub(crate) struct ResourceView { resource: Rc>, - async_ops_table: TableListState, + pub(crate) async_ops_table: TableListState, initial_render: bool, } @@ -34,14 +38,15 @@ impl ResourceView { self.async_ops_table.update_input(event) } - pub(crate) fn render( + pub(crate) fn render( &mut self, styles: &view::Styles, - frame: &mut tui::terminal::Frame, + frame: &mut ratatui::terminal::Frame, area: layout::Rect, state: &mut State, ) { let resource = &*self.resource.borrow(); + let controls = Controls::new(view_controls(), &area, styles); let (controls_area, stats_area, async_ops_area) = { let chunks = Layout::default() @@ -49,7 +54,7 @@ impl ResourceView { .constraints( [ // controls - layout::Constraint::Length(1), + layout::Constraint::Length(controls.height()), // resource stats layout::Constraint::Length(8), // async ops @@ -72,14 +77,6 @@ impl ResourceView { ) .split(stats_area); - let controls = Spans::from(vec![ - Span::raw("controls: "), - bold(styles.if_utf8("\u{238B} esc", "esc")), - Span::raw(" = return to task list, "), - bold("q"), - Span::raw(" = quit"), - ]); - let overview = vec![ Spans::from(vec![bold("ID: "), Span::raw(resource.id_str())]), Spans::from(vec![bold("Parent ID: "), Span::raw(resource.parent())]), @@ -107,7 +104,7 @@ impl ResourceView { Paragraph::new(overview).block(styles.border_block().title("Resource")); let fields_widget = Paragraph::new(fields).block(styles.border_block().title("Attributes")); - frame.render_widget(Block::default().title(controls), controls_area); + frame.render_widget(controls.into_widget(), controls_area); frame.render_widget(resource_widget, stats_area[0]); frame.render_widget(fields_widget, stats_area[1]); let ctx = AsyncOpsTableCtx { @@ -119,3 +116,24 @@ impl ResourceView { self.initial_render = false; } } + +impl HelpText for ResourceView { + fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static> { + controls_paragraph(view_controls(), styles) + } +} + +fn view_controls() -> &'static [ControlDisplay] { + static VIEW_CONTROLS: OnceCell> = OnceCell::new(); + + VIEW_CONTROLS.get_or_init(|| { + let resource_controls = &[ControlDisplay { + action: "return to task list", + keys: &[KeyDisplay { + base: "esc", + utf8: Some("\u{238B} esc"), + }], + }]; + [resource_controls, async_ops::view_controls()].concat() + }) +} diff --git a/tokio-console/src/view/resources.rs b/tokio-console/src/view/resources.rs index 4b65b00e8..124ba22d5 100644 --- a/tokio-console/src/view/resources.rs +++ b/tokio-console/src/view/resources.rs @@ -5,12 +5,13 @@ use crate::{ }, view::{ self, bold, - table::{self, TableList, TableListState}, - DUR_LEN, DUR_PRECISION, + controls::Controls, + table::{view_controls, TableList, TableListState}, + DUR_LEN, DUR_TABLE_PRECISION, }, }; -use tui::{ +use ratatui::{ layout, style::{self, Color, Style}, text::Spans, @@ -49,10 +50,10 @@ impl TableList<9> for ResourcesTable { Self::HEADER[8].len() + 1, ]; - fn render( + fn render( table_list_state: &mut TableListState, styles: &view::Styles, - frame: &mut tui::terminal::Frame, + frame: &mut ratatui::terminal::Frame, area: layout::Rect, state: &mut State, _: Self::Context, @@ -104,12 +105,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)), @@ -164,7 +164,7 @@ impl TableList<9> for ResourcesTable { table_list_state.len() ))]); - let controls = table::Controls::for_area(&area, styles); + let controls = Controls::new(view_controls(), &area, styles); let layout = layout::Layout::default() .direction(layout::Direction::Vertical) @@ -173,7 +173,7 @@ impl TableList<9> for ResourcesTable { let chunks = layout .constraints( [ - layout::Constraint::Length(controls.height), + layout::Constraint::Length(controls.height()), layout::Constraint::Max(area.height), ] .as_ref(), @@ -203,7 +203,7 @@ impl TableList<9> for ResourcesTable { .highlight_style(Style::default().add_modifier(style::Modifier::BOLD)); frame.render_stateful_widget(table, tasks_area, &mut table_list_state.table_state); - frame.render_widget(controls.paragraph, controls_area); + frame.render_widget(controls.into_widget(), controls_area); table_list_state .sorted_items diff --git a/tokio-console/src/view/styles.rs b/tokio-console/src/view/styles.rs index 515308441..92049d419 100644 --- a/tokio-console/src/view/styles.rs +++ b/tokio-console/src/view/styles.rs @@ -1,10 +1,10 @@ use crate::config; -use serde::{Deserialize, Serialize}; -use std::{borrow::Cow, str::FromStr}; -use tui::{ +use ratatui::{ style::{Color, Modifier, Style}, text::Span, }; +use serde::{Deserialize, Serialize}; +use std::{str::FromStr, time::Duration}; #[derive(Debug, Clone)] pub struct Styles { @@ -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 { @@ -47,12 +77,32 @@ impl Styles { } } - pub fn error_init(&self) -> color_eyre::Result<()> { - use color_eyre::config::{HookBuilder, Theme}; + pub fn error_init(&self, cfg: &crate::config::Config) -> color_eyre::Result<()> { + use color_eyre::{ + config::{HookBuilder, Theme}, + ErrorKind, + }; let mut builder = HookBuilder::new() .issue_url(concat!(env!("CARGO_PKG_REPOSITORY"), "/issues/new")) + .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, + }) + // 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() .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 builder = builder.theme(Theme::new()); @@ -106,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 { @@ -221,11 +332,11 @@ impl Styles { } } - pub fn border_block(&self) -> tui::widgets::Block<'_> { + pub fn border_block(&self) -> ratatui::widgets::Block<'_> { if self.utf8 { - tui::widgets::Block::default() - .borders(tui::widgets::Borders::ALL) - .border_type(tui::widgets::BorderType::Rounded) + ratatui::widgets::Block::default() + .borders(ratatui::widgets::Borders::ALL) + .border_type(ratatui::widgets::BorderType::Rounded) } else { // TODO(eliza): configure an ascii-art border set instead? Default::default() diff --git a/tokio-console/src/view/table.rs b/tokio-console/src/view/table.rs index fd124fb81..10fdd16dc 100644 --- a/tokio-console/src/view/table.rs +++ b/tokio-console/src/view/table.rs @@ -1,13 +1,16 @@ use crate::{ input, state, - view::{self, bold}, + view::{ + self, + controls::{controls_paragraph, ControlDisplay, KeyDisplay}, + help::HelpText, + }, }; -use std::convert::TryFrom; -use tui::{ +use ratatui::{ layout, - text::{self, Span, Spans, Text}, - widgets::{Paragraph, TableState, Wrap}, + widgets::{Paragraph, TableState}, }; +use std::convert::TryFrom; use std::cell::RefCell; use std::rc::Weak; @@ -20,10 +23,10 @@ pub(crate) trait TableList { const HEADER: &'static [&'static str; N]; const WIDTHS: &'static [usize; N]; - fn render( + fn render( state: &mut TableListState, styles: &view::Styles, - frame: &mut tui::terminal::Frame, + frame: &mut ratatui::terminal::Frame, area: layout::Rect, state: &mut state::State, cx: Self::Context, @@ -45,11 +48,6 @@ pub(crate) struct TableListState, const N: usize> { last_key_event: Option, } -pub(crate) struct Controls { - pub(crate) paragraph: Paragraph<'static>, - pub(crate) height: u16, -} - impl, const N: usize> TableListState { pub(in crate::view) fn len(&self) -> usize { self.sorted_items.len() @@ -170,10 +168,10 @@ impl, const N: usize> TableListState { .unwrap_or_default() } - pub(in crate::view) fn render( + pub(in crate::view) fn render( &mut self, styles: &view::Styles, - frame: &mut tui::terminal::Frame, + frame: &mut ratatui::terminal::Frame, area: layout::Rect, state: &mut state::State, ctx: T::Context, @@ -201,52 +199,70 @@ where } } -impl Controls { - pub(in crate::view) fn for_area(area: &layout::Rect, styles: &view::Styles) -> Self { - let text = Text::from(Spans::from(vec![ - Span::raw("controls: "), - bold(styles.if_utf8("\u{2190}\u{2192}", "left, right")), - Span::raw(" or "), - bold("h, l"), - text::Span::raw(" = select column (sort), "), - bold(styles.if_utf8("\u{2191}\u{2193}", "up, down")), - Span::raw(" or "), - bold("k, j"), - text::Span::raw(" = scroll, "), - bold(styles.if_utf8("\u{21B5}", "enter")), - text::Span::raw(" = view details, "), - bold("i"), - text::Span::raw(" = invert sort (highest/lowest), "), - bold("q"), - text::Span::raw(" = quit "), - bold("gg"), - text::Span::raw(" = scroll to top, "), - bold("G"), - text::Span::raw(" = scroll to bottom"), - ])); - - // how many lines do we need to display the controls? - let mut height = 1; - - // if the area is narrower than the width of the controls text, we need - // to wrap the text across multiple lines. - let width = text.width() as u16; - if area.width < width { - height = width / area.width; - - // if the text's width is not neatly divisible by the area's width - // (and it almost never will be), round up for the remaining text. - if width % area.width > 0 { - height += 1 - }; - } - - Self { - // TODO(eliza): it would be nice if we could wrap this on commas, - // specifically, rather than whitespace...but that seems like a - // bunch of additional work... - paragraph: Paragraph::new(text).wrap(Wrap { trim: true }), - height, - } +impl HelpText for TableListState +where + T: TableList, +{ + fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static> { + controls_paragraph(view_controls(), styles) } } + +pub(crate) const fn view_controls() -> &'static [ControlDisplay] { + &[ + ControlDisplay { + action: "select column (sort)", + keys: &[ + KeyDisplay { + base: "left, right", + utf8: Some("\u{2190}\u{2192}"), + }, + KeyDisplay { + base: "h, l", + utf8: None, + }, + ], + }, + ControlDisplay { + action: "scroll", + keys: &[ + KeyDisplay { + base: "up, down", + utf8: Some("\u{2191}\u{2193}"), + }, + KeyDisplay { + base: "k, j", + utf8: None, + }, + ], + }, + ControlDisplay { + action: "view details", + keys: &[KeyDisplay { + base: "enter", + utf8: Some("\u{21B5}"), + }], + }, + ControlDisplay { + action: "invert sort (highest/lowest)", + keys: &[KeyDisplay { + base: "i", + utf8: None, + }], + }, + ControlDisplay { + action: "scroll to top", + keys: &[KeyDisplay { + base: "gg", + utf8: None, + }], + }, + ControlDisplay { + action: "scroll to bottom", + keys: &[KeyDisplay { + base: "G", + utf8: None, + }], + }, + ] +} diff --git a/tokio-console/src/view/task.rs b/tokio-console/src/view/task.rs index 2708bfb92..9713eecbb 100644 --- a/tokio-console/src/view/task.rs +++ b/tokio-console/src/view/task.rs @@ -1,26 +1,25 @@ use crate::{ input, - state::{ - histogram::DurationHistogram, - tasks::{Details, Task}, - DetailsRef, - }, + state::{tasks::Task, DetailsRef}, util::Percentage, view::{ self, bold, - mini_histogram::{HistogramMetadata, MiniHistogram}, + controls::{controls_paragraph, ControlDisplay, Controls, KeyDisplay}, + durations::Durations, + help::HelpText, }, }; +use ratatui::{ + layout::{self, Layout}, + text::{Span, Spans, Text}, + widgets::{List, ListItem, Paragraph}, +}; use std::{ cell::RefCell, + cmp, rc::Rc, time::{Duration, SystemTime}, }; -use tui::{ - layout::{self, Layout}, - text::{Span, Spans, Text}, - widgets::{Block, List, ListItem, Paragraph}, -}; pub(crate) struct TaskView { task: Rc>, @@ -36,10 +35,10 @@ impl TaskView { // TODO :D } - pub(crate) fn render( + pub(crate) fn render( &mut self, styles: &view::Styles, - frame: &mut tui::terminal::Frame, + frame: &mut ratatui::terminal::Frame, area: layout::Rect, now: SystemTime, ) { @@ -55,6 +54,8 @@ impl TaskView { .as_ref() .filter(|details| details.span_id() == task.span_id()); + let controls = Controls::new(view_controls(), &area, styles); + let warnings: Vec<_> = task .warnings() .iter() @@ -67,47 +68,64 @@ impl TaskView { }) .collect(); - let (controls_area, stats_area, poll_dur_area, fields_area, warnings_area) = - if warnings.is_empty() { - let chunks = Layout::default() - .direction(layout::Direction::Vertical) - .constraints( - [ - // controls - layout::Constraint::Length(1), - // task stats - layout::Constraint::Length(8), - // poll duration - layout::Constraint::Length(9), - // fields - layout::Constraint::Percentage(60), - ] - .as_ref(), - ) - .split(area); - (chunks[0], chunks[1], chunks[2], chunks[3], None) - } else { - let chunks = Layout::default() - .direction(layout::Direction::Vertical) - .constraints( - [ - // controls - layout::Constraint::Length(1), - // warnings (add 2 for top and bottom borders) - layout::Constraint::Length(warnings.len() as u16 + 2), - // task stats - layout::Constraint::Length(8), - // poll duration - layout::Constraint::Length(9), - // fields - layout::Constraint::Percentage(60), - ] - .as_ref(), - ) - .split(area); - - (chunks[0], chunks[2], chunks[3], chunks[4], Some(chunks[1])) - }; + let ( + controls_area, + stats_area, + poll_dur_area, + scheduled_dur_area, + fields_area, + warnings_area, + ) = if warnings.is_empty() { + let chunks = Layout::default() + .direction(layout::Direction::Vertical) + .constraints( + [ + // controls + layout::Constraint::Length(controls.height()), + // task stats + layout::Constraint::Length(10), + // poll duration + layout::Constraint::Length(9), + // scheduled duration + layout::Constraint::Length(9), + // fields + layout::Constraint::Percentage(60), + ] + .as_ref(), + ) + .split(area); + (chunks[0], chunks[1], chunks[2], chunks[3], chunks[4], None) + } else { + let chunks = Layout::default() + .direction(layout::Direction::Vertical) + .constraints( + [ + // controls + layout::Constraint::Length(controls.height()), + // warnings (add 2 for top and bottom borders) + layout::Constraint::Length(warnings.len() as u16 + 2), + // task stats + layout::Constraint::Length(10), + // poll duration + layout::Constraint::Length(9), + // scheduled duration + layout::Constraint::Length(9), + // fields + layout::Constraint::Percentage(60), + ] + .as_ref(), + ) + .split(area); + + ( + chunks[0], + chunks[2], + chunks[3], + chunks[4], + chunks[5], + Some(chunks[1]), + ) + }; let stats_area = Layout::default() .direction(layout::Direction::Horizontal) @@ -120,36 +138,8 @@ 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")), - Span::raw(" = return to task list, "), - bold("q"), - Span::raw(" = quit"), - ]); - // Just preallocate capacity for ID, name, target, total, busy, and idle. - let mut overview = Vec::with_capacity(7); + let mut overview = Vec::with_capacity(8); overview.push(Spans::from(vec![ bold("ID: "), Span::raw(format!("{} ", task.id())), @@ -165,10 +155,17 @@ impl TaskView { Span::raw(task.target()), ])); - overview.push(Spans::from(vec![ - bold("Location: "), - Span::raw(task.location()), - ])); + let title = "Location: "; + let location_max_width = stats_area[0].width as usize - 2 - title.len(); // NOTE: -2 for the border + let location = if task.location().len() > location_max_width { + let ellipsis = styles.if_utf8("\u{2026}", "..."); + let start = task.location().len() - location_max_width + ellipsis.chars().count(); + format!("{}{}", ellipsis, &task.location()[start..]) + } else { + task.location().to_string() + }; + + overview.push(Spans::from(vec![bold(title), Span::raw(location)])); let total = task.total(now); @@ -176,13 +173,17 @@ 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("Scheduled: ", task.scheduled(now))); overview.push(dur_percent("Idle: ", task.idle(now))); let mut waker_stats = vec![Spans::from(vec![ @@ -223,30 +224,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); @@ -254,104 +231,48 @@ 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_percentiles_title = "Poll Times Percentiles"; + let scheduled_percentiles_title = "Sched Times Percentiles"; + let percentiles_width = cmp::max( + poll_percentiles_title.len(), + scheduled_percentiles_title.len(), + ) as u16 + + 2_u16; // extra 2 characters for the border + let poll_durations_widget = Durations::new(styles) + .histogram(details.and_then(|d| d.poll_times_histogram())) + .percentiles_title(poll_percentiles_title) + .histogram_title("Poll Times Histogram") + .percentiles_width(percentiles_width); + let scheduled_durations_widget = Durations::new(styles) + .histogram(details.and_then(|d| d.scheduled_times_histogram())) + .percentiles_title(scheduled_percentiles_title) + .histogram_title("Scheduled Times Histogram") + .percentiles_width(percentiles_width); + 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(controls.into_widget(), 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(scheduled_durations_widget, scheduled_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 { 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)), - ]) - }) - }); - text.extend(percentiles); - text +impl HelpText for TaskView { + fn render_help_content(&self, styles: &view::Styles) -> Paragraph<'static> { + controls_paragraph(view_controls(), styles) } } -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)) +const fn view_controls() -> &'static [ControlDisplay] { + &[ControlDisplay { + action: "return to task list", + keys: &[KeyDisplay { + base: "esc", + utf8: Some("\u{238B} esc"), + }], + }] } diff --git a/tokio-console/src/view/tasks.rs b/tokio-console/src/view/tasks.rs index eb6b2365a..7d15934ac 100644 --- a/tokio-console/src/view/tasks.rs +++ b/tokio-console/src/view/tasks.rs @@ -5,11 +5,12 @@ use crate::{ }, view::{ self, bold, - table::{self, TableList, TableListState}, - DUR_LEN, DUR_PRECISION, + controls::Controls, + table::{view_controls, TableList, TableListState}, + DUR_LEN, DUR_TABLE_PRECISION, }, }; -use tui::{ +use ratatui::{ layout, style::{self, Color, Style}, text::{Span, Spans, Text}, @@ -19,17 +20,17 @@ use tui::{ #[derive(Debug, Default)] pub(crate) struct TasksTable {} -impl TableList<11> for TasksTable { +impl TableList<12> for TasksTable { type Row = Task; type Sort = SortBy; type Context = (); - const HEADER: &'static [&'static str; 11] = &[ - "Warn", "ID", "State", "Name", "Total", "Busy", "Idle", "Polls", "Target", "Location", - "Fields", + const HEADER: &'static [&'static str; 12] = &[ + "Warn", "ID", "State", "Name", "Total", "Busy", "Sched", "Idle", "Polls", "Target", + "Location", "Fields", ]; - const WIDTHS: &'static [usize; 11] = &[ + const WIDTHS: &'static [usize; 12] = &[ Self::HEADER[0].len() + 1, Self::HEADER[1].len() + 1, Self::HEADER[2].len() + 1, @@ -41,12 +42,13 @@ impl TableList<11> for TasksTable { Self::HEADER[8].len() + 1, Self::HEADER[9].len() + 1, Self::HEADER[10].len() + 1, + Self::HEADER[11].len() + 1, ]; - fn render( - table_list_state: &mut TableListState, + fn render( + table_list_state: &mut TableListState, styles: &view::Styles, - frame: &mut tui::terminal::Frame, + frame: &mut ratatui::terminal::Frame, area: layout::Rect, state: &mut State, _: Self::Context, @@ -68,12 +70,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... @@ -127,13 +124,14 @@ 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)), Cell::from(name_width.update_str(task.name().unwrap_or("")).to_string()), dur_cell(task.total(now)), dur_cell(task.busy(now)), + dur_cell(task.scheduled(now)), dur_cell(task.idle(now)), Cell::from(polls_width.update_str(task.total_polls().to_string())), Cell::from(target_width.update_str(task.target()).to_owned()), @@ -215,13 +213,13 @@ impl TableList<11> for TasksTable { .direction(layout::Direction::Vertical) .margin(0); - let controls = table::Controls::for_area(&area, styles); + let controls = Controls::new(view_controls(), &area, styles); let (controls_area, tasks_area, warnings_area) = if warnings.is_empty() { let chunks = layout .constraints( [ - layout::Constraint::Length(controls.height), + layout::Constraint::Length(controls.height()), layout::Constraint::Max(area.height), ] .as_ref(), @@ -233,7 +231,7 @@ impl TableList<11> for TasksTable { let chunks = layout .constraints( [ - layout::Constraint::Length(controls.height), + layout::Constraint::Length(controls.height()), layout::Constraint::Length(warnings_height), layout::Constraint::Max(area.height), ] @@ -257,6 +255,7 @@ impl TableList<11> for TasksTable { layout::Constraint::Length(DUR_LEN as u16), layout::Constraint::Length(DUR_LEN as u16), layout::Constraint::Length(DUR_LEN as u16), + layout::Constraint::Length(DUR_LEN as u16), polls_width.constraint(), target_width.constraint(), location_width.constraint(), @@ -271,7 +270,7 @@ impl TableList<11> for TasksTable { .highlight_style(Style::default().add_modifier(style::Modifier::BOLD)); frame.render_stateful_widget(table, tasks_area, &mut table_list_state.table_state); - frame.render_widget(controls.paragraph, controls_area); + frame.render_widget(controls.into_widget(), controls_area); if let Some(area) = warnings_area { let block = styles diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 000000000..260861f2e --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "xtask" +version = "0.1.0" +license = "MIT" +edition = "2021" +rust-version = "1.60.0" +publish = false + +[dependencies] +tonic-build = { version = "0.9", default-features = false, features = [ + "prost", "transport" +] } +clap = { version = "3", features = ["derive"] } +color-eyre = "0.6" \ No newline at end of file diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 000000000..4e07a0509 --- /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") +}