Home | History | Annotate | Download | only in sem_open
      1 /*
      2 * Copyright (c) 2005, Bull S.A..  All rights reserved.
      3 * Created by: Sebastien Decugis
      4 
      5 * This program is free software; you can redistribute it and/or modify it
      6 * under the terms of version 2 of the GNU General Public License as
      7 * published by the Free Software Foundation.
      8 *
      9 * This program is distributed in the hope that it would be useful, but
     10 * WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     12 *
     13 * You should have received a copy of the GNU General Public License along
     14 * with this program; if not, write the Free Software Foundation, Inc.,
     15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     16 
     17 * This scalability sample aims to test the following assertion:
     18 *  -> The sem_open() duration does not depend on the # of opened semaphores
     19 *     in the system
     20 
     21 * The steps are:
     22 * -> Create semaphores until failure
     23 
     24 * The test fails if the sem_open duration tends to grow with the # of semaphores,
     25 * or if the failure at last semaphore creation is unexpected.
     26 */
     27 
     28 /* We are testing conformance to IEEE Std 1003.1, 2003 Edition */
     29 #define _POSIX_C_SOURCE 200112L
     30 
     31 /********************************************************************************************/
     32 /****************************** standard includes *****************************************/
     33 /********************************************************************************************/
     34 #include <pthread.h>
     35 #include <stdarg.h>
     36 #include <stdio.h>
     37 #include <stdlib.h>
     38 #include <string.h>
     39 #include <unistd.h>
     40 
     41 #include <math.h>
     42 #include <errno.h>
     43 #include <time.h>
     44 #include <semaphore.h>
     45 #include <fcntl.h>
     46 
     47 /********************************************************************************************/
     48 /******************************   Test framework   *****************************************/
     49 /********************************************************************************************/
     50 #include "testfrmw.h"
     51 #include "testfrmw.c"
     52 /* This header is responsible for defining the following macros:
     53  * UNRESOLVED(ret, descr);
     54  *    where descr is a description of the error and ret is an int (error code for example)
     55  * FAILED(descr);
     56  *    where descr is a short text saying why the test has failed.
     57  * PASSED();
     58  *    No parameter.
     59  *
     60  * Both three macros shall terminate the calling process.
     61  * The testcase shall not terminate in any other maneer.
     62  *
     63  * The other file defines the functions
     64  * void output_init()
     65  * void output(char * string, ...)
     66  *
     67  * Those may be used to output information.
     68  */
     69 
     70 /********************************************************************************************/
     71 /********************************** Configuration ******************************************/
     72 /********************************************************************************************/
     73 #ifndef SCALABILITY_FACTOR
     74 #define SCALABILITY_FACTOR 1
     75 #endif
     76 #ifndef VERBOSE
     77 #define VERBOSE 1
     78 #endif
     79 
     80 #define BLOCKSIZE (100 * SCALABILITY_FACTOR)
     81 
     82 #ifdef PLOT_OUTPUT
     83 #undef VERBOSE
     84 #define VERBOSE 0
     85 #endif
     86 
     87 /********************************************************************************************/
     88 /***********************************       Test     *****************************************/
     89 /********************************************************************************************/
     90 
     91 /* The next structure is used to save the tests measures */
     92 
     93 typedef struct __mes_t {
     94 	int nsem;
     95 	long _data_open;	/* As we store sec values, a long type should be enough. */
     96 	long _data_close;	/* As we store sec values, a long type should be enough. */
     97 
     98 	struct __mes_t *next;
     99 
    100 	struct __mes_t *prev;
    101 } mes_t;
    102 
    103 /* Forward declaration */
    104 int parse_measure(mes_t * measures);
    105 
    106 /* Structure to store created semaphores */
    107 
    108 typedef struct __test_t {
    109 	sem_t *sems[BLOCKSIZE];
    110 
    111 	struct __test_t *next;
    112 
    113 	struct __test_t *prev;
    114 } test_t;
    115 
    116 /* Test routine */
    117 int main(int argc, char *argv[])
    118 {
    119 	int ret, status, locerrno;
    120 	int nsem, i;
    121 
    122 	struct timespec ts_ref, ts_fin;
    123 	mes_t sentinel;
    124 	mes_t *m_cur, *m_tmp;
    125 
    126 	char sem_name[255];
    127 	test_t sems;
    128 
    129 	struct __test_t *sems_cur = &sems, *sems_tmp;
    130 
    131 	long SEM_MAX = sysconf(_SC_SEM_NSEMS_MAX);
    132 
    133 	/* Initialize the measure list */
    134 	m_cur = &sentinel;
    135 	m_cur->next = NULL;
    136 	m_cur->prev = NULL;
    137 
    138 	/* Initialize output routine */
    139 	output_init();
    140 
    141 	/* Initialize sems */
    142 	sems_cur->next = NULL;
    143 	sems_cur->prev = NULL;
    144 
    145 #if VERBOSE > 1
    146 	output("SEM_NSEMS_MAX: %ld\n", SEM_MAX);
    147 
    148 #endif
    149 
    150 #ifdef PLOT_OUTPUT
    151 	output("# COLUMNS 3 Semaphores sem_open sem_close\n");
    152 
    153 #endif
    154 
    155 	nsem = 0;
    156 	status = 0;
    157 
    158 	while (1) {		/* we will break */
    159 		/* Create a new block */
    160 		sems_tmp = malloc(sizeof(test_t));
    161 
    162 		if (sems_tmp == NULL) {
    163 			/* We stop here */
    164 #if VERBOSE > 0
    165 			output("malloc failed with error %d (%s)\n", errno,
    166 			       strerror(errno));
    167 #endif
    168 			/* We can proceed anyway */
    169 			status = 1;
    170 
    171 			break;
    172 		}
    173 
    174 		/* read clock */
    175 		ret = clock_gettime(CLOCK_REALTIME, &ts_ref);
    176 
    177 		if (ret != 0) {
    178 			UNRESOLVED(errno, "Unable to read clock");
    179 		}
    180 
    181 		/* Open all semaphores in the current block */
    182 		for (i = 0; i < BLOCKSIZE; i++) {
    183 			sprintf(sem_name, "/sem_open_scal_s%d", nsem);
    184 			sems_tmp->sems[i] =
    185 			    sem_open(sem_name, O_CREAT, 0777, 1);
    186 
    187 			if (sems_tmp->sems[i] == SEM_FAILED) {
    188 #if VERBOSE > 0
    189 				output("sem_open failed with error %d (%s)\n",
    190 				       errno, strerror(errno));
    191 #endif
    192 				/* Check error code */
    193 
    194 				switch (errno) {
    195 				case EMFILE:
    196 				case ENFILE:
    197 				case ENOSPC:
    198 				case ENOMEM:
    199 					status = 2;
    200 					break;
    201 				default:
    202 					UNRESOLVED(errno, "Unexpected error!");
    203 				}
    204 
    205 				break;
    206 			}
    207 
    208 			if ((SEM_MAX > 0) && (nsem > SEM_MAX)) {
    209 				/* Erreur */
    210 				FAILED
    211 				    ("sem_open opened more than SEM_NSEMS_MAX semaphores");
    212 			}
    213 
    214 			nsem++;
    215 		}
    216 
    217 		/* read clock */
    218 		ret = clock_gettime(CLOCK_REALTIME, &ts_fin);
    219 
    220 		if (ret != 0) {
    221 			UNRESOLVED(errno, "Unable to read clock");
    222 		}
    223 
    224 		if (status == 2) {
    225 			/* We were not able to fill this bloc, so we can discard it */
    226 
    227 			for (--i; i >= 0; i--) {
    228 				ret = sem_close(sems_tmp->sems[i]);
    229 
    230 				if (ret != 0) {
    231 					UNRESOLVED(errno, "Failed to close");
    232 				}
    233 
    234 			}
    235 
    236 			free(sems_tmp);
    237 			break;
    238 
    239 		}
    240 
    241 		sems_tmp->prev = sems_cur;
    242 		sems_cur->next = sems_tmp;
    243 		sems_cur = sems_tmp;
    244 		sems_cur->next = NULL;
    245 
    246 		/* add to the measure list */
    247 		m_tmp = malloc(sizeof(mes_t));
    248 
    249 		if (m_tmp == NULL) {
    250 			/* We stop here */
    251 #if VERBOSE > 0
    252 			output("malloc failed with error %d (%s)\n", errno,
    253 			       strerror(errno));
    254 #endif
    255 			/* We can proceed anyway */
    256 			status = 3;
    257 
    258 			break;
    259 		}
    260 
    261 		m_tmp->nsem = nsem;
    262 		m_tmp->next = NULL;
    263 		m_tmp->prev = m_cur;
    264 		m_cur->next = m_tmp;
    265 
    266 		m_cur = m_tmp;
    267 
    268 		m_cur->_data_open =
    269 		    ((ts_fin.tv_sec - ts_ref.tv_sec) * 1000000) +
    270 		    ((ts_fin.tv_nsec - ts_ref.tv_nsec) / 1000);
    271 		m_cur->_data_close = 0;
    272 	}
    273 
    274 	locerrno = errno;
    275 
    276 	/* Unlink all existing semaphores */
    277 #if VERBOSE > 0
    278 	output("Unlinking %d semaphores\n", nsem);
    279 #endif
    280 
    281 	for (i = 0; i <= nsem; i++) {
    282 		sprintf(sem_name, "/sem_open_scal_s%d", i);
    283 		sem_unlink(sem_name);
    284 	}
    285 
    286 	/* Free all semaphore blocs */
    287 #if VERBOSE > 0
    288 	output("Close and free semaphores (this can take up to 10 minutes)\n");
    289 
    290 #endif
    291 
    292 	/* Reverse list order */
    293 	while (sems_cur != &sems) {
    294 		/* read clock */
    295 		ret = clock_gettime(CLOCK_REALTIME, &ts_ref);
    296 
    297 		if (ret != 0) {
    298 			UNRESOLVED(errno, "Unable to read clock");
    299 		}
    300 
    301 		/* Empty the sems_cur block */
    302 
    303 		for (i = 0; i < BLOCKSIZE; i++) {
    304 			ret = sem_close(sems_cur->sems[i]);
    305 
    306 			if (ret != 0) {
    307 				UNRESOLVED(errno,
    308 					   "Failed to close a semaphore");
    309 			}
    310 		}
    311 
    312 		/* read clock */
    313 		ret = clock_gettime(CLOCK_REALTIME, &ts_fin);
    314 
    315 		if (ret != 0) {
    316 			UNRESOLVED(errno, "Unable to read clock");
    317 		}
    318 
    319 		/* add this measure to measure list */
    320 
    321 		m_cur->_data_close =
    322 		    ((ts_fin.tv_sec - ts_ref.tv_sec) * 1000000) +
    323 		    ((ts_fin.tv_nsec - ts_ref.tv_nsec) / 1000);
    324 
    325 		m_cur = m_cur->prev;
    326 
    327 		/* remove the sem bloc */
    328 		sems_cur = sems_cur->prev;
    329 
    330 		free(sems_cur->next);
    331 
    332 		sems_cur->next = NULL;
    333 	}
    334 
    335 #if VERBOSE > 0
    336 	output("Parse results\n");
    337 
    338 #endif
    339 
    340 	/* Compute the results */
    341 	ret = parse_measure(&sentinel);
    342 
    343 	/* Free the resources and output the results */
    344 
    345 #if VERBOSE > 5
    346 	output("Dump : \n");
    347 
    348 	output("  nsem  |  open  |  close \n");
    349 
    350 #endif
    351 
    352 	while (sentinel.next != NULL) {
    353 		m_cur = sentinel.next;
    354 #if (VERBOSE > 5) || defined(PLOT_OUTPUT)
    355 		output("%8.8i %1.1li.%6.6li %1.1li.%6.6li\n", m_cur->nsem,
    356 		       m_cur->_data_open / 1000000, m_cur->_data_open % 1000000,
    357 		       m_cur->_data_close / 1000000,
    358 		       m_cur->_data_close % 1000000);
    359 
    360 #endif
    361 		sentinel.next = m_cur->next;
    362 
    363 		free(m_cur);
    364 	}
    365 
    366 	if (ret != 0) {
    367 		FAILED
    368 		    ("The function is not scalable, add verbosity for more information");
    369 	}
    370 
    371 	/* Check status */
    372 	if (status) {
    373 		UNRESOLVED(locerrno,
    374 			   "Function is scalable, but test terminated with error");
    375 	}
    376 #if VERBOSE > 0
    377 	output("-----\n");
    378 
    379 	output("All test data destroyed\n");
    380 
    381 	output("Test PASSED\n");
    382 
    383 #endif
    384 
    385 	PASSED;
    386 }
    387 
    388 /***
    389  * The next function will seek for the better model for each series of measurements.
    390  *
    391  * The tested models are: -- X = # threads; Y = latency
    392  * -> Y = a;      -- Error is r1 = avg((Y - Yavg));
    393  * -> Y = aX + b; -- Error is r2 = avg((Y -aX -b));
    394  *                -- where a = avg ((X - Xavg)(Y - Yavg)) / avg((X - Xavg))
    395  *                --         Note: We will call _q = sum((X - Xavg) * (Y - Yavg));
    396  *                --                       and  _d = sum((X - Xavg));
    397  *                -- and   b = Yavg - a * Xavg
    398  * -> Y = c * X^a;-- Same as previous, but with log(Y) = a log(X) + b; and b = log(c). Error is r3
    399  * -> Y = exp(aX + b); -- log(Y) = aX + b. Error is r4
    400  *
    401  * We compute each error factor (r1, r2, r3, r4) then search which is the smallest (with ponderation).
    402  * The function returns 0 when r1 is the best for all cases (latency is constant) and !0 otherwise.
    403  */
    404 
    405 struct row {
    406 	long X;			/* the X values -- copied from function argument */
    407 	long Y_o;		/* the Y values -- copied from function argument */
    408 	long Y_c;		/* the Y values -- copied from function argument */
    409 	long _x;		/* Value X - Xavg */
    410 	long _y_o;		/* Value Y - Yavg */
    411 	long _y_c;		/* Value Y - Yavg */
    412 	double LnX;		/* Natural logarithm of X values */
    413 	double LnY_o;		/* Natural logarithm of Y values */
    414 	double LnY_c;		/* Natural logarithm of Y values */
    415 	double _lnx;		/* Value LnX - LnXavg */
    416 	double _lny_o;		/* Value LnY - LnYavg */
    417 	double _lny_c;		/* Value LnY - LnYavg */
    418 };
    419 
    420 int parse_measure(mes_t * measures)
    421 {
    422 	int ret, r;
    423 
    424 	mes_t *cur;
    425 
    426 	double Xavg, Yavg_o, Yavg_c;
    427 	double LnXavg, LnYavg_o, LnYavg_c;
    428 
    429 	int N;
    430 
    431 	double r1_o, r2_o, r3_o, r4_o;
    432 	double r1_c, r2_c, r3_c, r4_c;
    433 
    434 	/* Some more intermediate vars */
    435 	long double _q_o[3];
    436 	long double _d_o[3];
    437 	long double _q_c[3];
    438 	long double _d_c[3];
    439 
    440 	long double t;		/* temp value */
    441 
    442 	struct row *Table = NULL;
    443 
    444 	/* This array contains the last element of each serie */
    445 	int array_max;
    446 
    447 	/* Initialize the datas */
    448 
    449 	array_max = -1;		/* means no data */
    450 	Xavg = 0.0;
    451 	LnXavg = 0.0;
    452 	Yavg_o = 0.0;
    453 	LnYavg_o = 0.0;
    454 	r1_o = 0.0;
    455 	r2_o = 0.0;
    456 	r3_o = 0.0;
    457 	r4_o = 0.0;
    458 	_q_o[0] = 0.0;
    459 	_q_o[1] = 0.0;
    460 	_q_o[2] = 0.0;
    461 	_d_o[0] = 0.0;
    462 	_d_o[1] = 0.0;
    463 	_d_o[2] = 0.0;
    464 	Yavg_c = 0.0;
    465 	LnYavg_c = 0.0;
    466 	r1_c = 0.0;
    467 	r2_c = 0.0;
    468 	r3_c = 0.0;
    469 	r4_c = 0.0;
    470 	_q_c[0] = 0.0;
    471 	_q_c[1] = 0.0;
    472 	_q_c[2] = 0.0;
    473 	_d_c[0] = 0.0;
    474 	_d_c[1] = 0.0;
    475 	_d_c[2] = 0.0;
    476 
    477 	N = 0;
    478 	cur = measures;
    479 
    480 #if VERBOSE > 1
    481 	output("Data analysis starting\n");
    482 #endif
    483 
    484 	/* We start with reading the list to find:
    485 	 * -> number of elements, to assign an array.
    486 	 * -> average values
    487 	 */
    488 
    489 	while (cur->next != NULL) {
    490 		cur = cur->next;
    491 
    492 		N++;
    493 
    494 		if (cur->_data_open != 0) {
    495 			array_max = N;
    496 			Xavg += (double)cur->nsem;
    497 			LnXavg += log((double)cur->nsem);
    498 			Yavg_o += (double)cur->_data_open;
    499 			LnYavg_o += log((double)cur->_data_open);
    500 			Yavg_c += (double)cur->_data_close;
    501 			LnYavg_c += log((double)cur->_data_close);
    502 		}
    503 
    504 	}
    505 
    506 	/* We have the sum; we can divide to obtain the average values */
    507 	if (array_max != -1) {
    508 		Xavg /= array_max;
    509 		LnXavg /= array_max;
    510 		Yavg_o /= array_max;
    511 		LnYavg_o /= array_max;
    512 		Yavg_c /= array_max;
    513 		LnYavg_c /= array_max;
    514 	}
    515 #if VERBOSE > 1
    516 	output(" Found %d rows\n", N);
    517 
    518 #endif
    519 
    520 	/* We will now alloc the array ... */
    521 
    522 	Table = calloc(N, sizeof(struct row));
    523 
    524 	if (Table == NULL) {
    525 		UNRESOLVED(errno, "Unable to alloc space for results parsing");
    526 	}
    527 
    528 	/* ... and fill it */
    529 	N = 0;
    530 
    531 	cur = measures;
    532 
    533 	while (cur->next != NULL) {
    534 		cur = cur->next;
    535 
    536 		Table[N].X = (long)cur->nsem;
    537 		Table[N].LnX = log((double)cur->nsem);
    538 
    539 		if (array_max > N) {
    540 			Table[N]._x = Table[N].X - Xavg;
    541 			Table[N]._lnx = Table[N].LnX - LnXavg;
    542 			Table[N].Y_o = cur->_data_open;
    543 			Table[N]._y_o = Table[N].Y_o - Yavg_o;
    544 			Table[N].LnY_o = log((double)cur->_data_open);
    545 			Table[N]._lny_o = Table[N].LnY_o - LnYavg_o;
    546 			Table[N].Y_c = cur->_data_close;
    547 			Table[N]._y_c = Table[N].Y_c - Yavg_c;
    548 			Table[N].LnY_c = log((double)cur->_data_close);
    549 			Table[N]._lny_c = Table[N].LnY_c - LnYavg_c;
    550 		}
    551 
    552 		N++;
    553 	}
    554 
    555 	/* We won't need the list anymore -- we'll work with the array which should be faster. */
    556 #if VERBOSE > 1
    557 	output(" Data was stored in an array.\n");
    558 
    559 #endif
    560 
    561 	/* We need to read the full array at least twice to compute all the error factors */
    562 
    563 	/* In the first pass, we'll compute:
    564 	 * -> r1 for each scenar.
    565 	 * -> "a" factor for linear (0), power (1) and exponential (2) approximations -- with using the _d and _q vars.
    566 	 */
    567 #if VERBOSE > 1
    568 	output("Starting first pass...\n");
    569 
    570 #endif
    571 	for (r = 0; r < array_max; r++) {
    572 		r1_o +=
    573 		    ((double)Table[r]._y_o / array_max) * (double)Table[r]._y_o;
    574 
    575 		_q_o[0] += Table[r]._y_o * Table[r]._x;
    576 		_d_o[0] += Table[r]._x * Table[r]._x;
    577 
    578 		_q_o[1] += Table[r]._lny_o * Table[r]._lnx;
    579 		_d_o[1] += Table[r]._lnx * Table[r]._lnx;
    580 
    581 		_q_o[2] += Table[r]._lny_o * Table[r]._x;
    582 		_d_o[2] += Table[r]._x * Table[r]._x;
    583 
    584 		r1_c +=
    585 		    ((double)Table[r]._y_c / array_max) * (double)Table[r]._y_c;
    586 
    587 		_q_c[0] += Table[r]._y_c * Table[r]._x;
    588 		_d_c[0] += Table[r]._x * Table[r]._x;
    589 
    590 		_q_c[1] += Table[r]._lny_c * Table[r]._lnx;
    591 		_d_c[1] += Table[r]._lnx * Table[r]._lnx;
    592 
    593 		_q_c[2] += Table[r]._lny_c * Table[r]._x;
    594 		_d_c[2] += Table[r]._x * Table[r]._x;
    595 
    596 	}
    597 
    598 	/* First pass is terminated; a2 = _q[0]/_d[0]; a3 = _q[1]/_d[1]; a4 = _q[2]/_d[2] */
    599 
    600 	/* In the first pass, we'll compute:
    601 	 * -> r2, r3, r4 for each scenar.
    602 	 */
    603 
    604 #if VERBOSE > 1
    605 	output("Starting second pass...\n");
    606 
    607 #endif
    608 	for (r = 0; r < array_max; r++) {
    609 		/* r2 = avg((y - ax -b));  t = (y - ax - b) = (y - yavg) - a (x - xavg); */
    610 		t = (Table[r]._y_o - ((_q_o[0] * Table[r]._x) / _d_o[0]));
    611 		r2_o += t * t / array_max;
    612 
    613 		t = (Table[r]._y_c - ((_q_c[0] * Table[r]._x) / _d_c[0]));
    614 		r2_c += t * t / array_max;
    615 
    616 		/* r3 = avg((y - c.x^a) );
    617 		   t = y - c * x ^ a
    618 		   = y - log (LnYavg - (_q[1]/_d[1]) * LnXavg) * x ^ (_q[1]/_d[1])
    619 		 */
    620 		t = (Table[r].Y_o
    621 		     - (logl(LnYavg_o - (_q_o[1] / _d_o[1]) * LnXavg)
    622 			* powl(Table[r].X, (_q_o[1] / _d_o[1]))
    623 		     ));
    624 		r3_o += t * t / array_max;
    625 
    626 		t = (Table[r].Y_c
    627 		     - (logl(LnYavg_c - (_q_c[1] / _d_c[1]) * LnXavg)
    628 			* powl(Table[r].X, (_q_c[1] / _d_c[1]))
    629 		     ));
    630 		r3_c += t * t / array_max;
    631 
    632 		/* r4 = avg((y - exp(ax+b)));
    633 		   t = y - exp(ax+b)
    634 		   = y - exp(_q[2]/_d[2] * x + (LnYavg - (_q[2]/_d[2] * Xavg)));
    635 		   = y - exp(_q[2]/_d[2] * (x - Xavg) + LnYavg);
    636 		 */
    637 		t = (Table[r].Y_o
    638 		     - expl((_q_o[2] / _d_o[2]) * Table[r]._x + LnYavg_o));
    639 		r4_o += t * t / array_max;
    640 
    641 		t = (Table[r].Y_c
    642 		     - expl((_q_c[2] / _d_c[2]) * Table[r]._x + LnYavg_c));
    643 		r4_c += t * t / array_max;
    644 
    645 	}
    646 
    647 #if VERBOSE > 1
    648 	output("All computing terminated.\n");
    649 
    650 #endif
    651 	ret = 0;
    652 
    653 #if VERBOSE > 1
    654 	output(" # of data: %i\n", array_max);
    655 
    656 	output("  Model: Y = k\n");
    657 
    658 	output("   sem_open:\n");
    659 
    660 	output("       k = %g\n", Yavg_o);
    661 
    662 	output("    Divergence %g\n", r1_o);
    663 
    664 	output("   sem_close:\n");
    665 
    666 	output("       k = %g\n", Yavg_c);
    667 
    668 	output("    Divergence %g\n", r1_c);
    669 
    670 	output("  Model: Y = a * X + b\n");
    671 
    672 	output("   sem_open:\n");
    673 
    674 	output("       a = %Lg\n", _q_o[0] / _d_o[0]);
    675 
    676 	output("       b = %Lg\n", Yavg_o - ((_q_o[0] / _d_o[0]) * Xavg));
    677 
    678 	output("    Divergence %g\n", r2_o);
    679 
    680 	output("   sem_close:\n");
    681 
    682 	output("       a = %Lg\n", _q_c[0] / _d_c[0]);
    683 
    684 	output("       b = %Lg\n", Yavg_c - ((_q_c[0] / _d_c[0]) * Xavg));
    685 
    686 	output("    Divergence %g\n", r2_c);
    687 
    688 	output("  Model: Y = c * X ^ a\n");
    689 
    690 	output("   sem_open:\n");
    691 
    692 	output("       a = %Lg\n", _q_o[1] / _d_o[1]);
    693 
    694 	output("       c = %Lg\n",
    695 	       logl(LnYavg_o - (_q_o[1] / _d_o[1]) * LnXavg));
    696 
    697 	output("    Divergence %g\n", r3_o);
    698 
    699 	output("   sem_close:\n");
    700 
    701 	output("       a = %Lg\n", _q_c[1] / _d_c[1]);
    702 
    703 	output("       c = %Lg\n",
    704 	       logl(LnYavg_c - (_q_c[1] / _d_c[1]) * LnXavg));
    705 
    706 	output("    Divergence %g\n", r3_c);
    707 
    708 	output("  Model: Y = exp(a * X + b)\n");
    709 
    710 	output("   sem_open:\n");
    711 
    712 	output("       a = %Lg\n", _q_o[2] / _d_o[2]);
    713 
    714 	output("       b = %Lg\n", LnYavg_o - ((_q_o[2] / _d_o[2]) * Xavg));
    715 
    716 	output("    Divergence %g\n", r4_o);
    717 
    718 	output("   sem_close:\n");
    719 
    720 	output("       a = %Lg\n", _q_c[2] / _d_c[2]);
    721 
    722 	output("       b = %Lg\n", LnYavg_c - ((_q_c[2] / _d_c[2]) * Xavg));
    723 
    724 	output("    Divergence %g\n", r4_c);
    725 
    726 #endif
    727 
    728 	if (array_max != -1) {
    729 		/* Compare r1 to other values, with some ponderations */
    730 
    731 		if ((r1_o > 1.1 * r2_o) || (r1_o > 1.2 * r3_o) ||
    732 		    (r1_o > 1.3 * r4_o) || (r1_c > 1.1 * r2_c) ||
    733 		    (r1_c > 1.2 * r3_c) || (r1_c > 1.3 * r4_c))
    734 			ret++;
    735 
    736 #if VERBOSE > 1
    737 		else
    738 			output(" Sanction: OK\n");
    739 
    740 #endif
    741 
    742 	}
    743 
    744 	/* We need to free the array */
    745 	free(Table);
    746 
    747 	/* We're done */
    748 	return ret;
    749 }
    750