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