commit
01b42998a7
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-18.04, ubuntu-20.04]
|
os: [ ubuntu-18.04, ubuntu-20.04 ]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
154
Cargo.lock
generated
154
Cargo.lock
generated
@ -13,9 +13,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "amdfand"
|
name = "amdfand"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"amdgpu",
|
"amdgpu",
|
||||||
|
"amdgpu-config",
|
||||||
"gumdrop",
|
"gumdrop",
|
||||||
"log",
|
"log",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
@ -26,7 +27,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "amdgpu"
|
name = "amdgpu"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gumdrop",
|
"gumdrop",
|
||||||
"log",
|
"log",
|
||||||
@ -37,10 +38,41 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "amdvold"
|
name = "amdgpu-config"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"amdgpu",
|
"amdgpu",
|
||||||
|
"csv",
|
||||||
|
"gumdrop",
|
||||||
|
"log",
|
||||||
|
"pretty_env_logger",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "amdmond"
|
||||||
|
version = "1.0.8"
|
||||||
|
dependencies = [
|
||||||
|
"amdgpu",
|
||||||
|
"amdgpu-config",
|
||||||
|
"chrono",
|
||||||
|
"csv",
|
||||||
|
"gumdrop",
|
||||||
|
"log",
|
||||||
|
"pretty_env_logger",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "amdvold"
|
||||||
|
version = "1.0.8"
|
||||||
|
dependencies = [
|
||||||
|
"amdgpu",
|
||||||
|
"amdgpu-config",
|
||||||
"gumdrop",
|
"gumdrop",
|
||||||
"log",
|
"log",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
@ -60,12 +92,66 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"time",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "csv"
|
||||||
|
version = "1.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
|
||||||
|
dependencies = [
|
||||||
|
"bstr",
|
||||||
|
"csv-core",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "csv-core"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@ -117,6 +203,18 @@ dependencies = [
|
|||||||
"quick-error",
|
"quick-error",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.97"
|
version = "0.2.97"
|
||||||
@ -138,6 +236,25 @@ version = "2.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_env_logger"
|
name = "pretty_env_logger"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -183,12 +300,24 @@ dependencies = [
|
|||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.25"
|
version = "0.6.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.126"
|
version = "1.0.126"
|
||||||
@ -249,6 +378,17 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.5.8"
|
version = "0.5.8"
|
||||||
@ -264,6 +404,12 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["amdfand", "amdgpu", "amdvold"]
|
members = ["amdgpu", "amdgpu-config", "amdfand", "amdvold", "amdmond"]
|
||||||
|
25
LICENSE.md
25
LICENSE.md
@ -2,20 +2,15 @@ MIT License
|
|||||||
|
|
||||||
Copyright (c) 2021 Adrian Woźniak
|
Copyright (c) 2021 Adrian Woźniak
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
||||||
in the Software without restriction, including without limitation the rights
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
||||||
copies or substantial portions of the Software.
|
Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
72
README.md
72
README.md
@ -1,74 +1,17 @@
|
|||||||
![GitHub](https://img.shields.io/github/license/Eraden/amdgpud)
|
![GitHub](https://img.shields.io/github/license/Eraden/amdgpud)
|
||||||
|
|
||||||
# AMDGPU Fan control service
|
# AMD GPU management tools
|
||||||
|
|
||||||
Available commands:
|
This repository holds couple tools for AMD graphic cards
|
||||||
|
|
||||||
* `monitor` - Print current temp and fan speed
|
* `amdfand` - fan speed daemon
|
||||||
* `service` - Set fan speed depends on GPU temperature
|
* `amdvold` - voltage and overclocking tool
|
||||||
* `set-automatic` - Switch to GPU automatic fan speed control
|
* `amdmond` - monitor daemon
|
||||||
* `set-manual` - Switch to GPU manual fan speed control
|
|
||||||
* `available` - Print available cards
|
|
||||||
|
|
||||||
#### amdfand set-automatic | set-manual [OPTIONS]
|
For more information please check README each of them.
|
||||||
|
|
||||||
Optional arguments:
|
|
||||||
|
|
||||||
* -h, --help Help message
|
|
||||||
* -c, --card CARD GPU Card number
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo install argonfand
|
|
||||||
|
|
||||||
sudo argonfand monitor # print current temperature, current fan speed, min and max fan speed
|
|
||||||
sudo argonfand service # check amdgpu temperature and adjust speed from config file
|
|
||||||
```
|
|
||||||
|
|
||||||
## Config file
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# /etc/amdfand/config.toml
|
|
||||||
log_level = "Error"
|
|
||||||
temp_input = "temp1_input"
|
|
||||||
|
|
||||||
[[speed_matrix]]
|
|
||||||
temp = 4.0
|
|
||||||
speed = 4.0
|
|
||||||
|
|
||||||
[[speed_matrix]]
|
|
||||||
temp = 30.0
|
|
||||||
speed = 33.0
|
|
||||||
|
|
||||||
[[speed_matrix]]
|
|
||||||
temp = 45.0
|
|
||||||
speed = 50.0
|
|
||||||
|
|
||||||
[[speed_matrix]]
|
|
||||||
temp = 60.0
|
|
||||||
speed = 66.0
|
|
||||||
|
|
||||||
[[speed_matrix]]
|
|
||||||
temp = 65.0
|
|
||||||
speed = 69.0
|
|
||||||
|
|
||||||
[[speed_matrix]]
|
|
||||||
temp = 70.0
|
|
||||||
speed = 75.0
|
|
||||||
|
|
||||||
[[speed_matrix]]
|
|
||||||
temp = 75.0
|
|
||||||
speed = 89.0
|
|
||||||
|
|
||||||
[[speed_matrix]]
|
|
||||||
temp = 80.0
|
|
||||||
speed = 100.0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
* [ ] Write metrics
|
|
||||||
* [ ] Add support for multiple cards
|
* [ ] Add support for multiple cards
|
||||||
* Multiple services must recognize card even if there's multiple same version cards is installed
|
* Multiple services must recognize card even if there's multiple same version cards is installed
|
||||||
* Support should be by using `--config` option
|
* Support should be by using `--config` option
|
||||||
@ -78,5 +21,4 @@ speed = 100.0
|
|||||||
|
|
||||||
## :bookmark: License
|
## :bookmark: License
|
||||||
|
|
||||||
This work is dual-licensed under Apache 2.0 and MIT.
|
This work is dual-licensed under Apache 2.0 and MIT. You can choose between one of them if you use this work.
|
||||||
You can choose between one of them if you use this work.
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "amdfand"
|
name = "amdfand"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "AMDGPU fan control service"
|
description = "AMDGPU fan control service"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@ -9,7 +9,8 @@ categories = ["hardware-support"]
|
|||||||
repository = "https://github.com/Eraden/amdgpud"
|
repository = "https://github.com/Eraden/amdgpud"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
amdgpu = { path = "../amdgpu", version = "1.0.7" }
|
amdgpu = { path = "../amdgpu", version = "1.0.8" }
|
||||||
|
amdgpu-config = { path = "../amdgpu-config", version = "1.0.8", features = ["fan"] }
|
||||||
|
|
||||||
serde = { version = "1.0.126", features = ["derive"] }
|
serde = { version = "1.0.126", features = ["derive"] }
|
||||||
toml = { version = "0.5.8" }
|
toml = { version = "0.5.8" }
|
||||||
@ -21,3 +22,4 @@ pretty_env_logger = { version = "0.4.0" }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
amdgpu = { path = "../amdgpu", version = "1.0" }
|
amdgpu = { path = "../amdgpu", version = "1.0" }
|
||||||
|
amdgpu-config = { path = "../amdgpu-config", version = "1.0", features = ["fan"] }
|
||||||
|
66
amdfand/README.md
Normal file
66
amdfand/README.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
![GitHub](https://img.shields.io/github/license/Eraden/amdgpud)
|
||||||
|
|
||||||
|
# AMDGPU Fan control service
|
||||||
|
|
||||||
|
Available commands:
|
||||||
|
|
||||||
|
* `service` - Set fan speed depends on GPU temperature
|
||||||
|
* `set-automatic` - Switch to GPU automatic fan speed control
|
||||||
|
* `set-manual` - Switch to GPU manual fan speed control
|
||||||
|
* `available` - Print available cards
|
||||||
|
|
||||||
|
#### amdfand set-automatic | set-manual [OPTIONS]
|
||||||
|
|
||||||
|
Optional arguments:
|
||||||
|
|
||||||
|
* -h, --help Help message
|
||||||
|
* -c, --card CARD GPU Card number
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install argonfand
|
||||||
|
|
||||||
|
sudo argonfand monitor # print current temperature, current fan speed, min and max fan speed
|
||||||
|
sudo argonfand service # check amdgpu temperature and adjust speed from config file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Config file
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# /etc/amdfand/config.toml
|
||||||
|
log_level = "Error"
|
||||||
|
temp_input = "temp1_input"
|
||||||
|
|
||||||
|
[[speed_matrix]]
|
||||||
|
temp = 4.0
|
||||||
|
speed = 4.0
|
||||||
|
|
||||||
|
[[speed_matrix]]
|
||||||
|
temp = 30.0
|
||||||
|
speed = 33.0
|
||||||
|
|
||||||
|
[[speed_matrix]]
|
||||||
|
temp = 45.0
|
||||||
|
speed = 50.0
|
||||||
|
|
||||||
|
[[speed_matrix]]
|
||||||
|
temp = 60.0
|
||||||
|
speed = 66.0
|
||||||
|
|
||||||
|
[[speed_matrix]]
|
||||||
|
temp = 65.0
|
||||||
|
speed = 69.0
|
||||||
|
|
||||||
|
[[speed_matrix]]
|
||||||
|
temp = 70.0
|
||||||
|
speed = 75.0
|
||||||
|
|
||||||
|
[[speed_matrix]]
|
||||||
|
temp = 75.0
|
||||||
|
speed = 89.0
|
||||||
|
|
||||||
|
[[speed_matrix]]
|
||||||
|
temp = 80.0
|
||||||
|
speed = 100.0
|
||||||
|
```
|
@ -1,8 +1,9 @@
|
|||||||
use crate::command::Fan;
|
|
||||||
use amdgpu::utils::hw_mons;
|
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
|
|
||||||
use crate::config::Config;
|
use amdgpu::utils::hw_mons;
|
||||||
|
use amdgpu_config::fan::Config;
|
||||||
|
|
||||||
|
use crate::command::Fan;
|
||||||
use crate::{AmdFanError, FanMode};
|
use crate::{AmdFanError, FanMode};
|
||||||
|
|
||||||
/// Change card fan mode to either automatic or manual
|
/// Change card fan mode to either automatic or manual
|
||||||
|
@ -1,23 +1,14 @@
|
|||||||
use crate::{change_mode, monitor, service, Config};
|
|
||||||
use amdgpu::hw_mon::HwMon;
|
|
||||||
use amdgpu::utils::linear_map;
|
|
||||||
use amdgpu::TempInput;
|
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
|
|
||||||
/// pulse width modulation fan control minimum level (0)
|
use amdgpu::hw_mon::HwMon;
|
||||||
const PULSE_WIDTH_MODULATION_MIN: &str = "pwm1_min";
|
use amdgpu::utils::{linear_map, load_temp_inputs};
|
||||||
|
use amdgpu::{
|
||||||
|
utils, TempInput, PULSE_WIDTH_MODULATION_AUTO, PULSE_WIDTH_MODULATION_MAX,
|
||||||
|
PULSE_WIDTH_MODULATION_MIN, PULSE_WIDTH_MODULATION_MODE,
|
||||||
|
};
|
||||||
|
use amdgpu_config::fan::Config;
|
||||||
|
|
||||||
/// pulse width modulation fan control maximum level (255)
|
use crate::{change_mode, service};
|
||||||
const PULSE_WIDTH_MODULATION_MAX: &str = "pwm1_max";
|
|
||||||
|
|
||||||
/// pulse width modulation fan level (0-255)
|
|
||||||
const PULSE_WIDTH_MODULATION: &str = "pwm1";
|
|
||||||
|
|
||||||
/// pulse width modulation fan control method (0: no fan speed control, 1: manual fan speed control using pwm interface, 2: automatic fan speed control)
|
|
||||||
const PULSE_WIDTH_MODULATION_MODE: &str = "pwm1_enable";
|
|
||||||
|
|
||||||
// static PULSE_WIDTH_MODULATION_DISABLED: &str = "0";
|
|
||||||
const PULSE_WIDTH_MODULATION_AUTO: &str = "2";
|
|
||||||
|
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Options)]
|
||||||
pub struct AvailableCards {
|
pub struct AvailableCards {
|
||||||
@ -27,8 +18,6 @@ pub struct AvailableCards {
|
|||||||
|
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Options)]
|
||||||
pub enum FanCommand {
|
pub enum FanCommand {
|
||||||
#[options(help = "Print current temperature and fan speed")]
|
|
||||||
Monitor(monitor::Monitor),
|
|
||||||
#[options(help = "Check AMD GPU temperature and change fan speed depends on configuration")]
|
#[options(help = "Check AMD GPU temperature and change fan speed depends on configuration")]
|
||||||
Service(service::Service),
|
Service(service::Service),
|
||||||
#[options(help = "Switch GPU to automatic fan speed control")]
|
#[options(help = "Switch GPU to automatic fan speed control")]
|
||||||
@ -48,11 +37,14 @@ pub enum FanError {
|
|||||||
#[error("Failed to read AMD GPU temperatures from tempX_input. No input was found")]
|
#[error("Failed to read AMD GPU temperatures from tempX_input. No input was found")]
|
||||||
EmptyTempSet,
|
EmptyTempSet,
|
||||||
#[error("Unable to change fan speed to manual mode. {0}")]
|
#[error("Unable to change fan speed to manual mode. {0}")]
|
||||||
ManualSpeedFailed(std::io::Error),
|
ManualSpeedFailed(utils::AmdGpuError),
|
||||||
#[error("Unable to change fan speed to automatic mode. {0}")]
|
#[error("Unable to change fan speed to automatic mode. {0}")]
|
||||||
AutomaticSpeedFailed(std::io::Error),
|
AutomaticSpeedFailed(utils::AmdGpuError),
|
||||||
#[error("Unable to change AMD GPU modulation (a.k.a. speed) to {value}. {error}")]
|
#[error("Unable to change AMD GPU modulation (a.k.a. speed) to {value}. {error}")]
|
||||||
FailedToChangeSpeed { value: u64, error: std::io::Error },
|
FailedToChangeSpeed {
|
||||||
|
value: u64,
|
||||||
|
error: utils::AmdGpuError,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Fan {
|
pub struct Fan {
|
||||||
@ -81,6 +73,8 @@ impl std::ops::DerefMut for Fan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MODULATION_ENABLED_FILE: &str = "pwm1_enable";
|
||||||
|
|
||||||
impl Fan {
|
impl Fan {
|
||||||
pub fn wrap(hw_mon: HwMon, config: &Config) -> Self {
|
pub fn wrap(hw_mon: HwMon, config: &Config) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -105,7 +99,7 @@ impl Fan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn write_manual(&self) -> crate::Result<()> {
|
pub(crate) fn write_manual(&self) -> crate::Result<()> {
|
||||||
self.hw_mon_write("pwm1_enable", 1)
|
self.hw_mon_write(MODULATION_ENABLED_FILE, 1)
|
||||||
.map_err(FanError::ManualSpeedFailed)?;
|
.map_err(FanError::ManualSpeedFailed)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -124,27 +118,6 @@ impl Fan {
|
|||||||
.map_err(|error| FanError::FailedToChangeSpeed { value, error })?;
|
.map_err(|error| FanError::FailedToChangeSpeed { value, error })?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn pwm_min(&mut self) -> u32 {
|
|
||||||
if self.pwm_min.is_none() {
|
|
||||||
self.pwm_min = Some(self.value_or(PULSE_WIDTH_MODULATION_MIN, 0));
|
|
||||||
};
|
|
||||||
self.pwm_min.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pwm_max(&mut self) -> u32 {
|
|
||||||
if self.pwm_max.is_none() {
|
|
||||||
self.pwm_max = Some(self.value_or(PULSE_WIDTH_MODULATION_MAX, 255));
|
|
||||||
};
|
|
||||||
self.pwm_max.unwrap_or(255)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pwm(&self) -> crate::Result<u32> {
|
|
||||||
let value = self
|
|
||||||
.hw_mon_read(PULSE_WIDTH_MODULATION)?
|
|
||||||
.parse()
|
|
||||||
.map_err(FanError::NonIntPwm)?;
|
|
||||||
Ok(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_fan_automatic(&self) -> bool {
|
pub fn is_fan_automatic(&self) -> bool {
|
||||||
self.hw_mon_read(PULSE_WIDTH_MODULATION_MODE)
|
self.hw_mon_read(PULSE_WIDTH_MODULATION_MODE)
|
||||||
@ -170,19 +143,6 @@ impl Fan {
|
|||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gpu_temp(&self) -> Vec<(String, crate::Result<f64>)> {
|
|
||||||
self.temp_inputs
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.map(|name| {
|
|
||||||
let temp = self
|
|
||||||
.read_gpu_temp(name.as_str())
|
|
||||||
.map(|temp| temp as f64 / 1000f64);
|
|
||||||
(name, temp)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn read_gpu_temp(&self, name: &str) -> crate::Result<u64> {
|
pub(crate) fn read_gpu_temp(&self, name: &str) -> crate::Result<u64> {
|
||||||
let value = self
|
let value = self
|
||||||
.hw_mon_read(name)?
|
.hw_mon_read(name)?
|
||||||
@ -190,19 +150,18 @@ impl Fan {
|
|||||||
.map_err(FanError::NonIntTemp)?;
|
.map_err(FanError::NonIntTemp)?;
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn load_temp_inputs(hw_mon: &HwMon) -> Vec<String> {
|
pub fn pwm_min(&mut self) -> u32 {
|
||||||
let dir = match std::fs::read_dir(hw_mon.mon_dir()) {
|
if self.pwm_min.is_none() {
|
||||||
Ok(d) => d,
|
self.pwm_min = Some(self.value_or(PULSE_WIDTH_MODULATION_MIN, 0));
|
||||||
_ => return vec![],
|
|
||||||
};
|
};
|
||||||
dir.filter_map(|f| f.ok())
|
self.pwm_min.unwrap_or_default()
|
||||||
.filter_map(|f| {
|
}
|
||||||
f.file_name()
|
|
||||||
.to_str()
|
pub fn pwm_max(&mut self) -> u32 {
|
||||||
.filter(|s| s.starts_with("temp") && s.ends_with("_input"))
|
if self.pwm_max.is_none() {
|
||||||
.map(String::from)
|
self.pwm_max = Some(self.value_or(PULSE_WIDTH_MODULATION_MAX, 255));
|
||||||
})
|
};
|
||||||
.collect()
|
self.pwm_max.unwrap_or(255)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
|
use amdgpu::{utils, AmdGpuError};
|
||||||
|
use amdgpu_config::fan::ConfigError;
|
||||||
|
|
||||||
use crate::command::FanError;
|
use crate::command::FanError;
|
||||||
use crate::config::ConfigError;
|
|
||||||
use amdgpu::AmdGpuError;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum AmdFanError {
|
pub enum AmdFanError {
|
||||||
#[error("Vendor is not AMD")]
|
#[error("Vendor is not AMD")]
|
||||||
NotAmdCard,
|
NotAmdCard,
|
||||||
#[error("Monitor format is not valid. Available values are: short, s, long l, verbose and v")]
|
|
||||||
InvalidMonitorFormat,
|
|
||||||
#[error("No hwmod has been found in sysfs")]
|
#[error("No hwmod has been found in sysfs")]
|
||||||
NoHwMonFound,
|
NoHwMonFound,
|
||||||
#[error("No AMD Card has been found in sysfs")]
|
#[error("No AMD Card has been found in sysfs")]
|
||||||
@ -20,4 +19,6 @@ pub enum AmdFanError {
|
|||||||
Config(#[from] ConfigError),
|
Config(#[from] ConfigError),
|
||||||
#[error("{0:}")]
|
#[error("{0:}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("{0:}")]
|
||||||
|
AmdUtils(#[from] utils::AmdGpuError),
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,19 @@
|
|||||||
|
extern crate log;
|
||||||
|
|
||||||
|
use gumdrop::Options;
|
||||||
|
|
||||||
|
use amdgpu::utils::{ensure_config_dir, hw_mons};
|
||||||
|
use amdgpu_config::fan::{load_config, Config, DEFAULT_FAN_CONFIG_PATH};
|
||||||
|
|
||||||
use crate::command::FanCommand;
|
use crate::command::FanCommand;
|
||||||
use crate::error::AmdFanError;
|
use crate::error::AmdFanError;
|
||||||
use amdgpu::utils::hw_mons;
|
|
||||||
use amdgpu::CONFIG_DIR;
|
|
||||||
use config::{load_config, Config};
|
|
||||||
use gumdrop::Options;
|
|
||||||
use std::io::ErrorKind;
|
|
||||||
|
|
||||||
mod change_mode;
|
mod change_mode;
|
||||||
mod command;
|
mod command;
|
||||||
mod config;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod monitor;
|
|
||||||
mod panic_handler;
|
mod panic_handler;
|
||||||
mod service;
|
mod service;
|
||||||
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
pub static DEFAULT_CONFIG_PATH: &str = "/etc/amdfand/config.toml";
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, AmdFanError>;
|
pub type Result<T> = std::result::Result<T, AmdFanError>;
|
||||||
|
|
||||||
pub enum FanMode {
|
pub enum FanMode {
|
||||||
@ -51,7 +47,6 @@ fn run(config: Config) -> Result<()> {
|
|||||||
|
|
||||||
match opts.command {
|
match opts.command {
|
||||||
None => service::run(config),
|
None => service::run(config),
|
||||||
Some(FanCommand::Monitor(monitor)) => monitor::run(monitor, config),
|
|
||||||
Some(FanCommand::Service(_)) => service::run(config),
|
Some(FanCommand::Service(_)) => service::run(config),
|
||||||
Some(FanCommand::SetAutomatic(switcher)) => {
|
Some(FanCommand::SetAutomatic(switcher)) => {
|
||||||
change_mode::run(switcher, FanMode::Automatic, config)
|
change_mode::run(switcher, FanMode::Automatic, config)
|
||||||
@ -78,13 +73,11 @@ fn setup() -> Result<(String, Config)> {
|
|||||||
std::env::set_var("RUST_LOG", "DEBUG");
|
std::env::set_var("RUST_LOG", "DEBUG");
|
||||||
}
|
}
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
if std::fs::read(CONFIG_DIR).map_err(|e| e.kind() == ErrorKind::NotFound) == Err(true) {
|
ensure_config_dir()?;
|
||||||
std::fs::create_dir_all(CONFIG_DIR)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let config_path = Opts::parse_args_default_or_exit()
|
let config_path = Opts::parse_args_default_or_exit()
|
||||||
.config
|
.config
|
||||||
.unwrap_or_else(|| DEFAULT_CONFIG_PATH.to_string());
|
.unwrap_or_else(|| DEFAULT_FAN_CONFIG_PATH.to_string());
|
||||||
let config = load_config(&config_path)?;
|
let config = load_config(&config_path)?;
|
||||||
log::info!("{:?}", config);
|
log::info!("{:?}", config);
|
||||||
log::set_max_level(config.log_level().as_str().parse().unwrap());
|
log::set_max_level(config.log_level().as_str().parse().unwrap());
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::command::Fan;
|
|
||||||
use amdgpu::utils::hw_mons;
|
use amdgpu::utils::hw_mons;
|
||||||
|
|
||||||
|
use crate::command::Fan;
|
||||||
|
|
||||||
pub fn restore_automatic() {
|
pub fn restore_automatic() {
|
||||||
for hw in hw_mons(true).unwrap_or_default() {
|
for hw in hw_mons(true).unwrap_or_default() {
|
||||||
if let Err(error) = (Fan {
|
if let Err(error) = (Fan {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use crate::command::Fan;
|
|
||||||
use crate::AmdFanError;
|
|
||||||
use amdgpu::utils::hw_mons;
|
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
|
|
||||||
use crate::config::Config;
|
use amdgpu::utils::hw_mons;
|
||||||
|
use amdgpu_config::fan::Config;
|
||||||
|
|
||||||
|
use crate::command::Fan;
|
||||||
|
use crate::AmdFanError;
|
||||||
|
|
||||||
/// Start service which will change fan speed according to config and GPU temperature
|
/// Start service which will change fan speed according to config and GPU temperature
|
||||||
pub fn run(config: Config) -> crate::Result<()> {
|
pub fn run(config: Config) -> crate::Result<()> {
|
||||||
|
29
amdgpu-config/Cargo.toml
Normal file
29
amdgpu-config/Cargo.toml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[package]
|
||||||
|
name = "amdgpu-config"
|
||||||
|
version = "1.0.8"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "amdgpu_config"
|
||||||
|
path = "./src/lib.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
fan = []
|
||||||
|
voltage = []
|
||||||
|
monitor = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
amdgpu = { path = "../amdgpu", version = "1.0.8" }
|
||||||
|
|
||||||
|
serde = { version = "1.0.126", features = ["derive"] }
|
||||||
|
toml = { version = "0.5.8" }
|
||||||
|
csv = { version = "1.1.6" }
|
||||||
|
|
||||||
|
thiserror = "1.0.30"
|
||||||
|
gumdrop = { version = "0.8.0" }
|
||||||
|
|
||||||
|
log = { version = "0.4.14" }
|
||||||
|
pretty_env_logger = { version = "0.4.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
amdgpu = { path = "../amdgpu", version = "1.0" }
|
5
amdgpu-config/README.md
Normal file
5
amdgpu-config/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# amdgpu-config
|
||||||
|
|
||||||
|
This crates holds config files for `amdfand`, `amdvold` and `amdmond`.
|
||||||
|
|
||||||
|
For more information please check those services.
|
@ -1,6 +1,7 @@
|
|||||||
use amdgpu::utils::linear_map;
|
use amdgpu::utils::{ensure_config, linear_map};
|
||||||
use amdgpu::{LogLevel, TempInput};
|
use amdgpu::{LogLevel, TempInput};
|
||||||
use std::io::ErrorKind;
|
|
||||||
|
pub static DEFAULT_FAN_CONFIG_PATH: &str = "/etc/amdfand/config.toml";
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
pub struct MatrixPoint {
|
pub struct MatrixPoint {
|
||||||
@ -27,6 +28,13 @@ impl Config {
|
|||||||
self.cards.as_ref()
|
self.cards.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn speed_matrix_point(&self, temp: f64) -> Option<&MatrixPoint> {
|
||||||
|
match self.speed_matrix.iter().rposition(|p| p.temp <= temp) {
|
||||||
|
Some(idx) => self.speed_matrix.get(idx),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn speed_for_temp(&self, temp: f64) -> f64 {
|
pub fn speed_for_temp(&self, temp: f64) -> f64 {
|
||||||
let idx = match self.speed_matrix.iter().rposition(|p| p.temp <= temp) {
|
let idx = match self.speed_matrix.iter().rposition(|p| p.temp <= temp) {
|
||||||
Some(idx) => idx,
|
Some(idx) => idx,
|
||||||
@ -108,7 +116,7 @@ impl Default for Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, PartialEq)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ConfigError {
|
pub enum ConfigError {
|
||||||
#[error("Fan speed {value:?} for config entry {index:} is too low (minimal value is 0.0)")]
|
#[error("Fan speed {value:?} for config entry {index:} is too low (minimal value is 0.0)")]
|
||||||
FanSpeedTooLow { value: f64, index: usize },
|
FanSpeedTooLow { value: f64, index: usize },
|
||||||
@ -130,21 +138,12 @@ pub enum ConfigError {
|
|||||||
index: usize,
|
index: usize,
|
||||||
last: f64,
|
last: f64,
|
||||||
},
|
},
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
pub fn load_config(config_path: &str) -> Result<Config, ConfigError> {
|
||||||
let config = match std::fs::read_to_string(config_path) {
|
let config = ensure_config::<Config, ConfigError, _>(config_path)?;
|
||||||
Ok(s) => toml::from_str(&s).unwrap(),
|
|
||||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
|
||||||
let config = Config::default();
|
|
||||||
std::fs::write(config_path, toml::to_string(&config).unwrap())?;
|
|
||||||
config
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut last_point: Option<&MatrixPoint> = None;
|
let mut last_point: Option<&MatrixPoint> = None;
|
||||||
|
|
||||||
@ -154,8 +153,7 @@ pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
|||||||
return Err(ConfigError::FanSpeedTooLow {
|
return Err(ConfigError::FanSpeedTooLow {
|
||||||
value: matrix_point.speed,
|
value: matrix_point.speed,
|
||||||
index,
|
index,
|
||||||
}
|
});
|
||||||
.into());
|
|
||||||
}
|
}
|
||||||
if matrix_point.speed > 100f64 {
|
if matrix_point.speed > 100f64 {
|
||||||
log::error!(
|
log::error!(
|
||||||
@ -165,8 +163,7 @@ pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
|||||||
return Err(ConfigError::FanSpeedTooHigh {
|
return Err(ConfigError::FanSpeedTooHigh {
|
||||||
value: matrix_point.speed,
|
value: matrix_point.speed,
|
||||||
index,
|
index,
|
||||||
}
|
});
|
||||||
.into());
|
|
||||||
}
|
}
|
||||||
if let Some(last_point) = last_point {
|
if let Some(last_point) = last_point {
|
||||||
if matrix_point.speed < last_point.speed {
|
if matrix_point.speed < last_point.speed {
|
||||||
@ -180,8 +177,7 @@ pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
|||||||
current: matrix_point.speed,
|
current: matrix_point.speed,
|
||||||
last: last_point.speed,
|
last: last_point.speed,
|
||||||
index,
|
index,
|
||||||
}
|
});
|
||||||
.into());
|
|
||||||
}
|
}
|
||||||
if matrix_point.temp < last_point.temp {
|
if matrix_point.temp < last_point.temp {
|
||||||
log::error!(
|
log::error!(
|
||||||
@ -194,8 +190,7 @@ pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
|||||||
current: matrix_point.temp,
|
current: matrix_point.temp,
|
||||||
last: last_point.temp,
|
last: last_point.temp,
|
||||||
index,
|
index,
|
||||||
}
|
});
|
||||||
.into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,10 +202,10 @@ pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod parse_config {
|
mod parse_config {
|
||||||
use crate::config::TempInput;
|
|
||||||
use amdgpu::{AmdGpuError, Card};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use amdgpu::{AmdGpuError, Card, TempInput};
|
||||||
|
|
||||||
#[derive(Deserialize, PartialEq, Debug)]
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
pub struct Foo {
|
pub struct Foo {
|
||||||
card: Card,
|
card: Card,
|
9
amdgpu-config/src/lib.rs
Normal file
9
amdgpu-config/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#[cfg(feature = "fan")]
|
||||||
|
pub mod fan;
|
||||||
|
#[cfg(feature = "monitor")]
|
||||||
|
pub mod monitor;
|
||||||
|
#[cfg(feature = "voltage")]
|
||||||
|
pub mod voltage;
|
||||||
|
|
||||||
|
/// pulse width modulation fan level (0-255)
|
||||||
|
pub static PULSE_WIDTH_MODULATION: &str = "pwm1";
|
53
amdgpu-config/src/monitor.rs
Normal file
53
amdgpu-config/src/monitor.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use amdgpu::utils::ensure_config;
|
||||||
|
use amdgpu::LogLevel;
|
||||||
|
|
||||||
|
pub static DEFAULT_MONITOR_CONFIG_PATH: &str = "/etc/amdfand/monitor.toml";
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
/// Minimal log level
|
||||||
|
log_level: LogLevel,
|
||||||
|
/// Time in milliseconds
|
||||||
|
interval: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ConfigError {
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
log_level: LogLevel::Error,
|
||||||
|
interval: 5000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn log_level(&self) -> LogLevel {
|
||||||
|
self.log_level
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interval(&self) -> u32 {
|
||||||
|
self.interval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_config(config_path: &str) -> Result<Config, ConfigError> {
|
||||||
|
let mut config: Config = ensure_config::<Config, ConfigError, _>(config_path)?;
|
||||||
|
|
||||||
|
if config.interval < 100 {
|
||||||
|
log::warn!(
|
||||||
|
"Minimal interval is 100ms, overriding {}ms",
|
||||||
|
config.interval
|
||||||
|
);
|
||||||
|
config.interval = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
31
amdgpu-config/src/voltage.rs
Normal file
31
amdgpu-config/src/voltage.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use amdgpu::utils::ensure_config;
|
||||||
|
use amdgpu::LogLevel;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ConfigError {
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
log_level: LogLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
log_level: LogLevel::Error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn log_level(&self) -> LogLevel {
|
||||||
|
self.log_level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_config(config_path: &str) -> Result<Config, ConfigError> {
|
||||||
|
ensure_config::<Config, ConfigError, _>(config_path)
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "amdgpu"
|
name = "amdgpu"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Subcomponent of AMDGPU fan control service"
|
description = "Subcomponent of AMDGPU fan control service"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
5
amdgpu/README.md
Normal file
5
amdgpu/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# amdgpu-config
|
||||||
|
|
||||||
|
This is shared data for `amdfand`, `amdvold` and `amdmond`.
|
||||||
|
|
||||||
|
For more information please check those services.
|
@ -1,6 +1,7 @@
|
|||||||
use crate::AmdGpuError;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::AmdGpuError;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct Card(pub u32);
|
pub struct Card(pub u32);
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{AmdGpuError, Card, ROOT_DIR};
|
use crate::{utils, AmdGpuError, Card, ROOT_DIR};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HwMonName(pub String);
|
pub struct HwMonName(pub String);
|
||||||
@ -30,7 +30,7 @@ impl HwMon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn name(&self) -> std::io::Result<String> {
|
pub fn name(&self) -> utils::Result<String> {
|
||||||
self.hw_mon_read("name")
|
self.hw_mon_read("name")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,21 +70,21 @@ impl HwMon {
|
|||||||
.unwrap_or(fallback)
|
.unwrap_or(fallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hw_mon_read(&self, name: &str) -> std::io::Result<String> {
|
pub fn hw_mon_read(&self, name: &str) -> utils::Result<String> {
|
||||||
std::fs::read_to_string(self.mon_file_path(name)).map(|s| String::from(s.trim()))
|
utils::read_to_string(self.mon_file_path(name)).map(|s| String::from(s.trim()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn device_read(&self, name: &str) -> std::io::Result<String> {
|
pub fn device_read(&self, name: &str) -> utils::Result<String> {
|
||||||
std::fs::read_to_string(self.device_dir().join(name)).map(|s| String::from(s.trim()))
|
utils::read_to_string(self.device_dir().join(name)).map(|s| String::from(s.trim()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hw_mon_write(&self, name: &str, value: u64) -> std::io::Result<()> {
|
pub fn hw_mon_write(&self, name: &str, value: u64) -> utils::Result<()> {
|
||||||
std::fs::write(self.mon_file_path(name), format!("{}", value))?;
|
utils::write(self.mon_file_path(name), format!("{}", value))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn device_write<C: AsRef<[u8]>>(&self, name: &str, value: C) -> std::io::Result<()> {
|
pub fn device_write<C: AsRef<[u8]>>(&self, name: &str, value: C) -> utils::Result<()> {
|
||||||
std::fs::write(self.device_dir().join(name), value)?;
|
utils::write(self.device_dir().join(name), value)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,35 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub use card::*;
|
||||||
|
pub use error::*;
|
||||||
|
pub use temp_input::*;
|
||||||
|
|
||||||
mod card;
|
mod card;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod hw_mon;
|
pub mod hw_mon;
|
||||||
mod temp_input;
|
mod temp_input;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub use card::*;
|
|
||||||
pub use error::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
pub use temp_input::*;
|
|
||||||
|
|
||||||
pub static CONFIG_DIR: &str = "/etc/amdfand";
|
pub static CONFIG_DIR: &str = "/etc/amdfand";
|
||||||
|
|
||||||
pub static ROOT_DIR: &str = "/sys/class/drm";
|
pub static ROOT_DIR: &str = "/sys/class/drm";
|
||||||
pub static HW_MON_DIR: &str = "hwmon";
|
pub static HW_MON_DIR: &str = "hwmon";
|
||||||
|
|
||||||
|
/// pulse width modulation fan control minimum level (0)
|
||||||
|
pub static PULSE_WIDTH_MODULATION_MIN: &str = "pwm1_min";
|
||||||
|
|
||||||
|
/// pulse width modulation fan control maximum level (255)
|
||||||
|
pub static PULSE_WIDTH_MODULATION_MAX: &str = "pwm1_max";
|
||||||
|
|
||||||
|
/// pulse width modulation fan level (0-255)
|
||||||
|
pub static PULSE_WIDTH_MODULATION: &str = "pwm1";
|
||||||
|
|
||||||
|
/// pulse width modulation fan control method (0: no fan speed control, 1: manual fan speed control using pwm interface, 2: automatic fan speed control)
|
||||||
|
pub static PULSE_WIDTH_MODULATION_MODE: &str = "pwm1_enable";
|
||||||
|
|
||||||
|
// static PULSE_WIDTH_MODULATION_DISABLED: &str = "0";
|
||||||
|
pub static PULSE_WIDTH_MODULATION_AUTO: &str = "2";
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, AmdGpuError>;
|
pub type Result<T> = std::result::Result<T, AmdGpuError>;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
|
||||||
|
@ -1,5 +1,45 @@
|
|||||||
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
use crate::hw_mon::HwMon;
|
use crate::hw_mon::HwMon;
|
||||||
use crate::{hw_mon, Card, ROOT_DIR};
|
use crate::{hw_mon, Card, CONFIG_DIR, ROOT_DIR};
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, AmdGpuError>;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum AmdGpuError {
|
||||||
|
#[error("Write to {path:?} failed. {io}")]
|
||||||
|
Write { io: std::io::Error, path: String },
|
||||||
|
#[error("Read from {path:?} failed. {io}")]
|
||||||
|
Read { io: std::io::Error, path: String },
|
||||||
|
#[error("File {0:?} does not exists")]
|
||||||
|
FileNotFound(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_to_string<P: AsRef<std::path::Path>>(path: P) -> Result<String> {
|
||||||
|
std::fs::read_to_string(&path).map_err(|io| {
|
||||||
|
if io.kind() == std::io::ErrorKind::NotFound {
|
||||||
|
AmdGpuError::FileNotFound(path.as_ref().to_str().map(String::from).unwrap_or_default())
|
||||||
|
} else {
|
||||||
|
AmdGpuError::Read {
|
||||||
|
io,
|
||||||
|
path: path.as_ref().to_str().map(String::from).unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write<P: AsRef<std::path::Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
|
||||||
|
std::fs::write(&path, contents).map_err(|io| {
|
||||||
|
if io.kind() == std::io::ErrorKind::NotFound {
|
||||||
|
AmdGpuError::FileNotFound(path.as_ref().to_str().map(String::from).unwrap_or_default())
|
||||||
|
} else {
|
||||||
|
AmdGpuError::Write {
|
||||||
|
io,
|
||||||
|
path: path.as_ref().to_str().map(String::from).unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// linear mapping from the xrange to the yrange
|
/// linear mapping from the xrange to the yrange
|
||||||
pub fn linear_map(x: f64, x1: f64, x2: f64, y1: f64, y2: f64) -> f64 {
|
pub fn linear_map(x: f64, x1: f64, x2: f64, y1: f64, y2: f64) -> f64 {
|
||||||
@ -41,3 +81,45 @@ pub fn hw_mons(filter: bool) -> std::io::Result<Vec<HwMon>> {
|
|||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ensure_config<Config, Error, P>(config_path: P) -> std::result::Result<Config, Error>
|
||||||
|
where
|
||||||
|
Config: serde::Serialize + serde::de::DeserializeOwned + Default + Sized,
|
||||||
|
P: AsRef<std::path::Path>,
|
||||||
|
Error: From<std::io::Error>,
|
||||||
|
{
|
||||||
|
match std::fs::read_to_string(&config_path) {
|
||||||
|
Ok(s) => Ok(toml::from_str::<Config>(s.as_str()).unwrap()),
|
||||||
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
let config = Config::default();
|
||||||
|
std::fs::write(config_path, toml::to_string(&config).unwrap())?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{:?}", e);
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_temp_inputs(hw_mon: &HwMon) -> Vec<String> {
|
||||||
|
let dir = match std::fs::read_dir(hw_mon.mon_dir()) {
|
||||||
|
Ok(d) => d,
|
||||||
|
_ => return vec![],
|
||||||
|
};
|
||||||
|
dir.filter_map(|f| f.ok())
|
||||||
|
.filter_map(|f| {
|
||||||
|
f.file_name()
|
||||||
|
.to_str()
|
||||||
|
.filter(|s| s.starts_with("temp") && s.ends_with("_input"))
|
||||||
|
.map(String::from)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensure_config_dir() -> std::io::Result<()> {
|
||||||
|
if std::fs::read(CONFIG_DIR).map_err(|e| e.kind() == ErrorKind::NotFound) == Err(true) {
|
||||||
|
std::fs::create_dir_all(CONFIG_DIR)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
24
amdmond/Cargo.toml
Normal file
24
amdmond/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "amdmond"
|
||||||
|
version = "1.0.8"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
amdgpu = { path = "../amdgpu", version = "1.0.8" }
|
||||||
|
amdgpu-config = { path = "../amdgpu-config", version = "1.0.8", features = ["monitor", "fan"] }
|
||||||
|
|
||||||
|
serde = { version = "1.0.126", features = ["derive"] }
|
||||||
|
toml = { version = "0.5.8" }
|
||||||
|
csv = { version = "1.1.6" }
|
||||||
|
|
||||||
|
thiserror = "1.0.30"
|
||||||
|
gumdrop = { version = "0.8.0" }
|
||||||
|
|
||||||
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
|
|
||||||
|
log = { version = "0.4.14" }
|
||||||
|
pretty_env_logger = { version = "0.4.0" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
amdgpu = { path = "../amdgpu", version = "1.0" }
|
||||||
|
amdgpu-config = { path = "../amdgpu-config", version = "1.0", features = ["monitor", "fan"] }
|
66
amdmond/README.md
Normal file
66
amdmond/README.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# AMD Monitoring daemon
|
||||||
|
|
||||||
|
## Watch mode
|
||||||
|
|
||||||
|
Tool will check temperature and prints:
|
||||||
|
|
||||||
|
* Minimal modulation
|
||||||
|
* Maximal modulation
|
||||||
|
* Current modulation
|
||||||
|
* Current fan speed in percentage (PWD / PWD MAX * 100)
|
||||||
|
* Current value of each temperature sensor (typically temp1_input is which should be observed)
|
||||||
|
|
||||||
|
> `modulation` is a value between 0-255 which indicate how fast fan should be moving
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/usr/bin/amdmond watch --format short
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formats
|
||||||
|
|
||||||
|
There are 2 possible formats.
|
||||||
|
|
||||||
|
* `short` - very compact info
|
||||||
|
* `long` - more human-readable info
|
||||||
|
|
||||||
|
## Log File mode
|
||||||
|
|
||||||
|
This tool can be used to track GPU temperature and amdfand speed curve management to prevent GPU card from generating
|
||||||
|
unnecessary noise.
|
||||||
|
|
||||||
|
It will create csv log file with:
|
||||||
|
|
||||||
|
* time
|
||||||
|
* temperature
|
||||||
|
* card modulation
|
||||||
|
* matrix point temperature
|
||||||
|
* matrix point speed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/usr/bin/amdmond log_file -s /var/log/amdmon.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install amdmond
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### minimal:
|
||||||
|
|
||||||
|
```
|
||||||
|
amdmond log_file -s /var/log/amdmon.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
Required arguments:
|
||||||
|
|
||||||
|
* `-s`, `--stat-file STAT-FILE` Full path to statistics file
|
||||||
|
|
||||||
|
Optional arguments:
|
||||||
|
|
||||||
|
* `-h`, `--help` Help message
|
||||||
|
* `-v`, `--version` Print version
|
||||||
|
* `-c`, `--config CONFIG` Config location
|
||||||
|
* `-i`, `--interval INTERVAL` Time between each check. 1000 is 1s, by default 5s
|
114
amdmond/src/command.rs
Normal file
114
amdmond/src/command.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
use crate::{log_file, watch, AmdMonError};
|
||||||
|
use amdgpu::hw_mon::HwMon;
|
||||||
|
use amdgpu::utils::load_temp_inputs;
|
||||||
|
use amdgpu::{TempInput, PULSE_WIDTH_MODULATION_MAX, PULSE_WIDTH_MODULATION_MIN};
|
||||||
|
use amdgpu_config::{fan, PULSE_WIDTH_MODULATION};
|
||||||
|
|
||||||
|
#[derive(gumdrop::Options)]
|
||||||
|
pub enum Command {
|
||||||
|
Watch(watch::Watch),
|
||||||
|
LogFile(log_file::LogFile),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Command {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Watch(watch::Watch::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AmdMon {
|
||||||
|
temp_input: Option<TempInput>,
|
||||||
|
inputs: Vec<String>,
|
||||||
|
hw_mon: HwMon,
|
||||||
|
/// Minimal modulation (between 0-255)
|
||||||
|
pub pwm_min: Option<u32>,
|
||||||
|
/// Maximal modulation (between 0-255)
|
||||||
|
pub pwm_max: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for AmdMon {
|
||||||
|
type Target = HwMon;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.hw_mon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AmdMon {
|
||||||
|
pub(crate) fn wrap_all(mons: Vec<HwMon>, config: &fan::Config) -> Vec<Self> {
|
||||||
|
mons.into_iter()
|
||||||
|
.map(|hw_mon| Self::wrap(hw_mon, config))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrap(hw_mon: HwMon, config: &fan::Config) -> Self {
|
||||||
|
Self {
|
||||||
|
temp_input: config.temp_input().cloned(),
|
||||||
|
inputs: load_temp_inputs(&hw_mon),
|
||||||
|
hw_mon,
|
||||||
|
pwm_min: None,
|
||||||
|
pwm_max: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gpu_temp(&self) -> Vec<(String, crate::Result<f64>)> {
|
||||||
|
self.inputs
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.map(|name| {
|
||||||
|
let temp = self
|
||||||
|
.read_gpu_temp(name.as_str())
|
||||||
|
.map(|temp| temp as f64 / 1000f64);
|
||||||
|
(name, temp)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_gpu_temp(&self, name: &str) -> crate::Result<u64> {
|
||||||
|
let value = self
|
||||||
|
.hw_mon_read(name)?
|
||||||
|
.parse::<u64>()
|
||||||
|
.map_err(AmdMonError::NonIntTemp)?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pwm(&self) -> crate::Result<u32> {
|
||||||
|
let value = self
|
||||||
|
.hw_mon_read(PULSE_WIDTH_MODULATION)?
|
||||||
|
.parse()
|
||||||
|
.map_err(AmdMonError::NonIntPwm)?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pwm_min(&mut self) -> u32 {
|
||||||
|
if self.pwm_min.is_none() {
|
||||||
|
self.pwm_min = Some(self.value_or(PULSE_WIDTH_MODULATION_MIN, 0));
|
||||||
|
};
|
||||||
|
self.pwm_min.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pwm_max(&mut self) -> u32 {
|
||||||
|
if self.pwm_max.is_none() {
|
||||||
|
self.pwm_max = Some(self.value_or(PULSE_WIDTH_MODULATION_MAX, 255));
|
||||||
|
};
|
||||||
|
self.pwm_max.unwrap_or(255)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_gpu_temp(&self) -> crate::Result<f64> {
|
||||||
|
if let Some(input) = self.temp_input.as_ref() {
|
||||||
|
let value = self.read_gpu_temp(&input.as_string())?;
|
||||||
|
return Ok(value as f64 / 1000f64);
|
||||||
|
}
|
||||||
|
let mut results = Vec::with_capacity(self.inputs.len());
|
||||||
|
for name in self.inputs.iter() {
|
||||||
|
results.push(self.read_gpu_temp(name).unwrap_or(0));
|
||||||
|
}
|
||||||
|
results.sort_unstable();
|
||||||
|
let value = results
|
||||||
|
.last()
|
||||||
|
.copied()
|
||||||
|
.map(|temp| temp as f64 / 1000f64)
|
||||||
|
.ok_or(AmdMonError::EmptyTempSet)?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
98
amdmond/src/log_file.rs
Normal file
98
amdmond/src/log_file.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use crate::command::AmdMon;
|
||||||
|
use crate::AmdMonError;
|
||||||
|
use amdgpu::utils::hw_mons;
|
||||||
|
use amdgpu_config::fan;
|
||||||
|
use amdgpu_config::fan::DEFAULT_FAN_CONFIG_PATH;
|
||||||
|
use amdgpu_config::monitor::Config;
|
||||||
|
|
||||||
|
#[derive(gumdrop::Options)]
|
||||||
|
pub struct LogFile {
|
||||||
|
#[options(help = "Help message")]
|
||||||
|
help: bool,
|
||||||
|
#[options(help = "Full path to statistics file")]
|
||||||
|
stat_file: String,
|
||||||
|
#[options(help = "Time between each check. 1000 is 1s, by default 5s")]
|
||||||
|
interval: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct Stat {
|
||||||
|
time: chrono::DateTime<chrono::Local>,
|
||||||
|
temperature: f64,
|
||||||
|
modulation: u32,
|
||||||
|
speed_setting: f64,
|
||||||
|
temperature_setting: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(command: LogFile, config: Config) -> crate::Result<()> {
|
||||||
|
let fan_config = fan::load_config(DEFAULT_FAN_CONFIG_PATH)?;
|
||||||
|
|
||||||
|
let duration = std::time::Duration::new(
|
||||||
|
0,
|
||||||
|
command.interval.unwrap_or_else(|| config.interval()) * 1_000_000,
|
||||||
|
);
|
||||||
|
log::info!("Updating each: {:?}", duration);
|
||||||
|
|
||||||
|
let _ = std::fs::remove_file(command.stat_file.as_str());
|
||||||
|
let stat_file = std::fs::OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(false)
|
||||||
|
.write(true)
|
||||||
|
.open(command.stat_file.as_str())?;
|
||||||
|
|
||||||
|
let mut writer = csv::WriterBuilder::new()
|
||||||
|
.double_quote(true)
|
||||||
|
.has_headers(true)
|
||||||
|
.buffer_capacity(100)
|
||||||
|
.from_writer(stat_file);
|
||||||
|
|
||||||
|
let mon = {
|
||||||
|
let mons = hw_mons(true)?;
|
||||||
|
if mons.is_empty() {
|
||||||
|
return Err(AmdMonError::NoHwMon);
|
||||||
|
}
|
||||||
|
AmdMon::wrap(hw_mons(true)?.remove(0), &fan_config)
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let time = chrono::Local::now();
|
||||||
|
let temperature = {
|
||||||
|
let mut temperatures = mon.gpu_temp();
|
||||||
|
|
||||||
|
if let Some(input) = fan_config.temp_input() {
|
||||||
|
let input_name = input.as_string();
|
||||||
|
temperatures
|
||||||
|
.into_iter()
|
||||||
|
.find(|(name, _value)| name == &input_name)
|
||||||
|
.and_then(|(_, v)| v.ok())
|
||||||
|
.unwrap_or_default()
|
||||||
|
} else if temperatures.len() > 1 {
|
||||||
|
temperatures.remove(1).1.unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
temperatures
|
||||||
|
.get(0)
|
||||||
|
.and_then(|(_, v)| v.as_ref().ok().copied())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (speed_setting, temperature_setting) =
|
||||||
|
if let Some(speed_matrix_point) = fan_config.speed_matrix_point(temperature) {
|
||||||
|
(speed_matrix_point.speed, speed_matrix_point.temp)
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let stat = Stat {
|
||||||
|
time,
|
||||||
|
temperature,
|
||||||
|
modulation: mon.pwm()?,
|
||||||
|
speed_setting,
|
||||||
|
temperature_setting,
|
||||||
|
};
|
||||||
|
|
||||||
|
writer.serialize(stat)?;
|
||||||
|
writer.flush()?;
|
||||||
|
|
||||||
|
std::thread::sleep(duration);
|
||||||
|
}
|
||||||
|
}
|
99
amdmond/src/main.rs
Normal file
99
amdmond/src/main.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
mod command;
|
||||||
|
mod log_file;
|
||||||
|
mod watch;
|
||||||
|
|
||||||
|
use amdgpu::utils;
|
||||||
|
use gumdrop::Options;
|
||||||
|
|
||||||
|
use crate::command::Command;
|
||||||
|
use amdgpu::utils::ensure_config_dir;
|
||||||
|
use amdgpu_config::monitor::{load_config, Config, DEFAULT_MONITOR_CONFIG_PATH};
|
||||||
|
use amdgpu_config::{fan, monitor};
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum AmdMonError {
|
||||||
|
#[error("Mon AMD GPU card was found")]
|
||||||
|
NoHwMon,
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("{0}")]
|
||||||
|
MonConfigError(#[from] monitor::ConfigError),
|
||||||
|
#[error("{0}")]
|
||||||
|
FanConfigError(#[from] fan::ConfigError),
|
||||||
|
#[error("{0}")]
|
||||||
|
AmdUtils(#[from] utils::AmdGpuError),
|
||||||
|
#[error("{0}")]
|
||||||
|
Csv(#[from] csv::Error),
|
||||||
|
#[error("AMD GPU temperature is malformed. It should be number. {0:?}")]
|
||||||
|
NonIntTemp(std::num::ParseIntError),
|
||||||
|
#[error("AMD GPU fan speed is malformed. It should be number. {0:?}")]
|
||||||
|
NonIntPwm(std::num::ParseIntError),
|
||||||
|
#[error("Monitor format is not valid. Available values are: short, s, long l, verbose and v")]
|
||||||
|
InvalidMonitorFormat,
|
||||||
|
#[error("Failed to read AMD GPU temperatures from tempX_input. No input was found")]
|
||||||
|
EmptyTempSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, AmdMonError>;
|
||||||
|
|
||||||
|
#[derive(gumdrop::Options)]
|
||||||
|
pub struct Opts {
|
||||||
|
#[options(help = "Help message")]
|
||||||
|
pub help: bool,
|
||||||
|
#[options(help = "Print version")]
|
||||||
|
pub version: bool,
|
||||||
|
#[options(help = "Config location")]
|
||||||
|
pub config: Option<String>,
|
||||||
|
#[options(command)]
|
||||||
|
pub command: Option<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(config: Config) -> Result<()> {
|
||||||
|
let opts: Opts = Opts::parse_args_default_or_exit();
|
||||||
|
|
||||||
|
if opts.version {
|
||||||
|
println!("amdfand {}", env!("CARGO_PKG_VERSION"));
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
match opts.command {
|
||||||
|
Some(Command::Watch(w)) => watch::run(w, config),
|
||||||
|
Some(Command::LogFile(l)) => log_file::run(l, config),
|
||||||
|
_ => {
|
||||||
|
println!("{}", <Opts as gumdrop::Options>::usage());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup() -> Result<(String, Config)> {
|
||||||
|
if std::env::var("RUST_LOG").is_err() {
|
||||||
|
std::env::set_var("RUST_LOG", "DEBUG");
|
||||||
|
}
|
||||||
|
pretty_env_logger::init();
|
||||||
|
ensure_config_dir()?;
|
||||||
|
|
||||||
|
let config_path = Opts::parse_args_default_or_exit()
|
||||||
|
.config
|
||||||
|
.unwrap_or_else(|| DEFAULT_MONITOR_CONFIG_PATH.to_string());
|
||||||
|
let config = load_config(&config_path)?;
|
||||||
|
log::info!("{:?}", config);
|
||||||
|
log::set_max_level(config.log_level().as_str().parse().unwrap());
|
||||||
|
Ok((config_path, config))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let (_config_path, config) = match setup() {
|
||||||
|
Ok(config) => config,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match run(config) {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
use crate::command::Fan;
|
|
||||||
use crate::AmdFanError;
|
|
||||||
use amdgpu::utils::{hw_mons, linear_map};
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::config::Config;
|
use amdgpu::utils::{hw_mons, linear_map};
|
||||||
|
use amdgpu_config::fan::DEFAULT_FAN_CONFIG_PATH;
|
||||||
|
use amdgpu_config::{fan, monitor};
|
||||||
|
|
||||||
|
use crate::command::AmdMon;
|
||||||
|
use crate::AmdMonError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MonitorFormat {
|
pub enum MonitorFormat {
|
||||||
@ -18,35 +20,45 @@ impl Default for MonitorFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for MonitorFormat {
|
impl FromStr for MonitorFormat {
|
||||||
type Err = AmdFanError;
|
type Err = AmdMonError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
match s {
|
match s {
|
||||||
"short" | "s" => Ok(MonitorFormat::Short),
|
"short" | "s" => Ok(MonitorFormat::Short),
|
||||||
"verbose" | "v" | "long" | "l" => Ok(MonitorFormat::Verbose),
|
"verbose" | "v" | "long" | "l" => Ok(MonitorFormat::Verbose),
|
||||||
_ => Err(AmdFanError::InvalidMonitorFormat),
|
_ => Err(AmdMonError::InvalidMonitorFormat),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, gumdrop::Options)]
|
#[derive(Debug, gumdrop::Options)]
|
||||||
pub struct Monitor {
|
pub struct Watch {
|
||||||
#[options(help = "Help message")]
|
#[options(help = "Help message")]
|
||||||
help: bool,
|
help: bool,
|
||||||
#[options(help = "Help message")]
|
#[options(help = "Monitor format")]
|
||||||
format: MonitorFormat,
|
format: MonitorFormat,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start print cards temperature and fan speed
|
impl Default for Watch {
|
||||||
pub fn run(monitor: Monitor, config: Config) -> crate::Result<()> {
|
fn default() -> Self {
|
||||||
match monitor.format {
|
Self {
|
||||||
MonitorFormat::Short => short(config),
|
help: false,
|
||||||
MonitorFormat::Verbose => verbose(config),
|
format: MonitorFormat::Short,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verbose(config: Config) -> crate::Result<()> {
|
/// Start print cards temperature and fan speed
|
||||||
let mut hw_mons = Fan::wrap_all(hw_mons(true)?, &config);
|
pub fn run(monitor: Watch, _config: monitor::Config) -> crate::Result<()> {
|
||||||
|
let fan_config = fan::load_config(DEFAULT_FAN_CONFIG_PATH)?;
|
||||||
|
match monitor.format {
|
||||||
|
MonitorFormat::Short => short(fan_config),
|
||||||
|
MonitorFormat::Verbose => verbose(fan_config),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verbose(config: fan::Config) -> crate::Result<()> {
|
||||||
|
let mut hw_mons = AmdMon::wrap_all(hw_mons(true)?, &config);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
|
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
|
||||||
@ -67,7 +79,7 @@ pub fn verbose(config: Config) -> crate::Result<()> {
|
|||||||
min as f64,
|
min as f64,
|
||||||
max as f64,
|
max as f64,
|
||||||
0f64,
|
0f64,
|
||||||
100f64
|
100f64,
|
||||||
))
|
))
|
||||||
.round(),
|
.round(),
|
||||||
);
|
);
|
||||||
@ -88,8 +100,8 @@ pub fn verbose(config: Config) -> crate::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn short(config: Config) -> crate::Result<()> {
|
pub fn short(config: fan::Config) -> crate::Result<()> {
|
||||||
let mut hw_mons = Fan::wrap_all(hw_mons(true)?, &config);
|
let mut hw_mons = AmdMon::wrap_all(hw_mons(true)?, &config);
|
||||||
loop {
|
loop {
|
||||||
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
|
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
|
||||||
for hw_mon in hw_mons.iter_mut() {
|
for hw_mon in hw_mons.iter_mut() {
|
||||||
@ -110,7 +122,7 @@ pub fn short(config: Config) -> crate::Result<()> {
|
|||||||
min as f64,
|
min as f64,
|
||||||
max as f64,
|
max as f64,
|
||||||
0f64,
|
0f64,
|
||||||
100f64
|
100f64,
|
||||||
)
|
)
|
||||||
.round(),
|
.round(),
|
||||||
);
|
);
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "amdvold"
|
name = "amdvold"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "AMDGPU fan control service"
|
description = "AMDGPU fan control service"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@ -9,7 +9,8 @@ categories = ["hardware-support"]
|
|||||||
repository = "https://github.com/Eraden/amdgpud"
|
repository = "https://github.com/Eraden/amdgpud"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
amdgpu = { path = "../amdgpu", version = "1.0.7" }
|
amdgpu = { path = "../amdgpu", version = "1.0.8" }
|
||||||
|
amdgpu-config = { path = "../amdgpu-config", version = "1.0.8", features = ["voltage"] }
|
||||||
|
|
||||||
serde = { version = "1.0.126", features = ["derive"] }
|
serde = { version = "1.0.126", features = ["derive"] }
|
||||||
toml = { version = "0.5.8" }
|
toml = { version = "0.5.8" }
|
||||||
@ -21,3 +22,4 @@ pretty_env_logger = { version = "0.4.0" }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
amdgpu = { path = "../amdgpu", version = "1.0" }
|
amdgpu = { path = "../amdgpu", version = "1.0" }
|
||||||
|
amdgpu-config = { path = "../amdgpu-config", version = "1.0", features = ["voltage"] }
|
||||||
|
51
amdvold/README.md
Normal file
51
amdvold/README.md
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# AMD graphic card voltage manager
|
||||||
|
|
||||||
|
This tool can be used to overclock you AMD graphic card on Linux
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install amdvold
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Available commands:
|
||||||
|
|
||||||
|
* `setup-info` - prints information how to enable voltage management on Linux (see Requirements)
|
||||||
|
* `print-states` - prints current card states
|
||||||
|
* `change-state` - change card voltage states
|
||||||
|
* `apply-changes` - apply changes
|
||||||
|
|
||||||
|
## Changing states
|
||||||
|
|
||||||
|
Positional arguments:
|
||||||
|
* `index` Profile number
|
||||||
|
* `module` Either memory or engine
|
||||||
|
* `frequency` New GPU module frequency
|
||||||
|
* `voltage` New GPU module voltage
|
||||||
|
|
||||||
|
Optional arguments:
|
||||||
|
* `-a`, `--apply-immediately` Apply changes immediately after change
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
amdvold 1 engine 1450MHz 772mV
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
To enable AMD GPU voltage manipulation kernel parameter must be added, please do one of the following:
|
||||||
|
|
||||||
|
* In GRUB add to "GRUB_CMDLINE_LINUX_DEFAULT" following text "amdgpu.ppfeaturemask=0xffffffff", example:
|
||||||
|
|
||||||
|
GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 cryptdevice=/dev/nvme0n1p3:cryptroot amdgpu.ppfeaturemask=0xffffffff psi=1"
|
||||||
|
|
||||||
|
Easiest way is to modify "/etc/default/grub" and generate new grub config.
|
||||||
|
|
||||||
|
* If you have hooks enabled add in "/etc/modprobe.d/amdgpu.conf" to "options" following text "amdgpu.ppfeaturemask=0xffffffff", example:
|
||||||
|
|
||||||
|
options amdgpu si_support=1 cik_support=1 vm_fragment_size=9 audio=0 dc=0 aspm=0 ppfeaturemask=0xffffffff
|
||||||
|
|
||||||
|
(only "ppfeaturemask=0xffffffff" is required and if you don't have "options amdgpu" you can just add "options amdgpu ppfeaturemask=0xffffffff")
|
@ -1,6 +1,7 @@
|
|||||||
|
use amdgpu::utils::hw_mons;
|
||||||
|
|
||||||
use crate::command::VoltageManipulator;
|
use crate::command::VoltageManipulator;
|
||||||
use crate::{Config, VoltageError};
|
use crate::{Config, VoltageError};
|
||||||
use amdgpu::utils::hw_mons;
|
|
||||||
|
|
||||||
#[derive(Debug, gumdrop::Options)]
|
#[derive(Debug, gumdrop::Options)]
|
||||||
pub struct ApplyChanges {
|
pub struct ApplyChanges {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
use amdgpu::utils::hw_mons;
|
||||||
|
|
||||||
use crate::clock_state::{Frequency, Voltage};
|
use crate::clock_state::{Frequency, Voltage};
|
||||||
use crate::command::{HardwareModule, VoltageManipulator};
|
use crate::command::{HardwareModule, VoltageManipulator};
|
||||||
use crate::{Config, VoltageError};
|
use crate::{Config, VoltageError};
|
||||||
use amdgpu::utils::hw_mons;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ChangeStateError {
|
pub enum ChangeStateError {
|
||||||
@ -17,6 +18,8 @@ pub enum ChangeStateError {
|
|||||||
|
|
||||||
#[derive(Debug, gumdrop::Options)]
|
#[derive(Debug, gumdrop::Options)]
|
||||||
pub struct ChangeState {
|
pub struct ChangeState {
|
||||||
|
#[options(help = "Help message")]
|
||||||
|
help: bool,
|
||||||
#[options(help = "Profile number", free)]
|
#[options(help = "Profile number", free)]
|
||||||
index: u16,
|
index: u16,
|
||||||
#[options(help = "Either memory or engine", free)]
|
#[options(help = "Either memory or engine", free)]
|
||||||
@ -36,6 +39,7 @@ pub fn run(command: ChangeState, config: &Config) -> crate::Result<()> {
|
|||||||
}
|
}
|
||||||
let mon = mons.remove(0);
|
let mon = mons.remove(0);
|
||||||
let ChangeState {
|
let ChangeState {
|
||||||
|
help: _,
|
||||||
index,
|
index,
|
||||||
module,
|
module,
|
||||||
frequency,
|
frequency,
|
||||||
|
@ -385,47 +385,47 @@ VDDC_CURVE_VOLT[2]: 750mV 1200mV
|
|||||||
CurvePoint {
|
CurvePoint {
|
||||||
freq: Frequency {
|
freq: Frequency {
|
||||||
value: 800,
|
value: 800,
|
||||||
unit: "MHz".to_string()
|
unit: "MHz".to_string(),
|
||||||
},
|
},
|
||||||
voltage: Voltage {
|
voltage: Voltage {
|
||||||
value: 706,
|
value: 706,
|
||||||
unit: "mV".to_string()
|
unit: "mV".to_string(),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
CurvePoint {
|
CurvePoint {
|
||||||
freq: Frequency {
|
freq: Frequency {
|
||||||
value: 1450,
|
value: 1450,
|
||||||
unit: "MHz".to_string()
|
unit: "MHz".to_string(),
|
||||||
},
|
},
|
||||||
voltage: Voltage {
|
voltage: Voltage {
|
||||||
value: 772,
|
value: 772,
|
||||||
unit: "mV".to_string()
|
unit: "mV".to_string(),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
CurvePoint {
|
CurvePoint {
|
||||||
freq: Frequency {
|
freq: Frequency {
|
||||||
value: 2100,
|
value: 2100,
|
||||||
unit: "MHz".to_string()
|
unit: "MHz".to_string(),
|
||||||
},
|
},
|
||||||
voltage: Voltage {
|
voltage: Voltage {
|
||||||
value: 1143,
|
value: 1143,
|
||||||
unit: "mV".to_string()
|
unit: "mV".to_string(),
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
engine_label_lowest: Some(Frequency {
|
engine_label_lowest: Some(Frequency {
|
||||||
value: 800,
|
value: 800,
|
||||||
unit: "Mhz".to_string()
|
unit: "Mhz".to_string(),
|
||||||
}),
|
}),
|
||||||
engine_label_highest: Some(Frequency {
|
engine_label_highest: Some(Frequency {
|
||||||
value: 2100,
|
value: 2100,
|
||||||
unit: "Mhz".to_string()
|
unit: "Mhz".to_string(),
|
||||||
}),
|
}),
|
||||||
memory_label_lowest: Some(Frequency {
|
memory_label_lowest: Some(Frequency {
|
||||||
value: 875,
|
value: 875,
|
||||||
unit: "MHz".to_string()
|
unit: "MHz".to_string(),
|
||||||
}),
|
}),
|
||||||
memory_label_highest: None
|
memory_label_highest: None,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
use amdgpu::hw_mon::HwMon;
|
||||||
|
|
||||||
use crate::apply_changes::ApplyChanges;
|
use crate::apply_changes::ApplyChanges;
|
||||||
use crate::change_state::ChangeState;
|
use crate::change_state::ChangeState;
|
||||||
use crate::clock_state::{ClockState, Frequency, Voltage};
|
use crate::clock_state::{ClockState, Frequency, Voltage};
|
||||||
use crate::print_states::PrintStates;
|
use crate::print_states::PrintStates;
|
||||||
use crate::setup_info::SetupInfo;
|
use crate::setup_info::SetupInfo;
|
||||||
use crate::{Config, VoltageError};
|
use crate::{Config, VoltageError};
|
||||||
use amdgpu::hw_mon::HwMon;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum HardwareModule {
|
pub enum HardwareModule {
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
use amdgpu::LogLevel;
|
|
||||||
use std::io::ErrorKind;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum ConfigError {}
|
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct Config {
|
|
||||||
log_level: LogLevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
log_level: LogLevel::Error,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn log_level(&self) -> LogLevel {
|
|
||||||
self.log_level
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_config(config_path: &str) -> crate::Result<Config> {
|
|
||||||
let config = match std::fs::read_to_string(config_path) {
|
|
||||||
Ok(s) => toml::from_str(&s).unwrap(),
|
|
||||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
|
||||||
let config = Config::default();
|
|
||||||
std::fs::write(config_path, toml::to_string(&config).unwrap())?;
|
|
||||||
config
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
panic!();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
@ -1,7 +1,8 @@
|
|||||||
|
use amdgpu::{utils, AmdGpuError};
|
||||||
|
use amdgpu_config::voltage::ConfigError;
|
||||||
|
|
||||||
use crate::change_state::ChangeStateError;
|
use crate::change_state::ChangeStateError;
|
||||||
use crate::clock_state::ClockStateError;
|
use crate::clock_state::ClockStateError;
|
||||||
use crate::config::ConfigError;
|
|
||||||
use amdgpu::AmdGpuError;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum VoltageError {
|
pub enum VoltageError {
|
||||||
@ -19,4 +20,6 @@ pub enum VoltageError {
|
|||||||
ClockState(#[from] ClockStateError),
|
ClockState(#[from] ClockStateError),
|
||||||
#[error("{0:}")]
|
#[error("{0:}")]
|
||||||
ChangeStateError(#[from] ChangeStateError),
|
ChangeStateError(#[from] ChangeStateError),
|
||||||
|
#[error("{0:}")]
|
||||||
|
AmdUtils(#[from] utils::AmdGpuError),
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
use crate::command::VoltageCommand;
|
|
||||||
use crate::config::{load_config, Config};
|
|
||||||
use crate::error::VoltageError;
|
|
||||||
use amdgpu::CONFIG_DIR;
|
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
use std::io::ErrorKind;
|
|
||||||
|
use amdgpu::utils::ensure_config_dir;
|
||||||
|
use amdgpu_config::voltage::{load_config, Config};
|
||||||
|
|
||||||
|
use crate::command::VoltageCommand;
|
||||||
|
use crate::error::VoltageError;
|
||||||
|
|
||||||
mod apply_changes;
|
mod apply_changes;
|
||||||
mod change_state;
|
mod change_state;
|
||||||
mod clock_state;
|
mod clock_state;
|
||||||
mod command;
|
mod command;
|
||||||
mod config;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod print_states;
|
mod print_states;
|
||||||
mod setup_info;
|
mod setup_info;
|
||||||
@ -29,6 +29,7 @@ pub struct Opts {
|
|||||||
#[options(command)]
|
#[options(command)]
|
||||||
command: Option<command::VoltageCommand>,
|
command: Option<command::VoltageCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(config: Config) -> Result<()> {
|
fn run(config: Config) -> Result<()> {
|
||||||
let opts: Opts = Opts::parse_args_default_or_exit();
|
let opts: Opts = Opts::parse_args_default_or_exit();
|
||||||
|
|
||||||
@ -54,9 +55,7 @@ fn setup() -> Result<(String, Config)> {
|
|||||||
std::env::set_var("RUST_LOG", "DEBUG");
|
std::env::set_var("RUST_LOG", "DEBUG");
|
||||||
}
|
}
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
if std::fs::read(CONFIG_DIR).map_err(|e| e.kind() == ErrorKind::NotFound) == Err(true) {
|
ensure_config_dir()?;
|
||||||
std::fs::create_dir_all(CONFIG_DIR)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let config_path = Opts::parse_args_default_or_exit()
|
let config_path = Opts::parse_args_default_or_exit()
|
||||||
.config
|
.config
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
use amdgpu::utils::hw_mons;
|
||||||
|
|
||||||
use crate::command::VoltageManipulator;
|
use crate::command::VoltageManipulator;
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
use amdgpu::utils::hw_mons;
|
|
||||||
|
|
||||||
#[derive(Debug, gumdrop::Options)]
|
#[derive(Debug, gumdrop::Options)]
|
||||||
pub struct PrintStates {
|
pub struct PrintStates {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::config::Config;
|
use amdgpu_config::voltage::Config;
|
||||||
|
|
||||||
pub static ENABLE_VOLTAGE_INFO: &str = include_str!("../assets/enable_voltage_info.txt");
|
pub static ENABLE_VOLTAGE_INFO: &str = include_str!("../assets/enable_voltage_info.txt");
|
||||||
|
|
||||||
|
9
build.sh
9
build.sh
@ -1,6 +1,11 @@
|
|||||||
#!/usr/bin/env zsh
|
#!/usr/bin/env zsh
|
||||||
|
|
||||||
cargo build --release
|
cargo build --release
|
||||||
strip target/x86_64-unknown-linux-musl/release/amdfand
|
|
||||||
upx --best --lzma target/x86_64-unknown-linux-musl/release/amdfand
|
|
||||||
|
|
||||||
|
strip target/x86_64-unknown-linux-musl/release/amdfand
|
||||||
|
strip target/x86_64-unknown-linux-musl/release/amdvold
|
||||||
|
strip target/x86_64-unknown-linux-musl/release/amdmond
|
||||||
|
|
||||||
|
upx --best --lzma target/x86_64-unknown-linux-musl/release/amdfand
|
||||||
|
upx --best --lzma target/x86_64-unknown-linux-musl/release/amdvold
|
||||||
|
upx --best --lzma target/x86_64-unknown-linux-musl/release/amdmond
|
||||||
|
18
publish.sh
Executable file
18
publish.sh
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env zsh
|
||||||
|
|
||||||
|
git_root() { echo "$(git rev-parse --show-toplevel)" }
|
||||||
|
|
||||||
|
cd $(git_root)/amdgpu
|
||||||
|
cargo publish
|
||||||
|
|
||||||
|
cd $(git_root)/amdgpu-config
|
||||||
|
cargo publish
|
||||||
|
|
||||||
|
cd $(git_root)/amdfand
|
||||||
|
cargo publish
|
||||||
|
|
||||||
|
cd $(git_root)/amdvold
|
||||||
|
cargo publish
|
||||||
|
|
||||||
|
cd $(git_root)/amdmond
|
||||||
|
cargo publish
|
@ -5,6 +5,7 @@ pidfile="/run/${SVCNAME}.pid"
|
|||||||
command="/usr/bin/amdfand"
|
command="/usr/bin/amdfand"
|
||||||
command_args="service"
|
command_args="service"
|
||||||
command_user="root"
|
command_user="root"
|
||||||
|
command_background=true
|
||||||
|
|
||||||
depend() {
|
depend() {
|
||||||
need udev
|
need udev
|
||||||
|
12
services/amdmond
Executable file
12
services/amdmond
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/sbin/openrc-run
|
||||||
|
|
||||||
|
description="AMD card monitoring tool."
|
||||||
|
pidfile="/run/${SVCNAME}.pid"
|
||||||
|
command="/usr/bin/amdfand log_file -s /var/log/amdmon.csv"
|
||||||
|
command_args="service"
|
||||||
|
command_user="root"
|
||||||
|
command_background=true
|
||||||
|
|
||||||
|
depend() {
|
||||||
|
need udev
|
||||||
|
}
|
9
services/amdmond.service
Normal file
9
services/amdmond.service
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=AMD GPU monitoring tool
|
||||||
|
After=sysinit.target local-fs.target
|
||||||
|
[Service]
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=4
|
||||||
|
ExecStart=/usr/bin/amdmond log_file -s /var/log/amdmon.csv
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
@ -5,6 +5,7 @@ pidfile="/run/${SVCNAME}.pid"
|
|||||||
command="/usr/bin/amdvold"
|
command="/usr/bin/amdvold"
|
||||||
command_args="service"
|
command_args="service"
|
||||||
command_user="root"
|
command_user="root"
|
||||||
|
command_background=true
|
||||||
|
|
||||||
depend() {
|
depend() {
|
||||||
need udev
|
need udev
|
||||||
|
Loading…
Reference in New Issue
Block a user