/*
* Copyright (c) 2014 Remy Noel.
* Copyright (c) 2014 Richard Braun.
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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, see .
*
*
* This is a stress test for perfmon thread monitoring.
* The goal is to pass through a maximum of code paths of the perfmon module.
* We therefore try to get a high migration rate by schedueling at least as
* many threads as CPUs.
* Also, we stop and restart some threads along the way in order to check wether
* stopped threads are propery handled.
*
* TODO: replace thread selection with a proper pseudo-random function once we
* get one.
*/
#include
#include
#include
#include
#include
#include
#include
#include
enum test_thread_state {
TEST_LAUNCHED = 0,
TEST_RUNNING,
TEST_STOPPING,
TEST_STOPPED,
};
struct test_thread {
struct thread *thread;
struct perfmon_group *group;
struct perfmon_event *event;
bool monitored;
enum test_thread_state state;
unsigned long long count;
};
struct threads_stats {
size_t num_group_start;
size_t num_group_started;
size_t num_thread_start;
size_t num_thread_started;
};
static struct test_thread **test_threads;
static struct thread *test_control;
#define TEST_WAIT_INSTRUCT_COUNT 1000UL
#define TEST_NUM_LOOP_STATUS_PRINT 200000
static void
test_wait(void)
{
volatile unsigned long i;
/* TODO: Do something a a bit more clever once timers are here */
for (i = 0; i < TEST_WAIT_INSTRUCT_COUNT; i++);
}
static void
test_thread_run(void *arg)
{
struct test_thread *thread = arg;
unsigned long num_loops;
assert(thread->state == TEST_LAUNCHED);
num_loops = 0;
thread->state = TEST_RUNNING;
for (;;) {
barrier();
if (thread->state == TEST_STOPPING) {
break;
}
/* Invividual threads waits twice as much as control one in order to
* induce some asynchronism between control and treads.
*/
test_wait();
test_wait();
num_loops++;
}
thread->state = TEST_STOPPED;
}
static void
test_thread_toggle_monitor(struct test_thread *thread,
struct threads_stats *stats)
{
int error;
struct perfmon_group *group;
group = thread->group;
if (!thread->monitored) {
error = perfmon_group_start(group);
error_check(error, "perfmon_group_start");
thread->monitored = true;
stats->num_group_start++;
stats->num_group_started++;
} else {
perfmon_group_update(group);
thread->count = perfmon_event_read(thread->event);
error = perfmon_group_stop(group);
error_check(error, "perfmon_group_stop");
thread->monitored = false;
stats->num_group_started--;
}
}
static int
test_thread_create_monitored_thread(struct thread **thread, size_t index,
void *arg)
{
struct thread_attr attr;
char name[THREAD_NAME_SIZE];
snprintf(name, sizeof(name), THREAD_KERNEL_PREFIX
"test_monitored_thread:%zu", index);
thread_attr_init(&attr, name);
thread_attr_set_detached(&attr);
return thread_create(thread, &attr, test_thread_run, arg);
}
static void
test_thread_toggle_state(size_t index,
struct threads_stats *stats)
{
int error;
struct perfmon_group *group;
struct test_thread *thread;
thread = test_threads[index];
group = thread->group;
switch (thread->state) {
case TEST_RUNNING:
thread->state = TEST_STOPPING;
stats->num_thread_started--;
break;
case TEST_STOPPED:
/* restart thread and attach it to the group of the previous thread.
*/
if (thread->monitored) {
test_thread_toggle_monitor(thread, stats);
}
error = perfmon_group_detach(group);
error_check(error, "perfmon_group_detach");
thread->state = TEST_LAUNCHED;
error = test_thread_create_monitored_thread(&thread->thread, index,
thread);
error_check(error, "thread_recreate monitored");
error = perfmon_group_attach(group, thread->thread);
error_check(error, "perfmon_group_attach");
stats->num_thread_start++;
stats->num_thread_started++;
break;
default:
/* Do nothing if the thread is not in a stable state */
break;
}
}
static void
test_x15_test_control_run(void *arg)
{
size_t selected_thread;
size_t stopped_thread;
struct test_thread *thread;
size_t nr_threads;
size_t loop_since_status;
struct threads_stats stats;
(void)arg;
nr_threads = MAX(cpu_count() - 1, 1);
selected_thread = 0;
stopped_thread = 0;
loop_since_status = 0;
stats.num_group_start = 0;
stats.num_group_started = 0;
stats.num_thread_start = nr_threads;
stats.num_thread_started = nr_threads;
printf("monitoring %zu threads\n", nr_threads);
for (;;) {
/* Dummy `random` thread selection. */
selected_thread = (selected_thread + 7) % nr_threads;
thread = test_threads[selected_thread];
test_thread_toggle_monitor(thread, &stats);
/* only half of the threads may be stopped / restarted */
stopped_thread = (stopped_thread + 11) % ((nr_threads + 1) / 2);
test_thread_toggle_state(stopped_thread, &stats);
test_wait();
if (!(++loop_since_status % TEST_NUM_LOOP_STATUS_PRINT)) {
printf("===============================\n");
printf("%zu groups started (%zu total)\n", stats.num_group_started,
stats.num_group_start);
printf("%zu threads started (%zu total)\n",
stats.num_thread_started, stats.num_thread_start);
printf("monitor value: ");
for (size_t i = 0; i < nr_threads; i++) {
printf("%zu: %llu, ", i, test_threads[i]->count);
}
printf("\n");
}
}
}
static struct test_thread *
test_thread_create(size_t index)
{
struct test_thread *thread;
int error;
thread = kmem_zalloc(sizeof(*thread));
if (thread == NULL) {
panic("thread allocation failed");
}
error = test_thread_create_monitored_thread(&thread->thread, index, thread);
error_check(error, "thread_create");
error = perfmon_group_create(&thread->group);
error_check(error, "perfmon_group_create");
error = perfmon_event_create(&thread->event, PERFMON_ET_GENERIC,
PERFMON_EV_CYCLE, PERFMON_EF_KERN);
error_check(error, "perfmon_event_create");
perfmon_group_add(thread->group, thread->event);
error = perfmon_group_attach(thread->group, thread->thread);
error_check(error, "perfmon_group_attach");
return thread;
}
void
test_setup(void)
{
struct thread_attr attr;
size_t nr_threads;
size_t i;
int error;
nr_threads = MAX(cpu_count() - 1, 1);
test_threads = kmem_alloc(nr_threads * sizeof(*test_threads));
for (i = 0; i < nr_threads; i++) {
test_threads[i] = test_thread_create(i);
}
thread_attr_init(&attr, "15_test_control_thread");
thread_attr_set_detached(&attr);
error = thread_create(&test_control, &attr, test_x15_test_control_run,
NULL);
error_check(error, "thread_create control");
}