You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
6.3 KiB
232 lines
6.3 KiB
/*
|
|
* Copyright (c) 2018 Google, Inc.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*
|
|
* A CFS task is affined to a particular CPU. The task runs as a CPU hog for a
|
|
* while then as a very small task for a while. The latency for the CPU
|
|
* frequency of the CPU to reach max and then min is verified.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <sched.h>
|
|
#include <time.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 "sched_process_exit sched_process_fork cpu_frequency"
|
|
|
|
#define MAX_FREQ_INCREASE_LATENCY_US 70000
|
|
#define MAX_FREQ_DECREASE_LATENCY_US 70000
|
|
|
|
static int test_cpu;
|
|
|
|
#define BURN_MSEC 500
|
|
static void *burn_fn(void *arg LTP_ATTRIBUTE_UNUSED)
|
|
{
|
|
int i = 0;
|
|
unsigned int scaling_min_freq, scaling_cur_freq;
|
|
char scaling_freq_file[60];
|
|
|
|
affine(test_cpu);
|
|
|
|
/*
|
|
* wait a bit to allow any hacks to boost frequency on migration
|
|
* to take effect
|
|
*/
|
|
usleep(200);
|
|
|
|
sprintf(scaling_freq_file,
|
|
"/sys/devices/system/cpu/cpu%d/cpufreq/scaling_min_freq",
|
|
test_cpu);
|
|
SAFE_FILE_SCANF(scaling_freq_file, "%d", &scaling_min_freq);
|
|
|
|
sprintf(scaling_freq_file,
|
|
"/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq",
|
|
test_cpu);
|
|
|
|
/* wait for test_cpu to reach scaling_min_freq */
|
|
while(i++ < 10) {
|
|
usleep(100 * 1000);
|
|
SAFE_FILE_SCANF(scaling_freq_file, "%d",
|
|
&scaling_cur_freq);
|
|
if (scaling_cur_freq == scaling_min_freq)
|
|
break;
|
|
}
|
|
if (i >= 10) {
|
|
printf("Unable to reach scaling_min_freq before test!\n");
|
|
return NULL;
|
|
}
|
|
|
|
tracefs_write("trace_marker", "affined");
|
|
burn(BURN_MSEC * 1000, 0);
|
|
tracefs_write("trace_marker", "small task");
|
|
burn(BURN_MSEC * 1000, 1);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int parse_results(void)
|
|
{
|
|
int i;
|
|
|
|
int start_idx;
|
|
int sleep_idx;
|
|
unsigned int max_freq_seen = 0;
|
|
int max_freq_seen_idx;
|
|
unsigned int min_freq_seen = UINT_MAX;
|
|
int min_freq_seen_idx;
|
|
|
|
char scaling_freq_file[60];
|
|
unsigned int scaling_max_freq;
|
|
unsigned int scaling_min_freq;
|
|
|
|
unsigned int increase_latency_usec;
|
|
unsigned int decrease_latency_usec;
|
|
|
|
/* find starting timestamp of test */
|
|
for (i = 0; i < num_trace_records; i++)
|
|
if (trace[i].event_type == TRACE_RECORD_TRACING_MARK_WRITE &&
|
|
!strcmp(trace[i].event_data, "affined"))
|
|
break;
|
|
if (i == num_trace_records) {
|
|
printf("Did not find start of burn thread in trace!\n");
|
|
return -1;
|
|
}
|
|
start_idx = i;
|
|
|
|
/* find timestamp when burn thread sleeps */
|
|
for (; i < num_trace_records; i++)
|
|
if (trace[i].event_type == TRACE_RECORD_TRACING_MARK_WRITE &&
|
|
!strcmp(trace[i].event_data, "small task"))
|
|
break;
|
|
if (i == num_trace_records) {
|
|
printf("Did not find switch to small task of burn thread in "
|
|
"trace!\n");
|
|
return -1;
|
|
}
|
|
sleep_idx = i;
|
|
|
|
/* find highest CPU frequency bewteen start and sleep timestamp */
|
|
for (i = start_idx; i < sleep_idx; i++)
|
|
if (trace[i].event_type == TRACE_RECORD_CPU_FREQUENCY) {
|
|
struct trace_cpu_frequency *t = trace[i].event_data;
|
|
if (t->cpu == test_cpu && t->state > max_freq_seen) {
|
|
max_freq_seen = t->state;
|
|
max_freq_seen_idx = i;
|
|
}
|
|
}
|
|
if (max_freq_seen == 0) {
|
|
printf("No freq events between start and sleep!\n");
|
|
return -1;
|
|
}
|
|
|
|
/* find lowest CPU frequency between sleep timestamp and end */
|
|
for (; i < num_trace_records; i++)
|
|
if (trace[i].event_type == TRACE_RECORD_CPU_FREQUENCY) {
|
|
struct trace_cpu_frequency *t = trace[i].event_data;
|
|
if (t->cpu == test_cpu && t->state < min_freq_seen) {
|
|
min_freq_seen = t->state;
|
|
min_freq_seen_idx = i;
|
|
}
|
|
}
|
|
if (min_freq_seen == UINT_MAX) {
|
|
printf("No freq events between sleep and end!\n");
|
|
return -1;
|
|
}
|
|
|
|
/* is highest CPU freq equal or greater than highest reported in
|
|
* scaling_max_freq? */
|
|
sprintf(scaling_freq_file,
|
|
"/sys/devices/system/cpu/cpu%d/cpufreq/scaling_max_freq",
|
|
test_cpu);
|
|
SAFE_FILE_SCANF(scaling_freq_file, "%d", &scaling_max_freq);
|
|
if (max_freq_seen < scaling_max_freq) {
|
|
printf("CPU%d did not reach scaling_max_freq!\n",
|
|
test_cpu);
|
|
return -1;
|
|
} else {
|
|
printf("CPU%d reached %d MHz during test "
|
|
"(scaling_max_freq %d MHz).\n", test_cpu,
|
|
max_freq_seen / 1000, scaling_max_freq / 1000);
|
|
}
|
|
|
|
/* is lowest CPU freq equal or less than scaling_min_freq? */
|
|
sprintf(scaling_freq_file,
|
|
"/sys/devices/system/cpu/cpu%d/cpufreq/scaling_min_freq",
|
|
test_cpu);
|
|
SAFE_FILE_SCANF(scaling_freq_file, "%d", &scaling_min_freq);
|
|
if (min_freq_seen > scaling_min_freq) {
|
|
printf("CPU%d did not reach scaling_min_freq!\n",
|
|
test_cpu);
|
|
return -1;
|
|
} else {
|
|
printf("CPU%d reached %d MHz after test "
|
|
"(scaling_min_freq %d Mhz).\n",
|
|
test_cpu, min_freq_seen / 1000,
|
|
scaling_min_freq / 1000);
|
|
}
|
|
|
|
/* calculate and check latencies */
|
|
increase_latency_usec = trace[max_freq_seen_idx].ts.sec * USEC_PER_SEC +
|
|
trace[max_freq_seen_idx].ts.usec;
|
|
increase_latency_usec -= trace[start_idx].ts.sec * USEC_PER_SEC +
|
|
trace[start_idx].ts.usec;
|
|
|
|
decrease_latency_usec = trace[min_freq_seen_idx].ts.sec * USEC_PER_SEC +
|
|
trace[min_freq_seen_idx].ts.usec;
|
|
decrease_latency_usec -= trace[sleep_idx].ts.sec * USEC_PER_SEC +
|
|
trace[sleep_idx].ts.usec;
|
|
|
|
printf("Increase latency: %d usec\n", increase_latency_usec);
|
|
printf("Decrease latency: %d usec\n", decrease_latency_usec);
|
|
|
|
return (increase_latency_usec > MAX_FREQ_INCREASE_LATENCY_US ||
|
|
decrease_latency_usec > MAX_FREQ_DECREASE_LATENCY_US);
|
|
}
|
|
|
|
static void run(void)
|
|
{
|
|
pthread_t burn_thread;
|
|
|
|
tst_res(TINFO, "Max acceptable latency to fmax: %d usec\n",
|
|
MAX_FREQ_INCREASE_LATENCY_US);
|
|
tst_res(TINFO, "Max acceptable latency to fmin: %d usec\n",
|
|
MAX_FREQ_DECREASE_LATENCY_US);
|
|
|
|
test_cpu = tst_ncpus() - 1;
|
|
printf("CPU hog will be bound to CPU %d.\n", test_cpu);
|
|
|
|
/* 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_JOIN(burn_thread, NULL);
|
|
|
|
/* disable tracing */
|
|
tracefs_write("tracing_on", "0");
|
|
LOAD_TRACE();
|
|
|
|
if (parse_results())
|
|
tst_res(TFAIL, "Governor did not meet latency targets.\n");
|
|
else
|
|
tst_res(TPASS, "Governor met latency targets.\n");
|
|
}
|
|
|
|
static struct tst_test test = {
|
|
.test_all = run,
|
|
.setup = trace_setup,
|
|
.cleanup = trace_cleanup,
|
|
};
|