class RadeonNoise::AMDGPUCard
Holds data associated with each AMDGPU card and provides access to its fans and settings.
Constants
- PROTECTLVL
- PWMERR
- PWMLVL
Attributes
Store the current settings for the graphics card: # detail is the sysfs hardware object data # sclk is the processor clock # mclk is the memory clock # pwm is whether fans are manual (true) or auto (false) # fan_curve is a list of values for the fan speeds # by temp.
Store the current settings for the graphics card: # detail is the sysfs hardware object data # sclk is the processor clock # mclk is the memory clock # pwm is whether fans are manual (true) or auto (false) # fan_curve is a list of values for the fan speeds # by temp.
Public Class Methods
Constructor
# File lib/radeonnoise/core.rb 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
Public Instance Methods
Split multi-line read-ins and then select the active option only (for settings)
# File lib/radeonnoise/core.rb 301 def active_s(data) 302 data.split(/\n/) 303 .reject { |item| !item.strip.match?(/\*$/) } 304 .collect { |item| item.split(":").last.strip } 305 end
Read fan data
# File lib/radeonnoise/core.rb 270 def fans 271 [@config[:rg_fans], @cache[:dir]].then { |rg, d| 272 {fans: Component::Fan.new(rg, "#{d}/*")} } 273 end
PWM control type Accepts: [Integer, String, Symbol]
If String, convert to lowercase w/o whitespace, then check if it's an Integer or Symbol, and recurse.
If Integer, check if in PWMLVL
. If Symbol, check if in inverse hash of PWMLVL
. Return `nil`, if not present – up to user how to handle. If anything else, return error message.
# File lib/radeonnoise/core.rb 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
Change performance type (manual / auto) /device/power_dpm_force_performance_level “manual” / “auto”
# File lib/radeonnoise/core.rb 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
Change the power cap on the card – microWatts
# File lib/radeonnoise/core.rb 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
Set the level of unsafe protection
# File lib/radeonnoise/core.rb 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
PWM speed controller ONLY USABLE AS ROOT
Take either a string or integer and write it to the PWM speed file. The value must be less than :pwm_max, so before writing, take the min of the max and input values.
If the value is an int already, take the absolute value, to ensure only a positive (or zero) number is entered. If it is a string, match it to an int, automatically removing ANY non-digits (including negatives).
# File lib/radeonnoise/core.rb 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
Set the PWM type ONLY USABLE AS ROOT
Takes a user-entered value to choose the PWM mode setting – no control, manual control, or automatic control.
Use `pwmtype` to check validity. If invalid, just print an error. Otherwise, if the value is returned as a symbol, pass it back to produce and integer to write to the file. Otherwise, write the returned integer directly
# File lib/radeonnoise/core.rb 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
Stat the current cached data
# File lib/radeonnoise/core.rb 149 def stat() @cache end
Attach the `lspci` subsystem data Essentially, this is the most useful data that is really relevant from the command, at least for the purpose of this interface. I may add more, though.
# File lib/radeonnoise/core.rb 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
Temperatures
# File lib/radeonnoise/core.rb 261 def temps 262 [@config[:rg_temps], @cache[:dir]].then { |rg, d| 263 {temps: Component::Temp.new(rg, "#{d}/*")} } 264 end
Update core device stats
# File lib/radeonnoise/core.rb 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
Update the fan data
# File lib/radeonnoise/core.rb 276 def ufans() @cache[:fans].update end
Frequencies sclk is the processor clock mclk is the VRAM clock
# File lib/radeonnoise/core.rb 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
Warn the user about unsafe operations
# File lib/radeonnoise/core.rb 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
PCIe speed Although this is a
# File lib/radeonnoise/core.rb 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
Update the card values
# File lib/radeonnoise/core.rb 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
Power consumption This monitors the current and maximum possible power usage of the card. 'pcap', however, can be lower than the card's physical limit, if set as such
# File lib/radeonnoise/core.rb 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
PWM (fan power)
# File lib/radeonnoise/core.rb 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
Update the temperatures
# File lib/radeonnoise/core.rb 267 def utemps() @cache[:temps].update end
Voltage The inputs were re-classified from a previous commit: they have a specific meaning and are not actually variable. in0 is the graphics processor, while in1 is the northbridge, if a sensor is present.
As such, will update with a value or empty.
# File lib/radeonnoise/core.rb 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