radeonnoise 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,5 @@
1
+ # Change log
2
+ This file will document any important changes to the RadeonNoise gem
3
+
4
+ ## [INITIAL RELEASE]
5
+ -radeonnoise-0.1.0
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
+ ```
@@ -0,0 +1,3 @@
1
+ # lib/radeonnoise.rb
2
+ # Main file
3
+ require_relative './radeonnoise/core.rb'
@@ -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
@@ -0,0 +1,4 @@
1
+ # lib/radeonnoise/version.rb
2
+ module RadeonNoise
3
+ VERSION = "0.1.0"
4
+ end
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: []