]> jfr.im git - irc/quakenet/newserv.git/blame - chanstats/chanstats.c
r657@blue (orig r496): slug | 2006-05-16 00:09:51 +0100
[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"
12
13#include <stdio.h>
14#include <string.h>
15
16int csext;
17int sampleindex;
18time_t lastsample;
19time_t lastday;
20int uponehour;
21int failedinit;
22time_t lastsave;
23
24unsigned int lastdaysamples[HISTORYDAYS];
25unsigned int todaysamples;
26
27void doupdate(void *arg);
28void updatechanstats(chanindex *cip, time_t now);
29void rotatechanstats();
30void savechanstats();
31void loadchanstats();
32int dochanstats(void *source, int argc, char **argv);
33int doexpirecheck(void *source, int cargc, char **cargv);
34int douserhistogram(void *source, int cargc, char **cargv);
35int dochanhistogram(void *source, int cargc, char **cargv);
36void dohist_now(int *data, float *bounds, int cats, nick *np);
37void dohist_today(int *data, float *bounds, int cats);
38void dohist_days(int *data, float *bounds, int cats, int days);
39void dohist_namelen(int *data, float *bounds, int cats);
40
41void _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
84void _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
102void 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
143void 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
169void 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
220void 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
256void 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
324int 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
405int 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
447int 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 }
4ad1cf7a
CP
504
505 return CMD_OK;
c86edd1d
Q
506}
507
508#define HISTITEMS 30
509
510int dochanhistogram(void *source, int cargc, char **cargv) {
511 int histdata[6][HISTITEMS];
512 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,
513 25.0, 50.0, 75.0, 100.0, 150.0, 200.0, 250.0, 300.0, 400.0, 500.0,
514 1000.0, 1000000.0 };
515 int histsize=23;
516 int hists=0;
517 nick *sender=(nick *)source;
518 nick *targetnick;
519 int i,j,pos;
520 char header[(6*14)+10];
521 char buf[512];
522
523 header[0]='\0';
524
525 if (cargc<1) {
526 controlreply(sender,"Usage: channelhistogram <metrics>");
527 controlreply(sender,"Valid metrics are: now, today, yesterday, days <daycount>, nick <nick>.");
528 controlreply(sender,"Up to 6 metrics may be specified.");
529 return CMD_ERROR;
530 }
531
532 for (i=0;i<cargc;i++) {
533 /* Look for a valid metric */
534 if (!ircd_strcmp(cargv[i],"now")) {
535 dohist_now(histdata[hists++],bounds,histsize,NULL);
536 strcat(header,"Current Chans ");
537 } else if (!ircd_strcmp(cargv[i],"today")) {
538 dohist_today(histdata[hists++],bounds,histsize);
539 strcat(header,"Today Average ");
540 } else if (!ircd_strcmp(cargv[i],"yesterday")) {
541 dohist_days(histdata[hists++],bounds,histsize,1);
542 strcat(header,"Yesterday Avg ");
543 } else if (!ircd_strcmp(cargv[i],"namelen")) {
544 dohist_namelen(histdata[hists++],bounds,histsize);
545 strcat(header,"Name Length ");
546 } else if (!ircd_strcmp(cargv[i],"days")) {
547 if ((i+1)>=cargc) {
548 controlreply(sender,"'days' metric requires a numeric argument.");
549 return CMD_ERROR;
550 }
551 j=strtol(cargv[i+1],NULL,10);
552 if (j<1 || j>14) {
553 controlreply(sender,"Must specify a number of days between 1 and 14.");
554 return CMD_ERROR;
555 }
556 dohist_days(histdata[hists++],bounds,histsize,j);
557 sprintf(buf,"%2d Day Avg ",j);
558 strcat(header,buf);
559 i++;
560 } else if (!ircd_strcmp(cargv[i],"nick")) {
561 if ((i+1)>=cargc) {
562 controlreply(sender,"'nick' metric requires a nickname argument.");
563 return CMD_ERROR;
564 }
565 if ((targetnick=getnickbynick(cargv[i+1]))==NULL) {
566 controlreply(sender,"Couldn't find user %s.",cargv[i+1]);
567 return CMD_ERROR;
568 }
569 dohist_now (histdata[hists++],bounds,histsize,targetnick);
570 sprintf(buf,"w/%-11.11s ",targetnick->nick);
571 strcat(header,buf);
572 i++;
573 } else {
574 controlreply(sender,"Unknown metric %s.",cargv[i]);
575 return CMD_ERROR;
576 }
577 }
578
579 controlreply(sender," %s",header);
580
581 buf[0]='\0';
582 for (i=0;i<hists;i++) {
583 strcat(buf,"InRnge Range+ ");
584 }
585 controlreply(sender," %s",buf);
586
587 for(i=0;i<(histsize-1);i++) {
588 if (i==(histsize-2)) {
589 pos=sprintf(buf,"%6.1f+ ",bounds[i]);
590 } else {
591 pos=sprintf(buf,"%6.1f-%6.1f ",bounds[i],bounds[i+1]);
592 }
593 for(j=0;j<hists;j++) {
594 pos+=sprintf(&(buf[pos]),"%6d %6d ",histdata[j][i]-histdata[j][i+1],histdata[j][i]);
595 }
596 controlreply(sender,"%s",buf);
597 }
598
599 return CMD_OK;
600}
601
602void dohist_now(int *data, float *bounds, int cats, nick *np) {
603 int i,j;
604 chanindex *cip;
605
606 for (i=0;i<cats;i++)
607 data[i]=0;
608
609 for (i=0;i<CHANNELHASHSIZE;i++) {
610 for (cip=chantable[i];cip;cip=cip->next) {
611 if (cip->channel==NULL) {
612 continue;
613 }
614 if (np==NULL || NULL!=getnumerichandlefromchanhash(cip->channel->users, np->numeric))
615 for (j=0;j<cats;j++) {
616 if (cip->channel->users->totalusers>=bounds[j]) {
617 data[j]++;
618 }
619 }
620 }
621 }
622}
623
624void dohist_today(int *data, float *bounds, int cats) {
625 int i,j;
626 chanindex *cip;
627 chanstats *csp;
628 float f;
629
630 for (i=0;i<cats;i++)
631 data[i]=0;
632
633 for (i=0;i<CHANNELHASHSIZE;i++) {
634 for (cip=chantable[i];cip;cip=cip->next) {
635 if ((csp=cip->exts[csext])==NULL) {
636 continue;
637 }
638 f=(float)csp->todayusers/todaysamples;
639 for(j=0;j<cats;j++) {
640 if (f>=bounds[j]) {
641 data[j]++;
642 }
643 }
644 }
645 }
646}
647
648void dohist_days(int *data, float *bounds, int cats, int days) {
649 int i,j,k;
650 chanindex *cip;
651 chanstats *csp;
652 float f;
653
654 for (i=0;i<cats;i++)
655 data[i]=0;
656
657 for (i=0;i<CHANNELHASHSIZE;i++) {
658 for (cip=chantable[i];cip;cip=cip->next) {
659 if ((csp=cip->exts[csext])==NULL) {
660 continue;
661 }
662 k=0;
663 for (j=0;j<days;j++) {
664 k+=csp->lastdays[j];
665 }
666 f=(float)k/(days*10);
667 for(j=0;j<cats;j++) {
668 if (f>=bounds[j]) {
669 data[j]++;
670 }
671 }
672 }
673 }
674}
675
676void dohist_namelen(int *data, float *bounds, int cats) {
677 int i,j;
678 chanindex *cip;
679
680 for (i=0;i<cats;i++)
681 data[i]=0;
682
683 for(i=0;i<CHANNELHASHSIZE;i++) {
684 for (cip=chantable[i];cip;cip=cip->next) {
685 for (j=0;j<cats;j++) {
686 if (cip->name->length>=bounds[j]) {
687 data[j]++;
688 }
689 }
690 }
691 }
692}