From 9212f19de18f3e574a35780e6bb451fde15ce19d Mon Sep 17 00:00:00 2001 From: Dr-Noob Date: Fri, 5 Jul 2024 08:40:46 +0100 Subject: [PATCH] [v1.05] Add support for frequency measurement (both x86 and ARM) (branch measure-freq #220) --- Makefile | 4 +- src/arm/midr.c | 7 +++ src/common/freq.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++ src/common/freq.h | 6 ++ src/x86/cpuid.c | 12 ++++ 5 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 src/common/freq.c create mode 100644 src/common/freq.h diff --git a/Makefile b/Makefile index 4ce178f..7ba02f1 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,8 @@ PREFIX ?= /usr SRC_COMMON=src/common/ -COMMON_SRC = $(SRC_COMMON)main.c $(SRC_COMMON)cpu.c $(SRC_COMMON)udev.c $(SRC_COMMON)printer.c $(SRC_COMMON)args.c $(SRC_COMMON)global.c -COMMON_HDR = $(SRC_COMMON)ascii.h $(SRC_COMMON)cpu.h $(SRC_COMMON)udev.h $(SRC_COMMON)printer.h $(SRC_COMMON)args.h $(SRC_COMMON)global.h +COMMON_SRC = $(SRC_COMMON)main.c $(SRC_COMMON)cpu.c $(SRC_COMMON)udev.c $(SRC_COMMON)printer.c $(SRC_COMMON)args.c $(SRC_COMMON)global.c $(SRC_COMMON)freq.c +COMMON_HDR = $(SRC_COMMON)ascii.h $(SRC_COMMON)cpu.h $(SRC_COMMON)udev.h $(SRC_COMMON)printer.h $(SRC_COMMON)args.h $(SRC_COMMON)global.h $(SRC_COMMON)freq.h ifneq ($(OS),Windows_NT) GIT_VERSION := "$(shell git describe --abbrev=4 --dirty --always --tags)" diff --git a/src/arm/midr.c b/src/arm/midr.c index 805ec47..7048dbf 100644 --- a/src/arm/midr.c +++ b/src/arm/midr.c @@ -8,6 +8,7 @@ #ifdef __linux__ #include #include + #include "../common/freq.h" #elif defined __APPLE__ || __MACH__ #include "sysctl.h" #endif @@ -41,6 +42,12 @@ struct frequency* get_frequency_info(uint32_t core) { freq->base = UNKNOWN_DATA; freq->max = get_max_freq_from_file(core); + #ifdef __linux__ + if (freq->max == UNKNOWN_DATA) { + printWarn("Unable to find max frequency from udev, measuring CPU frequency"); + freq->max = measure_max_frequency(core); + } + #endif return freq; } diff --git a/src/common/freq.c b/src/common/freq.c new file mode 100644 index 0000000..743a49a --- /dev/null +++ b/src/common/freq.c @@ -0,0 +1,154 @@ +#ifdef __linux__ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "global.h" + +static long +perf_event_open(struct perf_event_attr *hw_event, pid_t pid, + int cpu, int group_fd, unsigned long flags) { + int ret; + ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, + group_fd, flags); + return ret; +} + +#define INSERT_ASM_ONCE __asm volatile("nop"); +#define INSERT_ASM_10_TIMES \ + INSERT_ASM_ONCE \ + INSERT_ASM_ONCE \ + INSERT_ASM_ONCE \ + INSERT_ASM_ONCE \ + INSERT_ASM_ONCE \ + INSERT_ASM_ONCE \ + INSERT_ASM_ONCE \ + INSERT_ASM_ONCE \ + INSERT_ASM_ONCE \ + INSERT_ASM_ONCE \ + +#define INSERT_ASM_100_TIMES \ + INSERT_ASM_10_TIMES \ + INSERT_ASM_10_TIMES \ + INSERT_ASM_10_TIMES \ + INSERT_ASM_10_TIMES \ + INSERT_ASM_10_TIMES \ + INSERT_ASM_10_TIMES \ + INSERT_ASM_10_TIMES \ + INSERT_ASM_10_TIMES \ + INSERT_ASM_10_TIMES \ + INSERT_ASM_10_TIMES + +#define INSERT_ASM_1000_TIMES \ + INSERT_ASM_100_TIMES \ + INSERT_ASM_100_TIMES \ + INSERT_ASM_100_TIMES \ + INSERT_ASM_100_TIMES \ + INSERT_ASM_100_TIMES \ + INSERT_ASM_100_TIMES \ + INSERT_ASM_100_TIMES \ + INSERT_ASM_100_TIMES \ + INSERT_ASM_100_TIMES \ + INSERT_ASM_100_TIMES \ + +void nop_function(uint64_t iters) { + for (uint64_t i = 0; i < iters; i++) { + INSERT_ASM_1000_TIMES + INSERT_ASM_1000_TIMES + INSERT_ASM_1000_TIMES + INSERT_ASM_1000_TIMES + } +} + +// Differences between x86 measure_frequency this measure_max_frequency: +// - measure_frequency employs all cores simultaneously wherease +// measure_max_frequency only employs 1. +// - measure_frequency runs the computation and checks /proc/cpuinfo whereas +// measure_max_frequency does not rely on /proc/cpuinfo and simply +// counts cpu cycles to measure frequency. +// - measure_frequency uses actual computation while measuring the frequency +// whereas measure_max_frequency uses nop instructions. This makes the former +// x86 dependant whereas the latter is architecture independant. +int64_t measure_max_frequency(uint32_t core) { + if (!bind_to_cpu(core)) { + printErr("Failed binding the process to CPU %d", core); + return -1; + } + + clockid_t clock = CLOCK_PROCESS_CPUTIME_ID; + + struct perf_event_attr pe; + uint64_t instructions; + int fd; + int pid = 0; + + memset(&pe, 0, sizeof(struct perf_event_attr)); + pe.type = PERF_TYPE_HARDWARE; + pe.size = sizeof(struct perf_event_attr); + pe.config = PERF_COUNT_HW_CPU_CYCLES; + pe.disabled = 1; + pe.exclude_kernel = 1; + pe.exclude_hv = 1; + + fd = perf_event_open(&pe, pid, core, -1, 0); + if (fd == -1) { + perror("perf_event_open"); + if (errno == EPERM || errno == EACCES) { + printf("You may not have permission to collect stats.\n"); + printf("Consider tweaking /proc/sys/kernel/perf_event_paranoid or running as root.\n"); + } + return -1; + } + + const char* frequency_banner = "cpufetch is measuring the max frequency..."; + printf("%s", frequency_banner); + fflush(stdout); + + uint64_t iters = 10000000; + struct timespec start, end; + if (clock_gettime(clock, &start) == -1) { + perror("clock_gettime"); + return -1; + } + if(ioctl(fd, PERF_EVENT_IOC_RESET, 0) == -1) { + perror("ioctl"); + return -1; + } + if(ioctl(fd, PERF_EVENT_IOC_ENABLE, 0) == -1) { + perror("ioctl"); + return -1; + } + + nop_function(iters); + + read(fd, &instructions, sizeof(uint64_t)); + if(ioctl(fd, PERF_EVENT_IOC_DISABLE, 0) == -1) { + perror("ioctl"); + return -1; + } + if (clock_gettime(clock, &end) == -1) { + perror("clock_gettime"); + return -1; + } + + uint64_t nsecs = (end.tv_sec*1e9 + end.tv_nsec) - (start.tv_sec*1e9 + start.tv_nsec); + uint64_t usecs = nsecs/1000; + double frequency = instructions/((double)usecs); + + printf("\r%*c\r", (int) strlen(frequency_banner), ' '); + + // Discard last digit in the frequency, which should help providing + // more reliable and predictable values. + return (((int) frequency + 5)/10) * 10; +} + +#endif // #ifdef __linux__ diff --git a/src/common/freq.h b/src/common/freq.h new file mode 100644 index 0000000..0eb42ab --- /dev/null +++ b/src/common/freq.h @@ -0,0 +1,6 @@ +#ifndef __COMMON_FREQ__ +#define __COMMON_FREQ__ + +int64_t measure_max_frequency(uint32_t core); + +#endif diff --git a/src/x86/cpuid.c b/src/x86/cpuid.c index a937cfa..48f10f8 100644 --- a/src/x86/cpuid.c +++ b/src/x86/cpuid.c @@ -6,6 +6,10 @@ #include #endif +#ifdef __linux__ + #include "../common/freq.h" +#endif + #include #include #include @@ -967,6 +971,14 @@ struct frequency* get_frequency_info(struct cpuInfo* cpu) { } } + #ifdef __linux__ + if (freq->max == UNKNOWN_DATA) { + printWarn("All previous methods failed, measuring CPU frequency"); + // TODO: Support hybrid architectures + freq->max = measure_max_frequency(0); + } + #endif + return freq; }