chiark / gitweb /
close idle connections and spot unresponsive ones
[innduct.git] / backends / ninpaths.c
1 /*  $Id: ninpaths.c 6362 2003-05-31 18:35:04Z rra $
2 **
3 **  New inpaths reporting program.
4 **
5 **  Idea, data structures and part of code based on inpaths 2.5
6 **  by Brian Reid, Landon Curt Noll
7 **
8 **  This version written by Olaf Titz, Feb. 1997.  Public domain.
9 */
10
11 #include "config.h"
12 #include "clibrary.h"
13 #include <ctype.h>
14 #include <time.h>
15
16 #define VERSION "3.1.1"
17
18 #define MAXFNAME 1024   /* max length of file name */
19 #define MAXLINE 1024    /* max length of Path line */
20 #define HASH_TBL 65536  /* hash table size (power of two) */
21 #define MAXHOST 128     /* max length of host name */
22 #define HOSTF "%127s"   /* scanf format for host name */
23 #define RECLINE 120     /* dump file line length softlimit */
24
25 /* structure used to tally the traffic between two hosts */
26 struct trec {
27     struct trec *rlink;         /* next in chain */
28     struct nrec *linkid;        /* pointer to... */
29     long tally;                 /* count */
30 };
31
32 /* structure to hold the information about a host */
33 struct nrec {
34     struct nrec *link;          /* next in chain */
35     struct trec *rlink;         /* start of trec chain */
36     char *id;                   /* host name */
37     long no;                    /* identificator for dump file */
38     long sentto;                /* tally of articles sent from here */
39 };
40
41 struct nrec *hosthash[HASH_TBL];
42
43 time_t starttime;       /* Start time */
44 double atimes=0.0;      /* Sum of articles times wrt. starttime */
45 long total=0,           /* Total articles processed */
46      sites=0;           /* Total sites known */
47
48 /* malloc and warn if out of mem */
49 void *
50 wmalloc(size_t s)
51 {
52     void *p=malloc(s);
53     if (!p)
54         fprintf(stderr, "warning: out of memory\n");
55     return p;
56 }
57
58 /* Hash function due to Glenn Fowler / Landon Curt Noll / Phong Vo */
59 int
60 hash(const char *str)
61 {
62     unsigned long val;
63     unsigned long c;
64
65     for (val = 0; (c=(unsigned long)(*str)); ++str) {
66         val *= 16777619;        /* magic */
67         val ^= c;               /* more magic */
68     }
69     return (int)(val & (unsigned long)(HASH_TBL-1));
70 }
71
72 /* Look up a host in the hash table. Add if necessary. */
73 struct nrec *
74 hhost(const char *n)
75 {
76     struct nrec *h;
77     int i=hash(n);
78
79     for (h=hosthash[i]; h; h=h->link)
80         if (!strcmp(n, h->id))
81             return h;
82     /* not there - allocate */
83     h=wmalloc(sizeof(struct nrec));
84     if (!h)
85         return NULL;
86     h->id=strdup(n);
87     if (!h->id) {
88         free(h); return NULL;
89     }
90     h->link=hosthash[i];
91     h->rlink=NULL;
92     h->no=h->sentto=0;
93     hosthash[i]=h;
94     sites++;
95     return h;
96 }
97
98 /* Look up a tally record between hosts. Add if necessary. */
99 struct trec *
100 tallyrec(struct nrec *r, struct nrec *h)
101 {
102     struct trec *t;
103     for (t=r->rlink; t; t=t->rlink)
104         if (t->linkid==h)
105             return t;
106     t=wmalloc(sizeof(struct trec));
107     if (!t)
108         return NULL;
109     t->rlink=r->rlink;
110     t->linkid=h;
111     t->tally=0;
112     r->rlink=t;
113     return t;
114 }
115
116
117 /* Dump file format:
118    "!!NINP" <version> <starttime> <endtime> <sites> <total> <avgtime> "\n"
119    followed by <sites> S-records,
120    "!!NLREC\n"
121    [3.0]
122        followed by max. <sites>^2 L-records
123    [3.1]
124        followed by max. <sites> L-records
125    "!!NEND" <nlrecs> "\n"
126    starttime, endtime, avgtime as UNIX date
127    the records are separated by space or \n
128    an S-record is "site count"
129    [3.0]
130        an L-record is "sitea!siteb!count"
131    [3.1]
132        an L-record is ":sitea" { "!siteb,count" }...
133        ",count" omitted if count==1
134    where sitea and siteb are numbers of the S-records starting at 0
135 */
136
137 int
138 writedump(FILE *f)
139 {
140     int i, j;
141     long n;
142     struct nrec *h;
143     struct trec *t;
144
145     if (!total) {
146         return -1;
147     }
148     fprintf(f, "!!NINP " VERSION " %lu %lu %ld %ld %ld\n",
149             (unsigned long) starttime, (unsigned long) time(NULL), sites,
150             total, (long)(atimes/total)+starttime);
151     n=j=0;
152     /* write the S-records (hosts), numbering them in the process */
153     for (i=0; i<HASH_TBL; ++i)
154         for (h=hosthash[i]; h; h=h->link) {
155             h->no=n++;
156             j+=fprintf(f, "%s %ld", h->id, h->sentto);
157             if (j>RECLINE) {
158                 j=0;
159                 fprintf(f, "\n");
160             } else {
161                 fprintf(f, " ");
162             }
163         }
164     if (n!=sites)
165         fprintf(stderr, "internal error: sites=%ld, dumped=%ld\n", sites, n);
166
167     fprintf(f, "\n!!NLREC\n");
168
169     n=j=0;
170     /* write the L-records (links) */
171     for (i=0; i<HASH_TBL; ++i)
172         for (h=hosthash[i]; h; h=h->link)
173             if ((t=h->rlink)) {
174                 j+=fprintf(f, ":%ld", h->no);
175                 for (; t; t=t->rlink) {
176                     j+=fprintf(f, "!%ld", t->linkid->no);
177                     if (t->tally>1)
178                         j+=fprintf(f, ",%ld", t->tally);
179                     n++;
180                 }
181                 if (j>RECLINE) {
182                     j=0;
183                     fprintf(f, "\n");
184                 }
185             }
186     fprintf(f, "\n!!NLEND %ld\n", n);
187     return 0;
188 }
189
190 /* Write dump to a named file. Substitute %d in file name with system time. */
191
192 void
193 writedumpfile(const char *n)
194 {
195     char buf[MAXFNAME];
196     FILE *d;
197
198     if (n[0]=='-' && n[1]=='\0') {
199         writedump(stdout);
200         return;
201     }
202     snprintf(buf, sizeof(buf), n, time(0));
203     d=fopen(buf, "w");
204     if (d) {
205         if (writedump(d)<0)
206             unlink(buf);
207     } else {
208         perror("writedumpfile: fopen");
209     }
210 }
211
212 /* Read a dump file. */
213
214 int
215 readdump(FILE *f)
216 {
217     int a, b;
218     long i, m, l;
219     unsigned long st, et, at;
220     long sit, tot;
221     struct nrec **n;
222     struct trec *t;
223     char c[MAXHOST];
224     char v[16];
225
226     #define formerr(i) {\
227         fprintf(stderr, "dump file format error #%d\n", (i)); return -1; }
228
229     if (fscanf(f, "!!NINP %15s %lu %lu %ld %ld %lu\n",
230                v, &st, &et, &sit, &tot, &at)!=6)
231         formerr(0);
232
233     n=calloc(sit, sizeof(struct nrec *));
234     if (!n) {
235         fprintf(stderr, "error: out of memory\n");
236         return -1;
237     }
238     for (i=0; i<sit; i++) {
239         if (fscanf(f, HOSTF " %ld ", c, &l)!=2) {
240             fprintf(stderr, "read %ld ", i);
241             formerr(1);
242         }
243         n[i]=hhost(c);
244         if (!n[i])
245             return -1;
246         n[i]->sentto+=l;
247     }
248     if ((fscanf(f, HOSTF "\n", c)!=1) ||
249         strcmp(c, "!!NLREC"))
250         formerr(2);
251     m=0;
252     if (!strncmp(v, "3.0", 3)) {
253         /* Read 3.0-format L-records */
254         while (fscanf(f, "%d!%d!%ld ", &a, &b, &l)==3) {
255             t=tallyrec(n[a], n[b]);
256             if (!t)
257                 return -1;
258             t->tally+=l;
259             ++m;
260         }
261     } else if (!strncmp(v, "3.1", 3)) {
262         /* Read L-records */
263         while (fscanf(f, " :%d", &a)==1) {
264             while ((i=fscanf(f, "!%d,%ld", &b, &l))>0) {
265                 t=tallyrec(n[a], n[b]);
266                 if (i<2)
267                     l=1;
268                 if (!t)
269                     return -1;
270                 t->tally+=l;
271                 ++m;
272             }
273         }
274     } else {
275         fprintf(stderr, "version %s ", v);
276         formerr(9);
277     }
278     if ((fscanf(f, "!!NLEND %ld\n", &i)!=1)
279         || (i!=m))
280         formerr(3);
281 #ifdef DEBUG
282     fprintf(stderr, " dumped start %s   total=%ld atimes=%ld (%ld)\n",
283             ctime(&st), tot, at, at-st);
284 #endif
285     /* Adjust the time average and total count */
286     if ((unsigned long) starttime > st) {
287         atimes+=(double)total*(starttime-st);
288         starttime=st;
289     }
290     atimes+=(double)tot*(at-starttime);
291     total+=tot;
292 #ifdef DEBUG
293     fprintf(stderr, " current start %s   total=%ld atimes=%.0f (%.0f)\n\n",
294             ctime(&starttime), total, atimes, atimes/total);
295 #endif
296     free(n);
297     return 0;
298 }
299
300 /* Read dump from a file. */
301
302 int
303 readdumpfile(const char *n)
304 {
305     FILE *d;
306     int i;
307
308     if (n[0]=='-' && n[1]=='\0')
309         return readdump(stdin);
310
311     d=fopen(n, "r");
312     if (d) {
313         /* fprintf(stderr, "Reading dump file %s\n", n); */
314         i=readdump(d);
315         fclose(d);
316         return i;
317     } else {
318         perror("readdumpfile: fopen");
319         return -1;
320     }
321 }
322
323
324 /* Process a Path line. */
325
326 int
327 pathline(char *c)
328 {
329     char *c2;
330     struct nrec *h, *r;
331     struct trec *t;
332
333     r=NULL;
334     while (*c) {
335         for (c2=c; *c2 && *c2!='!'; c2++);
336         if (c2-c>MAXHOST-1)
337             /* looks broken, dont bother with rest */
338             return 0;
339         while (*c2=='!')
340             *c2++='\0'; /* skip "!!" too */
341         h=hhost(c);
342         if (!h)
343             return -1;
344         ++h->sentto;
345         if (r && r!=h) {
346             t=tallyrec(r, h);
347             if (!t)
348                 return -1;
349             ++t->tally;
350         }
351         c=c2;
352         r=h;
353     }
354     return 0;
355 }
356
357 /* Take Path lines from file (stdin used here). */
358
359 void
360 procpaths(FILE *f)
361 {
362     char buf[MAXLINE];
363     char *c, *ce;
364     int v=1; /* current line is valid */
365
366     while (fgets(buf, sizeof(buf), f)) {
367         c=buf;
368         if (!strncmp(c, "Path: ", 6))
369             c+=6;
370         /* find end of line. Some broken newsreaders preload Path with
371            a name containing spaces. Chop off those entries. */
372         for (ce=c; *ce && !CTYPE(isspace, *ce); ++ce);
373         if (!*ce) {
374             /* bogus line */
375             v=0;
376         } else if (v) {
377             /* valid line */
378             for (; ce>c && *ce!='!'; --ce); /* ignore last element */
379             *ce='\0';
380             if (pathline(c)<0) /* process it */
381                 /* If an out of memory condition occurs while reading
382                    Path lines, stop reading and write the dump so far.
383                    INN will restart a fresh ninpaths. */
384                 return;
385             /* update average age and grand total */
386             atimes+=(time(0)-starttime);
387             ++total;
388         } else {
389             /* next line is valid */
390             v=1;
391         }
392     }
393 }
394
395 /* Output a report suitable for mailing. From inpaths 2.5 */
396
397 void
398 report(const char *hostname, int verbose)
399 {
400     double avgAge;
401     int i, columns, needHost;
402     long nhosts=0, nlinks=0;
403     struct nrec *list, *relay;
404     struct trec *rlist;
405     char hostString[MAXHOST];
406     time_t t0=time(0);
407
408     if (!total) {
409         fprintf(stderr, "report: no traffic\n");
410         return;
411     }
412     /* mark own site to not report it */
413     list=hhost(hostname);
414     if (list)
415         list->id[0]='\0';
416
417     avgAge=((double)t0 - (atimes/total + (double)starttime)) /86400.0;
418     printf("ZCZC begin inhosts %s %s %d %ld %3.1f\n",
419         VERSION,hostname,verbose,total,avgAge);
420     for (i=0; i<HASH_TBL-1; i++) {
421         list = hosthash[i];
422         while (list != NULL) {
423             if (list->id[0] != 0 && list->rlink != NULL) {
424                 if (verbose > 0 || (100*list->sentto > total))
425                     printf("%ld\t%s\n",list->sentto, list->id);
426             }
427             list = list->link;
428         }
429     }
430     printf("ZCZC end inhosts %s\n",hostname);
431
432     printf("ZCZC begin inpaths %s %s %d %ld %3.1f\n",
433         VERSION,hostname,verbose,total,avgAge);
434     for (i=0; i<HASH_TBL-1; i++) {
435         list = hosthash[i];
436         while (list != NULL) {
437             if (verbose > 1 || (100*list->sentto > total)) {
438                 if (list->id[0] != 0 && list->rlink != NULL) {
439                     columns = 3+strlen(list->id);
440                     snprintf(hostString,sizeof(hostString),"%s H ",list->id);
441                     needHost = 1;
442                     rlist = list->rlink;
443                     while (rlist != NULL) {
444                         if (
445                              (100*rlist->tally > total)
446                           || ((verbose > 1)&&(5000*rlist->tally>total))
447                            ) {
448                             if (needHost) printf("%s",hostString);
449                             needHost = 0;
450                             relay = rlist->linkid;
451                             if (relay->id[0] != 0) {
452                               if (columns > 70) {
453                                 printf("\n%s",hostString);
454                                 columns = 3+strlen(list->id);
455                               }
456                               printf("%ld Z %s U ", rlist->tally, relay->id);
457                               columns += 9+strlen(relay->id);
458                             }
459                         }
460                         rlist = rlist->rlink;
461                         ++nlinks;
462                     }
463                     if (!needHost) printf("\n");
464                 }
465             }
466             list = list->link;
467             ++nhosts;
468         }
469     }
470     printf("ZCZC end inpaths %s\n",hostname);
471 #ifdef DEBUG
472     fprintf(stderr, "Processed %ld hosts, %ld links.\n", nhosts, nlinks);
473 #endif
474 }
475
476 extern char *optarg;
477
478 int
479 main(int argc, char *argv[])
480 {
481     int i;
482     int pf=0, vf=2;
483     char *df=NULL, *rf=NULL;
484
485     for (i=0; i<HASH_TBL; i++)
486         hosthash[i]=NULL;
487     starttime=time(0);
488
489     while ((i=getopt(argc, argv, "pd:u:r:v:"))!=EOF)
490         switch (i) {
491         case 'p':
492             /* read Path lines from stdin */
493             pf=1; break;
494         case 'd':
495             /* make a dump to the named file */
496             df=optarg; break;
497         case 'u':
498             /* read dump from the named file */
499             if (readdumpfile(optarg)<0)
500                 exit(1);
501             break;
502         case 'r':
503             /* make a report for the named site */
504             rf=optarg; break;
505         case 'v':
506             /* control report verbosity */
507             vf=atoi(optarg); break;
508         default:
509             fprintf(stderr, "unknown option %c\n", i);
510         }
511
512     if (pf)
513         procpaths(stdin);
514     if (df)
515         writedumpfile(df);
516     if (rf)
517         report(rf, vf);
518     return 0;
519 }