climateprediction.net home page
Monitoring total system throughput in instructions per second

Monitoring total system throughput in instructions per second

Questions and Answers : Unix/Linux : Monitoring total system throughput in instructions per second
Message board moderation

To post messages, you must log in.

AuthorMessage
SolarSyonyk

Send message
Joined: 7 Sep 16
Posts: 257
Credit: 31,975,197
RAC: 35,342
Message 69040 - Posted: 28 Jun 2023, 20:29:27 UTC

I've talked a bit about monitoring "total instructions retired per second" for throughput optimization, and figured I should probably at least share the code I wrote to do it...

This code will only work for AMD chips, on Linux. You'll need to 'modprobe cpuid' and 'modprobe msr' before it will run. It monitors the instruction counters on each core and reports out the differences, in instructions per second.

gcc -o ryzen_counters ryzen_counters.c should build it.

Run it as root, and if you pass a command line argument, it'll use that as the number of seconds to average over - so './ryzen_counters 60' will measure for 60 seconds and then report out the IPS.

// Code written by Syonyk, https://www.sevarg.net - keep this attribution.

#include <fcntl.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define TARGET_CPU "AuthenticAMD"
#define DEV_PATH_BASE "/dev/cpu/"

#define MSR_HWCR    0xC0010015
#define MSR_RETIRED 0xC00000E9
#define IPERF_EN    0x40000000

// I'm *sure* that I'll regret setting such a low limit... at some point...
// If this isn't enough, update it.
#define MAX_CPU_CORES 512

struct cpu_retired_counts
{
    uint64_t timestamp;
    uint64_t current_counts[MAX_CPU_CORES];
};

struct cpu_msr_fds
{
    size_t max_cpus;
    int fd[MAX_CPU_CORES];
};


/*
 * Checks to see if there's a real AMD CPU underneath.  This code does not work
 * on Intel, and will generally refuse to try.  Only checks CPU 0 - if the
 * different CPU cores are different vendors, this is probably someone messing
 * around with a hypervisor and I simply don't care to validate this.
 * 
 * Bytes 4-7 (ebx):  "Auth"
 * Bytes 12-15 (edx): "enti"
 * Bytes 8-11 (ecx): "cAMD"
 */
bool CheckCpu(void)
{
    // If we can't open /dev/cpu/0/cpuid, we're probably not root.
    FILE *f = fopen("/dev/cpu/0/cpuid", "r");
    char buf[16];
    int rc;
    if (!f)
    {
        printf("ERROR: Cannot open /dev/cpu/0/cpuid.  Are you root?\n");
        return false;
    }

    // Vendor string is at offset 0 - so we should just be able to read 16 bytes
    // at the current offset and see what the 12 bytes out is.
    rc = fread(buf, 1, 16, f);
    fclose(f);
    
    if (rc != 16)
    {
        printf("ERROR: Could not read from /dev/cpu/0/cpuid.  Are you root?\n");
        return false;
    }

    if (memcmp(&buf[4], "Auth", 4) || memcmp(&buf[12], "enti", 4) || memcmp(&buf[8], "cAMD", 4))
    {
        printf("This CPU does not appear to be AMD.  Sorry!\n");
        return false;
    }

    return true;
}

/*
 * Ensure that the MSR dev exists, and that we can read it.  We'll just read the
 * timestamp counter MSR, 0x10, and ensure that the second read is more than the
 * first read - that should be enough to validate that we have a working MSR
 * interface.
 * 
 * We *should* have root permissions if we're here - we read cpuid!
 * 
 * The /dev/cpu/?/msr interface doesn't seem to like a separate seek and read -
 * you need to use pread to properly read the interface.
 */
bool CheckMSR(void)
{
    int fd = open("/dev/cpu/0/msr", O_RDONLY);
    uint64_t val1, val2;
    int rc;
    if (fd < 0)
    {
        printf("ERROR: Cannot open /dev/cpu/0/msr.  You might "
        "'sudo modprobe msr' and try again?\n");
        return false;
    }

    // MSR reads simply involve reading to the desired location - here, 0x10.
    
    rc = pread(fd, &val1, 8, 0x10);
    rc += pread(fd, &val2, 8, 0x10);
    close(fd);
    
    if (rc != 16)
    {
        printf("ERROR: Could not read from /dev/cpu/0/msr.  I got nothing...\n");
        return false;
    }

    printf("TSC reads: 0x%16lx 0x%16lx\n", val1, val2);

    if (!val1 || !val2 || (val1 == val2) || (val1 > val2))
    {
        printf("ERROR: TSC MSR sanity check failed.\n");
        return false;
    }

    return true;
}

/*
 * Open FDs for all the MSR interfaces.  We know we can read MSRs from the test,
 * so these should always be available.  If they're not after opening, something
 * catastrophic has happened, and it's not my problem!
 * 
 * How do we know how many CPUs there are?  I assume that the CPU numbers in
 * Linux are sequential, so when I run out, I'm done!
 */
struct cpu_msr_fds OpenAllCPUMSRs(void)
{
    struct cpu_msr_fds ret = {0};

