Class: ComputeUnit::Device

Inherits:
Object
  • Object
show all
Includes:
Utils
Defined in:
lib/compute_unit/device.rb

Direct Known Subclasses

ComputeBase

Constant Summary collapse

SYSFS_DEVICES_PATH =

We can supply a mock sysfs path in order to test with containers and other scenarios

File.join(ComputeUnit::SYSFS_PATH, 'bus', 'pci', 'devices')
PROC_PATH =
ENV['PROC_PATH'] || '/proc'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils

check_for_root, #root?, root?

Constructor Details

#initialize(device_path, opts = {}) ⇒ Device

Returns a new instance of Device.



26
27
28
29
30
31
32
33
# File 'lib/compute_unit/device.rb', line 26

def initialize(device_path, opts = {})
  @device_path = device_path
  @device_class_id = opts[:device_class_id]
  @device_id = opts[:device_id]
  @device_vendor_id = opts[:device_vendor_id]
  @subsystem_vendor_id = opts[:subsystem_vendor_id]
  @subsystem_device_id = opts[:subsystem_device_id]
end

Instance Attribute Details

#device_class_idObject (readonly)

Returns the value of attribute device_class_id.



14
15
16
# File 'lib/compute_unit/device.rb', line 14

def device_class_id
  @device_class_id
end

#device_idObject (readonly)

Returns the value of attribute device_id.



14
15
16
# File 'lib/compute_unit/device.rb', line 14

def device_id
  @device_id
end

#device_pathObject (readonly)

Returns the value of attribute device_path.



14
15
16
# File 'lib/compute_unit/device.rb', line 14

def device_path
  @device_path
end

#device_vendor_idObject (readonly)

Returns the value of attribute device_vendor_id.



14
15
16
# File 'lib/compute_unit/device.rb', line 14

def device_vendor_id
  @device_vendor_id
end

#makeString (readonly)

Returns - the name of the vendor who made the device.

Returns:

  • (String)
    • the name of the vendor who made the device



87
88
89
# File 'lib/compute_unit/device.rb', line 87

def make
  @make
end

#modelString (readonly)

Returns - the name of the device model (specific name).

Returns:

  • (String)
    • the name of the device model (specific name)



114
115
116
# File 'lib/compute_unit/device.rb', line 114

def model
  @model
end

#subsystem_device_idObject (readonly)

Returns the value of attribute subsystem_device_id.



14
15
16
# File 'lib/compute_unit/device.rb', line 14

def subsystem_device_id
  @subsystem_device_id
end

#subsystem_vendor_idObject (readonly)

Returns the value of attribute subsystem_vendor_id.



14
15
16
# File 'lib/compute_unit/device.rb', line 14

def subsystem_vendor_id
  @subsystem_vendor_id
end

#vendorString (readonly)

Returns - the name of the vendor who resold the device.

Returns:

  • (String)
    • the name of the vendor who resold the device



95
96
97
# File 'lib/compute_unit/device.rb', line 95

def vendor
  @vendor
end

Class Method Details

.create_from_path(device_path) ⇒ Device

Returns - creates a device from the given path.

Parameters:

  • device_path (String)
    • the sysfs path to of the device

Returns:

  • (Device)
    • creates a device from the given path



183
184
185
186
187
188
189
190
191
192
# File 'lib/compute_unit/device.rb', line 183

def self.create_from_path(device_path)
  opts = {
    device_class_id: device_class(device_path),
    device_id: device(device_path),
    device_vendor_id: device_vendor(device_path),
    subsystem_vendor_id: subsystem_vendor(device_path),
    subsystem_device_id: subsystem_device(device_path)
  }
  new(device_path, opts)
end

.device(device_path) ⇒ String

Returns - the device number ie. 1002.

Returns:

  • (String)
    • the device number ie. 1002



246
247
248
# File 'lib/compute_unit/device.rb', line 246

def self.device(device_path)
  read_kernel_setting(device_path, 'device', '').slice(2, 6)
end

.device_class(device_path) ⇒ String

Note:

helps determine what kind of device this is ie. graphics, storage, …

Returns - the class number ie. 040300.

Returns:

  • (String)
    • the class number ie. 040300



