diff options
| author | Doug Ledford <dledford@redhat.com> | 2012-05-31 19:26:37 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-31 20:49:31 -0400 |
| commit | 7820b0715b6fb1378fab41b27fb7aa3950852cb7 (patch) | |
| tree | f5720d49643717253c6c427fab45300ba4818bdf /tools/testing/selftests | |
| parent | 113289cc086f80f28acd06f160a7c6423cdd4191 (diff) | |
tools/selftests: add mq_perf_tests
Add the mq_perf_tests tool I used when creating my mq performance patch.
Also add a local .gitignore to keep the binaries from showing up in git
status output.
[akpm@linux-foundation.org: checkpatch fixes]
Signed-off-by: Doug Ledford <dledford@redhat.com>
Cc: Stephen Rothwell <sfr@canb.auug.org.au>
Cc: Manfred Spraul <manfred@colorfullife.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Acked-by: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'tools/testing/selftests')
| -rw-r--r-- | tools/testing/selftests/mqueue/.gitignore | 2 | ||||
| -rw-r--r-- | tools/testing/selftests/mqueue/Makefile | 4 | ||||
| -rw-r--r-- | tools/testing/selftests/mqueue/mq_perf_tests.c | 741 |
3 files changed, 746 insertions, 1 deletions
diff --git a/tools/testing/selftests/mqueue/.gitignore b/tools/testing/selftests/mqueue/.gitignore new file mode 100644 index 000000000000..d8d42377205a --- /dev/null +++ b/tools/testing/selftests/mqueue/.gitignore | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | mq_open_tests | ||
| 2 | mq_perf_tests | ||
diff --git a/tools/testing/selftests/mqueue/Makefile b/tools/testing/selftests/mqueue/Makefile index bd74142a173f..54c0aad2b47c 100644 --- a/tools/testing/selftests/mqueue/Makefile +++ b/tools/testing/selftests/mqueue/Makefile | |||
| @@ -1,8 +1,10 @@ | |||
| 1 | all: | 1 | all: |
| 2 | gcc -O2 -lrt mq_open_tests.c -o mq_open_tests | 2 | gcc -O2 -lrt mq_open_tests.c -o mq_open_tests |
| 3 | gcc -O2 -lrt -lpthread -lpopt -o mq_perf_tests mq_perf_tests.c | ||
| 3 | 4 | ||
| 4 | run_tests: | 5 | run_tests: |
| 5 | ./mq_open_tests /test1 | 6 | ./mq_open_tests /test1 |
| 7 | ./mq_perf_tests | ||
| 6 | 8 | ||
| 7 | clean: | 9 | clean: |
| 8 | rm -f mq_open_tests | 10 | rm -f mq_open_tests mq_perf_tests |
diff --git a/tools/testing/selftests/mqueue/mq_perf_tests.c b/tools/testing/selftests/mqueue/mq_perf_tests.c new file mode 100644 index 000000000000..2fadd4b97045 --- /dev/null +++ b/tools/testing/selftests/mqueue/mq_perf_tests.c | |||
| @@ -0,0 +1,741 @@ | |||
| 1 | /* | ||
| 2 | * This application is Copyright 2012 Red Hat, Inc. | ||
| 3 | * Doug Ledford <dledford@redhat.com> | ||
| 4 | * | ||
| 5 | * mq_perf_tests is free software: you can redistribute it and/or modify | ||
| 6 | * it under the terms of the GNU General Public License as published by | ||
| 7 | * the Free Software Foundation, version 3. | ||
| 8 | * | ||
| 9 | * mq_perf_tests is distributed in the hope that it will be useful, | ||
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 12 | * GNU General Public License for more details. | ||
| 13 | * | ||
| 14 | * For the full text of the license, see <http://www.gnu.org/licenses/>. | ||
| 15 | * | ||
| 16 | * mq_perf_tests.c | ||
| 17 | * Tests various types of message queue workloads, concentrating on those | ||
| 18 | * situations that invole large message sizes, large message queue depths, | ||
| 19 | * or both, and reports back useful metrics about kernel message queue | ||
| 20 | * performance. | ||
| 21 | * | ||
| 22 | */ | ||
| 23 | #define _GNU_SOURCE | ||
| 24 | #include <stdio.h> | ||
| 25 | #include <stdlib.h> | ||
| 26 | #include <unistd.h> | ||
| 27 | #include <fcntl.h> | ||
| 28 | #include <string.h> | ||
| 29 | #include <limits.h> | ||
| 30 | #include <errno.h> | ||
| 31 | #include <signal.h> | ||
| 32 | #include <pthread.h> | ||
| 33 | #include <sched.h> | ||
| 34 | #include <sys/types.h> | ||
| 35 | #include <sys/time.h> | ||
| 36 | #include <sys/resource.h> | ||
| 37 | #include <sys/stat.h> | ||
| 38 | #include <mqueue.h> | ||
| 39 | #include <popt.h> | ||
| 40 | |||
| 41 | static char *usage = | ||
| 42 | "Usage:\n" | ||
| 43 | " %s [-c #[,#..] -f] path\n" | ||
| 44 | "\n" | ||
| 45 | " -c # Skip most tests and go straight to a high queue depth test\n" | ||
| 46 | " and then run that test continuously (useful for running at\n" | ||
| 47 | " the same time as some other workload to see how much the\n" | ||
| 48 | " cache thrashing caused by adding messages to a very deep\n" | ||
| 49 | " queue impacts the performance of other programs). The number\n" | ||
| 50 | " indicates which CPU core we should bind the process to during\n" | ||
| 51 | " the run. If you have more than one physical CPU, then you\n" | ||
| 52 | " will need one copy per physical CPU package, and you should\n" | ||
| 53 | " specify the CPU cores to pin ourself to via a comma separated\n" | ||
| 54 | " list of CPU values.\n" | ||
| 55 | " -f Only usable with continuous mode. Pin ourself to the CPUs\n" | ||
| 56 | " as requested, then instead of looping doing a high mq\n" | ||
| 57 | " workload, just busy loop. This will allow us to lock up a\n" | ||
| 58 | " single CPU just like we normally would, but without actually\n" | ||
| 59 | " thrashing the CPU cache. This is to make it easier to get\n" | ||
| 60 | " comparable numbers from some other workload running on the\n" | ||
| 61 | " other CPUs. One set of numbers with # CPUs locked up running\n" | ||
| 62 | " an mq workload, and another set of numbers with those same\n" | ||
| 63 | " CPUs locked away from the test workload, but not doing\n" | ||
| 64 | " anything to trash the cache like the mq workload might.\n" | ||
| 65 | " path Path name of the message queue to create\n" | ||
| 66 | "\n" | ||
| 67 | " Note: this program must be run as root in order to enable all tests\n" | ||
| 68 | "\n"; | ||
| 69 | |||
| 70 | char *MAX_MSGS = "/proc/sys/fs/mqueue/msg_max"; | ||
| 71 | char *MAX_MSGSIZE = "/proc/sys/fs/mqueue/msgsize_max"; | ||
| 72 | |||
| 73 | #define min(a, b) ((a) < (b) ? (a) : (b)) | ||
| 74 | #define MAX_CPUS 64 | ||
| 75 | char *cpu_option_string; | ||
| 76 | int cpus_to_pin[MAX_CPUS]; | ||
| 77 | int num_cpus_to_pin; | ||
| 78 | pthread_t cpu_threads[MAX_CPUS]; | ||
| 79 | pthread_t main_thread; | ||
| 80 | cpu_set_t *cpu_set; | ||
| 81 | int cpu_set_size; | ||
| 82 | int cpus_online; | ||
| 83 | |||
| 84 | #define MSG_SIZE 16 | ||
| 85 | #define TEST1_LOOPS 10000000 | ||
| 86 | #define TEST2_LOOPS 100000 | ||
| 87 | int continuous_mode; | ||
| 88 | int continuous_mode_fake; | ||
| 89 | |||
| 90 | struct rlimit saved_limits, cur_limits; | ||
| 91 | int saved_max_msgs, saved_max_msgsize; | ||
| 92 | int cur_max_msgs, cur_max_msgsize; | ||
| 93 | FILE *max_msgs, *max_msgsize; | ||
| 94 | int cur_nice; | ||
| 95 | char *queue_path = "/mq_perf_tests"; | ||
| 96 | mqd_t queue = -1; | ||
| 97 | struct mq_attr result; | ||
| 98 | int mq_prio_max; | ||
| 99 | |||
| 100 | const struct poptOption options[] = { | ||
| 101 | { | ||
| 102 | .longName = "continuous", | ||
| 103 | .shortName = 'c', | ||
| 104 | .argInfo = POPT_ARG_STRING, | ||
| 105 | .arg = &cpu_option_string, | ||
| 106 | .val = 'c', | ||
| 107 | .descrip = "Run continuous tests at a high queue depth in " | ||
| 108 | "order to test the effects of cache thrashing on " | ||
| 109 | "other tasks on the system. This test is intended " | ||
| 110 | "to be run on one core of each physical CPU while " | ||
| 111 | "some other CPU intensive task is run on all the other " | ||
| 112 | "cores of that same physical CPU and the other task " | ||
| 113 | "is timed. It is assumed that the process of adding " | ||
| 114 | "messages to the message queue in a tight loop will " | ||
| 115 | "impact that other task to some degree. Once the " | ||
| 116 | "tests are performed in this way, you should then " | ||
| 117 | "re-run the tests using fake mode in order to check " | ||
| 118 | "the difference in time required to perform the CPU " | ||
| 119 | "intensive task", | ||
| 120 | .argDescrip = "cpu[,cpu]", | ||
| 121 | }, | ||
| 122 | { | ||
| 123 | .longName = "fake", | ||
| 124 | .shortName = 'f', | ||
| 125 | .argInfo = POPT_ARG_NONE, | ||
| 126 | .arg = &continuous_mode_fake, | ||
| 127 | .val = 0, | ||
| 128 | .descrip = "Tie up the CPUs that we would normally tie up in" | ||
| 129 | "continuous mode, but don't actually do any mq stuff, " | ||
| 130 | "just keep the CPU busy so it can't be used to process " | ||
| 131 | "system level tasks as this would free up resources on " | ||
| 132 | "the other CPU cores and skew the comparison between " | ||
| 133 | "the no-mqueue work and mqueue work tests", | ||
| 134 | .argDescrip = NULL, | ||
| 135 | }, | ||
| 136 | { | ||
| 137 | .longName = "path", | ||
| 138 | .shortName = 'p', | ||
| 139 | .argInfo = POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT, | ||
| 140 | .arg = &queue_path, | ||
| 141 | .val = 'p', | ||
| 142 | .descrip = "The name of the path to use in the mqueue " | ||
| 143 | "filesystem for our tests", | ||
| 144 | .argDescrip = "pathname", | ||
| 145 | }, | ||
| 146 | POPT_AUTOHELP | ||
| 147 | POPT_TABLEEND | ||
| 148 | }; | ||
| 149 | |||
| 150 | static inline void __set(FILE *stream, int value, char *err_msg); | ||
| 151 | void shutdown(int exit_val, char *err_cause, int line_no); | ||
| 152 | void sig_action_SIGUSR1(int signum, siginfo_t *info, void *context); | ||
| 153 | void sig_action(int signum, siginfo_t *info, void *context); | ||
| 154 | static inline int get(FILE *stream); | ||
| 155 | static inline void set(FILE *stream, int value); | ||
| 156 | static inline int try_set(FILE *stream, int value); | ||
| 157 | static inline void getr(int type, struct rlimit *rlim); | ||
| 158 | static inline void setr(int type, struct rlimit *rlim); | ||
| 159 | static inline void open_queue(struct mq_attr *attr); | ||
| 160 | void increase_limits(void); | ||
| 161 | |||
| 162 | static inline void __set(FILE *stream, int value, char *err_msg) | ||
| 163 | { | ||
| 164 | rewind(stream); | ||
| 165 | if (fprintf(stream, "%d", value) < 0) | ||
| 166 | perror(err_msg); | ||
| 167 | } | ||
| 168 | |||
| 169 | |||
| 170 | void shutdown(int exit_val, char *err_cause, int line_no) | ||
| 171 | { | ||
| 172 | static int in_shutdown = 0; | ||
| 173 | int errno_at_shutdown = errno; | ||
| 174 | int i; | ||
| 175 | |||
| 176 | /* In case we get called by multiple threads or from an sighandler */ | ||
| 177 | if (in_shutdown++) | ||
| 178 | return; | ||
| 179 | |||
| 180 | for (i = 0; i < num_cpus_to_pin; i++) | ||
| 181 | if (cpu_threads[i]) { | ||
| 182 | pthread_kill(cpu_threads[i], SIGUSR1); | ||
| 183 | pthread_join(cpu_threads[i], NULL); | ||
| 184 | } | ||
| 185 | |||
| 186 | if (queue != -1) | ||
| 187 | if (mq_close(queue)) | ||
| 188 | perror("mq_close() during shutdown"); | ||
| 189 | if (queue_path) | ||
| 190 | /* | ||
| 191 | * Be silent if this fails, if we cleaned up already it's | ||
| 192 | * expected to fail | ||
| 193 | */ | ||
| 194 | mq_unlink(queue_path); | ||
| 195 | if (saved_max_msgs) | ||
| 196 | __set(max_msgs, saved_max_msgs, | ||
| 197 | "failed to restore saved_max_msgs"); | ||
| 198 | if (saved_max_msgsize) | ||
| 199 | __set(max_msgsize, saved_max_msgsize, | ||
| 200 | "failed to restore saved_max_msgsize"); | ||
| 201 | if (exit_val) | ||
| 202 | error(exit_val, errno_at_shutdown, "%s at %d", | ||
| 203 | err_cause, line_no); | ||
| 204 | exit(0); | ||
| 205 | } | ||
| 206 | |||
| 207 | void sig_action_SIGUSR1(int signum, siginfo_t *info, void *context) | ||
| 208 | { | ||
| 209 | if (pthread_self() != main_thread) | ||
| 210 | pthread_exit(0); | ||
| 211 | else { | ||
| 212 | fprintf(stderr, "Caught signal %d in SIGUSR1 handler, " | ||
| 213 | "exiting\n", signum); | ||
| 214 | shutdown(0, "", 0); | ||
| 215 | fprintf(stderr, "\n\nReturned from shutdown?!?!\n\n"); | ||
| 216 | exit(0); | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | void sig_action(int signum, siginfo_t *info, void *context) | ||
| 221 | { | ||
| 222 | if (pthread_self() != main_thread) | ||
| 223 | pthread_kill(main_thread, signum); | ||
| 224 | else { | ||
| 225 | fprintf(stderr, "Caught signal %d, exiting\n", signum); | ||
| 226 | shutdown(0, "", 0); | ||
| 227 | fprintf(stderr, "\n\nReturned from shutdown?!?!\n\n"); | ||
| 228 | exit(0); | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | static inline int get(FILE *stream) | ||
| 233 | { | ||
| 234 | int value; | ||
| 235 | rewind(stream); | ||
| 236 | if (fscanf(stream, "%d", &value) != 1) | ||
| 237 | shutdown(4, "Error reading /proc entry", __LINE__); | ||
| 238 | return value; | ||
| 239 | } | ||
| 240 | |||
| 241 | static inline void set(FILE *stream, int value) | ||
| 242 | { | ||
| 243 | int new_value; | ||
| 244 | |||
| 245 | rewind(stream); | ||
| 246 | if (fprintf(stream, "%d", value) < 0) | ||
| 247 | return shutdown(5, "Failed writing to /proc file", __LINE__); | ||
| 248 | new_value = get(stream); | ||
| 249 | if (new_value != value) | ||
| 250 | return shutdown(5, "We didn't get what we wrote to /proc back", | ||
| 251 | __LINE__); | ||
| 252 | } | ||
| 253 | |||
| 254 | static inline int try_set(FILE *stream, int value) | ||
| 255 | { | ||
| 256 | int new_value; | ||
| 257 | |||
| 258 | rewind(stream); | ||
| 259 | fprintf(stream, "%d", value); | ||
| 260 | new_value = get(stream); | ||
| 261 | return new_value == value; | ||
| 262 | } | ||
| 263 | |||
| 264 | static inline void getr(int type, struct rlimit *rlim) | ||
| 265 | { | ||
| 266 | if (getrlimit(type, rlim)) | ||
| 267 | shutdown(6, "getrlimit()", __LINE__); | ||
| 268 | } | ||
| 269 | |||
| 270 | static inline void setr(int type, struct rlimit *rlim) | ||
| 271 | { | ||
| 272 | if (setrlimit(type, rlim)) | ||
| 273 | shutdown(7, "setrlimit()", __LINE__); | ||
| 274 | } | ||
| 275 | |||
| 276 | /** | ||
| 277 | * open_queue - open the global queue for testing | ||
| 278 | * @attr - An attr struct specifying the desired queue traits | ||
| 279 | * @result - An attr struct that lists the actual traits the queue has | ||
| 280 | * | ||
| 281 | * This open is not allowed to fail, failure will result in an orderly | ||
| 282 | * shutdown of the program. The global queue_path is used to set what | ||
| 283 | * queue to open, the queue descriptor is saved in the global queue | ||
| 284 | * variable. | ||
| 285 | */ | ||
| 286 | static inline void open_queue(struct mq_attr *attr) | ||
| 287 | { | ||
| 288 | int flags = O_RDWR | O_EXCL | O_CREAT | O_NONBLOCK; | ||
| 289 | int perms = DEFFILEMODE; | ||
| 290 | |||
| 291 | queue = mq_open(queue_path, flags, perms, attr); | ||
| 292 | if (queue == -1) | ||
| 293 | shutdown(1, "mq_open()", __LINE__); | ||
| 294 | if (mq_getattr(queue, &result)) | ||
| 295 | shutdown(1, "mq_getattr()", __LINE__); | ||
| 296 | printf("\n\tQueue %s created:\n", queue_path); | ||
| 297 | printf("\t\tmq_flags:\t\t\t%s\n", result.mq_flags & O_NONBLOCK ? | ||
| 298 | "O_NONBLOCK" : "(null)"); | ||
| 299 | printf("\t\tmq_maxmsg:\t\t\t%d\n", result.mq_maxmsg); | ||
| 300 | printf("\t\tmq_msgsize:\t\t\t%d\n", result.mq_msgsize); | ||
| 301 | printf("\t\tmq_curmsgs:\t\t\t%d\n", result.mq_curmsgs); | ||
| 302 | } | ||
| 303 | |||
| 304 | void *fake_cont_thread(void *arg) | ||
| 305 | { | ||
| 306 | int i; | ||
| 307 | |||
| 308 | for (i = 0; i < num_cpus_to_pin; i++) | ||
| 309 | if (cpu_threads[i] == pthread_self()) | ||
| 310 | break; | ||
| 311 | printf("\tStarted fake continuous mode thread %d on CPU %d\n", i, | ||
| 312 | cpus_to_pin[i]); | ||
| 313 | while (1) | ||
| 314 | ; | ||
| 315 | } | ||
| 316 | |||
| 317 | void *cont_thread(void *arg) | ||
| 318 | { | ||
| 319 | char buff[MSG_SIZE]; | ||
| 320 | int i, priority; | ||
| 321 | |||
| 322 | for (i = 0; i < num_cpus_to_pin; i++) | ||
| 323 | if (cpu_threads[i] == pthread_self()) | ||
| 324 | break; | ||
| 325 | printf("\tStarted continuous mode thread %d on CPU %d\n", i, | ||
| 326 | cpus_to_pin[i]); | ||
| 327 | while (1) { | ||
| 328 | while (mq_send(queue, buff, sizeof(buff), 0) == 0) | ||
| 329 | ; | ||
| 330 | mq_receive(queue, buff, sizeof(buff), &priority); | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | #define drain_queue() \ | ||
| 335 | while (mq_receive(queue, buff, MSG_SIZE, &prio_in) == MSG_SIZE) | ||
| 336 | |||
| 337 | #define do_untimed_send() \ | ||
| 338 | do { \ | ||
| 339 | if (mq_send(queue, buff, MSG_SIZE, prio_out)) \ | ||
| 340 | shutdown(3, "Test send failure", __LINE__); \ | ||
| 341 | } while (0) | ||
| 342 | |||
| 343 | #define do_send_recv() \ | ||
| 344 | do { \ | ||
| 345 | clock_gettime(clock, &start); \ | ||
| 346 | if (mq_send(queue, buff, MSG_SIZE, prio_out)) \ | ||
| 347 | shutdown(3, "Test send failure", __LINE__); \ | ||
| 348 | clock_gettime(clock, &middle); \ | ||
| 349 | if (mq_receive(queue, buff, MSG_SIZE, &prio_in) != MSG_SIZE) \ | ||
| 350 | shutdown(3, "Test receive failure", __LINE__); \ | ||
| 351 | clock_gettime(clock, &end); \ | ||
| 352 | nsec = ((middle.tv_sec - start.tv_sec) * 1000000000) + \ | ||
| 353 | (middle.tv_nsec - start.tv_nsec); \ | ||
| 354 | send_total.tv_nsec += nsec; \ | ||
| 355 | if (send_total.tv_nsec >= 1000000000) { \ | ||
| 356 | send_total.tv_sec++; \ | ||
| 357 | send_total.tv_nsec -= 1000000000; \ | ||
| 358 | } \ | ||
| 359 | nsec = ((end.tv_sec - middle.tv_sec) * 1000000000) + \ | ||
| 360 | (end.tv_nsec - middle.tv_nsec); \ | ||
| 361 | recv_total.tv_nsec += nsec; \ | ||
| 362 | if (recv_total.tv_nsec >= 1000000000) { \ | ||
| 363 | recv_total.tv_sec++; \ | ||
| 364 | recv_total.tv_nsec -= 1000000000; \ | ||
| 365 | } \ | ||
| 366 | } while (0) | ||
| 367 | |||
| 368 | struct test { | ||
| 369 | char *desc; | ||
| 370 | void (*func)(int *); | ||
| 371 | }; | ||
| 372 | |||
| 373 | void const_prio(int *prio) | ||
| 374 | { | ||
| 375 | return; | ||
| 376 | } | ||
| 377 | |||
| 378 | void inc_prio(int *prio) | ||
| 379 | { | ||
| 380 | if (++*prio == mq_prio_max) | ||
| 381 | *prio = 0; | ||
| 382 | } | ||
| 383 | |||
| 384 | void dec_prio(int *prio) | ||
| 385 | { | ||
| 386 | if (--*prio < 0) | ||
| 387 | *prio = mq_prio_max - 1; | ||
| 388 | } | ||
| 389 | |||
| 390 | void random_prio(int *prio) | ||
| 391 | { | ||
| 392 | *prio = random() % mq_prio_max; | ||
| 393 | } | ||
| 394 | |||
| 395 | struct test test2[] = { | ||
| 396 | {"\n\tTest #2a: Time send/recv message, queue full, constant prio\n", | ||
| 397 | const_prio}, | ||
| 398 | {"\n\tTest #2b: Time send/recv message, queue full, increasing prio\n", | ||
| 399 | inc_prio}, | ||
| 400 | {"\n\tTest #2c: Time send/recv message, queue full, decreasing prio\n", | ||
| 401 | dec_prio}, | ||
| 402 | {"\n\tTest #2d: Time send/recv message, queue full, random prio\n", | ||
| 403 | random_prio}, | ||
| 404 | {NULL, NULL} | ||
| 405 | }; | ||
| 406 | |||
| 407 | /** | ||
| 408 | * Tests to perform (all done with MSG_SIZE messages): | ||
| 409 | * | ||
| 410 | * 1) Time to add/remove message with 0 messages on queue | ||
| 411 | * 1a) with constant prio | ||
| 412 | * 2) Time to add/remove message when queue close to capacity: | ||
| 413 | * 2a) with constant prio | ||
| 414 | * 2b) with increasing prio | ||
| 415 | * 2c) with decreasing prio | ||
| 416 | * 2d) with random prio | ||
| 417 | * 3) Test limits of priorities honored (double check _SC_MQ_PRIO_MAX) | ||
| 418 | */ | ||
| 419 | void *perf_test_thread(void *arg) | ||
| 420 | { | ||
| 421 | char buff[MSG_SIZE]; | ||
| 422 | int prio_out, prio_in; | ||
| 423 | int i; | ||
| 424 | clockid_t clock; | ||
| 425 | pthread_t *t; | ||
| 426 | struct timespec res, start, middle, end, send_total, recv_total; | ||
| 427 | unsigned long long nsec; | ||
| 428 | struct test *cur_test; | ||
| 429 | |||
| 430 | t = &cpu_threads[0]; | ||
| 431 | printf("\n\tStarted mqueue performance test thread on CPU %d\n", | ||
| 432 | cpus_to_pin[0]); | ||
| 433 | mq_prio_max = sysconf(_SC_MQ_PRIO_MAX); | ||
| 434 | if (mq_prio_max == -1) | ||
| 435 | shutdown(2, "sysconf(_SC_MQ_PRIO_MAX)", __LINE__); | ||
| 436 | if (pthread_getcpuclockid(cpu_threads[0], &clock) != 0) | ||
| 437 | shutdown(2, "pthread_getcpuclockid", __LINE__); | ||
| 438 | |||
| 439 | if (clock_getres(clock, &res)) | ||
| 440 | shutdown(2, "clock_getres()", __LINE__); | ||
| 441 | |||
| 442 | printf("\t\tMax priorities:\t\t\t%d\n", mq_prio_max); | ||
| 443 | printf("\t\tClock resolution:\t\t%d nsec%s\n", res.tv_nsec, | ||
| 444 | res.tv_nsec > 1 ? "s" : ""); | ||
| 445 | |||
| 446 | |||
| 447 | |||
| 448 | printf("\n\tTest #1: Time send/recv message, queue empty\n"); | ||
| 449 | printf("\t\t(%d iterations)\n", TEST1_LOOPS); | ||
| 450 | prio_out = 0; | ||
| 451 | send_total.tv_sec = 0; | ||
| 452 | send_total.tv_nsec = 0; | ||
| 453 | recv_total.tv_sec = 0; | ||
| 454 | recv_total.tv_nsec = 0; | ||
| 455 | for (i = 0; i < TEST1_LOOPS; i++) | ||
| 456 | do_send_recv(); | ||
| 457 | printf("\t\tSend msg:\t\t\t%d.%ds total time\n", | ||
| 458 | send_total.tv_sec, send_total.tv_nsec); | ||
| 459 | nsec = ((unsigned long long)send_total.tv_sec * 1000000000 + | ||
| 460 | send_total.tv_nsec) / TEST1_LOOPS; | ||
| 461 | printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); | ||
| 462 | printf("\t\tRecv msg:\t\t\t%d.%ds total time\n", | ||
| 463 | recv_total.tv_sec, recv_total.tv_nsec); | ||
| 464 | nsec = ((unsigned long long)recv_total.tv_sec * 1000000000 + | ||
| 465 | recv_total.tv_nsec) / TEST1_LOOPS; | ||
| 466 | printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); | ||
| 467 | |||
| 468 | |||
| 469 | for (cur_test = test2; cur_test->desc != NULL; cur_test++) { | ||
| 470 | printf(cur_test->desc); | ||
| 471 | printf("\t\t(%d iterations)\n", TEST2_LOOPS); | ||
| 472 | prio_out = 0; | ||
| 473 | send_total.tv_sec = 0; | ||
| 474 | send_total.tv_nsec = 0; | ||
| 475 | recv_total.tv_sec = 0; | ||
| 476 | recv_total.tv_nsec = 0; | ||
| 477 | printf("\t\tFilling queue..."); | ||
| 478 | fflush(stdout); | ||
| 479 | clock_gettime(clock, &start); | ||
| 480 | for (i = 0; i < result.mq_maxmsg - 1; i++) { | ||
| 481 | do_untimed_send(); | ||
| 482 | cur_test->func(&prio_out); | ||
| 483 | } | ||
| 484 | clock_gettime(clock, &end); | ||
| 485 | nsec = ((unsigned long long)(end.tv_sec - start.tv_sec) * | ||
| 486 | 1000000000) + (end.tv_nsec - start.tv_nsec); | ||
| 487 | printf("done.\t\t%lld.%llds\n", nsec / 1000000000, | ||
| 488 | nsec % 1000000000); | ||
| 489 | printf("\t\tTesting..."); | ||
| 490 | fflush(stdout); | ||
| 491 | for (i = 0; i < TEST2_LOOPS; i++) { | ||
| 492 | do_send_recv(); | ||
| 493 | cur_test->func(&prio_out); | ||
| 494 | } | ||
| 495 | printf("done.\n"); | ||
| 496 | printf("\t\tSend msg:\t\t\t%d.%ds total time\n", | ||
| 497 | send_total.tv_sec, send_total.tv_nsec); | ||
| 498 | nsec = ((unsigned long long)send_total.tv_sec * 1000000000 + | ||
| 499 | send_total.tv_nsec) / TEST2_LOOPS; | ||
| 500 | printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); | ||
| 501 | printf("\t\tRecv msg:\t\t\t%d.%ds total time\n", | ||
| 502 | recv_total.tv_sec, recv_total.tv_nsec); | ||
| 503 | nsec = ((unsigned long long)recv_total.tv_sec * 1000000000 + | ||
| 504 | recv_total.tv_nsec) / TEST2_LOOPS; | ||
| 505 | printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); | ||
| 506 | printf("\t\tDraining queue..."); | ||
| 507 | fflush(stdout); | ||
| 508 | clock_gettime(clock, &start); | ||
| 509 | drain_queue(); | ||
| 510 | clock_gettime(clock, &end); | ||
| 511 | nsec = ((unsigned long long)(end.tv_sec - start.tv_sec) * | ||
| 512 | 1000000000) + (end.tv_nsec - start.tv_nsec); | ||
| 513 | printf("done.\t\t%lld.%llds\n", nsec / 1000000000, | ||
| 514 | nsec % 1000000000); | ||
| 515 | } | ||
| 516 | return 0; | ||
| 517 | } | ||
| 518 | |||
| 519 | void increase_limits(void) | ||
| 520 | { | ||
| 521 | cur_limits.rlim_cur = RLIM_INFINITY; | ||
| 522 | cur_limits.rlim_max = RLIM_INFINITY; | ||
| 523 | setr(RLIMIT_MSGQUEUE, &cur_limits); | ||
| 524 | while (try_set(max_msgs, cur_max_msgs += 10)) | ||
| 525 | ; | ||
| 526 | cur_max_msgs = get(max_msgs); | ||
| 527 | while (try_set(max_msgsize, cur_max_msgsize += 1024)) | ||
| 528 | ; | ||
| 529 | cur_max_msgsize = get(max_msgsize); | ||
| 530 | if (setpriority(PRIO_PROCESS, 0, -20) != 0) | ||
| 531 | shutdown(2, "setpriority()", __LINE__); | ||
| 532 | cur_nice = -20; | ||
| 533 | } | ||
| 534 | |||
| 535 | int main(int argc, char *argv[]) | ||
| 536 | { | ||
| 537 | struct mq_attr attr; | ||
| 538 | char *option, *next_option; | ||
| 539 | int i, cpu; | ||
| 540 | struct sigaction sa; | ||
| 541 | poptContext popt_context; | ||
| 542 | char rc; | ||
| 543 | void *retval; | ||
| 544 | |||
| 545 | main_thread = pthread_self(); | ||
| 546 | num_cpus_to_pin = 0; | ||
| 547 | |||
| 548 | if (sysconf(_SC_NPROCESSORS_ONLN) == -1) { | ||
| 549 | perror("sysconf(_SC_NPROCESSORS_ONLN)"); | ||
| 550 | exit(1); | ||
| 551 | } | ||
| 552 | cpus_online = min(MAX_CPUS, sysconf(_SC_NPROCESSORS_ONLN)); | ||
| 553 | cpu_set = CPU_ALLOC(cpus_online); | ||
| 554 | if (cpu_set == NULL) { | ||
| 555 | perror("CPU_ALLOC()"); | ||
| 556 | exit(1); | ||
| 557 | } | ||
| 558 | cpu_set_size = CPU_ALLOC_SIZE(cpus_online); | ||
| 559 | CPU_ZERO_S(cpu_set_size, cpu_set); | ||
| 560 | |||
| 561 | popt_context = poptGetContext(NULL, argc, (const char **)argv, | ||
| 562 | options, 0); | ||
| 563 | |||
| 564 | while ((rc = poptGetNextOpt(popt_context)) > 0) { | ||
| 565 | switch (rc) { | ||
| 566 | case 'c': | ||
| 567 | continuous_mode = 1; | ||
| 568 | option = cpu_option_string; | ||
| 569 | do { | ||
| 570 | next_option = strchr(option, ','); | ||
| 571 | if (next_option) | ||
| 572 | *next_option = '\0'; | ||
| 573 | cpu = atoi(option); | ||
| 574 | if (cpu >= cpus_online) | ||
| 575 | fprintf(stderr, "CPU %d exceeds " | ||
| 576 | "cpus online, ignoring.\n", | ||
| 577 | cpu); | ||
| 578 | else | ||
| 579 | cpus_to_pin[num_cpus_to_pin++] = cpu; | ||
| 580 | if (next_option) | ||
| 581 | option = ++next_option; | ||
| 582 | } while (next_option && num_cpus_to_pin < MAX_CPUS); | ||
| 583 | /* Double check that they didn't give us the same CPU | ||
| 584 | * more than once */ | ||
| 585 | for (cpu = 0; cpu < num_cpus_to_pin; cpu++) { | ||
| 586 | if (CPU_ISSET_S(cpus_to_pin[cpu], cpu_set_size, | ||
| 587 | cpu_set)) { | ||
| 588 | fprintf(stderr, "Any given CPU may " | ||
| 589 | "only be given once.\n"); | ||
| 590 | exit(1); | ||
| 591 | } else | ||
| 592 | CPU_SET_S(cpus_to_pin[cpu], | ||
| 593 | cpu_set_size, cpu_set); | ||
| 594 | } | ||
| 595 | break; | ||
| 596 | case 'p': | ||
| 597 | /* | ||
| 598 | * Although we can create a msg queue with a | ||
| 599 | * non-absolute path name, unlink will fail. So, | ||
| 600 | * if the name doesn't start with a /, add one | ||
| 601 | * when we save it. | ||
| 602 | */ | ||
| 603 | option = queue_path; | ||
| 604 | if (*option != '/') { | ||
| 605 | queue_path = malloc(strlen(option) + 2); | ||
| 606 | if (!queue_path) { | ||
| 607 | perror("malloc()"); | ||
| 608 | exit(1); | ||
| 609 | } | ||
| 610 | queue_path[0] = '/'; | ||
| 611 | queue_path[1] = 0; | ||
| 612 | strcat(queue_path, option); | ||
| 613 | free(option); | ||
| 614 | } | ||
| 615 | break; | ||
| 616 | } | ||
| 617 | } | ||
| 618 | |||
| 619 | if (continuous_mode && num_cpus_to_pin == 0) { | ||
| 620 | fprintf(stderr, "Must pass at least one CPU to continuous " | ||
| 621 | "mode.\n"); | ||
| 622 | poptPrintUsage(popt_context, stderr, 0); | ||
| 623 | exit(1); | ||
| 624 | } else if (!continuous_mode) { | ||
| 625 | num_cpus_to_pin = 1; | ||
| 626 | cpus_to_pin[0] = cpus_online - 1; | ||
| 627 | } | ||
| 628 | |||
| 629 | if (getuid() != 0) { | ||
| 630 | fprintf(stderr, "Not running as root, but almost all tests " | ||
| 631 | "require root in order to modify\nsystem settings. " | ||
| 632 | "Exiting.\n"); | ||
| 633 | exit(1); | ||
| 634 | } | ||
| 635 | |||
| 636 | max_msgs = fopen(MAX_MSGS, "r+"); | ||
| 637 | max_msgsize = fopen(MAX_MSGSIZE, "r+"); | ||
| 638 | if (!max_msgs) | ||
| 639 | shutdown(2, "Failed to open msg_max", __LINE__); | ||
| 640 | if (!max_msgsize) | ||
| 641 | shutdown(2, "Failed to open msgsize_max", __LINE__); | ||
| 642 | |||
| 643 | /* Load up the current system values for everything we can */ | ||
| 644 | getr(RLIMIT_MSGQUEUE, &saved_limits); | ||
| 645 | cur_limits = saved_limits; | ||
| 646 | saved_max_msgs = cur_max_msgs = get(max_msgs); | ||
| 647 | saved_max_msgsize = cur_max_msgsize = get(max_msgsize); | ||
| 648 | errno = 0; | ||
| 649 | cur_nice = getpriority(PRIO_PROCESS, 0); | ||
| 650 | if (errno) | ||
| 651 | shutdown(2, "getpriority()", __LINE__); | ||
| 652 | |||
| 653 | /* Tell the user our initial state */ | ||
| 654 | printf("\nInitial system state:\n"); | ||
| 655 | printf("\tUsing queue path:\t\t\t%s\n", queue_path); | ||
| 656 | printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%d\n", saved_limits.rlim_cur); | ||
| 657 | printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%d\n", saved_limits.rlim_max); | ||
| 658 | printf("\tMaximum Message Size:\t\t\t%d\n", saved_max_msgsize); | ||
| 659 | printf("\tMaximum Queue Size:\t\t\t%d\n", saved_max_msgs); | ||
| 660 | printf("\tNice value:\t\t\t\t%d\n", cur_nice); | ||
| 661 | printf("\n"); | ||
| 662 | |||
| 663 | increase_limits(); | ||
| 664 | |||
| 665 | printf("Adjusted system state for testing:\n"); | ||
| 666 | if (cur_limits.rlim_cur == RLIM_INFINITY) { | ||
| 667 | printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t(unlimited)\n"); | ||
| 668 | printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t(unlimited)\n"); | ||
| 669 | } else { | ||
| 670 | printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%d\n", | ||
| 671 | cur_limits.rlim_cur); | ||
| 672 | printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%d\n", | ||
| 673 | cur_limits.rlim_max); | ||
| 674 | } | ||
| 675 | printf("\tMaximum Message Size:\t\t\t%d\n", cur_max_msgsize); | ||
| 676 | printf("\tMaximum Queue Size:\t\t\t%d\n", cur_max_msgs); | ||
| 677 | printf("\tNice value:\t\t\t\t%d\n", cur_nice); | ||
| 678 | printf("\tContinuous mode:\t\t\t(%s)\n", continuous_mode ? | ||
| 679 | (continuous_mode_fake ? "fake mode" : "enabled") : | ||
| 680 | "disabled"); | ||
| 681 | printf("\tCPUs to pin:\t\t\t\t%d", cpus_to_pin[0]); | ||
| 682 | for (cpu = 1; cpu < num_cpus_to_pin; cpu++) | ||
| 683 | printf(",%d", cpus_to_pin[cpu]); | ||
| 684 | printf("\n"); | ||
| 685 | |||
| 686 | sa.sa_sigaction = sig_action_SIGUSR1; | ||
| 687 | sigemptyset(&sa.sa_mask); | ||
| 688 | sigaddset(&sa.sa_mask, SIGHUP); | ||
| 689 | sigaddset(&sa.sa_mask, SIGINT); | ||
| 690 | sigaddset(&sa.sa_mask, SIGQUIT); | ||
| 691 | sigaddset(&sa.sa_mask, SIGTERM); | ||
| 692 | sa.sa_flags = SA_SIGINFO; | ||
| 693 | if (sigaction(SIGUSR1, &sa, NULL) == -1) | ||
| 694 | shutdown(1, "sigaction(SIGUSR1)", __LINE__); | ||
| 695 | sa.sa_sigaction = sig_action; | ||
| 696 | if (sigaction(SIGHUP, &sa, NULL) == -1) | ||
| 697 | shutdown(1, "sigaction(SIGHUP)", __LINE__); | ||
| 698 | if (sigaction(SIGINT, &sa, NULL) == -1) | ||
| 699 | shutdown(1, "sigaction(SIGINT)", __LINE__); | ||
| 700 | if (sigaction(SIGQUIT, &sa, NULL) == -1) | ||
| 701 | shutdown(1, "sigaction(SIGQUIT)", __LINE__); | ||
| 702 | if (sigaction(SIGTERM, &sa, NULL) == -1) | ||
| 703 | shutdown(1, "sigaction(SIGTERM)", __LINE__); | ||
| 704 | |||
| 705 | if (!continuous_mode_fake) { | ||
| 706 | attr.mq_flags = O_NONBLOCK; | ||
| 707 | attr.mq_maxmsg = cur_max_msgs; | ||
| 708 | attr.mq_msgsize = MSG_SIZE; | ||
| 709 | open_queue(&attr); | ||
| 710 | } | ||
| 711 | for (i = 0; i < num_cpus_to_pin; i++) { | ||
| 712 | pthread_attr_t thread_attr; | ||
| 713 | void *thread_func; | ||
| 714 | |||
| 715 | if (continuous_mode_fake) | ||
| 716 | thread_func = &fake_cont_thread; | ||
| 717 | else if (continuous_mode) | ||
| 718 | thread_func = &cont_thread; | ||
| 719 | else | ||
| 720 | thread_func = &perf_test_thread; | ||
| 721 | |||
| 722 | CPU_ZERO_S(cpu_set_size, cpu_set); | ||
| 723 | CPU_SET_S(cpus_to_pin[i], cpu_set_size, cpu_set); | ||
| 724 | pthread_attr_init(&thread_attr); | ||
| 725 | pthread_attr_setaffinity_np(&thread_attr, cpu_set_size, | ||
| 726 | cpu_set); | ||
| 727 | if (pthread_create(&cpu_threads[i], &thread_attr, thread_func, | ||
| 728 | NULL)) | ||
| 729 | shutdown(1, "pthread_create()", __LINE__); | ||
| 730 | pthread_attr_destroy(&thread_attr); | ||
| 731 | } | ||
| 732 | |||
| 733 | if (!continuous_mode) { | ||
| 734 | pthread_join(cpu_threads[0], &retval); | ||
| 735 | shutdown((long)retval, "perf_test_thread()", __LINE__); | ||
| 736 | } else { | ||
| 737 | while (1) | ||
| 738 | sleep(1); | ||
| 739 | } | ||
| 740 | shutdown(0, "", 0); | ||
| 741 | } | ||
