]>
Commit | Line | Data |
---|---|---|
76a50749 UD |
1 | #define _GNU_SOURCE |
2 | #include <argp.h> | |
3 | #include <complex.h> | |
4 | #include <errno.h> | |
5 | #include <error.h> | |
6 | #include <fcntl.h> | |
7 | #include <gd.h> | |
8 | #include <inttypes.h> | |
9 | #include <pthread.h> | |
10 | #include <signal.h> | |
11 | #include <stdbool.h> | |
12 | #include <stddef.h> | |
13 | #include <stdio.h> | |
14 | #include <stdlib.h> | |
15 | #include <string.h> | |
16 | #include <time.h> | |
17 | #include <unistd.h> | |
18 | #include <sys/param.h> | |
19 | #include <sys/poll.h> | |
20 | #include <sys/socket.h> | |
21 | #include <sys/un.h> | |
22 | ||
23 | ||
24 | #define size_x 320 | |
25 | #define size_y 240 | |
26 | ||
27 | ||
28 | #define PATH "/tmp/s.sockperf" | |
29 | ||
30 | ||
31 | struct thread_param | |
32 | { | |
33 | unsigned int from; | |
34 | unsigned int to; | |
35 | unsigned int nserv; | |
36 | }; | |
37 | ||
38 | struct coord | |
39 | { | |
40 | unsigned int x; | |
41 | unsigned int y; | |
42 | complex double z; | |
43 | }; | |
44 | ||
45 | ||
46 | /* We use 64bit values for the times. */ | |
47 | typedef unsigned long long int hp_timing_t; | |
48 | ||
49 | ||
50 | static unsigned int nclients = 2; | |
51 | static unsigned int nservers = 2; | |
52 | ||
53 | static bool timing; | |
54 | static int points; | |
55 | ||
56 | ||
57 | static complex double top_left = -0.7 + 0.2i; | |
58 | static complex double bottom_right = -0.5 - 0.0i; | |
59 | ||
60 | ||
61 | static int colors[256]; | |
62 | static gdImagePtr image; | |
63 | static pthread_mutex_t image_lock; | |
64 | ||
65 | static int sock; | |
66 | ||
67 | ||
68 | static void * | |
69 | client (void *arg) | |
70 | { | |
71 | struct thread_param *param = arg; | |
72 | unsigned int cnt; | |
73 | unsigned int nserv = param->nserv; | |
74 | int clisock[nserv]; | |
75 | struct pollfd servpoll[nserv]; | |
76 | struct sockaddr_un servaddr; | |
77 | socklen_t servlen; | |
78 | struct coord c; | |
79 | ||
80 | bool new_coord (void) | |
81 | { | |
82 | if (cnt >= param->to) | |
83 | return false; | |
84 | ||
85 | unsigned int row = cnt / size_x; | |
86 | unsigned int col = cnt % size_x; | |
87 | ||
88 | c.x = col; | |
89 | c.y = row; | |
90 | c.z = (top_left | |
91 | + ((col | |
92 | * (creal (bottom_right) - creal (top_left))) / size_x) | |
93 | + (_Complex_I * (row * (cimag (bottom_right) - cimag (top_left))) | |
94 | / size_y)); | |
95 | ||
96 | ++cnt; | |
97 | ||
98 | return true; | |
99 | } | |
100 | ||
101 | ||
102 | for (cnt = 0; cnt < nserv; ++cnt) | |
103 | { | |
104 | servpoll[cnt].fd = socket (AF_UNIX, SOCK_STREAM, 0); | |
105 | if (clisock < 0) | |
106 | { | |
107 | puts ("cannot create socket in client"); | |
108 | return NULL; | |
109 | } | |
110 | ||
111 | memset (&servaddr, '\0', sizeof (servaddr)); | |
112 | servaddr.sun_family = AF_UNIX; | |
113 | strncpy (servaddr.sun_path, PATH, sizeof (servaddr.sun_path)); | |
114 | servlen = offsetof (struct sockaddr_un, sun_path) + strlen (PATH) + 1; | |
115 | ||
116 | ||
117 | int err; | |
118 | while (1) | |
119 | { | |
120 | err = TEMP_FAILURE_RETRY (connect (servpoll[cnt].fd, &servaddr, | |
121 | servlen)); | |
122 | if (err != -1 || errno != ECONNREFUSED) | |
123 | break; | |
124 | ||
125 | pthread_yield (); | |
126 | } | |
127 | ||
128 | if (err == -1) | |
129 | { | |
130 | printf ("cannot connect: %m (%d)\n", errno); | |
131 | exit (1); | |
132 | } | |
133 | ||
134 | servpoll[cnt].events = POLLOUT; | |
135 | servpoll[cnt].revents = 0; | |
136 | } | |
137 | ||
138 | cnt = param->from; | |
139 | ||
140 | new_coord (); | |
141 | bool z_valid = true; | |
142 | ||
143 | while (1) | |
144 | { | |
145 | int i; | |
146 | int n = poll (servpoll, nserv, -1); | |
147 | if (n == -1) | |
148 | { | |
149 | puts ("poll returned error"); | |
150 | break; | |
151 | } | |
152 | ||
153 | bool cont = false; | |
154 | for (i = 0; i < nserv && n > 0; ++i) | |
155 | if (servpoll[i].revents != 0) | |
156 | { | |
157 | if (servpoll[i].revents == POLLIN) | |
158 | { | |
159 | unsigned int vals[3]; | |
160 | if (TEMP_FAILURE_RETRY (read (servpoll[i].fd, &vals, | |
161 | sizeof (vals))) | |
162 | != sizeof (vals)) | |
163 | { | |
164 | puts ("read error in client"); | |
165 | return NULL; | |
166 | } | |
167 | ||
168 | pthread_mutex_lock (&image_lock); | |
169 | ||
170 | gdImageSetPixel (image, vals[0], vals[1], vals[2]); | |
171 | ++points; | |
172 | ||
173 | pthread_mutex_unlock (&image_lock); | |
174 | ||
175 | servpoll[i].events = POLLOUT; | |
176 | } | |
177 | else | |
178 | { | |
179 | if (servpoll[i].revents != POLLOUT) | |
180 | printf ("revents: %hd != POLLOUT ???\n", | |
181 | servpoll[i].revents); | |
182 | ||
183 | if (z_valid) | |
184 | { | |
185 | if (TEMP_FAILURE_RETRY (write (servpoll[i].fd, &c, | |
186 | sizeof (c))) != sizeof (c)) | |
187 | { | |
188 | puts ("write error in client"); | |
189 | return NULL; | |
190 | } | |
191 | cont = true; | |
192 | servpoll[i].events = POLLIN; | |
193 | ||
194 | z_valid = new_coord (); | |
195 | if (! z_valid) | |
196 | /* No more to do. Clear the event fields. */ | |
197 | for (i = 0; i < nserv; ++i) | |
198 | if (servpoll[i].events == POLLOUT) | |
199 | servpoll[i].events = servpoll[i].revents = 0; | |
200 | } | |
201 | else | |
202 | servpoll[i].events = servpoll[i].revents = 0; | |
203 | } | |
204 | ||
205 | --n; | |
206 | } | |
207 | else if (servpoll[i].events != 0) | |
208 | cont = true; | |
209 | ||
210 | if (! cont && ! z_valid) | |
211 | break; | |
212 | } | |
213 | ||
214 | c.x = 0xffffffff; | |
215 | c.y = 0xffffffff; | |
216 | for (cnt = 0; cnt < nserv; ++cnt) | |
217 | { | |
218 | TEMP_FAILURE_RETRY (write (servpoll[cnt].fd, &c, sizeof (c))); | |
219 | close (servpoll[cnt].fd); | |
220 | } | |
221 | ||
222 | return NULL; | |
223 | } | |
224 | ||
225 | ||
226 | static void * | |
227 | server (void *arg) | |
228 | { | |
229 | struct sockaddr_un cliaddr; | |
230 | socklen_t clilen; | |
231 | int clisock = TEMP_FAILURE_RETRY (accept (sock, &cliaddr, &clilen)); | |
232 | ||
233 | if (clisock == -1) | |
234 | { | |
235 | puts ("accept failed"); | |
236 | return NULL; | |
237 | } | |
238 | ||
239 | while (1) | |
240 | { | |
241 | struct coord c; | |
242 | ||
243 | if (TEMP_FAILURE_RETRY (read (clisock, &c, sizeof (c))) != sizeof (c)) | |
244 | { | |
245 | printf ("server read failed: %m (%d)\n", errno); | |
246 | break; | |
247 | } | |
248 | ||
249 | if (c.x == 0xffffffff && c.y == 0xffffffff) | |
250 | break; | |
251 | ||
252 | unsigned int rnds = 0; | |
253 | complex double z = c.z; | |
254 | while (cabs (z) < 4.0) | |
255 | { | |
256 | z = z * z - 1; | |
257 | if (++rnds == 255) | |
258 | break; | |
259 | } | |
260 | ||
261 | unsigned int vals[3] = { c.x, c.y, rnds }; | |
262 | if (TEMP_FAILURE_RETRY (write (clisock, vals, sizeof (vals))) | |
263 | != sizeof (vals)) | |
264 | { | |
265 | puts ("server write error"); | |
266 | return NULL; | |
267 | } | |
268 | } | |
269 | ||
270 | close (clisock); | |
271 | ||
272 | return NULL; | |
273 | } | |
274 | ||
275 | ||
276 | static const char *outfilename = "test.png"; | |
277 | ||
278 | ||
279 | static const struct argp_option options[] = | |
280 | { | |
281 | { "clients", 'c', "NUMBER", 0, "Number of client threads" }, | |
282 | { "servers", 's', "NUMBER", 0, "Number of server threads per client" }, | |
283 | { "timing", 'T', NULL, 0, | |
284 | "Measure time from startup to the last thread finishing" }, | |
285 | { NULL, 0, NULL, 0, NULL } | |
286 | }; | |
287 | ||
288 | /* Prototype for option handler. */ | |
289 | static error_t parse_opt (int key, char *arg, struct argp_state *state); | |
290 | ||
291 | /* Data structure to communicate with argp functions. */ | |
292 | static struct argp argp = | |
293 | { | |
294 | options, parse_opt | |
295 | }; | |
296 | ||
297 | ||
298 | int | |
299 | main (int argc, char *argv[]) | |
300 | { | |
301 | int cnt; | |
302 | FILE *outfile; | |
303 | struct sockaddr_un servaddr; | |
304 | socklen_t servlen; | |
305 | int remaining; | |
306 | ||
307 | /* Parse and process arguments. */ | |
308 | argp_parse (&argp, argc, argv, 0, &remaining, NULL); | |
309 | ||
310 | ||
311 | pthread_t servth[nservers * nclients]; | |
312 | pthread_t clntth[nclients]; | |
313 | struct thread_param clntparam[nclients]; | |
314 | ||
315 | ||
316 | image = gdImageCreate (size_x, size_y); | |
317 | if (image == NULL) | |
318 | { | |
319 | puts ("gdImageCreate failed"); | |
320 | return 1; | |
321 | } | |
322 | ||
323 | for (cnt = 0; cnt < 255; ++cnt) | |
324 | colors[cnt] = gdImageColorAllocate (image, 256 - cnt, 256 - cnt, | |
325 | 256 - cnt); | |
326 | /* Black. */ | |
327 | colors[cnt] = gdImageColorAllocate (image, 0, 0, 0); | |
328 | ||
329 | ||
330 | sock = socket (AF_UNIX, SOCK_STREAM, 0); | |
331 | if (sock < 0) | |
332 | error (EXIT_FAILURE, errno, "cannot create socket"); | |
333 | ||
334 | memset (&servaddr, '\0', sizeof (servaddr)); | |
335 | servaddr.sun_family = AF_UNIX; | |
336 | strncpy (servaddr.sun_path, PATH, sizeof (servaddr.sun_path)); | |
337 | servlen = offsetof (struct sockaddr_un, sun_path) + strlen (PATH) + 1; | |
338 | ||
339 | if (bind (sock, &servaddr, servlen) == -1) | |
340 | error (EXIT_FAILURE, errno, "bind failed"); | |
341 | ||
342 | listen (sock, SOMAXCONN); | |
343 | ||
344 | pthread_mutex_init (&image_lock, NULL); | |
345 | ||
346 | ||
347 | struct sigaction sa; | |
348 | sa.sa_handler = SIG_IGN; | |
349 | sigemptyset (&sa.sa_mask); | |
350 | sa.sa_flags = 0; | |
351 | ||
352 | clockid_t cl; | |
353 | struct timespec start_time; | |
354 | if (timing) | |
355 | { | |
356 | if (clock_getcpuclockid (0, &cl) != 0 | |
357 | || clock_gettime (cl, &start_time) != 0) | |
358 | timing = false; | |
359 | } | |
360 | ||
361 | /* Start the servers. */ | |
362 | for (cnt = 0; cnt < nservers * nclients; ++cnt) | |
363 | { | |
364 | if (pthread_create (&servth[cnt], NULL, server, NULL) != 0) | |
365 | { | |
366 | puts ("pthread_create for server failed"); | |
367 | exit (1); | |
368 | } | |
369 | } | |
370 | ||
371 | for (cnt = 0; cnt < nclients; ++cnt) | |
372 | { | |
373 | clntparam[cnt].from = cnt * (size_x * size_y) / nclients; | |
374 | clntparam[cnt].to = MIN ((cnt + 1) * (size_x * size_y) / nclients, | |
375 | size_x * size_y); | |
376 | clntparam[cnt].nserv = nservers; | |
377 | ||
378 | if (pthread_create (&clntth[cnt], NULL, client, &clntparam[cnt]) != 0) | |
379 | { | |
380 | puts ("pthread_create for client failed"); | |
381 | exit (1); | |
382 | } | |
383 | } | |
384 | ||
385 | ||
386 | /* Wait for the clients. */ | |
387 | for (cnt = 0; cnt < nclients; ++cnt) | |
388 | if (pthread_join (clntth[cnt], NULL) != 0) | |
389 | { | |
390 | puts ("client pthread_join failed"); | |
391 | exit (1); | |
392 | } | |
393 | ||
394 | /* Wait for the servers. */ | |
395 | for (cnt = 0; cnt < nclients * nservers; ++cnt) | |
396 | if (pthread_join (servth[cnt], NULL) != 0) | |
397 | { | |
398 | puts ("server pthread_join failed"); | |
399 | exit (1); | |
400 | } | |
401 | ||
402 | ||
403 | if (timing) | |
404 | { | |
405 | struct timespec end_time; | |
406 | ||
407 | if (clock_gettime (cl, &end_time) == 0) | |
408 | { | |
409 | end_time.tv_sec -= start_time.tv_sec; | |
410 | end_time.tv_nsec -= start_time.tv_nsec; | |
411 | if (end_time.tv_nsec < 0) | |
412 | { | |
413 | end_time.tv_nsec += 1000000000; | |
414 | --end_time.tv_sec; | |
415 | } | |
416 | ||
417 | printf ("\nRuntime: %lu.%09lu seconds\n%d points computed\n", | |
418 | (unsigned long int) end_time.tv_sec, | |
419 | (unsigned long int) end_time.tv_nsec, | |
420 | points); | |
421 | } | |
422 | } | |
423 | ||
424 | ||
425 | outfile = fopen (outfilename, "w"); | |
426 | if (outfile == NULL) | |
427 | error (EXIT_FAILURE, errno, "cannot open output file '%s'", outfilename); | |
428 | ||
429 | gdImagePng (image, outfile); | |
430 | ||
431 | fclose (outfile); | |
432 | ||
433 | unlink (PATH); | |
434 | ||
435 | return 0; | |
436 | } | |
437 | ||
438 | ||
439 | /* Handle program arguments. */ | |
440 | static error_t | |
441 | parse_opt (int key, char *arg, struct argp_state *state) | |
442 | { | |
443 | switch (key) | |
444 | { | |
445 | case 'c': | |
446 | nclients = strtoul (arg, NULL, 0); | |
447 | break; | |
448 | ||
449 | case 's': | |
450 | nservers = strtoul (arg, NULL, 0); | |
451 | break; | |
452 | ||
453 | case 'T': | |
454 | timing = true; | |
455 | break; | |
456 | ||
457 | default: | |
458 | return ARGP_ERR_UNKNOWN; | |
459 | } | |
460 | ||
461 | return 0; | |
462 | } | |
463 | ||
464 | ||
465 | static hp_timing_t | |
466 | get_clockfreq (void) | |
467 | { | |
468 | /* We read the information from the /proc filesystem. It contains at | |
469 | least one line like | |
470 | cpu MHz : 497.840237 | |
471 | or also | |
472 | cpu MHz : 497.841 | |
473 | We search for this line and convert the number in an integer. */ | |
474 | static hp_timing_t result; | |
475 | int fd; | |
476 | ||
477 | /* If this function was called before, we know the result. */ | |
478 | if (result != 0) | |
479 | return result; | |
480 | ||
481 | fd = open ("/proc/cpuinfo", O_RDONLY); | |
482 | if (__builtin_expect (fd != -1, 1)) | |
483 | { | |
484 | /* XXX AFAIK the /proc filesystem can generate "files" only up | |
485 | to a size of 4096 bytes. */ | |
486 | char buf[4096]; | |
487 | ssize_t n; | |
488 | ||
489 | n = read (fd, buf, sizeof buf); | |
490 | if (__builtin_expect (n, 1) > 0) | |
491 | { | |
492 | char *mhz = memmem (buf, n, "cpu MHz", 7); | |
493 | ||
494 | if (__builtin_expect (mhz != NULL, 1)) | |
495 | { | |
496 | char *endp = buf + n; | |
497 | int seen_decpoint = 0; | |
498 | int ndigits = 0; | |
499 | ||
500 | /* Search for the beginning of the string. */ | |
501 | while (mhz < endp && (*mhz < '0' || *mhz > '9') && *mhz != '\n') | |
502 | ++mhz; | |
503 | ||
504 | while (mhz < endp && *mhz != '\n') | |
505 | { | |
506 | if (*mhz >= '0' && *mhz <= '9') | |
507 | { | |
508 | result *= 10; | |
509 | result += *mhz - '0'; | |
510 | if (seen_decpoint) | |
511 | ++ndigits; | |
512 | } | |
513 | else if (*mhz == '.') | |
514 | seen_decpoint = 1; | |
515 | ||
516 | ++mhz; | |
517 | } | |
518 | ||
519 | /* Compensate for missing digits at the end. */ | |
520 | while (ndigits++ < 6) | |
521 | result *= 10; | |
522 | } | |
523 | } | |
524 | ||
525 | close (fd); | |
526 | } | |
527 | ||
528 | return result; | |
529 | } | |
530 | ||
531 | ||
532 | int | |
533 | clock_getcpuclockid (pid_t pid, clockid_t *clock_id) | |
534 | { | |
535 | /* We don't allow any process ID but our own. */ | |
536 | if (pid != 0 && pid != getpid ()) | |
537 | return EPERM; | |
538 | ||
539 | #ifdef CLOCK_PROCESS_CPUTIME_ID | |
540 | /* Store the number. */ | |
541 | *clock_id = CLOCK_PROCESS_CPUTIME_ID; | |
542 | ||
543 | return 0; | |
544 | #else | |
545 | /* We don't have a timer for that. */ | |
546 | return ENOENT; | |
547 | #endif | |
548 | } | |
549 | ||
550 | ||
551 | #define HP_TIMING_NOW(Var) __asm__ __volatile__ ("rdtsc" : "=A" (Var)) | |
552 | ||
553 | /* Get current value of CLOCK and store it in TP. */ | |
554 | int | |
555 | clock_gettime (clockid_t clock_id, struct timespec *tp) | |
556 | { | |
557 | int retval = -1; | |
558 | ||
559 | switch (clock_id) | |
560 | { | |
561 | case CLOCK_PROCESS_CPUTIME_ID: | |
562 | { | |
563 | ||
564 | static hp_timing_t freq; | |
565 | hp_timing_t tsc; | |
566 | ||
567 | /* Get the current counter. */ | |
568 | HP_TIMING_NOW (tsc); | |
569 | ||
570 | if (freq == 0) | |
571 | { | |
572 | freq = get_clockfreq (); | |
573 | if (freq == 0) | |
574 | return EINVAL; | |
575 | } | |
576 | ||
577 | /* Compute the seconds. */ | |
578 | tp->tv_sec = tsc / freq; | |
579 | ||
580 | /* And the nanoseconds. This computation should be stable until | |
581 | we get machines with about 16GHz frequency. */ | |
582 | tp->tv_nsec = ((tsc % freq) * UINT64_C (1000000000)) / freq; | |
583 | ||
584 | retval = 0; | |
585 | } | |
586 | break; | |
587 | ||
588 | default: | |
589 | errno = EINVAL; | |
590 | break; | |
591 | } | |
592 | ||
593 | return retval; | |
594 | } |