]>
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");
57 lastday
=(getnettime()/(24*3600));
60 memset(lastdaysamples
,0,sizeof(lastdaysamples
));
66 /* Work out when to take the next sample */
68 if (now
< chanstats_lastsample
) {
69 Error("chanstats",ERR_WARNING
,"Last sample time in future (%jd > %jd)",(intmax_t)chanstats_lastsample
,(intmax_t)now
);
71 } else if (now
<(chanstats_lastsample
+SAMPLEINTERVAL
)) {
72 lastday
=chanstats_lastsample
/(24*3600);
73 when
=chanstats_lastsample
+SAMPLEINTERVAL
;
75 if ((chanstats_lastsample
/(24*3600))<lastday
) {
82 registercontrolhelpcmd("chanstats",NO_OPER
,1,&dochanstats
, "Show usage statistics for a channel");
83 registercontrolhelpcmd("channelhistogram",NO_OPER
,13,&dochanhistogram
, "Display a histogram of network channel sizes");
84 registercontrolhelpcmd("userhistogram",NO_OPER
,1,&douserhistogram
, "Display a user histogram of channel size");
85 registercontrolhelpcmd("expirecheck",NO_DEVELOPER
,1,&doexpirecheck
, "Check if channel has too few users for services");
86 registercontrolhelpcmd("chanstatssave",NO_DEVELOPER
,1, &dochanstatssave
, "Usage: chanstatssave\nForce a save of chanstats data");
87 schedulerecurring(when
,0,SAMPLEINTERVAL
,&doupdate
,NULL
);
94 deleteschedule(NULL
,&doupdate
,NULL
);
95 deregistercontrolcmd("chanstats",&dochanstats
);
96 deregistercontrolcmd("channelhistogram",&dochanhistogram
);
97 deregistercontrolcmd("userhistogram",&douserhistogram
);
98 deregistercontrolcmd("expirecheck",&doexpirecheck
);
99 deregistercontrolcmd("chanstatssave",&dochanstatssave
);
100 releasechanext(csext
);
107 * This is the core function which is scheduled.
110 void doupdate(void *arg
) {
115 /* Get the current time and move the history pointer along one and count the sample */
118 Error("chanstats",ERR_INFO
,"Running chanstats update.");
120 if (now
/(24*3600) > lastday
) {
122 lastday
=(now
/(24*3600));
127 if (sampleindex
==SAMPLEHISTORY
) {
133 * Loop over all chans doing update
136 for(i
=0;i
<CHANNELHASHSIZE
;i
++) {
137 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
138 if ((cip
->channel
!=NULL
) || (cip
->exts
[csext
]!=NULL
)) {
139 updatechanstats(cip
,now
);
144 chanstats_lastsample
=now
;
145 if (now
-lastsave
> SAVEINTERVAL
) {
151 void updatechanstats(chanindex
*cip
, time_t now
) {
155 csp
=cip
->exts
[csext
];
157 csp
=cip
->exts
[csext
]=getchanstats();
158 memset(csp
,0,sizeof(chanstats
));
161 if (cip
->channel
!=NULL
) {
162 channel
*cp
= cip
->channel
;
166 currentusers
=countuniquehosts(cp
);
168 for (i
=0;i
<cp
->users
->hashsize
;i
++) {
169 if (cp
->users
->content
[i
]==nouser
)
172 if ((np
=getnickbynumeric(cp
->users
->content
[i
]))==NULL
) {
173 Error("channel",ERR_ERROR
,"Found unknown numeric %lu on channel %s",cp
->users
->content
[i
],cp
->index
->name
->content
);
177 if (IsXOper(np
) || IsService(np
))
186 csp
->lastsamples
[sampleindex
]=currentusers
;
187 csp
->lastsampled
=now
;
188 csp
->todayusers
+=currentusers
;
190 if (currentusers
> csp
->todaymax
) {
191 csp
->todaymax
=currentusers
;
195 void rotatechanstats() {
197 chanindex
*cip
,*ncip
;
200 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
201 for (cip
=chantable
[i
];cip
;cip
=ncip
) {
204 if ((csp
=cip
->exts
[csext
])==NULL
) {
208 if (csp
->todaysamples
==0) {
209 /* No samples today, might want to delete */
211 for(j
=0;j
<HISTORYDAYS
;j
++) {
216 cip
->exts
[csext
]=NULL
;
217 releasechanindex(cip
);
222 /* Move the samples along one */
223 memmove(&(csp
->lastdays
[1]),&(csp
->lastdays
[0]),(HISTORYDAYS
-1)*sizeof(short));
224 memmove(&(csp
->lastdaysamples
[1]),&(csp
->lastdaysamples
[0]),(HISTORYDAYS
-1)*sizeof(char));
225 memmove(&(csp
->lastmax
[1]),&(csp
->lastmax
[0]),(HISTORYDAYS
-1)*sizeof(unsigned short));
227 csp
->lastdaysamples
[0]=csp
->todaysamples
;
228 csp
->lastdays
[0]=(csp
->todayusers
* 10)/todaysamples
;
229 csp
->lastmax
[0]=csp
->todaymax
;
234 csp
->todaymax
=cip
->channel
->users
->totalusers
;
241 memmove(&lastdaysamples
[1],&lastdaysamples
[0],(HISTORYDAYS
-1)*sizeof(unsigned int));
242 lastdaysamples
[0]=todaysamples
;
246 void savechanstats() {
252 if ((fp
=fopen("data/chanstats","w"))==NULL
) {
256 /* header: samples for today + last HISTORYDAYS days */
258 fprintf(fp
,"%lu %u",chanstats_lastsample
,todaysamples
);
259 for(i
=0;i
<HISTORYDAYS
;i
++) {
260 fprintf(fp
," %u",lastdaysamples
[i
]);
264 /* body: channel, chanstats_lastsample, samplestoday, sizetoday, <last sizes, last samples> */
265 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
266 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
267 if ((chp
=cip
->exts
[csext
])==NULL
) {
270 fprintf(fp
,"%s %lu %u %u %u",cip
->name
->content
,chp
->lastsampled
,chp
->todaysamples
,chp
->todayusers
,chp
->todaymax
);
272 for (j
=0;j
<HISTORYDAYS
;j
++) {
273 fprintf(fp
," %u %u %u",chp
->lastdays
[j
],chp
->lastdaysamples
[j
],chp
->lastmax
[j
]);
282 void loadchanstats() {
287 unsigned int chancount
=0;
291 if ((fp
=fopen("data/chanstats","r"))==NULL
) {
292 Error("chanstats",ERR_ERROR
,"Unable to load channel stats file");
296 fgets(inbuf
,2048,fp
);
298 Error("chanstats",ERR_ERROR
,"Corrupt channel stats file");
303 if ((splitline(inbuf
,args
,50,0))!=(HISTORYDAYS
+2)) {
304 Error("chanstats",ERR_ERROR
,"Corrupt channel stats file");
309 chanstats_lastsample
=strtol(args
[0],NULL
,10);
310 todaysamples
=strtol(args
[1],NULL
,10);
311 for(i
=0;i
<HISTORYDAYS
;i
++) {
312 lastdaysamples
[i
]=strtol(args
[i
+2],NULL
,10);
316 fgets(inbuf
,2048,fp
);
321 if ((splitline(inbuf
,args
,100,0))!=5+(HISTORYDAYS
*3)) {
322 Error("chanstats",ERR_WARNING
,"Corrupt channel stats line");
326 cip
=findorcreatechanindex(args
[0]);
327 if (cip
->exts
[csext
]!=NULL
) {
328 Error("chanstats",ERR_ERROR
,"Duplicate stats entry for channel %s",args
[0]);
331 cip
->exts
[csext
]=chp
=getchanstats();
333 chp
->lastsampled
=strtol(args
[1],NULL
,10);
334 chp
->todaysamples
=strtol(args
[2],NULL
,10);
335 chp
->todayusers
=strtol(args
[3],NULL
,10);
336 chp
->todaymax
=strtol(args
[4],NULL
,10);
338 for(i
=0;i
<HISTORYDAYS
;i
++) {
339 chp
->lastdays
[i
]=strtol(args
[5+(i
*3)],NULL
,10);
340 chp
->lastdaysamples
[i
]=strtol(args
[6+(i
*3)],NULL
,10);
341 chp
->lastmax
[i
]=strtol(args
[7+(i
*3)],NULL
,10);
347 Error("chanstats",ERR_INFO
,"Loaded %u channels",chancount
);
350 int dochanstats(void *source
, int cargc
, char **cargv
) {
353 nick
*sender
=(nick
*)source
;
359 controlreply(sender
,"Usage: chanstats #channel");
363 cip
=findchanindex(cargv
[0]);
366 controlreply(sender
,"Can't find any record of %s.",cargv
[0]);
370 csp
=cip
->exts
[csext
];
373 controlreply(sender
,"Can't find any stats for %s.",cip
->name
->content
);
377 controlreply(sender
,"Statistics for channel %s:",cip
->name
->content
);
380 controlreply(sender
," [Service up <1hour, can't report last hour stats]");
383 for(i
=0;i
<SAMPLEHISTORY
;i
++) {
384 tot
+=csp
->lastsamples
[i
];
385 if (csp
->lastsamples
[i
]==0) {
389 controlreply(sender
," Last hour : %6.1f average users, empty %5.1f%% of the time",(float)tot
/SAMPLEHISTORY
,((float)emp
/SAMPLEHISTORY
)*100);
391 controlreply(sender
, " Today so far : %6.1f average users, empty %5.1f%% of the time, %4d max",
392 (float)csp
->todayusers
/todaysamples
, ((float)(todaysamples
-csp
->todaysamples
)/todaysamples
)*100,
395 themax
=csp
->lastmax
[0];
397 controlreply(sender
, " Yesterday : %6.1f average users, empty %5.1f%% of the time, %4d max",
398 (float)csp
->lastdays
[0]/10, ((float)(lastdaysamples
[0]-csp
->lastdaysamples
[0])/lastdaysamples
[0])*100,
405 k
+=csp
->lastdaysamples
[i
];
406 l
+=lastdaysamples
[i
];
407 if (csp
->lastmax
[i
]>themax
) {
408 themax
=csp
->lastmax
[i
];
411 controlreply(sender
, " 7-day average : %6.1f average users, empty %5.1f%% of the time, %4d max",
412 (float)j
/70,(float)((l
-k
)*100)/l
, themax
);
414 /* 14-day average: continuation of last loop */
417 k
+=csp
->lastdaysamples
[i
];
418 l
+=lastdaysamples
[i
];
419 if (csp
->lastmax
[i
]>themax
) {
420 themax
=csp
->lastmax
[i
];
423 controlreply(sender
, " 14-day average: %6.1f average users, empty %5.1f%% of the time, %4d max",
424 (float)j
/140,(float)((l
-k
)*100)/l
, themax
);
429 int doexpirecheck(void *source
, int cargc
, char **cargv
) {
430 nick
*sender
=(nick
*)source
;
436 controlreply(sender
, "Usage: expirecheck #channel");
440 if ((cip
=findchanindex(cargv
[0]))==NULL
) {
441 /* Couldn't find the channel at all: it's in no way big enough! */
443 controlreply(sender
,"delchan %s",cargv
[0]);
447 if ((csp
=(chanstats
*)cip
->exts
[csext
])==NULL
) {
448 /* No stats: similar */
450 controlreply(sender
,"delchan %s",cargv
[0]);
454 /* Did they hit the minimum today? */
455 if (csp
->todaymax
>= EXPIREMIN
) {
456 controlreply(sender
,"OK %s",cargv
[0]);
461 for (i
=0;i
<HISTORYDAYS
;i
++) {
462 if (csp
->lastmax
[i
] >= EXPIREMIN
) {
463 controlreply(sender
,"OK %s",cargv
[0]);
468 /* If not, delchan time! */
469 controlreply(sender
,"delchan %s",cargv
[0]);
473 int douserhistogram(void *source
, int cargc
, char **cargv
) {
477 nick
*sender
=(nick
*)source
;
478 int top
=0,tot
=0,servertot
=0,servertop
=0;
481 int sig
=0,serversig
=0;
484 if ((theserver
=findserver(cargv
[0]))<0) {
485 controlreply(sender
,"Can't find server %s",cargv
[0]);
495 for (i
=0;i
<NICKHASHSIZE
;i
++) {
496 for (np
=nicktable
[i
];np
;np
=np
->next
) {
497 if (np
->channels
->cursi
<= 20) {
498 histdata
[np
->channels
->cursi
]++;
499 if (theserver
>=0 && homeserver(np
->numeric
)==theserver
) {
501 serverdata
[np
->channels
->cursi
]++;
511 controlreply(sender
,"Size Count %%ge Count+ %s",(theserver
>=0?serverlist
[theserver
].name
->content
:""));
515 controlreply(sender
,"%4d %6d %4.1f%% %6d %6d %4.1f%% %6d",i
,histdata
[i
],(100.0*histdata
[i
])/tot
,top
,
516 serverdata
[i
],(100.0*serverdata
[i
])/servertot
, servertop
);
517 servertop
-=serverdata
[i
];
518 serversig
+= (i
* serverdata
[i
]);
520 controlreply(sender
,"%4d %6d %4.1f%% %6d",i
,histdata
[i
],(100.0*histdata
[i
])/tot
,top
);
522 sig
+= (i
* histdata
[i
]);
526 controlreply(sender
,"Average channel count (all users): %.2f",(float)sig
/tot
);
528 controlreply(sender
,"Average channel count (%s): %.2f",serverlist
[theserver
].name
->content
,(float)serversig
/servertot
);
536 int dochanhistogram(void *source
, int cargc
, char **cargv
) {
537 int histdata
[6][HISTITEMS
];
538 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,
539 25.0, 50.0, 75.0, 100.0, 150.0, 200.0, 250.0, 300.0, 400.0, 500.0,
543 nick
*sender
=(nick
*)source
;
546 char header
[(6*14)+10];
552 controlreply(sender
,"Usage: channelhistogram <metrics>");
553 controlreply(sender
,"Valid metrics are: now, today, yesterday, days <daycount>, nick <nick>.");
554 controlreply(sender
,"Up to 6 metrics may be specified.");
558 for (i
=0;i
<cargc
;i
++) {
559 /* Look for a valid metric */
560 if (!ircd_strcmp(cargv
[i
],"now")) {
561 dohist_now(histdata
[hists
++],bounds
,histsize
,NULL
);
562 strcat(header
,"Current Chans ");
563 } else if (!ircd_strcmp(cargv
[i
],"today")) {
564 dohist_today(histdata
[hists
++],bounds
,histsize
);
565 strcat(header
,"Today Average ");
566 } else if (!ircd_strcmp(cargv
[i
],"yesterday")) {
567 dohist_days(histdata
[hists
++],bounds
,histsize
,1);
568 strcat(header
,"Yesterday Avg ");
569 } else if (!ircd_strcmp(cargv
[i
],"namelen")) {
570 dohist_namelen(histdata
[hists
++],bounds
,histsize
);
571 strcat(header
,"Name Length ");
572 } else if (!ircd_strcmp(cargv
[i
],"days")) {
574 controlreply(sender
,"'days' metric requires a numeric argument.");
577 j
=strtol(cargv
[i
+1],NULL
,10);
579 controlreply(sender
,"Must specify a number of days between 1 and 14.");
582 dohist_days(histdata
[hists
++],bounds
,histsize
,j
);
583 sprintf(buf
,"%2d Day Avg ",j
);
586 } else if (!ircd_strcmp(cargv
[i
],"nick")) {
588 controlreply(sender
,"'nick' metric requires a nickname argument.");
591 if ((targetnick
=getnickbynick(cargv
[i
+1]))==NULL
) {
592 controlreply(sender
,"Couldn't find user %s.",cargv
[i
+1]);
595 dohist_now (histdata
[hists
++],bounds
,histsize
,targetnick
);
596 sprintf(buf
,"w/%-11.11s ",targetnick
->nick
);
600 controlreply(sender
,"Unknown metric %s.",cargv
[i
]);
605 controlreply(sender
," %s",header
);
608 for (i
=0;i
<hists
;i
++) {
609 strcat(buf
,"InRnge Range+ ");
611 controlreply(sender
," %s",buf
);
613 for(i
=0;i
<(histsize
-1);i
++) {
614 if (i
==(histsize
-2)) {
615 pos
=sprintf(buf
,"%6.1f+ ",bounds
[i
]);
617 pos
=sprintf(buf
,"%6.1f-%6.1f ",bounds
[i
],bounds
[i
+1]);
619 for(j
=0;j
<hists
;j
++) {
620 pos
+=sprintf(&(buf
[pos
]),"%6d %6d ",histdata
[j
][i
]-histdata
[j
][i
+1],histdata
[j
][i
]);
622 controlreply(sender
,"%s",buf
);
628 void dohist_now(int *data
, float *bounds
, int cats
, nick
*np
) {
635 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
636 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
637 if (cip
->channel
==NULL
) {
640 if (np
==NULL
|| NULL
!=getnumerichandlefromchanhash(cip
->channel
->users
, np
->numeric
))
641 for (j
=0;j
<cats
;j
++) {
642 if (cip
->channel
->users
->totalusers
>=bounds
[j
]) {
650 void dohist_today(int *data
, float *bounds
, int cats
) {
659 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
660 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
661 if ((csp
=cip
->exts
[csext
])==NULL
) {
664 f
=(float)csp
->todayusers
/todaysamples
;
665 for(j
=0;j
<cats
;j
++) {
674 void dohist_days(int *data
, float *bounds
, int cats
, int days
) {
683 for (i
=0;i
<CHANNELHASHSIZE
;i
++) {
684 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
685 if ((csp
=cip
->exts
[csext
])==NULL
) {
689 for (j
=0;j
<days
;j
++) {
692 f
=(float)k
/(days
*10);
693 for(j
=0;j
<cats
;j
++) {
702 void dohist_namelen(int *data
, float *bounds
, int cats
) {
709 for(i
=0;i
<CHANNELHASHSIZE
;i
++) {
710 for (cip
=chantable
[i
];cip
;cip
=cip
->next
) {
711 for (j
=0;j
<cats
;j
++) {
712 if (cip
->name
->length
>=bounds
[j
]) {
720 int dochanstatssave(void *source
, int cargc
, char **cargv
) {
721 nick
*sender
=(nick
*)source
;
723 controlreply(sender
,"Saving Chanstats..");
725 controlreply(sender
,"Chanstats saved");