]>
Commit | Line | Data |
---|---|---|
b75a7d8f A |
1 | // |
2 | //******************************************************************** | |
73c04bcf | 3 | // Copyright (C) 2002-2005, International Business Machines |
b75a7d8f A |
4 | // Corporation and others. All Rights Reserved. |
5 | //******************************************************************** | |
6 | // | |
7 | // File threadtest.cpp | |
8 | // | |
9 | ||
10 | #include <stdlib.h> | |
11 | #include <stdio.h> | |
12 | #include <string.h> | |
13 | ||
14 | #include "unicode/utypes.h" | |
15 | #include "unicode/uclean.h" | |
16 | #include "umutex.h" | |
17 | #include "threadtest.h" | |
18 | ||
19 | ||
20 | //------------------------------------------------------------------------------ | |
21 | // | |
22 | // Factory functions for creating different test types. | |
23 | // | |
24 | //------------------------------------------------------------------------------ | |
25 | extern AbstractThreadTest *createStringTest(); | |
26 | extern AbstractThreadTest *createConvertTest(); | |
27 | ||
28 | ||
29 | ||
30 | //------------------------------------------------------------------------------ | |
31 | // | |
32 | // Windows specific code for starting threads | |
33 | // | |
34 | //------------------------------------------------------------------------------ | |
73c04bcf | 35 | #ifdef U_WINDOWS |
b75a7d8f A |
36 | |
37 | #include "Windows.h" | |
38 | #include "process.h" | |
39 | ||
40 | ||
41 | ||
42 | typedef void (*ThreadFunc)(void *); | |
43 | ||
44 | class ThreadFuncs // This class isolates OS dependent threading | |
45 | { // functions from the rest of ThreadTest program. | |
46 | public: | |
47 | static void Sleep(int millis) {::Sleep(millis);}; | |
48 | static void startThread(ThreadFunc, void *param); | |
49 | static unsigned long getCurrentMillis(); | |
50 | static void yield() {::Sleep(0);}; | |
51 | }; | |
52 | ||
53 | void ThreadFuncs::startThread(ThreadFunc func, void *param) | |
54 | { | |
55 | unsigned long x; | |
56 | x = _beginthread(func, 0x10000, param); | |
57 | if (x == -1) | |
58 | { | |
59 | fprintf(stderr, "Error starting thread. Errno = %d\n", errno); | |
60 | exit(-1); | |
61 | } | |
62 | } | |
63 | ||
64 | unsigned long ThreadFuncs::getCurrentMillis() | |
65 | { | |
66 | return (unsigned long)::GetTickCount(); | |
67 | } | |
68 | ||
69 | ||
70 | ||
71 | ||
72 | // #elif defined (POSIX) | |
73 | #else | |
74 | ||
75 | //------------------------------------------------------------------------------ | |
76 | // | |
77 | // UNIX specific code for starting threads | |
78 | // | |
79 | //------------------------------------------------------------------------------ | |
80 | #include <pthread.h> | |
81 | #include <unistd.h> | |
82 | #include <errno.h> | |
83 | #include <sched.h> | |
84 | #include <sys/timeb.h> | |
85 | ||
86 | ||
87 | extern "C" { | |
88 | ||
89 | ||
90 | typedef void (*ThreadFunc)(void *); | |
91 | typedef void *(*pthreadfunc)(void *); | |
92 | ||
93 | class ThreadFuncs // This class isolates OS dependent threading | |
94 | { // functions from the rest of ThreadTest program. | |
95 | public: | |
96 | static void Sleep(int millis); | |
97 | static void startThread(ThreadFunc, void *param); | |
98 | static unsigned long getCurrentMillis(); | |
99 | static void yield() {sched_yield();}; | |
100 | }; | |
101 | ||
102 | void ThreadFuncs::Sleep(int millis) | |
103 | { | |
104 | int seconds = millis/1000; | |
105 | if (seconds <= 0) seconds = 1; | |
106 | ::sleep(seconds); | |
107 | } | |
108 | ||
109 | ||
110 | void ThreadFuncs::startThread(ThreadFunc func, void *param) | |
111 | { | |
112 | unsigned long x; | |
113 | ||
114 | pthread_t tId; | |
115 | //thread_t tId; | |
116 | #if defined(_HP_UX) && defined(XML_USE_DCE) | |
117 | x = pthread_create( &tId, pthread_attr_default, (pthreadfunc)func, param); | |
118 | #else | |
119 | pthread_attr_t attr; | |
120 | pthread_attr_init(&attr); | |
121 | x = pthread_create( &tId, &attr, (pthreadfunc)func, param); | |
122 | #endif | |
123 | if (x == -1) | |
124 | { | |
125 | fprintf(stderr, "Error starting thread. Errno = %d\n", errno); | |
126 | exit(-1); | |
127 | } | |
128 | } | |
129 | ||
130 | unsigned long ThreadFuncs::getCurrentMillis() { | |
131 | timeb aTime; | |
132 | ftime(&aTime); | |
133 | return (unsigned long)(aTime.time*1000 + aTime.millitm); | |
134 | } | |
135 | } | |
136 | ||
137 | ||
138 | // #else | |
139 | // #error This platform is not supported | |
140 | #endif | |
141 | ||
142 | ||
143 | ||
144 | //------------------------------------------------------------------------------ | |
145 | // | |
146 | // struct runInfo Holds the info extracted from the command line and data | |
147 | // that is shared by all threads. | |
148 | // There is only one of these, and it is static. | |
149 | // During the test, the threads will access this info without | |
150 | // any synchronization. | |
151 | // | |
152 | //------------------------------------------------------------------------------ | |
153 | const int MAXINFILES = 25; | |
154 | struct RunInfo | |
155 | { | |
156 | bool quiet; | |
157 | bool verbose; | |
158 | int numThreads; | |
159 | int totalTime; | |
160 | int checkTime; | |
161 | AbstractThreadTest *fTest; | |
162 | bool stopFlag; | |
163 | bool exitFlag; | |
164 | int32_t runningThreads; | |
165 | }; | |
166 | ||
167 | ||
168 | //------------------------------------------------------------------------------ | |
169 | // | |
170 | // struct threadInfo Holds information specific to an individual thread. | |
171 | // One of these is set up for each thread in the test. | |
172 | // The main program monitors the threads by looking | |
173 | // at the status stored in these structs. | |
174 | // | |
175 | //------------------------------------------------------------------------------ | |
176 | struct ThreadInfo | |
177 | { | |
178 | bool fHeartBeat; // Set true by the thread each time it finishes | |
179 | // a test. | |
180 | unsigned int fCycles; // Number of cycles completed. | |
181 | int fThreadNum; // Identifying number for this thread. | |
182 | ThreadInfo() { | |
183 | fHeartBeat = false; | |
184 | fCycles = 0; | |
185 | fThreadNum = -1; | |
186 | } | |
187 | }; | |
188 | ||
189 | ||
190 | // | |
191 | //------------------------------------------------------------------------------ | |
192 | // | |
193 | // Global Data | |
194 | // | |
195 | //------------------------------------------------------------------------------ | |
196 | RunInfo gRunInfo; | |
197 | ThreadInfo *gThreadInfo; | |
198 | UMTX gStopMutex; // Lets main thread suspend test threads. | |
199 | UMTX gInfoMutex; // Synchronize access to data passed between | |
200 | // worker threads and the main thread | |
201 | ||
202 | ||
203 | //---------------------------------------------------------------------- | |
204 | // | |
205 | // parseCommandLine Read through the command line, and save all | |
206 | // of the options in the gRunInfo struct. | |
207 | // | |
208 | // Display the usage message if the command line | |
209 | // is no good. | |
210 | // | |
211 | // Probably ought to be a member function of RunInfo. | |
212 | // | |
213 | //---------------------------------------------------------------------- | |
214 | ||
215 | void parseCommandLine(int argc, char **argv) | |
216 | { | |
217 | gRunInfo.quiet = false; // Set up defaults for run. | |
218 | gRunInfo.verbose = false; | |
219 | gRunInfo.numThreads = 2; | |
220 | gRunInfo.totalTime = 0; | |
221 | gRunInfo.checkTime = 10; | |
222 | ||
223 | try // Use exceptions for command line syntax errors. | |
224 | { | |
225 | int argnum = 1; | |
226 | while (argnum < argc) | |
227 | { | |
228 | if (strcmp(argv[argnum], "-quiet") == 0) | |
229 | gRunInfo.quiet = true; | |
230 | else if (strcmp(argv[argnum], "-verbose") == 0) | |
231 | gRunInfo.verbose = true; | |
232 | else if (strcmp(argv[argnum], "--help") == 0 || | |
233 | (strcmp(argv[argnum], "?") == 0)) {throw 1; } | |
234 | ||
235 | else if (strcmp(argv[argnum], "-threads") == 0) | |
236 | { | |
237 | ++argnum; | |
238 | if (argnum >= argc) | |
239 | throw 1; | |
240 | gRunInfo.numThreads = atoi(argv[argnum]); | |
241 | if (gRunInfo.numThreads < 0) | |
242 | throw 1; | |
243 | } | |
244 | else if (strcmp(argv[argnum], "-time") == 0) | |
245 | { | |
246 | ++argnum; | |
247 | if (argnum >= argc) | |
248 | throw 1; | |
249 | gRunInfo.totalTime = atoi(argv[argnum]); | |
250 | if (gRunInfo.totalTime < 1) | |
251 | throw 1; | |
252 | } | |
253 | else if (strcmp(argv[argnum], "-ctime") == 0) | |
254 | { | |
255 | ++argnum; | |
256 | if (argnum >= argc) | |
257 | throw 1; | |
258 | gRunInfo.checkTime = atoi(argv[argnum]); | |
259 | if (gRunInfo.checkTime < 1) | |
260 | throw 1; | |
261 | } | |
262 | else if (strcmp(argv[argnum], "string") == 0) | |
263 | { | |
264 | gRunInfo.fTest = createStringTest(); | |
265 | } | |
266 | else if (strcmp(argv[argnum], "convert") == 0) | |
267 | { | |
268 | gRunInfo.fTest = createConvertTest(); | |
269 | } | |
270 | else | |
271 | { | |
272 | fprintf(stderr, "Unrecognized command line option. Scanning \"%s\"\n", | |
273 | argv[argnum]); | |
274 | throw 1; | |
275 | } | |
276 | argnum++; | |
277 | } | |
278 | // We've reached the end of the command line parameters. | |
279 | // Fail if no test name was specified. | |
280 | if (gRunInfo.fTest == NULL) { | |
281 | fprintf(stderr, "No test specified.\n"); | |
282 | throw 1; | |
283 | } | |
284 | ||
285 | } | |
286 | catch (int) | |
287 | { | |
288 | fprintf(stderr, "usage: threadtest [-threads nnn] [-time nnn] [-quiet] [-verbose] test-name\n" | |
289 | " -quiet Suppress periodic status display. \n" | |
290 | " -verbose Display extra messages. \n" | |
291 | " -threads nnn Number of threads. Default is 2. \n" | |
292 | " -time nnn Total time to run, in seconds. Default is forever.\n" | |
293 | " -ctime nnn Time between extra consistency checks, in seconds. Default 10\n" | |
294 | " testname string | convert\n" | |
295 | ); | |
296 | exit(1); | |
297 | } | |
298 | } | |
299 | ||
300 | ||
301 | ||
302 | ||
303 | ||
304 | //---------------------------------------------------------------------- | |
305 | // | |
306 | // threadMain The main function for each of the swarm of test threads. | |
307 | // Run in a loop, executing the runOnce() test function each time. | |
308 | // | |
309 | // | |
310 | //---------------------------------------------------------------------- | |
311 | ||
312 | extern "C" { | |
313 | ||
314 | void threadMain (void *param) | |
315 | { | |
316 | ThreadInfo *thInfo = (ThreadInfo *)param; | |
317 | ||
318 | if (gRunInfo.verbose) | |
319 | printf("Thread #%d: starting\n", thInfo->fThreadNum); | |
320 | umtx_atomic_inc(&gRunInfo.runningThreads); | |
321 | ||
322 | // | |
323 | // | |
324 | while (true) | |
325 | { | |
326 | if (gRunInfo.verbose ) | |
327 | printf("Thread #%d: starting loop\n", thInfo->fThreadNum); | |
328 | ||
329 | // | |
330 | // If the main thread is asking us to wait, do so by locking gStopMutex | |
331 | // which will block us, since the main thread will be holding it already. | |
332 | // | |
333 | umtx_lock(&gInfoMutex); | |
334 | UBool stop = gRunInfo.stopFlag; // Need mutex for processors with flakey memory models. | |
335 | umtx_unlock(&gInfoMutex); | |
336 | ||
337 | if (stop) { | |
338 | if (gRunInfo.verbose) { | |
339 | fprintf(stderr, "Thread #%d: suspending\n", thInfo->fThreadNum); | |
340 | } | |
341 | umtx_atomic_dec(&gRunInfo.runningThreads); | |
342 | while (gRunInfo.stopFlag) { | |
343 | umtx_lock(&gStopMutex); | |
344 | umtx_unlock(&gStopMutex); | |
345 | } | |
346 | umtx_atomic_inc(&gRunInfo.runningThreads); | |
347 | if (gRunInfo.verbose) { | |
348 | fprintf(stderr, "Thread #%d: restarting\n", thInfo->fThreadNum); | |
349 | } | |
350 | } | |
351 | ||
352 | // | |
353 | // The real work of the test happens here. | |
354 | // | |
355 | gRunInfo.fTest->runOnce(); | |
356 | ||
357 | umtx_lock(&gInfoMutex); | |
358 | thInfo->fHeartBeat = true; | |
359 | thInfo->fCycles++; | |
360 | UBool exitNow = gRunInfo.exitFlag; | |
361 | umtx_unlock(&gInfoMutex); | |
362 | ||
363 | // | |
364 | // If main thread says it's time to exit, break out of the loop. | |
365 | // | |
366 | if (exitNow) { | |
367 | break; | |
368 | } | |
369 | } | |
370 | ||
371 | umtx_atomic_dec(&gRunInfo.runningThreads); | |
372 | ||
373 | // Returning will kill the thread. | |
374 | return; | |
375 | } | |
376 | ||
377 | } | |
378 | ||
379 | ||
380 | ||
381 | ||
382 | //---------------------------------------------------------------------- | |
383 | // | |
384 | // main | |
385 | // | |
386 | //---------------------------------------------------------------------- | |
387 | ||
388 | int main (int argc, char **argv) | |
389 | { | |
390 | // | |
391 | // Parse the command line options, and create the specified kind of test. | |
392 | // | |
393 | parseCommandLine(argc, argv); | |
394 | ||
395 | ||
396 | // | |
397 | // Fire off the requested number of parallel threads | |
398 | // | |
399 | ||
400 | if (gRunInfo.numThreads == 0) | |
401 | exit(0); | |
402 | ||
403 | gRunInfo.exitFlag = FALSE; | |
404 | gRunInfo.stopFlag = TRUE; // Will cause the new threads to block | |
405 | umtx_lock(&gStopMutex); | |
406 | ||
407 | gThreadInfo = new ThreadInfo[gRunInfo.numThreads]; | |
408 | int threadNum; | |
409 | for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) | |
410 | { | |
411 | gThreadInfo[threadNum].fThreadNum = threadNum; | |
412 | ThreadFuncs::startThread(threadMain, &gThreadInfo[threadNum]); | |
413 | } | |
414 | ||
415 | ||
416 | unsigned long startTime = ThreadFuncs::getCurrentMillis(); | |
417 | int elapsedSeconds = 0; | |
418 | int timeSinceCheck = 0; | |
419 | ||
420 | // | |
421 | // Unblock the threads. | |
422 | // | |
423 | gRunInfo.stopFlag = FALSE; // Unblocks the worker threads. | |
424 | umtx_unlock(&gStopMutex); | |
425 | ||
426 | // | |
427 | // Loop, watching the heartbeat of the worker threads. | |
428 | // Each second, | |
429 | // display "+" if all threads have completed at least one loop | |
430 | // display "." if some thread hasn't since previous "+" | |
431 | // Each "ctime" seconds, | |
432 | // Stop all the worker threads at the top of their loop, then | |
433 | // call the test's check function. | |
434 | // | |
435 | while (gRunInfo.totalTime == 0 || gRunInfo.totalTime > elapsedSeconds) | |
436 | { | |
437 | ThreadFuncs::Sleep(1000); // We sleep while threads do their work ... | |
438 | ||
439 | if (gRunInfo.quiet == false && gRunInfo.verbose == false) | |
440 | { | |
441 | char c = '+'; | |
442 | int threadNum; | |
443 | umtx_lock(&gInfoMutex); | |
444 | for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) | |
445 | { | |
446 | if (gThreadInfo[threadNum].fHeartBeat == false) | |
447 | { | |
448 | c = '.'; | |
449 | break; | |
450 | }; | |
451 | } | |
452 | umtx_unlock(&gInfoMutex); | |
453 | fputc(c, stdout); | |
454 | fflush(stdout); | |
455 | if (c == '+') | |
456 | for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) | |
457 | gThreadInfo[threadNum].fHeartBeat = false; | |
458 | } | |
459 | ||
460 | // | |
461 | // Update running times. | |
462 | // | |
463 | timeSinceCheck -= elapsedSeconds; | |
464 | elapsedSeconds = (ThreadFuncs::getCurrentMillis() - startTime) / 1000; | |
465 | timeSinceCheck += elapsedSeconds; | |
466 | ||
467 | // | |
468 | // Call back to the test to let it check its internal validity | |
469 | // | |
470 | if (timeSinceCheck >= gRunInfo.checkTime) { | |
471 | if (gRunInfo.verbose) { | |
472 | fprintf(stderr, "Main: suspending all threads\n"); | |
473 | } | |
474 | umtx_lock(&gStopMutex); // Block the worker threads at the top of their loop | |
475 | gRunInfo.stopFlag = TRUE; | |
476 | for (;;) { | |
477 | umtx_lock(&gInfoMutex); | |
478 | UBool done = gRunInfo.runningThreads == 0; | |
479 | umtx_unlock(&gInfoMutex); | |
480 | if (done) { break;} | |
481 | ThreadFuncs::yield(); | |
482 | } | |
483 | ||
484 | ||
485 | ||
486 | gRunInfo.fTest->check(); | |
487 | if (gRunInfo.quiet == false && gRunInfo.verbose == false) { | |
488 | fputc('C', stdout); | |
489 | } | |
490 | ||
491 | if (gRunInfo.verbose) { | |
492 | fprintf(stderr, "Main: starting all threads.\n"); | |
493 | } | |
494 | gRunInfo.stopFlag = FALSE; // Unblock the worker threads. | |
495 | umtx_unlock(&gStopMutex); | |
496 | timeSinceCheck = 0; | |
497 | } | |
498 | }; | |
499 | ||
500 | // | |
501 | // Time's up, we are done. (We only get here if this was a timed run) | |
502 | // Tell the threads to exit. | |
503 | // | |
504 | gRunInfo.exitFlag = true; | |
505 | for (;;) { | |
506 | umtx_lock(&gInfoMutex); | |
507 | UBool done = gRunInfo.runningThreads == 0; | |
508 | umtx_unlock(&gInfoMutex); | |
509 | if (done) { break;} | |
510 | ThreadFuncs::yield(); | |
511 | } | |
512 | ||
513 | // | |
514 | // Tally up the total number of cycles completed by each of the threads. | |
515 | // | |
516 | double totalCyclesCompleted = 0; | |
517 | for (threadNum=0; threadNum < gRunInfo.numThreads; threadNum++) { | |
518 | totalCyclesCompleted += gThreadInfo[threadNum].fCycles; | |
519 | } | |
520 | ||
521 | double cyclesPerMinute = totalCyclesCompleted / (double(gRunInfo.totalTime) / double(60)); | |
522 | printf("\n%8.1f cycles per minute.", cyclesPerMinute); | |
523 | ||
524 | // | |
525 | // Memory should be clean coming out | |
526 | // | |
527 | delete gRunInfo.fTest; | |
528 | delete [] gThreadInfo; | |
529 | umtx_destroy(&gInfoMutex); | |
530 | umtx_destroy(&gStopMutex); | |
531 | u_cleanup(); | |
532 | ||
533 | return 0; | |
534 | } | |
535 | ||
536 |