/* Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. 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. 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #ifdef __GNUC__ /* Required for useconds_t and C99 */ #undef _XOPEN_SOURCE #define _XOPEN_SOURCE 600 #endif #include #include #include #include #include "workqueue.h" #include #ifdef HAVE_SRANDOMDEV #define seed_randomizer(X) srandomdev() #else #define seed_randomizer(X) srandom(X) #endif #define TEST_ITERATIONS 1000000 #define DO_SLEEP 1 void * producer_thread(void *arg); void * consumer_thread(void *arg); struct threadinfo { struct workqueue *q; int queue_size; int nconsumers; int producer_batch_size; int producer_median_sleep; int consumer_median_sleep; int iterations; int report_interval; }; struct threadreturn { unsigned int nrecv; }; int run_test(struct threadinfo *); void express_nanosec(Uint64 nsec); int sleep_microseconds(int); int main() { const char * status; struct workqueue *queue= (struct workqueue *) calloc(1, sizeof(struct workqueue)); struct threadinfo test0 = { queue, 32768, 1, 0, 0, 0, 10000000, 1000000 }; struct threadinfo test1 = { queue, 32768, 2, 0, 0, 0, 10000000, 1000000 }; struct threadinfo test2 = { queue, 8192, 2, 10, 800, 200, 100000, 25000 }; struct threadinfo test3 = { queue, 8192, 2, 1, 850, 50, 125000, 25000 }; struct threadinfo test4 = { queue, 8192, 2, 20, 50, 500, 100000, 25000 }; struct threadinfo test5 = { queue, 8192, 2, 1, 50, 0, 150000, 50000 }; struct threadinfo test6 = { queue, 16384, 8, 1, 20, 160, 200000, 50000 }; seed_randomizer(1); /* Note! Note! For TAP, tests are numbered 1 to 7 */ printf("1..7\n"); /* Test 0: no-sleep with 1 consumer */ status = run_test(& test0) ? "ok" : "not ok"; printf("%s 1 No-sleep test with 1 consumer\n", status); /* Test 1: the no-sleep test */ status = run_test(& test1) ? "ok" : "not ok"; printf("%s 2 No-sleep wham!bam! test with %d iterations\n", status, test1.iterations); /* Test 2: fast producer, slow consumer */ status = run_test(& test2) ? "ok" : "not ok"; printf("%s 3 Fast producer / slow consumer test\n", status); /* Test 3: slow producer, fast consumer */ status = run_test(& test3) ? "ok" : "not ok"; printf("%s 4 Slow producer / fast consumer test\n", status); /* Test 4: very slow consumer */ status = run_test(& test4) ? "ok" : "not ok"; printf("%s 5 very slow consumer test\n", status); /* Test 5: whambam! consumer */ status = run_test(& test5) ? "ok" : "not ok"; printf("%s 6 Sluggish producer, whambam! consumer test\n", status); /* Test 6: simulation */ status = run_test(& test6) ? "ok" : "not ok"; printf("%s 7 Memcached simluation test\n", status); } int run_test(struct threadinfo *params) { pthread_t producer_thd_id; pthread_t *consumer_thd_ids; int i; int total_consumed = 0; consumer_thd_ids = calloc(sizeof(pthread_t), params->nconsumers); if(workqueue_init(params->q, params->queue_size, params->nconsumers) ) { printf("Bail out! Workqueue init failed.\n"); exit(1); } pthread_create(&producer_thd_id, NULL, producer_thread, (void *) params); for(i = 0; i < params->nconsumers; i++) pthread_create(& consumer_thd_ids[i], NULL, consumer_thread, (void *) params); pthread_join(producer_thd_id, NULL); for(i = 0; i < params->nconsumers; i++) { void *ret; pthread_join(consumer_thd_ids[i], &ret); total_consumed += ((struct threadreturn *) ret)->nrecv; } workqueue_destroy(params->q); free(consumer_thd_ids); return (total_consumed == params->iterations); } void * producer_thread(void *arg) { long long total_sleep = 0; int slp = 0; size_t i = 1; int n_ints; int sample_interval = 1000; int nsamples = 0, total_depth = 0; int do_sample = random() % sample_interval; struct threadinfo *testinfo = (struct threadinfo *) arg; struct workqueue *queue = testinfo->q; int batchsize = testinfo->producer_batch_size; int sleeptime = testinfo->producer_median_sleep; unsigned int iterations = testinfo->iterations + 1; /* Generate consecutive integers, in random batches. And sleep for random amounts of time between them. Put them on the queue. */ while(i < iterations) { /* we count from e.g. "1" to "100000" */ /* sleep time: non-uniform ("two dice") distribution */ if(sleeptime) slp = ( (random() % sleeptime) + (random() % sleeptime) ); /* how many numbers to generate: */ if(batchsize) n_ints = (random() % batchsize) + 1; else n_ints = 1; while(n_ints-- && i < iterations) { workqueue_add(queue, (void *) i); i++; } if(sleeptime) total_sleep += sleep_microseconds(slp); if(do_sample-- == 0) { nsamples++; total_depth += queue->depth; do_sample = random() % sample_interval; } } printf(" .. Producer thread sent %d. Slept for %f sec. Average depth: %d\n", (int) i-1, (double) total_sleep / 1000000, total_depth / nsamples); workqueue_abort(queue); return 0; } void * consumer_thread(void *arg) { int slp = 0; long long total_sleep = 0; size_t i; size_t last_i = 0; struct threadinfo *testinfo = (struct threadinfo *) arg; struct workqueue *queue = testinfo->q; int sleeptime = testinfo->consumer_median_sleep; struct threadreturn *ret = malloc(sizeof(struct threadreturn)); ret->nrecv = 0; /* fetch items from the queue, 1 at a time, and sleep for some time to simulate processing them */ while(1) { /* sleep time: non-uniform ("two dice") distribution */ if(sleeptime) slp = ( (random() % sleeptime) + (random() % sleeptime) ); i = (size_t) workqueue_consumer_wait(queue); if(i) { ret->nrecv++; if(i == 10) printf(" .. read 10.\n"); if(i % testinfo->report_interval == 0) printf(" .. read %d.\n", (int) i); assert(i > last_i); last_i = i; if(sleeptime) total_sleep += sleep_microseconds(slp); } else { printf(" .. Consumer thread read %d; slept for %f sec. \n", ret->nrecv, (double) total_sleep / 1000000); return (void *) ret; } } } /* sleep for some number of microseconds, less than a full second. returns number of microseconds slept. */ int sleep_microseconds(int usec) { struct timespec time_to_sleep; struct timespec time_slept; time_to_sleep.tv_nsec = (usec * 1000); time_to_sleep.tv_sec = 0; if(nanosleep(&time_to_sleep, &time_slept)) /* interrupted */ return (( time_to_sleep.tv_nsec - time_slept.tv_nsec) / 1000); else return usec; } void express_nanosec(Uint64 ns) { const char *units[4] = { "ns", "us", "ms", "s" }; int p; for(p = 0; ns > 1000 && p < 4; ns /= 1000, p++); printf("%llu %s\n", (unsigned long long) ns, units[p]); }