diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..233beff9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: cargo + directory: / + schedule: + interval: weekly + groups: + # Group together all dependency updates into a single PR + cargo-dependencies: + patterns: + - "*" + open-pull-requests-limit: 10 + versioning-strategy: lockfile-only + allow: + - dependency-type: all diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index bdc6ea36..7d1be114 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -34,50 +34,87 @@ env: jobs: build-deb: - name: ${{ matrix.distro }} + name: "${{ matrix.distro }}:${{ matrix.release }}" runs-on: ubuntu-latest - container: ${{ matrix.distro }} + container: "${{ matrix.distro }}:${{ matrix.release }}" strategy: matrix: - distro: - - debian:bullseye # oldstable - - debian:bookworm # stable - - debian:trixie # testing - - # - ubuntu:trusty # LTS until Apr 2024 - # - ubuntu:xenial # LTS until Apr 2026 - # - ubuntu:bionic # LTS until Apr 2028 - - ubuntu:focal # LTS until Apr 2030 - - ubuntu:jammy # LTS until Apr 2032 - - ubuntu:kinetic # Previous release - - ubuntu:lunar # Current release + include: + - { distro: debian, release: buster } + - { distro: debian, release: bullseye } + - { distro: debian, release: bookworm } + + # trusty and xenial are too old to properly support cross compiling + # (or a compiler that supports C++17). + # - { distro: ubuntu, release: trusty } # LTS until Apr 2024 + # - { distro: ubuntu, release: xenial } # LTS until Apr 2026 + - { distro: ubuntu, release: bionic } # LTS until Apr 2028 + - { distro: ubuntu, release: focal } # LTS until Apr 2030 + - { distro: ubuntu, release: jammy } # LTS until Apr 2032 + - { distro: ubuntu, release: lunar } # Current release fail-fast: false + env: + # dpkg-buildpackage issues a warning if we attempt to cross compile and + # tests are still enabled. Disabling the test step fixes this. + # + # Note that we don't run tests anyway so this doesn't really change + # anything for us. + DEB_BUILD_OPTS: nocheck steps: - uses: actions/checkout@v3 - - name: setup some variables - id: vars - shell: bash + - name: modify /etc/apt/sources.list + if: ${{ matrix.distro == 'ubuntu' }} run: | - echo distro="$(echo '${{ matrix.distro }}' | cut -d : -f 1)" >> "$GITHUB_OUTPUT" - echo release="$(echo '${{ matrix.distro }}' | cut -d : -f 2)" >> "$GITHUB_OUTPUT" - - - name: Install apt dependencies - shell: bash + cat /etc/apt/sources.list | \ + sed 's/^deb /deb [arch=amd64] /g' | \ + grep '^deb ' \ + > amd64.list + + cat /etc/apt/sources.list | \ + sed 's/^deb /deb [arch=arm64] /g' | \ + grep archive.ubuntu.com | \ + grep '^deb ' | \ + sed 's|archive.ubuntu.com/ubuntu|ports.ubuntu.com|g' \ + > arm64.list + + cat amd64.list arm64.list > /etc/apt/sources.list + rm amd64.list arm64.list + + - name: enable arm64 dpkg architecture + run: dpkg --add-architecture arm64 + + - name: install buildsystem apt dependencies run: | apt-get update - apt-get install -y curl build-essential pkg-config libssl-dev jq debhelper lsb-release \ - cmake libclang-dev clang libelf-dev + apt-get install -y \ + build-essential \ + crossbuild-essential-arm64 \ + curl jq lsb-release unzip gpg - name: install rust - shell: bash run: | - curl https://static.rust-lang.org/rustup/rustup-init.sh -o rustup-init.sh - chmod +x rustup-init.sh + curl -sSf https://sh.rustup.rs | sh /dev/stdin -y + echo "PATH=$HOME/.cargo/bin:$PATH" >> "$GITHUB_ENV" + + - name: enable additional rustup targets + run: rustup target add aarch64-unknown-linux-gnu + + # Newer clang versions need a newer version of libstdc++ then bionic provides. + - name: install a newer libstc++ version needed for clang + if: ${{ matrix.release == 'bionic' }} + run: | + apt-get -q install -y software-properties-common + add-apt-repository -y ppa:ubuntu-toolchain-r/test - ./rustup-init.sh -y - echo "${CARGO_HOME:-$HOME/.cargo}/bin" >> $GITHUB_PATH - rm -f rustup-init.sh + # bpf-rs needs a newer version of clang than is available in older distro releases + - name: install a newer clang version + run: | + curl -sSL https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - + cat > /etc/apt/sources.list.d/llvm.list <> $GITHUB_ENV + # Changelogs with revisions cause dpkg-source to emit an error when + # building. We only use the source package to install the build deps + # so building it with an invalid version is ok. + - name: build source package + run: dpkg-source --build . + - name: generate changelog shell: bash run: ./debian/gen-changelog.sh > debian/changelog - - name: build debs - shell: bash - run: dpkg-buildpackage -b -us -uc + - name: install arm64 build dependencies + run: apt-get build-dep -y -a arm64 ../rezolus*.dsc + - name: build arm64 package + run: dpkg-buildpackage -b -us -uc --host-arch arm64 + + - name: install x86_64 build dependencies + run: apt-get build-dep -y -a amd64 ../rezolus*.dsc + - name: build x86_64 package + run: dpkg-buildpackage -b -us -uc --host-arch amd64 - name: copy debs shell: bash @@ -113,7 +162,7 @@ jobs: - uses: actions/upload-artifact@v3 with: path: target/debian/* - name: ${{ steps.vars.outputs.distro }}_${{ steps.vars.outputs.release }}_all + name: ${{ matrix.distro }}_${{ matrix.release }}_all upload-to-apt-repo: if: ${{ github.event_name == 'release' || github.event_name == 'workflow_dispatch' }} @@ -147,3 +196,37 @@ jobs: gcloud artifacts apt upload "$release" --source "$artifact" echo "::endgroup::" done + + upload-release-artifacts: + if: ${{ github.event_name == 'release' }} + runs-on: ubuntu-latest + needs: + - build-deb + steps: + - uses: actions/checkout@v3 + - uses: actions/download-artifact@v3 + with: + path: target/debian/ + + - name: upload packages + shell: bash + run: | + set -x + shopt -s nullglob + + url="$(echo '${{ github.event.release.upload_url }}' | sed -e 's/{.*}//g')" + + for artifact in target/debian/**/*; do + name="$(basename "$artifact")" + directory="$(basename "$(dirname "$artifact")")" + + release="$(echo "$directory" | cut -d _ -f 2)" + + curl -L -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "Content-Type: application/octet-stream" \ + -H "X-Github-Api-Version: 2022-11-28" \ + "$url?name=${release}_${name}" \ + --data-binary "@$artifact" + done diff --git a/CHANGELOG.md b/CHANGELOG.md index fd5f2d0f..8d921513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,54 @@ ## [Unreleased] +## [3.6.0] - 2023-10-26 + +### Added + +- Allow configuration of individual samplers in the config file. This allows + each sampler to be individually enabled/disabled and have its collection + intervals adjusted. +- TCP connection state sampler which tracks the number of tcp connections in + each state. +- Rezolus sampler which monitors resource utilization of Rezolus itself. +- Optional exposition of histogram buckets on the Prometheus/OpenTelemetry + endpoint. +- Track latencies for each group of syscalls to help understand the breakdown of + total syscall latency. + +### Fixed + +- Corrected a length check of the mmap'd histogram regions. This fix enables the + fast path for reading histogram data into userspace. + +## [3.5.0] - 2023-10-16 + +### Changed + +- Updated `metriken` and replaced heatmaps with histograms. This reduces runtime + resource utilization. + +## [3.4.0] - 2023-10-10 + +### Changed + +- Moved to fetching multiple percentiles at once to reduce overhead. +- Refactor of the hardware info sampler into a separate crate to allow reuse and + make improvements to that sampler. + +### Fixed + +- Update `warp` to address RUSTSEC-2023-0065. + +## [3.3.3] - 2023-08-08 + +### Added + +- Packaging support for `aarch64` + +### Fixed + +- Updated dependencies to pull-in fixes and improvements. + ## [3.3.2] - 2023-08-08 ### Fixed @@ -47,7 +96,11 @@ - Rewritten implementation of Rezolus using libbpf-rs and perf-event2 to provide a more modern approach to BPF and Perf Event instrumentation. -[unreleased]: https://github.com/iopsystems/rezolus/compare/v3.3.2...HEAD +[unreleased]: https://github.com/iopsystems/rezolus/compare/v3.6.0...HEAD +[3.6.0]: https://github.com/iopsystems/rezolus/compare/v3.5.0...v3.6.0 +[3.5.0]: https://github.com/iopsystems/rezolus/compare/v3.4.0...v3.5.0 +[3.4.0]: https://github.com/iopsystems/rezolus/compare/v3.3.3...v3.4.0 +[3.3.3]: https://github.com/iopsystems/rezolus/compare/v3.3.2...v3.3.3 [3.3.2]: https://github.com/iopsystems/rezolus/compare/v3.3.1...v3.3.2 [3.3.1]: https://github.com/iopsystems/rezolus/compare/v3.3.0...v3.3.1 [3.3.0]: https://github.com/iopsystems/rezolus/compare/v3.2.0...v3.3.0 diff --git a/Cargo.lock b/Cargo.lock index c80edcdb..a040f700 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -19,14 +19,24 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "72832d73be48bac96a5d7944568f305d829ed55b0ce3b483647089dfaf6cf704" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", ] [[package]] @@ -37,30 +47,29 @@ checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" [[package]] name = "anstream" -version = "0.3.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" dependencies = [ "utf8parse", ] @@ -71,24 +80,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.69" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "autocfg" @@ -98,9 +107,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -119,9 +128,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" @@ -131,9 +140,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "block-buffer" @@ -146,36 +155,36 @@ dependencies = [ [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "c-enum" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5fca268f63fbeb9819c5afb333888cd5a245017f6c47f530da2d3b4964055c3" +checksum = "a4b71931af9c2a7756e9eed1b6ac105c31588a44b496423b41412e506f0f47e5" [[package]] name = "camino" -version = "1.1.3" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6031a462f977dd38968b6f23378356512feeace69cef817e1a4475108093cec3" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" dependencies = [ "serde", ] @@ -196,9 +205,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -208,20 +220,19 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.3.19" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.19" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" dependencies = [ "anstream", "anstyle", @@ -231,21 +242,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.12" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "clocksource" @@ -268,9 +279,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4" dependencies = [ "libc", ] @@ -320,11 +331,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", @@ -332,9 +358,9 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -356,6 +382,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -368,12 +405,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fnv" @@ -383,18 +417,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -402,27 +436,27 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-core", "futures-sink", @@ -434,9 +468,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -444,9 +478,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -455,15 +489,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "h2" -version = "0.3.18" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" dependencies = [ "bytes", "fnv", @@ -471,7 +505,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 1.9.2", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -515,18 +549,6 @@ dependencies = [ "http", ] -[[package]] -name = "heatmap" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8cbf90780d798f8068becac8b922ab9901af29e2406e6327c4fda70353c58fa" -dependencies = [ - "clocksource", - "histogram", - "parking_lot", - "thiserror", -] - [[package]] name = "heck" version = "0.4.1" @@ -535,15 +557,15 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "histogram" -version = "0.7.3" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0978bb4ae7b21dded5037bc688271ec01443b6eb2c7713aad75fce2cf670923" +checksum = "72cc09ccef50843ffa24cfc42861950fd45a2b377cc4b66c49ac47d17449f144" dependencies = [ "thiserror", ] @@ -578,15 +600,21 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.24" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -599,7 +627,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -614,9 +642,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -624,9 +652,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", @@ -642,42 +670,11 @@ dependencies = [ "hashbrown 0.14.0", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" -dependencies = [ - "libc", - "windows-sys 0.45.0", -] - -[[package]] -name = "is-terminal" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys 0.45.0", -] - [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "kernel32-sys" @@ -749,9 +746,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.147" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libloading" @@ -765,35 +762,35 @@ dependencies = [ [[package]] name = "linkme" -version = "0.3.14" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6c7bb4dcb6a456ffd750601c389889a58daff6857125b75ee6e5edabf6388c8" +checksum = "91ed2ee9464ff9707af8e9ad834cffa4802f072caad90639c583dd3c62e6e608" dependencies = [ "linkme-impl", ] [[package]] name = "linkme-impl" -version = "0.3.14" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d5adefdb2c04ca9173223070228ff26a04667003619257b8442f192d9986218" +checksum = "ba125974b109d512fccbc6c0244e7580143e460895dfd6ea7f8bbb692fd94396" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -801,12 +798,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "mach" @@ -819,9 +813,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memmap2" @@ -834,9 +828,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" +checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375" dependencies = [ "libc", ] @@ -852,11 +846,10 @@ dependencies = [ [[package]] name = "metriken" -version = "0.2.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283da0f4c6ea37b3d8f932e02d368f1c1f77b3072653a91a5cdef6931b6db889" +checksum = "033e8371ec83b73e4605dd1b1bbf1eaa0eea2884244eb3f713490ab08ca5ae6e" dependencies = [ - "heatmap", "histogram", "linkme", "metriken-derive", @@ -879,9 +872,9 @@ dependencies = [ [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" @@ -904,14 +897,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", - "log", "wasi", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -940,16 +932,15 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", "memoffset", "pin-utils", - "static_assertions", ] [[package]] @@ -1008,9 +999,9 @@ dependencies = [ [[package]] name = "object" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -1042,7 +1033,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -1057,30 +1048,30 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "perf-event-data" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963b5dfa83fa6c28af556b19fc258be88823de8af982469747c6594f48a8b9ba" +checksum = "18b661276d313fbcc35c5ba169a739731e96f1a508326de8837cda5749ea34ce" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.4.0", "perf-event-open-sys2", ] @@ -1095,14 +1086,14 @@ dependencies = [ [[package]] name = "perf-event2" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9a94d7757493d3d22c1bc3055f70680161722dfe6a95457f9a9e165c83d098" +checksum = "5e25284c4a86df3bccd1818dee1fe9390424fce73a58fe2f19957f2cc5de8b28" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.4.0", "c-enum", "libc", - "memmap2 0.7.1", + "memmap2 0.9.0", "perf-event-data", "perf-event-open-sys2", ] @@ -1137,7 +1128,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -1151,29 +1142,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1187,6 +1178,12 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1229,18 +1226,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1277,35 +1274,58 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.7.1" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rezolus" -version = "3.3.2" +version = "3.6.0" dependencies = [ "backtrace", "clap", - "clocksource", + "humantime", "lazy_static", "libbpf-cargo", "libbpf-rs", @@ -1323,6 +1343,7 @@ dependencies = [ "serde_json", "syscall-numbers", "sysconf", + "systeminfo", "tokio", "toml", "walkdir", @@ -1331,9 +1352,9 @@ dependencies = [ [[package]] name = "ringlog" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c90d3d1e4db43daedfdae26373e7b4cb4f56b26f217fc8f661e4f439827b4a46" +checksum = "325bf804f14769bdcd0bcf437a9ac2af50d12986e969df4448205397684ca5dc" dependencies = [ "ahash", "clocksource", @@ -1344,44 +1365,43 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.36.9" +version = "0.38.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", + "bitflags 2.4.0", + "errno 0.3.2", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.0", + "base64 0.21.2", ] [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -1400,9 +1420,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scroll" @@ -1412,49 +1432,49 @@ checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" [[package]] name = "scroll_derive" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbda6ac5cd1321e724fa9cee216f3a61885889b896f073b8f82322789c5250e" +checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] name = "semver" -version = "1.0.16" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.180" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.180" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -1484,9 +1504,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1504,35 +1524,45 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi 0.3.9", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "spin" version = "0.9.8" @@ -1583,9 +1613,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -1604,52 +1634,63 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59e93f5d45535f49b6a05ef7ac2f0f795d28de494cf53a512751602c9849bea3" dependencies = [ - "errno", + "errno 0.2.8", "kernel32-sys", "libc", "winapi 0.2.8", ] +[[package]] +name = "systeminfo" +version = "3.6.0" +dependencies = [ + "serde", + "serde_json", + "walkdir", +] + [[package]] name = "tempfile" -version = "3.4.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "thiserror" -version = "1.0.39" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.39" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] name = "time" -version = "0.3.20" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -1657,15 +1698,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -1687,11 +1728,10 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", @@ -1700,9 +1740,9 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.3", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1713,14 +1753,14 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] name = "tokio-stream" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -1729,9 +1769,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.18.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", @@ -1741,9 +1781,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" dependencies = [ "bytes", "futures-core", @@ -1755,9 +1795,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" dependencies = [ "serde", "serde_spanned", @@ -1776,9 +1816,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.0.0", "serde", @@ -1795,11 +1835,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-core", @@ -1807,9 +1846,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] @@ -1822,13 +1861,13 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" -version = "0.18.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ - "base64 0.13.1", "byteorder", "bytes", + "data-encoding", "http", "httparse", "log", @@ -1841,30 +1880,30 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] [[package]] name = "unicode-bidi" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -1877,9 +1916,9 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -1916,9 +1955,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -1926,19 +1965,18 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] [[package]] name = "warp" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" +checksum = "c1e92e22e03ff1230c03a1a8ee37d2f89cd489e2e541b7550d6afad96faed169" dependencies = [ "bytes", "futures-channel", @@ -2001,9 +2039,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi 0.3.9", ] @@ -2014,158 +2052,77 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.1", -] - [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.1", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", -] - -[[package]] -name = "windows-targets" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.3" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46aab759304e4d7b2075a9aecba26228bb073ee8c50db796b2c72c676b5d807" +checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" dependencies = [ "memchr", ] @@ -2181,3 +2138,23 @@ dependencies = [ "quote", "syn 1.0.109", ] + +[[package]] +name = "zerocopy" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a7af71d8643341260a65f89fa60c0eeaa907f34544d8f6d9b0df72f069b5e74" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9731702e2f0617ad526794ae28fbc6f6ca8849b5ba729666c2a5bc4b6ddee2cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] diff --git a/Cargo.toml b/Cargo.toml index c481754d..72d6c274 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,31 +1,37 @@ [package] name = "rezolus" -version = "3.3.2" -license = "MIT OR Apache-2.0" +version = { workspace = true } +license = { workspace = true } publish = false edition = "2021" description = "High resolution systems performance telemetry agent" +[workspace.package] +version = "3.6.0" +license = "MIT OR Apache-2.0" + [dependencies] -backtrace = "0.3.68" -clap = "4.3.19" -clocksource = "0.6.0" +systeminfo = { workspace = true } + +backtrace = "0.3.69" +clap = "4.3.24" +humantime = "2.1.0" lazy_static = "1.4.0" libc = "0.2.147" -linkme = "0.3.14" -metriken = "0.2.1" +linkme = "0.3.15" +metriken = "0.3.3" memmap2 = "0.5.10" once_cell = "1.18.0" ouroboros = "0.17.2" -serde = { version = "1.0.180", features = ["derive"] } -serde_json = "1.0.104" +ringlog = "0.3.2" +serde = { version = "1.0.185", features = ["derive"] } +serde_json = "1.0.105" sysconf = "0.3.4" syscall-numbers = "3.1.0" -ringlog = "0.2.0" -tokio = { version = "1.29.1", features = ["full"] } +tokio = { version = "1.32.0", features = ["full"] } toml = "0.7.6" walkdir = "2.3.3" -warp = "0.3.5" +warp = "0.3.6" [target.'cfg(target_os = "linux")'.dependencies] libbpf-rs = { version = "0.21.2", optional = true } @@ -50,3 +56,12 @@ codegen-units = 1 debug = true lto = true codegen-units = 1 + +[workspace] +members = [ + ".", + "crates/*" +] + +[workspace.dependencies] +systeminfo = { path = "crates/systeminfo" } diff --git a/Makefile b/Makefile index 28739421..1a1baef8 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,6 @@ pipeline-synth: pull-request-ci: cargo fmt -- --check \ - && cargo clippy --all-targets --all-features -- -D warnings -W clippy::unwrap_used -W clippy::todo -W clippy::panic_in_result_fn -W clippy::expect_used \ + && cargo clippy --all-targets --all-features -- -D warnings -W clippy::todo -W clippy::panic_in_result_fn \ && cargo build --release --features bpf diff --git a/build.rs b/build.rs index d3ab1bb6..f1eaf5a5 100644 --- a/build.rs +++ b/build.rs @@ -22,27 +22,27 @@ mod bpf { pub fn generate() { let out_dir = std::env::var("OUT_DIR").unwrap(); + let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); for (sampler, prog) in SOURCES { let src = format!("src/samplers/{sampler}/{prog}/mod.bpf.c"); let tgt = format!("{out_dir}/{sampler}_{prog}.bpf.rs"); - #[cfg(target_arch = "x86_64")] - SkeletonBuilder::new() - .source(&src) - .clang_args("-I src/common/bpf/x86_64 -fno-unwind-tables") - .build_and_generate(&tgt) - .unwrap(); - - #[cfg(target_arch = "aarch64")] - SkeletonBuilder::new() - .source(&src) - .clang_args("-I src/common/bpf/aarch64 -fno-unwind-tables") - .build_and_generate(&tgt) - .unwrap(); - - #[cfg(all(not(target_arch = "aarch64"), not(target_arch = "x86_64")))] - panic!("BPF support only available for x86_64 and aarch64 architectures"); + if target_arch == "x86_64" { + SkeletonBuilder::new() + .source(&src) + .clang_args("-Isrc/common/bpf/x86_64 -fno-unwind-tables -D__TARGET_ARCH_x86") + .build_and_generate(&tgt) + .unwrap(); + } else if target_arch == "aarch64" { + SkeletonBuilder::new() + .source(&src) + .clang_args("-Isrc/common/bpf/aarch64 -fno-unwind-tables -D__TARGET_ARCH_arm64") + .build_and_generate(&tgt) + .unwrap(); + } else { + panic!("BPF support only available for x86_64 and aarch64 architectures"); + } println!("cargo:rerun-if-changed={src}"); } diff --git a/config.toml b/config.toml index 253e8047..6c9eff20 100644 --- a/config.toml +++ b/config.toml @@ -1,4 +1,124 @@ [general] listen = "0.0.0.0:4242" -[samplers] +[prometheus] +# Controls whether the full distribution for each histogram is exposed via the +# prometheus endpoint (`/metrics`). This adds a considerable number of time +# series depending on the downsampling factor as each histogram bucket is +# represented as its own time series. +histograms = false + +# The histogram can be downsampled for exposition to reduce the number of +# buckets, and therefore reduce the number of timeseries needed to store the +# distribution. +# +# The grouping power must be in the range 2..=7. The native histograms are +# recorded with a grouping power of 7. Any reduction in the grouping power will +# increase the relative error, as the buckets are wider with lower grouping +# powers. +# +# By default, we reduce the grouping power to 4 to greatly reduce the number of +# timeseries but maintain an acceptable relative error for most uses. +# +# See https://docs.rs/histogram/ for more information about the grouping power. +# +# Power: Error: Buckets: +# 7 0.781% 7424 +# 6 1.56% 3776 +# 5 3.13% 1920 +# 4 6.25% 976 +# 3 12.5% 496 +# 2 25.0% 252 +histogram_grouping_power = 4 + +# The defaults are used for each sampler unless there's a sampler level +# configuration present. + +[defaults] +# Controls whether the samplers are enabled or not. Setting the default to +# true means that individual sampler configs can be used to opt-out of +# collection for that sampler. Setting the default to false requires that +# individual sampler configs are used to opt-in to collection. +enabled = true +# The collection interval for counter and gauge based metrics. Shorter intervals +# allow for more accurately capturing bursts in the related percentile metrics. +interval = "10ms" +# The collection interval for metrics that sample a distribution. Shorter +# intervals reduce the uncertainty of the exact period corresponding to the +# related percentile metrics. +distribution_interval = "50ms" + +# Each sampler can then be individually configured to override the defaults. All +# of the configuration options in the `[defaults]` section are allowed. + +# BPF sampler that instruments block_io request queue to measure latency and +# size distribution. +[samplers.block_io_latency] +enabled = true + +# Instruments CPU frequency, instructions, and cycles using perf events with +# fallback to instrumenting frequency only via /proc/cpuinfo +[samplers.cpu_perf] +enabled = true + +# Instruments CPU usage by state by reading /proc/stat +[samplers.cpu_proc_stat] +enabled = true + +# Produces various nVIDIA specific GPU metrics using NVML +[samplers.gpu_nvidia] +enabled = true + +# Memory utilization from /proc/meminfo +[samplers.memory_meminfo] +enabled = true + +# Memory NUMA metrics from /proc/vmstat +[samplers.memory_vmstat] +enabled = true + +# Sample resource utilization for Rezolus itself +[samplers.rezolus_rusage] +enabled = true + +# BPF sampler that instruments scheduler events and measures runqueue latency, +# process running time, and context switch information. +[samplers.scheduler_runqueue] +enabled = true + +# BPF sampler that instruments syscall enter and exit to gather syscall counts +# and latencies. +[samplers.syscall_latency] +enabled = true + +# Instruments TCP connection states by reading /proc/net/tcp +# +# Note: this sampler causes higher CPU utilization than our other samplers when +# running with short intervals. To reduce that cost, we override this to sample +# on a secondly basis. +[samplers.tcp_connection_state] +enabled = true +interval = "1s" + +# BPF sampler that probes TCP receive path to measure latency from a packet +# being received until application reads from the socket. +[samplers.tcp_packet_latency] +enabled = true + +# BPF sampler that probes TCP receive path to measure jitter and smoothed round +# trip time. +[samplers.tcp_receive] +enabled = true + +# BPF sampler that probes TCP retransmit path to measure retransmits. +[samplers.tcp_retransmit] +enabled = true + +# TCP sampler that reads from /proc/snmp +[samplers.tcp_snmp] +enabled = true + +# BPF sampler that probes TCP send and receive paths to instrument tx/rx size +# distribution, bytes, and packets. +[samplers.tcp_traffic] +enabled = true diff --git a/crates/systeminfo/Cargo.toml b/crates/systeminfo/Cargo.toml new file mode 100644 index 00000000..3877eeb4 --- /dev/null +++ b/crates/systeminfo/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "systeminfo" +edition = "2021" +version = { workspace = true } +license = { workspace = true } +publish = false + +[features] +cli = [ + "dep:serde_json" +] + +[dependencies] +serde = { version = "1.0.188", features = ["derive"] } +serde_json = { version = "1.0.107", optional = true } +walkdir = "2.4.0" + + +[[bin]] +name = "systeminfo" +path = "src/main.rs" +required-features = ["cli"] diff --git a/crates/systeminfo/src/error.rs b/crates/systeminfo/src/error.rs new file mode 100644 index 00000000..e12cb818 --- /dev/null +++ b/crates/systeminfo/src/error.rs @@ -0,0 +1,114 @@ +use core::fmt; +use std::io; +use std::path::{Path, PathBuf}; + +pub type Result = std::result::Result; + +#[derive(Debug)] +pub struct Error { + path: Option, + source: ErrorSource, + kind: ErrorKind, +} + +impl Error { + pub(crate) fn new(kind: ErrorKind, source: ErrorSource, path: Option) -> Self { + Self { kind, source, path } + } + + pub(crate) fn with_path( + kind: ErrorKind, + source: impl Into, + path: impl Into, + ) -> Self { + Self::new(kind, source.into(), Some(path.into())) + } + + pub(crate) fn without_path(kind: ErrorKind, source: impl Into) -> Self { + Self::new(kind, source.into(), None) + } + + fn path(&self) -> &Path { + match &self.path { + Some(path) => path, + None => Path::new(""), + } + } + + pub(crate) fn unreadable(error: io::Error, path: impl AsRef) -> Self { + Self::with_path(ErrorKind::UnreadableFile, error, path.as_ref()) + } + + pub(crate) fn unparseable(error: impl Into, path: impl AsRef) -> Self { + Self::with_path(ErrorKind::UnparseableFile, error, path.as_ref()) + } + + pub(crate) fn invalid_interface_name() -> Self { + Self::without_path(ErrorKind::InvalidInterfaceName, ErrorSource::None) + } +} + +#[derive(Debug)] +pub(crate) enum ErrorSource { + Io(io::Error), + ParseInt(std::num::ParseIntError), + None, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum ErrorKind { + UnreadableFile, + UnparseableFile, + InvalidInterfaceName, +} + +impl fmt::Display for ErrorSource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Io(e) => e.fmt(f), + Self::ParseInt(e) => e.fmt(f), + Self::None => f.write_str(""), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.kind { + ErrorKind::UnreadableFile => write!( + f, + "could not read {}: {}", + self.path().display(), + self.source + ), + ErrorKind::UnparseableFile => write!( + f, + "could not parse the contents of {}", + self.path().display() + ), + ErrorKind::InvalidInterfaceName => f.write_str("interface name was not valid UTF-8"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match &self.source { + ErrorSource::Io(err) => Some(err), + ErrorSource::ParseInt(err) => Some(err), + ErrorSource::None => None, + } + } +} + +impl From for ErrorSource { + fn from(value: io::Error) -> Self { + Self::Io(value) + } +} + +impl From for ErrorSource { + fn from(value: std::num::ParseIntError) -> Self { + Self::ParseInt(value) + } +} diff --git a/src/samplers/hwinfo/cache.rs b/crates/systeminfo/src/hwinfo/cache.rs similarity index 70% rename from src/samplers/hwinfo/cache.rs rename to crates/systeminfo/src/hwinfo/cache.rs index 8bb03d9d..9cbd3bb0 100644 --- a/src/samplers/hwinfo/cache.rs +++ b/crates/systeminfo/src/hwinfo/cache.rs @@ -1,16 +1,21 @@ -use super::*; +use std::path::Path; -#[derive(Clone, Serialize)] +use super::util::*; +use crate::error::ErrorSource; +use crate::{Error, Result}; + +#[non_exhaustive] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Cache { - coherency_line_size: usize, - number_of_sets: usize, - shared_cpus: Vec, - size: String, - r#type: CacheType, - ways_of_associativity: usize, + pub coherency_line_size: usize, + pub number_of_sets: usize, + pub shared_cpus: Vec, + pub size: String, + pub r#type: CacheType, + pub ways_of_associativity: usize, } -#[derive(Clone, Serialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum CacheType { Data, @@ -51,17 +56,16 @@ impl Cache { } fn read_cache_type(path: impl AsRef) -> Result { - let raw = std::fs::read_to_string(path)?; + let path = path.as_ref(); + + let raw = std::fs::read_to_string(path).map_err(|e| Error::unreadable(e, path))?; let raw = raw.trim(); match raw { - "Data" | "data" => Ok(CacheType::Data), - "Instruction" | "instruction" => Ok(CacheType::Instruction), - "Unified" | "unified" => Ok(CacheType::Unified), - _ => Err(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "unexpected cache type", - )), + _ if raw.eq_ignore_ascii_case("data") => Ok(CacheType::Data), + _ if raw.eq_ignore_ascii_case("instruction") => Ok(CacheType::Instruction), + _ if raw.eq_ignore_ascii_case("unified") => Ok(CacheType::Unified), + _ => Err(Error::unparseable(ErrorSource::None, path)), } } diff --git a/src/samplers/hwinfo/cpu.rs b/crates/systeminfo/src/hwinfo/cpu.rs similarity index 52% rename from src/samplers/hwinfo/cpu.rs rename to crates/systeminfo/src/hwinfo/cpu.rs index 16cd9d0d..d6e2bc16 100644 --- a/src/samplers/hwinfo/cpu.rs +++ b/crates/systeminfo/src/hwinfo/cpu.rs @@ -1,26 +1,35 @@ -use super::*; - -#[derive(Serialize)] +use std::collections::BTreeMap; +use std::collections::HashSet; +use std::fs::File; +use std::io::BufRead; +use std::io::BufReader; + +use super::util::*; +use super::Cache; +use crate::{Error, Result}; + +#[non_exhaustive] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Cpu { - id: usize, + pub id: usize, - core_id: usize, - die_id: usize, - package_id: usize, + pub core_id: usize, + pub die_id: usize, + pub package_id: usize, - core_cpus: Vec, - die_cpus: Vec, - package_cpus: Vec, + pub core_cpus: Vec, + pub die_cpus: Vec, + pub package_cpus: Vec, - core_siblings: Vec, - thread_siblings: Vec, + pub core_siblings: Vec, + pub thread_siblings: Vec, - microcode: Option, - vendor: Option, - model_name: Option, - features: Option, + pub microcode: Option, + pub vendor: Option, + pub model_name: Option, + pub features: Option>, - caches: Vec, + pub caches: Vec, } impl Cpu { @@ -42,7 +51,7 @@ impl Cpu { } pub fn get_cpus() -> Result> { - let mut tmp = HashMap::new(); + let mut tmp = BTreeMap::new(); // first read from /sys and build up some basic information let ids = read_list("/sys/devices/system/cpu/online")?; @@ -108,63 +117,67 @@ pub fn get_cpus() -> Result> { // there's a lot of information that's easier to get from /proc/cpuinfo - let file = File::open("/proc/cpuinfo")?; - let reader = BufReader::new(file); + let path = "/proc/cpuinfo"; + let file = File::open(path).map_err(|e| Error::unreadable(e, path))?; + let mut reader = BufReader::new(file); let mut id: Option = None; - - for line in reader.lines() { - if line.is_err() { - break; + let mut line = String::new(); + + while reader + .read_line(&mut line) + .map_err(|e| Error::unreadable(e, path))? + != 0 + { + let line = ClearGuard::new(&mut line); + let parts: Vec<&str> = line.split(':').map(|v| v.trim()).collect(); + + if parts.len() != 2 { + continue; } - let line = line.unwrap(); - - let parts: Vec = line.split(':').map(|v| v.trim().to_owned()).collect(); - - if parts.len() == 2 { - match parts[0].as_str() { - "processor" => { - if let Ok(v) = parts[1].parse() { - id = Some(v); - } + match parts[0] { + "processor" => { + if let Ok(v) = parts[1].parse() { + id = Some(v); } - "vendor_id" => { - if let Some(id) = id { - if let Some(cpu) = tmp.get_mut(&id) { - cpu.vendor = Some(parts[1].clone()); - } + } + "vendor_id" => { + if let Some(id) = id { + if let Some(cpu) = tmp.get_mut(&id) { + cpu.vendor = Some(parts[1].to_owned()); } } - "model name" => { - if let Some(id) = id { - if let Some(cpu) = tmp.get_mut(&id) { - cpu.model_name = Some(parts[1].clone()); - } + } + "model name" => { + if let Some(id) = id { + if let Some(cpu) = tmp.get_mut(&id) { + cpu.model_name = Some(parts[1].to_owned()); } } - "microcode" => { - if let Some(id) = id { - if let Some(cpu) = tmp.get_mut(&id) { - cpu.microcode = Some(parts[1].clone()); - } + } + "microcode" => { + if let Some(id) = id { + if let Some(cpu) = tmp.get_mut(&id) { + cpu.microcode = Some(parts[1].to_owned()); } } - "flags" | "Features" => { - if let Some(id) = id { - if let Some(cpu) = tmp.get_mut(&id) { - cpu.features = Some(parts[1].clone()); - } + } + "flags" | "Features" => { + if let Some(id) = id { + if let Some(cpu) = tmp.get_mut(&id) { + cpu.features = Some( + parts[1] + .split_ascii_whitespace() + .map(|s| s.to_owned()) + .collect(), + ); } } - _ => {} } + _ => (), } } - let mut ret: Vec = tmp.drain().map(|(_, v)| v).collect(); - - ret.sort_by(|a, b| a.id.cmp(&b.id)); - - Ok(ret) + Ok(tmp.into_values().collect()) } diff --git a/crates/systeminfo/src/hwinfo/memory.rs b/crates/systeminfo/src/hwinfo/memory.rs new file mode 100644 index 00000000..22b1c9ab --- /dev/null +++ b/crates/systeminfo/src/hwinfo/memory.rs @@ -0,0 +1,60 @@ +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::Path; + +use super::util::*; +use crate::{Error, Result}; + +#[non_exhaustive] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Memory { + pub total_bytes: u64, +} + +impl Memory { + pub fn new() -> Result { + Self::from_file("/proc/meminfo") + } + + pub fn node(id: usize) -> Result { + Self::from_file(format!("/sys/devices/system/node/node{id}/meminfo")) + } + + fn from_file(path: impl AsRef) -> Result { + let path = path.as_ref(); + let file = File::open(path).map_err(|e| Error::unreadable(e, path))?; + let mut reader = BufReader::new(file); + + let mut memory = Self { total_bytes: 0 }; + let mut line = String::new(); + + while reader + .read_line(&mut line) + .map_err(|e| Error::unreadable(e, path))? + != 0 + { + let line = ClearGuard::new(&mut line); + + if !line.starts_with("MemTotal:") { + continue; + } + + let mut parts = line.split_ascii_whitespace().skip(1); + let Some(value) = parts.next() else { + continue; + }; + let Some(_unit) = parts.next() else { + continue; + }; + + if parts.next().is_some() { + continue; + } + + let kilobytes: u64 = value.parse().map_err(|e| Error::unparseable(e, path))?; + memory.total_bytes = kilobytes * 1024; + } + + Ok(memory) + } +} diff --git a/crates/systeminfo/src/hwinfo/mod.rs b/crates/systeminfo/src/hwinfo/mod.rs new file mode 100644 index 00000000..ab711a68 --- /dev/null +++ b/crates/systeminfo/src/hwinfo/mod.rs @@ -0,0 +1,40 @@ +use crate::Result; + +mod cache; +mod cpu; +mod memory; +mod net; +mod node; +mod util; + +pub use self::cache::{Cache, CacheType}; +pub use self::cpu::Cpu; +pub use self::memory::Memory; +pub use self::net::{Interface, Queues}; +pub use self::node::Node; + +#[non_exhaustive] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct HwInfo { + pub caches: Vec>, + pub cpus: Vec, + pub memory: Memory, + pub network: Vec, + pub nodes: Vec, +} + +impl HwInfo { + pub fn new() -> Result { + Ok(Self { + caches: self::cache::get_caches()?, + cpus: self::cpu::get_cpus()?, + memory: Memory::new()?, + network: self::net::get_interfaces(), + nodes: self::node::get_nodes()?, + }) + } + + pub fn get_cpus(&self) -> &Vec { + &self.cpus + } +} diff --git a/src/samplers/hwinfo/net.rs b/crates/systeminfo/src/hwinfo/net.rs similarity index 80% rename from src/samplers/hwinfo/net.rs rename to crates/systeminfo/src/hwinfo/net.rs index 59af9143..02b86cc6 100644 --- a/src/samplers/hwinfo/net.rs +++ b/crates/systeminfo/src/hwinfo/net.rs @@ -1,26 +1,31 @@ -use super::*; +use std::ffi::OsStr; -#[derive(Serialize)] +use walkdir::WalkDir; + +use super::util::*; +use crate::{Error, Result}; + +#[non_exhaustive] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Interface { - name: String, - carrier: bool, - speed: Option, - node: Option, - mtu: usize, - queues: Queues, + pub name: String, + pub carrier: bool, + pub speed: Option, + pub node: Option, + pub mtu: usize, + pub queues: Queues, } -#[derive(Serialize)] +#[non_exhaustive] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Queues { - tx: usize, - rx: usize, - combined: usize, + pub tx: usize, + pub rx: usize, + pub combined: usize, } fn get_interface(name: &OsStr) -> Result> { - let name = name.to_str().ok_or_else(|| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "bad interface name") - })?; + let name = name.to_str().ok_or_else(Error::invalid_interface_name)?; // skip any that aren't "up" let operstate = read_string(format!("/sys/class/net/{name}/operstate"))?; diff --git a/src/samplers/hwinfo/node.rs b/crates/systeminfo/src/hwinfo/node.rs similarity index 79% rename from src/samplers/hwinfo/node.rs rename to crates/systeminfo/src/hwinfo/node.rs index 84d93ae8..18b463d2 100644 --- a/src/samplers/hwinfo/node.rs +++ b/crates/systeminfo/src/hwinfo/node.rs @@ -1,10 +1,13 @@ -use super::*; +use super::memory::Memory; +use super::util::*; +use crate::Result; -#[derive(Serialize)] +#[non_exhaustive] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Node { - id: usize, - memory: Memory, - cpus: Vec, + pub id: usize, + pub memory: Memory, + pub cpus: Vec, } pub fn get_nodes() -> Result> { diff --git a/crates/systeminfo/src/hwinfo/util.rs b/crates/systeminfo/src/hwinfo/util.rs new file mode 100644 index 00000000..25ac0576 --- /dev/null +++ b/crates/systeminfo/src/hwinfo/util.rs @@ -0,0 +1,111 @@ +use std::ops::{Deref, DerefMut}; +use std::path::Path; + +use walkdir::DirEntry; + +use crate::{Error, Result}; + +pub(crate) fn read_usize(path: impl AsRef) -> Result { + let path = path.as_ref(); + + let raw = std::fs::read_to_string(path).map_err(|e| Error::unreadable(e, path))?; + let raw = raw.trim(); + + raw.parse().map_err(|e| Error::unparseable(e, path)) +} + +pub(crate) fn read_string(path: impl AsRef) -> Result { + let path = path.as_ref(); + + let raw = std::fs::read_to_string(path).map_err(|e| Error::unreadable(e, path))?; + let raw = raw.trim(); + + Ok(raw.to_string()) +} + +pub(crate) fn read_list(path: impl AsRef) -> Result> { + let path = path.as_ref(); + let raw = std::fs::read_to_string(path).map_err(|e| Error::unreadable(e, path))?; + parse_list(&raw, path) +} + +fn parse_list(raw: &str, path: &Path) -> Result> { + let raw = raw.trim(); + let mut ret = Vec::new(); + + for range in raw.split(',') { + let mut parts = range.split('-'); + + let first: Option = parts + .next() + .map(|text| text.parse()) + .transpose() + .map_err(|e| Error::unparseable(e, path))?; + let second: Option = parts + .next() + .map(|text| text.parse()) + .transpose() + .map_err(|e| Error::unparseable(e, path))?; + + if parts.next().is_some() { + // The line is invalid, skip it. + continue; + } + + match (first, second) { + (Some(value), None) => ret.push(value), + (Some(start), Some(stop)) => ret.extend(start..=stop), + _ => continue, + } + } + + Ok(ret) +} + +pub(crate) fn is_hidden(entry: &DirEntry) -> bool { + entry + .file_name() + .to_str() + .map(|s| s.starts_with('.')) + .unwrap_or(false) +} + +/// Guard which clears the contained string upon drop. +pub(crate) struct ClearGuard<'a>(&'a mut String); + +impl<'a> ClearGuard<'a> { + pub fn new(value: &'a mut String) -> Self { + Self(value) + } +} + +impl<'a> Drop for ClearGuard<'a> { + fn drop(&mut self) { + self.0.clear(); + } +} + +impl<'a> Deref for ClearGuard<'a> { + type Target = String; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a> DerefMut for ClearGuard<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn list_parsing() { + let list = "0-1\r\n"; + assert_eq!(parse_list(list, "/test/case".as_ref()).unwrap(), vec![0, 1]); + } +} diff --git a/crates/systeminfo/src/lib.rs b/crates/systeminfo/src/lib.rs new file mode 100644 index 00000000..47b29615 --- /dev/null +++ b/crates/systeminfo/src/lib.rs @@ -0,0 +1,29 @@ +//! Gather a comprehensive description of the current system. +//! + +#[macro_use] +extern crate serde; + +mod error; +pub mod hwinfo; + +pub use crate::error::{Error, Result}; + +/// Read the [`SystemInfo`] for the current system. +pub fn systeminfo() -> Result { + SystemInfo::new() +} + +#[non_exhaustive] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SystemInfo { + pub hwinfo: crate::hwinfo::HwInfo, +} + +impl SystemInfo { + pub fn new() -> Result { + Ok(Self { + hwinfo: crate::hwinfo::HwInfo::new()?, + }) + } +} diff --git a/crates/systeminfo/src/main.rs b/crates/systeminfo/src/main.rs new file mode 100644 index 00000000..da97e339 --- /dev/null +++ b/crates/systeminfo/src/main.rs @@ -0,0 +1,6 @@ +fn main() { + let sysinfo = systeminfo::systeminfo().expect("failed to gather systeminfo"); + let json = serde_json::to_string_pretty(&sysinfo).unwrap(); + + println!("{json}"); +} diff --git a/debian/.gitignore b/debian/.gitignore index 8f1a92ac..4cf96531 100644 --- a/debian/.gitignore +++ b/debian/.gitignore @@ -1,4 +1,6 @@ /*/ +!/cargo/ +!/source/ debhelper-build-stamp files *.debhelper diff --git a/debian/cargo/create-config.sh b/debian/cargo/create-config.sh new file mode 100755 index 00000000..a2fa341d --- /dev/null +++ b/debian/cargo/create-config.sh @@ -0,0 +1,31 @@ +#!/usr/bin/bash + +set -eu + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd "$SCRIPT_DIR/.." + + +DEB_HOST_GNU_TYPE=${DEB_HOST_GNU_TYPE:-$(dpkg-architecture -qDEB_HOST_GNU_TYPE)} + +RUSTFLAGS=("${RUSTFLAGS[@]}") +RUSTFLAGS+=( + --cap-lints warn + -Cdebuginfo=2 + "-Clinker=$DEB_HOST_GNU_TYPE-gcc" +) + +LDFLAGS="${LDFLAGS:-}" +for ldflag in $LDFLAGS; do + RUSTFLAGS+=("-Clink-arg=$ldflag") +done + + +echo "[build]" +echo "rustflags = [" + +for flag in "${RUSTFLAGS[@]}"; do + echo " \"$flag\"," +done + +echo "]" diff --git a/debian/cargo/cross-compile.mk b/debian/cargo/cross-compile.mk new file mode 100644 index 00000000..c0602d74 --- /dev/null +++ b/debian/cargo/cross-compile.mk @@ -0,0 +1,27 @@ + +include /usr/share/dpkg/buildtools.mk +include debian/cargo/rustc-architecture.mk + +export DEB_HOST_RUST_TYPE DEB_BUILD_RUST_TYPE DEB_TARGET_RUST_TYPE + +ifneq ($(DEB_HOST_GNU_TYPE), $(DEB_BUILD_GNU_TYPE)) + +# The pkg-config crate needs this to be set otherwise it will refuse to allow +# any cross compiling. +export PKG_CONFIG_ALLOW_CROSS := 1 + +# The clang driver used by bindgen uses native headers unless you explicitly set +# the sysroot so we do it here. +export BINDGEN_EXTRA_CLANG_ARGS := --sysroot /usr/$(DEB_HOST_GNU_TYPE) + +endif + +export TARGET_AR := $(AR) +export TARGET_CC := $(CC) +export TARGET_CXX := $(CXX) +export TARGET_PKG_CONFIG := $(PKG_CONFIG) + +export HOST_AR := $(AR_FOR_BUILD) +export HOST_CC := $(CC_FOR_BUILD) +export HOST_CXX := $(CXX_FOR_BUILD) +export HOST_PKG_CONFIG := $(PKG_CONFIG_FOR_BUILD) diff --git a/debian/cargo/rustc-architecture.mk b/debian/cargo/rustc-architecture.mk new file mode 100644 index 00000000..f6184d12 --- /dev/null +++ b/debian/cargo/rustc-architecture.mk @@ -0,0 +1,20 @@ +# This file was taken from the rustc debian package. + +# This Makefile snippet defines DEB_*_RUST_TYPE triples based on DEB_*_GNU_TYPE + +include /usr/share/dpkg/architecture.mk + +rust_cpu = $(subst i586,i686,\ +$(if $(findstring -riscv64-,-$(2)-),$(subst riscv64,riscv64gc,$(1)),\ +$(if $(findstring -armhf-,-$(2)-),$(subst arm,armv7,$(1)),\ +$(if $(findstring -armel-,-$(2)-),$(subst arm,armv5te,$(1)),\ +$(1))))) +rust_type_setvar = $(1)_RUST_TYPE ?= $(call rust_cpu,$($(1)_GNU_CPU),$($(1)_ARCH))-unknown-$($(1)_GNU_SYSTEM) + +$(foreach machine,BUILD HOST TARGET,\ + $(eval $(call rust_type_setvar,DEB_$(machine)))) + +# fallback for older dpkg versions +ifeq ($(DEB_TARGET_RUST_TYPE),-unknown-) + DEB_TARGET_RUST_TYPE = $(DEB_HOST_RUST_TYPE) +endif diff --git a/debian/changelog b/debian/changelog index cad69634..46cb9e27 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -rezolus (3.0.0-alpha.1) UNRELEASED; urgency=medium +rezolus (0.0.0) UNRELEASED; urgency=medium * Rezolus 3.0.0-alpha.1 release. diff --git a/debian/control b/debian/control index 28f69e6e..675d354f 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,13 @@ Source: rezolus Section: devel Priority: optional Maintainer: IOP Systems -Build-Depends: build-essential, debhelper (>= 10), jq +Standards-Version: 4.6.2 +Build-Depends: + debhelper (>= 10), + pkg-config, + libelf-dev, + libelf-dev:native, + clang:native Package: rezolus Architecture: any diff --git a/debian/rezolus.install b/debian/rezolus.install index c4d4b1b6..aa9582f8 100644 --- a/debian/rezolus.install +++ b/debian/rezolus.install @@ -1,2 +1,2 @@ -target/rezolus usr/bin/ +bin/rezolus usr/bin/ config.toml etc/rezolus/ diff --git a/debian/rules b/debian/rules index 957503e1..6558c8bf 100755 --- a/debian/rules +++ b/debian/rules @@ -1,24 +1,38 @@ #!/usr/bin/make -f -TARGET ?= $(shell cargo metadata --format-version 1 | jq -r .target_directory) +include debian/cargo/cross-compile.mk + +export CARGO_HOME := debian/cargo_home +export CARGO_TARGET_DIR := debian/cargo_target + +TRIPLE := $(DEB_HOST_RUST_TYPE) PROFILE ?= release +CARGO ?= cargo +CARGOFLAGS ?= --locked # The debug profile is the only one whose directory under target does not match # the profile name so we need to special case it. -ifeq (${PROFILE}, dev) -BINDIR = "${TARGET}/debug" +ifeq ($(strip ${PROFILE}), dev) +DIRNAME := debug else -BINDIR = "${TARGET}/${PROFILE}" +DIRNAME := $(PROFILE) endif +BINDIR := $(CARGO_TARGET_DIR)/$(TRIPLE)/$(DIRNAME) + %: dh $@ override_dh_auto_clean: + rm -f debian/cargo_home/config.toml + +override_dh_auto_configure: + @mkdir -p debian/cargo_home + bash debian/cargo/create-config.sh > debian/cargo_home/config.toml override_dh_auto_build: - cargo build --profile ${PROFILE} --bins + $(CARGO) build --target $(DEB_HOST_RUST_TYPE) --profile $(PROFILE) --bins $(CARGOFLAGS) override_dh_auto_install: - mkdir -p debian/tmp - ln -s "${BINDIR}" debian/tmp/target \ No newline at end of file + @mkdir -p debian/tmp/bin + cp -f "$(BINDIR)/rezolus" debian/tmp/bin/ \ No newline at end of file diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 00000000..89ae9db8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/debian/source/local-options b/debian/source/local-options new file mode 100644 index 00000000..500a62a2 --- /dev/null +++ b/debian/source/local-options @@ -0,0 +1,5 @@ +--tar-ignore=.git +--tar-ignore=target +--tar-ignore=.vscode +--tar-ignore=debian/cargo_home +--tar-ignore=debian/cargo_target diff --git a/src/common/bpf/counters.rs b/src/common/bpf/counters.rs index 6f5c9511..21da257f 100644 --- a/src/common/bpf/counters.rs +++ b/src/common/bpf/counters.rs @@ -1,4 +1,5 @@ use super::*; +use ringlog::*; /// Represents a collection of counters in a open BPF map. The map must be /// created with: @@ -50,32 +51,51 @@ impl<'a> Counters<'a> { } } - pub fn refresh(&mut self, now: Instant, elapsed: f64) { - for value in self.values.iter_mut() { - *value = 0; - } + pub fn refresh(&mut self, elapsed: f64) { + // reset the values of the combined counters to zero + self.values.fill(0); + + let counters_per_cpu = self.cachelines * CACHELINE_SIZE / std::mem::size_of::(); + + let (_prefix, values, _suffix) = unsafe { self.mmap.align_to::() }; + + // if the number of aligned u64 values matches the total number of + // per-cpu counters, then we can use a more efficient update strategy + if values.len() == MAX_CPUS * counters_per_cpu { + for cpu in 0..MAX_CPUS { + for idx in 0..self.counters.len() { + // add this CPU's counter to the combined value for this counter + self.values[idx] = + self.values[idx].wrapping_add(values[idx + cpu * counters_per_cpu]); + } + } + } else { + warn!("mmap region misaligned or did not have expected number of values"); - for cpu in 0..MAX_CPUS { - for idx in 0..self.counters.len() { - let start = - (cpu * self.cachelines * CACHELINE_SIZE) + (idx * std::mem::size_of::()); - let value = u64::from_ne_bytes([ - self.mmap[start + 0], - self.mmap[start + 1], - self.mmap[start + 2], - self.mmap[start + 3], - self.mmap[start + 4], - self.mmap[start + 5], - self.mmap[start + 6], - self.mmap[start + 7], - ]); + for cpu in 0..MAX_CPUS { + for idx in 0..self.counters.len() { + let start = (cpu * self.cachelines * CACHELINE_SIZE) + + (idx * std::mem::size_of::()); + let value = u64::from_ne_bytes([ + self.mmap[start + 0], + self.mmap[start + 1], + self.mmap[start + 2], + self.mmap[start + 3], + self.mmap[start + 4], + self.mmap[start + 5], + self.mmap[start + 6], + self.mmap[start + 7], + ]); - self.values[idx] = self.values[idx].wrapping_add(value); + // add this CPU's counter to the combined value for this counter + self.values[idx] = self.values[idx].wrapping_add(value); + } } } + // set each counter to its new combined value for (value, counter) in self.values.iter().zip(self.counters.iter_mut()) { - counter.set(now, elapsed, *value); + counter.set(elapsed, *value); } } } diff --git a/src/common/bpf/distribution.rs b/src/common/bpf/distribution.rs index 07b3beff..92bfd472 100644 --- a/src/common/bpf/distribution.rs +++ b/src/common/bpf/distribution.rs @@ -1,4 +1,5 @@ use super::*; +use ringlog::*; /// Represents a distribution in a BPF map. The distribution must be created /// with: @@ -16,23 +17,23 @@ use super::*; /// This distribution must also be indexed into using the `value_to_index` /// helper from `histogram.h`. This results in a histogram that uses 64bit /// counters and covers the entire range of u64 values. This histogram occupies -/// 60KB in kernel space and an additional ~3.5MB in user space. +/// 60KB in kernel space and an additional 60KB in user space. /// /// The distribution should be given some meaningful name in the BPF program. pub struct Distribution<'a> { _map: &'a libbpf_rs::Map, mmap: memmap2::MmapMut, - prev: [u64; HISTOGRAM_BUCKETS], - heatmap: &'static Heatmap, + buffer: Vec, + histogram: &'static RwLockHistogram, } impl<'a> Distribution<'a> { - pub fn new(map: &'a libbpf_rs::Map, heatmap: &'static Heatmap) -> Self { + pub fn new(map: &'a libbpf_rs::Map, histogram: &'static RwLockHistogram) -> Self { let fd = map.as_fd().as_raw_fd(); let file = unsafe { std::fs::File::from_raw_fd(fd as _) }; let mmap = unsafe { memmap2::MmapOptions::new() - .len(HISTOGRAM_PAGES * PAGE_SIZE) // TODO(bmartin): double check this... + .len(HISTOGRAM_PAGES * PAGE_SIZE) .map_mut(&file) .expect("failed to mmap() bpf distribution") }; @@ -40,33 +41,50 @@ impl<'a> Distribution<'a> { Self { _map: map, mmap, - prev: [0; HISTOGRAM_BUCKETS], - heatmap, + buffer: Vec::new(), + histogram, } } - pub fn refresh(&mut self, now: Instant) { - for (idx, prev) in self.prev.iter_mut().enumerate() { - let start = idx * std::mem::size_of::(); - let val = u64::from_ne_bytes([ - self.mmap[start + 0], - self.mmap[start + 1], - self.mmap[start + 2], - self.mmap[start + 3], - self.mmap[start + 4], - self.mmap[start + 5], - self.mmap[start + 6], - self.mmap[start + 7], - ]); + pub fn refresh(&mut self) { + // If the mmap'd region is properly aligned we can more efficiently + // update the histogram. Otherwise, fall-back to the old strategy. - let delta = val - *prev; + let (_prefix, buckets, _suffix) = unsafe { self.mmap.align_to::() }; - *prev = val; + let expected_len = HISTOGRAM_PAGES * PAGE_SIZE / 8; - if delta > 0 { - let value = key_to_value(idx as u64); - let _ = self.heatmap.add(now, value as _, delta as _); + if buckets.len() == expected_len { + let _ = self.histogram.update_from(&buckets[0..HISTOGRAM_BUCKETS]); + } else { + warn!("mmap region misaligned or did not have expected number of values {} != {expected_len}", buckets.len()); + + self.buffer.resize(HISTOGRAM_BUCKETS, 0); + + for (idx, bucket) in self.buffer.iter_mut().enumerate() { + let start = idx * std::mem::size_of::(); + + if start + 7 >= self.mmap.len() { + break; + } + + let val = u64::from_ne_bytes([ + self.mmap[start + 0], + self.mmap[start + 1], + self.mmap[start + 2], + self.mmap[start + 3], + self.mmap[start + 4], + self.mmap[start + 5], + self.mmap[start + 6], + self.mmap[start + 7], + ]); + + *bucket = val; } + + let _ = self + .histogram + .update_from(&self.buffer[0..HISTOGRAM_BUCKETS]); } } } diff --git a/src/common/bpf/mod.rs b/src/common/bpf/mod.rs index 389ceb0a..f6b1da99 100644 --- a/src/common/bpf/mod.rs +++ b/src/common/bpf/mod.rs @@ -1,4 +1,5 @@ use super::*; +use metriken::RwLockHistogram; use ouroboros::*; use std::os::fd::{AsFd, AsRawFd, FromRawFd}; @@ -19,33 +20,13 @@ const CACHELINE_SIZE: usize = 64; static MAX_CPUS: usize = 1024; /// The number of histogram buckets based on a rustcommon histogram with the -/// parameters `m = 0`, `r = 8`, `n = 64`. +/// parameters `grouping_power = 7` `max_value_power = 64`. /// /// NOTE: this *must* remain in-sync across both C and Rust components of BPF /// code. const HISTOGRAM_BUCKETS: usize = 7424; const HISTOGRAM_PAGES: usize = 15; -/// This function converts indices back to values for rustcommon histogram with -/// the parameters `m = 0`, `r = 8`, `n = 64`. This covers the entire range from -/// 1 to u64::MAX and uses 7424 buckets per histogram, which works out to 58KB -/// for each histogram in kernelspace (64bit counters). In userspace, we will -/// we will likely have 61 histograms => 1769KB per stat in userspace. -pub fn key_to_value(index: u64) -> u64 { - // g = index >> (r - m - 1) - let g = index >> 7; - // b = index - g * G + 1 - let b = index - g * 128 + 1; - - if g < 1 { - // (1 << m) * b - 1 - b - 1 - } else { - // (1 << (r - 2 + g)) + (1 << (m + g - 1)) * b - 1 - (1 << (6 + g)) + (1 << (g - 1)) * b - 1 - } -} - #[self_referencing] pub struct Bpf { skel: T, @@ -82,25 +63,25 @@ impl Bpf { }) } - pub fn add_distribution(&mut self, name: &str, heatmap: &'static Heatmap) { + pub fn add_distribution(&mut self, name: &str, histogram: &'static RwLockHistogram) { self.with_mut(|this| { this.distributions - .push(Distribution::new(this.skel.map(name), heatmap)); + .push(Distribution::new(this.skel.map(name), histogram)); }) } - pub fn refresh_counters(&mut self, now: Instant, elapsed: f64) { + pub fn refresh_counters(&mut self, elapsed: f64) { self.with_mut(|this| { for counters in this.counters.iter_mut() { - counters.refresh(now, elapsed); + counters.refresh(elapsed); } }) } - pub fn refresh_distributions(&mut self, now: Instant) { + pub fn refresh_distributions(&mut self) { self.with_mut(|this| { for distribution in this.distributions.iter_mut() { - distribution.refresh(now); + distribution.refresh(); } }) } diff --git a/src/common/mod.rs b/src/common/mod.rs index cf94a984..2d5c5f92 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -5,43 +5,43 @@ pub mod classic; mod nop; -use metriken::Heatmap; +use metriken::AtomicHistogram; use metriken::LazyCounter; pub use nop::Nop; -type Instant = clocksource::Instant>; +pub const HISTOGRAM_GROUPING_POWER: u8 = 7; /// A `Counter` is a wrapper type that enables us to automatically calculate /// percentiles for secondly rates between subsequent counter observations. /// /// To do this, it contains the current reading, previous reading, and -/// optionally a heatmap to store rate observations. +/// optionally a histogram to store rate observations. pub struct Counter { previous: Option, counter: &'static LazyCounter, - heatmap: Option<&'static Heatmap>, + histogram: Option<&'static AtomicHistogram>, } impl Counter { /// Construct a new counter that wraps a `metriken` counter and optionally a - /// `metriken` heatmap. - pub fn new(counter: &'static LazyCounter, heatmap: Option<&'static Heatmap>) -> Self { + /// `metriken` histogram. + pub fn new(counter: &'static LazyCounter, histogram: Option<&'static AtomicHistogram>) -> Self { Self { previous: None, counter, - heatmap, + histogram, } } /// Updates the counter by setting the current value to a new value. If this - /// counter has a heatmap it also calculates the rate since the last reading - /// and increments the heatmap. - pub fn set(&mut self, now: Instant, elapsed: f64, value: u64) { + /// counter has a histogram it also calculates the rate since the last reading + /// and increments the histogram. + pub fn set(&mut self, elapsed: f64, value: u64) { if let Some(previous) = self.previous { let delta = value.wrapping_sub(previous); self.counter.add(delta); - if let Some(heatmap) = self.heatmap { - let _ = heatmap.increment(now, (delta as f64 / elapsed) as _); + if let Some(histogram) = self.histogram { + let _ = histogram.increment((delta as f64 / elapsed) as _); } } self.previous = Some(value); @@ -101,18 +101,18 @@ macro_rules! gauge { #[macro_export] #[rustfmt::skip] /// A convenience macro for constructing a lazily initialized -/// `metriken::Heatmap` given an identifier, name, and optional description. +/// `metriken::AtomicHistogram` given an identifier, name, and optional +/// description. /// -/// The heatmap configuration used here can record counts for all 64bit integer -/// values with a maximum error of 0.78%. The heatmap covers a moving window of -/// one minute with one second resolution. -macro_rules! heatmap { +/// The histogram configuration used here can record counts for all 64bit +/// integer values with a maximum error of 0.78%. +macro_rules! histogram { ($ident:ident, $name:tt) => { #[metriken::metric( name = $name, crate = metriken )] - pub static $ident: metriken::Heatmap = metriken::Heatmap::new(0, 8, 64, core::time::Duration::from_secs(1), core::time::Duration::from_millis(100)); + pub static $ident: metriken::AtomicHistogram = metriken::AtomicHistogram::new($crate::common::HISTOGRAM_GROUPING_POWER, 64); }; ($ident:ident, $name:tt, $description:tt) => { #[metriken::metric( @@ -120,37 +120,63 @@ macro_rules! heatmap { description = $description, crate = metriken )] - pub static $ident: metriken::Heatmap = metriken::Heatmap::new(0, 8, 64, core::time::Duration::from_secs(1), core::time::Duration::from_millis(100)); + pub static $ident: metriken::AtomicHistogram = metriken::AtomicHistogram::new($crate::common::HISTOGRAM_GROUPING_POWER, 64); + }; +} + +#[macro_export] +#[rustfmt::skip] +/// A convenience macro for constructing a lazily initialized +/// `metriken::RwLockHistogram` given an identifier, name, and optional +/// description. +/// +/// The histogram configuration used here can record counts for all 64bit +/// integer values with a maximum error of 0.78%. +macro_rules! bpfhistogram { + ($ident:ident, $name:tt) => { + #[metriken::metric( + name = $name, + crate = metriken + )] + pub static $ident: metriken::RwLockHistogram = metriken::RwLockHistogram::new($crate::common::HISTOGRAM_GROUPING_POWER, 64); + }; + ($ident:ident, $name:tt, $description:tt) => { + #[metriken::metric( + name = $name, + description = $description, + crate = metriken + )] + pub static $ident: metriken::RwLockHistogram = metriken::RwLockHistogram::new($crate::common::HISTOGRAM_GROUPING_POWER, 64); }; } #[macro_export] #[rustfmt::skip] /// A convenience macro for constructing a lazily initialized counter with a -/// heatmap which will track secondly rates for the same counter. -macro_rules! counter_with_heatmap { - ($counter:ident, $heatmap:ident, $name:tt) => { +/// histogram which will track secondly rates for the same counter. +macro_rules! counter_with_histogram { + ($counter:ident, $histogram:ident, $name:tt) => { self::counter!($counter, $name); - self::heatmap!($heatmap, $name); + self::histogram!($histogram, $name); }; - ($counter:ident, $heatmap:ident, $name:tt, $description:tt) => { + ($counter:ident, $histogram:ident, $name:tt, $description:tt) => { self::counter!($counter, $name, $description); - self::heatmap!($heatmap, $name, $description); + self::histogram!($histogram, $name, $description); } } #[macro_export] #[rustfmt::skip] /// A convenience macro for constructing a lazily initialized gauge with a -/// heatmap which will track instantaneous readings for the same gauge. -macro_rules! gauge_with_heatmap { - ($gauge:ident, $heatmap:ident, $name:tt) => { +/// histogram which will track instantaneous readings for the same gauge. +macro_rules! gauge_with_histogram { + ($gauge:ident, $histogram:ident, $name:tt) => { self::gauge!($gauge, $name); - self::heatmap!($heatmap, $name); + self::histogram!($histogram, $name); }; - ($gauge:ident, $heatmap:ident, $name:tt, $description:tt) => { + ($gauge:ident, $histogram:ident, $name:tt, $description:tt) => { self::gauge!($gauge, $name, $description); - self::heatmap!($heatmap, $name, $description); + self::histogram!($histogram, $name, $description); } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 3b8151ee..97f1eebb 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,3 +1,4 @@ +use crate::Duration; use serde::Deserialize; use std::collections::HashMap; use std::net::SocketAddr; @@ -8,6 +9,8 @@ use std::path::Path; // #[serde(deny_unknown_fields)] pub struct Config { general: General, + prometheus: Prometheus, + defaults: SamplerConfig, samplers: HashMap, } @@ -20,10 +23,26 @@ impl Config { }) .unwrap(); - toml::from_str(&content).map_err(|e| { - eprintln!("failed to parse config file: {e}"); - std::process::exit(1); - }) + let config: Config = toml::from_str(&content) + .map_err(|e| { + eprintln!("failed to parse config file: {e}"); + std::process::exit(1); + }) + .unwrap(); + + config.prometheus().check(); + + config.defaults.check("default"); + + for (name, config) in config.samplers.iter() { + config.check(name); + } + + Ok(config) + } + + pub fn defaults(&self) -> &SamplerConfig { + &self.defaults } pub fn sampler_config(&self, name: &str) -> Option<&SamplerConfig> { @@ -34,6 +53,10 @@ impl Config { &self.general } + pub fn prometheus(&self) -> &Prometheus { + &self.prometheus + } + #[cfg(feature = "bpf")] pub fn bpf(&self) -> bool { true @@ -43,7 +66,29 @@ impl Config { pub fn bpf(&self) -> bool { false } + + pub fn enabled(&self, name: &str) -> bool { + self.samplers + .get(name) + .map(|c| c.enabled()) + .unwrap_or(self.defaults.enabled()) + } + + pub fn interval(&self, name: &str) -> Duration { + self.samplers + .get(name) + .map(|c| c.interval()) + .unwrap_or(self.defaults.interval()) + } + + pub fn distribution_interval(&self, name: &str) -> Duration { + self.samplers + .get(name) + .map(|c| c.distribution_interval()) + .unwrap_or(self.defaults.distribution_interval()) + } } + #[derive(Deserialize)] pub struct General { listen: String, @@ -67,13 +112,108 @@ impl General { } } +#[derive(Deserialize)] +pub struct Prometheus { + #[serde(default = "disabled")] + histograms: bool, + #[serde(default = "four")] + histogram_grouping_power: u8, +} + +impl Prometheus { + pub fn check(&self) { + if !(2..=(crate::common::HISTOGRAM_GROUPING_POWER)).contains(&self.histogram_grouping_power) + { + eprintln!( + "prometheus histogram downsample factor must be in the range 2..={}", + crate::common::HISTOGRAM_GROUPING_POWER + ); + std::process::exit(1); + } + } + + pub fn histograms(&self) -> bool { + self.histograms + } + + pub fn histogram_grouping_power(&self) -> u8 { + self.histogram_grouping_power + } +} + +pub fn enabled() -> bool { + true +} + +pub fn disabled() -> bool { + false +} + +pub fn four() -> u8 { + 4 +} + +pub fn interval() -> String { + "10ms".into() +} + +pub fn distribution_interval() -> String { + "50ms".into() +} + #[derive(Deserialize)] pub struct SamplerConfig { + #[serde(default = "enabled")] enabled: bool, + #[serde(default = "interval")] + interval: String, + #[serde(default = "distribution_interval")] + distribution_interval: String, } impl SamplerConfig { + pub fn check(&self, name: &str) { + if let Err(e) = self.interval.parse::() { + eprintln!("{name} sampler interval is not valid: {e}"); + std::process::exit(1); + } + if self.interval() < Duration::from_millis(1) { + eprintln!("{name} sampler interval is too short. Minimum interval is: 1ms"); + std::process::exit(1); + } + + if let Err(e) = self.distribution_interval.parse::() { + eprintln!("{name} sampler distribution interval is not valid: {e}"); + std::process::exit(1); + } + + if self.distribution_interval() < Duration::from_millis(1) { + eprintln!( + "{name} sampler distribution interval is too short. Minimum interval is: 1ms" + ); + std::process::exit(1); + } + } + pub fn enabled(&self) -> bool { self.enabled } + + pub fn interval(&self) -> Duration { + Duration::from_nanos( + self.interval + .parse::() + .unwrap() + .as_nanos() as _, + ) + } + + pub fn distribution_interval(&self) -> Duration { + Duration::from_nanos( + self.distribution_interval + .parse::() + .unwrap() + .as_nanos() as _, + ) + } } diff --git a/src/exposition/http.rs b/src/exposition/http.rs index 2d626b74..82510dc4 100644 --- a/src/exposition/http.rs +++ b/src/exposition/http.rs @@ -1,11 +1,14 @@ +use crate::Arc; +use crate::Config; use crate::PERCENTILES; -use metriken::{Counter, Gauge, Heatmap}; +use metriken::histogram::Snapshot; +use metriken::{AtomicHistogram, Counter, Gauge, RwLockHistogram}; use warp::Filter; /// HTTP exposition -pub async fn http() { - let http = filters::http(); +pub async fn http(config: Arc) { + let http = filters::http(config); warp::serve(http).run(([0, 0, 0, 0], 4242)).await; } @@ -13,16 +16,28 @@ pub async fn http() { mod filters { use super::*; + fn with_config( + config: Arc, + ) -> impl Filter,), Error = std::convert::Infallible> + Clone { + warp::any().map(move || config.clone()) + } + /// The combined set of http endpoint filters - pub fn http() -> impl Filter + Clone { - prometheus_stats().or(human_stats()).or(hardware_info()) + pub fn http( + config: Arc, + ) -> impl Filter + Clone { + prometheus_stats(config.clone()) + .or(human_stats()) + .or(hardware_info()) } /// GET /metrics pub fn prometheus_stats( + config: Arc, ) -> impl Filter + Clone { warp::path!("metrics") .and(warp::get()) + .and(with_config(config)) .and_then(handlers::prometheus_stats) } @@ -45,11 +60,22 @@ mod filters { mod handlers { use super::*; + use crate::common::HISTOGRAM_GROUPING_POWER; + use crate::SNAPSHOTS; use core::convert::Infallible; + use std::time::UNIX_EPOCH; - pub async fn prometheus_stats() -> Result { + pub async fn prometheus_stats(config: Arc) -> Result { let mut data = Vec::new(); + let snapshots = SNAPSHOTS.read().await; + + let timestamp = snapshots + .timestamp + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(); + for metric in &metriken::metrics() { let any = match metric.as_any() { Some(any) => any, @@ -58,43 +84,92 @@ mod handlers { } }; - if metric.name().starts_with("log_") { + let name = metric.name(); + + if name.starts_with("log_") { continue; } - if let Some(counter) = any.downcast_ref::() { if metric.metadata().is_empty() { data.push(format!( - "# TYPE {}_total counter\n{}_total {}", - metric.name(), - metric.name(), + "# TYPE {name}_total counter\n{name}_total {}", counter.value() )); } else { data.push(format!( - "# TYPE {} counter\n{} {}", - metric.name(), + "# TYPE {name} counter\n{} {}", metric.formatted(metriken::Format::Prometheus), counter.value() )); } } else if let Some(gauge) = any.downcast_ref::() { data.push(format!( - "# TYPE {} gauge\n{} {}", - metric.name(), + "# TYPE {name} gauge\n{} {}", metric.formatted(metriken::Format::Prometheus), gauge.value() )); - } else if let Some(heatmap) = any.downcast_ref::() { - for (_label, percentile) in PERCENTILES { - if let Some(Ok(bucket)) = heatmap.percentile(*percentile) { - data.push(format!( - "# TYPE {} gauge\n{}{{percentile=\"{:02}\"}} {}", - metric.name(), - metric.name(), - percentile, - bucket.high() - )); + } else if any.downcast_ref::().is_some() + || any.downcast_ref::().is_some() + { + if let Some(delta) = snapshots.deltas.get(metric.name()) { + let percentiles: Vec = PERCENTILES.iter().map(|(_, p)| *p).collect(); + + if let Ok(result) = delta.percentiles(&percentiles) { + for (percentile, value) in result.iter().map(|(p, b)| (p, b.end())) { + data.push(format!( + "# TYPE {name} gauge\n{name}{{percentile=\"{:02}\"}} {value} {timestamp}", + percentile, + )); + } + } + } + if config.prometheus().histograms() { + if let Some(snapshot) = snapshots.previous.get(metric.name()) { + let current = HISTOGRAM_GROUPING_POWER; + let target = config.prometheus().histogram_grouping_power(); + + // downsample the snapshot if necessary + let downsampled: Option = if current == target { + // the powers matched, we don't need to downsample + None + } else { + Some(snapshot.downsample(target).unwrap()) + }; + + // reassign to either use the downsampled snapshot or the original + let snapshot = if let Some(snapshot) = downsampled.as_ref() { + snapshot + } else { + snapshot + }; + + // we need to export a total count (free-running) + let mut count = 0; + // we also need to export a total sum of all observations + // which is also free-running + let mut sum = 0; + + let mut entry = format!("# TYPE {name}_distribution histogram\n"); + for bucket in snapshot { + // add this bucket's sum of observations + sum += bucket.count() * bucket.end(); + + // add the count to the aggregate + count += bucket.count(); + + entry += &format!( + "{name}_distribution_bucket{{le=\"{}\"}} {count} {timestamp}\n", + bucket.end() + ); + } + + entry += &format!( + "{name}_distribution_bucket{{le=\"+Inf\"}} {count} {timestamp}\n" + ); + entry += &format!("{name}_distribution_count {count} {timestamp}\n"); + entry += &format!("{name}_distribution_sum {sum} {timestamp}\n"); + + data.push(entry); } } } @@ -111,6 +186,8 @@ mod handlers { pub async fn human_stats() -> Result { let mut data = Vec::new(); + let snapshots = SNAPSHOTS.read().await; + for metric in &metriken::metrics() { let any = match metric.as_any() { Some(any) => any, @@ -135,15 +212,25 @@ mod handlers { metric.formatted(metriken::Format::Simple), gauge.value() )); - } else if let Some(heatmap) = any.downcast_ref::() { - for (label, p) in PERCENTILES { - if let Some(Ok(bucket)) = heatmap.percentile(*p) { - data.push(format!( - "{}/{}: {}", - metric.formatted(metriken::Format::Simple), - label, - bucket.high() - )); + } else if any.downcast_ref::().is_some() + || any.downcast_ref::().is_some() + { + if let Some(delta) = snapshots.deltas.get(metric.name()) { + let percentiles: Vec = PERCENTILES.iter().map(|(_, p)| *p).collect(); + + if let Ok(result) = delta.percentiles(&percentiles) { + for (value, label) in result + .iter() + .map(|(_, b)| b.end()) + .zip(PERCENTILES.iter().map(|(l, _)| l)) + { + data.push(format!( + "{}/{}: {}", + metric.formatted(metriken::Format::Simple), + label, + value + )); + } } } } diff --git a/src/main.rs b/src/main.rs index f78aae90..3a7cd894 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,102 @@ use clap::{Arg, Command}; use linkme::distributed_slice; use metriken::Lazy; use ringlog::*; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::{Duration, Instant, SystemTime}; +use tokio::sync::RwLock; -type Duration = clocksource::Duration>; -type Instant = clocksource::Instant>; +type HistogramSnapshots = HashMap; + +static SNAPSHOTS: Lazy>> = + Lazy::new(|| Arc::new(RwLock::new(Snapshots::new()))); + +pub struct Snapshots { + timestamp: SystemTime, + previous: HistogramSnapshots, + deltas: HistogramSnapshots, +} + +impl Default for Snapshots { + fn default() -> Self { + Self::new() + } +} + +impl Snapshots { + pub fn new() -> Self { + let timestamp = SystemTime::now(); + + let mut current = HashMap::new(); + + for metric in metriken::metrics().iter() { + let any = if let Some(any) = metric.as_any() { + any + } else { + continue; + }; + + let key = metric.name().to_string(); + + let snapshot = if let Some(histogram) = any.downcast_ref::() + { + histogram.snapshot() + } else if let Some(histogram) = any.downcast_ref::() { + histogram.snapshot() + } else { + None + }; + + if let Some(snapshot) = snapshot { + current.insert(key, snapshot); + } + } + + let deltas = current.clone(); + + Self { + timestamp, + previous: current, + deltas, + } + } + + pub fn update(&mut self) { + self.timestamp = SystemTime::now(); + + let mut current = HashMap::new(); + + for metric in metriken::metrics().iter() { + let any = if let Some(any) = metric.as_any() { + any + } else { + continue; + }; + + let key = metric.name().to_string(); + + let snapshot = if let Some(histogram) = any.downcast_ref::() + { + histogram.snapshot() + } else if let Some(histogram) = any.downcast_ref::() { + histogram.snapshot() + } else { + None + }; + + if let Some(snapshot) = snapshot { + if let Some(previous) = self.previous.get(&key) { + self.deltas + .insert(key.clone(), snapshot.wrapping_sub(previous).unwrap()); + } + + current.insert(key, snapshot); + } + } + + self.previous = current; + } +} mod common; mod config; @@ -27,9 +120,6 @@ pub static PERCENTILES: &[(&str, f64)] = &[ #[distributed_slice] pub static SAMPLERS: [fn(config: &Config) -> Box] = [..]; -// #[distributed_slice] -// pub static BPF_SAMPLERS: [fn(config: &Config) -> Box] = [..]; - counter!(RUNTIME_SAMPLE_LOOP, "runtime/sample/loop"); fn main() { @@ -54,11 +144,11 @@ fn main() { .get_matches(); // load config from file - let config = { + let config: Arc = { let file = matches.get_one::("CONFIG").unwrap(); debug!("loading config: {}", file); match Config::load(file) { - Ok(c) => c, + Ok(c) => c.into(), Err(error) => { eprintln!("error loading config file: {file}\n{error}"); std::process::exit(1); @@ -68,19 +158,6 @@ fn main() { // configure debug log let debug_output: Box = Box::new(Stderr::new()); - // let debug_output: Box = if let Some(file) = config.debug().log_file() { - // let backup = config - // .debug() - // .log_backup() - // .unwrap_or(format!("{}.old", file)); - // Box::new( - // File::new(&file, &backup, config.debug().log_max_size()) - // .expect("failed to open debug log file"), - // ) - // } else { - // // by default, log to stderr - // Box::new(Stderr::new()) - // }; let level = Level::Info; @@ -90,8 +167,6 @@ fn main() { LogBuilder::new() } .output(debug_output) - // .log_queue_depth(config.debug().log_queue_depth()) - // .single_message_size(config.debug().log_single_message_size()) .build() .expect("failed to initialize debug log"); @@ -116,10 +191,24 @@ fn main() { } }); + // spawn thread to maintain histogram snapshots + rt.spawn(async { + loop { + // acquire a lock and update the snapshots + { + let mut snapshots = SNAPSHOTS.write().await; + snapshots.update(); + } + + // delay until next update + tokio::time::sleep(core::time::Duration::from_secs(1)).await; + } + }); + info!("rezolus"); // spawn http exposition thread - rt.spawn(exposition::http()); + rt.spawn(exposition::http(config.clone())); // initialize and gather the samplers let mut samplers: Vec> = Vec::new(); @@ -128,12 +217,6 @@ fn main() { samplers.push(sampler(&config)); } - // let mut bpf_samplers: Vec> = Vec::new(); - - // for sampler in BPF_SAMPLERS { - // bpf_samplers.push(sampler(&config)); - // } - info!("initialization complete"); // main loop @@ -148,23 +231,14 @@ fn main() { sampler.sample(); } - // calculate how long we took during this iteration - let stop = Instant::now(); - let elapsed = (stop - start).as_nanos(); - - // calculate how long to sleep and sleep before next iteration - // this wakeup period allows a maximum of 1kHz sampling - let sleep = 1_000_000_u64.saturating_sub(elapsed); - std::thread::sleep(std::time::Duration::from_nanos(sleep)); + // Sleep for the remainder of one millisecond minus the sampling time. + // This wakeup period allows a maximum of 1kHz sampling + let delay = Duration::from_millis(1).saturating_sub(start.elapsed()); + std::thread::sleep(delay); } } pub trait Sampler { - // #[allow(clippy::result_unit_err)] - // fn configure(&self, config: &Config) -> Result<(), ()>; - /// Do some sampling and updating of stats fn sample(&mut self); - - // fn name(&self) -> &str; } diff --git a/src/samplers/block_io/latency/mod.rs b/src/samplers/block_io/latency/mod.rs index 2dfef558..998f45e7 100644 --- a/src/samplers/block_io/latency/mod.rs +++ b/src/samplers/block_io/latency/mod.rs @@ -11,6 +11,8 @@ mod bpf { include!(concat!(env!("OUT_DIR"), "/block_io_latency.bpf.rs")); } +static NAME: &str = "block_io_latency"; + use bpf::*; use super::stats::*; @@ -43,7 +45,12 @@ pub struct Biolat { } impl Biolat { - pub fn new(_config: &Config) -> Result { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let builder = ModSkelBuilder::default(); let mut skel = builder .open() @@ -58,16 +65,16 @@ impl Biolat { let mut distributions = vec![("latency", &BLOCKIO_LATENCY), ("size", &BLOCKIO_SIZE)]; - for (name, heatmap) in distributions.drain(..) { - bpf.add_distribution(name, heatmap); + for (name, histogram) in distributions.drain(..) { + bpf.add_distribution(name, histogram); } Ok(Self { bpf, - counter_interval: Duration::from_millis(10), + counter_interval: config.interval(NAME), counter_next: Instant::now(), counter_prev: Instant::now(), - distribution_interval: Duration::from_millis(50), + distribution_interval: config.distribution_interval(NAME), distribution_next: Instant::now(), distribution_prev: Instant::now(), }) @@ -80,7 +87,7 @@ impl Biolat { let elapsed = (now - self.counter_prev).as_secs_f64(); - self.bpf.refresh_counters(now, elapsed); + self.bpf.refresh_counters(elapsed); // determine when to sample next let next = self.counter_next + self.counter_interval; @@ -101,7 +108,7 @@ impl Biolat { return; } - self.bpf.refresh_distributions(now); + self.bpf.refresh_distributions(); // determine when to sample next let next = self.distribution_next + self.distribution_interval; diff --git a/src/samplers/block_io/stats.rs b/src/samplers/block_io/stats.rs index e8f529c7..f04277d8 100644 --- a/src/samplers/block_io/stats.rs +++ b/src/samplers/block_io/stats.rs @@ -1,11 +1,11 @@ use crate::*; -heatmap!( +bpfhistogram!( BLOCKIO_LATENCY, "blockio/latency", "distribution of block IO latencies" ); -heatmap!( +bpfhistogram!( BLOCKIO_SIZE, "blockio/size", "distribution of block IO sizes" diff --git a/src/samplers/cpu/perf/mod.rs b/src/samplers/cpu/perf/mod.rs index 7a18f6a5..f2b30c07 100644 --- a/src/samplers/cpu/perf/mod.rs +++ b/src/samplers/cpu/perf/mod.rs @@ -28,6 +28,8 @@ fn init(config: &Config) -> Box { } } +const NAME: &str = "cpu_perf"; + pub struct Perf { prev: Instant, next: Instant, @@ -37,7 +39,12 @@ pub struct Perf { } impl Perf { - pub fn new(_config: &Config) -> Result { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let now = Instant::now(); let cpus = match hardware_info() { @@ -90,7 +97,7 @@ impl Perf { return Ok(Self { prev: now, next: now, - interval: Duration::from_millis(10), + interval: config.interval(NAME), groups, counters, }); @@ -122,9 +129,9 @@ impl Sampler for Perf { avg_ipus += reading.ipus; avg_base_frequency += reading.base_frequency_mhz; avg_running_frequency += reading.running_frequency_mhz; - let _ = CPU_IPKC_HEATMAP.increment(now, reading.ipkc); - let _ = CPU_IPUS_HEATMAP.increment(now, reading.ipus); - let _ = CPU_FREQUENCY_HEATMAP.increment(now, reading.running_frequency_mhz); + let _ = CPU_IPKC_HISTOGRAM.increment(reading.ipkc); + let _ = CPU_IPUS_HISTOGRAM.increment(reading.ipus); + let _ = CPU_FREQUENCY_HISTOGRAM.increment(reading.running_frequency_mhz); self.counters[reading.id][0].set(reading.cycles); self.counters[reading.id][1].set(reading.instructions); diff --git a/src/samplers/cpu/perf/proc_cpuinfo.rs b/src/samplers/cpu/perf/proc_cpuinfo.rs index 9c1ae2b7..41c9e0ce 100644 --- a/src/samplers/cpu/perf/proc_cpuinfo.rs +++ b/src/samplers/cpu/perf/proc_cpuinfo.rs @@ -33,7 +33,7 @@ impl Sampler for ProcCpuinfo { return; } - if self.sample_proc_cpuinfo(now).is_err() { + if self.sample_proc_cpuinfo().is_err() { return; } @@ -55,7 +55,7 @@ impl Sampler for ProcCpuinfo { } impl ProcCpuinfo { - fn sample_proc_cpuinfo(&mut self, now: Instant) -> Result<(), std::io::Error> { + fn sample_proc_cpuinfo(&mut self) -> Result<(), std::io::Error> { self.file.rewind()?; let mut data = String::new(); @@ -79,7 +79,7 @@ impl ProcCpuinfo { .get(3) .map(|v| v.parse::().map(|v| v.floor() as u64)) { - let _ = CPU_FREQUENCY_HEATMAP.increment(now, freq); + let _ = CPU_FREQUENCY_HISTOGRAM.increment(freq); frequency += freq; } } diff --git a/src/samplers/cpu/proc_stat/mod.rs b/src/samplers/cpu/proc_stat/mod.rs index 7f91f3c8..d9e49b3e 100644 --- a/src/samplers/cpu/proc_stat/mod.rs +++ b/src/samplers/cpu/proc_stat/mod.rs @@ -17,6 +17,8 @@ fn init(config: &Config) -> Box { } } +const NAME: &str = "cpu_proc_stat"; + pub struct ProcStat { prev: Instant, next: Instant, @@ -28,7 +30,12 @@ pub struct ProcStat { } impl ProcStat { - pub fn new(_config: &Config) -> Result { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let now = Instant::now(); let cpus = match hardware_info() { @@ -37,16 +44,16 @@ impl ProcStat { }; let counters_total = vec![ - Counter::new(&CPU_USAGE_USER, Some(&CPU_USAGE_USER_HEATMAP)), - Counter::new(&CPU_USAGE_NICE, Some(&CPU_USAGE_NICE_HEATMAP)), - Counter::new(&CPU_USAGE_SYSTEM, Some(&CPU_USAGE_SYSTEM_HEATMAP)), - Counter::new(&CPU_USAGE_IDLE, Some(&CPU_USAGE_IDLE_HEATMAP)), - Counter::new(&CPU_USAGE_IO_WAIT, Some(&CPU_USAGE_IO_WAIT_HEATMAP)), - Counter::new(&CPU_USAGE_IRQ, Some(&CPU_USAGE_IRQ_HEATMAP)), - Counter::new(&CPU_USAGE_SOFTIRQ, Some(&CPU_USAGE_SOFTIRQ_HEATMAP)), - Counter::new(&CPU_USAGE_STEAL, Some(&CPU_USAGE_STEAL_HEATMAP)), - Counter::new(&CPU_USAGE_GUEST, Some(&CPU_USAGE_GUEST_HEATMAP)), - Counter::new(&CPU_USAGE_GUEST_NICE, Some(&CPU_USAGE_GUEST_NICE_HEATMAP)), + Counter::new(&CPU_USAGE_USER, Some(&CPU_USAGE_USER_HISTOGRAM)), + Counter::new(&CPU_USAGE_NICE, Some(&CPU_USAGE_NICE_HISTOGRAM)), + Counter::new(&CPU_USAGE_SYSTEM, Some(&CPU_USAGE_SYSTEM_HISTOGRAM)), + Counter::new(&CPU_USAGE_IDLE, Some(&CPU_USAGE_IDLE_HISTOGRAM)), + Counter::new(&CPU_USAGE_IO_WAIT, Some(&CPU_USAGE_IO_WAIT_HISTOGRAM)), + Counter::new(&CPU_USAGE_IRQ, Some(&CPU_USAGE_IRQ_HISTOGRAM)), + Counter::new(&CPU_USAGE_SOFTIRQ, Some(&CPU_USAGE_SOFTIRQ_HISTOGRAM)), + Counter::new(&CPU_USAGE_STEAL, Some(&CPU_USAGE_STEAL_HISTOGRAM)), + Counter::new(&CPU_USAGE_GUEST, Some(&CPU_USAGE_GUEST_HISTOGRAM)), + Counter::new(&CPU_USAGE_GUEST_NICE, Some(&CPU_USAGE_GUEST_NICE_HISTOGRAM)), ]; let mut counters_percpu = Vec::with_capacity(cpus.len()); @@ -96,7 +103,7 @@ impl ProcStat { nanos_per_tick, prev: now, next: now, - interval: Duration::from_millis(10), + interval: config.interval(NAME), }) } } @@ -111,7 +118,7 @@ impl Sampler for ProcStat { let elapsed = (now - self.prev).as_secs_f64(); - if self.sample_proc_stat(now, elapsed).is_err() { + if self.sample_proc_stat(elapsed).is_err() { return; } @@ -133,7 +140,7 @@ impl Sampler for ProcStat { } impl ProcStat { - fn sample_proc_stat(&mut self, now: Instant, elapsed: f64) -> Result<(), std::io::Error> { + fn sample_proc_stat(&mut self, elapsed: f64) -> Result<(), std::io::Error> { self.file.rewind()?; let mut data = String::new(); @@ -153,7 +160,7 @@ impl ProcStat { if *header == "cpu" { for (field, counter) in self.counters_total.iter_mut().enumerate() { if let Some(Ok(v)) = parts.get(field + 1).map(|v| v.parse::()) { - counter.set(now, elapsed, v.wrapping_mul(self.nanos_per_tick)) + counter.set(elapsed, v.wrapping_mul(self.nanos_per_tick)) } } } else if header.starts_with("cpu") { diff --git a/src/samplers/cpu/stats.rs b/src/samplers/cpu/stats.rs index 5f935207..432f2186 100644 --- a/src/samplers/cpu/stats.rs +++ b/src/samplers/cpu/stats.rs @@ -15,7 +15,7 @@ pub static CPU_CORES: LazyGauge = LazyGauge::new(Gauge::default); )] pub static CPU_USAGE_USER: LazyCounter = LazyCounter::new(Counter::default); -heatmap!(CPU_USAGE_USER_HEATMAP, "cpu/usage/user"); +histogram!(CPU_USAGE_USER_HISTOGRAM, "cpu/usage/user"); #[metric( name = "cpu/usage", @@ -25,7 +25,7 @@ heatmap!(CPU_USAGE_USER_HEATMAP, "cpu/usage/user"); )] pub static CPU_USAGE_NICE: LazyCounter = LazyCounter::new(Counter::default); -heatmap!(CPU_USAGE_NICE_HEATMAP, "cpu/usage/nice"); +histogram!(CPU_USAGE_NICE_HISTOGRAM, "cpu/usage/nice"); #[metric( name = "cpu/usage", @@ -35,7 +35,7 @@ heatmap!(CPU_USAGE_NICE_HEATMAP, "cpu/usage/nice"); )] pub static CPU_USAGE_SYSTEM: LazyCounter = LazyCounter::new(Counter::default); -heatmap!(CPU_USAGE_SYSTEM_HEATMAP, "cpu/usage/system"); +histogram!(CPU_USAGE_SYSTEM_HISTOGRAM, "cpu/usage/system"); #[metric( name = "cpu/usage", @@ -45,7 +45,7 @@ heatmap!(CPU_USAGE_SYSTEM_HEATMAP, "cpu/usage/system"); )] pub static CPU_USAGE_IDLE: LazyCounter = LazyCounter::new(Counter::default); -heatmap!(CPU_USAGE_IDLE_HEATMAP, "cpu/usage/idle"); +histogram!(CPU_USAGE_IDLE_HISTOGRAM, "cpu/usage/idle"); #[metric( name = "cpu/usage", @@ -55,7 +55,7 @@ heatmap!(CPU_USAGE_IDLE_HEATMAP, "cpu/usage/idle"); )] pub static CPU_USAGE_IO_WAIT: LazyCounter = LazyCounter::new(Counter::default); -heatmap!(CPU_USAGE_IO_WAIT_HEATMAP, "cpu/usage/io_wait"); +histogram!(CPU_USAGE_IO_WAIT_HISTOGRAM, "cpu/usage/io_wait"); #[metric( name = "cpu/usage", @@ -65,7 +65,7 @@ heatmap!(CPU_USAGE_IO_WAIT_HEATMAP, "cpu/usage/io_wait"); )] pub static CPU_USAGE_IRQ: LazyCounter = LazyCounter::new(Counter::default); -heatmap!(CPU_USAGE_IRQ_HEATMAP, "cpu/usage/irq"); +histogram!(CPU_USAGE_IRQ_HISTOGRAM, "cpu/usage/irq"); #[metric( name = "cpu/usage", @@ -75,7 +75,7 @@ heatmap!(CPU_USAGE_IRQ_HEATMAP, "cpu/usage/irq"); )] pub static CPU_USAGE_SOFTIRQ: LazyCounter = LazyCounter::new(Counter::default); -heatmap!(CPU_USAGE_SOFTIRQ_HEATMAP, "cpu/usage/softirq"); +histogram!(CPU_USAGE_SOFTIRQ_HISTOGRAM, "cpu/usage/softirq"); #[metric( name = "cpu/usage", @@ -85,7 +85,7 @@ heatmap!(CPU_USAGE_SOFTIRQ_HEATMAP, "cpu/usage/softirq"); )] pub static CPU_USAGE_STEAL: LazyCounter = LazyCounter::new(Counter::default); -heatmap!(CPU_USAGE_STEAL_HEATMAP, "cpu/usage/steal"); +histogram!(CPU_USAGE_STEAL_HISTOGRAM, "cpu/usage/steal"); #[metric( name = "cpu/usage", @@ -95,7 +95,7 @@ heatmap!(CPU_USAGE_STEAL_HEATMAP, "cpu/usage/steal"); )] pub static CPU_USAGE_GUEST: LazyCounter = LazyCounter::new(Counter::default); -heatmap!(CPU_USAGE_GUEST_HEATMAP, "cpu/usage/guest"); +histogram!(CPU_USAGE_GUEST_HISTOGRAM, "cpu/usage/guest"); #[metric( name = "cpu/usage", @@ -105,7 +105,7 @@ heatmap!(CPU_USAGE_GUEST_HEATMAP, "cpu/usage/guest"); )] pub static CPU_USAGE_GUEST_NICE: LazyCounter = LazyCounter::new(Counter::default); -heatmap!(CPU_USAGE_GUEST_NICE_HEATMAP, "cpu/usage/guest_nice"); +histogram!(CPU_USAGE_GUEST_NICE_HISTOGRAM, "cpu/usage/guest_nice"); #[metric( name = "cpu/cycles", @@ -133,8 +133,8 @@ pub static CPU_PERF_GROUPS_ACTIVE: LazyGauge = LazyGauge::new(Gauge::default); )] pub static CPU_IPKC_AVERAGE: LazyGauge = LazyGauge::new(Gauge::default); -heatmap!( - CPU_IPKC_HEATMAP, +histogram!( + CPU_IPKC_HISTOGRAM, "cpu/ipkc", "distribution of per-CPU IPKC (Instructions Per Thousand Cycles)" ); @@ -145,8 +145,8 @@ heatmap!( )] pub static CPU_IPUS_AVERAGE: LazyGauge = LazyGauge::new(Gauge::default); -heatmap!( - CPU_IPUS_HEATMAP, +histogram!( + CPU_IPUS_HISTOGRAM, "cpu/ipus", "Distribution of per-CPU IPUS (Instructions Per Microsecond)" ); @@ -163,8 +163,8 @@ pub static CPU_BASE_FREQUENCY_AVERAGE: LazyGauge = LazyGauge::new(Gauge::default )] pub static CPU_FREQUENCY_AVERAGE: LazyGauge = LazyGauge::new(Gauge::default); -heatmap!( - CPU_FREQUENCY_HEATMAP, +histogram!( + CPU_FREQUENCY_HISTOGRAM, "cpu/frequency", "Distribution of the per-CPU running frequencies" ); diff --git a/src/samplers/gpu/nvidia/mod.rs b/src/samplers/gpu/nvidia/mod.rs index dca9f38e..2ba70aec 100644 --- a/src/samplers/gpu/nvidia/mod.rs +++ b/src/samplers/gpu/nvidia/mod.rs @@ -18,6 +18,8 @@ fn init(config: &Config) -> Box { } } +const NAME: &str = "gpu_nvidia"; + pub struct Nvidia { prev: Instant, next: Instant, @@ -52,7 +54,12 @@ struct GpuMetrics { } impl Nvidia { - pub fn new(_config: &Config) -> Result { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let now = Instant::now(); let nvml = Nvml::init().map_err(|e| { error!("error initializing: {e}"); @@ -125,7 +132,7 @@ impl Nvidia { nvml, prev: now, next: now, - interval: Duration::from_millis(50), + interval: config.interval(NAME), pergpu_metrics, }) } diff --git a/src/samplers/hwinfo/memory.rs b/src/samplers/hwinfo/memory.rs deleted file mode 100644 index 63e9296c..00000000 --- a/src/samplers/hwinfo/memory.rs +++ /dev/null @@ -1,48 +0,0 @@ -pub use super::*; - -#[derive(Serialize)] -pub struct Memory { - total_bytes: u64, -} - -impl Memory { - pub fn new() -> Result { - let file = File::open("/proc/meminfo")?; - let reader = BufReader::new(file); - - let mut ret = Self { total_bytes: 0 }; - - for line in reader.lines() { - let line = line.unwrap(); - if line.starts_with("MemTotal:") { - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() == 3 { - ret.total_bytes = parts[1].parse::().map(|v| v * 1024).map_err(|_| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "bad value") - })?; - } - } - } - - Ok(ret) - } - - pub fn node(id: usize) -> Result { - let file = File::open(format!("/sys/devices/system/node/node{id}/meminfo"))?; - let reader = BufReader::new(file); - - let mut ret = Self { total_bytes: 0 }; - - for line in reader.lines() { - let line = line.unwrap(); - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() >= 4 && parts[2] == "MemTotal:" { - ret.total_bytes = parts[3].parse::().map(|v| v * 1024).map_err(|_| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "bad value") - })?; - } - } - - Ok(ret) - } -} diff --git a/src/samplers/hwinfo/mod.rs b/src/samplers/hwinfo/mod.rs index 1df44cdb..e984b9af 100644 --- a/src/samplers/hwinfo/mod.rs +++ b/src/samplers/hwinfo/mod.rs @@ -1,121 +1,13 @@ -use lazy_static::lazy_static; -use serde::Serialize; -use std::collections::HashMap; -use std::ffi::OsStr; -use std::fs::File; -use std::io::BufRead; -use std::io::BufReader; -use std::io::Result; -use std::path::Path; -use walkdir::DirEntry; -use walkdir::WalkDir; - -mod cache; -mod cpu; -mod memory; -mod net; -mod node; +use std::io; -use cache::*; -use cpu::*; -use memory::*; -use net::*; -use node::*; +use lazy_static::lazy_static; +use systeminfo::hwinfo::HwInfo; lazy_static! { - pub static ref HWINFO: Result = Hwinfo::new(); + pub static ref HWINFO: io::Result = HwInfo::new() // + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)); } -pub fn hardware_info() -> std::result::Result<&'static Hwinfo, &'static std::io::Error> { +pub fn hardware_info() -> std::result::Result<&'static HwInfo, &'static std::io::Error> { HWINFO.as_ref() } - -#[derive(Serialize)] -pub struct Hwinfo { - caches: Vec>, - cpus: Vec, - memory: Memory, - network: Vec, - nodes: Vec, -} - -impl Hwinfo { - fn new() -> Result { - Ok(Self { - caches: get_caches()?, - cpus: get_cpus()?, - memory: Memory::new()?, - network: get_interfaces(), - nodes: get_nodes()?, - }) - } - - pub fn get_cpus(&self) -> &Vec { - return &self.cpus; - } -} - -fn read_usize(path: impl AsRef) -> Result { - let raw = std::fs::read_to_string(path)?; - let raw = raw.trim(); - - raw.parse() - .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "not a number")) -} - -fn read_string(path: impl AsRef) -> Result { - let raw = std::fs::read_to_string(path)?; - let raw = raw.trim(); - - Ok(raw.to_string()) -} - -fn read_list(path: impl AsRef) -> Result> { - let raw = std::fs::read_to_string(path)?; - parse_list(raw) -} - -fn parse_list(raw: String) -> Result> { - let raw = raw.trim(); - let mut ret = Vec::new(); - let ranges: Vec<&str> = raw.split(',').collect(); - for range in ranges { - let parts: Vec<&str> = range.split('-').collect(); - if parts.len() == 1 { - ret.push(parts[0].parse().map_err(|_| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "not a number") - })?); - } else if parts.len() == 2 { - let start: usize = parts[0].parse().map_err(|_| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "not a number") - })?; - let stop: usize = parts[1].parse().map_err(|_| { - std::io::Error::new(std::io::ErrorKind::InvalidData, "not a number") - })?; - for i in start..=stop { - ret.push(i); - } - } - } - - Ok(ret) -} - -fn is_hidden(entry: &DirEntry) -> bool { - entry - .file_name() - .to_str() - .map(|s| s.starts_with('.')) - .unwrap_or(false) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn list_parsing() { - let list = "0-1\r\n"; - assert_eq!(parse_list(list.to_string()).unwrap(), vec![0, 1]); - } -} diff --git a/src/samplers/memory/mod.rs b/src/samplers/memory/mod.rs index 4a20e1f6..caf02d3a 100644 --- a/src/samplers/memory/mod.rs +++ b/src/samplers/memory/mod.rs @@ -4,5 +4,7 @@ sampler!(Memory, "memory", MEMORY_SAMPLERS); mod stats; +#[cfg(target_os = "linux")] mod proc_meminfo; +#[cfg(target_os = "linux")] mod proc_vmstat; diff --git a/src/samplers/memory/proc_meminfo/mod.rs b/src/samplers/memory/proc_meminfo/mod.rs index 757873ba..dec3334b 100644 --- a/src/samplers/memory/proc_meminfo/mod.rs +++ b/src/samplers/memory/proc_meminfo/mod.rs @@ -1,16 +1,22 @@ use super::stats::*; use super::*; +use crate::common::Nop; use metriken::LazyGauge; use std::collections::HashMap; use std::fs::File; use std::io::{Read, Seek}; -#[cfg(target_os = "linux")] #[distributed_slice(MEMORY_SAMPLERS)] fn init(config: &Config) -> Box { - Box::new(ProcMeminfo::new(config)) + if let Ok(s) = ProcMeminfo::new(config) { + Box::new(s) + } else { + Box::new(Nop {}) + } } +const NAME: &str = "memory_meminfo"; + pub struct ProcMeminfo { prev: Instant, next: Instant, @@ -21,7 +27,12 @@ pub struct ProcMeminfo { impl ProcMeminfo { #![allow(dead_code)] - pub fn new(_config: &Config) -> Self { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let now = Instant::now(); let gauges = HashMap::from([ @@ -32,13 +43,13 @@ impl ProcMeminfo { ("Cached:", &MEMORY_CACHED), ]); - Self { + Ok(Self { file: File::open("/proc/meminfo").expect("file not found"), gauges, prev: now, next: now, - interval: Duration::from_millis(10), - } + interval: config.interval(NAME), + }) } } diff --git a/src/samplers/memory/proc_vmstat/mod.rs b/src/samplers/memory/proc_vmstat/mod.rs index 6c9b6726..9b3fe4d1 100644 --- a/src/samplers/memory/proc_vmstat/mod.rs +++ b/src/samplers/memory/proc_vmstat/mod.rs @@ -1,18 +1,21 @@ -// use crate::common::Noop; use super::stats::*; use super::*; -use crate::common::Counter; +use crate::common::{Counter, Nop}; use std::collections::HashMap; use std::fs::File; -use std::io::Read; -use std::io::Seek; +use std::io::{Read, Seek}; -#[cfg(target_os = "linux")] #[distributed_slice(MEMORY_SAMPLERS)] fn init(config: &Config) -> Box { - Box::new(ProcVmstat::new(config)) + if let Ok(s) = ProcVmstat::new(config) { + Box::new(s) + } else { + Box::new(Nop {}) + } } +const NAME: &str = "memory_vmstat"; + pub struct ProcVmstat { prev: Instant, next: Instant, @@ -23,7 +26,12 @@ pub struct ProcVmstat { impl ProcVmstat { #[allow(dead_code)] - pub fn new(_config: &Config) -> Self { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let now = Instant::now(); let counters = HashMap::from([ @@ -38,13 +46,13 @@ impl ProcVmstat { ("numa_other", Counter::new(&MEMORY_NUMA_OTHER, None)), ]); - Self { + Ok(Self { file: File::open("/proc/vmstat").expect("file not found"), counters, prev: now, next: now, - interval: Duration::from_millis(10), - } + interval: config.interval(NAME), + }) } } @@ -58,7 +66,7 @@ impl Sampler for ProcVmstat { let elapsed = (now - self.prev).as_secs_f64(); - if self.sample_proc_vmstat(now, elapsed).is_err() { + if self.sample_proc_vmstat(elapsed).is_err() { return; } @@ -80,7 +88,7 @@ impl Sampler for ProcVmstat { } impl ProcVmstat { - fn sample_proc_vmstat(&mut self, now: Instant, elapsed: f64) -> Result<(), std::io::Error> { + fn sample_proc_vmstat(&mut self, elapsed: f64) -> Result<(), std::io::Error> { self.file.rewind()?; let mut data = String::new(); @@ -97,7 +105,7 @@ impl ProcVmstat { if let Some(counter) = self.counters.get_mut(*parts.first().unwrap()) { if let Some(Ok(v)) = parts.get(1).map(|v| v.parse::()) { - counter.set(now, elapsed, v); + counter.set(elapsed, v); } } } diff --git a/src/samplers/mod.rs b/src/samplers/mod.rs index f4cbe50f..a0822966 100644 --- a/src/samplers/mod.rs +++ b/src/samplers/mod.rs @@ -3,6 +3,7 @@ mod cpu; mod gpu; pub mod hwinfo; mod memory; +mod rezolus; mod scheduler; mod syscall; mod tcp; diff --git a/src/samplers/network/mod.rs b/src/samplers/network/mod.rs deleted file mode 100644 index 964f3571..00000000 --- a/src/samplers/network/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::*; - -sampler!(Tcp, "tcp", TCP_SAMPLERS); - -mod stats; - -mod snmp; - -#[cfg(all(feature = "bpf", target_os = "linux"))] -mod receive; - -#[cfg(all(feature = "bpf", target_os = "linux"))] -mod retransmit; - -#[cfg(all(feature = "bpf", target_os = "linux"))] -mod traffic; diff --git a/src/samplers/rezolus/mod.rs b/src/samplers/rezolus/mod.rs new file mode 100644 index 00000000..e49a8655 --- /dev/null +++ b/src/samplers/rezolus/mod.rs @@ -0,0 +1,7 @@ +use crate::*; + +sampler!(Rezolus, "rezolus", REZOLUS_SAMPLERS); + +mod stats; + +mod rusage; diff --git a/src/samplers/rezolus/rusage/mod.rs b/src/samplers/rezolus/rusage/mod.rs new file mode 100644 index 00000000..630a5a26 --- /dev/null +++ b/src/samplers/rezolus/rusage/mod.rs @@ -0,0 +1,129 @@ +use super::stats::*; +use super::*; +use crate::common::Counter; +use crate::common::Nop; + +const S: u64 = 1_000_000_000; +const US: u64 = 1_000; +const KB: i64 = 1024; + +#[distributed_slice(REZOLUS_SAMPLERS)] +fn init(config: &Config) -> Box { + if let Ok(rusage) = Rusage::new(config) { + Box::new(rusage) + } else { + Box::new(Nop {}) + } +} + +const NAME: &str = "rezolus_rusage"; + +pub struct Rusage { + prev: Instant, + next: Instant, + interval: Duration, + ru_utime: Counter, + ru_stime: Counter, +} + +impl Rusage { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + + let now = Instant::now(); + + Ok(Self { + prev: now, + next: now, + interval: config.interval(NAME), + ru_utime: Counter::new(&RU_UTIME, Some(&RU_UTIME_HISTOGRAM)), + ru_stime: Counter::new(&RU_STIME, Some(&RU_STIME_HISTOGRAM)), + }) + } +} + +impl Sampler for Rusage { + fn sample(&mut self) { + let now = Instant::now(); + + if now < self.next { + return; + } + + let elapsed = (now - self.prev).as_secs_f64(); + + self.sample_rusage(elapsed); + + // determine when to sample next + let next = self.next + self.interval; + + // it's possible we fell behind + if next > now { + // if we didn't, sample at the next planned time + self.next = next; + } else { + // if we did, sample after the interval has elapsed + self.next = now + self.interval; + } + + // mark when we last sampled + self.prev = now; + } +} + +impl Rusage { + fn sample_rusage(&mut self, elapsed: f64) { + let mut rusage = libc::rusage { + ru_utime: libc::timeval { + tv_sec: 0, + tv_usec: 0, + }, + ru_stime: libc::timeval { + tv_sec: 0, + tv_usec: 0, + }, + ru_maxrss: 0, + ru_ixrss: 0, + ru_idrss: 0, + ru_isrss: 0, + ru_minflt: 0, + ru_majflt: 0, + ru_nswap: 0, + ru_inblock: 0, + ru_oublock: 0, + ru_msgsnd: 0, + ru_msgrcv: 0, + ru_nsignals: 0, + ru_nvcsw: 0, + ru_nivcsw: 0, + }; + + if unsafe { libc::getrusage(libc::RUSAGE_SELF, &mut rusage) } == 0 { + self.ru_utime.set( + elapsed, + rusage.ru_utime.tv_sec as u64 * S + rusage.ru_utime.tv_usec as u64 * US, + ); + self.ru_stime.set( + elapsed, + rusage.ru_stime.tv_sec as u64 * S + rusage.ru_stime.tv_usec as u64 * US, + ); + RU_MAXRSS.set(rusage.ru_maxrss * KB); + RU_IXRSS.set(rusage.ru_ixrss * KB); + RU_IDRSS.set(rusage.ru_idrss * KB); + RU_ISRSS.set(rusage.ru_isrss * KB); + RU_MINFLT.set(rusage.ru_minflt as u64); + RU_MAJFLT.set(rusage.ru_majflt as u64); + RU_NSWAP.set(rusage.ru_nswap as u64); + RU_INBLOCK.set(rusage.ru_inblock as u64); + RU_OUBLOCK.set(rusage.ru_oublock as u64); + RU_MSGSND.set(rusage.ru_msgsnd as u64); + RU_MSGRCV.set(rusage.ru_msgrcv as u64); + RU_NSIGNALS.set(rusage.ru_nsignals as u64); + RU_NVCSW.set(rusage.ru_nvcsw as u64); + RU_NIVCSW.set(rusage.ru_nivcsw as u64); + } + } +} diff --git a/src/samplers/rezolus/stats.rs b/src/samplers/rezolus/stats.rs new file mode 100644 index 00000000..e8076b14 --- /dev/null +++ b/src/samplers/rezolus/stats.rs @@ -0,0 +1,37 @@ +use crate::*; + +counter_with_histogram!( + RU_UTIME, + RU_UTIME_HISTOGRAM, + "rezolus/cpu/usage/user", + "The amount of CPU time Rezolus was executing in user mode" +); +counter_with_histogram!( + RU_STIME, + RU_STIME_HISTOGRAM, + "rezolus/cpu/usage/system", + "The amount of CPU time Rezolus was executing in system mode" +); + +gauge!( + RU_MAXRSS, + "rezolus/memory/usage/resident_set_size", + "The total amount of memory allocated by Rezolus" +); +gauge!(RU_IXRSS, "rezolus/memory/usage/shared_memory_size"); +gauge!(RU_IDRSS, "rezolus/memory/usage/data_size"); +gauge!(RU_ISRSS, "rezolus/memory/usage/stack_size"); +counter!(RU_MINFLT, "rezolus/memory/page/reclaims"); +counter!(RU_MAJFLT, "rezolus/memory/page/faults"); +counter!(RU_NSWAP, "rezolus/memory/usage/shared_memory_size"); + +counter!(RU_INBLOCK, "rezolus/io/block/reads"); +counter!(RU_OUBLOCK, "rezolus/io/block/writes"); + +counter!(RU_MSGSND, "rezolus/messages/sentg"); +counter!(RU_MSGRCV, "rezolus/messages/received"); + +counter!(RU_NSIGNALS, "rezolus/signals/received"); + +counter!(RU_NVCSW, "rezolus/context_switch/voluntary"); +counter!(RU_NIVCSW, "rezolus/context_switch/involuntary"); diff --git a/src/samplers/scheduler/runqueue/mod.rs b/src/samplers/scheduler/runqueue/mod.rs index c12c0423..8fc5ef5b 100644 --- a/src/samplers/scheduler/runqueue/mod.rs +++ b/src/samplers/scheduler/runqueue/mod.rs @@ -11,6 +11,8 @@ mod bpf { include!(concat!(env!("OUT_DIR"), "/scheduler_runqueue.bpf.rs")); } +const NAME: &str = "scheduler_runqueue"; + use bpf::*; use super::stats::*; @@ -45,7 +47,12 @@ pub struct Runqlat { } impl Runqlat { - pub fn new(_config: &Config) -> Result { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let builder = ModSkelBuilder::default(); let mut skel = builder .open() @@ -67,16 +74,16 @@ impl Runqlat { ("running", &SCHEDULER_RUNNING), ]; - for (name, heatmap) in distributions.drain(..) { - bpf.add_distribution(name, heatmap); + for (name, histogram) in distributions.drain(..) { + bpf.add_distribution(name, histogram); } Ok(Self { bpf, - counter_interval: Duration::from_millis(10), + counter_interval: config.interval(NAME), counter_next: Instant::now(), counter_prev: Instant::now(), - distribution_interval: Duration::from_millis(50), + distribution_interval: config.distribution_interval(NAME), distribution_next: Instant::now(), distribution_prev: Instant::now(), }) @@ -89,7 +96,7 @@ impl Runqlat { let elapsed = (now - self.counter_prev).as_secs_f64(); - self.bpf.refresh_counters(now, elapsed); + self.bpf.refresh_counters(elapsed); // determine when to sample next let next = self.counter_next + self.counter_interval; @@ -110,7 +117,7 @@ impl Runqlat { return; } - self.bpf.refresh_distributions(now); + self.bpf.refresh_distributions(); // determine when to sample next let next = self.distribution_next + self.distribution_interval; diff --git a/src/samplers/scheduler/stats.rs b/src/samplers/scheduler/stats.rs index 2a8f2953..7f825138 100644 --- a/src/samplers/scheduler/stats.rs +++ b/src/samplers/scheduler/stats.rs @@ -1,11 +1,11 @@ use crate::*; -heatmap!( +bpfhistogram!( SCHEDULER_RUNQUEUE_LATENCY, "scheduler/runqueue/latency", "distribution of task wait times in the runqueue" ); -heatmap!( +bpfhistogram!( SCHEDULER_RUNNING, "scheduler/running", "distribution of task on-cpu time" diff --git a/src/samplers/syscall/latency/mod.bpf.c b/src/samplers/syscall/latency/mod.bpf.c index 828a7e4a..3b4f948a 100644 --- a/src/samplers/syscall/latency/mod.bpf.c +++ b/src/samplers/syscall/latency/mod.bpf.c @@ -51,6 +51,62 @@ struct { __uint(max_entries, HISTOGRAM_BUCKETS); } total_latency SEC(".maps"); +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(map_flags, BPF_F_MMAPABLE); + __type(key, u32); + __type(value, u64); + __uint(max_entries, HISTOGRAM_BUCKETS); +} read_latency SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(map_flags, BPF_F_MMAPABLE); + __type(key, u32); + __type(value, u64); + __uint(max_entries, HISTOGRAM_BUCKETS); +} write_latency SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(map_flags, BPF_F_MMAPABLE); + __type(key, u32); + __type(value, u64); + __uint(max_entries, HISTOGRAM_BUCKETS); +} poll_latency SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(map_flags, BPF_F_MMAPABLE); + __type(key, u32); + __type(value, u64); + __uint(max_entries, HISTOGRAM_BUCKETS); +} lock_latency SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(map_flags, BPF_F_MMAPABLE); + __type(key, u32); + __type(value, u64); + __uint(max_entries, HISTOGRAM_BUCKETS); +} time_latency SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(map_flags, BPF_F_MMAPABLE); + __type(key, u32); + __type(value, u64); + __uint(max_entries, HISTOGRAM_BUCKETS); +} sleep_latency SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(map_flags, BPF_F_MMAPABLE); + __type(key, u32); + __type(value, u64); + __uint(max_entries, HISTOGRAM_BUCKETS); +} socket_latency SEC(".maps"); + // provides a lookup table from syscall id to a counter index offset struct { __uint(type, BPF_MAP_TYPE_ARRAY); @@ -102,7 +158,7 @@ int sys_exit(struct trace_event_raw_sys_exit *args) if (syscall_id < MAX_SYSCALL_ID) { u32 *counter_offset = bpf_map_lookup_elem(&syscall_lut, &syscall_id); - if (counter_offset && *counter_offset < COUNTER_GROUP_WIDTH) { + if (counter_offset && *counter_offset && *counter_offset < COUNTER_GROUP_WIDTH) { idx = COUNTER_GROUP_WIDTH * bpf_get_smp_processor_id() + ((u32)*counter_offset); cnt = bpf_map_lookup_elem(&counters, &idx); @@ -133,6 +189,35 @@ int sys_exit(struct trace_event_raw_sys_exit *args) __sync_fetch_and_add(cnt, 1); } + // increment latency histogram for the syscall family + if (syscall_id < MAX_SYSCALL_ID) { + u32 *counter_offset = bpf_map_lookup_elem(&syscall_lut, &syscall_id); + + if (!counter_offset || !*counter_offset || *counter_offset >= COUNTER_GROUP_WIDTH) { + return 0; + } + + if (*counter_offset == 1) { + cnt = bpf_map_lookup_elem(&read_latency, &idx); + } else if (*counter_offset == 2) { + cnt = bpf_map_lookup_elem(&write_latency, &idx); + } else if (*counter_offset == 3) { + cnt = bpf_map_lookup_elem(&poll_latency, &idx); + } else if (*counter_offset == 4) { + cnt = bpf_map_lookup_elem(&lock_latency, &idx); + } else if (*counter_offset == 5) { + cnt = bpf_map_lookup_elem(&time_latency, &idx); + } else if (*counter_offset == 6) { + cnt = bpf_map_lookup_elem(&sleep_latency, &idx); + } else if (*counter_offset == 7) { + cnt = bpf_map_lookup_elem(&socket_latency, &idx); + } + + if (cnt) { + __sync_fetch_and_add(cnt, 1); + } + } + return 0; } diff --git a/src/samplers/syscall/latency/mod.rs b/src/samplers/syscall/latency/mod.rs index d613a638..c33186e4 100644 --- a/src/samplers/syscall/latency/mod.rs +++ b/src/samplers/syscall/latency/mod.rs @@ -11,6 +11,8 @@ mod bpf { include!(concat!(env!("OUT_DIR"), "/syscall_latency.bpf.rs")); } +const NAME: &str = "syscall_latency"; + use bpf::*; use super::stats::*; @@ -44,7 +46,12 @@ pub struct Syscall { } impl Syscall { - pub fn new(_config: &Config) -> Result { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let builder = ModSkelBuilder::default(); let mut skel = builder .open() @@ -109,30 +116,39 @@ impl Syscall { let _ = syscall_lut.flush(); let counters = vec![ - Counter::new(&SYSCALL_TOTAL, Some(&SYSCALL_TOTAL_HEATMAP)), - Counter::new(&SYSCALL_READ, Some(&SYSCALL_READ_HEATMAP)), - Counter::new(&SYSCALL_WRITE, Some(&SYSCALL_WRITE_HEATMAP)), - Counter::new(&SYSCALL_POLL, Some(&SYSCALL_POLL_HEATMAP)), - Counter::new(&SYSCALL_LOCK, Some(&SYSCALL_LOCK_HEATMAP)), - Counter::new(&SYSCALL_TIME, Some(&SYSCALL_TIME_HEATMAP)), - Counter::new(&SYSCALL_SLEEP, Some(&SYSCALL_SLEEP_HEATMAP)), - Counter::new(&SYSCALL_SOCKET, Some(&SYSCALL_SOCKET_HEATMAP)), + Counter::new(&SYSCALL_TOTAL, Some(&SYSCALL_TOTAL_HISTOGRAM)), + Counter::new(&SYSCALL_READ, Some(&SYSCALL_READ_HISTOGRAM)), + Counter::new(&SYSCALL_WRITE, Some(&SYSCALL_WRITE_HISTOGRAM)), + Counter::new(&SYSCALL_POLL, Some(&SYSCALL_POLL_HISTOGRAM)), + Counter::new(&SYSCALL_LOCK, Some(&SYSCALL_LOCK_HISTOGRAM)), + Counter::new(&SYSCALL_TIME, Some(&SYSCALL_TIME_HISTOGRAM)), + Counter::new(&SYSCALL_SLEEP, Some(&SYSCALL_SLEEP_HISTOGRAM)), + Counter::new(&SYSCALL_SOCKET, Some(&SYSCALL_SOCKET_HISTOGRAM)), ]; bpf.add_counters("counters", counters); - let mut distributions = vec![("total_latency", &SYSCALL_TOTAL_LATENCY)]; + let mut distributions = vec![ + ("total_latency", &SYSCALL_TOTAL_LATENCY), + ("read_latency", &SYSCALL_READ_LATENCY), + ("write_latency", &SYSCALL_WRITE_LATENCY), + ("poll_latency", &SYSCALL_POLL_LATENCY), + ("lock_latency", &SYSCALL_LOCK_LATENCY), + ("time_latency", &SYSCALL_TIME_LATENCY), + ("sleep_latency", &SYSCALL_SLEEP_LATENCY), + ("socket_latency", &SYSCALL_SOCKET_LATENCY), + ]; - for (name, heatmap) in distributions.drain(..) { - bpf.add_distribution(name, heatmap); + for (name, histogram) in distributions.drain(..) { + bpf.add_distribution(name, histogram); } Ok(Self { bpf, - counter_interval: Duration::from_millis(10), + counter_interval: config.interval(NAME), counter_next: Instant::now(), counter_prev: Instant::now(), - distribution_interval: Duration::from_millis(50), + distribution_interval: config.distribution_interval(NAME), distribution_next: Instant::now(), distribution_prev: Instant::now(), }) @@ -145,7 +161,7 @@ impl Syscall { let elapsed = (now - self.counter_prev).as_secs_f64(); - self.bpf.refresh_counters(now, elapsed); + self.bpf.refresh_counters(elapsed); // determine when to sample next let next = self.counter_next + self.counter_interval; @@ -166,7 +182,7 @@ impl Syscall { return; } - self.bpf.refresh_distributions(now); + self.bpf.refresh_distributions(); // determine when to sample next let next = self.distribution_next + self.distribution_interval; diff --git a/src/samplers/syscall/stats.rs b/src/samplers/syscall/stats.rs index 77c55085..12ac1aa9 100644 --- a/src/samplers/syscall/stats.rs +++ b/src/samplers/syscall/stats.rs @@ -1,55 +1,90 @@ use crate::*; -counter_with_heatmap!( +counter_with_histogram!( SYSCALL_TOTAL, - SYSCALL_TOTAL_HEATMAP, + SYSCALL_TOTAL_HISTOGRAM, "syscall/total", "tracks total syscalls" ); -counter_with_heatmap!( +counter_with_histogram!( SYSCALL_READ, - SYSCALL_READ_HEATMAP, + SYSCALL_READ_HISTOGRAM, "syscall/read", "tracks read related syscalls (read, recvfrom, ...)" ); -counter_with_heatmap!( +counter_with_histogram!( SYSCALL_WRITE, - SYSCALL_WRITE_HEATMAP, + SYSCALL_WRITE_HISTOGRAM, "syscall/write", "tracks write related syscalls (write, sendto, ...)" ); -counter_with_heatmap!( +counter_with_histogram!( SYSCALL_POLL, - SYSCALL_POLL_HEATMAP, + SYSCALL_POLL_HISTOGRAM, "syscall/poll", "tracks poll related syscalls (poll, select, epoll, ...)" ); -counter_with_heatmap!( +counter_with_histogram!( SYSCALL_LOCK, - SYSCALL_LOCK_HEATMAP, + SYSCALL_LOCK_HISTOGRAM, "syscall/lock", "tracks lock related syscalls (futex)" ); -counter_with_heatmap!( +counter_with_histogram!( SYSCALL_TIME, - SYSCALL_TIME_HEATMAP, + SYSCALL_TIME_HISTOGRAM, "syscall/time", "tracks time related syscalls (clock_gettime, clock_settime, clock_getres, ...)" ); -counter_with_heatmap!( +counter_with_histogram!( SYSCALL_SLEEP, - SYSCALL_SLEEP_HEATMAP, + SYSCALL_SLEEP_HISTOGRAM, "syscall/sleep", "tracks sleep related syscalls (nanosleep, clock_nanosleep)" ); -counter_with_heatmap!( +counter_with_histogram!( SYSCALL_SOCKET, - SYSCALL_SOCKET_HEATMAP, + SYSCALL_SOCKET_HISTOGRAM, "syscall/socket", "tracks socket related syscalls (accept, connect, bind, setsockopt, ...)" ); -heatmap!( +bpfhistogram!( SYSCALL_TOTAL_LATENCY, "syscall/total/latency", "latency of all syscalls" ); +bpfhistogram!( + SYSCALL_READ_LATENCY, + "syscall/read/latency", + "latency of read related syscalls (read, recvfrom, ...)" +); +bpfhistogram!( + SYSCALL_WRITE_LATENCY, + "syscall/write/latency", + "latency of write related syscalls (write, sendto, ...)" +); +bpfhistogram!( + SYSCALL_POLL_LATENCY, + "syscall/poll/latency", + "latency of poll related syscalls (poll, select, epoll, ...)" +); +bpfhistogram!( + SYSCALL_LOCK_LATENCY, + "syscall/lock/latency", + "latency of lock related syscalls (futex)" +); +bpfhistogram!( + SYSCALL_TIME_LATENCY, + "syscall/time/latency", + "latency of time related syscalls (clock_gettime, clock_settime, clock_getres, ...)" +); +bpfhistogram!( + SYSCALL_SLEEP_LATENCY, + "syscall/sleep/latency", + "latency of sleep related syscalls (nanosleep, clock_nanosleep)" +); +bpfhistogram!( + SYSCALL_SOCKET_LATENCY, + "syscall/socket/latency", + "latency of socket related syscalls (accept, connect, bind, setsockopt, ...)" +); diff --git a/src/samplers/tcp/connection_state/mod.rs b/src/samplers/tcp/connection_state/mod.rs new file mode 100644 index 00000000..e67f25ce --- /dev/null +++ b/src/samplers/tcp/connection_state/mod.rs @@ -0,0 +1,133 @@ +use crate::common::Nop; +use crate::samplers::tcp::stats::*; +use crate::samplers::tcp::*; +use metriken::Gauge; +use std::fs::File; +use std::io::Read; +use std::io::Seek; + +#[distributed_slice(TCP_SAMPLERS)] +fn init(config: &Config) -> Box { + if let Ok(s) = ConnectionState::new(config) { + Box::new(s) + } else { + Box::new(Nop::new(config)) + } +} + +const NAME: &str = "tcp_connection_state"; + +pub struct ConnectionState { + prev: Instant, + next: Instant, + interval: Duration, + files: Vec, + gauges: Vec<(&'static Lazy, i64)>, +} + +impl ConnectionState { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + + let now = Instant::now(); + + let gauges = vec![ + (&TCP_CONN_STATE_ESTABLISHED, 0), + (&TCP_CONN_STATE_SYN_SENT, 0), + (&TCP_CONN_STATE_SYN_RECV, 0), + (&TCP_CONN_STATE_FIN_WAIT1, 0), + (&TCP_CONN_STATE_FIN_WAIT2, 0), + (&TCP_CONN_STATE_TIME_WAIT, 0), + (&TCP_CONN_STATE_CLOSE, 0), + (&TCP_CONN_STATE_CLOSE_WAIT, 0), + (&TCP_CONN_STATE_LAST_ACK, 0), + (&TCP_CONN_STATE_LISTEN, 0), + (&TCP_CONN_STATE_CLOSING, 0), + (&TCP_CONN_STATE_NEW_SYN_RECV, 0), + ]; + + let ipv4 = File::open("/proc/net/tcp").map_err(|e| { + error!("Failed to open /proc/net/tcp: {e}"); + }); + + let ipv6 = File::open("/proc/net/tcp6").map_err(|e| { + error!("Failed to open /proc/net/tcp6: {e}"); + }); + + let mut files: Vec> = vec![ipv4, ipv6]; + + let files: Vec = files.drain(..).filter_map(|v| v.ok()).collect(); + + if files.is_empty() { + error!("Could not open any file in /proc/net for this sampler"); + return Err(()); + } + + Ok(Self { + files, + gauges, + prev: now, + next: now, + interval: config.interval(NAME), + }) + } +} + +impl Sampler for ConnectionState { + fn sample(&mut self) { + let now = Instant::now(); + + if now < self.next { + return; + } + + // zero the temporary gauges + for (_, gauge) in self.gauges.iter_mut() { + *gauge = 0; + } + + for file in self.files.iter_mut() { + // seek to start to cause reload of content + if file.rewind().is_ok() { + let mut data = String::new(); + if file.read_to_string(&mut data).is_err() { + error!("error reading /proc/net/tcp"); + return; + } + + for line in data.lines() { + let parts: Vec<&str> = line.split_whitespace().collect(); + + // find and increment the temporary gauge for this state + if let Some(Ok(state)) = parts.get(3).map(|v| u8::from_str_radix(v, 16)) { + if let Some((_, gauge)) = self.gauges.get_mut(state as usize - 1) { + *gauge += 1; + } + } + } + } + } + + for (gauge, value) in self.gauges.iter() { + gauge.set(*value); + } + + // determine when to sample next + let next = self.next + self.interval; + + // it's possible we fell behind + if next > now { + // if we didn't, sample at the next planned time + self.next = next; + } else { + // if we did, sample after the interval has elapsed + self.next = now + self.interval; + } + + // mark when we last sampled + self.prev = now; + } +} diff --git a/src/samplers/tcp/mod.rs b/src/samplers/tcp/mod.rs index 9c2c04e1..6675f2ec 100644 --- a/src/samplers/tcp/mod.rs +++ b/src/samplers/tcp/mod.rs @@ -4,6 +4,7 @@ sampler!(Tcp, "tcp", TCP_SAMPLERS); mod stats; +mod connection_state; mod snmp; #[cfg(all(feature = "bpf", target_os = "linux"))] diff --git a/src/samplers/tcp/packet_latency/mod.rs b/src/samplers/tcp/packet_latency/mod.rs index 242a03ae..b63ca035 100644 --- a/src/samplers/tcp/packet_latency/mod.rs +++ b/src/samplers/tcp/packet_latency/mod.rs @@ -11,6 +11,8 @@ mod bpf { include!(concat!(env!("OUT_DIR"), "/tcp_packet_latency.bpf.rs")); } +const NAME: &str = "tcp_packet_latency"; + use bpf::*; use super::stats::*; @@ -42,7 +44,12 @@ pub struct PacketLatency { } impl PacketLatency { - pub fn new(_config: &Config) -> Result { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let builder = ModSkelBuilder::default(); let mut skel = builder .open() @@ -57,16 +64,16 @@ impl PacketLatency { let mut distributions = vec![("latency", &TCP_PACKET_LATENCY)]; - for (name, heatmap) in distributions.drain(..) { - bpf.add_distribution(name, heatmap); + for (name, histogram) in distributions.drain(..) { + bpf.add_distribution(name, histogram); } Ok(Self { bpf, - counter_interval: Duration::from_millis(10), + counter_interval: config.interval(NAME), counter_next: Instant::now(), counter_prev: Instant::now(), - distribution_interval: Duration::from_millis(50), + distribution_interval: config.distribution_interval(NAME), distribution_next: Instant::now(), distribution_prev: Instant::now(), }) @@ -79,7 +86,7 @@ impl PacketLatency { let elapsed = (now - self.counter_prev).as_secs_f64(); - self.bpf.refresh_counters(now, elapsed); + self.bpf.refresh_counters(elapsed); // determine when to sample next let next = self.counter_next + self.counter_interval; @@ -100,7 +107,7 @@ impl PacketLatency { return; } - self.bpf.refresh_distributions(now); + self.bpf.refresh_distributions(); // determine when to sample next let next = self.distribution_next + self.distribution_interval; diff --git a/src/samplers/tcp/receive/mod.rs b/src/samplers/tcp/receive/mod.rs index e1136a82..6f87ccae 100644 --- a/src/samplers/tcp/receive/mod.rs +++ b/src/samplers/tcp/receive/mod.rs @@ -11,6 +11,8 @@ mod bpf { include!(concat!(env!("OUT_DIR"), "/tcp_receive.bpf.rs")); } +const NAME: &str = "tcp_receive"; + use bpf::*; use super::stats::*; @@ -41,7 +43,12 @@ pub struct Receive { } impl Receive { - pub fn new(_config: &Config) -> Result { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let builder = ModSkelBuilder::default(); let mut skel = builder .open() @@ -56,16 +63,16 @@ impl Receive { let mut distributions = vec![("srtt", &TCP_SRTT), ("jitter", &TCP_JITTER)]; - for (name, heatmap) in distributions.drain(..) { - bpf.add_distribution(name, heatmap); + for (name, histogram) in distributions.drain(..) { + bpf.add_distribution(name, histogram); } Ok(Self { bpf, - counter_interval: Duration::from_millis(10), + counter_interval: config.interval(NAME), counter_next: Instant::now(), counter_prev: Instant::now(), - distribution_interval: Duration::from_millis(50), + distribution_interval: config.distribution_interval(NAME), distribution_next: Instant::now(), distribution_prev: Instant::now(), }) @@ -78,7 +85,7 @@ impl Receive { let elapsed = (now - self.counter_prev).as_secs_f64(); - self.bpf.refresh_counters(now, elapsed); + self.bpf.refresh_counters(elapsed); // determine when to sample next let next = self.counter_next + self.counter_interval; @@ -99,7 +106,7 @@ impl Receive { return; } - self.bpf.refresh_distributions(now); + self.bpf.refresh_distributions(); // determine when to sample next let next = self.distribution_next + self.distribution_interval; diff --git a/src/samplers/tcp/retransmit/mod.rs b/src/samplers/tcp/retransmit/mod.rs index 510395f1..6afbf0b0 100644 --- a/src/samplers/tcp/retransmit/mod.rs +++ b/src/samplers/tcp/retransmit/mod.rs @@ -11,6 +11,8 @@ mod bpf { include!(concat!(env!("OUT_DIR"), "/tcp_retransmit.bpf.rs")); } +const NAME: &str = "tcp_retransmit"; + use bpf::*; use super::stats::*; @@ -40,7 +42,12 @@ pub struct Retransmit { } impl Retransmit { - pub fn new(_config: &Config) -> Result { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let builder = ModSkelBuilder::default(); let mut skel = builder .open() @@ -55,17 +62,17 @@ impl Retransmit { let counters = vec![Counter::new( &TCP_TX_RETRANSMIT, - Some(&TCP_TX_RETRANSMIT_HEATMAP), + Some(&TCP_TX_RETRANSMIT_HISTOGRAM), )]; bpf.add_counters("counters", counters); Ok(Self { bpf, - counter_interval: Duration::from_millis(10), + counter_interval: config.interval(NAME), counter_next: Instant::now(), counter_prev: Instant::now(), - distribution_interval: Duration::from_millis(50), + distribution_interval: config.distribution_interval(NAME), distribution_next: Instant::now(), distribution_prev: Instant::now(), }) @@ -78,7 +85,7 @@ impl Retransmit { let elapsed = (now - self.counter_prev).as_secs_f64(); - self.bpf.refresh_counters(now, elapsed); + self.bpf.refresh_counters(elapsed); // determine when to sample next let next = self.counter_next + self.counter_interval; @@ -99,7 +106,7 @@ impl Retransmit { return; } - self.bpf.refresh_distributions(now); + self.bpf.refresh_distributions(); // determine when to sample next let next = self.distribution_next + self.distribution_interval; diff --git a/src/samplers/tcp/snmp/mod.rs b/src/samplers/tcp/snmp/mod.rs index 567e11f8..d53d74e8 100644 --- a/src/samplers/tcp/snmp/mod.rs +++ b/src/samplers/tcp/snmp/mod.rs @@ -10,11 +10,15 @@ fn init(config: &Config) -> Box { // if we have bpf enabled, we don't need to run this sampler at all if config.bpf() { Box::new(Nop::new(config)) + } else if let Ok(s) = Snmp::new(config) { + Box::new(s) } else { - Box::new(Snmp::new(config)) + Box::new(Nop::new(config)) } } +const NAME: &str = "tcp_snmp"; + pub struct Snmp { prev: Instant, next: Instant, @@ -24,29 +28,34 @@ pub struct Snmp { } impl Snmp { - pub fn new(_config: &Config) -> Self { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let now = Instant::now(); let counters = vec![ ( - Counter::new(&TCP_RX_SEGMENTS, Some(&TCP_RX_SEGMENTS_HEATMAP)), + Counter::new(&TCP_RX_SEGMENTS, Some(&TCP_RX_SEGMENTS_HISTOGRAM)), "Tcp:", "InSegs", ), ( - Counter::new(&TCP_TX_SEGMENTS, Some(&TCP_TX_SEGMENTS_HEATMAP)), + Counter::new(&TCP_TX_SEGMENTS, Some(&TCP_TX_SEGMENTS_HISTOGRAM)), "Tcp:", "OutSegs", ), ]; - Self { + Ok(Self { file: File::open("/proc/net/snmp").expect("file not found"), counters, prev: now, next: now, - interval: Duration::from_millis(10), - } + interval: config.interval(NAME), + }) } } @@ -63,7 +72,7 @@ impl Sampler for Snmp { if let Ok(nested_map) = NestedMap::try_from_procfs(&mut self.file) { for (counter, pkey, lkey) in self.counters.iter_mut() { if let Some(curr) = nested_map.get(pkey, lkey) { - counter.set(now, elapsed, curr); + counter.set(elapsed, curr); } } } diff --git a/src/samplers/tcp/stats.rs b/src/samplers/tcp/stats.rs index 8b8431dd..2ff992d3 100644 --- a/src/samplers/tcp/stats.rs +++ b/src/samplers/tcp/stats.rs @@ -1,47 +1,183 @@ use crate::*; +use metriken::metric; +use metriken::Format; +use metriken::Gauge; +use metriken::LazyGauge; +use metriken::MetricEntry; -counter_with_heatmap!( +counter_with_histogram!( TCP_RX_BYTES, - TCP_RX_BYTES_HEATMAP, + TCP_RX_BYTES_HISTOGRAM, "tcp/receive/bytes", "number of bytes received over TCP" ); -counter_with_heatmap!( +counter_with_histogram!( TCP_RX_SEGMENTS, - TCP_RX_SEGMENTS_HEATMAP, + TCP_RX_SEGMENTS_HISTOGRAM, "tcp/receive/segments", "number of TCP segments received" ); -counter_with_heatmap!( +counter_with_histogram!( TCP_TX_BYTES, - TCP_TX_BYTES_HEATMAP, + TCP_TX_BYTES_HISTOGRAM, "tcp/transmit/bytes", "number of bytes transmitted over TCP" ); -counter_with_heatmap!( +counter_with_histogram!( TCP_TX_SEGMENTS, - TCP_TX_SEGMENTS_HEATMAP, + TCP_TX_SEGMENTS_HISTOGRAM, "tcp/transmit/segments", "number of TCP segments transmitted" ); -counter_with_heatmap!( +counter_with_histogram!( TCP_TX_RETRANSMIT, - TCP_TX_RETRANSMIT_HEATMAP, + TCP_TX_RETRANSMIT_HISTOGRAM, "tcp/transmit/retransmit", "number of TCP segments retransmitted" ); -heatmap!( +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the ESTABLISHED state", + formatter = conn_state_formatter, + metadata = { state = "established" } +)] +pub static TCP_CONN_STATE_ESTABLISHED: LazyGauge = LazyGauge::new(Gauge::default); + +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the SYN_SENT state", + formatter = conn_state_formatter, + metadata = { state = "syn_sent" } +)] +pub static TCP_CONN_STATE_SYN_SENT: LazyGauge = LazyGauge::new(Gauge::default); + +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the SYN_RECV state", + formatter = conn_state_formatter, + metadata = { state = "syn_recv" } +)] +pub static TCP_CONN_STATE_SYN_RECV: LazyGauge = LazyGauge::new(Gauge::default); + +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the FIN_WAIT1 state", + formatter = conn_state_formatter, + metadata = { state = "fin_wait1" } +)] +pub static TCP_CONN_STATE_FIN_WAIT1: LazyGauge = LazyGauge::new(Gauge::default); + +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the FIN_WAIT2 state", + formatter = conn_state_formatter, + metadata = { state = "fin_wait2" } +)] +pub static TCP_CONN_STATE_FIN_WAIT2: LazyGauge = LazyGauge::new(Gauge::default); + +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the TIME_WAIT state", + formatter = conn_state_formatter, + metadata = { state = "time_wait" } +)] +pub static TCP_CONN_STATE_TIME_WAIT: LazyGauge = LazyGauge::new(Gauge::default); + +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the CLOSE state", + formatter = conn_state_formatter, + metadata = { state = "close" } +)] +pub static TCP_CONN_STATE_CLOSE: LazyGauge = LazyGauge::new(Gauge::default); + +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the CLOSE_WAIT state", + formatter = conn_state_formatter, + metadata = { state = "close_wait" } +)] +pub static TCP_CONN_STATE_CLOSE_WAIT: LazyGauge = LazyGauge::new(Gauge::default); + +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the LAST_ACK state", + formatter = conn_state_formatter, + metadata = { state = "last_ack" } +)] +pub static TCP_CONN_STATE_LAST_ACK: LazyGauge = LazyGauge::new(Gauge::default); + +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the LISTEN state", + formatter = conn_state_formatter, + metadata = { state = "listen" } +)] +pub static TCP_CONN_STATE_LISTEN: LazyGauge = LazyGauge::new(Gauge::default); + +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the CLOSING state", + formatter = conn_state_formatter, + metadata = { state = "closing" } +)] +pub static TCP_CONN_STATE_CLOSING: LazyGauge = LazyGauge::new(Gauge::default); + +#[metric( + name = "tcp/connection/state", + description = "The current number of TCP connections in the NEW_SYN_RECV state", + formatter = conn_state_formatter, + metadata = { state = "new_syn_recv" } +)] +pub static TCP_CONN_STATE_NEW_SYN_RECV: LazyGauge = LazyGauge::new(Gauge::default); + +bpfhistogram!( TCP_RX_SIZE, "tcp/receive/size", "distribution of receive segment sizes" ); -heatmap!( +bpfhistogram!( TCP_TX_SIZE, "tcp/transmit/size", "distribution of transmit segment sizes" ); -heatmap!(TCP_JITTER, "tcp/jitter"); -heatmap!(TCP_SRTT, "tcp/srtt"); +bpfhistogram!(TCP_JITTER, "tcp/jitter"); +bpfhistogram!(TCP_SRTT, "tcp/srtt"); + +bpfhistogram!(TCP_PACKET_LATENCY, "tcp/packet_latency"); + +/// A function to format the tcp connection state metrics. +/// +/// For the `Simple` format, the metrics will be formatted according to the +/// a pattern which depends on the metric metadata: +/// `{name}/{state}` eg: `cpu/connection/state/listen` +/// +/// For the `Prometheus` format, the state is supplied as a label. Note: we rely +/// on the exposition logic to convert the `/`s to `_`s in the metric name. +pub fn conn_state_formatter(metric: &MetricEntry, format: Format) -> String { + match format { + Format::Simple => { + if let Some(state) = metric.metadata().get("state") { + format!("{}/{state}", metric.name()) + } else { + metric.name().to_string() + } + } + Format::Prometheus => { + let metadata: Vec = metric + .metadata() + .iter() + .map(|(key, value)| format!("{key}=\"{value}\"")) + .collect(); + let metadata = metadata.join(", "); -heatmap!(TCP_PACKET_LATENCY, "tcp/packet_latency"); + if metadata.is_empty() { + metric.name().to_string() + } else { + format!("{}{{{metadata}}}", metric.name()) + } + } + _ => metriken::default_formatter(metric, format), + } +} diff --git a/src/samplers/tcp/traffic/mod.rs b/src/samplers/tcp/traffic/mod.rs index a59b5fbe..e7119d96 100644 --- a/src/samplers/tcp/traffic/mod.rs +++ b/src/samplers/tcp/traffic/mod.rs @@ -11,6 +11,8 @@ mod bpf { include!(concat!(env!("OUT_DIR"), "/tcp_traffic.bpf.rs")); } +const NAME: &str = "tcp_traffic"; + use bpf::*; use super::stats::*; @@ -46,7 +48,12 @@ pub struct Traffic { } impl Traffic { - pub fn new(_config: &Config) -> Result { + pub fn new(config: &Config) -> Result { + // check if sampler should be enabled + if !config.enabled(NAME) { + return Err(()); + } + let builder = ModSkelBuilder::default(); let mut skel = builder .open() @@ -60,26 +67,26 @@ impl Traffic { let mut bpf = Bpf::from_skel(skel); let counters = vec![ - Counter::new(&TCP_RX_BYTES, Some(&TCP_RX_BYTES_HEATMAP)), - Counter::new(&TCP_TX_BYTES, Some(&TCP_TX_BYTES_HEATMAP)), - Counter::new(&TCP_RX_SEGMENTS, Some(&TCP_RX_SEGMENTS_HEATMAP)), - Counter::new(&TCP_TX_SEGMENTS, Some(&TCP_TX_SEGMENTS_HEATMAP)), + Counter::new(&TCP_RX_BYTES, Some(&TCP_RX_BYTES_HISTOGRAM)), + Counter::new(&TCP_TX_BYTES, Some(&TCP_TX_BYTES_HISTOGRAM)), + Counter::new(&TCP_RX_SEGMENTS, Some(&TCP_RX_SEGMENTS_HISTOGRAM)), + Counter::new(&TCP_TX_SEGMENTS, Some(&TCP_TX_SEGMENTS_HISTOGRAM)), ]; bpf.add_counters("counters", counters); let mut distributions = vec![("rx_size", &TCP_RX_SIZE), ("tx_size", &TCP_TX_SIZE)]; - for (name, heatmap) in distributions.drain(..) { - bpf.add_distribution(name, heatmap); + for (name, histogram) in distributions.drain(..) { + bpf.add_distribution(name, histogram); } Ok(Self { bpf, - counter_interval: Duration::from_millis(10), + counter_interval: config.interval(NAME), counter_next: Instant::now(), counter_prev: Instant::now(), - distribution_interval: Duration::from_millis(50), + distribution_interval: config.distribution_interval(NAME), distribution_next: Instant::now(), distribution_prev: Instant::now(), }) @@ -92,7 +99,7 @@ impl Traffic { let elapsed = (now - self.counter_prev).as_secs_f64(); - self.bpf.refresh_counters(now, elapsed); + self.bpf.refresh_counters(elapsed); // determine when to sample next let next = self.counter_next + self.counter_interval; @@ -113,7 +120,7 @@ impl Traffic { return; } - self.bpf.refresh_distributions(now); + self.bpf.refresh_distributions(); // determine when to sample next let next = self.distribution_next + self.distribution_interval;