    for (size_t cpu = 0; cpu < MAX_CPU_CORES; cpu++)
    {
        char buf[128];
        int fd;
        snprintf(buf, sizeof(buf), "/dev/cpu/%lu/msr", cpu);
        fd = open(buf, O_RDONLY);
        if (fd >= 0)
        {
            printf("Opened MSR file for CPU %lu\n", cpu);
            ret.max_cpus++;
            ret.fd[cpu] = fd;
        }
        else
            break;
    }

    return ret;
}

/*
 * Ensure that the CPU counters are enabled on all the CPUs.  If not, either
 * report out, or just enable them.  Returns true if all counters are enabled,
 * false if any are not enabled and it was not told to enable them.
 */
bool EnableAllCPUCounters(struct cpu_msr_fds* cpu_fds, bool enable_counters)
{
    uint64_t hwcr;
    bool rc = true;

    for (size_t cpu = 0; cpu < cpu_fds->max_cpus; cpu++)
    {
        if (pread(cpu_fds->fd[cpu], &hwcr, 8, MSR_HWCR) != 8)
        {
            printf("Error reading MSR on CPU %lu... ?\n", cpu);
            return false;
        }

        if (!(hwcr & 0x1))
        {
            printf("Very interesting.  Your SMM is unlocked on CPU %lu!\n", cpu);
        }

        if (!(hwcr & IPERF_EN))
        {
            printf("IRPerfEn not set on CPU %lu\n", cpu);
            if (enable_counters)
            {
                hwcr |= IPERF_EN;
                pwrite(cpu_fds->fd[cpu], &hwcr, 8, MSR_HWCR);
            }
            else
                rc = false;
        }
    }
    return rc;
}

void ReadAllCounters(struct cpu_retired_counts* counts, struct cpu_msr_fds* cpu_fds)
{
    struct timespec spec;
    clock_gettime(CLOCK_MONOTONIC, &spec);

    //printf("Timespec: %lu/%lu\n", spec.tv_sec, spec.tv_nsec);

    counts->timestamp = spec.tv_sec * 1000000000 + spec.tv_nsec;

    for (size_t cpu = 0; cpu < cpu_fds->max_cpus; cpu++)
    {
        pread(cpu_fds->fd[cpu], &(counts->current_counts[cpu]), 8, MSR_RETIRED);
    }
}

void PrintInstructionCounts(struct cpu_retired_counts* cur, struct cpu_retired_counts* prev, size_t max_cpus)
{
    float seconds = (cur->timestamp - prev->timestamp) / 1000000000.0;
    float total = 0;
    char suffix = ' ';

    for (size_t cpu = 0; cpu < max_cpus; cpu++)
    {
        uint64_t insns = cur->current_counts[cpu] - prev->current_counts[cpu];
        float insns_per_sec = (float)insns / seconds;
        suffix = ' ';
        total += insns_per_sec;

        if (insns_per_sec >= 1000)
        {
            suffix = 'K';
            insns_per_sec /= 1000.0;
        }
        if (insns_per_sec >= 1000)
        {
            suffix = 'M';
            insns_per_sec /= 1000.0;
        }
        if (insns_per_sec >= 1000)
        {
            suffix = 'G';
            insns_per_sec /= 1000.0;
        }
        printf("CPU %3lu: %6.2f%c\n", cpu, insns_per_sec, suffix);
    }
    suffix = ' ';
    if (total >= 1000)
    {
        suffix = 'K';
        total /= 1000.0;
    }
    if (total >= 1000)
    {
        suffix = 'M';
        total /= 1000.0;
    }
    if (total >= 1000)
    {
        suffix = 'G';
        total /= 1000.0;
    }



    printf("\nTotal: %3.2f%c\n\n", total, suffix);
}

int main(int argc, char* argv[])
{
    struct cpu_msr_fds cpu_fds;
    struct cpu_retired_counts cur, prev;
    int delay = 1;

    if (argc == 2)
        delay = atoi(argv[1]);


    if (CheckCpu())
    {
        // Now check for MSR access.
        if (CheckMSR())
        {
            // Should be good to go!
            cpu_fds = OpenAllCPUMSRs();
            if (EnableAllCPUCounters(&cpu_fds, true))
            {
                ReadAllCounters(&prev, &cpu_fds);
                sleep(1);

                while(1)
                {
                    ReadAllCounters(&cur, &cpu_fds);
                    PrintInstructionCounts(&cur, &prev, cpu_fds.max_cpus);
                    memcpy(&prev, &cur, sizeof(cur));
                    sleep(delay);
                }
            }
        }
    }

    return 0;
}


If your system has suspended and woke, you may notice that there's no readout for CPU 0 - I've not quite worked out the details, but resetting one of the MSRs as root will solve it.

wrmsr -a 0xC0010015 0x1C9000011

If you're on Intel, the Performance Counter Monitor tools (https://github.com/intel/pcm) will do the same thing, and get you more data about DRAM bandwidth as well - I use those for the Intel boxes.

Hopefully this is helpful!
ID: 69040 · Report as offensive     Reply Quote

Questions and Answers : Unix/Linux : Monitoring total system throughput in instructions per second

©2024 climateprediction.net