/* * Copyright (c) 2005, Bull S.A.. All rights reserved. * Created by: Sebastien Decugis * This program is free software; you can redistribute it and/or modify it * under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * This program is distributed in the hope that it would be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the GNU General Public License along * with this program; if not, write the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * This scalability sample aims to test the following assertion: * -> The sem_open() duration does not depend on the # of opened semaphores * in the system * The steps are: * -> Create semaphores until failure * The test fails if the sem_open duration tends to grow with the # of semaphores, * or if the failure at last semaphore creation is unexpected. */ /* We are testing conformance to IEEE Std 1003.1, 2003 Edition */ #define _POSIX_C_SOURCE 200112L /********************************************************************************************/ /****************************** standard includes *****************************************/ /********************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include /********************************************************************************************/ /****************************** Test framework *****************************************/ /********************************************************************************************/ #include "testfrmw.h" #include "testfrmw.c" /* This header is responsible for defining the following macros: * UNRESOLVED(ret, descr); * where descr is a description of the error and ret is an int (error code for example) * FAILED(descr); * where descr is a short text saying why the test has failed. * PASSED(); * No parameter. * * Both three macros shall terminate the calling process. * The testcase shall not terminate in any other maneer. * * The other file defines the functions * void output_init() * void output(char * string, ...) * * Those may be used to output information. */ /********************************************************************************************/ /********************************** Configuration ******************************************/ /********************************************************************************************/ #ifndef SCALABILITY_FACTOR #define SCALABILITY_FACTOR 1 #endif #ifndef VERBOSE #define VERBOSE 1 #endif #define BLOCKSIZE (100 * SCALABILITY_FACTOR) #ifdef PLOT_OUTPUT #undef VERBOSE #define VERBOSE 0 #endif /********************************************************************************************/ /*********************************** Test *****************************************/ /********************************************************************************************/ /* The next structure is used to save the tests measures */ typedef struct __mes_t { int nsem; long _data_open; /* As we store µsec values, a long type should be enough. */ long _data_close; /* As we store µsec values, a long type should be enough. */ struct __mes_t *next; struct __mes_t *prev; } mes_t; /* Forward declaration */ int parse_measure(mes_t * measures); /* Structure to store created semaphores */ typedef struct __test_t { sem_t *sems[BLOCKSIZE]; struct __test_t *next; struct __test_t *prev; } test_t; /* Test routine */ int main(int argc, char *argv[]) { int ret, status, locerrno; int nsem, i; struct timespec ts_ref, ts_fin; mes_t sentinel; mes_t *m_cur, *m_tmp; char sem_name[255]; test_t sems; struct __test_t *sems_cur = &sems, *sems_tmp; long SEM_MAX = sysconf(_SC_SEM_NSEMS_MAX); /* Initialize the measure list */ m_cur = &sentinel; m_cur->next = NULL; m_cur->prev = NULL; /* Initialize output routine */ output_init(); /* Initialize sems */ sems_cur->next = NULL; sems_cur->prev = NULL; #if VERBOSE > 1 output("SEM_NSEMS_MAX: %ld\n", SEM_MAX); #endif #ifdef PLOT_OUTPUT output("# COLUMNS 3 Semaphores sem_open sem_close\n"); #endif nsem = 0; status = 0; while (1) { /* we will break */ /* Create a new block */ sems_tmp = malloc(sizeof(test_t)); if (sems_tmp == NULL) { /* We stop here */ #if VERBOSE > 0 output("malloc failed with error %d (%s)\n", errno, strerror(errno)); #endif /* We can proceed anyway */ status = 1; break; } /* read clock */ ret = clock_gettime(CLOCK_REALTIME, &ts_ref); if (ret != 0) { UNRESOLVED(errno, "Unable to read clock"); } /* Open all semaphores in the current block */ for (i = 0; i < BLOCKSIZE; i++) { sprintf(sem_name, "/sem_open_scal_s%d", nsem); sems_tmp->sems[i] = sem_open(sem_name, O_CREAT, 0777, 1); if (sems_tmp->sems[i] == SEM_FAILED) { #if VERBOSE > 0 output("sem_open failed with error %d (%s)\n", errno, strerror(errno)); #endif /* Check error code */ switch (errno) { case EMFILE: case ENFILE: case ENOSPC: case ENOMEM: status = 2; break; default: UNRESOLVED(errno, "Unexpected error!"); } break; } if ((SEM_MAX > 0) && (nsem > SEM_MAX)) { /* Erreur */ FAILED ("sem_open opened more than SEM_NSEMS_MAX semaphores"); } nsem++; } /* read clock */ ret = clock_gettime(CLOCK_REALTIME, &ts_fin); if (ret != 0) { UNRESOLVED(errno, "Unable to read clock"); } if (status == 2) { /* We were not able to fill this bloc, so we can discard it */ for (--i; i >= 0; i--) { ret = sem_close(sems_tmp->sems[i]); if (ret != 0) { UNRESOLVED(errno, "Failed to close"); } } free(sems_tmp); break; } sems_tmp->prev = sems_cur; sems_cur->next = sems_tmp; sems_cur = sems_tmp; sems_cur->next = NULL; /* add to the measure list */ m_tmp = malloc(sizeof(mes_t)); if (m_tmp == NULL) { /* We stop here */ #if VERBOSE > 0 output("malloc failed with error %d (%s)\n", errno, strerror(errno)); #endif /* We can proceed anyway */ status = 3; break; } m_tmp->nsem = nsem; m_tmp->next = NULL; m_tmp->prev = m_cur; m_cur->next = m_tmp; m_cur = m_tmp; m_cur->_data_open = ((ts_fin.tv_sec - ts_ref.tv_sec) * 1000000) + ((ts_fin.tv_nsec - ts_ref.tv_nsec) / 1000); m_cur->_data_close = 0; } locerrno = errno; /* Unlink all existing semaphores */ #if VERBOSE > 0 output("Unlinking %d semaphores\n", nsem); #endif for (i = 0; i <= nsem; i++) { sprintf(sem_name, "/sem_open_scal_s%d", i); sem_unlink(sem_name); } /* Free all semaphore blocs */ #if VERBOSE > 0 output("Close and free semaphores (this can take up to 10 minutes)\n"); #endif /* Reverse list order */ while (sems_cur != &sems) { /* read clock */ ret = clock_gettime(CLOCK_REALTIME, &ts_ref); if (ret != 0) { UNRESOLVED(errno, "Unable to read clock"); } /* Empty the sems_cur block */ for (i = 0; i < BLOCKSIZE; i++) { ret = sem_close(sems_cur->sems[i]); if (ret != 0) { UNRESOLVED(errno, "Failed to close a semaphore"); } } /* read clock */ ret = clock_gettime(CLOCK_REALTIME, &ts_fin); if (ret != 0) { UNRESOLVED(errno, "Unable to read clock"); } /* add this measure to measure list */ m_cur->_data_close = ((ts_fin.tv_sec - ts_ref.tv_sec) * 1000000) + ((ts_fin.tv_nsec - ts_ref.tv_nsec) / 1000); m_cur = m_cur->prev; /* remove the sem bloc */ sems_cur = sems_cur->prev; free(sems_cur->next); sems_cur->next = NULL; } #if VERBOSE > 0 output("Parse results\n"); #endif /* Compute the results */ ret = parse_measure(&sentinel); /* Free the resources and output the results */ #if VERBOSE > 5 output("Dump : \n"); output(" nsem | open | close \n"); #endif while (sentinel.next != NULL) { m_cur = sentinel.next; #if (VERBOSE > 5) || defined(PLOT_OUTPUT) output("%8.8i %1.1li.%6.6li %1.1li.%6.6li\n", m_cur->nsem, m_cur->_data_open / 1000000, m_cur->_data_open % 1000000, m_cur->_data_close / 1000000, m_cur->_data_close % 1000000); #endif sentinel.next = m_cur->next; free(m_cur); } if (ret != 0) { FAILED ("The function is not scalable, add verbosity for more information"); } /* Check status */ if (status) { UNRESOLVED(locerrno, "Function is scalable, but test terminated with error"); } #if VERBOSE > 0 output("-----\n"); output("All test data destroyed\n"); output("Test PASSED\n"); #endif PASSED; } /*** * The next function will seek for the better model for each series of measurements. * * The tested models are: -- X = # threads; Y = latency * -> Y = a; -- Error is r1 = avg((Y - Yavg)²); * -> Y = aX + b; -- Error is r2 = avg((Y -aX -b)²); * -- where a = avg ((X - Xavg)(Y - Yavg)) / avg((X - Xavg)²) * -- Note: We will call _q = sum((X - Xavg) * (Y - Yavg)); * -- and _d = sum((X - Xavg)²); * -- and b = Yavg - a * Xavg * -> Y = c * X^a;-- Same as previous, but with log(Y) = a log(X) + b; and b = log(c). Error is r3 * -> Y = exp(aX + b); -- log(Y) = aX + b. Error is r4 * * We compute each error factor (r1, r2, r3, r4) then search which is the smallest (with ponderation). * The function returns 0 when r1 is the best for all cases (latency is constant) and !0 otherwise. */ struct row { long X; /* the X values -- copied from function argument */ long Y_o; /* the Y values -- copied from function argument */ long Y_c; /* the Y values -- copied from function argument */ long _x; /* Value X - Xavg */ long _y_o; /* Value Y - Yavg */ long _y_c; /* Value Y - Yavg */ double LnX; /* Natural logarithm of X values */ double LnY_o; /* Natural logarithm of Y values */ double LnY_c; /* Natural logarithm of Y values */ double _lnx; /* Value LnX - LnXavg */ double _lny_o; /* Value LnY - LnYavg */ double _lny_c; /* Value LnY - LnYavg */ }; int parse_measure(mes_t * measures) { int ret, r; mes_t *cur; double Xavg, Yavg_o, Yavg_c; double LnXavg, LnYavg_o, LnYavg_c; int N; double r1_o, r2_o, r3_o, r4_o; double r1_c, r2_c, r3_c, r4_c; /* Some more intermediate vars */ long double _q_o[3]; long double _d_o[3]; long double _q_c[3]; long double _d_c[3]; long double t; /* temp value */ struct row *Table = NULL; /* This array contains the last element of each serie */ int array_max; /* Initialize the datas */ array_max = -1; /* means no data */ Xavg = 0.0; LnXavg = 0.0; Yavg_o = 0.0; LnYavg_o = 0.0; r1_o = 0.0; r2_o = 0.0; r3_o = 0.0; r4_o = 0.0; _q_o[0] = 0.0; _q_o[1] = 0.0; _q_o[2] = 0.0; _d_o[0] = 0.0; _d_o[1] = 0.0; _d_o[2] = 0.0; Yavg_c = 0.0; LnYavg_c = 0.0; r1_c = 0.0; r2_c = 0.0; r3_c = 0.0; r4_c = 0.0; _q_c[0] = 0.0; _q_c[1] = 0.0; _q_c[2] = 0.0; _d_c[0] = 0.0; _d_c[1] = 0.0; _d_c[2] = 0.0; N = 0; cur = measures; #if VERBOSE > 1 output("Data analysis starting\n"); #endif /* We start with reading the list to find: * -> number of elements, to assign an array. * -> average values */ while (cur->next != NULL) { cur = cur->next; N++; if (cur->_data_open != 0) { array_max = N; Xavg += (double)cur->nsem; LnXavg += log((double)cur->nsem); Yavg_o += (double)cur->_data_open; LnYavg_o += log((double)cur->_data_open); Yavg_c += (double)cur->_data_close; LnYavg_c += log((double)cur->_data_close); } } /* We have the sum; we can divide to obtain the average values */ if (array_max != -1) { Xavg /= array_max; LnXavg /= array_max; Yavg_o /= array_max; LnYavg_o /= array_max; Yavg_c /= array_max; LnYavg_c /= array_max; } #if VERBOSE > 1 output(" Found %d rows\n", N); #endif /* We will now alloc the array ... */ Table = calloc(N, sizeof(struct row)); if (Table == NULL) { UNRESOLVED(errno, "Unable to alloc space for results parsing"); } /* ... and fill it */ N = 0; cur = measures; while (cur->next != NULL) { cur = cur->next; Table[N].X = (long)cur->nsem; Table[N].LnX = log((double)cur->nsem); if (array_max > N) { Table[N]._x = Table[N].X - Xavg; Table[N]._lnx = Table[N].LnX - LnXavg; Table[N].Y_o = cur->_data_open; Table[N]._y_o = Table[N].Y_o - Yavg_o; Table[N].LnY_o = log((double)cur->_data_open); Table[N]._lny_o = Table[N].LnY_o - LnYavg_o; Table[N].Y_c = cur->_data_close; Table[N]._y_c = Table[N].Y_c - Yavg_c; Table[N].LnY_c = log((double)cur->_data_close); Table[N]._lny_c = Table[N].LnY_c - LnYavg_c; } N++; } /* We won't need the list anymore -- we'll work with the array which should be faster. */ #if VERBOSE > 1 output(" Data was stored in an array.\n"); #endif /* We need to read the full array at least twice to compute all the error factors */ /* In the first pass, we'll compute: * -> r1 for each scenar. * -> "a" factor for linear (0), power (1) and exponential (2) approximations -- with using the _d and _q vars. */ #if VERBOSE > 1 output("Starting first pass...\n"); #endif for (r = 0; r < array_max; r++) { r1_o += ((double)Table[r]._y_o / array_max) * (double)Table[r]._y_o; _q_o[0] += Table[r]._y_o * Table[r]._x; _d_o[0] += Table[r]._x * Table[r]._x; _q_o[1] += Table[r]._lny_o * Table[r]._lnx; _d_o[1] += Table[r]._lnx * Table[r]._lnx; _q_o[2] += Table[r]._lny_o * Table[r]._x; _d_o[2] += Table[r]._x * Table[r]._x; r1_c += ((double)Table[r]._y_c / array_max) * (double)Table[r]._y_c; _q_c[0] += Table[r]._y_c * Table[r]._x; _d_c[0] += Table[r]._x * Table[r]._x; _q_c[1] += Table[r]._lny_c * Table[r]._lnx; _d_c[1] += Table[r]._lnx * Table[r]._lnx; _q_c[2] += Table[r]._lny_c * Table[r]._x; _d_c[2] += Table[r]._x * Table[r]._x; } /* First pass is terminated; a2 = _q[0]/_d[0]; a3 = _q[1]/_d[1]; a4 = _q[2]/_d[2] */ /* In the first pass, we'll compute: * -> r2, r3, r4 for each scenar. */ #if VERBOSE > 1 output("Starting second pass...\n"); #endif for (r = 0; r < array_max; r++) { /* r2 = avg((y - ax -b)²); t = (y - ax - b) = (y - yavg) - a (x - xavg); */ t = (Table[r]._y_o - ((_q_o[0] * Table[r]._x) / _d_o[0])); r2_o += t * t / array_max; t = (Table[r]._y_c - ((_q_c[0] * Table[r]._x) / _d_c[0])); r2_c += t * t / array_max; /* r3 = avg((y - c.x^a) ²); t = y - c * x ^ a = y - log (LnYavg - (_q[1]/_d[1]) * LnXavg) * x ^ (_q[1]/_d[1]) */ t = (Table[r].Y_o - (logl(LnYavg_o - (_q_o[1] / _d_o[1]) * LnXavg) * powl(Table[r].X, (_q_o[1] / _d_o[1])) )); r3_o += t * t / array_max; t = (Table[r].Y_c - (logl(LnYavg_c - (_q_c[1] / _d_c[1]) * LnXavg) * powl(Table[r].X, (_q_c[1] / _d_c[1])) )); r3_c += t * t / array_max; /* r4 = avg((y - exp(ax+b))²); t = y - exp(ax+b) = y - exp(_q[2]/_d[2] * x + (LnYavg - (_q[2]/_d[2] * Xavg))); = y - exp(_q[2]/_d[2] * (x - Xavg) + LnYavg); */ t = (Table[r].Y_o - expl((_q_o[2] / _d_o[2]) * Table[r]._x + LnYavg_o)); r4_o += t * t / array_max; t = (Table[r].Y_c - expl((_q_c[2] / _d_c[2]) * Table[r]._x + LnYavg_c)); r4_c += t * t / array_max; } #if VERBOSE > 1 output("All computing terminated.\n"); #endif ret = 0; #if VERBOSE > 1 output(" # of data: %i\n", array_max); output(" Model: Y = k\n"); output(" sem_open:\n"); output(" k = %g\n", Yavg_o); output(" Divergence %g\n", r1_o); output(" sem_close:\n"); output(" k = %g\n", Yavg_c); output(" Divergence %g\n", r1_c); output(" Model: Y = a * X + b\n"); output(" sem_open:\n"); output(" a = %Lg\n", _q_o[0] / _d_o[0]); output(" b = %Lg\n", Yavg_o - ((_q_o[0] / _d_o[0]) * Xavg)); output(" Divergence %g\n", r2_o); output(" sem_close:\n"); output(" a = %Lg\n", _q_c[0] / _d_c[0]); output(" b = %Lg\n", Yavg_c - ((_q_c[0] / _d_c[0]) * Xavg)); output(" Divergence %g\n", r2_c); output(" Model: Y = c * X ^ a\n"); output(" sem_open:\n"); output(" a = %Lg\n", _q_o[1] / _d_o[1]); output(" c = %Lg\n", logl(LnYavg_o - (_q_o[1] / _d_o[1]) * LnXavg)); output(" Divergence %g\n", r3_o); output(" sem_close:\n"); output(" a = %Lg\n", _q_c[1] / _d_c[1]); output(" c = %Lg\n", logl(LnYavg_c - (_q_c[1] / _d_c[1]) * LnXavg)); output(" Divergence %g\n", r3_c); output(" Model: Y = exp(a * X + b)\n"); output(" sem_open:\n"); output(" a = %Lg\n", _q_o[2] / _d_o[2]); output(" b = %Lg\n", LnYavg_o - ((_q_o[2] / _d_o[2]) * Xavg)); output(" Divergence %g\n", r4_o); output(" sem_close:\n"); output(" a = %Lg\n", _q_c[2] / _d_c[2]); output(" b = %Lg\n", LnYavg_c - ((_q_c[2] / _d_c[2]) * Xavg)); output(" Divergence %g\n", r4_c); #endif if (array_max != -1) { /* Compare r1 to other values, with some ponderations */ if ((r1_o > 1.1 * r2_o) || (r1_o > 1.2 * r3_o) || (r1_o > 1.3 * r4_o) || (r1_c > 1.1 * r2_c) || (r1_c > 1.2 * r3_c) || (r1_c > 1.3 * r4_c)) ret++; #if VERBOSE > 1 else output(" Sanction: OK\n"); #endif } /* We need to free the array */ free(Table); /* We're done */ return ret; }