257
258
259
# File 'lib/compute_unit/device.rb', line 257

def self.device_class(device_path)
  read_kernel_setting(device_path, 'class', '').slice(2, 8)
end

.device_lookup(device_id) ⇒ String

queries the pci_database that is shipped with this gem and looks up the device

Parameters:

  • - (String)

    the device id such as ‘686f’

Returns:

  • (String)
    • the device name



382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/compute_unit/device.rb', line 382

def self.device_lookup(device_id)
  # "\t687f  Vega 10 XT [Radeon RX Vega 64]\n"
  re = Regexp.new(/\A\t#{device_id}/)
  d = pci_database.find do |line|
    re.match(line)
  rescue ArgumentError
    next
  end
  return d unless d

  name = d[/\t\w+\s+(.*)\n/, 1]
  name[/.*\[(.*)\]/, 1] || name if name
end

.device_vendor(device_path) ⇒ String

Returns - the vendor number ie. 1002.

Returns:

  • (String)
    • the vendor number ie. 1002



251
252
253
# File 'lib/compute_unit/device.rb', line 251

def self.device_vendor(device_path)
  read_kernel_setting(device_path, 'vendor', '').slice(2, 6)
end

.find_all(device_class_id = nil) ⇒ Array

Note:

there is not a filter applied

Returns - an array of pci bus device locations (every device on the pci bus).

Parameters:

  • - (String)

    the device class for filtering out devices

Returns:

  • (Array)
    • an array of pci bus device locations (every device on the pci bus)



197
198
199
200
201
202
# File 'lib/compute_unit/device.rb', line 197

def self.find_all(device_class_id = nil)
  Dir.glob(File.join(ComputeUnit::Device::SYSFS_DEVICES_PATH, '*')).map do |device_path|
    next create_from_path(device_path) unless device_class_id
    next create_from_path(device_path) if device_class(device_path) == device_class_id
  end.compact
end

.loggerObject



396
397
398
# File 'lib/compute_unit/device.rb', line 396

def self.logger
  ComputeUnit::Logger.logger
end

.manual_device_databaseObject



315
316
317
318
319
# File 'lib/compute_unit/device.rb', line 315

def self.manual_device_database
  @manual_device_database ||= {
    '687f_0b36_1002' => 'Radeon RX Vega 64'
  }
end

.manual_device_lookup(device_id, subsystem_device_id, vendor_id) ⇒ Object



321
322
323
324
# File 'lib/compute_unit/device.rb', line 321

def self.manual_device_lookup(device_id, subsystem_device_id, vendor_id)
  key = "#{device_id}_#{subsystem_device_id}_#{vendor_id}"
  manual_device_database.fetch(key, nil)
end

.manual_vendor_lookup(id) ⇒ String

Returns - return the string of the device vendor.

Parameters:

  • id (String)
    • the vendor id

Returns:

  • (String)
    • return the string of the device vendor



360
361
362
# File 'lib/compute_unit/device.rb', line 360

def self.manual_vendor_lookup(id)
  manual_vendors[id]
end

.manual_vendorsHash

Returns - a hash of vendors names and ids.

Returns:

  • (Hash)
    • a hash of vendors names and ids



354
355
356
# File 'lib/compute_unit/device.rb', line 354

def self.manual_vendors
  @manual_vendors ||= { '196e' => 'PNY' }
end

.name_mapHash

sometimes we just wamnt a shorter name to see

Returns:

  • (Hash)
    • Name translation map



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/compute_unit/device.rb', line 211

def self.name_map
  @name_map ||= {
    'ASUSTeK COMPUTER INC.' => 'Asus',
    'ASUSTeK Computer Inc.' => 'Asus',
    'Advanced Micro Devices, Inc. [AMD/ATI]' => 'AMD',
    'XFX Pine Group Inc.' => 'XFX',
    'NVIDIA Corporation' => 'Nvidia',
    'Gigabyte Technology Co., Ltd' => 'Gigabyte',
    'Sapphire Technology Limited' => 'Sapphire',
    'eVga.com. Corp.' => 'Evga',
    'Micro-Star International Co., Ltd. [MSI]' => 'MSI',
    'Micro-Star International Co., Ltd.' => 'MSI',
    'Intel Corporation' => 'Intel',
    'ASMedia Technology Inc.' => 'AsMedia Tech',
    'Advanced Micro Devices, Inc. [AMD]' => 'AMD',
    'Tul Corporation / PowerColor' => 'PowerColor',
    'PC Partner Limited / Sapphire Technology' => 'Sapphire',
    'Realtek Semiconductor Co., Ltd.' => 'Realtek',
    'Samsung Electronics Co Ltd' => 'Samsung',
    'ZOTAC International (MCO) Ltd.' => 'Zotac'
  }
end

.name_translation(name) ⇒ String

Returns - translation of vendor names to how we known them.

Returns:

  • (String)
    • translation of vendor names to how we known them



205
206
207
# File 'lib/compute_unit/device.rb', line 205

def self.name_translation(name)
  name_map[name] || name
end

.pci_databaseArray

Syntax: vendor vendor_name

device  device_name                             <-- single tab
        subvendor subdevice  subsystem_name     <-- two tabs

Returns:

  • (Array)
    • array of lines of the pci database



309
310
311
312
313
# File 'lib/compute_unit/device.rb', line 309

def self.pci_database
  @pci_database ||= begin
    IO.foreach(ComputeUnit::PCI_DATABASE_PATH).lazy
  end
end

.read_kernel_setting(device_path, setting, default = 0) ⇒ String

Returns - read a kernel setting using the device_path.

Parameters:

  • - (String)

    the device_path to read from

  • - (String)

    the name of the kernel file in the device path to read

  • - (Object)

    a default value to use if there is a problem reading the setting

Returns:

  • (String)
    • read a kernel setting using the device_path



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/compute_unit/device.rb', line 265

def self.read_kernel_setting(device_path, setting, default = 0)
  path = File.join(device_path, setting)
  logger.debug("reading kernel file #{path}")
  value = begin
    File.read(path).chomp
          rescue Errno::EINVAL, Errno::EPERM
            logger.fatal(e.message)
            default
          rescue Errno::ENOENT
            logger.debug("File #{path} does not exist")
            default
          rescue Errno::EACCES
            logger.fatal('Run this command as root or with sudo')
            default
  end
end

.subsystem_device(device_path) ⇒ String

Returns - the device number ie. 1002 (can be different from vendor).

Returns:

  • (String)
    • the device number ie. 1002 (can be different from vendor)



235
236
237
# File 'lib/compute_unit/device.rb', line 235

def self.subsystem_device(device_path)
  read_kernel_setting(device_path, 'subsystem_device', '').slice(2, 6)
end

.subsystem_device_lookup(device_id, subsystem_device_id, vendor_id) ⇒ String

Returns - the name of the device.

Parameters:

  • device_id (String)
    • the device id of the device

  • subsystem_device_id (String)
    • the subsystem_device_id of the device

  • vendor_id (String)
    • the subsystem vendor id

Returns:

  • (String)
    • the name of the device



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/compute_unit/device.rb', line 330

def self.subsystem_device_lookup(device_id, subsystem_device_id, vendor_id)
  # "\t\t1002 687f  Vega 10 XT [Radeon RX Vega 64]\n"
  p = manual_device_lookup(device_id, subsystem_device_id, vendor_id)
  return p if p

  re = Regexp.new(/\A\t\t#{vendor_id}\s+#{subsystem_device_id}/)
  d = pci_database.find do |line|
    re.match(line)
  rescue ArgumentError
    next
  end
  return d unless d

  name = d[/\t\t\w+\s+\w+\s+(.*)\n/, 1]
  name[/.*\[(.*)\]/, 1] || name if name
end

.subsystem_vendor(device_path) ⇒ String

example: XFX is the reseller of the AMD chips

Returns:

  • (String)
    • the vendor number ie. 1002 (can be different from vendor)



241
242
243
# File 'lib/compute_unit/device.rb', line 241

def self.subsystem_vendor(device_path)
  read_kernel_setting(device_path, 'subsystem_vendor', '').slice(2, 6)
end

.subsystem_vendor_lookup(vendor_id) ⇒ String

Returns - the name of the subsystem vendor with a possible translation.

Returns:

  • (String)
    • the name of the subsystem vendor with a possible translation



348
349
350
351
# File 'lib/compute_unit/device.rb', line 348

def self.subsystem_vendor_lookup(vendor_id)
  # "1002  Advanced Micro Devices, Inc. [AMD/ATI]\n"
  vendor_lookup(vendor_id)
end

.system_checksumString

Returns - a sha1 digest that represents the pci devices found on the system.

Returns:

  • (String)
    • a sha1 digest that represents the pci devices found on the system



298
299
300
301
302
# File 'lib/compute_unit/device.rb', line 298

def self.system_checksum
  @system_checksum ||= Dir.chdir(ComputeUnit::SYS_DEVICE_PATH) do
    Digest::SHA1.hexdigest(Dir.glob('*').sort.join)
  end
end

.vendor_lookup(vendor_id) ⇒ String

Returns - the name of the subsystem vendor with a possible translation.

Returns:

  • (String)
    • the name of the subsystem vendor with a possible translation



365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/compute_unit/device.rb', line 365

def self.vendor_lookup(vendor_id)
  # "1002  Advanced Micro Devices, Inc. [AMD/ATI]\n"
  re = Regexp.new(/\A#{vendor_id}/)
  d = pci_database.find do |line|
    re.match(line)
  rescue ArgumentError
    next
  end
  return manual_vendor_lookup(vendor_id) unless d

  name = d[/\w+\s+(.*)\n/, 1]
  name_translation(name)
end

.write_kernel_setting(device_path, setting, value) ⇒ String

Returns - read a kernel setting using the device_path.

Parameters:

  • - (String)

    the device_path to write to

  • - (String)

    the name of the kernel file in the device path to read

Returns:

  • (String)
    • read a kernel setting using the device_path



285
286
287
288
289
290
291
292
293
294
295
# File 'lib/compute_unit/device.rb', line 285

def self.write_kernel_setting(device_path, setting, value)
  path = File.join(device_path, setting)
  File.write(path, value)
  read_kernel_setting(device_path, setting)
rescue Errno::EINVAL, Errno::EPERM => e
  logger.fatal(e.message)
rescue Errno::ENOENT
  logger.warn("File #{path} does not exist")
rescue Errno::EACCES
  logger.fatal('Run this command as root or with sudo')
end

Instance Method Details

#base_hwmon_pathString

Note:

this is used mainly for easier mocking

Returns - base hwmon path of the device.

Returns:

  • (String)
    • base hwmon path of the device



159
160
161
# File 'lib/compute_unit/device.rb', line 159

def base_hwmon_path
  File.join(device_path, 'hwmon')
end

#expired_metadata?Boolean

Returns:

  • (Boolean)


173
174
175
# File 'lib/compute_unit/device.rb', line 173

def expired_metadata?
  timestamp + CACHE_TIMEOUT < Time.now.to_i
end

#generic_modelString

Returns - the name of the device model (sometimes not specific).

Returns:

  • (String)
    • the name of the device model (sometimes not specific)



121
122
123
124
125
126
# File 'lib/compute_unit/device.rb', line 121

def generic_model
  @generic_model ||= begin
    name = self.class.device_lookup(device_id)
    name[/\[?(.*)\]?/, 1] if name
  end
end

#hwmon_pathString

Note:

this path can be different for each device

Note:

returns the base_hwmon_path if no path is found

Returns - the directory path to the hwmon dir for this device.

Returns:

  • (String)
    • the directory path to the hwmon dir for this device



166
167
168
169
170
171
# File 'lib/compute_unit/device.rb', line 166

def hwmon_path
  @hwmon_path ||= begin
    paths = Dir.glob(File.join(base_hwmon_path, '*'))
    paths.first || base_hwmon_path
  end
end

#lock_romString

must be root

Returns:

  • (String)
    • writes a 0 to the rom file, therby locking it



56
57
58
# File 'lib/compute_unit/device.rb', line 56

def lock_rom
  File.write(rom_path, '0') if File.exist?(rom_path)
end

#read_file(path, default = nil) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/compute_unit/device.rb', line 128

def read_file(path, default = nil)
  File.read(path).chomp
rescue Errno::EINVAL, Errno::EPERM
  default
rescue Errno::ENOENT
  logger.debug("File #{path} does not exist, using defaults")
  default
rescue Errno::EACCES
  logger.fatal('run this command as root or with sudo, using default value')
  default
end

#read_hwmon_data(item, default = nil) ⇒ String

Returns - the value of the item looked up.

Parameters:

  • item (String)
    • the name of the hwmon file to read from

  • default (Object) (defaults to: nil)
    • the default value to return if the file is empty or not readable

Returns:

  • (String)
    • the value of the item looked up



143
144
145
146
# File 'lib/compute_unit/device.rb', line 143

def read_hwmon_data(item, default = nil)
  path = File.join(hwmon_path, item)
  read_file(path, default)
end

#read_kernel_setting(setting, default = 0) ⇒ String

Returns - read a kernel setting using the device_path.

Parameters:

  • - (String)

    the name of the kernel file in the device path to read

  • - (Object)

    a default value to use if there is a problem reading the setting

Returns:

  • (String)
    • read a kernel setting using the device_path



75
76
77
# File 'lib/compute_unit/device.rb', line 75

def read_kernel_setting(setting, default = 0)
  self.class.read_kernel_setting(device_path, setting, default)
end

#rom_dataString::IO

Returns - the contents of the rom file.

Returns:

  • (String::IO)
    • the contents of the rom file



61
62
63
64
65
66
67
68
69
70
# File 'lib/compute_unit/device.rb', line 61

def rom_data
  return unless File.exist?(rom_path)

  begin
    unlock_rom
    IO.read(rom_path, mode: 'rb')
  ensure
    lock_rom
  end
end

#rom_pathString

Returns - the path to the rom file if available.

Returns:

  • (String)
    • the path to the rom file if available



44
45
46
# File 'lib/compute_unit/device.rb', line 44

def rom_path
  @rom_path ||= File.join(device_path, 'rom')
end

#sysfs_model_nameString

Returns - the name of the device model (specific name).

Returns:

  • (String)
    • the name of the device model (specific name)



107
108
109
110
111
# File 'lib/compute_unit/device.rb', line 107

def sysfs_model_name
  name = self.class.subsystem_device_lookup(device_id, subsystem_device_id, subsystem_vendor_id)
  m = name[/\[?(.*)\]?/, 1] if name
  m || generic_model
end

#to_hObject



35
36
37
38
39
40
41
# File 'lib/compute_unit/device.rb', line 35

def to_h
  { chip_make: make, make: vendor, model: model,
    device_id: device_id, vendor_id: device_vendor_id,
    subsystem_device_id: subsystem_device_id,
    subsystem_vendor_id: subsystem_vendor_id,
    device_class: device_class_id }
end

#to_json(c = nil) ⇒ Object



177
178
179
# File 'lib/compute_unit/device.rb', line 177

def to_json(c = nil)
  to_h.to_json(c)
end

#unlock_romString

must be root

Returns:

  • (String)
    • writes a 1 to the rom file, therby unlocking it for reading



50
51
52
# File 'lib/compute_unit/device.rb', line 50

def unlock_rom
  File.write(rom_path, '1') if File.exist?(rom_path)
end

#write_hwmon_data(item, value) ⇒ Object

Parameters:

  • item (String)
    • the name of the hwmon file to write to

  • value (String)
    • the value you want to write to the hwmon file



150
151
152
153
154
155
# File 'lib/compute_unit/device.rb', line 150

def write_hwmon_data(item, value)
  File.write(File.join(hwmon_path, item), value)
rescue Errno::EACCES => e
  logger.info(e.message)
  check_for_root
end

#write_kernel_setting(setting, value) ⇒ String

Returns - a reading of the kernel setting using the device_path.

Parameters:

  • value (String)
    • the value to assign to the setting

  • setting (String)
    • the name of the kernel file in the device path to read

Returns:

  • (String)
    • a reading of the kernel setting using the device_path



82
83
84
# File 'lib/compute_unit/device.rb', line 82

def write_kernel_setting(setting, value)
  self.class.write_kernel_setting(device_path, setting, value)
end