[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018-2020 Ondrej Čerman\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Zen monitor\nZen monitor is monitoring software for AMD Zen-based CPUs.\n\nIt can monitor these values:\n - CPU Temperature\n - CPU Core (SVI2) Voltage, Current and Power\n - SOC (SVI2) Voltage, Current and Power\n - Package and Core Power (RAPL)\n - Core Frequency (from OS)\n\n![screenshot](screenshot.png)\n\n## Dependencies\n - [zenpower driver](https://github.com/ocerman/zenpower/) - For monitoring CPU temperature and SVI2 sensors\n - MSR driver - For monitoring Package/Core Power (RAPL)\n\nFollow [zenpower README.md](https://github.com/ocerman/zenpower/blob/master/README.md) to install and activate zenpower module.\nEnter `sudo modprobe msr` to enable MSR driver.\n\n## Building \nMake sure that GTK3 dev package and common build tools are installed.\n```\nmake\n```\n\n## Launching\nYou can launch app by `sudo ./zenmonitor`, or you can install it to your system and then launch it from your OS menu.\n\nNote: Because superuser privileges are usually needed to access data from MSR driver, you need to launch zenmonitor as root for monitoring CPU power usage (RAPL).\nAlternatively, you can set capabilities to zenmonitor executable: `sudo setcap cap_sys_rawio,cap_dac_read_search+ep ./zenmonitor`\n\n## Command line arguments\n\n``--coreid`` - Display core_id instead of core index\n\n## Installing\nBy default, Zenmonitor will be installed to /usr/local.\n```\nsudo make install\n```\n\nTo add menu item for launching zenpower as root (Polkit is required):\n```\nsudo make install-polkit\n```\n\n## Uninstalling\n```\nsudo make uninstall\n```\n\n## Setup on ubuntu\nFirst follow [installation instructions on zenpower](https://github.com/ocerman/zenpower/blob/master/README.md#installation-commands-for-ubuntu)\nThen:\n```\nsudo modprobe msr\nsudo bash -c 'echo \"msr\" > /etc/modules-load.d/msr.conf'\nsudo apt install build-essential libgtk-3-dev git\ncd ~\ngit clone https://github.com/ocerman/zenmonitor\ncd zenmonitor\nmake\nsudo make install\nsudo make install-polkit\n```\n## Setup on Arch\nYou may use the AUR package [zenmonitor-git](https://aur.archlinux.org/packages/zenmonitor-git/) to install via [traditional method](https://wiki.archlinux.org/index.php/Arch_User_Repository) or using an AUR helper (like yay)\n"
  },
  {
    "path": "data/org.pkexec.zenmonitor.policy.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE policyconfig PUBLIC \"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN\"\n \"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd\">\n<policyconfig>\n\n\t<action id=\"org.pkexec.zenmonitor\">\n\t\t<description>Run Zenmonitor as root</description>\n\t\t<message>Authentication is required to run Zenmonitor as root.</message>\n\t\t<defaults>\n\t\t\t<allow_any>auth_admin</allow_any>\n\t\t\t<allow_inactive>auth_admin</allow_inactive>\n\t\t\t<allow_active>auth_admin</allow_active>\n\t\t</defaults>\n\t\t<annotate key=\"org.freedesktop.policykit.exec.path\">@APP_EXEC@</annotate>\n\t\t<annotate key=\"org.freedesktop.policykit.exec.allow_gui\">true</annotate>\n\t</action>\n\n</policyconfig>\n"
  },
  {
    "path": "data/zenmonitor-root.desktop.in",
    "content": "[Desktop Entry]\nName=Zenmonitor (root)\nComment=Monitoring software for AMD Zen-based CPUs\nExec=pkexec @APP_EXEC@\nType=Application\nCategories=GTK;System;\nTerminal=false\nKeywords=CPU;AMD;zen;system;core;speed;clock;temperature;voltage;\n"
  },
  {
    "path": "data/zenmonitor.desktop.in",
    "content": "[Desktop Entry]\nName=Zenmonitor\nComment=Monitoring software for AMD Zen-based CPUs\nExec=@APP_EXEC@\nType=Application\nCategories=GTK;System;\nTerminal=false\nKeywords=CPU;AMD;zen;system;core;speed;clock;temperature;voltage;\n"
  },
  {
    "path": "makefile",
    "content": "ifeq ($(PREFIX),)\n\tPREFIX := /usr/local\nendif\n\nbuild:\n\tcc -Isrc/include `pkg-config --cflags gtk+-3.0` src/*.c src/ss/*.c -o zenmonitor `pkg-config --libs gtk+-3.0` -lm -no-pie -Wall\n\ninstall:\n\tmkdir -p $(DESTDIR)$(PREFIX)/bin\n\tinstall -m 755 zenmonitor $(DESTDIR)$(PREFIX)/bin\n\n\tmkdir -p $(DESTDIR)$(PREFIX)/share/applications\n\tsed -e \"s|@APP_EXEC@|${DESTDIR}${PREFIX}/bin/zenmonitor|\" \\\n\t\t\tdata/zenmonitor.desktop.in > \\\n\t\t\t$(DESTDIR)$(PREFIX)/share/applications/zenmonitor.desktop\n\ninstall-polkit:\n\tsed -e \"s|@APP_EXEC@|${DESTDIR}${PREFIX}/bin/zenmonitor|\" \\\n\t\t\tdata/zenmonitor-root.desktop.in > \\\n\t\t\t$(DESTDIR)$(PREFIX)/share/applications/zenmonitor-root.desktop\n\n\tsed -e \"s|@APP_EXEC@|${DESTDIR}${PREFIX}/bin/zenmonitor|\" \\\n\t\t\tdata/org.pkexec.zenmonitor.policy.in > \\\n\t\t\t$(DESTDIR)/usr/share/polkit-1/actions/org.pkexec.zenmonitor.policy\n\nuninstall:\n\trm -f $(DESTDIR)$(PREFIX)/bin/zenmonitor\n\trm -f $(DESTDIR)$(PREFIX)/share/applications/zenmonitor.desktop\n\trm -f $(DESTDIR)$(PREFIX)/share/applications/zenmonitor-root.desktop\n\trm -f $(DESTDIR)/usr/share/polkit-1/actions/org.pkexec.zenmonitor.policy\n\nclean:\n\trm -f zenmonitor\n"
  },
  {
    "path": "src/gui.c",
    "content": "#include <cpuid.h>\n#include <gtk/gtk.h>\n#include \"gui.h\"\n#include \"zenmonitor.h\"\n\nGtkWidget *window;\n\nstatic GtkTreeModel *model = NULL;\nstatic guint timeout = 0;\nstatic SensorSource *sensor_sources;\nstatic const guint defaultHeight = 350;\n\nenum {\n    COLUMN_NAME,\n    COLUMN_HINT,\n    COLUMN_VALUE,\n    COLUMN_MIN,\n    COLUMN_MAX,\n    NUM_COLUMNS\n};\n\nstatic void init_sensors() {\n    GtkTreeIter iter;\n    GSList *sensor;\n    GtkListStore *store;\n    SensorSource *source;\n    const SensorInit *data;\n    guint i = 0;\n\n    store = GTK_LIST_STORE(model);\n    for (source = sensor_sources; source->drv; source++) {\n        if (source->func_init()){\n            source->sensors = source->func_get_sensors();\n            if (source->sensors != NULL) {\n                source->enabled = TRUE;\n\n                sensor = source->sensors;\n                while (sensor) {\n                    data = (SensorInit*)sensor->data;\n                    gtk_list_store_append(store, &iter);\n                    gtk_list_store_set(store, &iter,\n                                       COLUMN_NAME,  data->label,\n                                       COLUMN_HINT,  data->hint,\n                                       COLUMN_VALUE, \" --- \",\n                                       COLUMN_MIN,   \" --- \",\n                                       COLUMN_MAX,   \" --- \",\n                                       -1);\n                    sensor = sensor->next;\n                    i++;\n                }\n            }\n        }\n    }\n}\n\nstatic GtkTreeModel* create_model (void) {\n    GtkListStore *store;\n    store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);\n    return GTK_TREE_MODEL (store);\n}\n\nstatic void set_list_column_value(float num, const gchar *printf_format, GtkTreeIter *iter, gint column){\n    gchar *value;\n    if (num != ERROR_VALUE)\n        value = g_strdup_printf(printf_format, num);\n    else\n        value = g_strdup(\"    ? ? ?\");\n    gtk_list_store_set(GTK_LIST_STORE (model), iter, column, value, -1);\n    g_free(value);\n}\n\nstatic gboolean update_data (gpointer data) {\n    GtkTreeIter iter;\n    GSList *node;\n    SensorSource *source;\n    const SensorInit *sensorData;\n\n    if (model == NULL)\n        return G_SOURCE_REMOVE;\n\n    if (!gtk_tree_model_get_iter_first (model, &iter))\n        return G_SOURCE_REMOVE;\n\n    for (source = sensor_sources; source->drv; source++) {\n        if (!source->enabled)\n            continue;\n            \n        source->func_update();\n        if (source->sensors){\n            node = source->sensors;\n\n            while(node) {\n                sensorData = (SensorInit*)node->data;\n                set_list_column_value(*(sensorData->value), sensorData->printf_format, &iter, COLUMN_VALUE);\n                set_list_column_value(*(sensorData->min), sensorData->printf_format, &iter, COLUMN_MIN);\n                set_list_column_value(*(sensorData->max), sensorData->printf_format, &iter, COLUMN_MAX);\n\n                node = node->next;\n                if (!gtk_tree_model_iter_next(model, &iter))\n                    break;\n            }\n        }\n    }\n    return G_SOURCE_CONTINUE;\n}\n\nstatic void add_columns (GtkTreeView *treeview) {\n    GtkCellRenderer *renderer;\n    GtkTreeViewColumn *column;\n\n    // NAME\n    renderer = gtk_cell_renderer_text_new ();\n    column = gtk_tree_view_column_new_with_attributes (\"Sensor\", renderer,\n                                                     \"text\", COLUMN_NAME,\n                                                     NULL);\n    g_object_set(renderer, \"family\", \"monotype\", NULL);\n    gtk_tree_view_append_column (treeview, column);\n\n    //VALUE\n    renderer = gtk_cell_renderer_text_new ();\n    column = gtk_tree_view_column_new_with_attributes (\"Value\", renderer,\n                                                     \"text\", COLUMN_VALUE,\n                                                     NULL);\n    g_object_set(renderer, \"family\", \"monotype\", NULL);\n    gtk_tree_view_append_column (treeview, column);\n\n    //MIN\n    renderer = gtk_cell_renderer_text_new ();\n    column = gtk_tree_view_column_new_with_attributes (\"Min\", renderer,\n                                                     \"text\", COLUMN_MIN,\n                                                     NULL);\n    g_object_set(renderer, \"family\", \"monotype\", NULL);\n    gtk_tree_view_append_column (treeview, column);\n\n    //MAX\n    renderer = gtk_cell_renderer_text_new ();\n    column = gtk_tree_view_column_new_with_attributes (\"Max\", renderer,\n                                                     \"text\", COLUMN_MAX,\n                                                     NULL);\n    g_object_set(renderer, \"family\", \"monotype\", NULL);\n    gtk_tree_view_append_column (treeview, column);\n}\n\nstatic void about_btn_clicked(GtkButton *button, gpointer user_data) {\n    GtkWidget *dialog;\n    const gchar *website = \"https://github.com/ocerman/zenmonitor\";\n    const gchar *msg = \"<b>Zen Monitor</b> %s\\n\"\n                       \"Monitoring software for AMD Zen-based CPUs\\n\"\n                       \"<a href=\\\"%s\\\">%s</a>\\n\\n\"\n                       \"Created by: Ondrej Čerman\";\n\n    dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW (window),\n                                    GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,\n                                    GTK_MESSAGE_INFO, GTK_BUTTONS_OK,\n                                    msg, VERSION, website, website);\n\n    gtk_dialog_run(GTK_DIALOG(dialog));\n    gtk_widget_destroy(dialog);\n}\n\nstatic void clear_btn_clicked(GtkButton *button, gpointer user_data) {\n    SensorSource *source;\n\n    for (source = sensor_sources; source->drv; source++) {\n        if (!source->enabled)\n            continue;\n\n        source->func_clear_minmax();\n    }\n}\n\nstatic gboolean mid_search_eq_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter) {\n    gchar *iter_string = NULL, *lc_iter_string = NULL, *lc_key = NULL;\n    gboolean result;\n\n    gtk_tree_model_get(model, iter, column, &iter_string, -1);\n    lc_iter_string = g_utf8_strdown(iter_string, -1);\n    lc_key = g_utf8_strdown(key, -1);\n\n    result = (g_strrstr(lc_iter_string, lc_key) == NULL);\n\n    g_free(iter_string);\n    g_free(lc_iter_string);\n    g_free(lc_key);\n\n    return result;\n}\n\nstatic void resize_to_treeview(GtkWindow* window, GtkTreeView* treeview) {\n    gint uiHeight, cellHeight, vSeparator, rows;\n    GdkRectangle r;\n\n    GtkTreeViewColumn *col = gtk_tree_view_get_column(treeview, 0);\n    if (!col)\n        return;\n\n    gtk_tree_view_column_cell_get_size(col, NULL, NULL, NULL, NULL, &cellHeight);\n    gtk_widget_style_get(GTK_WIDGET(treeview), \"vertical-separator\", &vSeparator, NULL);\n    rows = gtk_tree_model_iter_n_children(gtk_tree_view_get_model(treeview), NULL);\n\n    gtk_tree_view_get_visible_rect(treeview, &r);\n    uiHeight = defaultHeight - r.height;\n\n    gtk_window_resize(window, 500, uiHeight + (vSeparator + cellHeight) * rows);\n}\n\nint start_gui (SensorSource *ss) {\n    GtkWidget *about_btn;\n    GtkWidget *clear_btn;\n    GtkWidget *box;\n    GtkWidget *header;\n    GtkWidget *treeview;\n    GtkWidget *sw;\n    GtkWidget *vbox;\n    GtkWidget *dialog;\n\n    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);\n    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);\n    gtk_window_set_default_size(GTK_WINDOW(window), 500, defaultHeight);\n\n    header = gtk_header_bar_new();\n    gtk_header_bar_set_show_close_button(GTK_HEADER_BAR (header), TRUE);\n    gtk_header_bar_set_title(GTK_HEADER_BAR (header), \"Zen monitor\");\n    gtk_header_bar_set_has_subtitle(GTK_HEADER_BAR (header), TRUE);\n    gtk_header_bar_set_subtitle(GTK_HEADER_BAR (header), cpu_model());\n    gtk_window_set_titlebar (GTK_WINDOW (window), header);\n\n    box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);\n    gtk_style_context_add_class (gtk_widget_get_style_context (box), \"linked\");\n\n    about_btn = gtk_button_new();\n    gtk_container_add(GTK_CONTAINER(about_btn), gtk_image_new_from_icon_name(\"dialog-information\", GTK_ICON_SIZE_BUTTON));\n    gtk_container_add(GTK_CONTAINER(box), about_btn);\n    gtk_widget_set_tooltip_text(about_btn, \"About Zen monitor\");\n\n    clear_btn = gtk_button_new();\n    gtk_container_add(GTK_CONTAINER(clear_btn), gtk_image_new_from_icon_name(\"edit-clear-all\", GTK_ICON_SIZE_BUTTON));\n    gtk_container_add(GTK_CONTAINER(box), clear_btn);\n    gtk_widget_set_tooltip_text(clear_btn, \"Clear Min/Max\");\n\n    gtk_header_bar_pack_start(GTK_HEADER_BAR(header), box);\n    g_signal_connect(about_btn, \"clicked\", G_CALLBACK(about_btn_clicked), NULL);\n    g_signal_connect(clear_btn, \"clicked\", G_CALLBACK(clear_btn_clicked), NULL);\n    g_signal_connect(window, \"destroy\", G_CALLBACK(gtk_main_quit), NULL);\n\n    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);\n    gtk_container_add(GTK_CONTAINER (window), vbox);\n\n    sw = gtk_scrolled_window_new (NULL, NULL);\n    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);\n    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);\n    gtk_box_pack_start(GTK_BOX (vbox), sw, TRUE, TRUE, 0);\n\n    model = create_model();\n    treeview = gtk_tree_view_new_with_model(model);\n    gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(treeview), COLUMN_HINT);\n\n    gtk_container_add (GTK_CONTAINER(sw), treeview);\n    add_columns(GTK_TREE_VIEW(treeview));\n    gtk_widget_show_all(window);\n\n    gtk_tree_view_set_search_column(GTK_TREE_VIEW(treeview), COLUMN_NAME);\n    gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(treeview),\n        (GtkTreeViewSearchEqualFunc)mid_search_eq_func, model, NULL);\n\n    g_object_unref(model);\n\n    if (check_zen()){\n        sensor_sources = ss;\n        init_sensors();\n\n        resize_to_treeview(GTK_WINDOW(window), GTK_TREE_VIEW(treeview));\n        timeout = g_timeout_add(300, update_data, NULL);\n    }\n    else{\n        dialog = gtk_message_dialog_new(GTK_WINDOW (window),\n                                        GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,\n                                        GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,\n                                        \"Zen CPU not detected!\");\n        gtk_dialog_run(GTK_DIALOG(dialog));\n        gtk_widget_destroy(dialog);\n    }\n\n    gtk_main();\n    return 0;\n}\n"
  },
  {
    "path": "src/include/gui.h",
    "content": "int start_gui();\n"
  },
  {
    "path": "src/include/msr.h",
    "content": "gboolean msr_init();\nvoid msr_update();\nvoid msr_clear_minmax();\nGSList* msr_get_sensors();\n"
  },
  {
    "path": "src/include/os.h",
    "content": "gboolean os_init(void);\nvoid os_update(void);\nvoid os_clear_minmax(void);\nGSList* os_get_sensors(void);\n"
  },
  {
    "path": "src/include/sysfs.h",
    "content": "#define SYSFS_DIR_CPUS \"/sys/devices/system/cpu\"\n\nstruct cpudev {\n\tgshort coreid;\n\tgshort cpuid;\n};\n\nstruct cpudev * get_cpu_dev_ids(void);\n"
  },
  {
    "path": "src/include/zenmonitor.h",
    "content": "#define ERROR_VALUE -999.0\n#define VERSION \"1.4.2\"\n\ntypedef struct\n{\n    gchar *label;\n    gchar *hint;\n    float *value;\n    float *min;\n    float *max;\n    const gchar *printf_format;\n}\nSensorInit;\n\ntypedef struct {\n    const gchar *drv;\n    gboolean  (*func_init)();\n    GSList* (*func_get_sensors)();\n    void (*func_update)();\n    void (*func_clear_minmax)();\n    gboolean enabled;\n    GSList *sensors;\n} SensorSource;\n\nSensorInit* sensor_init_new(void);\nvoid sensor_init_free(SensorInit *s);\ngboolean check_zen();\ngchar *cpu_model();\nguint get_core_count();\nextern gboolean display_coreid;\n"
  },
  {
    "path": "src/include/zenpower.h",
    "content": "gboolean zenpower_init();\nGSList* zenpower_get_sensors();\nvoid zenpower_update();\nvoid zenpower_clear_minmax();\n"
  },
  {
    "path": "src/ss/msr.c",
    "content": "#include <glib.h>\n#include <cpuid.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"zenmonitor.h\"\n#include \"msr.h\"\n#include \"sysfs.h\"\n\n#define MSR_PWR_PRINTF_FORMAT \" %8.3f W\"\n#define MSR_FID_PRINTF_FORMAT \" %8.3f GHz\"\n#define MESUREMENT_TIME 0.1\n\n// AMD PPR  = https://www.amd.com/system/files/TechDocs/54945_PPR_Family_17h_Models_00h-0Fh.pdf\n// AMD OSRR = https://developer.amd.com/wp-content/resources/56255_3_03.PDF\n\nstatic guint cores = 0;\nstatic gdouble energy_unit = 0;\nstatic struct cpudev *cpu_dev_ids;\n\nstatic gint *msr_files = NULL;\n\nstatic gulong package_eng_b = 0;\nstatic gulong package_eng_a = 0;\nstatic gulong *core_eng_b = NULL;\nstatic gulong *core_eng_a = NULL;\n\ngfloat package_power;\ngfloat package_power_min;\ngfloat package_power_max;\ngfloat *core_power;\ngfloat *core_fid;\ngfloat *core_power_min;\ngfloat *core_power_max;\ngfloat *core_fid_min;\ngfloat *core_fid_max;\n\n\nstatic gint open_msr(gshort devid) {\n    gchar msr_path[20];\n    sprintf(msr_path, \"/dev/cpu/%d/msr\", devid);\n    return open(msr_path, O_RDONLY);\n}\n\nstatic gboolean read_msr(gint file, guint index, gulong *data) {\n    if (file < 0)\n        return FALSE;\n\n    return pread(file, data, sizeof *data, index) == sizeof *data;\n}\n\ngdouble get_energy_unit() {\n    gulong data;\n    // AMD OSRR: page 139 - MSRC001_0299\n    if (!read_msr(msr_files[0], 0xC0010299, &data))\n        return 0.0;\n\n    return pow(1.0/2.0, (double)((data >> 8) & 0x1F));\n}\n\ngulong get_package_energy() {\n    gulong data;\n    // AMD OSRR: page 139 - MSRC001_029B\n    if (!read_msr(msr_files[0], 0xC001029B, &data))\n        return 0;\n\n    return data;\n}\n\ngulong get_core_energy(gint core) {\n    gulong data;\n    // AMD OSRR: page 139 - MSRC001_029A\n    if (!read_msr(msr_files[core], 0xC001029A, &data))\n        return 0;\n\n    return data;\n}\n\ngdouble get_core_fid(gint core) {\n    gdouble ratio;\n    gulong data;\n\n    // By reverse-engineering Ryzen Master, we know that\n    //  this undocumented MSR is responsible for returning\n    //  the FID and FDID for the core used for calculating the\n    //  effective frequency.\n    //\n    // The FID is returned in bits [8:0]\n    // The FDID is returned in bits [14:8]\n    if (!read_msr(msr_files[core], 0xC0010293, &data))\n        return 0;\n\n    ratio = (gdouble)(data & 0xff) / (gdouble)((data >> 8) & 0x3F);\n\n    // The effective ratio is based on increments of 200 MHz.\n    return ratio * 200.0 / 1000.0;\n}\n\ngboolean msr_init() {\n    guint i;\n\n    if (!check_zen())\n        return FALSE;\n\n    cores = get_core_count();\n    if (cores == 0)\n        return FALSE;\n\n    cpu_dev_ids = get_cpu_dev_ids();\n    msr_files = malloc(cores * sizeof (gint));\n    for (i = 0; i < cores; i++) {\n        msr_files[i] = open_msr(cpu_dev_ids[i].cpuid);\n    }\n\n    energy_unit = get_energy_unit();\n    if (energy_unit == 0)\n        return FALSE;\n\n    core_eng_b = malloc(cores * sizeof (gulong));\n    core_eng_a = malloc(cores * sizeof (gulong));\n    core_power = malloc(cores * sizeof (gfloat));\n    core_fid = malloc(cores * sizeof (gfloat));\n    core_power_min = malloc(cores * sizeof (gfloat));\n    core_power_max = malloc(cores * sizeof (gfloat));\n    core_fid_min = malloc(cores * sizeof (gfloat));\n    core_fid_max = malloc(cores * sizeof (gfloat));\n\n    msr_update();\n    memcpy(core_power_min, core_power, cores * sizeof (gfloat));\n    memcpy(core_power_max, core_power, cores * sizeof (gfloat));\n    memcpy(core_fid_min, core_fid, cores * sizeof (gfloat));\n    memcpy(core_fid_max, core_fid, cores * sizeof (gfloat));\n    package_power_min = package_power;\n    package_power_max = package_power;\n\n    return TRUE;\n}\n\nvoid msr_update() {\n    guint i;\n\n    package_eng_b = get_package_energy();\n    for (i = 0; i < cores; i++) {\n        core_eng_b[i] = get_core_energy(i);\n    }\n\n    usleep(MESUREMENT_TIME*1000000);\n\n    package_eng_a = get_package_energy();\n    for (i = 0; i < cores; i++) {\n        core_eng_a[i] = get_core_energy(i);\n    }\n\n    if (package_eng_a >= package_eng_b) {\n        package_power = (package_eng_a - package_eng_b) * energy_unit / MESUREMENT_TIME;\n\n        if (package_power < package_power_min)\n            package_power_min = package_power;\n        if (package_power > package_power_max)\n            package_power_max = package_power;\n    }\n\n    for (i = 0; i < cores; i++) {\n        if (core_eng_a[i] >= core_eng_b[i]) {\n            core_power[i] = (core_eng_a[i] - core_eng_b[i]) * energy_unit / MESUREMENT_TIME;\n\n            if (core_power[i] < core_power_min[i])\n                core_power_min[i] = core_power[i];\n            if (core_power[i] > core_power_max[i])\n                core_power_max[i] = core_power[i];\n        }\n\n        core_fid[i] = get_core_fid(i);\n\n        if (core_fid[i] < core_fid_min[i])\n            core_fid_min[i] = core_fid[i];\n        if (core_fid[i] > core_fid_max[i])\n            core_fid_max[i] = core_fid[i];\n    }\n}\n\nvoid msr_clear_minmax() {\n    guint i;\n\n    package_power_min = package_power;\n    package_power_max = package_power;\n    for (i = 0; i < cores; i++) {\n        core_power_min[i] = core_power[i];\n        core_power_max[i] = core_power[i];\n        core_fid_min[i] = core_fid[i];\n        core_fid_max[i] = core_fid[i];\n    }\n}\n\nGSList* msr_get_sensors() {\n    GSList *list = NULL;\n    SensorInit *data;\n    guint i;\n\n    data = sensor_init_new();\n    data->label = g_strdup(\"Package Power\");\n    data->hint = g_strdup(\"Package Power reported by RAPL\\nSource: cpu0 MSR\");\n    data->value = &package_power;\n    data->min = &package_power_min;\n    data->max = &package_power_max;\n    data->printf_format = MSR_PWR_PRINTF_FORMAT;\n    list = g_slist_append(list, data);\n\n    for (i = 0; i < cores; i++) {\n        data = sensor_init_new();\n        data->label = g_strdup_printf(\"Core %d Effective Frequency\", display_coreid ? cpu_dev_ids[i].coreid: i);\n        data->hint = g_strdup_printf(\"Source: cpu%d MSR\", cpu_dev_ids[i].cpuid);\n        data->value = &(core_fid[i]);\n        data->min = &(core_fid_min[i]);\n        data->max = &(core_fid_max[i]);\n        data->printf_format = MSR_FID_PRINTF_FORMAT;\n        list = g_slist_append(list, data);\n    }\n\n    for (i = 0; i < cores; i++) {\n        data = sensor_init_new();\n        data->label = g_strdup_printf(\"Core %d Power\", display_coreid ? cpu_dev_ids[i].coreid: i);\n        data->hint = g_strdup_printf(\"Core Power reported by RAPL\\nSource: cpu%d MSR\", cpu_dev_ids[i].cpuid);\n        data->value = &(core_power[i]);\n        data->min = &(core_power_min[i]);\n        data->max = &(core_power_max[i]);\n        data->printf_format = MSR_PWR_PRINTF_FORMAT;\n        list = g_slist_append(list, data);\n    }\n\n    return list;\n}\n"
  },
  {
    "path": "src/ss/os.c",
    "content": "#include <glib.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"zenmonitor.h\"\n#include \"sysfs.h\"\n#include \"os.h\"\n\n#define OS_FREQ_PRINTF_FORMAT \" %8.3f GHz\"\n\nstatic gchar **frq_files = NULL;\nstatic guint cores;\nstatic struct cpudev *cpu_dev_ids;\n\ngfloat *core_freq;\ngfloat *core_freq_min;\ngfloat *core_freq_max;\n\nstatic gdouble get_frequency(guint corei) {\n    gchar *data;\n    gdouble freq;\n\n    if (!g_file_get_contents(frq_files[corei], &data, NULL, NULL))\n        return 0.0;\n\n    freq = atoi(data) / 1000000.0;\n    g_free(data);\n\n    return freq;\n}\n\ngboolean os_init(void) {\n    guint i;\n\n    if (!check_zen())\n        return FALSE;\n\n    cores = get_core_count();\n    if (cores == 0)\n        return FALSE;\n\n    cpu_dev_ids = get_cpu_dev_ids();\n    frq_files = malloc(cores * sizeof (gchar*));\n    for (i = 0; i < cores; i++) {\n        frq_files[i] = g_strdup_printf(\n                        \"/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq\",\n                        cpu_dev_ids[i].cpuid);\n    }\n\n    core_freq = malloc(cores * sizeof (gfloat));\n    core_freq_min = malloc(cores * sizeof (gfloat));\n    core_freq_max = malloc(cores * sizeof (gfloat));\n\n    os_update();\n    memcpy(core_freq_min, core_freq, cores * sizeof (gfloat));\n    memcpy(core_freq_max, core_freq, cores * sizeof (gfloat));\n\n    return TRUE;\n}\n\nvoid os_update(void) {\n    guint i;\n\n    for (i = 0; i < cores; i++) {\n        core_freq[i] = get_frequency(i);\n        if (core_freq[i] < core_freq_min[i])\n            core_freq_min[i] = core_freq[i];\n        if (core_freq[i] > core_freq_max[i])\n            core_freq_max[i] = core_freq[i];\n    }\n}\n\nvoid os_clear_minmax(void) {\n    guint i;\n\n    for (i = 0; i < cores; i++) {\n        core_freq_min[i] = core_freq[i];\n        core_freq_max[i] = core_freq[i];\n    }\n}\n\nGSList* os_get_sensors(void) {\n    GSList *list = NULL;\n    SensorInit *data;\n    guint i;\n\n    for (i = 0; i < cores; i++) {\n        data = sensor_init_new();\n        data->label = g_strdup_printf(\"Core %d Frequency\", display_coreid ? cpu_dev_ids[i].coreid: i);\n        data->hint = g_strdup_printf(\"Current frequency of the CPU as determined by the governor and cpufreq core.\\n Source: %s\", frq_files[i]);\n        data->value = &(core_freq[i]);\n        data->min = &(core_freq_min[i]);\n        data->max = &(core_freq_max[i]);\n        data->printf_format = OS_FREQ_PRINTF_FORMAT;\n        list = g_slist_append(list, data);\n    }\n\n    return list;\n}\n"
  },
  {
    "path": "src/ss/zenpower.c",
    "content": "#include <glib.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"zenmonitor.h\"\n#include \"zenpower.h\"\n\nGSList *zp_sensors = NULL;\nstatic int nodes = 0;\n\ntypedef struct\n{\n    const gchar *label;\n    const gchar *hint;\n    const gchar *file;\n    const gchar *printf_format;\n    const double adjust_ratio;\n} HwmonSensorType;\n\ntypedef struct\n{\n    float current_value;\n    float min;\n    float max;\n    HwmonSensorType *type;\n    gchar *hwmon_dir;\n    int node;\n} HwmonSensor;\n\nstatic HwmonSensorType hwmon_stype[] = {\n  {\"CPU Temperature (tCtl)\",    \"Reported CPU Temperature\",                  \"temp1_input\",  \" %6.2f°C\", 1000.0},\n  {\"CPU Temperature (tDie)\",    \"Reported CPU Temperature - offset\",         \"temp2_input\",  \" %6.2f°C\", 1000.0},\n  {\"CCD1 Temperature\",          \"Core Complex Die 1 Temperature\",            \"temp3_input\",  \" %6.2f°C\", 1000.0},\n  {\"CCD2 Temperature\",          \"Core Complex Die 2 Temperature\",            \"temp4_input\",  \" %6.2f°C\", 1000.0},\n  {\"CCD3 Temperature\",          \"Core Complex Die 3 Temperature\",            \"temp5_input\",  \" %6.2f°C\", 1000.0},\n  {\"CCD4 Temperature\",          \"Core Complex Die 4 Temperature\",            \"temp6_input\",  \" %6.2f°C\", 1000.0},\n  {\"CCD5 Temperature\",          \"Core Complex Die 5 Temperature\",            \"temp7_input\",  \" %6.2f°C\", 1000.0},\n  {\"CCD6 Temperature\",          \"Core Complex Die 6 Temperature\",            \"temp8_input\",  \" %6.2f°C\", 1000.0},\n  {\"CCD7 Temperature\",          \"Core Complex Die 7 Temperature\",            \"temp9_input\",  \" %6.2f°C\", 1000.0},\n  {\"CCD8 Temperature\",          \"Core Complex Die 8 Temperature\",            \"temp10_input\", \" %6.2f°C\", 1000.0},\n  {\"CPU Core Voltage (SVI2)\",   \"Core Voltage reported by SVI2 telemetry\",   \"in1_input\",    \" %8.3f V\", 1000.0},\n  {\"SOC Voltage (SVI2)\",        \"SOC Voltage reported by SVI2 telemetry\",    \"in2_input\",    \" %8.3f V\", 1000.0},\n  {\"CPU Core Current (SVI2)\",   \"Core Current reported by SVI2 telemetry\\n\"\n                                \"Note: May not be accurate on some systems\", \"curr1_input\",  \" %8.3f A\", 1000.0},\n  {\"SOC Current (SVI2)\",        \"SOC Current reported by SVI2 telemetry\\n\"\n                                \"Note: May not be accurate on some systems\", \"curr2_input\",  \" %8.3f A\", 1000.0},\n  {\"CPU Core Power (SVI2)\",     \"Core Voltage * Current\\n\"\n                                \"Note: May not be accurate on some systems\", \"power1_input\", \" %8.3f W\", 1000000.0},\n  {\"SOC Power (SVI2)\",          \"Core Voltage * Current\\n\"\n                                \"Note: May not be accurate on some systems\", \"power2_input\", \" %8.3f W\", 1000000.0},\n  {0, NULL}\n};\n\nstatic gboolean hwmon_file_exists(const gchar *dir, const gchar *file) {\n    gchar *full_path;\n    gboolean result;\n\n    full_path = g_strdup_printf(\"/sys/class/hwmon/%s/%s\", dir, file);\n    result = g_file_test(full_path, G_FILE_TEST_EXISTS);\n\n    g_free(full_path);\n    return result;\n}\n\nstatic gboolean read_raw_hwmon_value(const gchar *dir, const gchar *file, gchar **result) {\n    gchar *full_path;\n    gboolean file_result;\n\n    full_path = g_strdup_printf(\"/sys/class/hwmon/%s/%s\", dir, file);\n    file_result = g_file_get_contents(full_path, result, NULL, NULL);\n\n    g_free(full_path);\n    return file_result;\n}\n\nstatic HwmonSensor *hwmon_sensor_new(HwmonSensorType *type, const gchar *dir, gint node) {\n    HwmonSensor *s;\n    s = g_new0(HwmonSensor, 1);\n    s->min = 999.0;\n    s->type = type;\n    s->hwmon_dir = g_strdup(dir);\n    s->node = node;\n    return s;\n}\n\ngboolean zenpower_init() {\n    GDir *hwmon;\n    const gchar *entry;\n    gchar *name = NULL;\n    HwmonSensorType *type;\n\n    hwmon = g_dir_open(\"/sys/class/hwmon\", 0, NULL);\n    if (!hwmon)\n        return FALSE;\n\n    while ((entry = g_dir_read_name(hwmon))) {\n        read_raw_hwmon_value(entry, \"name\", &name);\n\n        if (strcmp(g_strchomp(name), \"zenpower\") == 0) {\n\n            for (type = hwmon_stype; type->label; type++) {\n                if (hwmon_file_exists(entry, type->file)) {\n                    zp_sensors = g_slist_append(zp_sensors, hwmon_sensor_new(type, entry, nodes));\n                }\n            }\n            nodes++;\n\n        }\n        g_free(name);\n    }\n\n    if (zp_sensors == NULL)\n        return FALSE;\n\n    return TRUE;\n}\n\nvoid zenpower_update() {\n    gchar *tmp = NULL;\n    GSList *node;\n    HwmonSensor *sensor;\n\n    node = zp_sensors;\n    while(node) {\n        sensor = (HwmonSensor *)node->data;\n\n        if (read_raw_hwmon_value(sensor->hwmon_dir, sensor->type->file, &tmp)){\n            sensor->current_value = atof(tmp) / sensor->type->adjust_ratio;\n\n            if (sensor->current_value < sensor->min)\n                sensor->min = sensor->current_value;\n\n            if (sensor->current_value > sensor->max)\n                sensor->max = sensor->current_value;\n\n            g_free(tmp);\n        }\n        else{\n            sensor->current_value = ERROR_VALUE;\n        }\n        node = node->next;\n    }\n}\n\nvoid zenpower_clear_minmax() {\n    HwmonSensor *sensor;\n    GSList *node;\n    node = zp_sensors;\n    while(node) {\n        sensor = (HwmonSensor *)node->data;\n        sensor->min = sensor->current_value;\n        sensor->max = sensor->current_value;\n        node = node->next;\n    }\n}\n\nGSList* zenpower_get_sensors() {\n    GSList *list = NULL;\n    HwmonSensor *sensor;\n    GSList *node;\n    SensorInit *data;\n\n    node = zp_sensors;\n    while(node) {\n        sensor = (HwmonSensor *)node->data;\n\n        data = sensor_init_new();\n        if (nodes > 1){\n            data->label = g_strdup_printf(\"Node %d - %s\", sensor->node, sensor->type->label);\n        }\n        else{\n            data->label = g_strdup(sensor->type->label);\n        }\n        data->hint = g_strdup_printf(\"%s\\nSource: zenpower %s/%s\", sensor->type->hint, sensor->hwmon_dir, sensor->type->file);\n        data->value = &sensor->current_value;\n        data->min = &sensor->min;\n        data->max = &sensor->max;\n        data->printf_format = sensor->type->printf_format;\n        list = g_slist_append(list, data);\n\n        node = node->next;\n    }\n\n    return list;\n}\n"
  },
  {
    "path": "src/sysfs.c",
    "content": "#include <glib.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"sysfs.h\"\n#include \"zenmonitor.h\"\n\n#define CPUD_MAX 512\nstruct bitset {\n    guint bits[CPUD_MAX/32];\n};\n\nstatic int bitset_set(struct bitset *set, int id) {\n    if (id < CPUD_MAX) {\n        int v = (set->bits[id/32] >> (id & 31)) & 1;\n\n        set->bits[id/32] |= 1 << (id & 31);\n        return v;\n    }\n    return 1;\n}\n\nstatic int cmp_cpudev(const void *ap, const void *bp) {\n    return ((struct cpudev *)ap)->cpuid - ((struct cpudev *)bp)->cpuid;\n}\n\nstruct cpudev* get_cpu_dev_ids(void) {\n    struct cpudev *cpu_dev_ids;\n    gshort coreid, cpuid, siblingid;\n    GDir *dir;\n    const gchar *entry;\n    gchar *filename, *buffer;\n    gchar **cpusiblings;\n    gchar **ptr;\n    guint cores;\n    gboolean found;\n    struct bitset seen = { 0 };\n    int i;\n\n    cores = get_core_count();\n    cpu_dev_ids = malloc(cores * sizeof (*cpu_dev_ids));\n    for (i=0;i<cores;i++)\n        cpu_dev_ids[i] = (struct cpudev) { -1, -1 };\n\n    dir = g_dir_open(SYSFS_DIR_CPUS, 0, NULL);\n    if (dir) {\n        int i = 0;\n\n        while ((entry = g_dir_read_name(dir))) {\n            if (sscanf(entry, \"cpu%hd\", &cpuid) != 1) {\n                continue;\n            }\n\n            found = FALSE;\n\n            filename = g_build_filename(SYSFS_DIR_CPUS, entry, \"topology\", \"core_id\", NULL);\n            if (g_file_get_contents(filename, &buffer, NULL, NULL)) {\n                coreid = (gshort) atoi(buffer);\n\n                g_free(filename);\n                g_free(buffer);\n\n                filename = g_build_filename(SYSFS_DIR_CPUS, entry, \"topology\", \"thread_siblings_list\", NULL);\n                if (g_file_get_contents(filename, &buffer, NULL, NULL)) {\n                    cpusiblings = g_strsplit(buffer, \",\", -1);\n                    found = TRUE;\n\n                    // check whether cpu device is not for SMT thread sibling\n                    for (ptr = cpusiblings; *ptr; ptr++) {\n                        siblingid = (gshort) atoi(*ptr);\n\n                        // let's save the cpu device with lower number\n                        if (siblingid < cpuid)\n                            cpuid = siblingid;\n\n                        if (bitset_set(&seen, siblingid)) {\n                            found = FALSE;\n                        }\n                    }\n                    g_strfreev(cpusiblings);\n                }\n            }\n\n            if (found && i < cores) {\n                cpu_dev_ids[i++] = (struct cpudev) { coreid, cpuid };\n            }\n\n            g_free(filename);\n            g_free(buffer);\n        }\n    }\n\n    qsort(cpu_dev_ids, cores, sizeof(*cpu_dev_ids), cmp_cpudev);\n\n    return cpu_dev_ids;\n}\n"
  },
  {
    "path": "src/zenmonitor.c",
    "content": "#include <gtk/gtk.h>\n#include <cpuid.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"zenmonitor.h\"\n#include \"zenpower.h\"\n#include \"msr.h\"\n#include \"os.h\"\n#include \"gui.h\"\n\n#define AMD_STRING \"AuthenticAMD\"\n#define ZEN_FAMILY 0x17\n\n// AMD PPR = https://www.amd.com/system/files/TechDocs/54945_PPR_Family_17h_Models_00h-0Fh.pdf\n\ngboolean check_zen() {\n    guint32 eax = 0, ebx = 0, ecx = 0, edx = 0, ext_family;\n    char vendor[13];\n\n    __get_cpuid(0, &eax, &ebx, &ecx, &edx);\n\n    memcpy(vendor, &ebx, 4);\n    memcpy(vendor+4, &edx, 4);\n    memcpy(vendor+8, &ecx, 4);\n    vendor[12] = 0;\n\n    if (strcmp(vendor, AMD_STRING) != 0){\n        return FALSE;\n    }\n\n    __get_cpuid(1, &eax, &ebx, &ecx, &edx);\n\n    ext_family = ((eax >> 8) & 0xF) + ((eax >> 20) & 0xFF);\n    if (ext_family != ZEN_FAMILY){\n        return FALSE;\n    }\n\n    return TRUE;\n}\n\ngchar *cpu_model() {\n    guint32 eax = 0, ebx = 0, ecx = 0, edx = 0;\n    char model[48];\n\n    // AMD PPR: page 65-68 - CPUID_Fn80000002_EAX-CPUID_Fn80000004_EDX\n    __get_cpuid(0x80000002, &eax, &ebx, &ecx, &edx);\n    memcpy(model, &eax, 4);\n    memcpy(model+4, &ebx, 4);\n    memcpy(model+8, &ecx, 4);\n    memcpy(model+12, &edx, 4);\n\n    __get_cpuid(0x80000003, &eax, &ebx, &ecx, &edx);\n    memcpy(model+16, &eax, 4);\n    memcpy(model+20, &ebx, 4);\n    memcpy(model+24, &ecx, 4);\n    memcpy(model+28, &edx, 4);\n\n    __get_cpuid(0x80000004, &eax, &ebx, &ecx, &edx);\n    memcpy(model+32, &eax, 4);\n    memcpy(model+36, &ebx, 4);\n    memcpy(model+40, &ecx, 4);\n    memcpy(model+44, &edx, 4);\n\n    model[48] = 0;\n    return g_strdup(g_strchomp(model));\n}\n\nguint get_core_count() {\n    guint eax = 0, ebx = 0, ecx = 0, edx = 0;\n    guint logical_cpus, threads_per_code;\n\n    // AMD PPR: page 57 - CPUID_Fn00000001_EBX\n    __get_cpuid(1, &eax, &ebx, &ecx, &edx);\n    logical_cpus = (ebx >> 16) & 0xFF;\n\n    // AMD PPR: page 82 - CPUID_Fn8000001E_EBX\n    __get_cpuid(0x8000001E, &eax, &ebx, &ecx, &edx);\n    threads_per_code = ((ebx >> 8) & 0xF) + 1;\n\n    if (threads_per_code == 0)\n        return logical_cpus;\n\n    return logical_cpus / threads_per_code;\n}\n\nstatic SensorSource sensor_sources[] = {\n    {\n        \"zenpower\",\n        zenpower_init, zenpower_get_sensors, zenpower_update, zenpower_clear_minmax,\n        FALSE, NULL\n    },\n    {\n        \"msr\",\n        msr_init, msr_get_sensors, msr_update, msr_clear_minmax,\n        FALSE, NULL\n    },\n    {\n        \"os\",\n        os_init, os_get_sensors, os_update, os_clear_minmax,\n        FALSE, NULL\n    },\n    {\n        NULL\n    }\n};\n\nSensorInit *sensor_init_new() {\n    return g_new0(SensorInit, 1);\n}\n\nvoid sensor_init_free(SensorInit *s) {\n    if (s) {\n        g_free(s->label);\n        g_free(s->hint);\n        g_free(s);\n    }\n}\n\ngboolean display_coreid = 0;\n\nstatic GOptionEntry options[] =\n{\n    { \"coreid\", 'c', 0, G_OPTION_ARG_NONE, &display_coreid, \"Display core_id instead of core index\", NULL },\n    { NULL }\n};\n\nint main (int argc, char *argv[])\n{\n    GError *error = NULL;\n    GOptionContext *context;\n\n    context = g_option_context_new (\"- Zenmonitor display options\");\n    g_option_context_add_main_entries(context, options, NULL);\n    g_option_context_add_group(context, gtk_get_option_group (TRUE));\n    if (!g_option_context_parse(context, &argc, &argv, &error)) {\n        g_print (\"option parsing failed: %s\\n\", error->message);\n        exit (1);\n    }\n\n    start_gui(sensor_sources);\n}\n"
  }
]