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