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