amdgpu_fan 0.5.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 822ba3e84cdd6cb64680ace91e696351c3ac8f61fe7344554304be517c1ee141
4
- data.tar.gz: b74858f3916aec38afb02134694011f59f4c672d276dd4905e9da6accee69333
3
+ metadata.gz: 1fb09354467c6ffeb603c4cbc72b131c626e83d4403148ebeb5fac342222ac29
4
+ data.tar.gz: b62420cbf660f704682f34565128c749216bb5605b3d57b843f4bc85671aa09c
5
5
  SHA512:
6
- metadata.gz: 1798fa2cb6a578717c75d898501833e75e27fc8efd14881954c8a7cdf53e71a0dc1dfb0cba6b51b8b96572943761d6d9f23be33f26f8bc6ee3c42e38f017ab7c
7
- data.tar.gz: 95df0c6e478a1cbe580695c9be8cf123e555ec76ef767eb5a91b24d174693667cf33b0dabcc6a3ddd45cdfd54a7efd070317e6a74fc63395f2b96d207b77e0eb
6
+ metadata.gz: f9a94891e8024c9e2c58c46feb4b7654e69978a9215278d9588065db177e8df447670cad9be726f608f16bd77f4a26e2a05fe16c536ce42bce0c9242489d2b43
7
+ data.tar.gz: 8046858789272ed17ce75304f53ee3915973193bc81bd301335448456c99bd8d4d774962668b2b98388a24301e158d4cffb18a65a28d27c7fd45c130c639740d
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # amdgpu_fan
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/amdgpu_fan.svg)](https://badge.fury.io/rb/amdgpu_fan)
3
4
  [![Build Status](https://travis-ci.org/HarlemSquirrel/amdgpu-fan-rb.svg?branch=master)](https://travis-ci.org/HarlemSquirrel/amdgpu-fan-rb) [![Maintainability](https://api.codeclimate.com/v1/badges/27233cee17ef6a2c14fd/maintainability)](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
- - [`lspci`](https://linux.die.net/man/8/lspci) (included with most Linux distributions)
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
+ ```
@@ -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
@@ -0,0 +1,10 @@
1
+ ---
2
+ clock: ⏰
3
+ display: 📺
4
+ fan: 🌀
5
+ gpu: 👾
6
+ load: "⚖ "
7
+ memory: 💾
8
+ power: ⚡
9
+ temp: "🌡 "
10
+ vbios: 📄
@@ -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 "👾 #{'GPU:'.ljust(9)} #{amdgpu_service.name}",
56
- "📄 #{'vBIOS:'.ljust(9)} #{amdgpu_service.vbios_version}",
57
- "📺 Displays: #{amdgpu_service.connectors.map(&:display_name).compact.join(',')}",
58
- "⏰ #{'Clocks:'.ljust(9)} #{clock_status}",
59
- "💾 #{'Memory:'.ljust(9)} #{mem_total_mibibyes}",
60
- "🌀 #{'Fan:'.ljust(9)} #{fan_status}",
61
- "🌞 #{'Temp:'.ljust(9)} #{amdgpu_service.temperature}°C",
62
- "⚡ #{'Power:'.ljust(9)} #{amdgpu_service.profile_mode} profile in " \
63
- "#{amdgpu_service.power_dpm_state} mode using " \
64
- "#{amdgpu_service.power_draw} / #{amdgpu_service.power_max} Watts "\
65
- "(#{amdgpu_service.power_draw_percent}%)",
66
- "⚖ #{'Load:'.ljust(9)} #{percent_meter amdgpu_service.busy_percent, 20}"
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
- 'refreshed every n seconds.'
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
- 'refreshed every n seconds defaulting to 1 second.'
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, Core Clock (Mhz),Memory Clock (Mhz),Fan speed (rpm), '\
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
- puts [Time.now.strftime('%F %T'),
108
- amdgpu_service.core_clock,
109
- amdgpu_service.memory_clock,
110
- amdgpu_service.fan_speed_rpm,
111
- amdgpu_service.busy_percent,
112
- amdgpu_service.power_draw,
113
- amdgpu_service.temperature].join(',')
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
- "#{amdgpu_service.fan_speed_rpm} rpm (#{amdgpu_service.fan_speed_percent}%)"
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
@@ -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
- display_descriptor_leading_bytes: String.new('\x00\xFC\x00', encoding: 'ascii-8bit'),
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}-*"].sort.map do |dir_path|
22
- Connector.new card_num: card_num,
23
- dir_path: 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-Z]+/)
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
- edid.slice(EDID_DESCRIPTORS_CONF[:index_range])
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
- METER_CHAR = '*'
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
- def percent_meter(percent, length = 10)
16
- progress_bar_count = (length * percent.to_f / 100).round
17
- percent_string = "#{format '%<num>0.2i', num: percent}%".ljust(3)
18
- "[#{METER_CHAR * progress_bar_count}#{' ' * (length - progress_bar_count)}]#{percent_string}"
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
@@ -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
@@ -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: 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
- lspci_subsystem.split(': ')[1].strip
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 gpu_pci_id
138
- @gpu_pci_id ||= `lspci -v | grep VGA`.split(' ').first
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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AmdgpuFan
4
+ # Current version of RSpec Core, in semantic versioning format.
5
+ VERSION = '0.8.0'
6
+ 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.5.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: 2020-03-15 00:00:00.000000000 Z
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
- post_install_message:
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: '0'
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.1.2
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