]>
jfr.im git - irc/quakenet/newserv.git/blob - chanstats/chanstats.c
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"
22 time_t chanstats_lastsample
;
28 unsigned int lastdaysamples
[HISTORYDAYS
];
29 unsigned int todaysamples
;
31 void doupdate(void *arg
);
32 void updatechanstats(chanindex
*cip
, time_t now
);
33 void rotatechanstats();
36 int dochanstatssave(void *source
, int argc
, char **argv
);
37 int dochanstats(void *source
, int argc
, char **argv
);
38 int doexpirecheck(void *source
, int cargc
, char **cargv
);
39 int douserhistogram(void *source
, int cargc
, char **cargv
);
40 int dochanhistogram(void *source
, int cargc
, char **cargv
);
41 void dohist_now(int *data
, float *bounds
, int cats
, nick
*np
);
42 void dohist_today(int *data
, float *bounds
, int cats
);
43 void dohist_days(int *data
, float *bounds
, int cats
, int days
);
44 void dohist_namelen(int *data
, float *bounds
, int cats
);
51 csext
=registerchanext("chanstats");
53 /* PANIC PANIC PANIC PANIC */
54 Error("chanstats",ERR_ERROR
,"Couldn't register channel extension");
58 lastday
=(getnettime()/(24*3600));
61 memset(lastdaysamples
,0,sizeof(lastdaysamples
));
67 /* Work out when to take the next sample */
69 if (now
< chanstats_lastsample
) {
70 Error("chanstats",ERR_WARNING
,"Last sample time in future (%jd > %jd)",(intmax_t)chanstats_lastsample
,(intmax_t)now
);
72 } else if (now
<(chanstats_lastsample
+SAMPLEINTERVAL
)) {
73 lastday
=chanstats_lastsample
/(24*3600);
74 when
=chanstats_lastsample
+SAMPLEINTERVAL
;
76 if ((chanstats_lastsample
/(24*3600))<lastday
) {
83 registercontrolhelpcmd("chanstats",NO_OPER
,1,&dochanstats
, "Show usage statistics for a channel");
84 registercontrolhelpcmd("channelhistogram",NO_OPER
,13,&dochanhistogram
, "Display a histogram of network channel sizes");
85 registercontrolhelpcmd("userhistogram",NO_OPER
,1,&douserhistogram
, "Display a user histogram of channel size");
86 registercontrolhelpcmd("expirecheck",NO_DEVELOPER
,1,&doexpirecheck
, "Check if channel has too few users for services");
87 registercontrolhelpcmd("chanstatssave",NO_DEVELOPER
,1, &dochanstatssave
, "Usage: chanstatssave\nForce a save of chanstats data");
88 schedulerecurring(when
,0,SAMPLEINTERVAL
,&doupdate
,NULL
);
95 deleteschedule(NULL
,&doupdate
,NULL
);
96 deregistercontrolcmd("chanstats",&dochanstats
);
97 deregistercontrolcmd("channelhistogram",&dochanhistogram
);
98 deregistercontrolcmd("userhistogram",&douserhistogram
);
99 deregistercontrolcmd("expirecheck",&doexpirecheck
);
100 deregistercontrolcmd("chanstatssave",&dochanstatssave
);
101 releasechanext(csext
);
108 * This is the core function which is scheduled.
111 void doupdate(void *arg
) {
116 /* Get the current time and move the history pointer along one and count the sample */
119 Error("chanstats",ERR_INFO
,"Running chanstats update.");
121 if (now
/(24*3600) > lastday
) {
123 lastday
=(now
/(24*3600));
128 if (sampleindex
==SAMPLEHISTORY
) {
134 * Loop over all chans doing update
137 for(i
=0;i
<CHANNELHASHSIZE
;i
++) {
138 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
139 if ((cip
->channel
!=NULL
) || (cip
->exts
[csext
]!=NULL
)) {
140 updatechanstats(cip
,now
);
145 chanstats_lastsample
=now
;
146 if (now
-lastsave
> SAVEINTERVAL
) {
152 void updatechanstats(chanindex
*cip
, time_t now
) {
156 csp
=cip
->exts
[csext
];
158 csp
=cip
->exts
[csext
]=getchanstats();
159 memset(csp
,0,sizeof(chanstats
));
162 if (cip
->channel
!=NULL
) {
163 channel
*cp
= cip
->channel
;
167 currentusers
=countuniquehosts(cp
);
169 for (i
=0;i
<cp
->users
->hashsize
;i
++) {
170 if (cp
->users
->content
[i
]==nouser
)
173 if ((np
=getnickbynumeric(cp
->users
->content
[i
]))==NULL
) {
174 Error("channel",ERR_ERROR
,"Found unknown numeric %lu on channel %s",cp
->users
->content
[i
],cp
->index
->name
->content
);
178 if (IsXOper(np
) || IsService(np
))
187 csp
->lastsamples
[sampleindex
]=currentusers
;
188 csp
->lastsampled
=now
;
189 csp
->todayusers
+=currentusers
;
191 if (currentusers
> csp
->todaymax
) {
192 csp
->todaymax
=currentusers
;
196 void rotatechanstats() {
198 chanindex
*cip
,*ncip
;
201 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
202 for (cip
=chantable
[i
];cip
;cip
=ncip
) {
205 if ((csp
=cip
->exts
[csext
])==NULL
) {
209 if (csp
->todaysamples
==0) {
210 /* No samples today, might want to delete */
212 for(j
=0;j
<HISTORYDAYS
;j
++) {
217 cip
->exts
[csext
]=NULL
;
218 releasechanindex(cip
);
223 /* Move the samples along one */
224 memmove(&(csp
->lastdays
[1]),&(csp
->lastdays
[0]),(HISTORYDAYS
-1)*sizeof(short));
225 memmove(&(csp
->lastdaysamples
[1]),&(csp
->lastdaysamples
[0]),(HISTORYDAYS
-1)*sizeof(char));
226 memmove(&(csp
->lastmax
[1]),&(csp
->lastmax
[0]),(HISTORYDAYS
-1)*sizeof(unsigned short));
228 csp
->lastdaysamples
[0]=csp
->todaysamples
;
229 csp
->lastdays
[0]=(csp
->todayusers
* 10)/todaysamples
;
230 csp
->lastmax
[0]=csp
->todaymax
;
235 csp
->todaymax
=cip
->channel
->users
->totalusers
;
242 memmove(&lastdaysamples
[1],&lastdaysamples
[0],(HISTORYDAYS
-1)*sizeof(unsigned int));
243 lastdaysamples
[0]=todaysamples
;
247 void savechanstats() {
253 if ((fp
=fopen("data/chanstats","w"))==NULL
) {
257 /* header: samples for today + last HISTORYDAYS days */
259 fprintf(fp
,"%lu %u",chanstats_lastsample
,todaysamples
);
260 for(i
=0;i
<HISTORYDAYS
;i
++) {
261 fprintf(fp
," %u",lastdaysamples
[i
]);
265 /* body: channel, chanstats_lastsample, samplestoday, sizetoday, <last sizes, last samples> */
266 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
267 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
268 if ((chp
=cip
->exts
[csext
])==NULL
) {
271 fprintf(fp
,"%s %lu %u %u %u",cip
->name
->content
,chp
->lastsampled
,chp
->todaysamples
,chp
->todayusers
,chp
->todaymax
);
273 for (j
=0;j
<HISTORYDAYS
;j
++) {
274 fprintf(fp
," %u %u %u",chp
->lastdays
[j
],chp
->lastdaysamples
[j
],chp
->lastmax
[j
]);
283 void loadchanstats() {
288 unsigned int chancount
=0;
292 if ((fp
=fopen("data/chanstats","r"))==NULL
) {
293 Error("chanstats",ERR_ERROR
,"Unable to load channel stats file");
297 fgets(inbuf
,2048,fp
);
299 Error("chanstats",ERR_ERROR
,"Corrupt channel stats file");
304 if ((splitline(inbuf
,args
,50,0))!=(HISTORYDAYS
+2)) {
305 Error("chanstats",ERR_ERROR
,"Corrupt channel stats file");
310 chanstats_lastsample
=strtol(args
[0],NULL
,10);
311 todaysamples
=strtol(args
[1],NULL
,10);
312 for(i
=0;i
<HISTORYDAYS
;i
++) {
313 lastdaysamples
[i
]=strtol(args
[i
+2],NULL
,10);
317 fgets(inbuf
,2048,fp
);
322 if ((splitline(inbuf
,args
,100,0))!=5+(HISTORYDAYS
*3)) {
323 Error("chanstats",ERR_WARNING
,"Corrupt channel stats line");
327 cip
=findorcreatechanindex(args
[0]);
328 if (cip
->exts
[csext
]!=NULL
) {
329 Error("chanstats",ERR_ERROR
,"Duplicate stats entry for channel %s",args
[0]);
332 cip
->exts
[csext
]=chp
=getchanstats();
334 chp
->lastsampled
=strtol(args
[1],NULL
,10);
335 chp
->todaysamples
=strtol(args
[2],NULL
,10);
336 chp
->todayusers
=strtol(args
[3],NULL
,10);
337 chp
->todaymax
=strtol(args
[4],NULL
,10);
339 for(i
=0;i
<HISTORYDAYS
;i
++) {
340 chp
->lastdays
[i
]=strtol(args
[5+(i
*3)],NULL
,10);
341 chp
->lastdaysamples
[i
]=strtol(args
[6+(i
*3)],NULL
,10);
342 chp
->lastmax
[i
]=strtol(args
[7+(i
*3)],NULL
,10);
348 Error("chanstats",ERR_INFO
,"Loaded %u channels",chancount
);
351 int dochanstats(void *source
, int cargc
, char **cargv
) {
354 nick
*sender
=(nick
*)source
;
360 controlreply(sender
,"Usage: chanstats #channel");
364 cip
=findchanindex(cargv
[0]);
367 controlreply(sender
,"Can't find any record of %s.",cargv
[0]);
371 csp
=cip
->exts
[csext
];
374 controlreply(sender
,"Can't find any stats for %s.",cip
->name
->content
);
378 controlreply(sender
,"Statistics for channel %s:",cip
->name
->content
);
381 controlreply(sender
," [Service up <1hour, can't report last hour stats]");
384 for(i
=0;i
<SAMPLEHISTORY
;i
++) {
385 tot
+=csp
->lastsamples
[i
];
386 if (csp
->lastsamples
[i
]==0) {
390 controlreply(sender
," Last hour : %6.1f average users, empty %5.1f%% of the time",(float)tot
/SAMPLEHISTORY
,((float)emp
/SAMPLEHISTORY
)*100);
392 controlreply(sender
, " Today so far : %6.1f average users, empty %5.1f%% of the time, %4d max",
393 (float)csp
->todayusers
/todaysamples
, ((float)(todaysamples
-csp
->todaysamples
)/todaysamples
)*100,
396 themax
=csp
->lastmax
[0];
398 controlreply(sender
, " Yesterday : %6.1f average users, empty %5.1f%% of the time, %4d max",
399 (float)csp
->lastdays
[0]/10, ((float)(lastdaysamples
[0]-csp
->lastdaysamples
[0])/lastdaysamples
[0])*100,
406 k
+=csp
->lastdaysamples
[i
];
407 l
+=lastdaysamples
[i
];
408 if (csp
->lastmax
[i
]>themax
) {
409 themax
=csp
->lastmax
[i
];
412 controlreply(sender
, " 7-day average : %6.1f average users, empty %5.1f%% of the time, %4d max",
413 (float)j
/70,(float)((l
-k
)*100)/l
, themax
);
415 /* 14-day average: continuation of last loop */
418 k
+=csp
->lastdaysamples
[i
];
419 l
+=lastdaysamples
[i
];
420 if (csp
->lastmax
[i
]>themax
) {
421 themax
=csp
->lastmax
[i
];
424 controlreply(sender
, " 14-day average: %6.1f average users, empty %5.1f%% of the time, %4d max",
425 (float)j
/140,(float)((l
-k
)*100)/l
, themax
);
430 int doexpirecheck(void *source
, int cargc
, char **cargv
) {
431 nick
*sender
=(nick
*)source
;
437 controlreply(sender
, "Usage: expirecheck #channel");
441 if ((cip
=findchanindex(cargv
[0]))==NULL
) {
442 /* Couldn't find the channel at all: it's in no way big enough! */
444 controlreply(sender
,"delchan %s",cargv
[0]);
448 if ((csp
=(chanstats
*)cip
->exts
[csext
])==NULL
) {
449 /* No stats: similar */
451 controlreply(sender
,"delchan %s",cargv
[0]);
455 /* Did they hit the minimum today? */
456 if (csp
->todaymax
>= EXPIREMIN
) {
457 controlreply(sender
,"OK %s",cargv
[0]);
462 for (i
=0;i
<HISTORYDAYS
;i
++) {
463 if (csp
->lastmax
[i
] >= EXPIREMIN
) {
464 controlreply(sender
,"OK %s",cargv
[0]);
469 /* If not, delchan time! */
470 controlreply(sender
,"delchan %s",cargv
[0]);
474 int douserhistogram(void *source
, int cargc
, char **cargv
) {
478 nick
*sender
=(nick
*)source
;
479 int top
=0,tot
=0,servertot
=0,servertop
=0;
482 int sig
=0,serversig
=0;
485 if ((theserver
=findserver(cargv
[0]))<0) {
486 controlreply(sender
,"Can't find server %s",cargv
[0]);
496 for (i
=0;i
<NICKHASHSIZE
;i
++) {
497 for (np
=nicktable
[i
];np
;np
=np
->next
) {
498 if (np
->channels
->cursi
<= 20) {
499 histdata
[np
->channels
->cursi
]++;
500 if (theserver
>=0 && homeserver(np
->numeric
)==theserver
) {
502 serverdata
[np
->channels
->cursi
]++;
512 controlreply(sender
,"Size Count %%ge Count+ %s",(theserver
>=0?serverlist
[theserver
].name
->content
:""));
516 controlreply(sender
,"%4d %6d %4.1f%% %6d %6d %4.1f%% %6d",i
,histdata
[i
],(100.0*histdata
[i
])/tot
,top
,
517 serverdata
[i
],(100.0*serverdata
[i
])/servertot
, servertop
);
518 servertop
-=serverdata
[i
];
519 serversig
+= (i
* serverdata
[i
]);
521 controlreply(sender
,"%4d %6d %4.1f%% %6d",i
,histdata
[i
],(100.0*histdata
[i
])/tot
,top
);
523 sig
+= (i
* histdata
[i
]);
527 controlreply(sender
,"Average channel count (all users): %.2f",(float)sig
/tot
);
529 controlreply(sender
,"Average channel count (%s): %.2f",serverlist
[theserver
].name
->content
,(float)serversig
/servertot
);
537 int dochanhistogram(void *source
, int cargc
, char **cargv
) {
538 int histdata
[6][HISTITEMS
];
539 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,
540 25.0, 50.0, 75.0, 100.0, 150.0, 200.0, 250.0, 300.0, 400.0, 500.0,
544 nick
*sender
=(nick
*)source
;
547 char header
[(6*14)+10];
553 controlreply(sender
,"Usage: channelhistogram <metrics>");
554 controlreply(sender
,"Valid metrics are: now, today, yesterday, days <daycount>, nick <nick>.");
555 controlreply(sender
,"Up to 6 metrics may be specified.");
559 for (i
=0;i
<cargc
;i
++) {
560 /* Look for a valid metric */
561 if (!ircd_strcmp(cargv
[i
],"now")) {
562 dohist_now(histdata
[hists
++],bounds
,histsize
,NULL
);
563 strcat(header
,"Current Chans ");
564 } else if (!ircd_strcmp(cargv
[i
],"today")) {
565 dohist_today(histdata
[hists
++],bounds
,histsize
);
566 strcat(header
,"Today Average ");
567 } else if (!ircd_strcmp(cargv
[i
],"yesterday")) {
568 dohist_days(histdata
[hists
++],bounds
,histsize
,1);
569 strcat(header
,"Yesterday Avg ");
570 } else if (!ircd_strcmp(cargv
[i
],"namelen")) {
571 dohist_namelen(histdata
[hists
++],bounds
,histsize
);
572 strcat(header
,"Name Length ");
573 } else if (!ircd_strcmp(cargv
[i
],"days")) {
575 controlreply(sender
,"'days' metric requires a numeric argument.");
578 j
=strtol(cargv
[i
+1],NULL
,10);
580 controlreply(sender
,"Must specify a number of days between 1 and 14.");
583 dohist_days(histdata
[hists
++],bounds
,histsize
,j
);
584 sprintf(buf
,"%2d Day Avg ",j
);
587 } else if (!ircd_strcmp(cargv
[i
],"nick")) {
589 controlreply(sender
,"'nick' metric requires a nickname argument.");
592 if ((targetnick
=getnickbynick(cargv
[i
+1]))==NULL
) {
593 controlreply(sender
,"Couldn't find user %s.",cargv
[i
+1]);
596 dohist_now (histdata
[hists
++],bounds
,histsize
,targetnick
);
597 sprintf(buf
,"w/%-11.11s ",targetnick
->nick
);
601 controlreply(sender
,"Unknown metric %s.",cargv
[i
]);
606 controlreply(sender
," %s",header
);
609 for (i
=0;i
<hists
;i
++) {
610 strcat(buf
,"InRnge Range+ ");
612 controlreply(sender
," %s",buf
);
614 for(i
=0;i
<(histsize
-1);i
++) {
615 if (i
==(histsize
-2)) {
616 pos
=sprintf(buf
,"%6.1f+ ",bounds
[i
]);
618 pos
=sprintf(buf
,"%6.1f-%6.1f ",bounds
[i
],bounds
[i
+1]);
620 for(j
=0;j
<hists
;j
++) {
621 pos
+=sprintf(&(buf
[pos
]),"%6d %6d ",histdata
[j
][i
]-histdata
[j
][i
+1],histdata
[j
][i
]);
623 controlreply(sender
,"%s",buf
);
629 void dohist_now(int *data
, float *bounds
, int cats
, nick
*np
) {
636 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
637 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
638 if (cip
->channel
==NULL
) {
641 if (np
==NULL
|| NULL
!=getnumerichandlefromchanhash(cip
->channel
->users
, np
->numeric
))
642 for (j
=0;j
<cats
;j
++) {
643 if (cip
->channel
->users
->totalusers
>=bounds
[j
]) {
651 void dohist_today(int *data
, float *bounds
, int cats
) {
660 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
661 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
662 if ((csp
=cip
->exts
[csext
])==NULL
) {
665 f
=(float)csp
->todayusers
/todaysamples
;
666 for(j
=0;j
<cats
;j
++) {
675 void dohist_days(int *data
, float *bounds
, int cats
, int days
) {
684 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
685 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
686 if ((csp
=cip
->exts
[csext
])==NULL
) {
690 for (j
=0;j
<days
;j
++) {
693 f
=(float)k
/(days
*10);
694 for(j
=0;j
<cats
;j
++) {
703 void dohist_namelen(int *data
, float *bounds
, int cats
) {
710 for(i
=0;i
<CHANNELHASHSIZE
;i
++) {
711 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
712 for (j
=0;j
<cats
;j
++) {
713 if (cip
->name
->length
>=bounds
[j
]) {
721 int dochanstatssave(void *source
, int cargc
, char **cargv
) {
722 nick
*sender
=(nick
*)source
;
724 controlreply(sender
,"Saving Chanstats..");
726 controlreply(sender
,"Chanstats saved");