235 lines
5.8 KiB
C
235 lines
5.8 KiB
C
/*
|
|
* Copyright (c) 2018 Google, Inc.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*
|
|
* This test attempts to verify that the schedutil governor does not take into
|
|
* account stale utilization from an idle CPU when calculating the frequency for
|
|
* a shared policy.
|
|
*
|
|
* This test is not yet complete and may never be. The CPU in question may
|
|
* receive spurious updates which push the stale deadline out, causing the test
|
|
* to fail.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <sched.h>
|
|
#include <time.h>
|
|
#include <semaphore.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "tst_test.h"
|
|
#include "tst_safe_file_ops.h"
|
|
#include "tst_safe_pthread.h"
|
|
|
|
#include "trace_parse.h"
|
|
#include "util.h"
|
|
|
|
#define TRACE_EVENTS "sugov_next_freq sugov_util_update"
|
|
|
|
#define MAX_TEST_CPUS 32
|
|
static int policy_cpus[MAX_TEST_CPUS];
|
|
static int policy_num_cpus = 0;
|
|
|
|
static int test_cpu;
|
|
static sem_t sem;
|
|
|
|
/* sugov currently waits 1.125 * TICK_NSEC, which with HZ=300, is
|
|
* ~3.75ms for PELT
|
|
* On WALT, 1.125 * sched_ravg_window (20ms) is 22.5ms */
|
|
#define MAX_STALE_USEC 22500
|
|
/* The event task may not wake up right away due to timer slack. */
|
|
#define SLACK_USEC 10000
|
|
|
|
static void *event_fn(void *arg LTP_ATTRIBUTE_UNUSED)
|
|
{
|
|
/*
|
|
* FIXME: Proper logic to identify a multi-CPU policy and select two
|
|
* CPUS from it is required here.
|
|
*/
|
|
affine(test_cpu - 1);
|
|
|
|
sem_wait(&sem);
|
|
|
|
tracefs_write("trace_marker", "event task sleep");
|
|
usleep(MAX_STALE_USEC);
|
|
tracefs_write("trace_marker", "event task wake");
|
|
/*
|
|
* Waking up should be sufficient to get the cpufreq policy to
|
|
* re-evaluate.
|
|
*/
|
|
return NULL;
|
|
}
|
|
|
|
#define BURN_MSEC 500
|
|
static void *burn_fn(void *arg LTP_ATTRIBUTE_UNUSED)
|
|
{
|
|
affine(test_cpu);
|
|
|
|
/*
|
|
* wait a bit to allow any hacks to boost frequency on migration
|
|
* to take effect
|
|
*/
|
|
usleep(200);
|
|
|
|
/* Busy loop for BURN_MSEC to get the task demand to maximum. */
|
|
burn(BURN_MSEC * 1000, 0);
|
|
|
|
/*
|
|
* Sleep. The next sugov update after TICK_NSEC should not include
|
|
* this task's contribution.
|
|
*/
|
|
tracefs_write("trace_marker", "sleeping");
|
|
|
|
/*
|
|
* Wake up task on another CPU in the same policy which will sleep
|
|
* for stale_ns, then wake up briefly to trigger a recalculation of the
|
|
* cpufreq policy.
|
|
*/
|
|
sem_post(&sem);
|
|
sleep(2);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int cpu_in_policy(int cpu)
|
|
{
|
|
int i;
|
|
for (i = 0; i < policy_num_cpus; i++)
|
|
if (cpu == policy_cpus[i])
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_results(void)
|
|
{
|
|
int i, sleep_idx;
|
|
int max_util_seen = 0;
|
|
unsigned int stale_usec;
|
|
|
|
/* Verify that utilization reached 1024 before sleep. */
|
|
for (i = 0; i < num_trace_records; i++) {
|
|
if (trace[i].event_type == TRACE_RECORD_SUGOV_UTIL_UPDATE) {
|
|
struct trace_sugov_util_update *t =
|
|
trace[i].event_data;
|
|
if (t->cpu == test_cpu && t->util > max_util_seen)
|
|
max_util_seen = t->util;
|
|
}
|
|
if (trace[i].event_type == TRACE_RECORD_TRACING_MARK_WRITE &&
|
|
!strcmp(trace[i].event_data, "sleeping"))
|
|
break;
|
|
}
|
|
printf("Max util seen from CPU hog: %d\n", max_util_seen);
|
|
if (max_util_seen < 1000) {
|
|
printf("Trace parse error, utilization of CPU hog did "
|
|
"not reach 1000.\n");
|
|
return -1;
|
|
}
|
|
sleep_idx = i;
|
|
// print_trace_record(&trace[i]);
|
|
for (; i < num_trace_records; i++)
|
|
if (trace[i].event_type == TRACE_RECORD_SUGOV_NEXT_FREQ) {
|
|
struct trace_sugov_next_freq *t =
|
|
trace[i].event_data;
|
|
/* We should only see some minor utilization. */
|
|
if (cpu_in_policy(t->cpu) && t->util < 200)
|
|
break;
|
|
}
|
|
if (i == num_trace_records) {
|
|
printf("Trace parse error, util never went stale!\n");
|
|
return -1;
|
|
}
|
|
// print_trace_record(&trace[i]);
|
|
stale_usec = TS_TO_USEC(trace[i].ts) - TS_TO_USEC(trace[sleep_idx].ts);
|
|
|
|
printf("Stale vote shown to be cleared in %d usec.\n", stale_usec);
|
|
return (stale_usec > (MAX_STALE_USEC + SLACK_USEC));
|
|
}
|
|
|
|
#define POLICY_CPUS_BUFSIZE 1024
|
|
static void get_policy_cpus(void)
|
|
{
|
|
int i=0, len, policy_cpus_fd;
|
|
char policy_cpus_fname[128];;
|
|
char *buf;
|
|
|
|
sprintf(policy_cpus_fname,
|
|
"/sys/devices/system/cpu/cpu%d/cpufreq/related_cpus",
|
|
test_cpu);
|
|
buf = SAFE_MALLOC(POLICY_CPUS_BUFSIZE);
|
|
|
|
policy_cpus_fd = open(policy_cpus_fname, O_RDONLY);
|
|
if (policy_cpus_fd < 0) {
|
|
printf("Failed to open policy cpus (errno %d)\n",
|
|
errno);
|
|
return;
|
|
}
|
|
|
|
len = read(policy_cpus_fd, buf, POLICY_CPUS_BUFSIZE -1);
|
|
/* At least one digit is expected. */
|
|
if (len < 2) {
|
|
printf("Read of policy cpus returned %d (errno %d)\n",
|
|
len, errno);
|
|
return;
|
|
}
|
|
close(policy_cpus_fd);
|
|
/* buf now has a list of CPUs, parse it */
|
|
while(buf[i] >= '0' && buf[i] <= '9') {
|
|
int j = i;
|
|
while (buf[j] >= '0' && buf[j] <= '9')
|
|
j++;
|
|
buf[j] = 0;
|
|
policy_cpus[policy_num_cpus++] = atoi(&buf[i]);
|
|
i = j + 1;
|
|
}
|
|
printf("Testing on CPU %d, all CPUs in that policy:\n",
|
|
test_cpu);
|
|
for (int i = 0; i < policy_num_cpus; i++)
|
|
printf(" %d", policy_cpus[i]);
|
|
printf("\n");
|
|
free(buf);
|
|
}
|
|
|
|
static void run(void)
|
|
{
|
|
pthread_t burn_thread, event_thread;
|
|
|
|
test_cpu = tst_ncpus() - 1;
|
|
printf("CPU hog will be bound to CPU %d.\n", test_cpu);
|
|
get_policy_cpus();
|
|
|
|
sem_init(&sem, 0, 0);
|
|
|
|
/* configure and enable tracing */
|
|
tracefs_write("tracing_on", "0");
|
|
tracefs_write("buffer_size_kb", "16384");
|
|
tracefs_write("set_event", TRACE_EVENTS);
|
|
tracefs_write("trace", "\n");
|
|
tracefs_write("tracing_on", "1");
|
|
|
|
SAFE_PTHREAD_CREATE(&burn_thread, NULL, burn_fn, NULL);
|
|
SAFE_PTHREAD_CREATE(&event_thread, NULL, event_fn, NULL);
|
|
|
|
SAFE_PTHREAD_JOIN(burn_thread, NULL);
|
|
SAFE_PTHREAD_JOIN(event_thread, NULL);
|
|
|
|
/* disable tracing */
|
|
tracefs_write("tracing_on", "0");
|
|
LOAD_TRACE();
|
|
|
|
if (parse_results())
|
|
tst_res(TFAIL, "Stale utilization not cleared within expected "
|
|
"time (%d usec).\n", MAX_STALE_USEC + SLACK_USEC);
|
|
else
|
|
tst_res(TPASS, "Stale utilization cleared within expected "
|
|
"time.\n");
|
|
}
|
|
|
|
static struct tst_test test = {
|
|
.test_all = run,
|
|
.setup = trace_setup,
|
|
.cleanup = trace_cleanup,
|
|
};
|