radeonnoise 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/README.md +20 -0
- data/lib/radeonnoise.rb +3 -0
- data/lib/radeonnoise/core.rb +430 -0
- data/lib/radeonnoise/curve.rb +10 -0
- data/lib/radeonnoise/parts.rb +117 -0
- data/lib/radeonnoise/version.rb +4 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '02952e3cdd342be45f7b8ae82ecfe77e6c879b14033a517200ea82976a48448d'
|
4
|
+
data.tar.gz: 40f43b97f906aa5ce67f2b4fbf38f0cb4e4ca4ca819d0668318cfb74638340ce
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 938c9d89e13aa117be7ad45ffbdd5888360c27a91b686d6f908080fe949ca7cfbfef4edef7dc1f10520fb516cd63d50cdb00af33e7e4054262dfb48f7bba03c3
|
7
|
+
data.tar.gz: e9f0fde1fba7214c006c029020eaed87419580e9f102c6f93e93ef3cda747db618b12313f0e33d4febcf3e5f2339fdc361dc4b9794f0ad848b2a26e5305cab88
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
### OVERVIEW ###
|
2
|
+
"radeonnoise" is a port of my other Linux ``amdgpu`` project done in Elixir. It will allow monitoring and control of Radeon graphics cards that use the ``amdgpu`` driver.
|
3
|
+
|
4
|
+
I had previously hacked together this same library (also in Ruby) for a personal app, and I was planning to port the whole thing to Elixir. However, I just find Ruby more enjoyable and speedy to code in, and with Ruby 3.0.0's release, the Ractor model allows Ruby to bypass the GIL, so performance hits should be far less than the original private project.
|
5
|
+
|
6
|
+
Unlike the Elixir version, this is going to be a pluggable module and is not intended to inherently be a server unto itself.
|
7
|
+
|
8
|
+
The name is a play on Raildex's "Radio Noise Project", with the Misaka clones.
|
9
|
+
|
10
|
+
# TO-DO
|
11
|
+
1) Link driver version, probably by host bus and ``lspci``
|
12
|
+
1) Clean out unnecessary/unused code
|
13
|
+
1) Add better documentation
|
14
|
+
1) Add a fan curve configuration system
|
15
|
+
1) Add a fan curve controller
|
16
|
+
|
17
|
+
# INSTALLATION
|
18
|
+
```
|
19
|
+
gem install radeonnoise
|
20
|
+
```
|
data/lib/radeonnoise.rb
ADDED
@@ -0,0 +1,430 @@
|
|
1
|
+
# lib/radeonnoise/core.rb
|
2
|
+
|
3
|
+
# Local requires
|
4
|
+
require_relative "./parts.rb"
|
5
|
+
|
6
|
+
# This module will control the
|
7
|
+
# basic operation of a RadeonNoise
|
8
|
+
# application and configuration details.
|
9
|
+
module RadeonNoise
|
10
|
+
# Base directory for hwmon
|
11
|
+
BASEDIR = "/sys/class/hwmon"
|
12
|
+
|
13
|
+
# List of cards
|
14
|
+
attr_reader :cards, :lspci
|
15
|
+
|
16
|
+
# Initialize the RadeonNoise controls
|
17
|
+
def self.init
|
18
|
+
@cards = []
|
19
|
+
@lspci = self.read_lspci
|
20
|
+
# Iterate over hwmon subdirectories
|
21
|
+
Dir.glob("#{BASEDIR}/*")
|
22
|
+
# For each subdirectory, create an array
|
23
|
+
.collect do |subd|
|
24
|
+
# Read the uevent file
|
25
|
+
File.read("#{subd}/device/uevent")
|
26
|
+
.split(/\n/) # Divide it into lines
|
27
|
+
.map do |line|
|
28
|
+
parts = line.downcase.split("=") # Split into key-value pairs, lowercase
|
29
|
+
k,v = parts[0].strip.to_sym, parts[1].strip # Remove whitespace
|
30
|
+
|
31
|
+
# Return the intermediate value as a hash containing
|
32
|
+
# the hwmon subdirectory and the key-value pair
|
33
|
+
{dir: subd.split("/").last, k => v}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
# Merge each array into a single hash; if the array
|
37
|
+
# is empty, it will just be an empty hash
|
38
|
+
.collect { |card| card.reduce Hash.new, :update }
|
39
|
+
# We only want AMDGPU cards
|
40
|
+
.select { |card| card[:driver] == "amdgpu" }
|
41
|
+
# Convert each into a card object
|
42
|
+
.each { |card| @cards.push(AMDGPUCard.new(card)) }
|
43
|
+
|
44
|
+
# Return the cards
|
45
|
+
self.all
|
46
|
+
end
|
47
|
+
|
48
|
+
# Read the lspci data
|
49
|
+
def self.read_lspci
|
50
|
+
# See if `lspci` is present on the system
|
51
|
+
present = (Dir.glob("/usr/bin/*") + Dir.glob("/sbin/*"))
|
52
|
+
.select { |x| x.strip.match? "lspci" }
|
53
|
+
.then { |out| out.length > 0 }
|
54
|
+
if present then
|
55
|
+
# The output of this command directly fits for simple parsing
|
56
|
+
# of each system device. It will also likely be ported to the
|
57
|
+
# other project, which will become a dependency here.
|
58
|
+
`lspci -Dnnvvvkmm`.split(/^\s*$/).collect { |device|
|
59
|
+
self.split_device(device.strip) }
|
60
|
+
else
|
61
|
+
[{}]
|
62
|
+
end
|
63
|
+
end # End read of `lspci` data
|
64
|
+
|
65
|
+
# Divide the lspci data into devices
|
66
|
+
# Each pair should only have a single split, because the
|
67
|
+
# first colon represents the key to use
|
68
|
+
def self.split_device(device)
|
69
|
+
device.split("\n").collect { |line|
|
70
|
+
line.split(/:\s*/, 2).then { |k,v| {k.downcase.strip.to_sym => v.strip } }
|
71
|
+
}.reduce :update
|
72
|
+
end # End device splitter
|
73
|
+
|
74
|
+
# Return all the cards
|
75
|
+
def self.all() @cards end
|
76
|
+
|
77
|
+
# Return the PCI data
|
78
|
+
def self.pci() @lspci end
|
79
|
+
|
80
|
+
# Return whether the user is root
|
81
|
+
def self.root
|
82
|
+
if `id -u`.strip.to_i == 0 then
|
83
|
+
return true
|
84
|
+
else
|
85
|
+
puts "You need superuser/root permissions to use setters in this module"
|
86
|
+
end
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
# Holds data associated with each
|
91
|
+
# AMDGPU card and provides access to
|
92
|
+
# its fans and settings.
|
93
|
+
class AMDGPUCard
|
94
|
+
# Store the current settings for the
|
95
|
+
# graphics card:
|
96
|
+
# # detail is the sysfs hardware object data
|
97
|
+
# # sclk is the processor clock
|
98
|
+
# # mclk is the memory clock
|
99
|
+
# # pwm is whether fans are manual (true) or auto (false)
|
100
|
+
# # fan_curve is a list of values for the fan speeds
|
101
|
+
# # by temp.
|
102
|
+
attr_reader :config, :cache
|
103
|
+
|
104
|
+
PROTECTLVL = [:no_prot, :min_prot, :low_prot, :med_prot, :high_prot, :max_prot]
|
105
|
+
PWMLVL = {0 => :none, 1 => :manual, 2 => :auto}
|
106
|
+
PWMERR = "ERROR: accepted PWM type values are #{PWMLVL.values} or #{PWMLVL.invert.values}"
|
107
|
+
|
108
|
+
# Constructor
|
109
|
+
def initialize(hw)
|
110
|
+
# Initialize the card's config
|
111
|
+
@config = {
|
112
|
+
detail: hw,
|
113
|
+
sclk: [], # Processor clock
|
114
|
+
mclk: [], # Video memory clock
|
115
|
+
|
116
|
+
# Regexes for various settings
|
117
|
+
# Use these to detect the numbers for each
|
118
|
+
rg_pwm: /pwm\d*$/, # Fan power settings (0-255)
|
119
|
+
rg_freqs: /freq\d*_label/, # GPU frequencies
|
120
|
+
rg_power: /power\d*_average/, # GPU processor power consumption
|
121
|
+
rg_voltages: /in\d*_label/, # Voltage inputs
|
122
|
+
rg_temps: /temp\d*_label/, # Temperatures
|
123
|
+
rg_fans: /fan\d*_input/, # List of available fans
|
124
|
+
|
125
|
+
# Quick access to some files
|
126
|
+
# Manual/Auto clocking of processor
|
127
|
+
dpm_level: "device/power_dpm_force_performance_level",
|
128
|
+
# Power cap (used in a setting, as well as a read)
|
129
|
+
pcap: "power1_cap",
|
130
|
+
|
131
|
+
# Fan curve settings
|
132
|
+
fan_curve: {},
|
133
|
+
|
134
|
+
# Allow unsafe operations
|
135
|
+
protection: :max_prot,
|
136
|
+
}
|
137
|
+
|
138
|
+
# Initialize the card's stat cache
|
139
|
+
"#{RadeonNoise::BASEDIR}/#{@config[:detail][:dir]}".then { |d| @cache = {
|
140
|
+
dir: d,
|
141
|
+
vram_total: File.read("#{d}/device/mem_info_vram_total").to_f,
|
142
|
+
vbios: File.read("#{d}/device/vbios_version").strip,
|
143
|
+
}}
|
144
|
+
@cache.update(temps).update(fans).update(subsystem)
|
145
|
+
update
|
146
|
+
end # End constructor
|
147
|
+
|
148
|
+
# Stat the current cached data
|
149
|
+
def stat() @cache end
|
150
|
+
|
151
|
+
####################################
|
152
|
+
####################################
|
153
|
+
# +++ BASIC READERS SECTION
|
154
|
+
# +++ SECTION CONTAINS READ AND
|
155
|
+
# +++ UPDATE OPERATION METHODS
|
156
|
+
# +++ FOR SIMPLE READERS
|
157
|
+
####################################
|
158
|
+
####################################
|
159
|
+
|
160
|
+
# Attach the `lspci` subsystem data
|
161
|
+
# Essentially, this is the most useful data that is
|
162
|
+
# really relevant from the command, at least for the
|
163
|
+
# purpose of this interface. I may add more, though.
|
164
|
+
def subsystem
|
165
|
+
# There should only be one of these per card, as they
|
166
|
+
# are matched by bus ID. As such, only the first is
|
167
|
+
# selected.
|
168
|
+
RadeonNoise.pci.reject { |item|
|
169
|
+
item[:slot] != @config[:detail][:pci_slot_name]}
|
170
|
+
.first.then { |item| {subsystem: item} }
|
171
|
+
end # End card type
|
172
|
+
|
173
|
+
# Update the card values
|
174
|
+
def update
|
175
|
+
@cache.update(udevice)
|
176
|
+
.update({slot: upcie})
|
177
|
+
.update(upwm)
|
178
|
+
.update(ufreqs)
|
179
|
+
.update(uvolts)
|
180
|
+
.update(upower)
|
181
|
+
.then { utemps }
|
182
|
+
.then { ufans }
|
183
|
+
.then { @cache }
|
184
|
+
end # End abstract update function
|
185
|
+
|
186
|
+
# Update core device stats
|
187
|
+
def udevice
|
188
|
+
@cache[:dir].then { |d| {
|
189
|
+
level: File.read("#{d}/#{@config[:dpm_level]}").strip,
|
190
|
+
busy_proc: File.read("#{d}/device/gpu_busy_percent").to_f,
|
191
|
+
busy_mem: File.read("#{d}/device/mem_busy_percent").to_f,
|
192
|
+
}}
|
193
|
+
end # End core device data reader
|
194
|
+
|
195
|
+
# PWM (fan power)
|
196
|
+
def upwm
|
197
|
+
@cache[:dir].then { |d| {
|
198
|
+
pwm_control: pwmtype(File.read("#{d}/pwm1_enable").to_i),
|
199
|
+
pwm_max: File.read("#{d}/pwm1_max").to_i,
|
200
|
+
pwm_current: File.read("#{d}/pwm1").to_i,
|
201
|
+
}}
|
202
|
+
end # End PWM
|
203
|
+
|
204
|
+
# Voltage
|
205
|
+
# The inputs were re-classified from a previous commit:
|
206
|
+
# they have a specific meaning and are not actually
|
207
|
+
# variable. in0 is the graphics processor, while in1
|
208
|
+
# is the northbridge, if a sensor is present.
|
209
|
+
#
|
210
|
+
# As such, will update with a value or empty.
|
211
|
+
def uvolts
|
212
|
+
[@cache[:dir], "in0_input", "in1_input"].then { |d,c,n| {
|
213
|
+
volt_core: File.read("#{d}/#{c}").to_f,
|
214
|
+
volt_northbridge: File.exist?("#{d}/#{n}") ? File.read("#{d}/#{n}").to_f : nil,
|
215
|
+
}}
|
216
|
+
end # End voltage reader
|
217
|
+
|
218
|
+
# Power consumption
|
219
|
+
# This monitors the current and maximum possible
|
220
|
+
# power usage of the card. 'pcap', however, can be lower
|
221
|
+
# than the card's physical limit, if set as such
|
222
|
+
def upower
|
223
|
+
@cache[:dir].then { |d| {
|
224
|
+
power_cap: File.read("#{d}/#{@config[:pcap]}").strip,
|
225
|
+
power_usage: File.read("#{d}/power1_average").strip,
|
226
|
+
}}
|
227
|
+
end # End power reader
|
228
|
+
|
229
|
+
# Frequencies
|
230
|
+
# sclk is the processor clock
|
231
|
+
# mclk is the VRAM clock
|
232
|
+
def ufreqs
|
233
|
+
@cache[:dir].then { |d| {
|
234
|
+
freq_core: active_s(File.read("#{d}/device/pp_dpm_sclk"))
|
235
|
+
.collect { |item| item.to_f },
|
236
|
+
freq_vram: active_s(File.read("#{d}/device/pp_dpm_mclk"))
|
237
|
+
.collect { |item| item.to_f },
|
238
|
+
}}
|
239
|
+
end # End frequency reader
|
240
|
+
|
241
|
+
# PCIe speed
|
242
|
+
# Although this is a
|
243
|
+
def upcie
|
244
|
+
active_s(File.read("#{@cache[:dir]}/device/pp_dpm_pcie"))
|
245
|
+
.collect do |item|
|
246
|
+
item.split(",").then { |tf, mul|
|
247
|
+
{multiplier: mul.gsub(/\W/, ''), tfspeed: tf} }
|
248
|
+
end
|
249
|
+
end # End PCIe setting reader
|
250
|
+
|
251
|
+
####################################
|
252
|
+
####################################
|
253
|
+
# +++ COMPLEX ABSTRACT READERS SECTION
|
254
|
+
# +++ SECTION CONTAINS READ AND
|
255
|
+
# +++ UPDATE OPERATION METHODS
|
256
|
+
# +++ FOR VARIABLE READERS
|
257
|
+
####################################
|
258
|
+
####################################
|
259
|
+
|
260
|
+
# Temperatures
|
261
|
+
def temps
|
262
|
+
[@config[:rg_temps], @cache[:dir]].then { |rg, d|
|
263
|
+
{temps: Component::Temp.new(rg, "#{d}/*")} }
|
264
|
+
end # End temperatures
|
265
|
+
|
266
|
+
# Update the temperatures
|
267
|
+
def utemps() @cache[:temps].update end
|
268
|
+
|
269
|
+
# Read fan data
|
270
|
+
def fans
|
271
|
+
[@config[:rg_fans], @cache[:dir]].then { |rg, d|
|
272
|
+
{fans: Component::Fan.new(rg, "#{d}/*")} }
|
273
|
+
end
|
274
|
+
|
275
|
+
# Update the fan data
|
276
|
+
def ufans() @cache[:fans].update end
|
277
|
+
|
278
|
+
####################################
|
279
|
+
####################################
|
280
|
+
# +++ GENERAL HELPER FUNCTIONS
|
281
|
+
# +++ FOR SOME OF THE OTHER METHODS
|
282
|
+
####################################
|
283
|
+
####################################
|
284
|
+
|
285
|
+
# Warn the user about unsafe operations
|
286
|
+
def unsafe_warning(fn)
|
287
|
+
("+" * 20).then { |sep| <<~MSG
|
288
|
+
#{sep}\n'#{fn}' is a potentially damaging operation, and you must supply
|
289
|
+
the parameter 'force=true' to allow it to complete. This function
|
290
|
+
may cause graphics card crashes or damage to components, if used
|
291
|
+
incorrectly. During erroneous usage, data loss/corruption, due to a system
|
292
|
+
crash might be considered 'optimistic'.\n#{sep}
|
293
|
+
MSG
|
294
|
+
}.then { |msg| puts msg }
|
295
|
+
false
|
296
|
+
end # End unsafe warning
|
297
|
+
|
298
|
+
# Split multi-line read-ins
|
299
|
+
# and then select the active option
|
300
|
+
# only (for settings)
|
301
|
+
def active_s(data)
|
302
|
+
data.split(/\n/)
|
303
|
+
.reject { |item| !item.strip.match?(/\*$/) }
|
304
|
+
.collect { |item| item.split(":").last.strip }
|
305
|
+
end # End abstract active setting reader
|
306
|
+
|
307
|
+
# PWM control type
|
308
|
+
# Accepts: [Integer, String, Symbol]
|
309
|
+
#
|
310
|
+
# If String, convert to lowercase w/o whitespace, then
|
311
|
+
# check if it's an Integer or Symbol, and recurse.
|
312
|
+
#
|
313
|
+
# If Integer, check if in PWMLVL.
|
314
|
+
# If Symbol, check if in inverse hash of PWMLVL.
|
315
|
+
# Return `nil`, if not present -- up to user how to handle.
|
316
|
+
# If anything else, return error message.
|
317
|
+
def pwmtype(val)
|
318
|
+
case
|
319
|
+
when String === val # Try to recursively check on conversion
|
320
|
+
val.downcase.strip.then { |v| v.match?(/^\d+$/) ? pwmtype(v.to_i) : pwmtype(v.to_sym) }
|
321
|
+
when Integer === val # Check the constant directly
|
322
|
+
PWMLVL[val] || PWMERR
|
323
|
+
when Symbol === val # Invert constant, check
|
324
|
+
PWMLVL.invert[val] || PWMERR
|
325
|
+
else # Wrong class of input
|
326
|
+
"ERROR: wrong arg type (#{val.class}) for `pwmtype`. Accepts: [Integer, String, Symbol]"
|
327
|
+
end
|
328
|
+
end # End PWM type checker
|
329
|
+
|
330
|
+
####################################
|
331
|
+
####################################
|
332
|
+
# +++ SETTER FUNCTIONS: THESE NEED
|
333
|
+
# +++ TO BE RUN WITH ROOT PRIVILEGES
|
334
|
+
####################################
|
335
|
+
####################################
|
336
|
+
|
337
|
+
# Set the level of unsafe protection
|
338
|
+
def set_protection(val)
|
339
|
+
if RadeonNoise.root then
|
340
|
+
if PROTECTLVL.include?(val) then
|
341
|
+
@config[:protection] = val
|
342
|
+
else
|
343
|
+
("+" * 20).then { |sep| <<~ERR
|
344
|
+
#{sep}\n'#{val}' is not a valid protection setting
|
345
|
+
Valid options are: #{PROTECTLVL}
|
346
|
+
No change\n#{sep}
|
347
|
+
ERR
|
348
|
+
}.then { |e| puts e }
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end # End setter for protection level
|
352
|
+
|
353
|
+
# Change the power cap on the card -- microWatts
|
354
|
+
def set_pcap(uwatts, force=false)
|
355
|
+
# Potentially potentially damaging operation, must supply force param
|
356
|
+
if !force then
|
357
|
+
return unsafe_warning('set_pcap')
|
358
|
+
else
|
359
|
+
# Make sure user is root
|
360
|
+
if RadeonNoise.root then
|
361
|
+
# Writes the minimum value, as the minimum is either below the maximum
|
362
|
+
# power cap or the maximum power cap itself. Absolute value
|
363
|
+
# is applied, so that a negative cannot be entered
|
364
|
+
uwatts.to_i.then { |w|
|
365
|
+
File.write("#{@cache[:dir]}/#{@config[:pcap]}", [w, @cache[:power_max]].min.abs, mode: "r+")}
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end # End pcap setter
|
369
|
+
|
370
|
+
# Change performance type (manual / auto)
|
371
|
+
# /device/power_dpm_force_performance_level
|
372
|
+
# "manual" / "auto"
|
373
|
+
def set_dpm(setting)
|
374
|
+
if RadeonNoise.root then
|
375
|
+
if [:manual, :auto].include?(setting) then
|
376
|
+
File.write("#{@cache[:dir]}/#{@config[:dpm_level]}", setting, mode: "r+")
|
377
|
+
else
|
378
|
+
puts "DPM force setting can only be 'manual' or 'auto'"
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end # End dpm setter
|
382
|
+
|
383
|
+
# Set the PWM type
|
384
|
+
# ONLY USABLE AS ROOT
|
385
|
+
#
|
386
|
+
# Takes a user-entered value to choose the PWM mode
|
387
|
+
# setting -- no control, manual control, or automatic control.
|
388
|
+
#
|
389
|
+
# Use `pwmtype` to check validity. If invalid, just print an error.
|
390
|
+
# Otherwise, if the value is returned as a symbol, pass it back to
|
391
|
+
# produce and integer to write to the file. Otherwise, write the
|
392
|
+
# returned integer directly
|
393
|
+
def set_pwmt(val)
|
394
|
+
if RadeonNoise.root then
|
395
|
+
pwmtype(val).then { |v| # If the pwmtype output is valid
|
396
|
+
if Integer === v or Symbol === v then
|
397
|
+
(Integer === v ? v : pwmtype(v)).then { |data| # Only use Integers
|
398
|
+
File.write("#{@cache[:dir]}/pwm1_enable", data, mode: "r+") }
|
399
|
+
else # If the pwmtype output is any kind of error message
|
400
|
+
puts "#{v}"
|
401
|
+
end }
|
402
|
+
end
|
403
|
+
end # End setter for PWM type
|
404
|
+
|
405
|
+
# PWM speed controller
|
406
|
+
# ONLY USABLE AS ROOT
|
407
|
+
#
|
408
|
+
# Take either a string or integer and write it to the PWM
|
409
|
+
# speed file. The value must be less than :pwm_max, so before
|
410
|
+
# writing, take the min of the max and input values.
|
411
|
+
#
|
412
|
+
# If the value is an int already, take the absolute value, to
|
413
|
+
# ensure only a positive (or zero) number is entered. If it is
|
414
|
+
# a string, match it to an int, automatically removing ANY non-digits
|
415
|
+
# (including negatives).
|
416
|
+
def set_pwm(input)
|
417
|
+
if RadeonNoise.root then
|
418
|
+
[@cache[:dir], @cache[:pwm_max]].then { |d, mx|
|
419
|
+
if Integer === input then # If integer, write directly
|
420
|
+
File.write("#{d}/pwm1", [input.abs, mx].min, mode: "r+")
|
421
|
+
elsif String === input and input.match?(/^\d+$/) then
|
422
|
+
input.to_i.then { |data| # If string, convert to int
|
423
|
+
File.write("#{d}/pwm1", [data, mx].min, mode: "r+") }
|
424
|
+
else # Otherwise, error
|
425
|
+
puts "#{input} is not valid. Please enter a positive integer up to #{mx}"
|
426
|
+
end }
|
427
|
+
end
|
428
|
+
end # End setter for PWM speed
|
429
|
+
end # End class AMDGPUCard
|
430
|
+
end # End module
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# lib/radeonnoise/curve.rb
|
2
|
+
|
3
|
+
# This module will handle fan curve operations.
|
4
|
+
#
|
5
|
+
# It will process a card's fan curve profile, load in
|
6
|
+
# fan curve configurations, and operate offer the necessary
|
7
|
+
# interfaces to run a fan curve server / supervisor.
|
8
|
+
module Curve
|
9
|
+
|
10
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# lib/radeonnoise/parts.rb
|
2
|
+
|
3
|
+
# This module will handle several part objects
|
4
|
+
# and their abstract representations as components
|
5
|
+
# of a Radeon AMDGPU graphics card.
|
6
|
+
#
|
7
|
+
# They operate to represent possible multi-unit
|
8
|
+
# components, such as fans, graphics cores, power
|
9
|
+
# data, PWM components, and temperature sensors
|
10
|
+
module Component
|
11
|
+
# Inherited class
|
12
|
+
# No implementations.
|
13
|
+
class AbstractComponent
|
14
|
+
# Headers is being used to refer to the
|
15
|
+
# subclass-specific file prefixes and suffixes,
|
16
|
+
# which are merged with the indices to generate
|
17
|
+
# a full filename to be read from on each update.
|
18
|
+
#
|
19
|
+
# Files associated with the object
|
20
|
+
# Items of that type
|
21
|
+
attr_reader :path, :prefixes, :current
|
22
|
+
|
23
|
+
# Use the path (should end with 'hwmon[number]/*')
|
24
|
+
# to glob all the files by the object type's regex, rg
|
25
|
+
def initialize(rg, path)
|
26
|
+
# Initialize the object variables
|
27
|
+
@current, @prefixes, @path = {}, pfx(rg, index(rg, path)), path.gsub("/*", "")
|
28
|
+
|
29
|
+
# Create a card index for each prefix
|
30
|
+
@prefixes.each { |pref| @current[pref] = {} }
|
31
|
+
update
|
32
|
+
end # End constructor
|
33
|
+
|
34
|
+
# Attach regex components to
|
35
|
+
# item indices, so they can be used
|
36
|
+
# with the specific component items
|
37
|
+
def index(rg, path)
|
38
|
+
Dir.glob(path)
|
39
|
+
.reject { |item| !item.match?(rg) }
|
40
|
+
.collect { |item| item.split("/").last.gsub(/\D/, '').to_sym }
|
41
|
+
end # End index
|
42
|
+
|
43
|
+
# Generate prefixes
|
44
|
+
def pfx(rg, arr)
|
45
|
+
rg.source.gsub(/\\.*$/, '').then { |pf|
|
46
|
+
arr.collect { |item| "#{pf}#{item}".to_sym }}
|
47
|
+
end # End prefix
|
48
|
+
|
49
|
+
# Update the local data
|
50
|
+
# Set the values that don't change, permanently
|
51
|
+
def update
|
52
|
+
puts "method 'update' is not implemented for #{self.class}, yet"
|
53
|
+
end # End update
|
54
|
+
end # End AbstractComponent class
|
55
|
+
|
56
|
+
# Temp loader class
|
57
|
+
class Temp < AbstractComponent
|
58
|
+
# For temp, will read the files:
|
59
|
+
# -input (current temp)
|
60
|
+
# -label (name of sensor)
|
61
|
+
# -crit (critical max temperature)
|
62
|
+
# -crit_hyst (critical hysteresis temp)
|
63
|
+
# Permanent values are crit, crit_hyst, and label
|
64
|
+
def update
|
65
|
+
# Fill in all the data, only fill the permanent ones
|
66
|
+
# on the first load.
|
67
|
+
#
|
68
|
+
# Label should be stripped of trailing spaces, and
|
69
|
+
# all other values should be converted to a float and
|
70
|
+
# divided by 1000, to change back into degrees Celsius
|
71
|
+
@current.each do |k,v|
|
72
|
+
@current[k][:critical] ||= File.read("#{@path}/#{k}_crit").to_f / 1000
|
73
|
+
@current[k][:hysteresis] ||= File.read("#{@path}/#{k}_crit_hyst").to_f / 1000
|
74
|
+
@current[k][:label] ||= File.read("#{@path}/#{k}_label").strip
|
75
|
+
@current[k][:current] = File.read("#{@path}/#{k}_input").to_f / 1000
|
76
|
+
end
|
77
|
+
end # End update method
|
78
|
+
end # End Temp class
|
79
|
+
|
80
|
+
# Fan loader class
|
81
|
+
class Fan < AbstractComponent
|
82
|
+
# Read in the enabled state, max state, and target state.
|
83
|
+
#
|
84
|
+
# Although _target can be read in and manipulated, it is pointlessly
|
85
|
+
# risky to manipulate it, when the PWM controller in the core module
|
86
|
+
# already does this.
|
87
|
+
#
|
88
|
+
# Due to it being a mechanical RPM target, it will also
|
89
|
+
# CONSTANTLY need to readjust, rather than just keeping the pulse width
|
90
|
+
# setting the same and letting the motor do the lifting. Setting this
|
91
|
+
# forces the driver to take an active role in manipulating the speed, when
|
92
|
+
# it changes by even a small margin.
|
93
|
+
#
|
94
|
+
## Whether I will add this later or recommend users overload it themselves, ##
|
95
|
+
## I am not yet sure. Unlike the power cap, this does not provide any real ##
|
96
|
+
## benefit, and it can freeze up the driver, at least as tested on my system. ##
|
97
|
+
def update
|
98
|
+
@current.each do |k,v|
|
99
|
+
# If the value, when converted to an integer, is 1, it is enabled.
|
100
|
+
# If it is 1, the result should be true. `.zero?` returns true if 0,
|
101
|
+
# so invert it.
|
102
|
+
@current[k][:enabled] = !File.read("#{@path}/#{k}_enable").to_i.zero?
|
103
|
+
# The current speed of the fan
|
104
|
+
@current[k][:rpm_current] = File.read("#{@path}/#{k}_input").to_i
|
105
|
+
# As the below 2 values, _min and _max, cannot be changed, I have
|
106
|
+
# implemented them as such.
|
107
|
+
#
|
108
|
+
# _min is probably locked by firmware or the card's BIOS. However,
|
109
|
+
# it is here, anyway. Even root cannot modify this.
|
110
|
+
@current[k][:rpm_min] ||= File.read("#{@path}/#{k}_min").to_i
|
111
|
+
# _max does not seem editable either, but unlike _min, it is
|
112
|
+
# definitely useful to know the card's max RPM.
|
113
|
+
@current[k][:rpm_max] ||= File.read("#{@path}/#{k}_max").to_i
|
114
|
+
end
|
115
|
+
end # End update
|
116
|
+
end # End Fan class
|
117
|
+
end # End Component module
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: radeonnoise
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Edelweiss
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-04-19 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: |
|
14
|
+
Ruby module to provide access to Linux's AMDGPU
|
15
|
+
driver API from Ruby scripts.
|
16
|
+
|
17
|
+
+ Control operations require `root` access.
|
18
|
+
email:
|
19
|
+
executables: []
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files:
|
22
|
+
- README.md
|
23
|
+
- CHANGELOG.md
|
24
|
+
files:
|
25
|
+
- CHANGELOG.md
|
26
|
+
- README.md
|
27
|
+
- lib/radeonnoise.rb
|
28
|
+
- lib/radeonnoise/core.rb
|
29
|
+
- lib/radeonnoise/curve.rb
|
30
|
+
- lib/radeonnoise/parts.rb
|
31
|
+
- lib/radeonnoise/version.rb
|
32
|
+
homepage: https://github.com/KleineEdelweiss/radeonnoise_rb
|
33
|
+
licenses:
|
34
|
+
- LGPL-3.0
|
35
|
+
metadata:
|
36
|
+
homepage_uri: https://github.com/KleineEdelweiss/radeonnoise_rb
|
37
|
+
source_code_uri: https://github.com/KleineEdelweiss/radeonnoise_rb
|
38
|
+
bug_tracker_uri: https://github.com/KleineEdelweiss/radeonnoise_rb/issues
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- "--title"
|
42
|
+
- RadeonNoise RB -- Linux AMDGPU Controller
|
43
|
+
- "--main"
|
44
|
+
- README.md
|
45
|
+
- "--line-numbers"
|
46
|
+
- "--inline-source"
|
47
|
+
- "--quiet"
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.7.0
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubygems_version: 3.2.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: Linux AMDGPU controller in Ruby
|
65
|
+
test_files: []
|