amdgpu_fan 0.5.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +24 -1
- data/config/environment.rb +5 -0
- data/config/icons.yml +10 -0
- data/lib/amdgpu_fan/cli.rb +74 -25
- data/lib/amdgpu_fan/connector.rb +21 -10
- data/lib/amdgpu_fan/mixin/cli_output_format.rb +23 -7
- data/lib/amdgpu_fan/mixin/fan.rb +1 -2
- data/lib/amdgpu_fan/mixin/sys_write.rb +1 -2
- data/lib/amdgpu_fan/pci.rb +66 -0
- data/lib/amdgpu_fan/service.rb +42 -10
- data/lib/amdgpu_fan/stat_set.rb +25 -0
- data/lib/amdgpu_fan/version.rb +6 -0
- data/lib/amdgpu_fan/watcher.rb +59 -0
- metadata +99 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fb09354467c6ffeb603c4cbc72b131c626e83d4403148ebeb5fac342222ac29
|
4
|
+
data.tar.gz: b62420cbf660f704682f34565128c749216bb5605b3d57b843f4bc85671aa09c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9a94891e8024c9e2c58c46feb4b7654e69978a9215278d9588065db177e8df447670cad9be726f608f16bd77f4a26e2a05fe16c536ce42bce0c9242489d2b43
|
7
|
+
data.tar.gz: 8046858789272ed17ce75304f53ee3915973193bc81bd301335448456c99bd8d4d774962668b2b98388a24301e158d4cffb18a65a28d27c7fd45c130c639740d
|
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# amdgpu_fan
|
2
2
|
|
3
|
+
[](https://badge.fury.io/rb/amdgpu_fan)
|
3
4
|
[](https://travis-ci.org/HarlemSquirrel/amdgpu-fan-rb) [](https://codeclimate.com/github/HarlemSquirrel/amdgpu-fan-rb/maintainability)
|
4
5
|
|
5
6
|
A Ruby CLI to read and set fan speed, power profiles, and more for AMD Radeon graphics cards running on the AMDGPU Linux driver.
|
@@ -74,8 +75,30 @@ Watching Advanced Micro Devices, Inc. [AMD/ATI] Radeon R9 FURY X / NANO every 3
|
|
74
75
|
And now the watch is ended.
|
75
76
|
```
|
76
77
|
|
78
|
+
```
|
79
|
+
➤ bin/amdgpu_fan watch_avg
|
80
|
+
Watching Sapphire Technology Limited Vega 10 XL/XT [Radeon RX Vega 56/64] min, max and averges since
|
81
|
+
2020-06-02 23:05:20 -0400...
|
82
|
+
<Press Ctrl-C to exit>
|
83
|
+
⏰ Core clock min: 852 MHz avg: 887.0 MHz max: 1200 MHz now: 852 MHz
|
84
|
+
💾 Memory clk min: 167 MHz avg: 227.1 MHz max: 945 MHz now: 167 MHz
|
85
|
+
🌀 Fan speed min: 1231 RPM avg: 1231.0 RPM max: 1231 RPM now: 1231 RPM
|
86
|
+
⚡ Power usage min: 6.0 W avg: 21.8 W max: 141.0 W now: 6.0 W
|
87
|
+
🌡 Temperature min: 30 °C avg: 31.3 °C max: 35 °C now: 32 °C
|
88
|
+
^C
|
89
|
+
And now the watch is ended.
|
90
|
+
```
|
91
|
+
|
77
92
|
## Dependencies
|
78
93
|
|
79
94
|
- [Ruby](https://www.ruby-lang.org) with [Bundler](https://bundler.io)
|
80
95
|
- [Thor](http://whatisthor.com/) (installed with `bundle install`)
|
81
|
-
-
|
96
|
+
- Internet connection - Device info is retrieved from https://pci-ids.ucw.cz/
|
97
|
+
|
98
|
+
## Building a binary
|
99
|
+
|
100
|
+
[Ruby Packer](https://github.com/pmq20/ruby-packer) provides a convenient way to compile this into a single executable. For the best results, compile Ruby Packer from source from the lastest master branch.
|
101
|
+
|
102
|
+
```sh
|
103
|
+
rubyc amdgpu_fan --output amdgpu_fan
|
104
|
+
```
|
data/config/environment.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'async'
|
3
4
|
require 'thor'
|
4
5
|
|
5
6
|
require_relative '../lib/amdgpu_fan'
|
7
|
+
require_relative '../lib/amdgpu_fan/version'
|
6
8
|
|
7
9
|
require_relative '../lib/amdgpu_fan/mixin/cli_output_format'
|
8
10
|
require_relative '../lib/amdgpu_fan/mixin/sys_write'
|
9
11
|
|
10
12
|
require_relative '../lib/amdgpu_fan/service'
|
11
13
|
require_relative '../lib/amdgpu_fan/cli'
|
14
|
+
require_relative '../lib/amdgpu_fan/pci'
|
15
|
+
require_relative '../lib/amdgpu_fan/stat_set'
|
16
|
+
require_relative '../lib/amdgpu_fan/watcher'
|
data/config/icons.yml
ADDED
data/lib/amdgpu_fan/cli.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# rubocop:disable Metrics/AbcSize, Metrics/ClassLength, Metrics/MethodLength
|
4
|
+
|
5
|
+
require 'yaml'
|
6
|
+
|
3
7
|
module AmdgpuFan
|
4
8
|
# The command-line interface class
|
5
9
|
class Cli < Thor
|
6
10
|
include CliOutputFormat
|
7
11
|
|
12
|
+
ICONS = YAML.safe_load(File.read(File.join(__dir__, '../../config/icons.yml')))
|
13
|
+
.transform_keys(&:to_sym).freeze
|
8
14
|
WATCH_FIELD_SEPARATOR = ' | '
|
9
15
|
|
10
16
|
desc 'connectors', 'View the status of the display connectors.'
|
@@ -52,22 +58,28 @@ module AmdgpuFan
|
|
52
58
|
desc 'status [--logo]', 'View device info, current fan speed, and temperature.'
|
53
59
|
def status(option = nil)
|
54
60
|
puts radeon_logo if option == '--logo'
|
55
|
-
puts
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
"
|
61
|
+
puts ICONS[:gpu] + ' GPU:'.ljust(9) + amdgpu_service.name
|
62
|
+
puts ICONS[:vbios] + ' vBIOS:'.ljust(9) + amdgpu_service.vbios_version
|
63
|
+
puts "#{ICONS[:display]} Displays:#{amdgpu_service.display_names.join(', ')}"
|
64
|
+
puts ICONS[:clock] + ' Clocks:'.ljust(9) + clock_status
|
65
|
+
puts ICONS[:memory] + ' Memory:'.ljust(9) + mem_total_mibibyes
|
66
|
+
puts ICONS[:fan] + ' Fan:'.ljust(9) + fan_status
|
67
|
+
puts ICONS[:temp] + ' Temp:'.ljust(9) + "#{amdgpu_service.temperature}°C"
|
68
|
+
puts ICONS[:power] + ' Power:'.ljust(9) +
|
69
|
+
"#{amdgpu_service.profile_mode} profile in " \
|
70
|
+
"#{amdgpu_service.power_dpm_state} mode using " \
|
71
|
+
"#{amdgpu_service.power_draw} / #{amdgpu_service.power_max} Watts "\
|
72
|
+
"(#{amdgpu_service.power_draw_percent}%)"
|
73
|
+
puts ICONS[:load] + ' Load:'.ljust(9) + percent_meter(amdgpu_service.busy_percent)
|
74
|
+
end
|
75
|
+
|
76
|
+
desc 'version', 'Print the application version.'
|
77
|
+
def version
|
78
|
+
puts AmdgpuFan::VERSION
|
67
79
|
end
|
68
80
|
|
69
81
|
desc 'watch [SECONDS]', 'Watch fan speed, load, power, and temperature ' \
|
70
|
-
|
82
|
+
'refreshed every n seconds.'
|
71
83
|
def watch(seconds = 1)
|
72
84
|
return puts 'Seconds must be from 1 to 600' unless (1..600).cover?(seconds.to_i)
|
73
85
|
|
@@ -91,12 +103,41 @@ module AmdgpuFan
|
|
91
103
|
end
|
92
104
|
end
|
93
105
|
|
106
|
+
desc 'watch_avg',
|
107
|
+
<<~DOC
|
108
|
+
Watch min, max, average, and current stats.
|
109
|
+
DOC
|
110
|
+
def watch_avg
|
111
|
+
puts "Watching #{amdgpu_service.name} min, max and averges since #{Time.now}...",
|
112
|
+
' <Press Ctrl-C to exit>',
|
113
|
+
"\n\n\n\n\n"
|
114
|
+
|
115
|
+
trap 'SIGINT' do
|
116
|
+
puts "\nAnd now the watch is ended."
|
117
|
+
exit 0
|
118
|
+
end
|
119
|
+
|
120
|
+
watcher = Watcher.new amdgpu_service
|
121
|
+
|
122
|
+
loop do
|
123
|
+
watcher.measure
|
124
|
+
5.times { print "\033[K\033[A" } # move up a line and clear to end of line
|
125
|
+
|
126
|
+
puts "#{ICONS[:clock]} Core clock #{watcher.core_clock}",
|
127
|
+
"#{ICONS[:memory]} Memory clk #{watcher.memory_clock}",
|
128
|
+
"#{ICONS[:fan]} Fan speed #{watcher.fan_speed_rpm}",
|
129
|
+
"#{ICONS[:power]} Power usage #{watcher.power_draw}",
|
130
|
+
"#{ICONS[:temp]} Temperature #{watcher.temperature}"
|
131
|
+
sleep 1
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
94
135
|
desc 'watch_csv [SECONDS]', 'Watch stats in CSV format ' \
|
95
|
-
|
136
|
+
'refreshed every n seconds defaulting to 1 second.'
|
96
137
|
def watch_csv(seconds = 1)
|
97
138
|
return puts 'Seconds must be from 1 to 600' unless (1..600).cover?(seconds.to_i)
|
98
139
|
|
99
|
-
puts 'Timestamp,
|
140
|
+
puts 'Timestamp,Core Clock (Mhz),Memory Clock (Mhz),Fan speed (rpm),'\
|
100
141
|
'Load (%),Power (Watts),Temp (°C)'
|
101
142
|
|
102
143
|
trap 'SIGINT' do
|
@@ -104,13 +145,19 @@ module AmdgpuFan
|
|
104
145
|
end
|
105
146
|
|
106
147
|
loop do
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
148
|
+
list = []
|
149
|
+
threads = [
|
150
|
+
Thread.new { list[0] = amdgpu_service.core_clock },
|
151
|
+
Thread.new { list[1] = amdgpu_service.memory_clock },
|
152
|
+
Thread.new { list[2] = amdgpu_service.fan_speed_rpm },
|
153
|
+
Thread.new { list[3] = amdgpu_service.busy_percent },
|
154
|
+
Thread.new { list[4] = amdgpu_service.power_draw },
|
155
|
+
Thread.new { list[5] = amdgpu_service.temperature }
|
156
|
+
]
|
157
|
+
threads.each(&:join)
|
158
|
+
|
159
|
+
puts [Time.now.strftime('%F %T'), *list].join(',')
|
160
|
+
|
114
161
|
sleep seconds.to_i
|
115
162
|
end
|
116
163
|
end
|
@@ -127,7 +174,7 @@ module AmdgpuFan
|
|
127
174
|
|
128
175
|
def fan_status
|
129
176
|
"#{amdgpu_service.fan_mode} mode running at " \
|
130
|
-
|
177
|
+
"#{amdgpu_service.fan_speed_rpm} rpm (#{amdgpu_service.fan_speed_percent}%)"
|
131
178
|
end
|
132
179
|
|
133
180
|
def mem_total_mibibyes
|
@@ -139,8 +186,8 @@ module AmdgpuFan
|
|
139
186
|
end
|
140
187
|
|
141
188
|
def summary_clock
|
142
|
-
"Core: #{amdgpu_service.core_clock.rjust(7)}#{WATCH_FIELD_SEPARATOR}"\
|
143
|
-
"Memory: #{amdgpu_service.memory_clock.rjust(7)}"
|
189
|
+
"Core: #{amdgpu_service.core_clock.to_s.rjust(7)}#{WATCH_FIELD_SEPARATOR}"\
|
190
|
+
"Memory: #{amdgpu_service.memory_clock.to_s.rjust(7)}"
|
144
191
|
end
|
145
192
|
|
146
193
|
def summary_fan
|
@@ -163,3 +210,5 @@ module AmdgpuFan
|
|
163
210
|
end
|
164
211
|
end
|
165
212
|
end
|
213
|
+
|
214
|
+
# rubocop:enable Metrics/AbcSize, Metrics/ClassLength, Metrics/MethodLength
|
data/lib/amdgpu_fan/connector.rb
CHANGED
@@ -6,11 +6,11 @@ module AmdgpuFan
|
|
6
6
|
# A model class for a GPU connector
|
7
7
|
class Connector
|
8
8
|
EDID_DESCRIPTORS_CONF = {
|
9
|
-
|
9
|
+
display_name_leading_bytes: String.new('\x00\xFC\x00', encoding: 'ascii-8bit'),
|
10
|
+
unspecified_text_leading_bytes: String.new('\x00\xFE\x00', encoding: 'ascii-8bit'),
|
10
11
|
index_range: (54..125)
|
11
12
|
}.freeze
|
12
13
|
|
13
|
-
|
14
14
|
attr_reader :card_num, :dir_path, :index, :type
|
15
15
|
|
16
16
|
class << self
|
@@ -18,11 +18,11 @@ module AmdgpuFan
|
|
18
18
|
# Return an array of connector objects for the provided card number.
|
19
19
|
# The files are sorted to improve how they are displayed to the user.
|
20
20
|
def where(card_num:)
|
21
|
-
Dir["/sys/class/drm/card#{card_num}/card#{card_num}-*"].
|
22
|
-
Connector.new card_num
|
23
|
-
dir_path
|
21
|
+
Dir["/sys/class/drm/card#{card_num}/card#{card_num}-*"].map do |dir_path|
|
22
|
+
Connector.new card_num:,
|
23
|
+
dir_path:,
|
24
24
|
index: dir_path[-1],
|
25
|
-
type: dir_path.slice(/(?<=card#{card_num}-)[A-
|
25
|
+
type: dir_path.slice(/(?<=card#{card_num}-)[A-z]+/)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -41,10 +41,7 @@ module AmdgpuFan
|
|
41
41
|
def display_name
|
42
42
|
return if edid.to_s.empty?
|
43
43
|
|
44
|
-
|
45
|
-
.scan(/(?<=#{EDID_DESCRIPTORS_CONF[:display_descriptor_leading_bytes]}).{1,13}/)
|
46
|
-
.first
|
47
|
-
.strip
|
44
|
+
(display_name_text + unspecified_text).join(' ').strip
|
48
45
|
end
|
49
46
|
|
50
47
|
def status
|
@@ -53,8 +50,22 @@ module AmdgpuFan
|
|
53
50
|
|
54
51
|
private
|
55
52
|
|
53
|
+
def display_descriptors_raw
|
54
|
+
edid.slice EDID_DESCRIPTORS_CONF[:index_range]
|
55
|
+
end
|
56
|
+
|
57
|
+
def display_name_text
|
58
|
+
display_descriptors_raw
|
59
|
+
.scan(/(?<=#{EDID_DESCRIPTORS_CONF[:display_name_leading_bytes]}).{1,13}/)
|
60
|
+
end
|
61
|
+
|
56
62
|
def edid
|
57
63
|
File.read("#{dir_path}/edid", encoding: 'ascii-8bit')
|
58
64
|
end
|
65
|
+
|
66
|
+
def unspecified_text
|
67
|
+
display_descriptors_raw
|
68
|
+
.scan(/(?<=#{EDID_DESCRIPTORS_CONF[:unspecified_text_leading_bytes]}).{1,13}/)
|
69
|
+
end
|
59
70
|
end
|
60
71
|
end
|
@@ -3,19 +3,35 @@
|
|
3
3
|
module AmdgpuFan
|
4
4
|
# A mixin to help with CLI output formatting
|
5
5
|
module CliOutputFormat
|
6
|
-
|
6
|
+
METER_CHARS = [' ', *("\u2588".."\u258F").to_a.reverse].freeze
|
7
7
|
TIME_FORMAT = '%F %T'
|
8
8
|
|
9
|
-
private
|
10
|
-
|
11
9
|
def current_time
|
12
10
|
Time.now.strftime(TIME_FORMAT)
|
13
11
|
end
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
13
|
+
##
|
14
|
+
# Return a string with meter and percentage for +percent+
|
15
|
+
#
|
16
|
+
def percent_meter(percent, width = 3)
|
17
|
+
meter_char_indexes = []
|
18
|
+
percent_portion_size = 100.0 / width
|
19
|
+
width.times do |i|
|
20
|
+
current_portion = percent.to_i - (percent_portion_size * i)
|
21
|
+
|
22
|
+
current_portion_percent = if current_portion >= percent_portion_size
|
23
|
+
1
|
24
|
+
elsif current_portion <= 0
|
25
|
+
0
|
26
|
+
else
|
27
|
+
current_portion / percent_portion_size
|
28
|
+
end
|
29
|
+
|
30
|
+
meter_char_indexes << ((METER_CHARS.length - 1) * current_portion_percent.to_f).round
|
31
|
+
end
|
32
|
+
|
33
|
+
percent_string = "#{format '%<num>0.2i', num: percent}%".ljust(4)
|
34
|
+
"#{percent_string}[#{meter_char_indexes.map { |i| METER_CHARS[i] }.join}]"
|
19
35
|
end
|
20
36
|
|
21
37
|
def radeon_logo
|
data/lib/amdgpu_fan/mixin/fan.rb
CHANGED
@@ -4,7 +4,6 @@ module AmdgpuFan
|
|
4
4
|
##
|
5
5
|
# A mixin to read fan details and validate input
|
6
6
|
module Fan
|
7
|
-
|
8
7
|
private
|
9
8
|
|
10
9
|
def fan_file(type)
|
@@ -37,7 +36,7 @@ module AmdgpuFan
|
|
37
36
|
end
|
38
37
|
|
39
38
|
def valid_fan_percent_speed?(percent)
|
40
|
-
(1..100.to_i).cover?(percent.to_i)
|
39
|
+
(1..(100.to_i)).cover?(percent.to_i)
|
41
40
|
end
|
42
41
|
end
|
43
42
|
end
|
@@ -3,11 +3,10 @@
|
|
3
3
|
module AmdgpuFan
|
4
4
|
# A mixin to help with writing to system files
|
5
5
|
module SysWrite
|
6
|
-
|
7
6
|
private
|
8
7
|
|
9
8
|
##
|
10
|
-
# Write to a system file with elevated priviledges.
|
9
|
+
# Write to a system file with elevated priviledges.
|
11
10
|
def sudo_write(file_path, value)
|
12
11
|
`echo "#{value}" | sudo tee #{file_path}`
|
13
12
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
4
|
+
|
5
|
+
require 'net/http'
|
6
|
+
|
7
|
+
module AmdgpuFan
|
8
|
+
## PCI
|
9
|
+
#
|
10
|
+
# Retrieve device information from PCI IDs.
|
11
|
+
# https://pci-ids.ucw.cz/
|
12
|
+
#
|
13
|
+
class PCI
|
14
|
+
TMP_FILE = '/tmp/pci.ids.txt'
|
15
|
+
URL = 'https://pci-ids.ucw.cz/v2.2/pci.ids'
|
16
|
+
|
17
|
+
##
|
18
|
+
# Return the device name if available
|
19
|
+
#
|
20
|
+
def self.device_name(vendor_id, device_id, subdevice_id)
|
21
|
+
device = info.dig vendor_id, :devices, device_id
|
22
|
+
|
23
|
+
device.dig(subdevice_id, :name) || device[:name]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.vendor_name(vendor_id)
|
27
|
+
info.dig vendor_id, :name
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.info
|
31
|
+
current_vendor_id = nil
|
32
|
+
current_device_id = nil
|
33
|
+
|
34
|
+
@info = raw.each_line.with_object({}) do |line, hash|
|
35
|
+
next if line.empty? || line.start_with?('#')
|
36
|
+
|
37
|
+
if line[0] =~ /(\d|[a-z])/
|
38
|
+
# Vendor line
|
39
|
+
current_vendor_id = line.split(' ').first.to_i(16)
|
40
|
+
vendor_name = line.split(' ').last.strip
|
41
|
+
hash[current_vendor_id] = { name: vendor_name, devices: {} }
|
42
|
+
elsif line[0..1] =~ /\t\w/
|
43
|
+
# Device line
|
44
|
+
current_device_id = line.split(' ').first.to_i(16)
|
45
|
+
device_name = line.split(' ').last.strip
|
46
|
+
hash[current_vendor_id][:devices][current_device_id] = { name: device_name }
|
47
|
+
elsif line[0..2] =~ /\t\t\w/
|
48
|
+
# Subvendor line
|
49
|
+
# subvendor_id = line.split(' ').first.to_i(16)
|
50
|
+
subdevice_id = line.split[1].to_i(16)
|
51
|
+
subdevice_name = line.split(' ')[1].strip
|
52
|
+
hash[current_vendor_id][:devices][current_device_id][subdevice_id] =
|
53
|
+
{ name: subdevice_name }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.raw
|
59
|
+
File.write(TMP_FILE, Net::HTTP.get(URI(URL))) unless File.exist?(TMP_FILE)
|
60
|
+
|
61
|
+
@raw ||= File.read(TMP_FILE)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
data/lib/amdgpu_fan/service.rb
CHANGED
@@ -4,6 +4,7 @@ require_relative 'mixin/fan'
|
|
4
4
|
require_relative 'mixin/sys_write'
|
5
5
|
|
6
6
|
require_relative 'connector'
|
7
|
+
require_relative 'pci'
|
7
8
|
|
8
9
|
module AmdgpuFan
|
9
10
|
## AmdgpuService
|
@@ -30,21 +31,31 @@ module AmdgpuFan
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def connectors
|
33
|
-
@connectors ||= Connector.where card_num:
|
34
|
+
@connectors ||= Connector.where card_num:
|
34
35
|
end
|
35
36
|
|
36
37
|
def core_clock
|
37
38
|
clock_from_pp_file "#{base_card_dir}/pp_dpm_sclk"
|
38
39
|
end
|
39
40
|
|
41
|
+
def display_names
|
42
|
+
connectors.map(&:display_name).compact
|
43
|
+
end
|
44
|
+
|
40
45
|
def fan_mode
|
41
46
|
FAN_MODES[File.read(fan_mode_file).strip] || 'unknown'
|
42
47
|
end
|
43
48
|
|
49
|
+
##
|
50
|
+
# Set the fan mode to auto or manual.
|
51
|
+
#
|
44
52
|
def fan_mode=(mode)
|
45
53
|
sudo_write fan_mode_file, FAN_MODES.key(mode.to_s)
|
46
54
|
end
|
47
55
|
|
56
|
+
##
|
57
|
+
# Set the fan speed to a percentage if <= 100 or a raw value
|
58
|
+
#
|
48
59
|
def fan_speed=(value)
|
49
60
|
if valid_fan_percent_speed?(value)
|
50
61
|
new_raw = (value.to_f / 100 * fan_raw_speeds(:max).to_i).round
|
@@ -63,20 +74,37 @@ module AmdgpuFan
|
|
63
74
|
(fan_speed_raw.to_f / fan_raw_speeds(:max).to_i * 100).round
|
64
75
|
end
|
65
76
|
|
77
|
+
##
|
78
|
+
# Return the fan speed in revolutions per minute
|
79
|
+
#
|
66
80
|
def fan_speed_rpm
|
67
|
-
File.read(fan_file(:input)).strip
|
81
|
+
File.read(fan_file(:input)).strip.to_i
|
68
82
|
end
|
69
83
|
|
84
|
+
##
|
85
|
+
# Return the current memory clock speed.
|
86
|
+
#
|
70
87
|
def memory_clock
|
71
88
|
clock_from_pp_file "#{base_card_dir}/pp_dpm_mclk"
|
72
89
|
end
|
73
90
|
|
91
|
+
##
|
92
|
+
# Return the total memory on the card in bytes
|
93
|
+
#
|
74
94
|
def memory_total
|
75
95
|
File.read("#{base_card_dir}/mem_info_vram_total").to_i
|
76
96
|
end
|
77
97
|
|
98
|
+
def model_id
|
99
|
+
@model_id ||= File.read(File.join(base_card_dir, '/subsystem_device')).gsub(/\A0x/, '').upcase
|
100
|
+
end
|
101
|
+
|
78
102
|
def name
|
79
|
-
|
103
|
+
return 'Unknown' unless PCI.vendor_name(vendor_id)
|
104
|
+
|
105
|
+
[PCI.vendor_name(vendor_id),
|
106
|
+
PCI.device_name(vendor_id, device_id, subdevice_id) || 'unknown']
|
107
|
+
.join(' ')
|
80
108
|
end
|
81
109
|
|
82
110
|
def power_dpm_state
|
@@ -131,15 +159,11 @@ module AmdgpuFan
|
|
131
159
|
end
|
132
160
|
|
133
161
|
def clock_from_pp_file(file)
|
134
|
-
File.read(file).slice(/\w+(?= \*)/)
|
162
|
+
File.read(file).slice(/\w+(?= \*)/).to_i
|
135
163
|
end
|
136
164
|
|
137
|
-
def
|
138
|
-
@
|
139
|
-
end
|
140
|
-
|
141
|
-
def lspci_subsystem
|
142
|
-
@lspci_subsystem ||= `lspci -v -s #{gpu_pci_id} | grep "Subsystem:"`
|
165
|
+
def device_id
|
166
|
+
@device_id ||= File.read(File.join(base_card_dir, 'device')).to_i(16)
|
143
167
|
end
|
144
168
|
|
145
169
|
def power_avg_file
|
@@ -150,8 +174,16 @@ module AmdgpuFan
|
|
150
174
|
(raw_string.strip.to_f / 1_000_000).round(2)
|
151
175
|
end
|
152
176
|
|
177
|
+
def subdevice_id
|
178
|
+
@subdevice_id ||= File.read(File.join(base_card_dir, 'subsystem_device')).to_i(16)
|
179
|
+
end
|
180
|
+
|
153
181
|
def temperature_file
|
154
182
|
@temperature_file ||= Dir.glob("#{base_card_dir}/**/temp1_input").first
|
155
183
|
end
|
184
|
+
|
185
|
+
def vendor_id
|
186
|
+
@vendor_id ||= File.read(File.join(base_card_dir, 'vendor')).to_i(16)
|
187
|
+
end
|
156
188
|
end
|
157
189
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AmdgpuFan
|
4
|
+
# A set of stats
|
5
|
+
class StatSet < Hash
|
6
|
+
attr_accessor :avg, :max, :min, :now
|
7
|
+
attr_reader :unit
|
8
|
+
|
9
|
+
def initialize(unit)
|
10
|
+
super
|
11
|
+
@unit = unit
|
12
|
+
end
|
13
|
+
|
14
|
+
def stats
|
15
|
+
{ min: min, avg: avg, max: max, now: now }
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Return a string containing all the stats with units.
|
20
|
+
#
|
21
|
+
def to_s
|
22
|
+
stats.map { |k, v| "#{k}: #{v.to_s.rjust(6)} #{unit.ljust(3)} " }.join
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'async'
|
4
|
+
|
5
|
+
require_relative 'stat_set'
|
6
|
+
|
7
|
+
module AmdgpuFan
|
8
|
+
# Keep track of stats over time.
|
9
|
+
class Watcher
|
10
|
+
attr_reader :core_clock, :fan_speed_rpm, :num_measurements, :memory_clock, :power_draw,
|
11
|
+
:temperature
|
12
|
+
|
13
|
+
def initialize(amdgpu_service)
|
14
|
+
@amdgpu_service = amdgpu_service
|
15
|
+
@num_measurements = 0
|
16
|
+
|
17
|
+
@core_clock = StatSet.new 'MHz'
|
18
|
+
@memory_clock = StatSet.new 'MHz'
|
19
|
+
@fan_speed_rpm = StatSet.new 'RPM'
|
20
|
+
@power_draw = StatSet.new 'W'
|
21
|
+
@temperature = StatSet.new '°C'
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Take a new set of measurements and adjust the stats.
|
26
|
+
#
|
27
|
+
def measure
|
28
|
+
@num_measurements += 1
|
29
|
+
|
30
|
+
Async do |task|
|
31
|
+
%i[core_clock fan_speed_rpm memory_clock power_draw temperature].each do |stat|
|
32
|
+
task.async do
|
33
|
+
send(stat).now = amdgpu_service.send(stat)
|
34
|
+
calculate_stats(send(stat))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :amdgpu_service
|
43
|
+
|
44
|
+
def calculate_stats(stat_set)
|
45
|
+
if num_measurements == 1
|
46
|
+
stat_set.min = stat_set.now
|
47
|
+
stat_set.avg = stat_set.now.to_f
|
48
|
+
stat_set.max = stat_set.now
|
49
|
+
return
|
50
|
+
end
|
51
|
+
|
52
|
+
stat_set.min = stat_set.now if stat_set.now < stat_set.min
|
53
|
+
stat_set.avg =
|
54
|
+
((stat_set.now + (stat_set.avg * (num_measurements - 1))) / num_measurements.to_f)
|
55
|
+
.round(1)
|
56
|
+
stat_set.max = stat_set.now if stat_set.now > stat_set.max
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,99 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: amdgpu_fan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin McCormack
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
11
|
+
date: 2022-01-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: async
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.8'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.8'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.1'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.1'
|
13
97
|
description: A CLI for interacting with the amdgpu Linux driver
|
14
98
|
email: harlemsquirrel@gmail.com
|
15
99
|
executables:
|
@@ -20,18 +104,24 @@ files:
|
|
20
104
|
- README.md
|
21
105
|
- bin/amdgpu_fan
|
22
106
|
- config/environment.rb
|
107
|
+
- config/icons.yml
|
23
108
|
- lib/amdgpu_fan.rb
|
24
109
|
- lib/amdgpu_fan/cli.rb
|
25
110
|
- lib/amdgpu_fan/connector.rb
|
26
111
|
- lib/amdgpu_fan/mixin/cli_output_format.rb
|
27
112
|
- lib/amdgpu_fan/mixin/fan.rb
|
28
113
|
- lib/amdgpu_fan/mixin/sys_write.rb
|
114
|
+
- lib/amdgpu_fan/pci.rb
|
29
115
|
- lib/amdgpu_fan/service.rb
|
116
|
+
- lib/amdgpu_fan/stat_set.rb
|
117
|
+
- lib/amdgpu_fan/version.rb
|
118
|
+
- lib/amdgpu_fan/watcher.rb
|
30
119
|
homepage: https://github.com/HarlemSquirrel/amdgpu-fan-rb
|
31
120
|
licenses:
|
32
121
|
- MIT
|
33
|
-
metadata:
|
34
|
-
|
122
|
+
metadata:
|
123
|
+
rubygems_mfa_required: 'true'
|
124
|
+
post_install_message:
|
35
125
|
rdoc_options: []
|
36
126
|
require_paths:
|
37
127
|
- lib
|
@@ -39,15 +129,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
39
129
|
requirements:
|
40
130
|
- - ">="
|
41
131
|
- !ruby/object:Gem::Version
|
42
|
-
version:
|
132
|
+
version: 3.1.0
|
43
133
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
134
|
requirements:
|
45
135
|
- - ">="
|
46
136
|
- !ruby/object:Gem::Version
|
47
137
|
version: '0'
|
48
138
|
requirements: []
|
49
|
-
rubygems_version: 3.
|
50
|
-
signing_key:
|
139
|
+
rubygems_version: 3.3.3
|
140
|
+
signing_key:
|
51
141
|
specification_version: 4
|
52
142
|
summary: A CLI to view and set fan speeds for AMD graphics cards running on the open
|
53
143
|
source amdgpu Linux driver
|