#include #include #include #include #include #include #include #define CPU_TRACKING_WINDOW_SECS 5 #define IO_TRACKING_WINDOW_SECS 5 #define MEM_TRACKING_WINDOW_SECS 5 #define CPU_TRIGGER_THRESHOLD_MS 1000 #define IO_TRIGGER_THRESHOLD_MS 1000 #define MEM_TRIGGER_THRESHOLD_MS 1000 #define CPU_PRESSURE_FILE "/proc/pressure/cpu" #define IO_PRESSURE_FILE "/proc/pressure/io" #define MEM_PRESSURE_FILE "/proc/pressure/memory" #define FD_CPU_IDX 0 #define FD_IO_IDX 1 #define FD_MEM_IDX 2 struct pollfd fds[3]; /* One function that prints the system call and the error details and then exits with error code 1. Non-zero meaning things didn't go well. */ void fatal_error(const char *syscall) { perror(syscall); exit(1); } /* * PSI allows programs to wait for events related to pressure stalls * via poll() so that they can avoid continuously polling files in the * /proc/pressure directory. * * We setup to be notified via poll for two types of PSI events, one * for CPU and the other for I/O. * * */ void setup_polling() { /* Let's first setup our CPU PSI trigger */ fds[FD_CPU_IDX].fd = open(CPU_PRESSURE_FILE, O_RDWR | O_NONBLOCK); if (fds[FD_CPU_IDX].fd < 0) fatal_error("open(): " CPU_PRESSURE_FILE); /* Next, our I/O PSI trigger */ fds[FD_IO_IDX].fd = open(IO_PRESSURE_FILE, O_RDWR | O_NONBLOCK); if (fds[FD_IO_IDX].fd < 0) fatal_error("open(): " IO_PRESSURE_FILE); /* Let's setup our MEM PSI trigger */ fds[FD_MEM_IDX].fd = open(MEM_PRESSURE_FILE, O_RDWR | O_NONBLOCK); if (fds[FD_MEM_IDX].fd < 0) fatal_error("open(): " MEM_PRESSURE_FILE); fds[FD_CPU_IDX].events = fds[FD_IO_IDX].events = fds[FD_MEM_IDX].events = POLLPRI; char trigger[128]; snprintf(trigger, 128, "some %d %d", CPU_TRIGGER_THRESHOLD_MS * 1000, CPU_TRACKING_WINDOW_SECS * 1000000); printf("Trigger: %s\n", trigger); fflush(stdout); if (write(fds[FD_CPU_IDX].fd, trigger, strlen(trigger) + 1) < 0) fatal_error("write(): " CPU_PRESSURE_FILE); snprintf(trigger, 128, "some %d %d", IO_TRIGGER_THRESHOLD_MS * 1000, IO_TRACKING_WINDOW_SECS * 1000000); printf("Trigger: %s\n", trigger); fflush(stdout); if (write(fds[FD_IO_IDX].fd, trigger, strlen(trigger) + 1) < 0) fatal_error("write(): " IO_PRESSURE_FILE); snprintf(trigger, 128, "some %d %d", MEM_TRIGGER_THRESHOLD_MS * 1000, MEM_TRACKING_WINDOW_SECS * 1000000); printf("Trigger: %s\n", trigger); fflush(stdout); if (write(fds[FD_MEM_IDX].fd, trigger, strlen(trigger) + 1) < 0) fatal_error("write(): " MEM_PRESSURE_FILE); } /* * This is the main function where we wait for notifications from PSI. We * increment 2 separate variables that track CPU, MEM and I/O notification * counts separately and print them. */ void wait_for_notification() { int cpu_event_counter = 1; int mem_event_counter = 1; int io_event_counter = 1; while (1) { int n = poll(fds, 3, -1); if (n < 0) { fatal_error("poll()"); } for (int i = 0; i < 3; i++) { /* If the fd of the current iteration does not have any * events, move on to the next fd. * */ if (fds[i].revents == 0) continue; if (fds[i].revents & POLLERR) { fprintf(stderr, "Error: poll() event source is gone.\n"); exit(1); } if (fds[i].revents & POLLPRI) { if (i == FD_CPU_IDX) { printf("CPU PSI event %d triggered.\n", cpu_event_counter++); fflush(stdout); } else if (i == FD_IO_IDX) { printf("IO PSI event %d triggered.\n", mem_event_counter++); fflush(stdout); } else { printf("MEM PSI event %d triggered.\n", io_event_counter++); fflush(stdout); } } else { fprintf(stderr, "Unrecognized event: 0x%x.\n", fds[i].revents); exit(1); } } } } /* * We check for tell-tale signs of the running kernel supporting PSI. * Else, we print a friendly message and exit. * */ void check_basics() { struct stat st; int sret = stat(CPU_PRESSURE_FILE, &st); if (sret == -1) { fprintf(stderr, "Error! Your kernel does not expose pressure stall information.\n"); fprintf(stderr, "You may want to check if you have Linux Kernel v5.2+ with PSI enabled.\n"); exit(1); } } int main() { check_basics(); setup_polling(); wait_for_notification(); return 0; }