]>
Commit | Line | Data |
---|---|---|
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" | |
87698d77 | 12 | #include "../lib/version.h" |
c86edd1d Q |
13 | |
14 | #include <stdio.h> | |
15 | #include <string.h> | |
8bab42e7 | 16 | #include <stdint.h> |
c86edd1d | 17 | |
70b0a4e5 | 18 | MODULE_VERSION(""); |
87698d77 | 19 | |
c86edd1d Q |
20 | int csext; |
21 | int sampleindex; | |
e36fed08 | 22 | time_t chanstats_lastsample; |
c86edd1d Q |
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(); | |
ab0c8601 | 36 | int dochanstatssave(void *source, int argc, char **argv); |
c86edd1d Q |
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 | ||
95ff6b03 P |
46 | #define EXPIREMIN 4 |
47 | ||
c86edd1d Q |
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(); | |
e36fed08 | 69 | if (now < chanstats_lastsample) { |
58a4da4a | 70 | Error("chanstats",ERR_WARNING,"Last sample time in future (%jd > %jd)",(intmax_t)chanstats_lastsample,(intmax_t)now); |
c86edd1d | 71 | when=now; |
e36fed08 | 72 | } else if (now<(chanstats_lastsample+SAMPLEINTERVAL)) { |
73 | lastday=chanstats_lastsample/(24*3600); | |
74 | when=chanstats_lastsample+SAMPLEINTERVAL; | |
c86edd1d | 75 | } else { |
e36fed08 | 76 | if ((chanstats_lastsample/(24*3600))<lastday) { |
c86edd1d Q |
77 | rotatechanstats(); |
78 | } | |
79 | when=now; | |
80 | } | |
81 | ||
82 | lastsave=now; | |
95ff6b03 P |
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"); | |
ab0c8601 | 87 | registercontrolhelpcmd("chanstatssave",NO_DEVELOPER,1, &dochanstatssave, "Usage: chanstatssave\nForce a save of chanstats data"); |
c86edd1d Q |
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); | |
ab0c8601 | 100 | deregistercontrolcmd("chanstatssave",&dochanstatssave); |
c86edd1d Q |
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 | ||
e36fed08 | 145 | chanstats_lastsample=now; |
c86edd1d Q |
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) { | |
08d6ada8 CP |
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) { | |
16739dbe | 174 | Error("channel",ERR_ERROR,"Found unknown numeric %lu on channel %s",cp->users->content[i],cp->index->name->content); |
08d6ada8 CP |
175 | continue; |
176 | } | |
177 | ||
178 | if (IsXOper(np) || IsService(np)) | |
179 | currentusers--; | |
180 | } | |
181 | ||
c86edd1d Q |
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 | ||
ab0c8601 | 253 | if ((fp=fopen("data/chanstats","w"))==NULL) { |
c86edd1d Q |
254 | return; |
255 | } | |
256 | ||
257 | /* header: samples for today + last HISTORYDAYS days */ | |
258 | ||
e36fed08 | 259 | fprintf(fp,"%lu %u",chanstats_lastsample,todaysamples); |
c86edd1d Q |
260 | for(i=0;i<HISTORYDAYS;i++) { |
261 | fprintf(fp," %u",lastdaysamples[i]); | |
262 | } | |
263 | fprintf(fp,"\n"); | |
264 | ||
e36fed08 | 265 | /* body: channel, chanstats_lastsample, samplestoday, sizetoday, <last sizes, last samples> */ |
c86edd1d Q |
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 | ||
ab0c8601 | 292 | if ((fp=fopen("data/chanstats","r"))==NULL) { |
c86edd1d Q |
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 | ||
e36fed08 | 310 | chanstats_lastsample=strtol(args[0],NULL,10); |
c86edd1d Q |
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 | ||
c86edd1d Q |
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) { | |
95ff6b03 | 457 | controlreply(sender,"OK %s",cargv[0]); |
c86edd1d Q |
458 | return CMD_OK; |
459 | } | |
460 | ||
461 | /* Or recently? */ | |
462 | for (i=0;i<HISTORYDAYS;i++) { | |
463 | if (csp->lastmax[i] >= EXPIREMIN) { | |
95ff6b03 | 464 | controlreply(sender,"OK %s",cargv[0]); |
c86edd1d Q |
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 | } | |
4ad1cf7a CP |
531 | |
532 | return CMD_OK; | |
c86edd1d Q |
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 | } | |
ab0c8601 P |
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 | } |