root/src/display.cc

Revision 5, 30.2 KB (checked in by nick, 3 years ago)
Line 
1#include "apachetop.h"
2
3#include "inlines.cc"
4
5extern struct gstat gstats;
6extern time_t now;
7
8extern Circle *c;
9
10extern map *um, /* urlmap */
11           *im, /* ipmap */
12           *hm, /* hostmap */
13           *rm, /* referrermap */
14           *fm; /* filemap */
15
16extern struct config cf;
17
18/* global, so we can keep it after display() finishes; the only reason for
19 * this so far is so we know what's being displayed after this function
20 * finishes. Hence we can translate selected_item_screen into a
21 * selected_item_pos when the marker moves */
22extern itemlist *items;
23extern map *last_display_map;
24
25
26bool display(time_t last_display) /* {{{ */
27{
28        struct gstat *w_gstats;
29        Circle *w_c;
30
31        if (cf.do_immed_display)
32        {
33                /* we've been asked to update immediately, whether we're
34                 * paused or not */
35
36                /* this flag enforces a clear() (why? forgot now) */
37                clear();
38
39                /* clear that flag */
40                cf.do_immed_display = false;
41        }
42        else if (
43            /* do nothing if display is paused */
44            cf.display_paused
45            ||
46            /* or it's not time to update yet */
47            (now - last_display) < cf.refresh_delay  )
48                return false;
49
50        /* if we reach here, we're fine to update */
51
52        display_header();
53
54        //display_histogram();
55        //return true;
56
57        if (cf.display_mode != DISPLAY_DETAIL)
58        {
59                /* normal display */
60                display_list();
61
62                return true;
63        }
64
65        /* detailed display mode involves showing the item the user is
66         * interested in (complete stats, whether it be an URL or whatever),
67         * followed by alternate stats. Example. The user is interested in
68         * one particular URL, so we show that URL as it would be in
69         * DISPLAY_URLS, then split the rest of the screen between IPs
70         * hitting that URL, and referrers referring to that URL */
71       
72        /* first line is the pos of interest */
73        display_list();
74
75        /* now split the screen with all other stats */
76
77        /* one line for header, one for main stat */
78        #define FIRST_OFFSET 2
79
80        /* figure out how much room we have */
81        int size, cur_offset, num_sections;
82        cur_offset = FIRST_OFFSET;
83
84        switch(cf.display_mode_detail)
85        {
86                case DISPLAY_URLS:
87                        /* show IPs and Referrers */
88                        num_sections =
89                            (cf.detail_display_hosts ? 1 : 0) +
90                            (cf.detail_display_refs ? 1 : 0);
91
92                        /* anything to do? */
93                        if (num_sections == 0) break;
94
95                        size = (((LINES-LINES_RESERVED-2) - FIRST_OFFSET) /
96                            num_sections);
97
98                        if (cf.detail_display_hosts)
99                        {
100                                display_sub_list(DISPLAY_HOSTS,cur_offset,size);
101                                cur_offset += size + 1;
102                        }
103
104                        if (cf.detail_display_refs)
105                        {
106                                display_sub_list(DISPLAY_REFS,cur_offset,size);
107                                cur_offset += size + 1;
108                        }
109                        break;
110
111
112                case DISPLAY_HOSTS:
113                        /* show URLs and Referrers */
114                        num_sections =
115                            (cf.detail_display_urls ? 1 : 0) +
116                            (cf.detail_display_refs ? 1 : 0);
117
118                        /* anything to do? */
119                        if (num_sections == 0) break;
120
121                        size = (((LINES-LINES_RESERVED-2) - FIRST_OFFSET) /
122                            num_sections);
123
124                        if (cf.detail_display_urls)
125                        {
126                                display_sub_list(DISPLAY_URLS,cur_offset,size);
127                                cur_offset += size + 1;
128                        }
129                        if (cf.detail_display_refs)
130                        {
131                                display_sub_list(DISPLAY_REFS,cur_offset,size);
132                                cur_offset += size + 1;
133                        }
134                        break;
135
136
137                case DISPLAY_REFS:
138                        /* show URLs and IPs */
139                        num_sections =
140                            (cf.detail_display_urls ? 1 : 0) +
141                            (cf.detail_display_hosts ? 1 : 0);
142
143                        /* anything to do? */
144                        if (num_sections == 0) break;
145
146                        size = (((LINES-LINES_RESERVED-2) - FIRST_OFFSET) /
147                            num_sections);
148
149                        if (cf.detail_display_urls)
150                        {
151                                display_sub_list(DISPLAY_URLS,cur_offset,size);
152                                cur_offset += size + 1;
153                        }
154                        if (cf.detail_display_hosts)
155                        {
156                                display_sub_list(DISPLAY_HOSTS,cur_offset,size);
157                                cur_offset += size + 1;
158                        }
159                        break;
160        }
161        refresh();
162
163        return true;
164} /* }}} */
165
166void display_header() /* {{{ */
167{
168        int itmp;
169        float bytes, bps, per_req, ftmp;
170        unsigned int secs_offset, diff, d = 0, h = 0, m = 0, s = 0;
171        char bytes_suffix, bps_suffix, per_req_suffix;
172
173
174        move(0, 0);
175        clrtoeol();
176
177        /* last hit */
178        secs_offset = gstats.alltime.last % 86400;
179        mvprintw(0, 0, "last hit: %02d:%02d:%02d",
180          secs_offset / 3600, (secs_offset / 60) % 60, secs_offset % 60);
181       
182        /* uptime */
183        diff = (unsigned int)difftime(now, gstats.start);
184        if (diff > 86399) diff -= ((d = diff / 86400)*86400);
185        if (diff > 3599) diff -= ((h = diff / 3600)*3600);
186        if (diff > 59) diff -= ((m = diff / 60)*60);
187        s = diff;
188        mvprintw(0, 27, "atop runtime: %2d days, %02d:%02d:%02d", d, h, m, s);
189
190        /* are we paused? */
191        if (cf.display_paused)
192        {
193                DRAW_PAUSED(0,60); /* macro in display.h */
194        }
195
196        /* current time */
197        secs_offset = now % 86400;
198        mvprintw(0, 71, "%02d:%02d:%02d",
199          secs_offset /3600, (secs_offset/ 60) % 60, secs_offset % 60);
200
201
202        //All: 1,140,532 requests (39.45/sec),  999,540,593 bytes (857,235/sec)
203        ftmp = getMAX(now-gstats.alltime.first, 1); /* divide-by-zero hack */
204        bytes = readableNum(gstats.alltime.bytecount, &bytes_suffix);
205        bps = readableNum(gstats.alltime.bytecount/ftmp, &bps_suffix);
206        per_req = readableNum(
207            gstats.alltime.bytecount/getMAX(gstats.alltime.reqcount, 1),
208            &per_req_suffix);
209        attron(A_BOLD);
210        mvprintw(1, 0,
211          "All: %12.0f reqs (%6.1f/sec)  %11.1f%c (%7.1f%c/sec)   %7.1f%c/req",
212          gstats.alltime.reqcount,
213          gstats.alltime.reqcount/ftmp,
214          bytes, bytes_suffix,
215          bps, bps_suffix,
216          per_req, per_req_suffix);
217        attroff(A_BOLD);
218
219
220        // 2xx 1,604,104 (95%) 3xx 1,000,000 ( 3%) 4xx 1,000,000 ( 1%)
221        // 5xx 1,000,000 ( 1%)
222        ftmp = gstats.r_codes[2].reqcount + gstats.r_codes[3].reqcount+
223               gstats.r_codes[4].reqcount + gstats.r_codes[5].reqcount;
224        if (ftmp == 0) ftmp = 1; /* avoid NaN with no hits */
225        mvprintw(2, 0,
226         "2xx: %7.0f (%4.*f%%) 3xx: %7.0f (%4.*f%%) "
227         "4xx: %5.0f (%4.*f%%) 5xx: %5.0f (%4.*f%%) ",
228
229         gstats.r_codes[2].reqcount,
230         (gstats.r_codes[2].reqcount/ftmp) == 1 ? 0 : 1,
231         (gstats.r_codes[2].reqcount/ftmp)*100,
232
233         gstats.r_codes[3].reqcount,
234         (gstats.r_codes[3].reqcount/ftmp) == 1 ? 0 : 1,
235         (gstats.r_codes[3].reqcount/ftmp)*100,
236         
237         gstats.r_codes[4].reqcount,
238         (gstats.r_codes[4].reqcount/ftmp) == 1 ? 0 : 1,
239         (gstats.r_codes[4].reqcount/ftmp)*100,
240
241         gstats.r_codes[5].reqcount,
242         (gstats.r_codes[5].reqcount/ftmp) == 1 ? 0 : 1,
243         (gstats.r_codes[5].reqcount/ftmp)*100
244         
245        );
246
247        /* housecleaning on the circle, if its required in this class */
248        c->updatestats();
249        /* fetch the time of the first "recent" request */
250        itmp = now - c->oldest();
251        itmp = getMAX(itmp, 1); /* divide-by-zero hack */
252        bytes = readableNum(c->getbytecount(), &bytes_suffix);
253        bps = readableNum(c->getbytecount()/itmp, &bps_suffix);
254        per_req = readableNum(c->getbytecount()/getMAX(c->getreqcount(), 1),
255            &per_req_suffix);
256        attron(A_BOLD);
257        mvprintw(3, 0,
258          "R (%3ds): %7.0f reqs (%6.1f/sec)  %11.1f%c (%7.1f%c/sec)   %7.1f%c/req",
259          itmp, c->getreqcount(),
260          ((float)c->getreqcount()/itmp),
261          bytes, bytes_suffix,
262          bps, bps_suffix,
263          per_req, per_req_suffix
264        );
265        attroff(A_BOLD);
266
267        ftmp = c->getsummary(2) + c->getsummary(3) +
268               c->getsummary(4) + c->getsummary(5);
269        if (ftmp == 0) ftmp = 1; /* avoid NaN with no hits */
270        mvprintw(4, 0,
271          "2xx: %7.0f (%4.*f%%) 3xx: %7.0f (%4.*f%%) "
272          "4xx: %5.0f (%4.*f%%) 5xx: %5.0f (%4.*f%%) ",
273         c->getsummary(2),
274         (c->getsummary(2)/ftmp) == 1 ? 0 : 1,
275         (c->getsummary(2)/ftmp)*100,
276
277         c->getsummary(3),
278         (c->getsummary(3)/ftmp) == 1 ? 0 : 1,
279         (c->getsummary(3)/ftmp)*100,
280
281         c->getsummary(4),
282         (c->getsummary(4)/ftmp) == 1 ? 0 : 1,
283         (c->getsummary(4)/ftmp)*100,
284
285         c->getsummary(5),
286         (c->getsummary(5)/ftmp) == 1 ? 0 : 1,
287         (c->getsummary(5)/ftmp)*100
288        );
289
290//      mvprintw(5, 0,
291//          "Unique Objects:         Size Footprint:");
292
293        /* if any filters are active, and the user is not in a submenu,
294         * display a summary */
295        if (cf.in_submenu == SUBMENU_NONE)
296        {
297                int y;
298                char active_filters[20];
299                bzero(active_filters, sizeof(active_filters));
300
301                /* suss out which filters are active */
302                if (cf.urlfilter->isactive())
303                        strcat(active_filters, "URLs ");
304                if (cf.hostfilter->isactive())
305                        strcat(active_filters, "HOSTs ");
306                if (cf.reffilter->isactive())
307                        strcat(active_filters, "REFs ");
308
309                if (*active_filters)
310                {
311                        y = COLS - (strlen(active_filters) + 11);
312                        mvprintw(SUBMENU_LINE_NUMBER,
313                                 y, "Filtering: %s", active_filters);
314                }
315
316        }
317
318} /* }}} */
319
320void display_list() /* {{{ */
321{
322        int x, t, xx;
323        char *cptmp;
324        struct config scf; /* for copying cf, see comment below at memcpy */
325
326        OAHash item_hash;
327        int item_used = 0, disp;
328        double items_size;
329        struct itemlist *item_ptr = NULL;
330
331        /* for easy reference during walk() */
332        map *map; unsigned int hash;
333        int pos;
334
335        /* walk() pointer */
336        struct logbits *lb;
337
338        /* make an array containing all we have, then sort it */
339        items_size = c->getreqcount();
340        if (items_size == 0)
341        {
342                /* nothing to do! */
343                move(LINES_RESERVED-1, 0);
344                clrtobot();
345                refresh();
346                return;
347        }
348
349        item_hash.create( (int)items_size * 5 );
350
351        /* this is our array; the cast is because items_size is a double but
352         * I'm fairly sure, realistically, it'll never get high enough to be
353         * a problem, so uInt should be ok */
354        if (items) free(items); /* get rid of the last one */
355        items = (struct itemlist *)
356            calloc((unsigned int)items_size, sizeof(itemlist));
357
358        /* another thread may change the contents of cf while we're running,
359         * and it would be undesirable to have most of this change on us, so
360         * we make safe copies to use */
361        memcpy(&scf, &cf, sizeof(struct config));
362
363        /* if we are in detailed display mode, then we have to overwrite
364         * scf.display_mode so all these switch()'es work */
365        if (scf.display_mode == DISPLAY_DETAIL)
366            scf.display_mode = cf.display_mode_detail;
367
368        /* pick a map to use */
369        switch(scf.display_mode)
370        {
371                default:
372                case DISPLAY_URLS: map = um; break;
373                case DISPLAY_HOSTS: map = hm; break;
374                case DISPLAY_REFS: map = rm; break;
375                case DISPLAY_FILES: map = fm; break;
376        }
377
378        /* store that in a global that we can remember */
379        last_display_map = map;
380
381        /* walk the entire circle */
382        while(c->walk(&lb) != -1)
383        {
384                /* skip unused */
385                if (lb == NULL)
386                        continue;
387       
388
389                /* set up some pointers, depending on cf.display_mode, so
390                 * that we can refer to the url_pos or ip_pos via just pos,
391                 * and the urlmap or ipmap via just map
392                */
393                switch(scf.display_mode)
394                {
395                        default:
396                        case DISPLAY_URLS:
397                                hash = lb->url_hash; pos = lb->url_pos;
398                                break;
399
400                        case DISPLAY_HOSTS:
401                                hash = lb->host_hash; pos = lb->host_pos;
402                                break;
403
404                        case DISPLAY_REFS:
405                                hash = lb->ref_hash; pos = lb->ref_pos;
406                                break;
407#if HAVE_FILE_MODE_DISPLAY
408                        case DISPLAY_FILES:
409                                hash = lb->file_hash ; pos = lb->file_pos;
410                                break;
411#endif
412                }
413
414                /* FILTERS {{{ */
415                /* skip this item if it doesn't match our filter */
416                if (cf.urlfilter->isactive() &&
417                    !cf.urlfilter->match(um->reverse(lb->url_pos)))
418                        continue;
419
420                if (cf.hostfilter->isactive() &&
421                    !cf.hostfilter->match(hm->reverse(lb->host_pos)))
422                        continue;
423
424                if (cf.reffilter->isactive() &&
425                    !cf.reffilter->match(rm->reverse(lb->ref_pos)))
426                        continue;
427                /* }}} */
428
429                /* look up whatever string this pos is for */
430                cptmp = map->reverse(pos);
431
432                /* then lookup that string in items */
433                item_ptr = (struct itemlist *)item_hash.lookup(cptmp);
434               
435
436                /* do we have this string already? */
437                if (item_ptr == NULL)
438                {
439                        /* not seen it, make a new slot */
440                        item_ptr = &items[item_used];
441
442                        item_hash.insert(cptmp, item_ptr);
443                        item_ptr->item = pos;
444
445                        /* store the ip position in the itemlist too, just
446                        ** in case we have to look it up in show_map_line
447                        */
448                        item_ptr->ip_item = lb->ip_pos;
449
450                        item_ptr->hash = hash;
451
452                        ++item_used;
453                        item_ptr->last = now;
454                        item_ptr->first = lb->time;
455                }
456
457                /* we have the string in items now, so update stats in array */
458                item_ptr->reqcount++;
459                item_ptr->bytecount += lb->bytes;
460
461                /* we wish to count up how many times a given retcode
462                 * has occurred for this item */
463                t = (int)(lb->retcode/100);
464                item_ptr->r_codes[t].reqcount++;
465                item_ptr->r_codes[t].bytecount += lb->bytes;
466
467                if (lb->time < item_ptr->first)
468                        item_ptr->first = lb->time;
469        }
470
471        /* no further use for this hash */
472        item_hash.destroy();
473
474        /* calculate some stats; timespan, kbps, rps */
475        for(x = 0 ; x < item_used ; ++x)
476        {
477                item_ptr = &items[x];
478
479                item_ptr->timespan = item_ptr->last - item_ptr->first;
480                if (item_ptr->timespan == 0)
481                        item_ptr->timespan = 1; /* hack to avoid /0 */
482
483                for(xx = 2 ; xx < 6 ; xx++)
484                {
485                        item_ptr->r_codes[xx].rps = ((float)
486                            item_ptr->r_codes[xx].reqcount/item_ptr->timespan);
487
488                        item_ptr->r_codes[xx].bps = ((float)
489                            item_ptr->r_codes[xx].bytecount/item_ptr->timespan);
490                }
491
492                item_ptr->kbps = ((float)
493                    (item_ptr->bytecount/1024)/item_ptr->timespan);
494                item_ptr->rps = ((float)
495                    item_ptr->reqcount/item_ptr->timespan);
496        }
497
498        move(SUBMENU_LINE_NUMBER+1, 0);
499        clrtobot();
500
501        /* a suitable header, at LINES_RESERVED-1
502         * (-1 because curses starts at zero, but I've started at 1) */
503        switch(scf.numbers_mode)
504        {
505                case NUMBERS_HITS_BYTES:
506                mvaddstr(LINES_RESERVED-1, 0, " REQS REQ/S    KB KB/S");
507                break;
508
509                case NUMBERS_RETCODES: 
510                mvaddstr(LINES_RESERVED-1, 0, "  2xx   3xx   4xx  5xx");
511                break;
512        }
513
514        /* what are we showing in the table? we have a few options;
515         *
516         * top $X URLS or IPs/Hosts or Referrers
517         * detailed referrer stats for a given URL
518        */
519        if (cf.display_mode == DISPLAY_DETAIL)
520        {
521                /* detailed referrer stats for a given URL */
522        //      mvaddstr(LINES_RESERVED-1, 0, "REQS REQ/S    KB  KB/S");
523
524                /* display only the item we're interested in */
525                for (x = 0 ; x < item_used ; ++x)
526                {
527                        item_ptr = &items[x];
528                        if (item_ptr->hash == cf.selected_item_hash)
529                        {
530                                /* show stats for this line only */
531                                show_map_line(item_ptr, 0, map,
532                                    NO_INDENT, scf.numbers_mode);
533                                break;
534                        }
535                }
536                /* and return, display_sub_list can do the rest */
537                return;
538        }
539
540        /* top $X items; sort the array for display */
541        if (item_used) shellsort_wrapper(items, item_used, scf);
542
543        /* display something */
544
545        switch(scf.display_mode)
546        {
547                case DISPLAY_URLS:
548                mvaddstr(LINES_RESERVED-1, 23, "URL");
549                break;
550       
551                case DISPLAY_HOSTS:
552                mvaddstr(LINES_RESERVED-1, 23, "HOST");
553                break;
554
555                case DISPLAY_REFS:
556                mvaddstr(LINES_RESERVED-1, 23, "REFERRER");
557                break;
558        }
559
560        disp = 0; /* count how many we've shown */
561        for(x = 0, item_ptr = &items[0] ;
562            x < item_used &&  disp < LINES-LINES_RESERVED-1 ;
563            ++item_ptr, ++x)
564        {
565                /* skip empty tablespaces, even though none should exist */
566                if (item_ptr->reqcount == 0)
567                        continue;
568
569                /* render the line itself at position disp. map should be
570                 * already set from earlier in this function */
571                show_map_line(item_ptr, disp, map,
572                    NO_INDENT, scf.numbers_mode);
573
574                ++disp;
575        }
576
577        /* translate the screen position (cf.selected_item_screen) into an
578         * url/ip/ref_pos (cf.selected_item_pos), depending on current
579         * display mode. We can then use selected_item_pos to get info for
580         * the selected item if the user hits return to get it */
581        translate_screen_to_pos();
582
583#if 0
584        /* debug line */
585        mvprintw(5,0, "%s", map->reverse(cf.selected_item_pos));
586#endif
587
588        cf.current_display_size = disp;
589
590        /* if there were items, draw a marker */
591        if (cf.current_display_size)
592                drawMarker();
593        else
594        {
595                /* come to rest just above the column headers */
596                move(LINES_RESERVED-2,0);
597                refresh();
598        }
599} /* }}} */
600
601void display_sub_list(short display_mode_override, /* {{{ */
602    unsigned short offset, unsigned short limit)
603{
604        /* similar to display_list(), but this one just uses a section of
605         * the screen for displaying information pertinent to a particular
606         * URL or IP or REFERRER.
607        */
608
609        /* I'd like to lose this code and replace the breakdown screen
610         * with something more useful.. */
611
612        int x, t, xx;
613        char *cptmp;
614        unsigned int h;
615        struct config scf; /* for copying cf, see comment below at memcpy */
616
617        OAHash item_hash;
618        int item_used = 0, disp;
619        double items_size;
620        struct itemlist *subitems, *item_ptr = NULL;
621
622        /* for easy reference during walk() */
623        map *map; unsigned int pos;
624
625        /* walk() pointer */
626        struct logbits *lb;
627
628        if (c->getreqcount() == 0)
629        {
630                /* nothing to do! */
631                refresh();
632                return;
633        }
634
635        /* make an array containing all we have, then sort it */
636        items_size = c->getreqcount();
637        item_hash.create( (int)items_size * 5 );
638
639        /* this is our array; the cast is because items_size is a double but
640         * I'm fairly sure, realistically, it'll never get high enough to be
641         * a problem, so uInt should be ok */
642        //if (items) free(items); /* get rid of the last one */
643        subitems = (struct itemlist *)
644            calloc((unsigned int)items_size, sizeof(itemlist));
645       
646        /* another thread may change the contents of cf while we're running,
647         * and it would be undesirable to have most of this change on us, so
648         * we make safe copies to use */
649        memcpy(&scf, &cf, sizeof(struct config));
650
651        /* pick a map to use */
652        switch(display_mode_override)
653        {
654                default:
655                case DISPLAY_URLS: map = um; break;
656                case DISPLAY_HOSTS: map = hm; break;
657                case DISPLAY_REFS: map = rm; break;
658        }
659
660        /* walk the entire circle */
661        while(c->walk(&lb) != -1)
662        {
663                /* skip unused */
664                if (lb == NULL)
665                        continue;
666       
667                /* FILTERS? */
668
669
670                /* set up some pointers, depending on cf.display_mode, so
671                 * that we can refer to the url_pos or ip_pos via just pos,
672                 * and the urlmap or ipmap via just map
673                */
674                switch(scf.display_mode_detail)
675                {
676                        default:
677                        case DISPLAY_URLS: h = lb->url_hash; break;
678                        case DISPLAY_HOSTS: h = lb->host_hash; break;
679                        case DISPLAY_REFS: h = lb->ref_hash; break;
680                }
681
682                /* we're only interested in this circle item if it matches
683                 * the url/ip/referrer we're interested in; remember this is
684                 * a sub-list pertinent to one particular master item */
685                if (h != cf.selected_item_hash)
686                        continue;
687
688                switch(display_mode_override)
689                {
690                        default:
691                        case DISPLAY_URLS: pos = lb->url_pos; break;
692                        case DISPLAY_HOSTS: pos = lb->host_pos; break;
693                        case DISPLAY_REFS: pos = lb->ref_pos; break;
694                }
695
696                /* look up whatever string this pos is for */
697                cptmp = map->reverse(pos);
698
699                /* then lookup that string in items */
700                item_ptr = (struct itemlist *)item_hash.lookup(cptmp);
701               
702                /* do we have this string already? */
703                if (item_ptr == NULL)
704                {
705                        /* not seen it, make a new slot */
706                        item_ptr = &subitems[item_used];
707
708                        item_hash.insert(cptmp, item_ptr);
709                        item_ptr->item = pos;
710
711                        /* store the ip position in the itemlist too, just
712                        ** in case we have to look it up in show_map_line
713                        */
714                        item_ptr->ip_item = lb->ip_pos;
715
716                        item_ptr->last = now;
717                        item_ptr->first = lb->time;
718
719                        ++item_used;
720                }
721
722                /* we have the string in items now, so update stats in array */
723                ++(item_ptr->reqcount);
724                item_ptr->bytecount += lb->bytes;
725
726                /* we wish to count up how many times a given retcode
727                 * has occurred for this item */
728                t = (int)(lb->retcode/100);
729                item_ptr->r_codes[t].reqcount++;
730                item_ptr->r_codes[t].bytecount += lb->bytes;
731
732                if (lb->time < item_ptr->first)
733                        item_ptr->first = lb->time;
734        }
735
736        /* no further use for this lookup hash */
737        item_hash.destroy();
738
739        /* calculate some stats; timespan, kbps, rps */
740        for(x = 0 ; x < item_used ; ++x)
741        {
742                item_ptr = &subitems[x];
743
744                item_ptr->timespan = item_ptr->last - item_ptr->first;
745                if (item_ptr->timespan == 0)
746                        item_ptr->timespan = 1; /* hack to avoid /0 */
747
748                for(xx = 2 ; xx < 6 ; xx++)
749                {
750                        item_ptr->r_codes[xx].rps = ((float)
751                            item_ptr->r_codes[xx].reqcount/item_ptr->timespan);
752
753                        item_ptr->r_codes[xx].bps = ((float)
754                            item_ptr->r_codes[xx].bytecount/item_ptr->timespan);
755                }
756
757                item_ptr->kbps = ((float)
758                    (item_ptr->bytecount/1024)/item_ptr->timespan);
759                item_ptr->rps = ((float)
760                    item_ptr->reqcount/item_ptr->timespan);
761        }
762
763        /* top $X items; sort the array for display */
764        if (item_used) shellsort_wrapper(subitems, item_used, scf);
765
766
767        //clrtobot();
768
769        /* display something */
770
771        /* a suitable header, at offset
772         * (-1 because curses starts at zero, but I've started at 1) */
773        switch(display_mode_override)
774        {
775                case DISPLAY_URLS:
776                        mvaddstr(LINES_RESERVED+offset-1, 23, "URL");
777                        break;
778       
779                case DISPLAY_HOSTS:
780                        mvaddstr(LINES_RESERVED+offset-1, 23, "HOST");
781                        break;
782
783                case DISPLAY_REFS:
784                        mvaddstr(LINES_RESERVED+offset-1, 23, "REFERRER");
785                        break;
786        }
787
788        disp = offset; /* count how many we've shown */
789        for(x = 0, item_ptr = &subitems[0] ;
790            x < items_size  &&  disp < limit+offset ; ++item_ptr, ++x)
791        {
792                /* skip empty tablespaces, even though none should exist */
793                if (item_ptr->reqcount == 0)
794                        continue;
795
796                /* render the line itself at position disp. map should be
797                 * already set from earlier in this function */
798                show_map_line(item_ptr, disp, map, 2, scf.numbers_mode);
799
800                ++disp;
801        }
802        free(subitems);
803} /* }}} */
804
805void translate_screen_to_pos() /* {{{ */
806{
807        /* don't do anything if there's nothing on screen */
808        if (items == NULL) return;
809
810        /* convert cf.selected_item_screen to cf.selected_item_pos */
811        cf.selected_item_pos = items[cf.selected_item_screen].item;
812
813//      /* cf.selected_item_pos may well be zero */
814//      if (cf.selected_item_pos)
815//      {
816                /* make a hash of it */
817                cf.selected_item_hash =
818                   TTHash(last_display_map->reverse(cf.selected_item_pos));
819
820                cf.selected_item_mode = cf.display_mode;
821//      }
822} /* }}} */
823
824void drawMarker(void) /* update position of asterisk next to URLs {{{ */
825{
826        if (cf.current_display_size == 0)
827                return;
828
829        /* ensure our marker isn't beyond the end of the list */
830        if (cf.selected_item_screen > cf.current_display_size-1)
831                cf.selected_item_screen = cf.current_display_size-1;
832       
833        /* or above the start of it */
834        if (cf.selected_item_screen < 0) cf.selected_item_screen = 0;
835
836        /* draw an asterisk next to the selected item and clear adjacent lines*/
837        mvaddch(LINES_RESERVED+cf.selected_item_screen-1, COLS_RESERVED-3, ' ');
838        mvaddch(LINES_RESERVED+cf.selected_item_screen,
839            COLS_RESERVED-3, '*' | A_BOLD);
840        mvaddch(LINES_RESERVED+cf.selected_item_screen+1, COLS_RESERVED-3, ' ');
841
842        /* come to rest under header */
843        move(SUBMENU_LINE_NUMBER, 0);
844       
845        refresh();
846} /* }}} */
847
848
849/* render contents of item_ptr into a statistics line onscreen,
850 * at offset vert_location; pull the text portion out of m */
851void show_map_line(struct itemlist *item_ptr, int vert_location, /* {{{ */
852    map *m, unsigned short indent, unsigned short number_mode)
853{
854        if (number_mode == NUMBERS_HITS_BYTES)
855        {
856                /* start drawing at LINES_RESERVED */
857                mvprintw(LINES_RESERVED+vert_location, 0,
858                    "%5.0f %5.*f %5.*f %4.*f",
859                    item_ptr->reqcount,
860
861                    /* set 1dp if rps > 99, or 2 if not */
862                    ((item_ptr->rps > 99) ? 1 : 2),
863                    item_ptr->rps,
864
865                    /* scale KB display; if >1000K lose the decimal point */
866                    ((item_ptr->bytecount > (999*1024)) ? 0 : 1),
867                    ((float)item_ptr->bytecount/1024),
868                   
869                    /* scale KB/s display; if >1000K/s lose the decimal point */
870                    ((item_ptr->kbps > 99) ? 0 : 1),
871                    item_ptr->kbps);
872        }
873        else /* number_mode == NUMBERS_RETCODES */
874        {
875                mvprintw(LINES_RESERVED+vert_location, 0,
876                    "%5.0f %5.0f %5.0f %4.0f",
877                    item_ptr->r_codes[2].reqcount,
878                    item_ptr->r_codes[3].reqcount,
879                    item_ptr->r_codes[4].reqcount,
880                    item_ptr->r_codes[5].reqcount);
881        }
882
883        /* text after the number columns */
884
885        /* if we are displaying host/ip rather than anything else, we
886        ** have slightly more work to do; we need to generate a line of the
887        ** format host* [ip]
888        ** * only needs to be present if it's still resolving.
889        ** ip only needs to be present if list_show_ip is true.
890        */
891
892        /* 23+indent = start at 23, but move to the right by indent
893        ** spaces if we are doing, for example, a sublist
894        */
895        if (m == hm)
896        {
897                /* we are displaying a host line */
898                char *h = NULL, *i = NULL;
899                int host_item, ip_item;
900
901                /* 200 chars ought to be enough? */
902                #define MAX_IP_STR_WIDTH 200
903
904                char str[MAX_IP_STR_WIDTH];
905
906                host_item = item_ptr->item;
907                ip_item = item_ptr->ip_item;
908
909                if (host_item >= 0) h = m->reverse(host_item);
910                if (ip_item >= 0) i = im->reverse(ip_item);
911
912                if (h && i)
913                        snprintf(str, MAX_IP_STR_WIDTH, "%s [%s]", h, i);
914                else if (h)
915                        snprintf(str, MAX_IP_STR_WIDTH, "%s", h);
916                else if (i)
917                        snprintf(str, MAX_IP_STR_WIDTH, "[%s]", i);
918                else
919                        return; /* shouldn't get reached */
920
921                mvprintw(LINES_RESERVED+vert_location, 23+indent, "%.*s",
922                    (COLS-COLS_RESERVED /* width */), str);
923        }
924        else
925        {
926                char *str = m->reverse(item_ptr->item);
927                mvprintw(LINES_RESERVED+vert_location, 23+indent, "%.*s",
928                    (COLS-COLS_RESERVED /* width */), str);
929        }
930
931
932} /* }}} */
933
934void shellsort_wrapper(struct itemlist *items, unsigned int size, /* {{{ */
935    struct config pcf)
936{
937        short sort_method;
938
939        if (size == 0) return;
940
941        /* what are we displaying in the numbers column? */
942        switch(pcf.numbers_mode)
943        {
944                /* sorting by hits or bytes */
945                case NUMBERS_HITS_BYTES:
946                        sort_method = pcf.sort;
947                        break;
948               
949                /* sorting by a return code */
950                case NUMBERS_RETCODES:
951                        sort_method = pcf.retcodes_sort;
952                        break;
953        }
954
955        shellsort(items, size, sort_method);
956} /* }}} */
957void shellsort(struct itemlist *items, unsigned int size, int sorttype) /* {{{ */
958{
959        int x;
960        bool done;
961        unsigned int i, j, jmp = size;
962        double i_c, j_c;
963
964        struct itemlist tmp;
965
966        while (jmp > 1)
967        {
968                jmp >>= 1;
969                do
970                {
971                        done = true;
972                        for (j = 0; j < (size-jmp); j++)
973                        {
974                                i = j + jmp;
975                                if (cf.numbers_mode == NUMBERS_HITS_BYTES)
976                                {
977                                        /* numbers_mode = NUMBERS_HITS_BYTES */
978                                        switch(sorttype)
979                                        {
980                                                default:
981                                                case SORT_REQCOUNT:
982                                                i_c = items[i].reqcount;
983                                                j_c = items[j].reqcount;
984                                                break;
985
986                                                case SORT_REQPERSEC:
987                                                i_c = items[i].rps;
988                                                j_c = items[j].rps;
989                                                break;
990
991                                                case SORT_BYTECOUNT:
992                                                i_c = items[i].bytecount;
993                                                j_c = items[j].bytecount;
994                                                break;
995
996                                                case SORT_BYTESPERSEC:
997                                                i_c = items[i].kbps;
998                                                j_c = items[j].kbps;
999                                                break;
1000                                        }
1001                                }
1002                                else
1003                                {
1004                                        /* numbers_mode = NUMBERS_RETCODES */
1005                                        /* this is easier ;)
1006                                         * see apachetop.h for explanation
1007                                         * of what SORTTYPE_OFFSET_HACK
1008                                         * is */
1009                                        x = sorttype-SORTTYPE_OFFSET_HACK;
1010                                        i_c = items[i].r_codes[x].reqcount;
1011                                        j_c = items[j].r_codes[x].reqcount;
1012                                }
1013                               
1014                                if (i_c > j_c)
1015                                {
1016#define P_S_S sizeof(struct itemlist)
1017                                        memcpy(&tmp, &items[i], P_S_S);
1018                                        memcpy(&items[i], &items[j], P_S_S);
1019                                        memcpy(&items[j], &tmp, P_S_S);
1020
1021                                        done = false;
1022                                }
1023                        }
1024                } while (!done);
1025        }
1026} /* }}} */
1027
1028float readableNum(double num, char *suffix) /* {{{ */
1029{
1030        #define AP_TEN_KB ((double)(1024)*10)
1031        #define AP_TEN_MB ((double)(1024*1024)*10)
1032        #define AP_TEN_GB ((double)(1024*1024*1024)*10)
1033
1034        if (num > AP_TEN_GB)
1035        {
1036                *suffix = 'G';
1037                return (float)num/((double)(1024*1024*1024));
1038        }
1039        if (num > AP_TEN_MB)
1040        {
1041                *suffix = 'M';
1042                return (float)num/((double)(1024*1024));
1043        }
1044        if (num > AP_TEN_KB)
1045        {
1046                *suffix = 'K';
1047                return (float)num/1024;
1048        }
1049       
1050        *suffix = 'B';
1051        return (float)num;
1052} /* }}} */
1053
1054void display_submenu_banner(char *title, int title_len, char *banner)
1055{
1056        attron(A_REVERSE);
1057        mvaddstr(SUBMENU_LINE_NUMBER, 1, title);
1058        attroff(A_REVERSE);
1059        mvaddstr(SUBMENU_LINE_NUMBER, 1+title_len, banner);
1060        move(SUBMENU_LINE_NUMBER, 0);
1061        refresh();
1062}
1063
1064void clear_submenu_banner(void)
1065{
1066        /* remove submenu banner */
1067        move(SUBMENU_LINE_NUMBER, 0);
1068        clrtoeol();
1069        refresh();
1070}
1071
1072void display_help(void)
1073{
1074        clear();
1075
1076        move(0, 0);
1077        printw("ApacheTop version %s, Copyright (c) 2003-2004, Chris Elsworth",
1078            PACKAGE_VERSION);
1079       
1080        move(2, 0);
1081        addstr("ONE-TOUCH COMMANDS\n");
1082        addstr("d          : switch item display between urls/referrers/hosts\n");
1083        addstr("n          : switch numbers display between hits & bytes or return codes\n");
1084        addstr("h or ?     : this help window\n");
1085        addstr("p          : (un)pause display (freeze updates)\n");
1086        addstr("q          : quit ApacheTop\n");
1087        addstr("up/down    : move marker asterisk up/down\n");
1088        addstr("right/left : enter/exit detailed subdisplay mode\n");
1089        addstr("\n");
1090        addstr("SUBMENUS:\n");
1091        addstr("s:  SORT BY: [the appropriate menu will appear for your display]\n");
1092        addstr("\tr) requests  R) reqs/sec  b) bytes  B) bytes/sec\n");
1093        addstr("\t2) 2xx   3) 3xx   4) 4xx   5) 5xx\n\n");
1094        addstr("t:  TOGGLE SUBDISPLAYS ON/OFF:\n");
1095        addstr("\tu) urls  r) referrers  h) hosts\n\n");
1096        addstr("f:  MANIPULATE FILTERS:\n");
1097        addstr("\ta) add/edit menu c) clear all  s) show active (not done yet)\n");
1098        addstr("\ta:  ADD FILTER SUBMENU\n");
1099        addstr("\t\tu) to urls  r) to referrers  h) to hosts\n");
1100        addstr("\n");
1101        attron(A_REVERSE);
1102        addstr("Hit any key to continue:");
1103        attroff(A_REVERSE);
1104
1105        refresh();
1106        cf.display_paused = true;
1107}
1108
1109void display_active_filters()
1110{
1111        clear();
1112
1113        move(0, 0);
1114
1115        addstr("ApacheTop: Currently Active Filters\n");
1116
1117
1118        addstr("\n");
1119        attron(A_REVERSE);
1120        addstr("Hit any key to continue:");
1121        attroff(A_REVERSE);
1122
1123        refresh();
1124        cf.display_paused = true;
1125
1126}
1127
1128void display_histogram()
1129{
1130        int hist_height, hist_width;
1131        int i, j, age, oldest;
1132        //int y_scale;
1133        float y_scale, y_decr;
1134        float x_scale, max_bar = 0;
1135        char horiz_line[128];
1136
1137        struct logbits *lb;
1138
1139        /* histogram starts at LINES_RESERVED+10 */
1140        #define HISTOGRAM_START LINES_RESERVED+3
1141        hist_height = 10;
1142        hist_width = 60;
1143
1144        /* for every width character, figure out how many
1145         * characters high to draw the barchart
1146        */
1147        float bar_height[hist_width];
1148        char line[hist_width];
1149        for(i = 0 ; i < hist_width ; i++)
1150                bar_height[i] = 0;
1151
1152        /* figure out scales; includes divide-by-zero avoidance hack */
1153        if (cf.circle_mode == TIMED_CIRCLE)
1154                /* timed_circle = we know exactly how old we're going to get */
1155                x_scale = ((float)hist_width/cf.circle_size);
1156        else
1157                x_scale = ((float)hist_width/getMAX(now - c->oldest(), 1));
1158
1159        /* don't scale when we have less data than we have room for */
1160        if (x_scale > 1) x_scale = 1;
1161
1162        while(c->walk(&lb) != -1)
1163        {
1164                if (!lb)
1165                        continue;
1166
1167                /* we have hist_width bars, and we need to put the entire
1168                 * circle into that many bars. Devise which bar we're using
1169                 * for this particular lb->time */
1170                age = int( x_scale * (now - lb->time) );
1171
1172                /* add on x_scale; this is because if we are displaying 2
1173                 * seconds worth of data in one line, we only want to add on
1174                 * half. */
1175                bar_height[age] += x_scale;
1176        }
1177
1178        /* find the maximum bar height we have. */
1179        for(i = 0 ; i < hist_width ; ++i)
1180                max_bar = getMAX(max_bar, bar_height[i]);
1181
1182        y_scale = max_bar;
1183        y_decr = ((float)y_scale / hist_height);
1184
1185        for(i = 0 ; i < hist_height ; ++i)
1186        {
1187                if (i % 2 == 0)
1188                        mvprintw(HISTOGRAM_START + i, 0, "%3.0f", y_scale);
1189
1190                mvaddch(HISTOGRAM_START + i, 3, '|');
1191
1192                /* compose a row of hashes */
1193                memset(line, ' ', hist_width);
1194                line[hist_width] = (char)NULL;
1195                for(j = 0 ; j < hist_width ; ++j)
1196                {
1197                        if (bar_height[j] > y_scale)
1198                                line[j] = '#';
1199                }
1200                mvprintw(HISTOGRAM_START + i, 4, "%s", line);
1201
1202                y_scale -= y_decr;
1203        }
1204
1205        memset(horiz_line, '-', hist_width);
1206        horiz_line[hist_width] = (char)NULL;
1207        mvprintw(HISTOGRAM_START + hist_height, 2, "0+%*s",
1208            hist_width, horiz_line);
1209       
1210        mvprintw(HISTOGRAM_START + hist_height+1, 4, "NOW");
1211        mvprintw(HISTOGRAM_START + hist_height+1, hist_width+3, "-%ds",
1212            now - c->oldest());
1213
1214        refresh();
1215}
Note: See TracBrowser for help on using the browser.