]>
Commit | Line | Data |
---|---|---|
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 | } |