]> jfr.im git - irc/quakenet/newserv.git/blob - chanstats/chanstats.c
Initial Import
[irc/quakenet/newserv.git] / chanstats / chanstats.c
1
2 #include "chanstats.h"
3 #include "../channel/channel.h"
4 #include "../lib/sstring.h"
5 #include "../lib/splitline.h"
6 #include "../irc/irc.h"
7 #include "../core/schedule.h"
8 #include "../core/error.h"
9 #include "../control/control.h"
10 #include "../nick/nick.h"
11 #include "../lib/irc_string.h"
12
13 #include <stdio.h>
14 #include <string.h>
15
16 int csext;
17 int sampleindex;
18 time_t lastsample;
19 time_t lastday;
20 int uponehour;
21 int failedinit;
22 time_t lastsave;
23
24 unsigned int lastdaysamples[HISTORYDAYS];
25 unsigned int todaysamples;
26
27 void doupdate(void *arg);
28 void updatechanstats(chanindex *cip, time_t now);
29 void rotatechanstats();
30 void savechanstats();
31 void loadchanstats();
32 int dochanstats(void *source, int argc, char **argv);
33 int doexpirecheck(void *source, int cargc, char **cargv);
34 int douserhistogram(void *source, int cargc, char **cargv);
35 int dochanhistogram(void *source, int cargc, char **cargv);
36 void dohist_now(int *data, float *bounds, int cats, nick *np);
37 void dohist_today(int *data, float *bounds, int cats);
38 void dohist_days(int *data, float *bounds, int cats, int days);
39 void dohist_namelen(int *data, float *bounds, int cats);
40
41 void _init() {
42 time_t now,when;
43
44 csext=registerchanext("chanstats");
45 if (csext<0) {
46 /* PANIC PANIC PANIC PANIC */
47 Error("chanstats",ERR_ERROR,"Couldn't register channel extension");
48 failedinit=1;
49 } else {
50 initchanstatsalloc();
51 lastday=(getnettime()/(24*3600));
52 uponehour=0;
53 sampleindex=-1;
54 memset(lastdaysamples,0,sizeof(lastdaysamples));
55 todaysamples=0;
56 failedinit=0;
57
58 loadchanstats();
59
60 /* Work out when to take the next sample */
61 now=getnettime();
62 if (now < lastsample) {
63 Error("chanstats",ERR_WARNING,"Last sample time in future (%d > %d)",lastsample,now);
64 when=now;
65 } else if (now<(lastsample+SAMPLEINTERVAL)) {
66 lastday=lastsample/(24*3600);
67 when=lastsample+SAMPLEINTERVAL;
68 } else {
69 if ((lastsample/(24*3600))<lastday) {
70 rotatechanstats();
71 }
72 when=now;
73 }
74
75 lastsave=now;
76 registercontrolcmd("chanstats",10,1,&dochanstats);
77 registercontrolcmd("channelhistogram",10,13,&dochanhistogram);
78 registercontrolcmd("userhistogram",10,1,&douserhistogram);
79 registercontrolcmd("expirecheck",10,1,&doexpirecheck);
80 schedulerecurring(when,0,SAMPLEINTERVAL,&doupdate,NULL);
81 }
82 }
83
84 void _fini() {
85 if (failedinit==0) {
86 savechanstats();
87 deleteschedule(NULL,&doupdate,NULL);
88 deregistercontrolcmd("chanstats",&dochanstats);
89 deregistercontrolcmd("channelhistogram",&dochanhistogram);
90 deregistercontrolcmd("userhistogram",&douserhistogram);
91 deregistercontrolcmd("expirecheck",&doexpirecheck);
92 releasechanext(csext);
93 cstsfreeall();
94 }
95 }
96
97 /*
98 * doupdate:
99 * This is the core function which is scheduled.
100 */
101
102 void doupdate(void *arg) {
103 int i;
104 time_t now;
105 chanindex *cip;
106
107 /* Get the current time and move the history pointer along one and count the sample */
108 now=getnettime();
109
110 Error("chanstats",ERR_INFO,"Running chanstats update.");
111
112 if (now/(24*3600) > lastday) {
113 rotatechanstats();
114 lastday=(now/(24*3600));
115 }
116
117 todaysamples++;
118 sampleindex++;
119 if (sampleindex==SAMPLEHISTORY) {
120 sampleindex=0;
121 uponehour=1;
122 }
123
124 /*
125 * Loop over all chans doing update
126 */
127
128 for(i=0;i<CHANNELHASHSIZE;i++) {
129 for (cip=chantable[i];cip;cip=cip->next) {
130 if ((cip->channel!=NULL) || (cip->exts[csext]!=NULL)) {
131 updatechanstats(cip,now);
132 }
133 }
134 }
135
136 lastsample=now;
137 if (now-lastsave > SAVEINTERVAL) {
138 savechanstats();
139 lastsave=now;
140 }
141 }
142
143 void updatechanstats(chanindex *cip, time_t now) {
144 chanstats *csp;
145 int currentusers;
146
147 csp=cip->exts[csext];
148 if (csp==NULL) {
149 csp=cip->exts[csext]=getchanstats();
150 memset(csp,0,sizeof(chanstats));
151 }
152
153 if (cip->channel!=NULL) {
154 currentusers=countuniquehosts(cip->channel);
155 csp->todaysamples++;
156 } else {
157 currentusers=0;
158 }
159
160 csp->lastsamples[sampleindex]=currentusers;
161 csp->lastsampled=now;
162 csp->todayusers+=currentusers;
163
164 if (currentusers > csp->todaymax) {
165 csp->todaymax=currentusers;
166 }
167 }
168
169 void rotatechanstats() {
170 int i,j,k;
171 chanindex *cip,*ncip;
172 chanstats *csp;
173
174 for (i=0;i<CHANNELHASHSIZE;i++) {
175 for (cip=chantable[i];cip;cip=ncip) {
176 ncip=cip->next;
177
178 if ((csp=cip->exts[csext])==NULL) {
179 continue;
180 }
181
182 if (csp->todaysamples==0) {
183 /* No samples today, might want to delete */
184 k=0;
185 for(j=0;j<HISTORYDAYS;j++) {
186 k+=csp->lastdays[j];
187 }
188 if (k<50) {
189 freechanstats(csp);
190 cip->exts[csext]=NULL;
191 releasechanindex(cip);
192 continue;
193 }
194 }
195
196 /* Move the samples along one */
197 memmove(&(csp->lastdays[1]),&(csp->lastdays[0]),(HISTORYDAYS-1)*sizeof(short));
198 memmove(&(csp->lastdaysamples[1]),&(csp->lastdaysamples[0]),(HISTORYDAYS-1)*sizeof(char));
199 memmove(&(csp->lastmax[1]),&(csp->lastmax[0]),(HISTORYDAYS-1)*sizeof(unsigned short));
200
201 csp->lastdaysamples[0]=csp->todaysamples;
202 csp->lastdays[0]=(csp->todayusers * 10)/todaysamples;
203 csp->lastmax[0]=csp->todaymax;
204
205 csp->todaysamples=0;
206 csp->todayusers=0;
207 if (cip->channel) {
208 csp->todaymax=cip->channel->users->totalusers;
209 } else {
210 csp->todaymax=0;
211 }
212 }
213 }
214
215 memmove(&lastdaysamples[1],&lastdaysamples[0],(HISTORYDAYS-1)*sizeof(unsigned int));
216 lastdaysamples[0]=todaysamples;
217 todaysamples=0;
218 }
219
220 void savechanstats() {
221 FILE *fp;
222 int i,j;
223 chanindex *cip;
224 chanstats *chp;
225
226 if ((fp=fopen("chanstats","w"))==NULL) {
227 return;
228 }
229
230 /* header: samples for today + last HISTORYDAYS days */
231
232 fprintf(fp,"%lu %u",lastsample,todaysamples);
233 for(i=0;i<HISTORYDAYS;i++) {
234 fprintf(fp," %u",lastdaysamples[i]);
235 }
236 fprintf(fp,"\n");
237
238 /* body: channel, lastsample, samplestoday, sizetoday, <last sizes, last samples> */
239 for (i=0;i<CHANNELHASHSIZE;i++) {
240 for (cip=chantable[i];cip;cip=cip->next) {
241 if ((chp=cip->exts[csext])==NULL) {
242 continue;
243 }
244 fprintf(fp,"%s %lu %u %u %u",cip->name->content,chp->lastsampled,chp->todaysamples,chp->todayusers,chp->todaymax);
245
246 for (j=0;j<HISTORYDAYS;j++) {
247 fprintf(fp," %u %u %u",chp->lastdays[j],chp->lastdaysamples[j],chp->lastmax[j]);
248 }
249 fprintf(fp,"\n");
250 }
251 }
252
253 fclose(fp);
254 }
255
256 void loadchanstats() {
257 FILE *fp;
258 int i;
259 char inbuf[2048];
260 char *args[100];
261 unsigned int chancount=0;
262 chanstats *chp;
263 chanindex *cip;
264
265 if ((fp=fopen("chanstats","r"))==NULL) {
266 Error("chanstats",ERR_ERROR,"Unable to load channel stats file");
267 return;
268 }
269
270 fgets(inbuf,2048,fp);
271 if (feof(fp)) {
272 Error("chanstats",ERR_ERROR,"Corrupt channel stats file");
273 fclose(fp);
274 return;
275 }
276
277 if ((splitline(inbuf,args,50,0))!=(HISTORYDAYS+2)) {
278 Error("chanstats",ERR_ERROR,"Corrupt channel stats file");
279 fclose(fp);
280 return;
281 }
282
283 lastsample=strtol(args[0],NULL,10);
284 todaysamples=strtol(args[1],NULL,10);
285 for(i=0;i<HISTORYDAYS;i++) {
286 lastdaysamples[i]=strtol(args[i+2],NULL,10);
287 }
288
289 while (!feof(fp)) {
290 fgets(inbuf,2048,fp);
291 if (feof(fp)) {
292 break;
293 }
294
295 if ((splitline(inbuf,args,100,0))!=5+(HISTORYDAYS*3)) {
296 Error("chanstats",ERR_WARNING,"Corrupt channel stats line");
297 continue;
298 }
299
300 cip=findorcreatechanindex(args[0]);
301 if (cip->exts[csext]!=NULL) {
302 Error("chanstats",ERR_ERROR,"Duplicate stats entry for channel %s",args[0]);
303 continue;
304 }
305 cip->exts[csext]=chp=getchanstats();
306
307 chp->lastsampled=strtol(args[1],NULL,10);
308 chp->todaysamples=strtol(args[2],NULL,10);
309 chp->todayusers=strtol(args[3],NULL,10);
310 chp->todaymax=strtol(args[4],NULL,10);
311
312 for(i=0;i<HISTORYDAYS;i++) {
313 chp->lastdays[i]=strtol(args[5+(i*3)],NULL,10);
314 chp->lastdaysamples[i]=strtol(args[6+(i*3)],NULL,10);
315 chp->lastmax[i]=strtol(args[7+(i*3)],NULL,10);
316 }
317
318 chancount++;
319 }
320
321 Error("chanstats",ERR_INFO,"Loaded %u channels",chancount);
322 }
323
324 int dochanstats(void *source, int cargc, char **cargv) {
325 chanstats *csp;
326 chanindex *cip;
327 nick *sender=(nick *)source;
328 int i,j,k,l;
329 int tot,emp;
330 int themax;
331
332 if (cargc<1) {
333 controlreply(sender,"Usage: chanstats #channel");
334 return CMD_ERROR;
335 }
336
337 cip=findchanindex(cargv[0]);
338
339 if (cip==NULL) {
340 controlreply(sender,"Can't find any record of %s.",cargv[0]);
341 return CMD_ERROR;
342 }
343
344 csp=cip->exts[csext];
345
346 if (csp==NULL) {
347 controlreply(sender,"Can't find any stats for %s.",cip->name->content);
348 return CMD_ERROR;
349 }
350
351 controlreply(sender,"Statistics for channel %s:",cip->name->content);
352
353 if (uponehour==0) {
354 controlreply(sender," [Service up <1hour, can't report last hour stats]");
355 } else {
356 tot=0; emp=0;
357 for(i=0;i<SAMPLEHISTORY;i++) {
358 tot+=csp->lastsamples[i];
359 if (csp->lastsamples[i]==0) {
360 emp++;
361 }
362 }
363 controlreply(sender," Last hour : %6.1f average users, empty %5.1f%% of the time",(float)tot/SAMPLEHISTORY,((float)emp/SAMPLEHISTORY)*100);
364 }
365 controlreply(sender, " Today so far : %6.1f average users, empty %5.1f%% of the time, %4d max",
366 (float)csp->todayusers/todaysamples, ((float)(todaysamples-csp->todaysamples)/todaysamples)*100,
367 csp->todaymax);
368
369 themax=csp->lastmax[0];
370
371 controlreply(sender, " Yesterday : %6.1f average users, empty %5.1f%% of the time, %4d max",
372 (float)csp->lastdays[0]/10, ((float)(lastdaysamples[0]-csp->lastdaysamples[0])/lastdaysamples[0])*100,
373 themax);
374
375 /* 7-day average */
376 j=k=l=0;
377 for (i=0;i<7;i++) {
378 j+=csp->lastdays[i];
379 k+=csp->lastdaysamples[i];
380 l+=lastdaysamples[i];
381 if (csp->lastmax[i]>themax) {
382 themax=csp->lastmax[i];
383 }
384 }
385 controlreply(sender, " 7-day average : %6.1f average users, empty %5.1f%% of the time, %4d max",
386 (float)j/70,(float)((l-k)*100)/l, themax);
387
388 /* 14-day average: continuation of last loop */
389 for (;i<14;i++) {
390 j+=csp->lastdays[i];
391 k+=csp->lastdaysamples[i];
392 l+=lastdaysamples[i];
393 if (csp->lastmax[i]>themax) {
394 themax=csp->lastmax[i];
395 }
396 }
397 controlreply(sender, " 14-day average: %6.1f average users, empty %5.1f%% of the time, %4d max",
398 (float)j/140,(float)((l-k)*100)/l, themax);
399
400 return CMD_OK;
401 }
402
403 #define EXPIREMIN 4
404
405 int doexpirecheck(void *source, int cargc, char **cargv) {
406 nick *sender=(nick *)source;
407 chanindex *cip;
408 chanstats *csp;
409 int i;
410
411 if (cargc<1) {
412 controlreply(sender, "Usage: expirecheck #channel");
413 return CMD_ERROR;
414 }
415
416 if ((cip=findchanindex(cargv[0]))==NULL) {
417 /* Couldn't find the channel at all: it's in no way big enough! */
418
419 controlreply(sender,"delchan %s",cargv[0]);
420 return CMD_OK;
421 }
422
423 if ((csp=(chanstats *)cip->exts[csext])==NULL) {
424 /* No stats: similar */
425
426 controlreply(sender,"delchan %s",cargv[0]);
427 return CMD_OK;
428 }
429
430 /* Did they hit the minimum today? */
431 if (csp->todaymax >= EXPIREMIN) {
432 return CMD_OK;
433 }
434
435 /* Or recently? */
436 for (i=0;i<HISTORYDAYS;i++) {
437 if (csp->lastmax[i] >= EXPIREMIN) {
438 return CMD_OK;
439 }
440 }
441
442 /* If not, delchan time! */
443 controlreply(sender,"delchan %s",cargv[0]);
444 return CMD_OK;
445 }
446
447 int douserhistogram(void *source, int cargc, char **cargv) {
448 int histdata[21];
449 int serverdata[21];
450 nick *np;
451 nick *sender=(nick *)source;
452 int top=0,tot=0,servertot=0,servertop=0;
453 int i;
454 int theserver=-1;
455 int sig=0,serversig=0;
456
457 if (cargc>0) {
458 if ((theserver=findserver(cargv[0]))<0) {
459 controlreply(sender,"Can't find server %s",cargv[0]);
460 return CMD_ERROR;
461 }
462 }
463
464 for (i=0;i<21;i++) {
465 histdata[i]=0;
466 serverdata[i]=0;
467 }
468
469 for (i=0;i<NICKHASHSIZE;i++) {
470 for (np=nicktable[i];np;np=np->next) {
471 if (np->channels->cursi <= 20) {
472 histdata[np->channels->cursi]++;
473 if (theserver>=0 && homeserver(np->numeric)==theserver) {
474 servertop++;
475 serverdata[np->channels->cursi]++;
476 }
477 top++;
478 }
479 }
480 }
481
482 tot=top;
483 servertot=servertop;
484
485 controlreply(sender,"Size Count %%ge Count+ %s",(theserver>=0?serverlist[theserver].name->content:""));
486
487 for (i=0;i<21;i++) {
488 if (theserver>=0) {
489 controlreply(sender,"%4d %6d %4.1f%% %6d %6d %4.1f%% %6d",i,histdata[i],(100.0*histdata[i])/tot,top,
490 serverdata[i],(100.0*serverdata[i])/servertot, servertop);
491 servertop-=serverdata[i];
492 serversig += (i * serverdata[i]);
493 } else {
494 controlreply(sender,"%4d %6d %4.1f%% %6d",i,histdata[i],(100.0*histdata[i])/tot,top);
495 }
496 sig += (i * histdata[i]);
497 top-=histdata[i];
498 }
499
500 controlreply(sender,"Average channel count (all users): %.2f",(float)sig/tot);
501 if (theserver>=0) {
502 controlreply(sender,"Average channel count (%s): %.2f",serverlist[theserver].name->content,(float)serversig/servertot);
503 }
504 }
505
506 #define HISTITEMS 30
507
508 int dochanhistogram(void *source, int cargc, char **cargv) {
509 int histdata[6][HISTITEMS];
510 float bounds[HISTITEMS] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 10.0, 15.0, 20.0,
511 25.0, 50.0, 75.0, 100.0, 150.0, 200.0, 250.0, 300.0, 400.0, 500.0,
512 1000.0, 1000000.0 };
513 int histsize=23;
514 int hists=0;
515 nick *sender=(nick *)source;
516 nick *targetnick;
517 int i,j,pos;
518 char header[(6*14)+10];
519 char buf[512];
520
521 header[0]='\0';
522
523 if (cargc<1) {
524 controlreply(sender,"Usage: channelhistogram <metrics>");
525 controlreply(sender,"Valid metrics are: now, today, yesterday, days <daycount>, nick <nick>.");
526 controlreply(sender,"Up to 6 metrics may be specified.");
527 return CMD_ERROR;
528 }
529
530 for (i=0;i<cargc;i++) {
531 /* Look for a valid metric */
532 if (!ircd_strcmp(cargv[i],"now")) {
533 dohist_now(histdata[hists++],bounds,histsize,NULL);
534 strcat(header,"Current Chans ");
535 } else if (!ircd_strcmp(cargv[i],"today")) {
536 dohist_today(histdata[hists++],bounds,histsize);
537 strcat(header,"Today Average ");
538 } else if (!ircd_strcmp(cargv[i],"yesterday")) {
539 dohist_days(histdata[hists++],bounds,histsize,1);
540 strcat(header,"Yesterday Avg ");
541 } else if (!ircd_strcmp(cargv[i],"namelen")) {
542 dohist_namelen(histdata[hists++],bounds,histsize);
543 strcat(header,"Name Length ");
544 } else if (!ircd_strcmp(cargv[i],"days")) {
545 if ((i+1)>=cargc) {
546 controlreply(sender,"'days' metric requires a numeric argument.");
547 return CMD_ERROR;
548 }
549 j=strtol(cargv[i+1],NULL,10);
550 if (j<1 || j>14) {
551 controlreply(sender,"Must specify a number of days between 1 and 14.");
552 return CMD_ERROR;
553 }
554 dohist_days(histdata[hists++],bounds,histsize,j);
555 sprintf(buf,"%2d Day Avg ",j);
556 strcat(header,buf);
557 i++;
558 } else if (!ircd_strcmp(cargv[i],"nick")) {
559 if ((i+1)>=cargc) {
560 controlreply(sender,"'nick' metric requires a nickname argument.");
561 return CMD_ERROR;
562 }
563 if ((targetnick=getnickbynick(cargv[i+1]))==NULL) {
564 controlreply(sender,"Couldn't find user %s.",cargv[i+1]);
565 return CMD_ERROR;
566 }
567 dohist_now (histdata[hists++],bounds,histsize,targetnick);
568 sprintf(buf,"w/%-11.11s ",targetnick->nick);
569 strcat(header,buf);
570 i++;
571 } else {
572 controlreply(sender,"Unknown metric %s.",cargv[i]);
573 return CMD_ERROR;
574 }
575 }
576
577 controlreply(sender," %s",header);
578
579 buf[0]='\0';
580 for (i=0;i<hists;i++) {
581 strcat(buf,"InRnge Range+ ");
582 }
583 controlreply(sender," %s",buf);
584
585 for(i=0;i<(histsize-1);i++) {
586 if (i==(histsize-2)) {
587 pos=sprintf(buf,"%6.1f+ ",bounds[i]);
588 } else {
589 pos=sprintf(buf,"%6.1f-%6.1f ",bounds[i],bounds[i+1]);
590 }
591 for(j=0;j<hists;j++) {
592 pos+=sprintf(&(buf[pos]),"%6d %6d ",histdata[j][i]-histdata[j][i+1],histdata[j][i]);
593 }
594 controlreply(sender,"%s",buf);
595 }
596
597 return CMD_OK;
598 }
599
600 void dohist_now(int *data, float *bounds, int cats, nick *np) {
601 int i,j;
602 chanindex *cip;
603
604 for (i=0;i<cats;i++)
605 data[i]=0;
606
607 for (i=0;i<CHANNELHASHSIZE;i++) {
608 for (cip=chantable[i];cip;cip=cip->next) {
609 if (cip->channel==NULL) {
610 continue;
611 }
612 if (np==NULL || NULL!=getnumerichandlefromchanhash(cip->channel->users, np->numeric))
613 for (j=0;j<cats;j++) {
614 if (cip->channel->users->totalusers>=bounds[j]) {
615 data[j]++;
616 }
617 }
618 }
619 }
620 }
621
622 void dohist_today(int *data, float *bounds, int cats) {
623 int i,j;
624 chanindex *cip;
625 chanstats *csp;
626 float f;
627
628 for (i=0;i<cats;i++)
629 data[i]=0;
630
631 for (i=0;i<CHANNELHASHSIZE;i++) {
632 for (cip=chantable[i];cip;cip=cip->next) {
633 if ((csp=cip->exts[csext])==NULL) {
634 continue;
635 }
636 f=(float)csp->todayusers/todaysamples;
637 for(j=0;j<cats;j++) {
638 if (f>=bounds[j]) {
639 data[j]++;
640 }
641 }
642 }
643 }
644 }
645
646 void dohist_days(int *data, float *bounds, int cats, int days) {
647 int i,j,k;
648 chanindex *cip;
649 chanstats *csp;
650 float f;
651
652 for (i=0;i<cats;i++)
653 data[i]=0;
654
655 for (i=0;i<CHANNELHASHSIZE;i++) {
656 for (cip=chantable[i];cip;cip=cip->next) {
657 if ((csp=cip->exts[csext])==NULL) {
658 continue;
659 }
660 k=0;
661 for (j=0;j<days;j++) {
662 k+=csp->lastdays[j];
663 }
664 f=(float)k/(days*10);
665 for(j=0;j<cats;j++) {
666 if (f>=bounds[j]) {
667 data[j]++;
668 }
669 }
670 }
671 }
672 }
673
674 void dohist_namelen(int *data, float *bounds, int cats) {
675 int i,j;
676 chanindex *cip;
677
678 for (i=0;i<cats;i++)
679 data[i]=0;
680
681 for(i=0;i<CHANNELHASHSIZE;i++) {
682 for (cip=chantable[i];cip;cip=cip->next) {
683 for (j=0;j<cats;j++) {
684 if (cip->name->length>=bounds[j]) {
685 data[j]++;
686 }
687 }
688 }
689 }
690 }