AMD Ryzen-info

AMD Ryzen-info is a small Python script I wrote to monitor the main hardware system information of an AMD Ryzen system running on a recent version of Linux.
It relies on dmidecode and lm_sensors to gather the needed information, to get correct voltage and temperature readings it is critical that the most up to date version of the it87 kernel module is installed; Python version 3.5 or higher is also hard requirement.
Unluckily the it87 provided by Fedora’s repositories is old and does not completely support the IO chip used on most AM4 motherboards, compiling from source is the only option.
After compiling and installing the up to date version of it87 module reboot the system or reload it using the modprobe command.

$ $ dnf install kernel-devel lm_sensors
$ git clone https://github.com/groeck/it87.git
$ cd it87
$ make clean && make && sudo make install
$ sudo modprobe -r it87 && sudo modprobe it87 force_id=0x8628
$ sudo sensors-detect

Edit /etc/sysconfig/lm_sensors to make it load it87 modules at boot:

HWMON_MODULES="adt7475 it87"

I/O chip ID also needs to be forced, in my case the correct one is 0x8628:

options it87 force_id=0x8628

dmidecode requires to be runned as root and so does Ryzen-info since it relies on it to gather DRAM related information.

#!/usr/bin/env python3.5
# VERSION: 0.1 - 30 June 2017
########################################################################
# For this script to work lm_sensors and an up to date version of it87 #
# kernel modules must be installed and correctly loaded, for more      #
# informaton refer to the following page:                              #
#   https://uwot.eu/blog/amd-ryzen-on-linux/                           #
########################################################################
# Specify the path of local variables:                                 #
#
# CPU core voltage (default from ~0.4 to ~1.4 volt)
vcore = open('/sys/class/hwmon/hwmon1/in0_input', 'r')
#
# VSOC voltage (default ~0.9 volt)
vsoc = open('/sys/class/hwmon/hwmon1/in4_input', 'r')
#
# VDDP voltage (default ~0.9 volt)
vddp = open('/sys/class/hwmon/hwmon1/in5_input', 'r')
#
# DIMM voltage (default 1.2 volt)
vdimm = open('/sys/class/hwmon/hwmon1/in6_input', 'r')
#
# CPU die temperature
tdie = open('/sys/class/hwmon/hwmon1/temp3_input', 'r')
# CPU die temperature offset (precision 1/10000 Celsius degree)
tdie_offset = 15000
#
# CPU VRM temperature
tvrm = open('/sys/class/hwmon/hwmon1/temp5_input', 'r')
#
# END                                                                  #
########################################################################

import os
import time
import subprocess
import re
#from collections import Counter


thread_count = os.cpu_count()
raw_data = str(subprocess.check_output(['lscpu']))
pattern = re.compile("Core\(s\) per socket: ([\w ]+)")
raw_res = pattern.findall(raw_data)
core_count = int(raw_res[0])

# Gather CPU model from "lscpu"
pattern = re.compile("Model name: ([\w\- ]+)")
raw_res = pattern.findall(raw_data)
cpu_model = str(raw_res[0]).strip()

# Gather motherboard informations
f = open('/sys/devices/virtual/dmi/id/board_vendor', 'r')
board_vendor = f.read().rstrip()
f.close()
f = open('/sys/devices/virtual/dmi/id/board_name', 'r')
board_name = f.read().rstrip()
f.close()
f = open('/sys/devices/virtual/dmi/id/bios_date', 'r')
bios_date = f.read().rstrip()
f.close()
f = open('/sys/devices/virtual/dmi/id/bios_version', 'r')
bios_version = f.read().rstrip()
f.close()

sysinfo = "\n  CPU: " + cpu_model + "\n"
sysinfo += "  Motherboard vendor: " + board_vendor + "\n"
sysinfo += ("  Motherboard model: " + board_name + 
            " - BIOS: " + bios_version + 
            " (" + bios_date + ")\n")

print(sysinfo)
            
# Gather system memory informations 
raw_data = str(subprocess.check_output(['dmidecode', '-t', 'memory']))

pattern = re.compile("Type: ([\w ]+)", re.DOTALL)
raw_res = pattern.findall(raw_data)
for elem in raw_res:
    if((str(elem) != "None") and (str(elem) != "Unknown")):
        memory_type = str(elem)
        exit
pattern = re.compile("Clock Speed: ([\w ]+)\ MT/s")
memory_clock = pattern.findall(raw_data)
pattern = re.compile("Size: ([\w ]+)\ MB")
memory_size = pattern.findall(raw_data)
sticks_number = str(len(memory_size))     
pattern = re.compile("Part Number: ([\w ]+).([\w ]+)")
raw_res = pattern.findall(raw_data)
part_number = "Unknown"
for elem in raw_res:
    if(str(elem[0]) != "Unknown"):
        part_number = ""
        i = 1
        for chunk in elem:
            if i == len(elem):
                part_number += str(chunk)
            else:
                part_number += str(chunk) + "."
            i += 1       

sysinfo += ("  Memory type: " + memory_type +
            " - Part number: " + part_number + "\n")
sysinfo += ("  Memory size: " + sticks_number + "x" + memory_size[0] + " MB" +
            " - Clock speed: " + memory_clock[0] + " MHz\n")

