• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
main(int argc,char * argv[])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 
parse_measure(mes_t * measures)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