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.
525 lines
12 KiB
525 lines
12 KiB
|
|
/******************************************************************************
|
|
*
|
|
* Copyright (C) 2007-2009 Steven Rostedt <srostedt@redhat.com>
|
|
*
|
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License (not later!)
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* NAME
|
|
* rt-migrate-test.c
|
|
*
|
|
* DESCRIPTION
|
|
* This test makes sure that all the high prio tasks that are in the
|
|
* running state are actually running on a CPU if it can.
|
|
** Steps:
|
|
* - Creates N+1 threads with lower real time priorities.
|
|
* Where N is the number of CPUs in the system.
|
|
* - If the thread is high priority, and if a CPU is available, the
|
|
* thread runs on that CPU.
|
|
* - The thread records the start time and the number of ticks in the run
|
|
* interval.
|
|
* - The output indicates if lower prio task is quicker than higher
|
|
* priority task.
|
|
*
|
|
* USAGE:
|
|
* Use run_auto.sh in the current directory to build and run the test.
|
|
*
|
|
* AUTHOR
|
|
* Steven Rostedt <srostedt@redhat.com>
|
|
*
|
|
* HISTORY
|
|
* 30 July, 2009: Initial version by Steven Rostedt
|
|
* 11 Aug, 2009: Converted the coding style to the one used by the realtime
|
|
* testcases by Kiran Prakash
|
|
*
|
|
*/
|
|
#ifndef _GNU_SOURCE
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <getopt.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <sys/time.h>
|
|
#include <linux/unistd.h>
|
|
#include <sys/syscall.h>
|
|
#include <errno.h>
|
|
#include <sched.h>
|
|
#include <pthread.h>
|
|
#include <librttest.h>
|
|
#include <libstats.h>
|
|
|
|
#define gettid() syscall(__NR_gettid)
|
|
|
|
#define VERSION_STRING "V 0.4LTP"
|
|
|
|
int nr_tasks;
|
|
int lfd;
|
|
|
|
int numcpus;
|
|
static int mark_fd = -1;
|
|
static __thread char buff[BUFSIZ + 1];
|
|
|
|
static void setup_ftrace_marker(void)
|
|
{
|
|
struct stat st;
|
|
char *files[] = {
|
|
"/sys/kernel/debug/tracing/trace_marker",
|
|
"/debug/tracing/trace_marker",
|
|
"/debugfs/tracing/trace_marker",
|
|
};
|
|
int ret;
|
|
int i;
|
|
|
|
for (i = 0; i < (sizeof(files) / sizeof(char *)); i++) {
|
|
ret = stat(files[i], &st);
|
|
if (ret >= 0)
|
|
goto found;
|
|
}
|
|
/* todo, check mounts system */
|
|
return;
|
|
found:
|
|
mark_fd = open(files[i], O_WRONLY);
|
|
}
|
|
|
|
static void ftrace_write(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int n;
|
|
|
|
if (mark_fd < 0)
|
|
return;
|
|
|
|
va_start(ap, fmt);
|
|
n = vsnprintf(buff, BUFSIZ, fmt, ap);
|
|
va_end(ap);
|
|
|
|
/*
|
|
* This doesn't return any valid vs invalid exit codes, so printing out
|
|
* a perror to warn the end-user of an issue is sufficient.
|
|
*/
|
|
if (write(mark_fd, buff, n) < 0) {
|
|
perror("write");
|
|
}
|
|
}
|
|
|
|
#define INTERVAL 100ULL * NS_PER_MS
|
|
#define RUN_INTERVAL 20ULL * NS_PER_MS
|
|
#define NR_RUNS 50
|
|
#define PRIO_START 2
|
|
/* 1 millisec off */
|
|
#define MAX_ERR 1000 * NS_PER_US
|
|
|
|
#define PROGRESS_CHARS 70
|
|
|
|
static unsigned long long interval = INTERVAL;
|
|
static unsigned long long run_interval = RUN_INTERVAL;
|
|
static unsigned long long max_err = MAX_ERR;
|
|
static int nr_runs = NR_RUNS;
|
|
static int prio_start = PRIO_START;
|
|
static int check = 1;
|
|
static int stop;
|
|
|
|
static unsigned long long now;
|
|
|
|
static int done;
|
|
static int loop;
|
|
|
|
static pthread_barrier_t start_barrier;
|
|
static pthread_barrier_t end_barrier;
|
|
stats_container_t *intervals;
|
|
stats_container_t *intervals_length;
|
|
stats_container_t *intervals_loops;
|
|
static long *thread_pids;
|
|
|
|
static void print_progress_bar(int percent)
|
|
{
|
|
int i;
|
|
int p;
|
|
|
|
if (percent > 100)
|
|
percent = 100;
|
|
|
|
/* Use stderr, so we don't capture it */
|
|
putc('\r', stderr);
|
|
putc('|', stderr);
|
|
for (i = 0; i < PROGRESS_CHARS; i++)
|
|
putc(' ', stderr);
|
|
putc('|', stderr);
|
|
putc('\r', stderr);
|
|
putc('|', stderr);
|
|
|
|
p = PROGRESS_CHARS * percent / 100;
|
|
|
|
for (i = 0; i < p; i++)
|
|
putc('-', stderr);
|
|
|
|
fflush(stderr);
|
|
}
|
|
|
|
static void usage()
|
|
{
|
|
rt_help();
|
|
printf("Usage:\n"
|
|
"-a priority Priority of the threads"
|
|
"-r time Run time (ms) to busy loop the threads (20)\n"
|
|
"-t time Sleep time (ms) between intervals (100)\n"
|
|
"-e time Max allowed error (microsecs)\n"
|
|
"-l loops Number of iterations to run (50)\n");
|
|
}
|
|
|
|
/*
|
|
int rt_init(const char *options, int (*parse_arg)(int option, char *value),
|
|
int argc, char *argv[]);
|
|
*/
|
|
static int parse_args(int c, char *v)
|
|
{
|
|
int handled = 1;
|
|
switch (c) {
|
|
case 'a':
|
|
prio_start = atoi(v);
|
|
break;
|
|
case 'r':
|
|
run_interval = atoi(v);
|
|
break;
|
|
case 't':
|
|
interval = atoi(v);
|
|
break;
|
|
case 'l':
|
|
nr_runs = atoi(v);
|
|
break;
|
|
case 'e':
|
|
max_err = atoi(v) * NS_PER_US;
|
|
break;
|
|
case '?':
|
|
case 'h':
|
|
usage();
|
|
handled = 0;
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
static void record_time(int id, unsigned long long time, unsigned long l)
|
|
{
|
|
unsigned long long ltime;
|
|
stats_record_t rec;
|
|
if (loop >= nr_runs)
|
|
return;
|
|
time -= now;
|
|
ltime = rt_gettime() / NS_PER_US;
|
|
ltime -= now;
|
|
rec.x = loop;
|
|
rec.y = time;
|
|
stats_container_append(&intervals[id], rec);
|
|
rec.x = loop;
|
|
rec.y = ltime;
|
|
stats_container_append(&intervals_length[id], rec);
|
|
rec.x = loop;
|
|
rec.y = l;
|
|
stats_container_append(&intervals_loops[id], rec);
|
|
}
|
|
|
|
static void print_results(void)
|
|
{
|
|
int i;
|
|
int t;
|
|
unsigned long long tasks_max[nr_tasks];
|
|
unsigned long long tasks_min[nr_tasks];
|
|
unsigned long long tasks_avg[nr_tasks];
|
|
|
|
memset(tasks_max, 0, sizeof(tasks_max[0]) * nr_tasks);
|
|
memset(tasks_min, 0xff, sizeof(tasks_min[0]) * nr_tasks);
|
|
memset(tasks_avg, 0, sizeof(tasks_avg[0]) * nr_tasks);
|
|
|
|
printf("Iter: ");
|
|
for (t = 0; t < nr_tasks; t++)
|
|
printf("%6d ", t);
|
|
printf("\n");
|
|
|
|
for (t = 0; t < nr_tasks; t++) {
|
|
tasks_max[t] = stats_max(&intervals[t]);
|
|
tasks_min[t] = stats_min(&intervals[t]);
|
|
tasks_avg[t] = stats_avg(&intervals[t]);
|
|
}
|
|
for (i = 0; i < nr_runs; i++) {
|
|
printf("%4d: ", i);
|
|
for (t = 0; t < nr_tasks; t++)
|
|
printf("%6ld ", intervals[t].records[i].y);
|
|
|
|
printf("\n");
|
|
printf(" len: ");
|
|
for (t = 0; t < nr_tasks; t++)
|
|
printf("%6ld ", intervals_length[t].records[i].y);
|
|
|
|
printf("\n");
|
|
printf(" loops: ");
|
|
for (t = 0; t < nr_tasks; t++)
|
|
printf("%6ld ", intervals_loops[t].records[i].y);
|
|
|
|
printf("\n");
|
|
printf("\n");
|
|
}
|
|
|
|
printf("Parent pid: %d\n", getpid());
|
|
|
|
for (t = 0; t < nr_tasks; t++) {
|
|
printf(" Task %d (prio %d) (pid %ld):\n", t, t + prio_start,
|
|
thread_pids[t]);
|
|
printf(" Max: %lld us\n", tasks_max[t]);
|
|
printf(" Min: %lld us\n", tasks_min[t]);
|
|
printf(" Tot: %lld us\n", tasks_avg[t] * nr_runs);
|
|
printf(" Avg: %lld us\n", tasks_avg[t]);
|
|
printf("\n");
|
|
}
|
|
|
|
printf(" Result: %s\n", (check < 0) ? "FAIL" : "PASS");
|
|
}
|
|
|
|
static unsigned long busy_loop(unsigned long long start_time)
|
|
{
|
|
unsigned long long time;
|
|
unsigned long l = 0;
|
|
|
|
do {
|
|
l++;
|
|
time = rt_gettime();
|
|
} while ((time - start_time) < RUN_INTERVAL);
|
|
|
|
return l;
|
|
}
|
|
|
|
void *start_task(void *data)
|
|
{
|
|
struct thread *thr = (struct thread *)data;
|
|
long id = (long)thr->arg;
|
|
thread_pids[id] = gettid();
|
|
unsigned long long start_time;
|
|
int ret;
|
|
int high = 0;
|
|
cpu_set_t cpumask;
|
|
cpu_set_t save_cpumask;
|
|
int cpu = 0;
|
|
unsigned long l;
|
|
long pid;
|
|
|
|
ret = sched_getaffinity(0, sizeof(save_cpumask), &save_cpumask);
|
|
if (ret < 0)
|
|
debug(DBG_ERR, "sched_getaffinity failed: %s\n", strerror(ret));
|
|
|
|
pid = gettid();
|
|
|
|
/* Check if we are the highest prio task */
|
|
if (id == nr_tasks - 1)
|
|
high = 1;
|
|
|
|
while (!done) {
|
|
if (high) {
|
|
/* rotate around the CPUS */
|
|
if (!CPU_ISSET(cpu, &save_cpumask))
|
|
cpu = 0;
|
|
CPU_ZERO(&cpumask);
|
|
CPU_SET(cpu, &cpumask);
|
|
cpu++;
|
|
sched_setaffinity(0, sizeof(cpumask), &cpumask);
|
|
}
|
|
pthread_barrier_wait(&start_barrier);
|
|
start_time = rt_gettime();
|
|
ftrace_write("Thread %d: started %lld diff %lld\n",
|
|
pid, start_time, start_time - now);
|
|
l = busy_loop(start_time);
|
|
record_time(id, start_time / NS_PER_US, l);
|
|
pthread_barrier_wait(&end_barrier);
|
|
}
|
|
|
|
return (void *)pid;
|
|
}
|
|
|
|
static int check_times(int l)
|
|
{
|
|
int i;
|
|
unsigned long long last;
|
|
unsigned long long last_loops;
|
|
unsigned long long last_length;
|
|
|
|
for (i = 0; i < nr_tasks; i++) {
|
|
if (i && last < intervals[i].records[l].y &&
|
|
((intervals[i].records[l].y - last) > max_err)) {
|
|
/*
|
|
* May be a false positive.
|
|
* Make sure that we did more loops
|
|
* our start is before the end
|
|
* and the end should be tested.
|
|
*/
|
|
if (intervals_loops[i].records[l].y < last_loops ||
|
|
intervals[i].records[l].y > last_length ||
|
|
(intervals_length[i].records[l].y > last_length &&
|
|
intervals_length[i].records[l].y - last_length >
|
|
max_err)) {
|
|
check = -1;
|
|
return 1;
|
|
}
|
|
}
|
|
last = intervals[i].records[l].y;
|
|
last_loops = intervals_loops[i].records[l].y;
|
|
last_length = intervals_length[i].records[l].y;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void stop_log(int sig)
|
|
{
|
|
stop = 1;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
pthread_t *threads;
|
|
long i;
|
|
int ret;
|
|
struct timespec intv;
|
|
struct sched_param param;
|
|
|
|
rt_init("a:r:t:e:l:h:", parse_args, argc, argv);
|
|
signal(SIGINT, stop_log);
|
|
|
|
if (argc >= (optind + 1))
|
|
nr_tasks = atoi(argv[optind]);
|
|
else {
|
|
numcpus = sysconf(_SC_NPROCESSORS_ONLN);
|
|
nr_tasks = numcpus + 1;
|
|
}
|
|
|
|
intervals = malloc(sizeof(stats_container_t) * nr_tasks);
|
|
if (!intervals)
|
|
debug(DBG_ERR, "malloc failed\n");
|
|
memset(intervals, 0, sizeof(stats_container_t) * nr_tasks);
|
|
|
|
intervals_length = malloc(sizeof(stats_container_t) * nr_tasks);
|
|
if (!intervals_length)
|
|
debug(DBG_ERR, "malloc failed\n");
|
|
memset(intervals_length, 0, sizeof(stats_container_t) * nr_tasks);
|
|
|
|
if (!intervals_loops)
|
|
debug(DBG_ERR, "malloc failed\n");
|
|
intervals_loops = malloc(sizeof(stats_container_t) * nr_tasks);
|
|
memset(intervals_loops, 0, sizeof(stats_container_t) * nr_tasks);
|
|
|
|
threads = malloc(sizeof(*threads) * nr_tasks);
|
|
if (!threads)
|
|
debug(DBG_ERR, "malloc failed\n");
|
|
memset(threads, 0, sizeof(*threads) * nr_tasks);
|
|
|
|
ret = pthread_barrier_init(&start_barrier, NULL, nr_tasks + 1);
|
|
ret = pthread_barrier_init(&end_barrier, NULL, nr_tasks + 1);
|
|
if (ret < 0)
|
|
debug(DBG_ERR, "pthread_barrier_init failed: %s\n",
|
|
strerror(ret));
|
|
|
|
for (i = 0; i < nr_tasks; i++) {
|
|
stats_container_init(&intervals[i], nr_runs);
|
|
stats_container_init(&intervals_length[i], nr_runs);
|
|
stats_container_init(&intervals_loops[i], nr_runs);
|
|
}
|
|
|
|
thread_pids = malloc(sizeof(long) * nr_tasks);
|
|
if (!thread_pids)
|
|
debug(DBG_ERR, "malloc thread_pids failed\n");
|
|
|
|
for (i = 0; i < nr_tasks; i++) {
|
|
threads[i] = create_fifo_thread(start_task, (void *)i,
|
|
prio_start + i);
|
|
}
|
|
|
|
/*
|
|
* Progress bar uses stderr to let users see it when
|
|
* redirecting output. So we convert stderr to use line
|
|
* buffering so the progress bar doesn't flicker.
|
|
*/
|
|
setlinebuf(stderr);
|
|
|
|
/* up our prio above all tasks */
|
|
memset(¶m, 0, sizeof(param));
|
|
param.sched_priority = nr_tasks + prio_start;
|
|
if (sched_setscheduler(0, SCHED_FIFO, ¶m))
|
|
debug(DBG_WARN, "Warning, can't set priority of"
|
|
"main thread !\n");
|
|
intv.tv_sec = INTERVAL / NS_PER_SEC;
|
|
intv.tv_nsec = INTERVAL % (1 * NS_PER_SEC);
|
|
|
|
print_progress_bar(0);
|
|
|
|
setup_ftrace_marker();
|
|
|
|
for (loop = 0; loop < nr_runs; loop++) {
|
|
unsigned long long end;
|
|
|
|
now = rt_gettime() / NS_PER_US;
|
|
|
|
ftrace_write("Loop %d now=%lld\n", loop, now);
|
|
|
|
pthread_barrier_wait(&start_barrier);
|
|
|
|
ftrace_write("All running!!!\n");
|
|
|
|
rt_nanosleep(intv.tv_nsec);
|
|
print_progress_bar((loop * 100) / nr_runs);
|
|
|
|
end = rt_gettime() / NS_PER_US;
|
|
ftrace_write("Loop %d end now=%lld diff=%lld\n",
|
|
loop, end, end - now);
|
|
ret = pthread_barrier_wait(&end_barrier);
|
|
|
|
if (stop || (check && check_times(loop))) {
|
|
loop++;
|
|
nr_runs = loop;
|
|
break;
|
|
}
|
|
}
|
|
putc('\n', stderr);
|
|
|
|
pthread_barrier_wait(&start_barrier);
|
|
done = 1;
|
|
pthread_barrier_wait(&end_barrier);
|
|
|
|
join_threads();
|
|
print_results();
|
|
|
|
if (stop) {
|
|
/*
|
|
* We use this test in bash while loops
|
|
* So if we hit Ctrl-C then let the while
|
|
* loop know to break.
|
|
*/
|
|
if (check < 0)
|
|
exit(-1);
|
|
else
|
|
exit(1);
|
|
}
|
|
|
|
if (check < 0)
|
|
exit(-1);
|
|
else
|
|
exit(0);
|
|
}
|