blob: 344d1d3376ccd16693f3170b1dbb69056042afc0 [file] [log] [blame]
/*
* Copyright (c) 2004, 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 fork() duration does not depend on the # of processes in the system
* The steps are:
* -> Create processes until failure
* -> wait for each created process starting before creating the next one.
* -> processes are destroyed once we have reached the max processes in the system.
* The test fails if the fork duration tends to grow with the # of processes.
*/
/********************************************************************************************/
/****************************** standard includes *****************************************/
/********************************************************************************************/
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <time.h>
#include <semaphore.h>
#include <fcntl.h>
#include <math.h>
/********************************************************************************************/
/****************************** 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 RESOLUTION (5 * 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 nprocess;
long _data; /* As we store µsec values, a long type should be enough. */
struct __mes_t *next;
} mes_t;
/* Forward declaration */
int parse_measure(mes_t * measures);
sem_t *sem_synchro;
sem_t *sem_ending;
int main(int argc, char *argv[])
{
int ret, status;
pid_t pidctl;
pid_t *pr;
int nprocesses, i;
struct timespec ts_ref, ts_fin;
mes_t sentinel;
mes_t *m_cur, *m_tmp;
long CHILD_MAX = sysconf(_SC_CHILD_MAX);
long my_max = 1000 * SCALABILITY_FACTOR;
/* Initialize the measure list */
m_cur = &sentinel;
m_cur->next = NULL;
/* Initialize output routine */
output_init();
if (CHILD_MAX > 0)
my_max = CHILD_MAX;
pr = (pid_t *) calloc(1 + my_max, sizeof(pid_t));
if (pr == NULL) {
UNRESOLVED(errno, "Not enough memory for process IDs storage");
}
#if VERBOSE > 1
output("CHILD_MAX: %d\n", CHILD_MAX);
#endif
#ifdef PLOT_OUTPUT
output("# COLUMNS 2 #Process Duration\n");
#endif
/* Initilaize the semaphores */
sem_synchro = sem_open("/fork_scal_sync", O_CREAT, O_RDWR, 0);
if (sem_synchro == SEM_FAILED) {
UNRESOLVED(errno, "Failed to open a named semaphore\n");
}
sem_unlink("/fork_scal_sync");
sem_ending = sem_open("/fork_scal_end", O_CREAT, O_RDWR, 0);
if (sem_ending == SEM_FAILED) {
UNRESOLVED(errno, "Failed to open a named semaphore\n");
}
sem_unlink("/fork_scal_end");
nprocesses = 0;
m_cur = &sentinel;
while (1) { /* we will break */
/* read clock */
ret = clock_gettime(CLOCK_REALTIME, &ts_ref);
if (ret != 0) {
UNRESOLVED(errno, "Unable to read clock");
}
/* create a new child */
pr[nprocesses] = fork();
if (pr[nprocesses] == -1) {
if (errno == EAGAIN || errno == ENOMEM)
break;
FAILED
("Failed to fork and received an unexpected error");
/* Post the semaphore so running processes will terminate */
do {
ret = sem_post(sem_ending);
} while (ret != 0 && errno == EINTR);
if (ret != 0)
output
("Failed to post the semaphore on termination: error %d\n",
errno);
}
if (pr[nprocesses] == 0) {
/* Child */
/* Post the synchro semaphore */
do {
ret = sem_post(sem_synchro);
} while ((ret != 0) && (errno == EINTR));
if (ret != 0) {
/* In this case the test will hang... */
UNRESOLVED(errno,
"Failed post the sync semaphore");
}
/* Wait the end semaphore */
do {
ret = sem_wait(sem_ending);
} while ((ret != 0) && (errno == EINTR));
if (ret != 0) {
UNRESOLVED(errno,
"Failed wait for the end semaphore");
}
/* Cascade-post the end semaphore */
do {
ret = sem_post(sem_ending);
} while ((ret != 0) && (errno == EINTR));
if (ret != 0) {
UNRESOLVED(errno,
"Failed post the end semaphore");
}
/* Exit */
exit(PTS_PASS);
}
/* Parent */
nprocesses++;
/* FAILED if nprocesses > CHILD_MAX */
if (nprocesses > my_max) {
errno = 0;
if (CHILD_MAX > 0) {
#if VERBOSE > 0
output
("WARNING! We were able to create more than CHILD_MAX processes\n");
#endif
}
break;
}
/* wait for the semaphore */
do {
ret = sem_wait(sem_synchro);
} while ((ret == -1) && (errno == EINTR));
if (ret == -1) {
sem_post(sem_ending);
UNRESOLVED(errno,
"Failed to wait for the sync semaphore");
}
/* read clock */
ret = clock_gettime(CLOCK_REALTIME, &ts_fin);
if (ret != 0) {
UNRESOLVED(errno, "Unable to read clock");
}
/* add to the measure list if nprocesses % resolution == 0 */
if (((nprocesses % RESOLUTION) == 0) && (nprocesses != 0)) {
/* Create an empty new element */
m_tmp = malloc(sizeof(mes_t));
if (m_tmp == NULL) {
sem_post(sem_ending);
UNRESOLVED(errno,
"Unable to alloc memory for measure saving");
}
m_tmp->nprocess = nprocesses;
m_tmp->next = NULL;
m_tmp->_data = 0;
m_cur->next = m_tmp;
m_cur = m_cur->next;
m_cur->_data =
((ts_fin.tv_sec - ts_ref.tv_sec) * 1000000) +
((ts_fin.tv_nsec - ts_ref.tv_nsec) / 1000);
#if VERBOSE > 5
output("Added the following measure: n=%i, v=%li\n",
nprocesses, m_cur->_data);
#endif
}
}
#if VERBOSE > 3
if (errno)
output
("Could not create anymore processes. Current count is %i\n",
nprocesses);
else
output
("Should not create anymore processes. Current count is %i\n",
nprocesses);
#endif
/* Unblock every created children: post once, then cascade signaling */
do {
ret = sem_post(sem_ending);
}
while ((ret != 0) && (errno == EINTR));
if (ret != 0) {
UNRESOLVED(errno, "Failed post the end semaphore");
}
#if VERBOSE > 3
output("Waiting children termination\n");
#endif
for (i = 0; i < nprocesses; i++) {
pidctl = waitpid(pr[i], &status, 0);
if (pidctl != pr[i]) {
UNRESOLVED(errno, "Waitpid returned the wrong PID");
}
if ((!WIFEXITED(status)) || (WEXITSTATUS(status) != PTS_PASS)) {
FAILED("Child exited abnormally");
}
}
/* Free some memory before result parsing */
free(pr);
/* Compute the results */
ret = parse_measure(&sentinel);
/* Free the resources and output the results */
#if VERBOSE > 5
output("Dump : \n");
output(" nproc | dur \n");
#endif
while (sentinel.next != NULL) {
m_cur = sentinel.next;
#if (VERBOSE > 5) || defined(PLOT_OUTPUT)
output("%8.8i %1.1li.%6.6li\n", m_cur->nprocess,
m_cur->_data / 1000000, m_cur->_data % 1000000);
#endif
sentinel.next = m_cur->next;
free(m_cur);
}
if (ret != 0) {
FAILED
("The function is not scalable, add verbosity for more information");
}
#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; /* the Y values -- copied from function argument */
long _x; /* Value X - Xavg */
long _y; /* Value Y - Yavg */
double LnX; /* Natural logarithm of X values */
double LnY; /* Natural logarithm of Y values */
double _lnx; /* Value LnX - LnXavg */
double _lny; /* Value LnY - LnYavg */
};
int parse_measure(mes_t * measures)
{
int ret, r;
mes_t *cur;
double Xavg, Yavg;
double LnXavg, LnYavg;
int N;
double r1, r2, r3, r4;
/* Some more intermediate vars */
long double _q[3];
long double _d[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 = 0.0;
LnYavg = 0.0;
r1 = 0.0;
r2 = 0.0;
r3 = 0.0;
r4 = 0.0;
_q[0] = 0.0;
_q[1] = 0.0;
_q[2] = 0.0;
_d[0] = 0.0;
_d[1] = 0.0;
_d[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 != 0) {
array_max = N;
Xavg += (double)cur->nprocess;
LnXavg += log((double)cur->nprocess);
Yavg += (double)cur->_data;
LnYavg += log((double)cur->_data);
}
}
/* We have the sum; we can divide to obtain the average values */
if (array_max != -1) {
Xavg /= array_max;
LnXavg /= array_max;
Yavg /= array_max;
LnYavg /= 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->nprocess;
Table[N].LnX = log((double)cur->nprocess);
if (array_max > N) {
Table[N]._x = Table[N].X - Xavg;
Table[N]._lnx = Table[N].LnX - LnXavg;
Table[N].Y = cur->_data;
Table[N]._y = Table[N].Y - Yavg;
Table[N].LnY = log((double)cur->_data);
Table[N]._lny = Table[N].LnY - LnYavg;
}
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 += ((double)Table[r]._y / array_max) * (double)Table[r]._y;
_q[0] += Table[r]._y * Table[r]._x;
_d[0] += Table[r]._x * Table[r]._x;
_q[1] += Table[r]._lny * Table[r]._lnx;
_d[1] += Table[r]._lnx * Table[r]._lnx;
_q[2] += Table[r]._lny * Table[r]._x;
_d[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 - ((_q[0] * Table[r]._x) / _d[0]));
r2 += 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 - (logl(LnYavg - (_q[1] / _d[1]) * LnXavg)
* powl(Table[r].X, (_q[1] / _d[1]))
));
r3 += 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 - expl((_q[2] / _d[2]) * Table[r]._x + LnYavg));
r4 += 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(" k = %g\n", Yavg);
output(" Divergence %g\n", r1);
output(" Model: Y = a * X + b\n");
output(" a = %Lg\n", _q[0] / _d[0]);
output(" b = %Lg\n", Yavg - ((_q[0] / _d[0]) * Xavg));
output(" Divergence %g\n", r2);
output(" Model: Y = c * X ^ a\n");
output(" a = %Lg\n", _q[1] / _d[1]);
output(" c = %Lg\n", logl(LnYavg - (_q[1] / _d[1]) * LnXavg));
output(" Divergence %g\n", r2);
output(" Model: Y = exp(a * X + b)\n");
output(" a = %Lg\n", _q[2] / _d[2]);
output(" b = %Lg\n", LnYavg - ((_q[2] / _d[2]) * Xavg));
output(" Divergence %g\n", r2);
#endif
if (array_max != -1) {
/* Compare r1 to other values, with some ponderations */
if ((r1 > 1.1 * r2) || (r1 > 1.2 * r3) || (r1 > 1.3 * r4))
ret++;
#if VERBOSE > 1
else
output(" Sanction: OK\n");
#endif
}
/* We need to free the array */
free(Table);
/* We're done */
return ret;
}