Skip to content

Commit

Permalink
[v1.05] Add support for frequency measurement (both x86 and ARM) (bra…
Browse files Browse the repository at this point in the history
…nch measure-freq #220)
  • Loading branch information
Dr-Noob committed Jul 5, 2024
1 parent b019256 commit 9212f19
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 2 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down
7 changes: 7 additions & 0 deletions src/arm/midr.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#ifdef __linux__
#include <sys/auxv.h>
#include <asm/hwcap.h>
#include "../common/freq.h"
#elif defined __APPLE__ || __MACH__
#include "sysctl.h"
#endif
Expand Down Expand Up @@ -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;
}
Expand Down
154 changes: 154 additions & 0 deletions src/common/freq.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#ifdef __linux__

#define _GNU_SOURCE

#include <time.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <asm/unistd.h>
#include <sys/ioctl.h>
#include <linux/perf_event.h>

#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__
6 changes: 6 additions & 0 deletions src/common/freq.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef __COMMON_FREQ__
#define __COMMON_FREQ__

int64_t measure_max_frequency(uint32_t core);

#endif
12 changes: 12 additions & 0 deletions src/x86/cpuid.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#include <unistd.h>
#endif

#ifdef __linux__
#include "../common/freq.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -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;
}

Expand Down

0 comments on commit 9212f19

Please sign in to comment.