#define _GNU_SOURCE #include <argp.h> #include <complex.h> #include <errno.h> #include <error.h> #include <fcntl.h> #include <gd.h> #include <inttypes.h> #include <pthread.h> #include <signal.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #include <sys/param.h> #include <sys/poll.h> #include <sys/socket.h> #include <sys/un.h> #define size_x 320 #define size_y 240 #define PATH "/tmp/s.sockperf" struct thread_param { unsigned int from; unsigned int to; unsigned int nserv; }; struct coord { unsigned int x; unsigned int y; complex double z; }; /* We use 64bit values for the times. */ typedef unsigned long long int hp_timing_t; static unsigned int nclients = 2; static unsigned int nservers = 2; static bool timing; static int points; static complex double top_left = -0.7 + 0.2i; static complex double bottom_right = -0.5 - 0.0i; static int colors[256]; static gdImagePtr image; static pthread_mutex_t image_lock; static int sock; static void * client (void *arg) { struct thread_param *param = arg; unsigned int cnt; unsigned int nserv = param->nserv; int clisock[nserv]; struct pollfd servpoll[nserv]; struct sockaddr_un servaddr; socklen_t servlen; struct coord c; bool new_coord (void) { if (cnt >= param->to) return false; unsigned int row = cnt / size_x; unsigned int col = cnt % size_x; c.x = col; c.y = row; c.z = (top_left + ((col * (creal (bottom_right) - creal (top_left))) / size_x) + (_Complex_I * (row * (cimag (bottom_right) - cimag (top_left))) / size_y)); ++cnt; return true; } for (cnt = 0; cnt < nserv; ++cnt) { servpoll[cnt].fd = socket (AF_UNIX, SOCK_STREAM, 0); if (clisock < 0) { puts ("cannot create socket in client"); return NULL; } memset (&servaddr, '\0', sizeof (servaddr)); servaddr.sun_family = AF_UNIX; strncpy (servaddr.sun_path, PATH, sizeof (servaddr.sun_path)); servlen = offsetof (struct sockaddr_un, sun_path) + strlen (PATH) + 1; int err; while (1) { err = TEMP_FAILURE_RETRY (connect (servpoll[cnt].fd, &servaddr, servlen)); if (err != -1 || errno != ECONNREFUSED) break; pthread_yield (); } if (err == -1) { printf ("cannot connect: %m (%d)\n", errno); exit (1); } servpoll[cnt].events = POLLOUT; servpoll[cnt].revents = 0; } cnt = param->from; new_coord (); bool z_valid = true; while (1) { int i; int n = poll (servpoll, nserv, -1); if (n == -1) { puts ("poll returned error"); break; } bool cont = false; for (i = 0; i < nserv && n > 0; ++i) if (servpoll[i].revents != 0) { if (servpoll[i].revents == POLLIN) { unsigned int vals[3]; if (TEMP_FAILURE_RETRY (read (servpoll[i].fd, &vals, sizeof (vals))) != sizeof (vals)) { puts ("read error in client"); return NULL; } pthread_mutex_lock (&image_lock); gdImageSetPixel (image, vals[0], vals[1], vals[2]); ++points; pthread_mutex_unlock (&image_lock); servpoll[i].events = POLLOUT; } else { if (servpoll[i].revents != POLLOUT) printf ("revents: %hd != POLLOUT ???\n", servpoll[i].revents); if (z_valid) { if (TEMP_FAILURE_RETRY (write (servpoll[i].fd, &c, sizeof (c))) != sizeof (c)) { puts ("write error in client"); return NULL; } cont = true; servpoll[i].events = POLLIN; z_valid = new_coord (); if (! z_valid) /* No more to do. Clear the event fields. */ for (i = 0; i < nserv; ++i) if (servpoll[i].events == POLLOUT) servpoll[i].events = servpoll[i].revents = 0; } else servpoll[i].events = servpoll[i].revents = 0; } --n; } else if (servpoll[i].events != 0) cont = true; if (! cont && ! z_valid) break; } c.x = 0xffffffff; c.y = 0xffffffff; for (cnt = 0; cnt < nserv; ++cnt) { TEMP_FAILURE_RETRY (write (servpoll[cnt].fd, &c, sizeof (c))); close (servpoll[cnt].fd); } return NULL; } static void * server (void *arg) { struct sockaddr_un cliaddr; socklen_t clilen; int clisock = TEMP_FAILURE_RETRY (accept (sock, &cliaddr, &clilen)); if (clisock == -1) { puts ("accept failed"); return NULL; } while (1) { struct coord c; if (TEMP_FAILURE_RETRY (read (clisock, &c, sizeof (c))) != sizeof (c)) { printf ("server read failed: %m (%d)\n", errno); break; } if (c.x == 0xffffffff && c.y == 0xffffffff) break; unsigned int rnds = 0; complex double z = c.z; while (cabs (z) < 4.0) { z = z * z - 1; if (++rnds == 255) break; } unsigned int vals[3] = { c.x, c.y, rnds }; if (TEMP_FAILURE_RETRY (write (clisock, vals, sizeof (vals))) != sizeof (vals)) { puts ("server write error"); return NULL; } } close (clisock); return NULL; } static const char *outfilename = "test.png"; static const struct argp_option options[] = { { "clients", 'c', "NUMBER", 0, "Number of client threads" }, { "servers", 's', "NUMBER", 0, "Number of server threads per client" }, { "timing", 'T', NULL, 0, "Measure time from startup to the last thread finishing" }, { NULL, 0, NULL, 0, NULL } }; /* Prototype for option handler. */ static error_t parse_opt (int key, char *arg, struct argp_state *state); /* Data structure to communicate with argp functions. */ static struct argp argp = { options, parse_opt }; int main (int argc, char *argv[]) { int cnt; FILE *outfile; struct sockaddr_un servaddr; socklen_t servlen; int remaining; /* Parse and process arguments. */ argp_parse (&argp, argc, argv, 0, &remaining, NULL); pthread_t servth[nservers * nclients]; pthread_t clntth[nclients]; struct thread_param clntparam[nclients]; image = gdImageCreate (size_x, size_y); if (image == NULL) { puts ("gdImageCreate failed"); return 1; } for (cnt = 0; cnt < 255; ++cnt) colors[cnt] = gdImageColorAllocate (image, 256 - cnt, 256 - cnt, 256 - cnt); /* Black. */ colors[cnt] = gdImageColorAllocate (image, 0, 0, 0); sock = socket (AF_UNIX, SOCK_STREAM, 0); if (sock < 0) error (EXIT_FAILURE, errno, "cannot create socket"); memset (&servaddr, '\0', sizeof (servaddr)); servaddr.sun_family = AF_UNIX; strncpy (servaddr.sun_path, PATH, sizeof (servaddr.sun_path)); servlen = offsetof (struct sockaddr_un, sun_path) + strlen (PATH) + 1; if (bind (sock, &servaddr, servlen) == -1) error (EXIT_FAILURE, errno, "bind failed"); listen (sock, SOMAXCONN); pthread_mutex_init (&image_lock, NULL); struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; clockid_t cl; struct timespec start_time; if (timing) { if (clock_getcpuclockid (0, &cl) != 0 || clock_gettime (cl, &start_time) != 0) timing = false; } /* Start the servers. */ for (cnt = 0; cnt < nservers * nclients; ++cnt) { if (pthread_create (&servth[cnt], NULL, server, NULL) != 0) { puts ("pthread_create for server failed"); exit (1); } } for (cnt = 0; cnt < nclients; ++cnt) { clntparam[cnt].from = cnt * (size_x * size_y) / nclients; clntparam[cnt].to = MIN ((cnt + 1) * (size_x * size_y) / nclients, size_x * size_y); clntparam[cnt].nserv = nservers; if (pthread_create (&clntth[cnt], NULL, client, &clntparam[cnt]) != 0) { puts ("pthread_create for client failed"); exit (1); } } /* Wait for the clients. */ for (cnt = 0; cnt < nclients; ++cnt) if (pthread_join (clntth[cnt], NULL) != 0) { puts ("client pthread_join failed"); exit (1); } /* Wait for the servers. */ for (cnt = 0; cnt < nclients * nservers; ++cnt) if (pthread_join (servth[cnt], NULL) != 0) { puts ("server pthread_join failed"); exit (1); } if (timing) { struct timespec end_time; if (clock_gettime (cl, &end_time) == 0) { end_time.tv_sec -= start_time.tv_sec; end_time.tv_nsec -= start_time.tv_nsec; if (end_time.tv_nsec < 0) { end_time.tv_nsec += 1000000000; --end_time.tv_sec; } printf ("\nRuntime: %lu.%09lu seconds\n%d points computed\n", (unsigned long int) end_time.tv_sec, (unsigned long int) end_time.tv_nsec, points); } } outfile = fopen (outfilename, "w"); if (outfile == NULL) error (EXIT_FAILURE, errno, "cannot open output file '%s'", outfilename); gdImagePng (image, outfile); fclose (outfile); unlink (PATH); return 0; } /* Handle program arguments. */ static error_t parse_opt (int key, char *arg, struct argp_state *state) { switch (key) { case 'c': nclients = strtoul (arg, NULL, 0); break; case 's': nservers = strtoul (arg, NULL, 0); break; case 'T': timing = true; break; default: return ARGP_ERR_UNKNOWN; } return 0; } static hp_timing_t get_clockfreq (void) { /* We read the information from the /proc filesystem. It contains at least one line like cpu MHz : 497.840237 or also cpu MHz : 497.841 We search for this line and convert the number in an integer. */ static hp_timing_t result; int fd; /* If this function was called before, we know the result. */ if (result != 0) return result; fd = open ("/proc/cpuinfo", O_RDONLY); if (__builtin_expect (fd != -1, 1)) { /* XXX AFAIK the /proc filesystem can generate "files" only up to a size of 4096 bytes. */ char buf[4096]; ssize_t n; n = read (fd, buf, sizeof buf); if (__builtin_expect (n, 1) > 0) { char *mhz = memmem (buf, n, "cpu MHz", 7); if (__builtin_expect (mhz != NULL, 1)) { char *endp = buf + n; int seen_decpoint = 0; int ndigits = 0; /* Search for the beginning of the string. */ while (mhz < endp && (*mhz < '0' || *mhz > '9') && *mhz != '\n') ++mhz; while (mhz < endp && *mhz != '\n') { if (*mhz >= '0' && *mhz <= '9') { result *= 10; result += *mhz - '0'; if (seen_decpoint) ++ndigits; } else if (*mhz == '.') seen_decpoint = 1; ++mhz; } /* Compensate for missing digits at the end. */ while (ndigits++ < 6) result *= 10; } } close (fd); } return result; } int clock_getcpuclockid (pid_t pid, clockid_t *clock_id) { /* We don't allow any process ID but our own. */ if (pid != 0 && pid != getpid ()) return EPERM; #ifdef CLOCK_PROCESS_CPUTIME_ID /* Store the number. */ *clock_id = CLOCK_PROCESS_CPUTIME_ID; return 0; #else /* We don't have a timer for that. */ return ENOENT; #endif } #define HP_TIMING_NOW(Var) __asm__ __volatile__ ("rdtsc" : "=A" (Var)) /* Get current value of CLOCK and store it in TP. */ int clock_gettime (clockid_t clock_id, struct timespec *tp) { int retval = -1; switch (clock_id) { case CLOCK_PROCESS_CPUTIME_ID: { static hp_timing_t freq; hp_timing_t tsc; /* Get the current counter. */ HP_TIMING_NOW (tsc); if (freq == 0) { freq = get_clockfreq (); if (freq == 0) return EINVAL; } /* Compute the seconds. */ tp->tv_sec = tsc / freq; /* And the nanoseconds. This computation should be stable until we get machines with about 16GHz frequency. */ tp->tv_nsec = ((tsc % freq) * UINT64_C (1000000000)) / freq; retval = 0; } break; default: errno = EINVAL; break; } return retval; }