ministat.c revision 161692
1/*
2 * ----------------------------------------------------------------------------
3 * "THE BEER-WARE LICENSE" (Revision 42):
4 * <phk@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
5 * can do whatever you want with this stuff. If we meet some day, and you think
6 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
7 * ----------------------------------------------------------------------------
8 *
9 */
10
11#include <sys/cdefs.h>
12__FBSDID("$FreeBSD: head/usr.bin/ministat/ministat.c 161692 2006-08-28 08:27:02Z phk $");
13
14#include <stdio.h>
15#include <math.h>
16#include <err.h>
17#include <string.h>
18#include <stdlib.h>
19#include <unistd.h>
20#include <sys/ioctl.h>
21#include <sys/queue.h>
22#include <sys/ttycom.h>
23
24#define NSTUDENT 100
25#define NCONF 6
26double const studentpct[] = { 80, 90, 95, 98, 99, 99.5 };
27double student [NSTUDENT + 1][NCONF] = {
28/* inf */	{	1.282,	1.645,	1.960,	2.326,	2.576,	3.090  },
29/* 1. */	{	3.078,	6.314,	12.706,	31.821,	63.657,	318.313  },
30/* 2. */	{	1.886,	2.920,	4.303,	6.965,	9.925,	22.327  },
31/* 3. */	{	1.638,	2.353,	3.182,	4.541,	5.841,	10.215  },
32/* 4. */	{	1.533,	2.132,	2.776,	3.747,	4.604,	7.173  },
33/* 5. */	{	1.476,	2.015,	2.571,	3.365,	4.032,	5.893  },
34/* 6. */	{	1.440,	1.943,	2.447,	3.143,	3.707,	5.208  },
35/* 7. */	{	1.415,	1.895,	2.365,	2.998,	3.499,	4.782  },
36/* 8. */	{	1.397,	1.860,	2.306,	2.896,	3.355,	4.499  },
37/* 9. */	{	1.383,	1.833,	2.262,	2.821,	3.250,	4.296  },
38/* 10. */	{	1.372,	1.812,	2.228,	2.764,	3.169,	4.143  },
39/* 11. */	{	1.363,	1.796,	2.201,	2.718,	3.106,	4.024  },
40/* 12. */	{	1.356,	1.782,	2.179,	2.681,	3.055,	3.929  },
41/* 13. */	{	1.350,	1.771,	2.160,	2.650,	3.012,	3.852  },
42/* 14. */	{	1.345,	1.761,	2.145,	2.624,	2.977,	3.787  },
43/* 15. */	{	1.341,	1.753,	2.131,	2.602,	2.947,	3.733  },
44/* 16. */	{	1.337,	1.746,	2.120,	2.583,	2.921,	3.686  },
45/* 17. */	{	1.333,	1.740,	2.110,	2.567,	2.898,	3.646  },
46/* 18. */	{	1.330,	1.734,	2.101,	2.552,	2.878,	3.610  },
47/* 19. */	{	1.328,	1.729,	2.093,	2.539,	2.861,	3.579  },
48/* 20. */	{	1.325,	1.725,	2.086,	2.528,	2.845,	3.552  },
49/* 21. */	{	1.323,	1.721,	2.080,	2.518,	2.831,	3.527  },
50/* 22. */	{	1.321,	1.717,	2.074,	2.508,	2.819,	3.505  },
51/* 23. */	{	1.319,	1.714,	2.069,	2.500,	2.807,	3.485  },
52/* 24. */	{	1.318,	1.711,	2.064,	2.492,	2.797,	3.467  },
53/* 25. */	{	1.316,	1.708,	2.060,	2.485,	2.787,	3.450  },
54/* 26. */	{	1.315,	1.706,	2.056,	2.479,	2.779,	3.435  },
55/* 27. */	{	1.314,	1.703,	2.052,	2.473,	2.771,	3.421  },
56/* 28. */	{	1.313,	1.701,	2.048,	2.467,	2.763,	3.408  },
57/* 29. */	{	1.311,	1.699,	2.045,	2.462,	2.756,	3.396  },
58/* 30. */	{	1.310,	1.697,	2.042,	2.457,	2.750,	3.385  },
59/* 31. */	{	1.309,	1.696,	2.040,	2.453,	2.744,	3.375  },
60/* 32. */	{	1.309,	1.694,	2.037,	2.449,	2.738,	3.365  },
61/* 33. */	{	1.308,	1.692,	2.035,	2.445,	2.733,	3.356  },
62/* 34. */	{	1.307,	1.691,	2.032,	2.441,	2.728,	3.348  },
63/* 35. */	{	1.306,	1.690,	2.030,	2.438,	2.724,	3.340  },
64/* 36. */	{	1.306,	1.688,	2.028,	2.434,	2.719,	3.333  },
65/* 37. */	{	1.305,	1.687,	2.026,	2.431,	2.715,	3.326  },
66/* 38. */	{	1.304,	1.686,	2.024,	2.429,	2.712,	3.319  },
67/* 39. */	{	1.304,	1.685,	2.023,	2.426,	2.708,	3.313  },
68/* 40. */	{	1.303,	1.684,	2.021,	2.423,	2.704,	3.307  },
69/* 41. */	{	1.303,	1.683,	2.020,	2.421,	2.701,	3.301  },
70/* 42. */	{	1.302,	1.682,	2.018,	2.418,	2.698,	3.296  },
71/* 43. */	{	1.302,	1.681,	2.017,	2.416,	2.695,	3.291  },
72/* 44. */	{	1.301,	1.680,	2.015,	2.414,	2.692,	3.286  },
73/* 45. */	{	1.301,	1.679,	2.014,	2.412,	2.690,	3.281  },
74/* 46. */	{	1.300,	1.679,	2.013,	2.410,	2.687,	3.277  },
75/* 47. */	{	1.300,	1.678,	2.012,	2.408,	2.685,	3.273  },
76/* 48. */	{	1.299,	1.677,	2.011,	2.407,	2.682,	3.269  },
77/* 49. */	{	1.299,	1.677,	2.010,	2.405,	2.680,	3.265  },
78/* 50. */	{	1.299,	1.676,	2.009,	2.403,	2.678,	3.261  },
79/* 51. */	{	1.298,	1.675,	2.008,	2.402,	2.676,	3.258  },
80/* 52. */	{	1.298,	1.675,	2.007,	2.400,	2.674,	3.255  },
81/* 53. */	{	1.298,	1.674,	2.006,	2.399,	2.672,	3.251  },
82/* 54. */	{	1.297,	1.674,	2.005,	2.397,	2.670,	3.248  },
83/* 55. */	{	1.297,	1.673,	2.004,	2.396,	2.668,	3.245  },
84/* 56. */	{	1.297,	1.673,	2.003,	2.395,	2.667,	3.242  },
85/* 57. */	{	1.297,	1.672,	2.002,	2.394,	2.665,	3.239  },
86/* 58. */	{	1.296,	1.672,	2.002,	2.392,	2.663,	3.237  },
87/* 59. */	{	1.296,	1.671,	2.001,	2.391,	2.662,	3.234  },
88/* 60. */	{	1.296,	1.671,	2.000,	2.390,	2.660,	3.232  },
89/* 61. */	{	1.296,	1.670,	2.000,	2.389,	2.659,	3.229  },
90/* 62. */	{	1.295,	1.670,	1.999,	2.388,	2.657,	3.227  },
91/* 63. */	{	1.295,	1.669,	1.998,	2.387,	2.656,	3.225  },
92/* 64. */	{	1.295,	1.669,	1.998,	2.386,	2.655,	3.223  },
93/* 65. */	{	1.295,	1.669,	1.997,	2.385,	2.654,	3.220  },
94/* 66. */	{	1.295,	1.668,	1.997,	2.384,	2.652,	3.218  },
95/* 67. */	{	1.294,	1.668,	1.996,	2.383,	2.651,	3.216  },
96/* 68. */	{	1.294,	1.668,	1.995,	2.382,	2.650,	3.214  },
97/* 69. */	{	1.294,	1.667,	1.995,	2.382,	2.649,	3.213  },
98/* 70. */	{	1.294,	1.667,	1.994,	2.381,	2.648,	3.211  },
99/* 71. */	{	1.294,	1.667,	1.994,	2.380,	2.647,	3.209  },
100/* 72. */	{	1.293,	1.666,	1.993,	2.379,	2.646,	3.207  },
101/* 73. */	{	1.293,	1.666,	1.993,	2.379,	2.645,	3.206  },
102/* 74. */	{	1.293,	1.666,	1.993,	2.378,	2.644,	3.204  },
103/* 75. */	{	1.293,	1.665,	1.992,	2.377,	2.643,	3.202  },
104/* 76. */	{	1.293,	1.665,	1.992,	2.376,	2.642,	3.201  },
105/* 77. */	{	1.293,	1.665,	1.991,	2.376,	2.641,	3.199  },
106/* 78. */	{	1.292,	1.665,	1.991,	2.375,	2.640,	3.198  },
107/* 79. */	{	1.292,	1.664,	1.990,	2.374,	2.640,	3.197  },
108/* 80. */	{	1.292,	1.664,	1.990,	2.374,	2.639,	3.195  },
109/* 81. */	{	1.292,	1.664,	1.990,	2.373,	2.638,	3.194  },
110/* 82. */	{	1.292,	1.664,	1.989,	2.373,	2.637,	3.193  },
111/* 83. */	{	1.292,	1.663,	1.989,	2.372,	2.636,	3.191  },
112/* 84. */	{	1.292,	1.663,	1.989,	2.372,	2.636,	3.190  },
113/* 85. */	{	1.292,	1.663,	1.988,	2.371,	2.635,	3.189  },
114/* 86. */	{	1.291,	1.663,	1.988,	2.370,	2.634,	3.188  },
115/* 87. */	{	1.291,	1.663,	1.988,	2.370,	2.634,	3.187  },
116/* 88. */	{	1.291,	1.662,	1.987,	2.369,	2.633,	3.185  },
117/* 89. */	{	1.291,	1.662,	1.987,	2.369,	2.632,	3.184  },
118/* 90. */	{	1.291,	1.662,	1.987,	2.368,	2.632,	3.183  },
119/* 91. */	{	1.291,	1.662,	1.986,	2.368,	2.631,	3.182  },
120/* 92. */	{	1.291,	1.662,	1.986,	2.368,	2.630,	3.181  },
121/* 93. */	{	1.291,	1.661,	1.986,	2.367,	2.630,	3.180  },
122/* 94. */	{	1.291,	1.661,	1.986,	2.367,	2.629,	3.179  },
123/* 95. */	{	1.291,	1.661,	1.985,	2.366,	2.629,	3.178  },
124/* 96. */	{	1.290,	1.661,	1.985,	2.366,	2.628,	3.177  },
125/* 97. */	{	1.290,	1.661,	1.985,	2.365,	2.627,	3.176  },
126/* 98. */	{	1.290,	1.661,	1.984,	2.365,	2.627,	3.175  },
127/* 99. */	{	1.290,	1.660,	1.984,	2.365,	2.626,	3.175  },
128/* 100. */	{	1.290,	1.660,	1.984,	2.364,	2.626,	3.174  }
129};
130
131#define	MAX_DS	8
132static char symbol[MAX_DS] = { ' ', 'x', '+', '*', '%', '#', '@', 'O' };
133
134TAILQ_HEAD(pointlist, point);
135
136struct dataset {
137	char *name;
138	struct pointlist list;
139	double sy, syy;
140	int n;
141};
142
143static struct dataset *
144NewSet(void)
145{
146	struct dataset *ds;
147
148	ds = calloc(1, sizeof *ds);
149	TAILQ_INIT(&ds->list);
150	return(ds);
151}
152
153struct point {
154	TAILQ_ENTRY(point)	list;
155	double			val;
156};
157
158static void
159AddPoint(struct dataset *ds, double a)
160{
161	struct point *pp, *pp2;
162
163	pp = calloc(1, sizeof *pp);
164	pp->val = a;
165
166	ds->n++;
167	ds->sy += a;
168	ds->syy += a * a;
169	if (TAILQ_EMPTY(&ds->list)) {
170		TAILQ_INSERT_HEAD(&ds->list, pp, list);
171		return;
172	}
173	TAILQ_FOREACH(pp2, &ds->list, list) {
174		if (pp->val < pp2->val) {
175			TAILQ_INSERT_BEFORE(pp2, pp, list);
176			return;
177		}
178	}
179	TAILQ_INSERT_TAIL(&ds->list, pp, list);
180}
181
182static double
183Min(struct dataset *ds)
184{
185
186	return (TAILQ_FIRST(&ds->list)->val);
187}
188
189static double
190Max(struct dataset *ds)
191{
192
193	return(TAILQ_LAST(&ds->list, pointlist)->val);
194}
195
196static double
197Avg(struct dataset *ds)
198{
199
200	return(ds->sy / ds->n);
201}
202
203static double
204Median(struct dataset *ds)
205{
206	int even, i;
207	struct point *p1, *p2;
208
209	if ((ds->n % 2) == 1) {
210		i = (ds->n / 2) + 1;
211		even = 0;
212	} else {
213		i = ds->n / 2;
214		even = 1;
215	}
216	TAILQ_FOREACH(p1, &ds->list, list) {
217		--i;
218		if (i == 0)
219			break;
220	}
221	if (even) {
222		p2 = TAILQ_NEXT(p1, list);
223		return ((p2->val + p1->val) / 2);
224	}
225	return (p1->val);
226}
227
228static double
229Var(struct dataset *ds)
230{
231
232	return (ds->syy - ds->sy * ds->sy / ds->n) / (ds->n - 1.0);
233}
234
235static double
236Stddev(struct dataset *ds)
237{
238
239	return sqrt(Var(ds));
240}
241
242static void
243VitalsHead(void)
244{
245
246	printf("    N           Min           Max        Median           Avg        Stddev\n");
247}
248
249static void
250Vitals(struct dataset *ds, int flag)
251{
252	double a;
253
254	printf("%c %3d %13.8g %13.8g %13.8g %13.8g %13.8g", symbol[flag],
255	    ds->n, Min(ds), Max(ds), Median(ds), Avg(ds), Stddev(ds));
256	printf("\n");
257}
258
259static void
260Relative(struct dataset *ds, struct dataset *rs, int confidx)
261{
262	double spool, s, d, e, t;
263	int i, c;
264
265	i = ds->n + rs->n - 2;
266	if (i > NSTUDENT)
267		t = student[0][confidx];
268	else
269		t = student[i][confidx];
270	spool = (ds->n - 1) * Var(ds) + (rs->n - 1) * Var(rs);
271	spool /= ds->n + rs->n - 2;
272	spool = sqrt(spool);
273	s = spool * sqrt(1.0 / ds->n + 1.0 / rs->n);
274	d = Avg(ds) - Avg(rs);
275	e = t * s;
276
277	if (fabs(d) > e) {
278
279		printf("Difference at %.1f%% confidence\n", studentpct[confidx]);
280		printf("	%g +/- %g\n", d, e);
281		printf("	%g%% +/- %g%%\n", d * 100 / Avg(rs), e * 100 / Avg(rs));
282		printf("	(Student's t, pooled s = %g)\n", spool);
283	} else {
284		printf("No difference proven at %.1f%% confidence\n",
285		    studentpct[confidx]);
286	}
287}
288
289struct plot {
290	double		min;
291	double		max;
292	double		span;
293	int		width;
294
295	double		x0, dx;
296	int		height;
297	char		*data;
298	char		**bar;
299	int		separate_bars;
300	int		num_datasets;
301};
302
303static struct plot plot;
304
305static void
306SetupPlot(int width, int separate, int num_datasets)
307{
308	struct plot *pl;
309
310	pl = &plot;
311	pl->width = width;
312	pl->height = 0;
313	pl->data = NULL;
314	pl->bar = NULL;
315	pl->separate_bars = separate;
316	pl->num_datasets = num_datasets;
317	pl->min = 999e99;
318	pl->max = -999e99;
319}
320
321static void
322AdjPlot(double a)
323{
324	struct plot *pl;
325
326	pl = &plot;
327	if (a < pl->min)
328		pl->min = a;
329	if (a > pl->max)
330		pl->max = a;
331	pl->span = pl->max - pl->min;
332	pl->dx = pl->span / (pl->width - 1.0);
333	pl->x0 = pl->min - .5 * pl->dx;
334}
335
336static void
337DimPlot(struct dataset *ds)
338{
339	AdjPlot(Min(ds));
340	AdjPlot(Max(ds));
341	AdjPlot(Avg(ds) - Stddev(ds));
342	AdjPlot(Avg(ds) + Stddev(ds));
343}
344
345static void
346PlotSet(struct dataset *ds, int val)
347{
348	struct plot *pl;
349	struct point *pp;
350	int i, j, m, x;
351	int bar;
352
353	pl = &plot;
354	if (pl->span == 0)
355		return;
356
357	if (pl->separate_bars)
358		bar = val-1;
359	else
360		bar = 0;
361
362	if (pl->bar == NULL) {
363		pl->bar = malloc(sizeof(char *) * pl->num_datasets);
364		memset(pl->bar, 0, sizeof(char*) * pl->num_datasets);
365	}
366	if (pl->bar[bar] == NULL) {
367		pl->bar[bar] = malloc(pl->width);
368		memset(pl->bar[bar], 0, pl->width);
369	}
370
371	m = 1;
372	i = -1;
373	j = 0;
374	TAILQ_FOREACH(pp, &ds->list, list) {
375		x = (pp->val - pl->x0) / pl->dx;
376		if (x == i) {
377			j++;
378			if (j > m)
379				m = j;
380		} else {
381			j = 1;
382			i = x;
383		}
384	}
385	m += 1;
386	if (m > pl->height) {
387		pl->data = realloc(pl->data, pl->width * m);
388		memset(pl->data + pl->height * pl->width, 0,
389		    (m - pl->height) * pl->width);
390	}
391	pl->height = m;
392	i = -1;
393	TAILQ_FOREACH(pp, &ds->list, list) {
394		x = (pp->val - pl->x0) / pl->dx;
395		if (x == i) {
396			j++;
397		} else {
398			j = 1;
399			i = x;
400		}
401		pl->data[j * pl->width + x] |= val;
402	}
403	if (!isnan(Stddev(ds))) {
404		x = ((Avg(ds) - Stddev(ds)) - pl->x0) / pl->dx;
405		m = ((Avg(ds) + Stddev(ds)) - pl->x0) / pl->dx;
406		pl->bar[bar][m] = '|';
407		pl->bar[bar][x] = '|';
408		for (i = x + 1; i < m; i++)
409			if (pl->bar[bar][i] == 0)
410				pl->bar[bar][i] = '_';
411	}
412	x = (Median(ds) - pl->x0) / pl->dx;
413	pl->bar[bar][x] = 'M';
414	x = (Avg(ds) - pl->x0) / pl->dx;
415	pl->bar[bar][x] = 'A';
416}
417
418static void
419DumpPlot(void)
420{
421	struct plot *pl;
422	int i, j, k;
423
424	pl = &plot;
425	if (pl->span == 0) {
426		printf("[no plot, span is zero width]\n");
427		return;
428	}
429
430	putchar('+');
431	for (i = 0; i < pl->width; i++)
432		putchar('-');
433	putchar('+');
434	putchar('\n');
435	for (i = 1; i < pl->height; i++) {
436		putchar('|');
437		for (j = 0; j < pl->width; j++) {
438			k = pl->data[(pl->height - i) * pl->width + j];
439			if (k >= 0 && k < MAX_DS)
440				putchar(symbol[k]);
441			else
442				printf("[%02x]", k);
443		}
444		putchar('|');
445		putchar('\n');
446	}
447	for (i = 0; i < pl->num_datasets; i++) {
448		if (pl->bar[i] == NULL)
449			continue;
450		putchar('|');
451		for (j = 0; j < pl->width; j++) {
452			k = pl->bar[i][j];
453			if (k == 0)
454				k = ' ';
455			putchar(k);
456		}
457		putchar('|');
458		putchar('\n');
459	}
460	putchar('+');
461	for (i = 0; i < pl->width; i++)
462		putchar('-');
463	putchar('+');
464	putchar('\n');
465}
466
467
468static struct dataset *
469ReadSet(char *n, int column, char *delim)
470{
471	FILE *f;
472	char buf[BUFSIZ], *p, *t;
473	struct dataset *s;
474	double d;
475	int line;
476	int i;
477
478	if (n == NULL) {
479		f = stdin;
480		n = "<stdin>";
481	} else if (!strcmp(n, "-")) {
482		f = stdin;
483		n = "<stdin>";
484	} else {
485		f = fopen(n, "r");
486	}
487	if (f == NULL)
488		err(1, "Cannot open %s", n);
489	s = NewSet();
490	s->name = strdup(n);
491	line = 0;
492	while (fgets(buf, sizeof buf, f) != NULL) {
493		line++;
494
495		i = strlen(buf);
496		if (buf[i-1] == '\n')
497			buf[i-1] = '\0';
498		for (i = 1, t = strtok(buf, delim);
499		     t != NULL && *t != '#';
500		     i++, t = strtok(NULL, delim)) {
501			if (i == column)
502				break;
503		}
504		if (t == NULL || *t == '#')
505			continue;
506
507		d = strtod(t, &p);
508		if (p != NULL && *p != '\0')
509			err(2, "Invalid data on line %d in %s\n", line, n);
510		if (*buf != '\0')
511			AddPoint(s, d);
512	}
513	fclose(f);
514	if (s->n < 3) {
515		fprintf(stderr,
516		    "Dataset %s must contain at least 3 data points\n", n);
517		exit (2);
518	}
519	return (s);
520}
521
522static void
523usage(char const *whine)
524{
525	int i;
526
527	fprintf(stderr, "%s\n", whine);
528	fprintf(stderr,
529	    "Usage: ministat [-C column] [-c confidence] [-d delimiter(s)] [-ns] [-w width] [file [file ...]]\n");
530	fprintf(stderr, "\tconfidence = {");
531	for (i = 0; i < NCONF; i++) {
532		fprintf(stderr, "%s%g%%",
533		    i ? ", " : "",
534		    studentpct[i]);
535	}
536	fprintf(stderr, "}\n");
537	fprintf(stderr, "\t-C : column number to extract (starts and defaults to 1)\n");
538	fprintf(stderr, "\t-d : delimiter(s) string, default to \" \\t\"\n");
539	fprintf(stderr, "\t-n : print summary statistics only, no graph/test\n");
540	fprintf(stderr, "\t-s : print avg/median/stddev bars on separate lines\n");
541	fprintf(stderr, "\t-w : width of graph/test output (default 74 or terminal width)\n");
542	exit (2);
543}
544
545int
546main(int argc, char **argv)
547{
548	struct dataset *ds[7];
549	int nds;
550	double a;
551	char *delim = " \t";
552	char *p;
553	int c, i, ci;
554	int column = 1;
555	int flag_s = 0;
556	int flag_n = 0;
557	int termwidth = 74;
558
559	if (isatty(STDOUT_FILENO)) {
560		struct winsize wsz;
561
562		if ((p = getenv("COLUMNS")) != NULL && *p != '\0')
563			termwidth = atoi(p);
564		else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsz) != -1 &&
565			 wsz.ws_col > 0)
566			termwidth = wsz.ws_col - 2;
567	}
568
569	ci = -1;
570	while ((c = getopt(argc, argv, "C:c:d:snw:")) != -1)
571		switch (c) {
572		case 'C':
573			column = strtol(optarg, &p, 10);
574			if (p != NULL && *p != '\0')
575				usage("Invalid column number.");
576			if (column <= 0)
577				usage("Column number should be positive.");
578			break;
579		case 'c':
580			a = strtod(optarg, &p);
581			if (p != NULL && *p != '\0')
582				usage("Not a floating point number");
583			for (i = 0; i < NCONF; i++)
584				if (a == studentpct[i])
585					ci = i;
586			if (ci == -1)
587				usage("No support for confidence level");
588			break;
589		case 'd':
590			if (*optarg == '\0')
591				usage("Can't use empty delimiter string");
592			delim = optarg;
593			break;
594		case 'n':
595			flag_n = 1;
596			break;
597		case 's':
598			flag_s = 1;
599			break;
600		case 'w':
601			termwidth = strtol(optarg, &p, 10);
602			if (p != NULL && *p != '\0')
603				usage("Invalid width, not a number.");
604			if (termwidth < 0)
605				usage("Unable to move beyond left margin.");
606			break;
607		default:
608			usage("Unknown option");
609			break;
610		}
611	if (ci == -1)
612		ci = 2;
613	argc -= optind;
614	argv += optind;
615
616	if (argc == 0) {
617		ds[0] = ReadSet("-", column, delim);
618		nds = 1;
619	} else {
620		if (argc > (MAX_DS - 1))
621			usage("Too many datasets.");
622		nds = argc;
623		for (i = 0; i < nds; i++)
624			ds[i] = ReadSet(argv[i], column, delim);
625	}
626
627	for (i = 0; i < nds; i++)
628		printf("%c %s\n", symbol[i+1], ds[i]->name);
629
630	if (!flag_n) {
631		SetupPlot(termwidth, flag_s, nds);
632		for (i = 0; i < nds; i++)
633			DimPlot(ds[i]);
634		for (i = 0; i < nds; i++)
635			PlotSet(ds[i], i + 1);
636		DumpPlot();
637	}
638	VitalsHead();
639	Vitals(ds[0], 1);
640	for (i = 1; i < nds; i++) {
641		Vitals(ds[i], i + 1);
642		if (!flag_n)
643			Relative(ds[i], ds[0], ci);
644	}
645	exit(0);
646}
647