diff --git a/Makefile b/Makefile index c9a2c36..a19db51 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/common/freq.c b/src/common/freq.c new file mode 100644 index 0000000..8c972d4 --- /dev/null +++ b/src/common/freq.c @@ -0,0 +1,124 @@ +#ifdef __linux__ + +#define _GNU_SOURCE + +// TODO: This should be under common/ +#include "../x86/apic.h" +#include "../x86/freq/freq.h" +#include "global.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} + + +// 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(void) { + // TODO: Support binding on all cores inside a SoC (x86 + // hybrid topologies and ARM SoCs) + int core = 0; + if (!bind_to_cpu(core)) { + printErr("Failed binding the process to CPU %d", core); + return -1; + } + + const char* frequency_banner = "cpufetch is measuring the max frequency..."; + printf(frequency_banner); + fflush(stdout); + + clockid_t clock = CLOCK_PROCESS_CPUTIME_ID; + + struct perf_event_attr pe; + uint64_t instructions; + int fd; + int pid = 0; + + void (*nop_function)(uint64_t); + +#ifdef ARCH_X86 + nop_function = nop_function_x86; +#elif ARCH_ARM + // TODO + nop_function = nop_function_arm; +#endif + + 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"); + return -1; + } + + 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), ' '); + + printf("%ld %ld\n", instructions, usecs); + printf("%f\n", frequency); + + + // Discard last digit in the frequency which should help providing more reliable + // 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..10923ec --- /dev/null +++ b/src/common/freq.h @@ -0,0 +1,6 @@ +#ifndef __COMMON_FREQ__ +#define __COMMON_FREQ__ + +int64_t measure_max_frequency(void); + +#endif diff --git a/src/x86/cpuid.c b/src/x86/cpuid.c index 2636707..25b31ae 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 @@ -960,6 +964,13 @@ struct frequency* get_frequency_info(struct cpuInfo* cpu) { } } + #ifdef __linux__ + if (freq->max == UNKNOWN_DATA) { + printWarn("All previous methods failed, measuring CPU frequency"); + freq->max = measure_max_frequency(); + } + #endif + return freq; } diff --git a/src/x86/freq/freq.c b/src/x86/freq/freq.c index def701b..22cf23f 100644 --- a/src/x86/freq/freq.c +++ b/src/x86/freq/freq.c @@ -144,3 +144,49 @@ int64_t measure_frequency(struct cpuInfo* cpu) { printf("\r%*c", num_spaces, ' '); return freq_struct->freq; } + +#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_x86(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 + } +} \ No newline at end of file diff --git a/src/x86/freq/freq.h b/src/x86/freq/freq.h index de62916..8d78e5d 100644 --- a/src/x86/freq/freq.h +++ b/src/x86/freq/freq.h @@ -9,5 +9,6 @@ #define LOOP_ITERS 100000000 int64_t measure_frequency(struct cpuInfo* cpu); +void nop_function_x86(uint64_t iters); #endif