# Gather Cores frequency data
# /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq
cfreq = []
for i in range(0, thread_count):
    cfreq.append(open(str('/sys/devices/system/cpu/cpu' 
                          + str(i) + '/cpufreq/cpuinfo_cur_freq'), 
                          'r'))

try:
    vcore_cur = int(vcore.read().rstrip())
    vcore_min = vcore_cur
    vcore_max = vcore_cur
    vcore.seek(0)
    
    vsoc_cur = int(vsoc.read().rstrip())
    vsoc_min = vsoc_cur
    vsoc_max = vsoc_cur
    vsoc.seek(0)
    
    vddp_cur = int(vddp.read().rstrip())
    vddp_min = vddp_cur
    vddp_max = vddp_cur
    vddp.seek(0)
    
    vdimm_cur = int(vdimm.read().rstrip())
    vdimm_min = vdimm_cur
    vdimm_max = vdimm_cur
    vdimm.seek(0)
    
    tdie_cur = int(tdie.read().rstrip()) + tdie_offset
    tdie_min = tdie_cur
    tdie_max = tdie_cur
    tdie.seek(0)
    
    tvrm_cur = int(tvrm.read().rstrip())
    tvrm_min = tvrm_cur
    tvrm_max = tvrm_cur
    tvrm.seek(0)
    
    while True:
        os.system("clear")
        print(sysinfo)
        
        i = 0
        y = 0
        while i < thread_count:
            cfreq[i].seek(0)
            cfreq[i + 1].seek(0)
            freq1 = int(cfreq[i].read().rstrip()) / 1000
            freq2 = int(cfreq[i + 1].read().rstrip()) / 1000
            print("  core " + format(i, '2d') + ": " + 
                  format(float(freq1), '.2f') + 
                  " MHz   -   core " + format(i+1, '2d') + ": " + 
                  format(float(freq2), '.2f') + " MHz")

            i += 2
            y += 1

        vcore_cur = int(vcore.read().rstrip())
        if(vcore_cur < vcore_min):
            vcore_min = vcore_cur
        elif(vcore_cur > vcore_max):
            vcore_max = vcore_cur
        
        vsoc_cur = int(vsoc.read().rstrip())
        if(vsoc_cur < vsoc_min):
            vsoc_min = vsoc_cur
        elif(vsoc_cur > vsoc_max):
            vsoc_max = vsoc_cur
            
        vddp_cur = int(vddp.read().rstrip())
        if(vddp_cur < vddp_min):
            vddp_min = vddp_cur
        elif(vddp_cur > vddp_max):
            vddp_max = vddp_cur
            
        vdimm_cur = int(vdimm.read().rstrip())
        if(vdimm_cur < vdimm_min):
            vdimm_min = vdimm_cur
        elif(vdimm_cur > vdimm_max):
            vdimm_max = vdimm_cur
            
        tdie_cur = int(tdie.read().rstrip()) + tdie_offset
        if(tdie_cur < tdie_min):
            tdie_min = tdie_cur
        elif(tdie_cur > tdie_max):
            tdie_max = tdie_cur
            
        tvrm_cur = int(tvrm.read().rstrip())
        if(tvrm_cur < tvrm_min):
            tvrm_min = tvrm_cur
        elif(tvrm_cur > tvrm_max):
            tvrm_max = tvrm_cur

        print("\n  VCORE: " + 
              format(vcore_cur / 1000, '.3f') + "v (m: " + 
              format(vcore_min / 1000, '.3f') + "v M: " + 
              format(vcore_max / 1000, '.3f') + "v)" +
              "  VSOC:  " + 
              format(vsoc_cur / 1000, '.3f') + "v (m: " + 
              format(vsoc_min / 1000, '.3f') + "v M: " + 
              format(vsoc_max / 1000, '.3f') + "v)" +
              "\n  VDDP:  " + 
              format(vddp_cur / 1000, '.3f') + "v (m: " + 
              format(vddp_min / 1000, '.3f') + "v M: " + 
              format(vddp_max / 1000, '.3f') + "v)" +
              "  VDIMM: " + 
              format(vdimm_cur / 1000, '.3f') + "v (m: " + 
              format(vdimm_min / 1000, '.3f') + "v M: " + 
              format(vdimm_max / 1000, '.3f') + "v)")

        print("\n  CPU TDIE: " + 
              format(tdie_cur / 1000, '.0f') + "°C (m: " + 
              format(tdie_min / 1000, '.0f') + "°C M: " + 
              format(tdie_max / 1000, '.0f') + "°C)" +
              "     TVRM: " + 
              format(tvrm_cur / 1000, '.0f') + "°C (m: " + 
              format(tvrm_min / 1000, '.0f') + "°C M: " + 
              format(tvrm_max / 1000, '.0f') + "°C)")

        raw_data = str(subprocess.check_output(['dmesg']))
        he_count = str(raw_data.count('Hardware Error'))
        print("\n  Hardware errors: " + he_count)

        vcore.seek(0)
        vsoc.seek(0)
        vddp.seek(0)
        vdimm.seek(0)
        tdie.seek(0)
        tvrm.seek(0)

        time.sleep(1)
except KeyboardInterrupt:
    # Close open files
    for i in range(0, thread_count):
        cfreq[i].close()
        
    vcore.close()
    vsoc.close()
    vddp.close()
    vdimm.close()
    tdie.close()
    tvrm.close()
        
    print("Bye")

To run the script type in a terminal: sudo python3.5 ryzen-info.py