feat: opentelemetry metrics exporter (#776)

* feat: opentelemetry metrics exporter

* chore: run first export after the period has passed

* feat: include gpu name in attributes

* chore: fix typo

* chore: update docs

* fix: disable coverage action
This commit is contained in:
Ilya Zlobintsev
2025-10-12 12:28:09 +03:00
committed by GitHub
parent 476b6773af
commit 3d50c4dd44
13 changed files with 1541 additions and 22 deletions
+22 -21
View File
@@ -59,30 +59,31 @@ jobs:
- name: Run cargo audit
run: cargo audit
coverage:
runs-on: ubuntu-24.04
if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
steps:
- uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2
- uses: ./.github/actions/setup-environment
# Disabled temporarily due to failing dependency build
# coverage:
# runs-on: ubuntu-24.04
# if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
# steps:
# - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2
# - uses: ./.github/actions/setup-environment
- name: Install cargo-llvm-cov from crates.io
uses: baptiste0928/cargo-install@v3
with:
crate: cargo-llvm-cov
version: '0.6.18'
# - name: Install cargo-llvm-cov from crates.io
# uses: baptiste0928/cargo-install@v3
# with:
# crate: cargo-llvm-cov
# version: '0.6.18'
- name: Generate coverage report
run: cargo llvm-cov --no-fail-fast --include-ffi --workspace --lcov --output-path lcov.info
# - name: Generate coverage report
# run: cargo llvm-cov --no-fail-fast --include-ffi --workspace --lcov --output-path lcov.info
- name: Upload to Codecov
uses: codecov/codecov-action@v5.5.1
continue-on-error: true
with:
files: lcov.info
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
# - name: Upload to Codecov
# uses: codecov/codecov-action@v5.5.1
# continue-on-error: true
# with:
# files: lcov.info
# fail_ci_if_error: true
# token: ${{ secrets.CODECOV_TOKEN }}
# verbose: true
test-miri:
runs-on: ubuntu-24.04
Generated
+444
View File
@@ -428,6 +428,35 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]]
name = "cookie_store"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f"
dependencies = [
"cookie",
"document-features",
"idna",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"time",
"url",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -602,6 +631,15 @@ dependencies = [
"syn",
]
[[package]]
name = "document-features"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
dependencies = [
"litrs",
]
[[package]]
name = "easy_fuser"
version = "0.4.2"
@@ -746,6 +784,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
[[package]]
name = "flate2"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fluent"
version = "0.17.0"
@@ -809,6 +857,15 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
[[package]]
name = "fragile"
version = "2.0.1"
@@ -1261,6 +1318,23 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "http"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "i18n-config"
version = "0.4.8"
@@ -1352,12 +1426,119 @@ dependencies = [
"cc",
]
[[package]]
name = "icu_collections"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
[[package]]
name = "icu_provider"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.11.1"
@@ -1554,6 +1735,7 @@ dependencies = [
"tokio",
"tracing",
"tracing-subscriber",
"ureq",
"zbus",
]
@@ -1722,6 +1904,18 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "litemap"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]]
name = "litrs"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
[[package]]
name = "lock_api"
version = "0.4.13"
@@ -1775,6 +1969,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
@@ -2043,6 +2238,12 @@ dependencies = [
"serde",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@@ -2089,6 +2290,15 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "potential_utf"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
dependencies = [
"zerovec",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@@ -2260,6 +2470,20 @@ dependencies = [
"syn",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.16",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rle-decode-fast"
version = "1.0.3"
@@ -2334,6 +2558,50 @@ dependencies = [
"windows-sys 0.61.0",
]
[[package]]
name = "rustls"
version = "0.23.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
@@ -2510,6 +2778,12 @@ dependencies = [
"libc",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "similar"
version = "2.7.0"
@@ -2547,6 +2821,12 @@ dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
@@ -2559,6 +2839,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.106"
@@ -2570,6 +2856,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sys-locale"
version = "0.3.2"
@@ -2958,6 +3255,69 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39abd59bf32521c7f2301b52d05a6a2c975b6003521cbd0c6dc1582f0a22104"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537"
dependencies = [
"base64",
"cookie_store",
"flate2",
"log",
"percent-encoding",
"rustls",
"rustls-pemfile",
"rustls-pki-types",
"serde",
"serde_json",
"ureq-proto",
"utf-8",
"webpki-roots",
]
[[package]]
name = "ureq-proto"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2"
dependencies = [
"base64",
"http",
"httparse",
"log",
]
[[package]]
name = "url"
version = "2.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
@@ -3097,6 +3457,15 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "winapi"
version = "0.3.9"
@@ -3468,6 +3837,12 @@ dependencies = [
"syn",
]
[[package]]
name = "writeable"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "xattr"
version = "1.5.1"
@@ -3484,6 +3859,30 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "yoke"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zbus"
version = "5.11.0"
@@ -3564,6 +3963,38 @@ name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
@@ -3571,7 +4002,20 @@ version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
+2
View File
@@ -38,6 +38,7 @@ system.
- GPU undervolting (via voltage offset on AMD, [indirectly](https://github.com/ilya-zlobintsev/LACT/wiki/Frequently-asked-questions#how-to-undervolt-nvidia-gpus) on Nvidia)
- #### Settings profiles
- Automatic profile activation based on running processes or gamemode status
- #### [OpenTelemetry metrics exporter](./docs/EXPORTER.md)
GPU configuration is handled by a system service that does not depend on a graphical session (Wayland/X11).
@@ -53,6 +54,7 @@ The service can also be used standalone with a config file, for example in headl
- [API](./docs/API.md)
- [Power profiles daemon note](#power-profiles-daemon-note)
- [Recovery from a bad overclock](https://github.com/ilya-zlobintsev/LACT/wiki/Recovering-from-a-bad-overclock)
- [Metrics exporter](./docs/EXPORTER.md)
- [Contribute translations](#localization)
- [Support the project](#support-the-project)
+8
View File
@@ -31,6 +31,14 @@ daemon:
# By default TCP access is disabled, and only a unix socket is present.
# Specifying this option enables the TCP listener.
tcp_listen_address: 127.0.0.1:12853
# Opentelemetry metrics exporter configuration
# Omitted by default, meaning the exporter is disabled.
metrics:
# Opentelemetry collector URL. The following example is for local Prometheus.
collector_address: 'http://localhost:9090/api/v1/otlp/v1/metrics'
# Interval in seconds for how often to collect the metrics.
# Defaults to 30.
interval: 30
# Period in seconds for how long settings should wait to be confirmed.
# Most GPU setting change commands require a confirmation command to be used
+36
View File
@@ -0,0 +1,36 @@
# OpenTelemetry exporter
The LACT daemon supports exporting GPU metrics using the standard [OpenTelemetry metrics API](https://opentelemetry.io/docs/specs/otel/metrics/).
In particular, it uses the [OTLP/HTTP](https://opentelemetry.io/docs/specs/otlp/#otlphttp) protocol with a JSON payload.
To use this functionality, you need an OpenTelemetry-compatible metrics collector such as Prometheus [with OTLP support enabled](https://prometheus.io/docs/guides/opentelemetry/).
After this, you need to set the collector URL in `/etc/lact/config.yaml`. For example:
```yaml
daemon:
metrics:
# This example is a local Prometheus URL. Other collectors might simply use `/v1/metrics` as the path.
collector_address: 'http://localhost:9090/api/v1/otlp/v1/metrics'
# Metrics collection interval in seconds. Not necessary to set, defaults to 30 seconds.
interval: 30
```
Restart the daemon after changing the config.
The following metrics are currently supported :
- lact_gpu_usage
- lact_gpu_power_usage
- lact_gpu_power_usage_cap
- lact_gpu_temperature
- lact_gpu_clockspeed
- lact_gpu_voltage
- lact_gpu_fan_speed
- lact_gpu_fan_speed_max
- lact_gpu_fan_percent
- lact_gpu_vram_used
- lact_gpu_vram_total
Note: this is the source metric name, collectors might alter it for queries. For example, Prometheus adds a unit suffix, e.g. `lact_gpu_power_usage` -> `lact_gpu_power_usage_watts`
![image](../res/screenshots/grafana.png)
+559
View File
@@ -608,6 +608,32 @@
"dest": "cargo/vendor/console-0.15.11",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/cookie/cookie-0.18.1.crate",
"sha256": "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747",
"dest": "cargo/vendor/cookie-0.18.1"
},
{
"type": "inline",
"contents": "{\"package\": \"4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747\", \"files\": {}}",
"dest": "cargo/vendor/cookie-0.18.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/cookie_store/cookie_store-0.22.0.crate",
"sha256": "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f",
"dest": "cargo/vendor/cookie_store-0.22.0"
},
{
"type": "inline",
"contents": "{\"package\": \"3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f\", \"files\": {}}",
"dest": "cargo/vendor/cookie_store-0.22.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -842,6 +868,19 @@
"dest": "cargo/vendor/dlopen2_derive-0.4.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/document-features/document-features-0.2.11.crate",
"sha256": "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d",
"dest": "cargo/vendor/document-features-0.2.11"
},
{
"type": "inline",
"contents": "{\"package\": \"95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d\", \"files\": {}}",
"dest": "cargo/vendor/document-features-0.2.11",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -1050,6 +1089,19 @@
"dest": "cargo/vendor/find-msvc-tools-0.1.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/flate2/flate2-1.1.4.crate",
"sha256": "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9",
"dest": "cargo/vendor/flate2-1.1.4"
},
{
"type": "inline",
"contents": "{\"package\": \"dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9\", \"files\": {}}",
"dest": "cargo/vendor/flate2-1.1.4",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -1128,6 +1180,19 @@
"dest": "cargo/vendor/fnv-1.0.7",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/form_urlencoded/form_urlencoded-1.2.2.crate",
"sha256": "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf",
"dest": "cargo/vendor/form_urlencoded-1.2.2"
},
{
"type": "inline",
"contents": "{\"package\": \"cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf\", \"files\": {}}",
"dest": "cargo/vendor/form_urlencoded-1.2.2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -1635,6 +1700,32 @@
"dest": "cargo/vendor/hex-0.4.3",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/http/http-1.3.1.crate",
"sha256": "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565",
"dest": "cargo/vendor/http-1.3.1"
},
{
"type": "inline",
"contents": "{\"package\": \"f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565\", \"files\": {}}",
"dest": "cargo/vendor/http-1.3.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/httparse/httparse-1.10.1.crate",
"sha256": "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87",
"dest": "cargo/vendor/httparse-1.10.1"
},
{
"type": "inline",
"contents": "{\"package\": \"6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87\", \"files\": {}}",
"dest": "cargo/vendor/httparse-1.10.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -1713,6 +1804,97 @@
"dest": "cargo/vendor/iana-time-zone-haiku-0.1.2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/icu_collections/icu_collections-2.0.0.crate",
"sha256": "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47",
"dest": "cargo/vendor/icu_collections-2.0.0"
},
{
"type": "inline",
"contents": "{\"package\": \"200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47\", \"files\": {}}",
"dest": "cargo/vendor/icu_collections-2.0.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/icu_locale_core/icu_locale_core-2.0.0.crate",
"sha256": "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a",
"dest": "cargo/vendor/icu_locale_core-2.0.0"
},
{
"type": "inline",
"contents": "{\"package\": \"0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a\", \"files\": {}}",
"dest": "cargo/vendor/icu_locale_core-2.0.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/icu_normalizer/icu_normalizer-2.0.0.crate",
"sha256": "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979",
"dest": "cargo/vendor/icu_normalizer-2.0.0"
},
{
"type": "inline",
"contents": "{\"package\": \"436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979\", \"files\": {}}",
"dest": "cargo/vendor/icu_normalizer-2.0.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/icu_normalizer_data/icu_normalizer_data-2.0.0.crate",
"sha256": "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3",
"dest": "cargo/vendor/icu_normalizer_data-2.0.0"
},
{
"type": "inline",
"contents": "{\"package\": \"00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3\", \"files\": {}}",
"dest": "cargo/vendor/icu_normalizer_data-2.0.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/icu_properties/icu_properties-2.0.1.crate",
"sha256": "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b",
"dest": "cargo/vendor/icu_properties-2.0.1"
},
{
"type": "inline",
"contents": "{\"package\": \"016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b\", \"files\": {}}",
"dest": "cargo/vendor/icu_properties-2.0.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/icu_properties_data/icu_properties_data-2.0.1.crate",
"sha256": "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632",
"dest": "cargo/vendor/icu_properties_data-2.0.1"
},
{
"type": "inline",
"contents": "{\"package\": \"298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632\", \"files\": {}}",
"dest": "cargo/vendor/icu_properties_data-2.0.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/icu_provider/icu_provider-2.0.0.crate",
"sha256": "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af",
"dest": "cargo/vendor/icu_provider-2.0.0"
},
{
"type": "inline",
"contents": "{\"package\": \"03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af\", \"files\": {}}",
"dest": "cargo/vendor/icu_provider-2.0.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -1726,6 +1908,32 @@
"dest": "cargo/vendor/ident_case-1.0.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/idna/idna-1.1.0.crate",
"sha256": "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de",
"dest": "cargo/vendor/idna-1.1.0"
},
{
"type": "inline",
"contents": "{\"package\": \"3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de\", \"files\": {}}",
"dest": "cargo/vendor/idna-1.1.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/idna_adapter/idna_adapter-1.2.1.crate",
"sha256": "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344",
"dest": "cargo/vendor/idna_adapter-1.2.1"
},
{
"type": "inline",
"contents": "{\"package\": \"3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344\", \"files\": {}}",
"dest": "cargo/vendor/idna_adapter-1.2.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -2038,6 +2246,32 @@
"dest": "cargo/vendor/linux-raw-sys-0.11.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/litemap/litemap-0.8.0.crate",
"sha256": "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956",
"dest": "cargo/vendor/litemap-0.8.0"
},
{
"type": "inline",
"contents": "{\"package\": \"241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956\", \"files\": {}}",
"dest": "cargo/vendor/litemap-0.8.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/litrs/litrs-0.4.2.crate",
"sha256": "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed",
"dest": "cargo/vendor/litrs-0.4.2"
},
{
"type": "inline",
"contents": "{\"package\": \"f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed\", \"files\": {}}",
"dest": "cargo/vendor/litrs-0.4.2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -2480,6 +2714,19 @@
"dest": "cargo/vendor/pciid-parser-0.8.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/percent-encoding/percent-encoding-2.3.2.crate",
"sha256": "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220",
"dest": "cargo/vendor/percent-encoding-2.3.2"
},
{
"type": "inline",
"contents": "{\"package\": \"9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220\", \"files\": {}}",
"dest": "cargo/vendor/percent-encoding-2.3.2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -2558,6 +2805,19 @@
"dest": "cargo/vendor/plotters-cairo-0.8.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/potential_utf/potential_utf-0.1.3.crate",
"sha256": "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a",
"dest": "cargo/vendor/potential_utf-0.1.3"
},
{
"type": "inline",
"contents": "{\"package\": \"84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a\", \"files\": {}}",
"dest": "cargo/vendor/potential_utf-0.1.3",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -2792,6 +3052,19 @@
"dest": "cargo/vendor/relm4-macros-0.10.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/ring/ring-0.17.14.crate",
"sha256": "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7",
"dest": "cargo/vendor/ring-0.17.14"
},
{
"type": "inline",
"contents": "{\"package\": \"a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7\", \"files\": {}}",
"dest": "cargo/vendor/ring-0.17.14",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -2896,6 +3169,58 @@
"dest": "cargo/vendor/rustix-1.1.2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/rustls/rustls-0.23.32.crate",
"sha256": "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40",
"dest": "cargo/vendor/rustls-0.23.32"
},
{
"type": "inline",
"contents": "{\"package\": \"cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40\", \"files\": {}}",
"dest": "cargo/vendor/rustls-0.23.32",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/rustls-pemfile/rustls-pemfile-2.2.0.crate",
"sha256": "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50",
"dest": "cargo/vendor/rustls-pemfile-2.2.0"
},
{
"type": "inline",
"contents": "{\"package\": \"dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50\", \"files\": {}}",
"dest": "cargo/vendor/rustls-pemfile-2.2.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/rustls-pki-types/rustls-pki-types-1.12.0.crate",
"sha256": "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79",
"dest": "cargo/vendor/rustls-pki-types-1.12.0"
},
{
"type": "inline",
"contents": "{\"package\": \"229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79\", \"files\": {}}",
"dest": "cargo/vendor/rustls-pki-types-1.12.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/rustls-webpki/rustls-webpki-0.103.7.crate",
"sha256": "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf",
"dest": "cargo/vendor/rustls-webpki-0.103.7"
},
{
"type": "inline",
"contents": "{\"package\": \"e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf\", \"files\": {}}",
"dest": "cargo/vendor/rustls-webpki-0.103.7",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -3143,6 +3468,19 @@
"dest": "cargo/vendor/signal-hook-registry-1.4.6",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/simd-adler32/simd-adler32-0.3.7.crate",
"sha256": "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe",
"dest": "cargo/vendor/simd-adler32-0.3.7"
},
{
"type": "inline",
"contents": "{\"package\": \"d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe\", \"files\": {}}",
"dest": "cargo/vendor/simd-adler32-0.3.7",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -3208,6 +3546,19 @@
"dest": "cargo/vendor/spin-0.9.8",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/stable_deref_trait/stable_deref_trait-1.2.0.crate",
"sha256": "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3",
"dest": "cargo/vendor/stable_deref_trait-1.2.0"
},
{
"type": "inline",
"contents": "{\"package\": \"a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3\", \"files\": {}}",
"dest": "cargo/vendor/stable_deref_trait-1.2.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -3234,6 +3585,19 @@
"dest": "cargo/vendor/strsim-0.11.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/subtle/subtle-2.6.1.crate",
"sha256": "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292",
"dest": "cargo/vendor/subtle-2.6.1"
},
{
"type": "inline",
"contents": "{\"package\": \"13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292\", \"files\": {}}",
"dest": "cargo/vendor/subtle-2.6.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -3247,6 +3611,19 @@
"dest": "cargo/vendor/syn-2.0.106",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/synstructure/synstructure-0.13.2.crate",
"sha256": "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2",
"dest": "cargo/vendor/synstructure-0.13.2"
},
{
"type": "inline",
"contents": "{\"package\": \"728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2\", \"files\": {}}",
"dest": "cargo/vendor/synstructure-0.13.2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -3728,6 +4105,84 @@
"dest": "cargo/vendor/unsafe-libyaml-norway-0.2.15",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/untrusted/untrusted-0.9.0.crate",
"sha256": "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1",
"dest": "cargo/vendor/untrusted-0.9.0"
},
{
"type": "inline",
"contents": "{\"package\": \"8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1\", \"files\": {}}",
"dest": "cargo/vendor/untrusted-0.9.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/ureq/ureq-3.1.2.crate",
"sha256": "99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537",
"dest": "cargo/vendor/ureq-3.1.2"
},
{
"type": "inline",
"contents": "{\"package\": \"99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537\", \"files\": {}}",
"dest": "cargo/vendor/ureq-3.1.2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/ureq-proto/ureq-proto-0.5.2.crate",
"sha256": "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2",
"dest": "cargo/vendor/ureq-proto-0.5.2"
},
{
"type": "inline",
"contents": "{\"package\": \"60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2\", \"files\": {}}",
"dest": "cargo/vendor/ureq-proto-0.5.2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/url/url-2.5.7.crate",
"sha256": "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b",
"dest": "cargo/vendor/url-2.5.7"
},
{
"type": "inline",
"contents": "{\"package\": \"08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b\", \"files\": {}}",
"dest": "cargo/vendor/url-2.5.7",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/utf-8/utf-8-0.7.6.crate",
"sha256": "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9",
"dest": "cargo/vendor/utf-8-0.7.6"
},
{
"type": "inline",
"contents": "{\"package\": \"09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9\", \"files\": {}}",
"dest": "cargo/vendor/utf-8-0.7.6",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/utf8_iter/utf8_iter-1.0.4.crate",
"sha256": "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be",
"dest": "cargo/vendor/utf8_iter-1.0.4"
},
{
"type": "inline",
"contents": "{\"package\": \"b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be\", \"files\": {}}",
"dest": "cargo/vendor/utf8_iter-1.0.4",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -3923,6 +4378,19 @@
"dest": "cargo/vendor/web-sys-0.3.78",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/webpki-roots/webpki-roots-1.0.3.crate",
"sha256": "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8",
"dest": "cargo/vendor/webpki-roots-1.0.3"
},
{
"type": "inline",
"contents": "{\"package\": \"32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8\", \"files\": {}}",
"dest": "cargo/vendor/webpki-roots-1.0.3",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -4495,6 +4963,19 @@
"dest": "cargo/vendor/wrapcenum-derive-0.4.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/writeable/writeable-0.6.1.crate",
"sha256": "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb",
"dest": "cargo/vendor/writeable-0.6.1"
},
{
"type": "inline",
"contents": "{\"package\": \"ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb\", \"files\": {}}",
"dest": "cargo/vendor/writeable-0.6.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -4521,6 +5002,32 @@
"dest": "cargo/vendor/yansi-1.0.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/yoke/yoke-0.8.0.crate",
"sha256": "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc",
"dest": "cargo/vendor/yoke-0.8.0"
},
{
"type": "inline",
"contents": "{\"package\": \"5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc\", \"files\": {}}",
"dest": "cargo/vendor/yoke-0.8.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/yoke-derive/yoke-derive-0.8.0.crate",
"sha256": "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6",
"dest": "cargo/vendor/yoke-derive-0.8.0"
},
{
"type": "inline",
"contents": "{\"package\": \"38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6\", \"files\": {}}",
"dest": "cargo/vendor/yoke-derive-0.8.0",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -4599,6 +5106,45 @@
"dest": "cargo/vendor/zerofrom-0.1.6",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/zerofrom-derive/zerofrom-derive-0.1.6.crate",
"sha256": "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502",
"dest": "cargo/vendor/zerofrom-derive-0.1.6"
},
{
"type": "inline",
"contents": "{\"package\": \"d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502\", \"files\": {}}",
"dest": "cargo/vendor/zerofrom-derive-0.1.6",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/zeroize/zeroize-1.8.2.crate",
"sha256": "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0",
"dest": "cargo/vendor/zeroize-1.8.2"
},
{
"type": "inline",
"contents": "{\"package\": \"b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0\", \"files\": {}}",
"dest": "cargo/vendor/zeroize-1.8.2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/zerotrie/zerotrie-0.2.2.crate",
"sha256": "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595",
"dest": "cargo/vendor/zerotrie-0.2.2"
},
{
"type": "inline",
"contents": "{\"package\": \"36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595\", \"files\": {}}",
"dest": "cargo/vendor/zerotrie-0.2.2",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
@@ -4612,6 +5158,19 @@
"dest": "cargo/vendor/zerovec-0.11.4",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
"url": "https://static.crates.io/crates/zerovec-derive/zerovec-derive-0.11.1.crate",
"sha256": "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f",
"dest": "cargo/vendor/zerovec-derive-0.11.1"
},
{
"type": "inline",
"contents": "{\"package\": \"5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f\", \"files\": {}}",
"dest": "cargo/vendor/zerovec-derive-0.11.1",
"dest-filename": ".cargo-checksum.json"
},
{
"type": "archive",
"archive-type": "tar-gzip",
+2
View File
@@ -50,6 +50,8 @@ cl3 = { git = "https://github.com/kenba/cl3", branch = "develop", features = [
"static",
] }
ureq = { version = "3.1.2", features = ["json", "rustls"] }
[dev-dependencies]
pretty_assertions = { workspace = true }
insta = { version = "1.41.1", features = ["json", "yaml"] }
+13
View File
@@ -68,6 +68,7 @@ pub struct Daemon {
pub disable_clocks_cleanup: bool,
pub disable_nvapi: Option<bool>,
pub tcp_listen_address: Option<String>,
pub metrics: Option<Metrics>,
}
impl Default for Daemon {
@@ -88,6 +89,7 @@ impl Default for Daemon {
disable_clocks_cleanup: false,
tcp_listen_address: None,
disable_nvapi: None,
metrics: None,
}
}
}
@@ -278,6 +280,17 @@ impl Config {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Metrics {
pub collector_address: String,
#[serde(default = "default_metrics_interval")]
pub interval: u64,
}
fn default_metrics_interval() -> u64 {
30
}
pub fn start_watcher(config_last_saved: Rc<Cell<Instant>>) -> mpsc::UnboundedReceiver<Config> {
let (config_tx, config_rx) = mpsc::unbounded_channel();
let (event_tx, mut event_rx) = mpsc::channel(64);
+8 -1
View File
@@ -1,5 +1,6 @@
pub mod gpu_controller;
pub mod handler;
mod metrics;
mod opencl;
mod profiles;
mod vulkan;
@@ -15,7 +16,7 @@ use tokio::{
io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader},
net::{TcpListener, UnixListener},
};
use tracing::{error, info, instrument, trace};
use tracing::{debug, error, info, instrument, trace};
pub struct Server {
pub handler: Handler,
@@ -49,6 +50,12 @@ impl Server {
socket::cleanup();
})?;
if let Some(metrics_config) = handler.config.read().await.daemon.metrics.clone() {
metrics::setup(handler.clone(), metrics_config);
} else {
debug!("metrics exporter disabled");
}
Ok(Self {
handler,
unix_listener,
+345
View File
@@ -0,0 +1,345 @@
mod schema;
use crate::{
config,
server::{handler::Handler, metrics::schema::NumberValue},
};
use chrono::Local;
use indexmap::IndexMap;
use lact_schema::DeviceStats;
use schema::{
Attribute, Gauge, GaugeDataPoint, Metric, MetricsPayload, Resource, ResourceMetric, Scope,
ScopeMetric, Value,
};
use std::time::Duration;
use tokio::time::sleep;
use tracing::{debug, error, info};
pub fn setup(handler: Handler, config: config::Metrics) {
info!(
"exporting metrics to {} every {} seconds",
config.collector_address, config.interval
);
let interval = Duration::from_secs(config.interval);
tokio::task::spawn_local(async move {
let client = ureq::config::Config::builder()
.http_status_as_error(false)
.build()
.new_agent();
loop {
sleep(interval).await;
match get_stats(&handler).await {
Ok(devices) => {
debug!("collecting metrics for {} devices", devices.len());
let mut metrics = Vec::with_capacity(10);
let timestamp = Local::now()
.timestamp_nanos_opt()
.expect("Invalid timestamp")
.to_string();
for (gpu_id, (gpu_name, stats)) in &devices {
collect_metrics(gpu_id, gpu_name, stats, &mut metrics, &timestamp);
}
let metric_count = metrics.len();
let request = MetricsPayload {
resource_metrics: vec![ResourceMetric {
resource: Resource {
attributes: vec![Attribute {
key: "service.name",
value: Value::String("LACT"),
}],
},
scope_metrics: vec![ScopeMetric {
scope: Scope {
name: "LACT".to_owned(),
version: env!("CARGO_PKG_VERSION").to_owned(),
attributes: vec![],
},
metrics,
}],
}],
};
let url = config.collector_address.clone();
let client = client.clone();
let payload =
serde_json::to_string(&request).expect("Could not serialize request body");
let response = tokio::task::spawn_blocking(move || {
client
.post(url)
.content_type("application/json")
.send(payload)
})
.await
.unwrap();
match response {
Ok(response) => {
if response.status().is_success() {
debug!("{metric_count} metrics were uploaded");
} else {
let status = response.status();
let body = response
.into_body()
.read_to_string()
.unwrap_or_else(|_| "<Invalid string>".to_owned());
debug!("could not submit metrics, status code {status}: {body}");
}
}
Err(err) => error!("could not submit metrics: {err}"),
}
}
Err(err) => error!("could not fetch metrics: {err:#}"),
}
}
});
}
#[allow(
clippy::cast_possible_wrap,
clippy::cast_precision_loss,
clippy::too_many_lines
)]
fn collect_metrics<'a>(
gpu_id: &'a str,
gpu_name: &'a str,
stats: &'a DeviceStats,
metrics: &mut Vec<Metric<'a>>,
timestamp: &'a str,
) {
let base_attrs = vec![
Attribute {
key: "gpu_id",
value: Value::String(gpu_id),
},
Attribute {
key: "gpu_name",
value: Value::String(gpu_name),
},
];
if let Some(usage) = stats.busy_percent {
metrics.push(make_metric(
"lact_gpu_usage",
i64::from(usage).into(),
"%",
"Current GPU usage",
timestamp,
base_attrs.clone(),
));
}
if let Some(power) = stats.power.current.or(stats.power.average) {
metrics.push(make_metric(
"lact_gpu_power_usage",
power.into(),
"W",
"Current power usage",
timestamp,
base_attrs.clone(),
));
}
if let Some(power_cap) = stats.power.cap_current {
metrics.push(make_metric(
"lact_gpu_power_usage_cap",
power_cap.into(),
"W",
"Maximum configured power usage",
timestamp,
base_attrs.clone(),
));
}
for (key, temp) in &stats.temps {
let mut temp_attrs = base_attrs.clone();
temp_attrs.push(Attribute {
key: "sensor",
value: Value::String(key),
});
if let Some(current) = temp.current {
metrics.push(make_metric(
"lact_gpu_temperature",
f64::from(current).into(),
"Cel",
"Current temperature",
timestamp,
temp_attrs.clone(),
));
}
if let Some(crit) = temp.crit {
metrics.push(make_metric(
"lact_gpu_temperature_crit",
f64::from(crit).into(),
"Cel",
"Critical temperature",
timestamp,
temp_attrs.clone(),
));
}
}
if let Some(gpu_clock) = stats.clockspeed.gpu_clockspeed {
let mut attrs = base_attrs.clone();
attrs.push(Attribute {
key: "clockspeed_type",
value: Value::String("gpu_current"),
});
metrics.push(make_metric(
"lact_gpu_clockspeed",
(gpu_clock as i64 * 1000 * 1000).into(),
"Hz",
"Current clockspeed",
timestamp,
attrs,
));
}
if let Some(gpu_target_clock) = stats.clockspeed.current_gfxclk {
let mut attrs = base_attrs.clone();
attrs.push(Attribute {
key: "clockspeed_type",
value: Value::String("gpu_target"),
});
metrics.push(make_metric(
"lact_gpu_clockspeed",
(gpu_target_clock as i64 * 1000 * 1000).into(),
"Hz",
"Current clockspeed",
timestamp,
attrs,
));
}
if let Some(vram_clock) = stats.clockspeed.vram_clockspeed {
let mut attrs = base_attrs.clone();
attrs.push(Attribute {
key: "clockspeed_type",
value: Value::String("memory"),
});
metrics.push(make_metric(
"lact_gpu_clockspeed",
(vram_clock as i64 * 1000 * 1000).into(),
"Hz",
"Current clockspeed",
timestamp,
attrs,
));
}
if let Some(gpu_voltage) = stats.voltage.gpu {
metrics.push(make_metric(
"lact_gpu_voltage",
(gpu_voltage as f64 * 1000.0).into(),
"V",
"Current voltage",
timestamp,
base_attrs.clone(),
));
}
if let Some(fan_rpm) = stats.fan.speed_current {
metrics.push(make_metric(
"lact_gpu_fan_speed",
i64::from(fan_rpm).into(),
"RPM",
"Current fan speed (RPM)",
timestamp,
base_attrs.clone(),
));
}
if let Some(fan_rpm_max) = stats.fan.speed_max {
metrics.push(make_metric(
"lact_gpu_fan_speed_max",
i64::from(fan_rpm_max).into(),
"RPM",
"Maximum fan speed (RPM)",
timestamp,
base_attrs.clone(),
));
}
if let Some(fan_pwm) = stats.fan.pwm_current {
let percent = f64::from(fan_pwm) / f64::from(u8::MAX) * 100.0;
metrics.push(make_metric(
"lact_gpu_fan_percent",
percent.into(),
"%",
"Current fan speed (%)",
timestamp,
base_attrs.clone(),
));
}
if let Some(vram_used) = stats.vram.used {
metrics.push(make_metric(
"lact_gpu_vram_used",
(vram_used as i64).into(),
"By",
"Current VRAM usage",
timestamp,
base_attrs.clone(),
));
}
if let Some(vram_total) = stats.vram.total {
metrics.push(make_metric(
"lact_gpu_vram_total",
(vram_total as i64).into(),
"By",
"Total VRAM available",
timestamp,
base_attrs.clone(),
));
}
}
fn make_metric<'a>(
name: &'static str,
value: NumberValue,
unit: &'static str,
description: &'static str,
timestamp: &'a str,
attrs: Vec<Attribute<'a>>,
) -> Metric<'a> {
Metric {
name,
unit,
description,
gauge: Gauge {
data_points: vec![GaugeDataPoint {
value,
time_unix_nano: timestamp,
attributes: attrs,
}],
},
}
}
async fn get_stats(handler: &Handler) -> anyhow::Result<IndexMap<String, (String, DeviceStats)>> {
let mut devices = IndexMap::new();
let device_list = handler.list_devices().await;
for device in device_list {
let stats = handler.get_gpu_stats(&device.id).await?;
devices.insert(device.id, (device.name.unwrap_or_default(), stats));
}
Ok(devices)
}
+99
View File
@@ -0,0 +1,99 @@
use serde::Serialize;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MetricsPayload<'a> {
pub resource_metrics: Vec<ResourceMetric<'a>>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourceMetric<'a> {
pub resource: Resource<'a>,
pub scope_metrics: Vec<ScopeMetric<'a>>,
}
#[derive(Serialize)]
pub struct Resource<'a> {
pub attributes: Vec<Attribute<'a>>,
}
#[derive(Serialize)]
pub struct ScopeMetric<'a> {
pub scope: Scope<'a>,
pub metrics: Vec<Metric<'a>>,
}
#[derive(Serialize)]
pub struct Scope<'a> {
pub name: String,
pub version: String,
pub attributes: Vec<Attribute<'a>>,
}
#[derive(Serialize, Clone)]
pub struct Attribute<'a> {
pub key: &'static str,
pub value: Value<'a>,
}
#[derive(Serialize)]
pub struct Metric<'a> {
pub name: &'static str,
pub unit: &'static str,
pub description: &'static str,
pub gauge: Gauge<'a>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Gauge<'a> {
pub data_points: Vec<GaugeDataPoint<'a>>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GaugeDataPoint<'a> {
#[serde(flatten)]
pub value: NumberValue,
pub time_unix_nano: &'a str,
pub attributes: Vec<Attribute<'a>>,
}
#[derive(Serialize)]
pub enum NumberValue {
#[serde(rename = "asDouble")]
Float(f64),
#[serde(rename = "asInt")]
Int(i64),
}
impl From<f64> for NumberValue {
fn from(value: f64) -> Self {
Self::Float(value)
}
}
impl From<i64> for NumberValue {
fn from(value: i64) -> Self {
Self::Int(value)
}
}
#[derive(Serialize, Clone)]
pub enum Value<'a> {
#[serde(rename = "stringValue")]
String(&'a str),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize_value() {
let value = Value::String("asd");
let expected_result = r#"{"stringValue":"asd"}"#;
assert_eq!(expected_result, serde_json::to_string(&value).unwrap());
}
}
@@ -10,6 +10,9 @@ daemon:
disable_clocks_cleanup: false
disable_nvapi: false
tcp_listen_address: "127.0.0.1:12853"
metrics:
collector_address: "http://localhost:9090/api/v1/otlp/v1/metrics"
interval: 30
apply_settings_timer: 5
gpus:
"1002:687F-1043:0555-0000:0b:00.0":
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB