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