/* * 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"); }